summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--runsc/boot/loader.go16
-rw-r--r--runsc/cmd/kill.go17
-rw-r--r--runsc/container/container.go32
-rw-r--r--runsc/container/container_test.go58
-rw-r--r--runsc/container/multi_container_test.go12
-rw-r--r--runsc/container/test_app.go10
6 files changed, 124 insertions, 21 deletions
diff --git a/runsc/boot/loader.go b/runsc/boot/loader.go
index 0a3f67774..fa169d090 100644
--- a/runsc/boot/loader.go
+++ b/runsc/boot/loader.go
@@ -756,8 +756,22 @@ func (l *Loader) signalProcess(cid string, pid, signo int32, sendToFGProcess boo
ep, ok := l.processes[eid]
l.mu.Unlock()
+ // The caller may be signaling a process not started directly via exec.
+ // In this case, find the process in the container's PID namespace and
+ // signal it.
if !ok {
- return fmt.Errorf("failed to signal container %q PID %d: no such PID", cid, pid)
+ ep, ok := l.processes[execID{cid: cid}]
+ if !ok {
+ return fmt.Errorf("no container with ID: %q", cid)
+ }
+ tg := ep.tg.PIDNamespace().ThreadGroupWithID(kernel.ThreadID(pid))
+ if tg == nil {
+ return fmt.Errorf("failed to signal container %q PID %d: no such process", cid, pid)
+ }
+ if tg.Leader().ContainerID() != cid {
+ return fmt.Errorf("process %d is part of a different container: %q", pid, tg.Leader().ContainerID())
+ }
+ return tg.SendSignal(&arch.SignalInfo{Signo: signo})
}
if !sendToFGProcess {
diff --git a/runsc/cmd/kill.go b/runsc/cmd/kill.go
index dcb2988e3..7a98d10a2 100644
--- a/runsc/cmd/kill.go
+++ b/runsc/cmd/kill.go
@@ -31,6 +31,7 @@ import (
// Kill implements subcommands.Command for the "kill" command.
type Kill struct {
all bool
+ pid int
}
// Name implements subcommands.Command.Name.
@@ -51,6 +52,7 @@ func (*Kill) Usage() string {
// SetFlags implements subcommands.Command.SetFlags.
func (k *Kill) SetFlags(f *flag.FlagSet) {
f.BoolVar(&k.all, "all", false, "send the specified signal to all processes inside the container")
+ f.IntVar(&k.pid, "pid", 0, "send the specified signal to a specific process")
}
// Execute implements subcommands.Command.Execute.
@@ -63,6 +65,10 @@ func (k *Kill) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
id := f.Arg(0)
conf := args[0].(*boot.Config)
+ if k.pid != 0 && k.all {
+ Fatalf("it is invalid to specify both --all and --pid")
+ }
+
c, err := container.Load(conf.RootDir, id)
if err != nil {
Fatalf("error loading container: %v", err)
@@ -80,8 +86,15 @@ func (k *Kill) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
if err != nil {
Fatalf("%v", err)
}
- if err := c.Signal(sig, k.all); err != nil {
- Fatalf("%v", err)
+
+ if k.pid != 0 {
+ if err := c.SignalProcess(sig, int32(k.pid)); err != nil {
+ Fatalf("failed to signal pid %d: %v", k.pid, err)
+ }
+ } else {
+ if err := c.SignalContainer(sig, k.all); err != nil {
+ Fatalf("%v", err)
+ }
}
return subcommands.ExitSuccess
}
diff --git a/runsc/container/container.go b/runsc/container/container.go
index 774cb6e07..0ec4d03c1 100644
--- a/runsc/container/container.go
+++ b/runsc/container/container.go
@@ -174,7 +174,7 @@ func Load(rootDir, id string) (*Container, error) {
} else if c.Status == Running {
// Container state should reflect the actual state of the application, so
// we don't consider gofer process here.
- if err := c.Signal(syscall.Signal(0), false); err != nil {
+ if err := c.SignalContainer(syscall.Signal(0), false); err != nil {
c.changeStatus(Stopped)
}
}
@@ -445,7 +445,7 @@ func (c *Container) SandboxPid() int {
func (c *Container) Wait() (syscall.WaitStatus, error) {
log.Debugf("Wait on container %q", c.ID)
if !c.isSandboxRunning() {
- return 0, fmt.Errorf("container is not running")
+ return 0, fmt.Errorf("sandbox is not running")
}
return c.Sandbox.Wait(c.ID)
}
@@ -455,7 +455,7 @@ func (c *Container) Wait() (syscall.WaitStatus, error) {
func (c *Container) WaitRootPID(pid int32, clearStatus bool) (syscall.WaitStatus, error) {
log.Debugf("Wait on PID %d in sandbox %q", pid, c.Sandbox.ID)
if !c.isSandboxRunning() {
- return 0, fmt.Errorf("container is not running")
+ return 0, fmt.Errorf("sandbox is not running")
}
return c.Sandbox.WaitPID(c.Sandbox.ID, pid, clearStatus)
}
@@ -465,16 +465,16 @@ func (c *Container) WaitRootPID(pid int32, clearStatus bool) (syscall.WaitStatus
func (c *Container) WaitPID(pid int32, clearStatus bool) (syscall.WaitStatus, error) {
log.Debugf("Wait on PID %d in container %q", pid, c.ID)
if !c.isSandboxRunning() {
- return 0, fmt.Errorf("container is not running")
+ return 0, fmt.Errorf("sandbox is not running")
}
return c.Sandbox.WaitPID(c.ID, pid, clearStatus)
}
-// Signal sends the signal to the container. If all is true and signal is
-// SIGKILL, then waits for all processes to exit before returning.
-// Signal returns an error if the container is already stopped.
+// SignalContainer sends the signal to the container. If all is true and signal
+// is SIGKILL, then waits for all processes to exit before returning.
+// SignalContainer returns an error if the container is already stopped.
// TODO: Distinguish different error types.
-func (c *Container) Signal(sig syscall.Signal, all bool) error {
+func (c *Container) SignalContainer(sig syscall.Signal, all bool) error {
log.Debugf("Signal container %q: %v", c.ID, sig)
// Signaling container in Stopped state is allowed. When all=false,
// an error will be returned anyway; when all=true, this allows
@@ -485,11 +485,23 @@ func (c *Container) Signal(sig syscall.Signal, all bool) error {
return err
}
if !c.isSandboxRunning() {
- return fmt.Errorf("container is not running")
+ return fmt.Errorf("sandbox is not running")
}
return c.Sandbox.SignalContainer(c.ID, sig, all)
}
+// SignalProcess sends sig to a specific process in the container.
+func (c *Container) SignalProcess(sig syscall.Signal, pid int32) error {
+ log.Debugf("Signal process %d in container %q: %v", pid, c.ID, sig)
+ if err := c.requireStatus("signal a process inside", Running); err != nil {
+ return err
+ }
+ if !c.isSandboxRunning() {
+ return fmt.Errorf("sandbox is not running")
+ }
+ return c.Sandbox.SignalProcess(c.ID, int32(pid), sig, false)
+}
+
// ForwardSignals forwards all signals received by the current process to the
// container process inside the sandbox. It returns a function that will stop
// forwarding signals.
@@ -663,7 +675,7 @@ func (c *Container) waitForStopped() error {
b := backoff.WithContext(backoff.NewConstantBackOff(100*time.Millisecond), ctx)
op := func() error {
if c.isSandboxRunning() {
- if err := c.Signal(syscall.Signal(0), false); err == nil {
+ if err := c.SignalContainer(syscall.Signal(0), false); err == nil {
return fmt.Errorf("container is still running")
}
}
diff --git a/runsc/container/container_test.go b/runsc/container/container_test.go
index 94572667e..d9cd38c0a 100644
--- a/runsc/container/container_test.go
+++ b/runsc/container/container_test.go
@@ -354,7 +354,7 @@ func TestLifecycle(t *testing.T) {
<-ch
time.Sleep(100 * time.Millisecond)
// Send the container a SIGTERM which will cause it to stop.
- if err := c.Signal(syscall.SIGTERM, false); err != nil {
+ if err := c.SignalContainer(syscall.SIGTERM, false); err != nil {
t.Fatalf("error sending signal %v to container: %v", syscall.SIGTERM, err)
}
// Wait for it to die.
@@ -559,6 +559,62 @@ func TestExec(t *testing.T) {
}
}
+// TestKillPid verifies that we can signal individual exec'd processes.
+func TestKillPid(t *testing.T) {
+ for _, conf := range configs(overlay) {
+ t.Logf("Running test with conf: %+v", conf)
+
+ app, err := testutil.FindFile("runsc/container/test_app")
+ if err != nil {
+ t.Fatal("error finding test_app:", err)
+ }
+
+ const nProcs = 4
+ spec := testutil.NewSpecWithArgs(app, "task-tree", "--depth", strconv.Itoa(nProcs-1), "--width=1", "--pause=true")
+ rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
+ if err != nil {
+ t.Fatalf("error setting up container: %v", err)
+ }
+ defer os.RemoveAll(rootDir)
+ defer os.RemoveAll(bundleDir)
+
+ // Create and start the container.
+ cont, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "", "")
+ if err != nil {
+ t.Fatalf("error creating container: %v", err)
+ }
+ defer cont.Destroy()
+ if err := cont.Start(conf); err != nil {
+ t.Fatalf("error starting container: %v", err)
+ }
+
+ // Verify that all processes are running.
+ if err := waitForProcessCount(cont, nProcs); err != nil {
+ t.Fatalf("timed out waiting for processes to start: %v", err)
+ }
+
+ // Kill the child process with the largest PID.
+ procs, err := cont.Processes()
+ if err != nil {
+ t.Fatalf("failed to get process list: %v", err)
+ }
+ var pid int32
+ for _, p := range procs {
+ if pid < int32(p.PID) {
+ pid = int32(p.PID)
+ }
+ }
+ if err := cont.SignalProcess(syscall.SIGKILL, pid); err != nil {
+ t.Fatalf("failed to signal process %d: %v", pid, err)
+ }
+
+ // Verify that one process is gone.
+ if err := waitForProcessCount(cont, nProcs-1); err != nil {
+ t.Fatal(err)
+ }
+ }
+}
+
// TestCheckpointRestore creates a container that continuously writes successive integers
// to a file. To test checkpoint and restore functionality, the container is
// checkpointed and the last number printed to the file is recorded. Then, it is restored in two
diff --git a/runsc/container/multi_container_test.go b/runsc/container/multi_container_test.go
index 77f8da8b0..1781a4602 100644
--- a/runsc/container/multi_container_test.go
+++ b/runsc/container/multi_container_test.go
@@ -335,7 +335,7 @@ func TestMultiContainerSignal(t *testing.T) {
}
// Kill process 2.
- if err := containers[1].Signal(syscall.SIGKILL, false); err != nil {
+ if err := containers[1].SignalContainer(syscall.SIGKILL, false); err != nil {
t.Errorf("failed to kill process 2: %v", err)
}
@@ -368,12 +368,12 @@ func TestMultiContainerSignal(t *testing.T) {
// Now that process 2 is gone, ensure we get an error trying to
// signal it again.
- if err := containers[1].Signal(syscall.SIGKILL, false); err == nil {
+ if err := containers[1].SignalContainer(syscall.SIGKILL, false); err == nil {
t.Errorf("container %q shouldn't exist, but we were able to signal it", containers[1].ID)
}
// Kill process 1.
- if err := containers[0].Signal(syscall.SIGKILL, false); err != nil {
+ if err := containers[0].SignalContainer(syscall.SIGKILL, false); err != nil {
t.Errorf("failed to kill process 1: %v", err)
}
@@ -395,7 +395,7 @@ func TestMultiContainerSignal(t *testing.T) {
}
// The sentry should be gone, so signaling should yield an error.
- if err := containers[0].Signal(syscall.SIGKILL, false); err == nil {
+ if err := containers[0].SignalContainer(syscall.SIGKILL, false); err == nil {
t.Errorf("sandbox %q shouldn't exist, but we were able to signal it", containers[0].Sandbox.ID)
}
}
@@ -577,7 +577,7 @@ func TestMultiContainerKillAll(t *testing.T) {
if tc.killContainer {
// First kill the init process to make the container be stopped with
// processes still running inside.
- containers[1].Signal(syscall.SIGKILL, false)
+ containers[1].SignalContainer(syscall.SIGKILL, false)
op := func() error {
c, err := Load(conf.RootDir, ids[1])
if err != nil {
@@ -598,7 +598,7 @@ func TestMultiContainerKillAll(t *testing.T) {
t.Fatalf("failed to load child container %q: %v", c.ID, err)
}
// Kill'Em All
- if err := c.Signal(syscall.SIGKILL, true); err != nil {
+ if err := c.SignalContainer(syscall.SIGKILL, true); err != nil {
t.Fatalf("failed to send SIGKILL to container %q: %v", c.ID, err)
}
diff --git a/runsc/container/test_app.go b/runsc/container/test_app.go
index 9e4b5326d..cc3b087e1 100644
--- a/runsc/container/test_app.go
+++ b/runsc/container/test_app.go
@@ -125,6 +125,7 @@ func server(listener net.Listener, out *os.File) {
type taskTree struct {
depth int
width int
+ pause bool
}
// Name implements subcommands.Command.
@@ -146,6 +147,7 @@ func (*taskTree) Usage() string {
func (c *taskTree) SetFlags(f *flag.FlagSet) {
f.IntVar(&c.depth, "depth", 1, "number of levels to create")
f.IntVar(&c.width, "width", 1, "number of tasks at each level")
+ f.BoolVar(&c.pause, "pause", false, "whether the tasks should pause perpetually")
}
// Execute implements subcommands.Command.
@@ -164,7 +166,8 @@ func (c *taskTree) Execute(ctx context.Context, f *flag.FlagSet, args ...interfa
cmd := exec.Command(
"/proc/self/exe", c.Name(),
"--depth", strconv.Itoa(c.depth-1),
- "--width", strconv.Itoa(c.width))
+ "--width", strconv.Itoa(c.width),
+ "--pause", strconv.FormatBool(c.pause))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@@ -177,6 +180,11 @@ func (c *taskTree) Execute(ctx context.Context, f *flag.FlagSet, args ...interfa
for _, c := range cmds {
c.Wait()
}
+
+ if c.pause {
+ select {}
+ }
+
return subcommands.ExitSuccess
}