1// +build linux
2
3package main
4
5import (
6	"encoding/json"
7	"fmt"
8	"os"
9	"strconv"
10	"strings"
11
12	"github.com/codegangsta/cli"
13	"github.com/opencontainers/runc/libcontainer/utils"
14	"github.com/opencontainers/runtime-spec/specs-go"
15)
16
17var execCommand = cli.Command{
18	Name:  "exec",
19	Usage: "execute new process inside the container",
20	ArgsUsage: `<container-id> <container command>
21
22Where "<container-id>" is the name for the instance of the container and
23"<container command>" is the command to be executed in the container.
24
25For example, if the container is configured to run the linux ps command the
26following will output a list of processes running in the container:
27
28       # runc exec <container-id> ps`,
29	Flags: []cli.Flag{
30		cli.StringFlag{
31			Name:  "console",
32			Usage: "specify the pty slave path for use with the container",
33		},
34		cli.StringFlag{
35			Name:  "cwd",
36			Usage: "current working directory in the container",
37		},
38		cli.StringSliceFlag{
39			Name:  "env, e",
40			Usage: "set environment variables",
41		},
42		cli.BoolFlag{
43			Name:  "tty, t",
44			Usage: "allocate a pseudo-TTY",
45		},
46		cli.StringFlag{
47			Name:  "user, u",
48			Usage: "UID (format: <uid>[:<gid>])",
49		},
50		cli.StringFlag{
51			Name:  "process, p",
52			Usage: "path to the process.json",
53		},
54		cli.BoolFlag{
55			Name:  "detach,d",
56			Usage: "detach from the container's process",
57		},
58		cli.StringFlag{
59			Name:  "pid-file",
60			Value: "",
61			Usage: "specify the file to write the process id to",
62		},
63		cli.StringFlag{
64			Name:  "process-label",
65			Usage: "set the asm process label for the process commonly used with selinux",
66		},
67		cli.StringFlag{
68			Name:  "apparmor",
69			Usage: "set the apparmor profile for the process",
70		},
71		cli.BoolFlag{
72			Name:  "no-new-privs",
73			Usage: "set the no new privileges value for the process",
74		},
75		cli.StringSliceFlag{
76			Name:  "cap, c",
77			Value: &cli.StringSlice{},
78			Usage: "add a capability to the bounding set for the process",
79		},
80		cli.BoolFlag{
81			Name:  "no-subreaper",
82			Usage: "disable the use of the subreaper used to reap reparented processes",
83		},
84	},
85	Action: func(context *cli.Context) {
86		if os.Geteuid() != 0 {
87			fatalf("runc should be run as root")
88		}
89		status, err := execProcess(context)
90		if err != nil {
91			fatalf("exec failed: %v", err)
92		}
93		os.Exit(status)
94	},
95}
96
97func execProcess(context *cli.Context) (int, error) {
98	container, err := getContainer(context)
99	if err != nil {
100		return -1, err
101	}
102	detach := context.Bool("detach")
103	state, err := container.State()
104	if err != nil {
105		return -1, err
106	}
107	bundle := utils.SearchLabels(state.Config.Labels, "bundle")
108	p, err := getProcess(context, bundle)
109	if err != nil {
110		return -1, err
111	}
112	r := &runner{
113		enableSubreaper: !context.Bool("no-subreaper"),
114		shouldDestroy:   false,
115		container:       container,
116		console:         context.String("console"),
117		detach:          detach,
118		pidFile:         context.String("pid-file"),
119	}
120	return r.run(p)
121}
122
123func getProcess(context *cli.Context, bundle string) (*specs.Process, error) {
124	if path := context.String("process"); path != "" {
125		f, err := os.Open(path)
126		if err != nil {
127			return nil, err
128		}
129		defer f.Close()
130		var p specs.Process
131		if err := json.NewDecoder(f).Decode(&p); err != nil {
132			return nil, err
133		}
134		return &p, validateProcessSpec(&p)
135	}
136	// process via cli flags
137	if err := os.Chdir(bundle); err != nil {
138		return nil, err
139	}
140	spec, err := loadSpec(specConfig)
141	if err != nil {
142		return nil, err
143	}
144	p := spec.Process
145	p.Args = context.Args()[1:]
146	// override the cwd, if passed
147	if context.String("cwd") != "" {
148		p.Cwd = context.String("cwd")
149	}
150	if ap := context.String("apparmor"); ap != "" {
151		p.ApparmorProfile = ap
152	}
153	if l := context.String("process-label"); l != "" {
154		p.SelinuxLabel = l
155	}
156	if caps := context.StringSlice("cap"); len(caps) > 0 {
157		p.Capabilities = caps
158	}
159	// append the passed env variables
160	for _, e := range context.StringSlice("env") {
161		p.Env = append(p.Env, e)
162	}
163	// set the tty
164	if context.IsSet("tty") {
165		p.Terminal = context.Bool("tty")
166	}
167	if context.IsSet("no-new-privs") {
168		p.NoNewPrivileges = context.Bool("no-new-privs")
169	}
170	// override the user, if passed
171	if context.String("user") != "" {
172		u := strings.SplitN(context.String("user"), ":", 2)
173		if len(u) > 1 {
174			gid, err := strconv.Atoi(u[1])
175			if err != nil {
176				return nil, fmt.Errorf("parsing %s as int for gid failed: %v", u[1], err)
177			}
178			p.User.GID = uint32(gid)
179		}
180		uid, err := strconv.Atoi(u[0])
181		if err != nil {
182			return nil, fmt.Errorf("parsing %s as int for uid failed: %v", u[0], err)
183		}
184		p.User.UID = uint32(uid)
185	}
186	return &p, nil
187}
188