1
2
3 package main
4
5 import (
6 "encoding/json"
7 "fmt"
8 "os"
9 "path/filepath"
10 "strings"
11 "syscall"
12
13 "github.com/Microsoft/hcsshim/internal/appargs"
14 "github.com/opencontainers/runtime-spec/specs-go"
15 "github.com/urfave/cli"
16 )
17
18 var execCommand = cli.Command{
19 Name: "exec",
20 Usage: "execute new process inside the container",
21 ArgsUsage: `<container-id> <command> [command options] || -p process.json <container-id>
22
23 Where "<container-id>" is the name for the instance of the container and
24 "<command>" is the command to be executed in the container.
25 "<command>" can't be empty unless a "-p" flag provided.
26
27 EXAMPLE:
28 For example, if the container is configured to run the linux ps command the
29 following will output a list of processes running in the container:
30
31 # runhcs exec <container-id> ps`,
32 Flags: []cli.Flag{
33 cli.StringFlag{
34 Name: "cwd",
35 Usage: "current working directory in the container",
36 },
37 cli.StringSliceFlag{
38 Name: "env, e",
39 Usage: "set environment variables",
40 },
41 cli.BoolFlag{
42 Name: "tty, t",
43 Usage: "allocate a pseudo-TTY",
44 },
45 cli.StringFlag{
46 Name: "user, u",
47 },
48 cli.StringFlag{
49 Name: "process, p",
50 Usage: "path to the process.json",
51 },
52 cli.BoolFlag{
53 Name: "detach,d",
54 Usage: "detach from the container's process",
55 },
56 cli.StringFlag{
57 Name: "pid-file",
58 Value: "",
59 Usage: "specify the file to write the process id to",
60 },
61 cli.StringFlag{
62 Name: "shim-log",
63 Value: "",
64 Usage: `path to the log file or named pipe (e.g. \\.\pipe\ProtectedPrefix\Administrators\runhcs-<container-id>-<exec-id>-log) for the launched shim process`,
65 },
66 },
67 Before: appargs.Validate(argID, appargs.Rest(appargs.String)),
68 Action: func(context *cli.Context) error {
69 id := context.Args().First()
70 pidFile, err := absPathOrEmpty(context.String("pid-file"))
71 if err != nil {
72 return err
73 }
74 shimLog, err := absPathOrEmpty(context.String("shim-log"))
75 if err != nil {
76 return err
77 }
78 c, err := getContainer(id, false)
79 if err != nil {
80 return err
81 }
82 defer c.Close()
83 status, err := c.Status()
84 if err != nil {
85 return err
86 }
87 if status != containerRunning {
88 return errContainerStopped
89 }
90 spec, err := getProcessSpec(context, c)
91 if err != nil {
92 return err
93 }
94 p, err := startProcessShim(id, pidFile, shimLog, spec)
95 if err != nil {
96 return err
97 }
98 if !context.Bool("detach") {
99 state, err := p.Wait()
100 if err != nil {
101 return err
102 }
103 os.Exit(int(state.Sys().(syscall.WaitStatus).ExitCode))
104 }
105 return nil
106 },
107 SkipArgReorder: true,
108 }
109
110 func getProcessSpec(context *cli.Context, c *container) (*specs.Process, error) {
111 if path := context.String("process"); path != "" {
112 f, err := os.Open(path)
113 if err != nil {
114 return nil, err
115 }
116 defer f.Close()
117 var p specs.Process
118 if err := json.NewDecoder(f).Decode(&p); err != nil {
119 return nil, err
120 }
121 return &p, validateProcessSpec(&p)
122 }
123
124
125 p := c.Spec.Process
126
127 if len(context.Args()) == 1 {
128 return nil, fmt.Errorf("process args cannot be empty")
129 }
130 p.Args = context.Args()[1:]
131
132 if context.String("cwd") != "" {
133 p.Cwd = context.String("cwd")
134 }
135
136 p.Env = append(p.Env, context.StringSlice("env")...)
137
138
139 if context.IsSet("tty") {
140 p.Terminal = context.Bool("tty")
141 }
142
143 if context.String("user") != "" {
144 p.User.Username = context.String("user")
145 }
146 return p, nil
147 }
148
149 func validateProcessSpec(spec *specs.Process) error {
150 if spec.Cwd == "" {
151 return fmt.Errorf("cwd property must not be empty")
152 }
153
154
155 if !filepath.IsAbs(spec.Cwd) && !strings.HasPrefix(spec.Cwd, "/") {
156 return fmt.Errorf("cwd must be an absolute path")
157 }
158 if len(spec.Args) == 0 {
159 return fmt.Errorf("args must not be empty")
160 }
161 return nil
162 }
163
View as plain text