// Copyright (c) 2014,2015,2016 Docker, Inc. // Copyright (c) 2017 Intel Corporation // // SPDX-License-Identifier: Apache-2.0 // package main import ( "context" "encoding/json" "fmt" "io/ioutil" "os" "syscall" "github.com/kata-containers/runtime/pkg/katautils" "github.com/kata-containers/runtime/virtcontainers/pkg/oci" "github.com/kata-containers/runtime/virtcontainers/types" specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/urfave/cli" ) type execParams struct { ociProcess specs.Process cID string pidFile string console string consoleSock string processLabel string detach bool noSubreaper bool } var execCLICommand = cli.Command{ Name: "exec", Usage: "Execute new process inside the container", ArgsUsage: ` [command options] || -p process.json is the name for the instance of the container and is the command to be executed in the container. can't be empty unless a "-p" flag provided. EXAMPLE: If the container is configured to run the linux ps command the following will output a list of processes running in the container: # ` + name + ` ps`, Flags: []cli.Flag{ cli.StringFlag{ Name: "console", Usage: "path to a pseudo terminal", }, cli.StringFlag{ Name: "console-socket", Value: "", Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal", }, cli.StringFlag{ Name: "cwd", Usage: "current working directory in the container", }, cli.StringSliceFlag{ Name: "env, e", Usage: "set environment variables", }, cli.BoolFlag{ Name: "tty, t", Usage: "allocate a pseudo-TTY", }, cli.StringFlag{ Name: "user, u", Usage: "UID (format: [:])", }, cli.StringFlag{ Name: "process, p", Usage: "path to the process.json", }, cli.BoolFlag{ Name: "detach,d", Usage: "detach from the container's process", }, cli.StringFlag{ Name: "pid-file", Value: "", Usage: "specify the file to write the process id to", }, cli.StringFlag{ Name: "process-label", Usage: "set the asm process label for the process commonly used with selinux", }, cli.StringFlag{ Name: "apparmor", Usage: "set the apparmor profile for the process", }, cli.BoolFlag{ Name: "no-new-privs", Usage: "set the no new privileges value for the process", }, cli.StringSliceFlag{ Name: "cap, c", Value: &cli.StringSlice{}, Usage: "add a capability to the bounding set for the process", }, cli.BoolFlag{ Name: "no-subreaper", Usage: "disable the use of the subreaper used to reap reparented processes", Hidden: true, }, }, Action: func(context *cli.Context) error { ctx, err := cliContextToContext(context) if err != nil { return err } return execute(ctx, context) }, } func generateExecParams(context *cli.Context, specProcess *specs.Process) (execParams, error) { ctxArgs := context.Args() params := execParams{ cID: ctxArgs.First(), pidFile: context.String("pid-file"), console: context.String("console"), consoleSock: context.String("console-socket"), detach: context.Bool("detach"), processLabel: context.String("process-label"), noSubreaper: context.Bool("no-subreaper"), } if context.String("process") != "" { var ociProcess specs.Process fileContent, err := ioutil.ReadFile(context.String("process")) if err != nil { return execParams{}, err } if err := json.Unmarshal(fileContent, &ociProcess); err != nil { return execParams{}, err } params.ociProcess = ociProcess } else { params.ociProcess = *specProcess // Override terminal if context.IsSet("tty") { params.ociProcess.Terminal = context.Bool("tty") } // Override user if context.String("user") != "" { params.ociProcess.User = specs.User{ // This field is a Windows-only field // according to the specification. However, it // is abused here to allow the username // specified in the OCI runtime configuration // file to be overridden by a CLI request. Username: context.String("user"), } } // Override env params.ociProcess.Env = append(params.ociProcess.Env, context.StringSlice("env")...) // Override cwd if context.String("cwd") != "" { params.ociProcess.Cwd = context.String("cwd") } // Override no-new-privs if context.IsSet("no-new-privs") { params.ociProcess.NoNewPrivileges = context.Bool("no-new-privs") } // Override apparmor if context.String("apparmor") != "" { params.ociProcess.ApparmorProfile = context.String("apparmor") } params.ociProcess.Args = ctxArgs.Tail() } return params, nil } func execute(ctx context.Context, context *cli.Context) error { span, ctx := katautils.Trace(ctx, "execute") defer span.Finish() containerID := context.Args().First() kataLog = kataLog.WithField("container", containerID) setExternalLoggers(ctx, kataLog) span.SetTag("container", containerID) status, sandboxID, err := getExistingContainerInfo(ctx, containerID) if err != nil { return err } kataLog = kataLog.WithField("sandbox", sandboxID) setExternalLoggers(ctx, kataLog) span.SetTag("sandbox", sandboxID) // Retrieve OCI spec configuration. ociSpec, err := oci.GetOCIConfig(status) if err != nil { return err } params, err := generateExecParams(context, ociSpec.Process) if err != nil { return err } params.cID = status.ID containerID = params.cID kataLog = kataLog.WithField("container", containerID) setExternalLoggers(ctx, kataLog) span.SetTag("container", containerID) // container MUST be ready or running. if status.State.State != types.StateReady && status.State.State != types.StateRunning { return fmt.Errorf("Container %s is not ready or running", params.cID) } envVars, err := oci.EnvVars(params.ociProcess.Env) if err != nil { return err } consolePath, err := setupConsole(params.console, params.consoleSock) if err != nil { return err } user := fmt.Sprintf("%d:%d", params.ociProcess.User.UID, params.ociProcess.User.GID) if params.ociProcess.User.Username != "" { user = params.ociProcess.User.Username } cmd := types.Cmd{ Args: params.ociProcess.Args, Envs: envVars, WorkDir: params.ociProcess.Cwd, User: user, Interactive: params.ociProcess.Terminal, Console: consolePath, Detach: noNeedForOutput(params.detach, params.ociProcess.Terminal), } _, _, process, err := vci.EnterContainer(ctx, sandboxID, params.cID, cmd) if err != nil { return err } // Creation of PID file has to be the last thing done in the exec // because containerd considers the exec to have finished starting // after this file is created. if err := createPIDFile(ctx, params.pidFile, process.Pid); err != nil { return err } if params.detach { return nil } p, err := os.FindProcess(process.Pid) if err != nil { return err } ps, err := p.Wait() if err != nil { return fmt.Errorf("Process state %s, container info %+v: %v", ps.String(), status, err) } // Exit code has to be forwarded in this case. return cli.NewExitError("", ps.Sys().(syscall.WaitStatus).ExitStatus()) }