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
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
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 {
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
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
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
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
233 p.Env = append(p.Env, context.StringSlice("env")...)
234
235
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
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