github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/runsc/cmd/wait.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package cmd
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"os"
    21  
    22  	"github.com/google/subcommands"
    23  	"golang.org/x/sys/unix"
    24  	"github.com/SagerNet/gvisor/runsc/config"
    25  	"github.com/SagerNet/gvisor/runsc/container"
    26  	"github.com/SagerNet/gvisor/runsc/flag"
    27  )
    28  
    29  const (
    30  	unsetPID = -1
    31  )
    32  
    33  // Wait implements subcommands.Command for the "wait" command.
    34  type Wait struct {
    35  	rootPID int
    36  	pid     int
    37  }
    38  
    39  // Name implements subcommands.Command.Name.
    40  func (*Wait) Name() string {
    41  	return "wait"
    42  }
    43  
    44  // Synopsis implements subcommands.Command.Synopsis.
    45  func (*Wait) Synopsis() string {
    46  	return "wait on a process inside a container"
    47  }
    48  
    49  // Usage implements subcommands.Command.Usage.
    50  func (*Wait) Usage() string {
    51  	return `wait [flags] <container id>`
    52  }
    53  
    54  // SetFlags implements subcommands.Command.SetFlags.
    55  func (wt *Wait) SetFlags(f *flag.FlagSet) {
    56  	f.IntVar(&wt.rootPID, "rootpid", unsetPID, "select a PID in the sandbox root PID namespace to wait on instead of the container's root process")
    57  	f.IntVar(&wt.pid, "pid", unsetPID, "select a PID in the container's PID namespace to wait on instead of the container's root process")
    58  }
    59  
    60  // Execute implements subcommands.Command.Execute. It waits for a process in a
    61  // container to exit before returning.
    62  func (wt *Wait) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
    63  	if f.NArg() != 1 {
    64  		f.Usage()
    65  		return subcommands.ExitUsageError
    66  	}
    67  	// You can't specify both -pid and -rootpid.
    68  	if wt.rootPID != unsetPID && wt.pid != unsetPID {
    69  		Fatalf("only one of -pid and -rootPid can be set")
    70  	}
    71  
    72  	id := f.Arg(0)
    73  	conf := args[0].(*config.Config)
    74  
    75  	c, err := container.Load(conf.RootDir, container.FullID{ContainerID: id}, container.LoadOpts{})
    76  	if err != nil {
    77  		Fatalf("loading container: %v", err)
    78  	}
    79  
    80  	var waitStatus unix.WaitStatus
    81  	switch {
    82  	// Wait on the whole container.
    83  	case wt.rootPID == unsetPID && wt.pid == unsetPID:
    84  		ws, err := c.Wait()
    85  		if err != nil {
    86  			Fatalf("waiting on container %q: %v", c.ID, err)
    87  		}
    88  		waitStatus = ws
    89  	// Wait on a PID in the root PID namespace.
    90  	case wt.rootPID != unsetPID:
    91  		ws, err := c.WaitRootPID(int32(wt.rootPID))
    92  		if err != nil {
    93  			Fatalf("waiting on PID in root PID namespace %d in container %q: %v", wt.rootPID, c.ID, err)
    94  		}
    95  		waitStatus = ws
    96  	// Wait on a PID in the container's PID namespace.
    97  	case wt.pid != unsetPID:
    98  		ws, err := c.WaitPID(int32(wt.pid))
    99  		if err != nil {
   100  			Fatalf("waiting on PID %d in container %q: %v", wt.pid, c.ID, err)
   101  		}
   102  		waitStatus = ws
   103  	}
   104  	result := waitResult{
   105  		ID:         id,
   106  		ExitStatus: exitStatus(waitStatus),
   107  	}
   108  	// Write json-encoded wait result directly to stdout.
   109  	if err := json.NewEncoder(os.Stdout).Encode(result); err != nil {
   110  		Fatalf("marshaling wait result: %v", err)
   111  	}
   112  	return subcommands.ExitSuccess
   113  }
   114  
   115  type waitResult struct {
   116  	ID         string `json:"id"`
   117  	ExitStatus int    `json:"exitStatus"`
   118  }
   119  
   120  // exitStatus returns the correct exit status for a process based on if it
   121  // was signaled or exited cleanly.
   122  func exitStatus(status unix.WaitStatus) int {
   123  	if status.Signaled() {
   124  		return 128 + int(status.Signal())
   125  	}
   126  	return status.ExitStatus()
   127  }