mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement PR_{S,G}ET_CHILD_SUBREAPER.
Closes #2323 PiperOrigin-RevId: 548205854
This commit is contained in:
committed by
gVisor bot
parent
f5049f6885
commit
e7bd1b4c9c
@@ -397,6 +397,8 @@ func (t *Task) exitChildren() {
|
||||
// findReparentTargetLocked returns the task to which t's children should be
|
||||
// reparented. If no such task exists, findNewParentLocked returns nil.
|
||||
//
|
||||
// This corresponds to Linux's find_new_reaper().
|
||||
//
|
||||
// Preconditions: The TaskSet mutex must be locked.
|
||||
func (t *Task) findReparentTargetLocked() *Task {
|
||||
// Reparent to any sibling in the same thread group that hasn't begun
|
||||
@@ -404,12 +406,35 @@ func (t *Task) findReparentTargetLocked() *Task {
|
||||
if t2 := t.tg.anyNonExitingTaskLocked(); t2 != nil {
|
||||
return t2
|
||||
}
|
||||
// "A child process that is orphaned within the namespace will be
|
||||
// reparented to [the init process for the namespace] ..." -
|
||||
// pid_namespaces(7)
|
||||
if init := t.tg.pidns.tasks[InitTID]; init != nil {
|
||||
return init.tg.anyNonExitingTaskLocked()
|
||||
|
||||
if !t.tg.hasChildSubreaper {
|
||||
// No child subreaper exists. We can immediately return the
|
||||
// init process in this PID namespace if it exists.
|
||||
if init := t.tg.pidns.tasks[initTID]; init != nil {
|
||||
return init.tg.anyNonExitingTaskLocked()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Walk up the process tree until we either find a subreaper, or we hit
|
||||
// the init process in the PID namespace.
|
||||
for parent := t.parent; parent != nil; parent = parent.parent {
|
||||
if parent.tg.isInitInLocked(parent.PIDNamespace()) {
|
||||
// We found the init process for this pid namespace,
|
||||
// return a task from it. If the init process is
|
||||
// exiting, this might return nil.
|
||||
return parent.tg.anyNonExitingTaskLocked()
|
||||
}
|
||||
if parent.tg.isChildSubreaper {
|
||||
// We found a subreaper process. Return a non-exiting
|
||||
// task if there is one, otherwise keep walking up the
|
||||
// process tree.
|
||||
if target := parent.tg.anyNonExitingTaskLocked(); target != nil {
|
||||
return target
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -258,6 +258,12 @@ func (ts *TaskSet) newTask(ctx context.Context, cfg *TaskConfig) (*Task, error)
|
||||
tg.processGroup = parentPG
|
||||
tg.tty = t.parent.tg.tty
|
||||
}
|
||||
|
||||
// If our parent is a child subreaper, or if it has a child
|
||||
// subreaper, then this new thread group does as well.
|
||||
if t.parent != nil {
|
||||
tg.hasChildSubreaper = t.parent.tg.isChildSubreaper || t.parent.tg.hasChildSubreaper
|
||||
}
|
||||
}
|
||||
tg.tasks.PushBack(t)
|
||||
tg.tasksCount++
|
||||
@@ -330,7 +336,7 @@ func (ns *PIDNamespace) allocateTID() (ThreadID, error) {
|
||||
// Next.
|
||||
tid++
|
||||
if tid > TasksLimit {
|
||||
tid = InitTID + 1
|
||||
tid = initTID + 1
|
||||
}
|
||||
|
||||
// Is it available?
|
||||
|
||||
@@ -250,6 +250,21 @@ type ThreadGroup struct {
|
||||
// currently not used but is maintained for consistency.
|
||||
// TODO(gvisor.dev/issue/1967)
|
||||
oomScoreAdj atomicbitops.Int32
|
||||
|
||||
// isChildSubreaper and hasChildSubreaper correspond to Linux's
|
||||
// signal_struct::is_child_subreaper and has_child_subreaper.
|
||||
//
|
||||
// Both fields are protected by the TaskSet mutex.
|
||||
//
|
||||
// Quoting from signal.h:
|
||||
// "PR_SET_CHILD_SUBREAPER marks a process, like a service manager, to
|
||||
// re-parent orphan (double-forking) child processes to this process
|
||||
// instead of 'init'. The service manager is able to receive SIGCHLD
|
||||
// signals and is able to investigate the process until it calls
|
||||
// wait(). All children of this process will inherit a flag if they
|
||||
// should look for a child_subreaper process at exit"
|
||||
isChildSubreaper bool
|
||||
hasChildSubreaper bool
|
||||
}
|
||||
|
||||
// NewThreadGroup returns a new, empty thread group in PID namespace pidns. The
|
||||
@@ -318,10 +333,30 @@ func (tg *ThreadGroup) Release(ctx context.Context) {
|
||||
//
|
||||
// Precondition: TaskSet.mu must be held.
|
||||
func (tg *ThreadGroup) forEachChildThreadGroupLocked(fn func(*ThreadGroup)) {
|
||||
tg.walkDescendantThreadGroupsLocked(func(child *ThreadGroup) bool {
|
||||
fn(child)
|
||||
// Don't recurse below the immediate children.
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
// walkDescendantThreadGroupsLocked recursively walks all descendent
|
||||
// ThreadGroups and executes the visitor function. If visitor returns false for
|
||||
// a given ThreadGroup, then that ThreadGroups descendants are excluded from
|
||||
// further iteration.
|
||||
//
|
||||
// This corresponds to Linux's walk_process_tree.
|
||||
//
|
||||
// Precondition: TaskSet.mu must be held.
|
||||
func (tg *ThreadGroup) walkDescendantThreadGroupsLocked(visitor func(*ThreadGroup) bool) {
|
||||
for t := tg.tasks.Front(); t != nil; t = t.Next() {
|
||||
for child := range t.children {
|
||||
if child == child.tg.leader {
|
||||
fn(child.tg)
|
||||
if !visitor(child.tg) {
|
||||
// Don't recurse below child.
|
||||
continue
|
||||
}
|
||||
child.tg.walkDescendantThreadGroupsLocked(visitor)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -512,6 +547,53 @@ func (tg *ThreadGroup) SetForegroundProcessGroupID(tty *TTY, pgid ProcessGroupID
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetChildSubreaper marks this ThreadGroup sets the isChildSubreaper field on
|
||||
// this ThreadGroup, and marks all child ThreadGroups as having a subreaper.
|
||||
// Recursion stops if we find another subreaper process, which is either a
|
||||
// ThreadGroup with isChildSubreaper bit set, or a ThreadGroup with PID=1
|
||||
// inside a PID namespace.
|
||||
func (tg *ThreadGroup) SetChildSubreaper(isSubreaper bool) {
|
||||
ts := tg.TaskSet()
|
||||
ts.mu.Lock()
|
||||
defer ts.mu.Unlock()
|
||||
tg.isChildSubreaper = isSubreaper
|
||||
tg.walkDescendantThreadGroupsLocked(func(child *ThreadGroup) bool {
|
||||
// Is this child PID 1 in its PID namespace, or already a
|
||||
// subreaper?
|
||||
if child.isInitInLocked(child.PIDNamespace()) || child.isChildSubreaper {
|
||||
// Don't set hasChildSubreaper, and don't recurse.
|
||||
return false
|
||||
}
|
||||
child.hasChildSubreaper = isSubreaper
|
||||
return true // Recurse.
|
||||
})
|
||||
}
|
||||
|
||||
// IsChildSubreaper returns whether this ThreadGroup is a child subreaper.
|
||||
func (tg *ThreadGroup) IsChildSubreaper() bool {
|
||||
ts := tg.TaskSet()
|
||||
ts.mu.RLock()
|
||||
defer ts.mu.RUnlock()
|
||||
return tg.isChildSubreaper
|
||||
}
|
||||
|
||||
// IsInitIn returns whether this ThreadGroup has TID 1 int the given
|
||||
// PIDNamespace.
|
||||
func (tg *ThreadGroup) IsInitIn(pidns *PIDNamespace) bool {
|
||||
ts := tg.TaskSet()
|
||||
ts.mu.RLock()
|
||||
defer ts.mu.RUnlock()
|
||||
return tg.isInitInLocked(pidns)
|
||||
}
|
||||
|
||||
// isInitInLocked returns whether this ThreadGroup has TID 1 in the given
|
||||
// PIDNamespace.
|
||||
//
|
||||
// Preconditions: TaskSet.mu must be locked.
|
||||
func (tg *ThreadGroup) isInitInLocked(pidns *PIDNamespace) bool {
|
||||
return pidns.tgids[tg] == initTID
|
||||
}
|
||||
|
||||
// itimerRealListener implements ktime.Listener for ITIMER_REAL expirations.
|
||||
//
|
||||
// +stateify savable
|
||||
|
||||
@@ -46,11 +46,11 @@ func (tid ThreadID) String() string {
|
||||
return fmt.Sprintf("%d", tid)
|
||||
}
|
||||
|
||||
// InitTID is the TID given to the first task added to each PID namespace. The
|
||||
// thread group led by InitTID is called the namespace's init process. The
|
||||
// initTID is the TID given to the first task added to each PID namespace. The
|
||||
// thread group led by initTID is called the namespace's init process. The
|
||||
// death of a PID namespace's init process causes all tasks visible in that
|
||||
// namespace to be killed.
|
||||
const InitTID ThreadID = 1
|
||||
const initTID ThreadID = 1
|
||||
|
||||
// A TaskSet comprises all tasks in a system.
|
||||
//
|
||||
|
||||
@@ -225,17 +225,17 @@ func Prctl(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr,
|
||||
case linux.PR_SET_CHILD_SUBREAPER:
|
||||
// "If arg2 is nonzero, set the "child subreaper" attribute of
|
||||
// the calling process; if arg2 is zero, unset the attribute."
|
||||
//
|
||||
// TODO(gvisor.dev/issues/2323): We only support setting, and
|
||||
// only if the task is already TID 1 in the PID namespace,
|
||||
// because it already acts as a subreaper in that case.
|
||||
isPid1 := t.PIDNamespace().IDOfTask(t) == kernel.InitTID
|
||||
if args[1].Int() != 0 && isPid1 {
|
||||
return 0, nil, nil
|
||||
}
|
||||
isSubreaper := args[1].Int() != 0
|
||||
t.ThreadGroup().SetChildSubreaper(isSubreaper)
|
||||
return 0, nil, nil
|
||||
|
||||
t.Kernel().EmitUnimplementedEvent(t, sysno)
|
||||
return 0, nil, linuxerr.EINVAL
|
||||
case linux.PR_GET_CHILD_SUBREAPER:
|
||||
var isSubreaper int32
|
||||
if t.ThreadGroup().IsChildSubreaper() {
|
||||
isSubreaper = 1
|
||||
}
|
||||
_, err := primitive.CopyInt32Out(t, args[1].Pointer(), isSubreaper)
|
||||
return 0, nil, err
|
||||
|
||||
case linux.PR_GET_TIMING,
|
||||
linux.PR_SET_TIMING,
|
||||
@@ -248,7 +248,6 @@ func Prctl(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr,
|
||||
linux.PR_MCE_KILL,
|
||||
linux.PR_MCE_KILL_GET,
|
||||
linux.PR_GET_TID_ADDRESS,
|
||||
linux.PR_GET_CHILD_SUBREAPER,
|
||||
linux.PR_GET_THP_DISABLE,
|
||||
linux.PR_SET_THP_DISABLE,
|
||||
linux.PR_MPX_ENABLE_MANAGEMENT,
|
||||
|
||||
@@ -110,7 +110,8 @@ func Kill(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *
|
||||
if tg == t.ThreadGroup() {
|
||||
continue
|
||||
}
|
||||
if t.PIDNamespace().IDOfThreadGroup(tg) == kernel.InitTID {
|
||||
// Don't send the signal to the init process in t's PID namespace.
|
||||
if tg.IsInitIn(t.PIDNamespace()) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -11,14 +11,19 @@ go_binary(
|
||||
srcs = [
|
||||
"fds.go",
|
||||
"main.go",
|
||||
"zombies.go",
|
||||
],
|
||||
static = True,
|
||||
visibility = ["//runsc/container:__pkg__"],
|
||||
visibility = [
|
||||
"//runsc/container:__pkg__",
|
||||
"//test/syscalls/linux:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
"//pkg/test/testutil",
|
||||
"//pkg/unet",
|
||||
"//runsc/flag",
|
||||
"@com_github_google_subcommands//:go_default_library",
|
||||
"@com_github_kr_pty//:go_default_library",
|
||||
"@org_golang_x_sys//unix:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -46,12 +46,13 @@ func main() {
|
||||
subcommands.Register(new(fdReceiver), "")
|
||||
subcommands.Register(new(fdSender), "")
|
||||
subcommands.Register(new(forkBomb), "")
|
||||
subcommands.Register(new(fsTreeCreator), "")
|
||||
subcommands.Register(new(ptyRunner), "")
|
||||
subcommands.Register(new(reaper), "")
|
||||
subcommands.Register(new(syscall), "")
|
||||
subcommands.Register(new(taskTree), "")
|
||||
subcommands.Register(new(uds), "")
|
||||
subcommands.Register(new(fsTreeCreator), "")
|
||||
subcommands.Register(new(zombieTest), "")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
// Copyright 2021 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
sys "syscall"
|
||||
"time"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/runsc/flag"
|
||||
)
|
||||
|
||||
func fatalf(s string, args ...any) {
|
||||
fmt.Fprintf(os.Stderr, s+"\n", args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// zombieTest creates an orphaned process that will be reparented to PID 1
|
||||
// (or the nearest subreaper) and expect that it is reaped.
|
||||
//
|
||||
// The setup involves three different processes:
|
||||
//
|
||||
// 1. The zombiemonitor process starts the zombieparent process and reads the
|
||||
// zombiechild process pid from zombieparent's stdout. It waits on
|
||||
// zombieparent, and after that dies, zombiechild will be reparented to PID 1
|
||||
// (or nearest subreaper). The zombiemonitor kills zombiechild and expects
|
||||
// that it will be reaped.
|
||||
//
|
||||
// 2. The zombieparent process starts the zombiechild process, writes the
|
||||
// zombiechild process pid to stdout, and exits, causing zombiechild to be
|
||||
// reparented to PID 1 (or nearest subreaper).
|
||||
//
|
||||
// 3. zombiechild just waits until it is killed.
|
||||
type zombieTest struct{}
|
||||
|
||||
// Name implements subcommands.Command.Name.
|
||||
func (*zombieTest) Name() string {
|
||||
return "zombie_test"
|
||||
}
|
||||
|
||||
// Synopsis implements subcommands.Command.Synopsys.
|
||||
func (*zombieTest) Synopsis() string {
|
||||
return "creates an orphaned grandchild and expects to be reparented and reaped."
|
||||
}
|
||||
|
||||
// Usage implements subcommands.Command.Usage.
|
||||
func (*zombieTest) Usage() string {
|
||||
return "Usage: zombie_test [zombieparent|zombiechild]"
|
||||
}
|
||||
|
||||
// SetFlags implements subcommands.Command.SetFlags.
|
||||
func (*zombieTest) SetFlags(f *flag.FlagSet) {}
|
||||
|
||||
// Execute implements subcommands.Command.Execute.
|
||||
func (zt *zombieTest) Execute(ctx context.Context, f *flag.FlagSet, args ...any) subcommands.ExitStatus {
|
||||
n := f.NArg()
|
||||
if n > 1 {
|
||||
log.Fatal(zt.Usage())
|
||||
}
|
||||
if n == 0 {
|
||||
// Run the monitor, which is the main entrypoint of this program.
|
||||
runOrphanMonitor()
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
// One argument passed
|
||||
switch f.Arg(0) {
|
||||
case "zombieparent":
|
||||
runZombieParent()
|
||||
case "zombiechild":
|
||||
runZombieChild()
|
||||
default:
|
||||
log.Fatal(zt.Usage())
|
||||
}
|
||||
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
|
||||
func runOrphanMonitor() {
|
||||
// Start the zombieparent and read its output. The call to
|
||||
// CombinedOutput() will wait() on zombieparent, so when it returns we
|
||||
// know that zombiechild has been orphaned and reparented to PID 1.
|
||||
cmd := exec.Command("/proc/self/exe", "zombie_test", "zombieparent")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to exec zombieparent: %v\noutput: %s\n", err, string(out))
|
||||
}
|
||||
|
||||
// Parse zombiechild pid from zombieparent output.
|
||||
zombieChildPid, err := strconv.Atoi(strings.TrimSpace(string(out)))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to parse zombieparent output: %q", string(out))
|
||||
}
|
||||
fmt.Printf("started zombiechild with pid %d\n", zombieChildPid)
|
||||
|
||||
// Kill the zombiechild.
|
||||
fmt.Printf("killing zombiechild\n")
|
||||
if err := unix.Kill(zombieChildPid, unix.SIGTERM); err != nil {
|
||||
log.Fatalf("error killing for zombiechild: %v", err)
|
||||
}
|
||||
|
||||
// Wait for zombiechild to be reaped by PID 1.
|
||||
if err := waitForZombieReaped(zombieChildPid, 10*time.Second); err != nil {
|
||||
log.Fatalf("error waiting for zombiechild to be reaped: %v", err)
|
||||
}
|
||||
fmt.Printf("zombiechild has been reaped\n")
|
||||
|
||||
// Success.
|
||||
}
|
||||
|
||||
func runZombieParent() {
|
||||
// Start the zombiechild, and write the pid to stdout.
|
||||
cmd := exec.Command("/proc/self/exe", "zombie_test", "zombiechild")
|
||||
if err := cmd.Start(); err != nil {
|
||||
log.Fatalf("failed to exec zombiechild: %v", err)
|
||||
}
|
||||
fmt.Fprint(os.Stdout, strconv.Itoa(cmd.Process.Pid))
|
||||
|
||||
// Die. This will cause zombiechild to be reparented.
|
||||
}
|
||||
|
||||
func runZombieChild() {
|
||||
// Sleep for a long time. We will be killed before this exits.
|
||||
time.Sleep(1 * time.Minute)
|
||||
}
|
||||
|
||||
// waitForZombieReaped sends a harmless signal to the given pid until it gets
|
||||
// ESRCH, indicating that the process has been reaped, or until the timeout is
|
||||
// reached.
|
||||
func waitForZombieReaped(pid int, timeout time.Duration) error {
|
||||
deadline := time.Now().Add(timeout)
|
||||
for {
|
||||
if time.Now().After(deadline) {
|
||||
return fmt.Errorf("pid %d was not reaped after %v", pid, timeout)
|
||||
}
|
||||
|
||||
err := unix.Kill(pid, 0)
|
||||
if err == nil {
|
||||
fmt.Printf("pid %d still exists\n", pid)
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
if errno := err.(sys.Errno); errno != unix.ESRCH {
|
||||
return fmt.Errorf("unexpected error signalling pid %d: %v", pid, err)
|
||||
}
|
||||
fmt.Printf("pid %d has been reaped\n", pid)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1714,6 +1714,7 @@ cc_binary(
|
||||
name = "prctl_test",
|
||||
testonly = 1,
|
||||
srcs = ["prctl.cc"],
|
||||
data = ["//test/cmd/test_app"],
|
||||
linkstatic = 1,
|
||||
deps = [
|
||||
"//test/util:capability_util",
|
||||
@@ -1722,6 +1723,7 @@ cc_binary(
|
||||
gtest,
|
||||
"//test/util:multiprocess_util",
|
||||
"//test/util:posix_error",
|
||||
"//test/util:signal_util",
|
||||
"//test/util:test_util",
|
||||
"//test/util:thread_util",
|
||||
],
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "test/util/cleanup.h"
|
||||
#include "test/util/multiprocess_util.h"
|
||||
#include "test/util/posix_error.h"
|
||||
#include "test/util/signal_util.h"
|
||||
#include "test/util/test_util.h"
|
||||
#include "test/util/thread_util.h"
|
||||
|
||||
@@ -214,10 +215,96 @@ TEST(PrctlTest, RootDumpability) {
|
||||
SyscallFailsWithErrno(EINVAL));
|
||||
}
|
||||
|
||||
TEST(PrctlTest, SetGetSubreaper) {
|
||||
// Setting subreaper on PID 1 works vacuously because PID 1 is always a
|
||||
// subreaper.
|
||||
TEST(PrctlTest, SimpleSetGetChildSubreaper) {
|
||||
// Tasks start off not subreaper.
|
||||
int is_subreaper = 0;
|
||||
EXPECT_THAT(prctl(PR_GET_CHILD_SUBREAPER, &is_subreaper), SyscallSucceeds());
|
||||
EXPECT_EQ(is_subreaper, 0);
|
||||
|
||||
// Set to 1.
|
||||
EXPECT_THAT(prctl(PR_SET_CHILD_SUBREAPER, 1), SyscallSucceeds());
|
||||
EXPECT_THAT(prctl(PR_GET_CHILD_SUBREAPER, &is_subreaper), SyscallSucceeds());
|
||||
EXPECT_EQ(is_subreaper, 1);
|
||||
|
||||
// Set to something positive but not 1.
|
||||
EXPECT_THAT(prctl(PR_SET_CHILD_SUBREAPER, 42), SyscallSucceeds());
|
||||
// Get still returns 1.
|
||||
EXPECT_THAT(prctl(PR_GET_CHILD_SUBREAPER, &is_subreaper), SyscallSucceeds());
|
||||
EXPECT_EQ(is_subreaper, 1);
|
||||
|
||||
// Set to something negative.
|
||||
EXPECT_THAT(prctl(PR_SET_CHILD_SUBREAPER, -42), SyscallSucceeds());
|
||||
// Get still returns 1.
|
||||
EXPECT_THAT(prctl(PR_GET_CHILD_SUBREAPER, &is_subreaper), SyscallSucceeds());
|
||||
EXPECT_EQ(is_subreaper, 1);
|
||||
}
|
||||
|
||||
TEST(PrctlTest, ThreadsInheritChildSubreaperBit) {
|
||||
// Set child subreaper bit.
|
||||
ASSERT_THAT(prctl(PR_SET_CHILD_SUBREAPER, 1), SyscallSucceeds());
|
||||
ScopedThread thread([&] {
|
||||
int is_subreaper = 0;
|
||||
ASSERT_THAT(prctl(PR_GET_CHILD_SUBREAPER, &is_subreaper),
|
||||
SyscallSucceeds());
|
||||
EXPECT_EQ(is_subreaper, 1);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(PrctlTest, ProcessesDoNotInheritChildSubreaperBit) {
|
||||
// Set child subreaper bit.
|
||||
ASSERT_THAT(prctl(PR_SET_CHILD_SUBREAPER, 1), SyscallSucceeds());
|
||||
|
||||
const auto rest = [&] {
|
||||
int is_subreaper = 0;
|
||||
TEST_CHECK_SUCCESS(prctl(PR_GET_CHILD_SUBREAPER, &is_subreaper));
|
||||
TEST_CHECK(is_subreaper == 0);
|
||||
};
|
||||
|
||||
EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
|
||||
}
|
||||
|
||||
static std::atomic<bool> got_sigchild;
|
||||
|
||||
void sigchild_handler(int sig, siginfo_t* siginfo, void* arg) {
|
||||
got_sigchild = true;
|
||||
}
|
||||
|
||||
TEST(PrctlTest, OrphansReparentedToSubreaper) {
|
||||
// Set the subreaper bit.
|
||||
ASSERT_THAT(prctl(PR_SET_CHILD_SUBREAPER, 1), SyscallSucceeds());
|
||||
|
||||
// Set up a signal handler to listen for reparented children.
|
||||
struct sigaction sa = {};
|
||||
sa.sa_sigaction = sigchild_handler;
|
||||
sigfillset(&sa.sa_mask);
|
||||
auto const sig_cleanup =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGCHLD, sa));
|
||||
|
||||
// Execute the test_app zombie_test, which will create an orphaned process
|
||||
// and expect that it is reaped.
|
||||
constexpr char kTestApp[] = "test/cmd/test_app/test_app";
|
||||
const std::string path = RunfilePath(kTestApp);
|
||||
int execve_errno;
|
||||
pid_t pid;
|
||||
auto exec_cleanup = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
ForkAndExec(path, {path, "zombie_test"}, {}, &pid, &execve_errno));
|
||||
ASSERT_EQ(execve_errno, 0);
|
||||
|
||||
// Wait for 2 children: the process we just started, and the orphan that will
|
||||
// be reparented to us.
|
||||
for (int i = 0; i < 2; i++) {
|
||||
int status;
|
||||
int wait_pid;
|
||||
ASSERT_THAT(wait_pid = RetryEINTR(waitpid)(-1, &status, 0),
|
||||
SyscallSucceeds());
|
||||
if (wait_pid == pid) {
|
||||
// Test app should have exited cleanly.
|
||||
EXPECT_EQ(status, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// We should have gotten a SIGCHILD for the reparented orphan.
|
||||
EXPECT_TRUE(got_sigchild);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -39,7 +39,28 @@ std::string RunfilePath(std::string path) {
|
||||
return JoinPath("__main__", path);
|
||||
}
|
||||
|
||||
return runfiles->Rlocation(JoinPath("__main__", path));
|
||||
// Try to resolve the path as it was passed to us, and check that it exists
|
||||
// before returning.
|
||||
std::string runfile_path = runfiles->Rlocation(JoinPath("__main__", path));
|
||||
struct stat st = {};
|
||||
if (!runfile_path.empty() && stat(runfile_path.c_str(), &st) == 0) {
|
||||
// Found it.
|
||||
return runfile_path;
|
||||
}
|
||||
|
||||
// You are not gonna like this, but go_binary data dependencies have an extra
|
||||
// directory name with a "_" suffix, so we must check for that path too.
|
||||
//
|
||||
// For example, a go_binary with name"//foo/bar:baz" will be placed in
|
||||
// "<runfiles_dir>/foo/bar/baz_/baz".
|
||||
//
|
||||
// See
|
||||
// https://github.com/bazelbuild/rules_go/blob/d2a3cf2d6b18f5be19adccc6a6806e0c3b8c410b/go/private/context.bzl#L137.
|
||||
absl::string_view dirname = Dirname(path);
|
||||
absl::string_view basename = Basename(path);
|
||||
std::string go_binary_path =
|
||||
JoinPath(dirname, absl::StrCat(basename, "_"), basename);
|
||||
return runfiles->Rlocation(JoinPath("__main__", go_binary_path));
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
|
||||
Reference in New Issue
Block a user