...

Source file src/github.com/opencontainers/runc/exec.go

Documentation: github.com/opencontainers/runc

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/opencontainers/runc/libcontainer"
    12  	"github.com/opencontainers/runc/libcontainer/utils"
    13  	"github.com/opencontainers/runtime-spec/specs-go"
    14  	"github.com/urfave/cli"
    15  )
    16  
    17  var execCommand = cli.Command{
    18  	Name:  "exec",
    19  	Usage: "execute new process inside the container",
    20  	ArgsUsage: `<container-id> <command> [command options]  || -p process.json <container-id>
    21  
    22  Where "<container-id>" is the name for the instance of the container and
    23  "<command>" is the command to be executed in the container.
    24  "<command>" can't be empty unless a "-p" flag provided.
    25  
    26  EXAMPLE:
    27  For example, if the container is configured to run the linux ps command the
    28  following will output a list of processes running in the container:
    29  
    30         # runc exec <container-id> ps`,
    31  	Flags: []cli.Flag{
    32  		cli.StringFlag{
    33  			Name:  "console-socket",
    34  			Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal",
    35  		},
    36  		cli.StringFlag{
    37  			Name:  "cwd",
    38  			Usage: "current working directory in the container",
    39  		},
    40  		cli.StringSliceFlag{
    41  			Name:  "env, e",
    42  			Usage: "set environment variables",
    43  		},
    44  		cli.BoolFlag{
    45  			Name:  "tty, t",
    46  			Usage: "allocate a pseudo-TTY",
    47  		},
    48  		cli.StringFlag{
    49  			Name:  "user, u",
    50  			Usage: "UID (format: <uid>[:<gid>])",
    51  		},
    52  		cli.Int64SliceFlag{
    53  			Name:  "additional-gids, g",
    54  			Usage: "additional gids",
    55  		},
    56  		cli.StringFlag{
    57  			Name:  "process, p",
    58  			Usage: "path to the process.json",
    59  		},
    60  		cli.BoolFlag{
    61  			Name:  "detach,d",
    62  			Usage: "detach from the container's process",
    63  		},
    64  		cli.StringFlag{
    65  			Name:  "pid-file",
    66  			Value: "",
    67  			Usage: "specify the file to write the process id to",
    68  		},
    69  		cli.StringFlag{
    70  			Name:  "process-label",
    71  			Usage: "set the asm process label for the process commonly used with selinux",
    72  		},
    73  		cli.StringFlag{
    74  			Name:  "apparmor",
    75  			Usage: "set the apparmor profile for the process",
    76  		},
    77  		cli.BoolFlag{
    78  			Name:  "no-new-privs",
    79  			Usage: "set the no new privileges value for the process",
    80  		},
    81  		cli.StringSliceFlag{
    82  			Name:  "cap, c",
    83  			Value: &cli.StringSlice{},
    84  			Usage: "add a capability to the bounding set for the process",
    85  		},
    86  		cli.IntFlag{
    87  			Name:  "preserve-fds",
    88  			Usage: "Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total)",
    89  		},
    90  		cli.StringSliceFlag{
    91  			Name:  "cgroup",
    92  			Usage: "run the process in an (existing) sub-cgroup(s). Format is [<controller>:]<cgroup>.",
    93  		},
    94  		cli.BoolFlag{
    95  			Name:  "ignore-paused",
    96  			Usage: "allow exec in a paused container",
    97  		},
    98  	},
    99  	Action: func(context *cli.Context) error {
   100  		if err := checkArgs(context, 1, minArgs); err != nil {
   101  			return err
   102  		}
   103  		if err := revisePidFile(context); err != nil {
   104  			return err
   105  		}
   106  		status, err := execProcess(context)
   107  		if err == nil {
   108  			os.Exit(status)
   109  		}
   110  		fatalWithCode(fmt.Errorf("exec failed: %w", err), 255)
   111  		return nil // to satisfy the linter
   112  	},
   113  	SkipArgReorder: true,
   114  }
   115  
   116  func getSubCgroupPaths(args []string) (map[string]string, error) {
   117  	if len(args) == 0 {
   118  		return nil, nil
   119  	}
   120  	paths := make(map[string]string, len(args))
   121  	for _, c := range args {
   122  		// Split into controller:path.
   123  		cs := strings.SplitN(c, ":", 3)
   124  		if len(cs) > 2 {
   125  			return nil, fmt.Errorf("invalid --cgroup argument: %s", c)
   126  		}
   127  		if len(cs) == 1 { // no controller: prefix
   128  			if len(args) != 1 {
   129  				return nil, fmt.Errorf("invalid --cgroup argument: %s (missing <controller>: prefix)", c)
   130  			}
   131  			paths[""] = c
   132  		} else {
   133  			// There may be a few comma-separated controllers.
   134  			for _, ctrl := range strings.Split(cs[0], ",") {
   135  				paths[ctrl] = cs[1]
   136  			}
   137  		}
   138  	}
   139  	return paths, nil
   140  }
   141  
   142  func execProcess(context *cli.Context) (int, error) {
   143  	container, err := getContainer(context)
   144  	if err != nil {
   145  		return -1, err
   146  	}
   147  	status, err := container.Status()
   148  	if err != nil {
   149  		return -1, err
   150  	}
   151  	if status == libcontainer.Stopped {
   152  		return -1, errors.New("cannot exec in a stopped container")
   153  	}
   154  	if status == libcontainer.Paused && !context.Bool("ignore-paused") {
   155  		return -1, errors.New("cannot exec in a paused container (use --ignore-paused to override)")
   156  	}
   157  	path := context.String("process")
   158  	if path == "" && len(context.Args()) == 1 {
   159  		return -1, errors.New("process args cannot be empty")
   160  	}
   161  	state, err := container.State()
   162  	if err != nil {
   163  		return -1, err
   164  	}
   165  	bundle := utils.SearchLabels(state.Config.Labels, "bundle")
   166  	p, err := getProcess(context, bundle)
   167  	if err != nil {
   168  		return -1, err
   169  	}
   170  
   171  	cgPaths, err := getSubCgroupPaths(context.StringSlice("cgroup"))
   172  	if err != nil {
   173  		return -1, err
   174  	}
   175  
   176  	r := &runner{
   177  		enableSubreaper: false,
   178  		shouldDestroy:   false,
   179  		container:       container,
   180  		consoleSocket:   context.String("console-socket"),
   181  		detach:          context.Bool("detach"),
   182  		pidFile:         context.String("pid-file"),
   183  		action:          CT_ACT_RUN,
   184  		init:            false,
   185  		preserveFDs:     context.Int("preserve-fds"),
   186  		subCgroupPaths:  cgPaths,
   187  	}
   188  	return r.run(p)
   189  }
   190  
   191  func getProcess(context *cli.Context, bundle string) (*specs.Process, error) {
   192  	if path := context.String("process"); path != "" {
   193  		f, err := os.Open(path)
   194  		if err != nil {
   195  			return nil, err
   196  		}
   197  		defer f.Close()
   198  		var p specs.Process
   199  		if err := json.NewDecoder(f).Decode(&p); err != nil {
   200  			return nil, err
   201  		}
   202  		return &p, validateProcessSpec(&p)
   203  	}
   204  	// process via cli flags
   205  	if err := os.Chdir(bundle); err != nil {
   206  		return nil, err
   207  	}
   208  	spec, err := loadSpec(specConfig)
   209  	if err != nil {
   210  		return nil, err
   211  	}
   212  	p := spec.Process
   213  	p.Args = context.Args()[1:]
   214  	// override the cwd, if passed
   215  	if context.String("cwd") != "" {
   216  		p.Cwd = context.String("cwd")
   217  	}
   218  	if ap := context.String("apparmor"); ap != "" {
   219  		p.ApparmorProfile = ap
   220  	}
   221  	if l := context.String("process-label"); l != "" {
   222  		p.SelinuxLabel = l
   223  	}
   224  	if caps := context.StringSlice("cap"); len(caps) > 0 {
   225  		for _, c := range caps {
   226  			p.Capabilities.Bounding = append(p.Capabilities.Bounding, c)
   227  			p.Capabilities.Effective = append(p.Capabilities.Effective, c)
   228  			p.Capabilities.Permitted = append(p.Capabilities.Permitted, c)
   229  			p.Capabilities.Ambient = append(p.Capabilities.Ambient, c)
   230  		}
   231  	}
   232  	// append the passed env variables
   233  	p.Env = append(p.Env, context.StringSlice("env")...)
   234  
   235  	// set the tty
   236  	p.Terminal = false
   237  	if context.IsSet("tty") {
   238  		p.Terminal = context.Bool("tty")
   239  	}
   240  	if context.IsSet("no-new-privs") {
   241  		p.NoNewPrivileges = context.Bool("no-new-privs")
   242  	}
   243  	// override the user, if passed
   244  	if context.String("user") != "" {
   245  		u := strings.SplitN(context.String("user"), ":", 2)
   246  		if len(u) > 1 {
   247  			gid, err := strconv.Atoi(u[1])
   248  			if err != nil {
   249  				return nil, fmt.Errorf("parsing %s as int for gid failed: %w", u[1], err)
   250  			}
   251  			p.User.GID = uint32(gid)
   252  		}
   253  		uid, err := strconv.Atoi(u[0])
   254  		if err != nil {
   255  			return nil, fmt.Errorf("parsing %s as int for uid failed: %w", u[0], err)
   256  		}
   257  		p.User.UID = uint32(uid)
   258  	}
   259  	for _, gid := range context.Int64Slice("additional-gids") {
   260  		if gid < 0 {
   261  			return nil, fmt.Errorf("additional-gids must be a positive number %d", gid)
   262  		}
   263  		p.User.AdditionalGids = append(p.User.AdditionalGids, uint32(gid))
   264  	}
   265  	return p, validateProcessSpec(p)
   266  }
   267  

View as plain text