1
2
3 package main
4
5 import (
6 "context"
7 "fmt"
8 "io"
9 "os"
10 "strings"
11
12 "github.com/Microsoft/hcsshim/internal/cmd"
13 "github.com/Microsoft/hcsshim/internal/log"
14 "github.com/Microsoft/hcsshim/internal/memory"
15 "github.com/Microsoft/hcsshim/internal/uvm"
16 "github.com/Microsoft/hcsshim/pkg/securitypolicy"
17 "github.com/containerd/console"
18 "github.com/urfave/cli"
19 )
20
21 const (
22 bootFilesPathArgName = "boot-files-path"
23 consolePipeArgName = "console-pipe"
24 kernelDirectArgName = "kernel-direct"
25 kernelFileArgName = "kernel-file"
26 forwardStdoutArgName = "fwd-stdout"
27 forwardStderrArgName = "fwd-stderr"
28 outputHandlingArgName = "output-handling"
29 kernelArgsArgName = "kernel-args"
30 rootFSTypeArgName = "root-fs-type"
31 vpMemMaxCountArgName = "vpmem-max-count"
32 vpMemMaxSizeArgName = "vpmem-max-size"
33 scsiMountsArgName = "mount-scsi"
34 vpmemMountsArgName = "mount-vpmem"
35 shareFilesArgName = "share"
36 securityPolicyArgName = "security-policy"
37 securityHardwareFlag = "security-hardware"
38 securityPolicyEnforcerArgName = "security-policy-enforcer"
39 )
40
41 var (
42 lcowUseTerminal bool
43 lcowDisableTimeSync bool
44 )
45
46 var lcowCommand = cli.Command{
47 Name: "lcow",
48 Usage: "Boot an LCOW UVM",
49 Flags: []cli.Flag{
50 cli.StringFlag{
51 Name: kernelArgsArgName,
52 Value: "",
53 Usage: "Additional arguments to pass to the kernel",
54 },
55 cli.StringFlag{
56 Name: rootFSTypeArgName,
57 Usage: "Either 'initrd', 'vhd' or 'none'. (default: 'vhd' if rootfs.vhd exists)",
58 },
59 cli.StringFlag{
60 Name: bootFilesPathArgName,
61 Usage: "The `path` to the boot files directory",
62 },
63 cli.UintFlag{
64 Name: vpMemMaxCountArgName,
65 Usage: "Number of VPMem devices on the UVM. Uses hcsshim default if not specified",
66 },
67 cli.Uint64Flag{
68 Name: vpMemMaxSizeArgName,
69 Usage: "Size of each VPMem device, in MB. Uses hcsshim default if not specified",
70 },
71 cli.BoolFlag{
72 Name: kernelDirectArgName,
73 Usage: "Use kernel direct booting for UVM (default: true on builds >= 18286)",
74 },
75 cli.StringFlag{
76 Name: kernelFileArgName,
77 Usage: "The kernel `file` to use; either 'kernel' or 'vmlinux'. (default: 'kernel')",
78 },
79 cli.BoolFlag{
80 Name: "disable-time-sync",
81 Usage: "Disable the time synchronization service",
82 Destination: &lcowDisableTimeSync,
83 },
84 cli.StringFlag{
85 Name: securityPolicyArgName,
86 Usage: "Security policy to set on the UVM. Leave empty to use an open door policy",
87 },
88 cli.StringFlag{
89 Name: securityPolicyEnforcerArgName,
90 Usage: "Security policy enforcer to use for a given security policy. " +
91 "Leave empty to use the default enforcer",
92 },
93 cli.BoolFlag{
94 Name: securityHardwareFlag,
95 Usage: "Use VMGS file to run on secure hardware. ('root-fs-type' must be set to 'none')",
96 },
97 cli.StringFlag{
98 Name: execCommandLineArgName,
99 Usage: "Command to execute in the UVM.",
100 },
101 cli.BoolFlag{
102 Name: forwardStdoutArgName,
103 Usage: "Whether stdout from the process in the UVM should be forwarded",
104 },
105 cli.BoolFlag{
106 Name: forwardStderrArgName,
107 Usage: "Whether stderr from the process in the UVM should be forwarded",
108 },
109 cli.StringFlag{
110 Name: outputHandlingArgName,
111 Usage: "Controls how output from UVM is handled. Use 'stdout' to print all output to stdout",
112 },
113 cli.StringFlag{
114 Name: consolePipeArgName,
115 Usage: "Named pipe for serial console output (which will be enabled)",
116 },
117 cli.BoolFlag{
118 Name: "tty,t",
119 Usage: "create the process in the UVM with a TTY enabled",
120 Destination: &lcowUseTerminal,
121 },
122 cli.StringSliceFlag{
123 Name: scsiMountsArgName,
124 Usage: "List of VHDs to SCSI mount into the UVM. Use repeat instances to add multiple. " +
125 "Value is of the form `'host[,guest[,w]]'`, where 'host' is path to the VHD, " +
126 `'guest' is an optional mount path inside the UVM, and 'w' mounts the VHD as writeable`,
127 },
128 cli.StringSliceFlag{
129 Name: shareFilesArgName,
130 Usage: "List of paths or files to plan9 share into the UVM. Use repeat instances to add multiple. " +
131 "Value is of the form `'host,guest[,w]' where 'host' is path to the VHD, " +
132 `'guest' is the mount path inside the UVM, and 'w' sets the shared files to writeable`,
133 },
134 cli.StringSliceFlag{
135 Name: vpmemMountsArgName,
136 Usage: "List of VHDs to VPMem mount into the UVM. Use repeat instances to add multiple. ",
137 },
138 },
139 Action: func(c *cli.Context) error {
140 runMany(c, func(id string) error {
141 ctx := context.Background()
142
143 options, err := createLCOWOptions(ctx, c, id)
144 if err != nil {
145 return err
146 }
147
148 if err := runLCOW(ctx, options, c); err != nil {
149 return err
150 }
151
152 return nil
153 })
154
155 return nil
156 },
157 }
158
159 func init() {
160 lcowCommand.CustomHelpTemplate = cli.CommandHelpTemplate + "EXAMPLES:\n" +
161 `.\uvmboot.exe -gcs lcow -boot-files-path "C:\ContainerPlat\LinuxBootFiles" -root-fs-type vhd -t -exec "/bin/bash"`
162 }
163
164 func createLCOWOptions(_ context.Context, c *cli.Context, id string) (*uvm.OptionsLCOW, error) {
165 options := uvm.NewDefaultOptionsLCOW(id, "")
166 setGlobalOptions(c, options.Options)
167
168
169 if c.IsSet(bootFilesPathArgName) {
170 options.BootFilesPath = c.String(bootFilesPathArgName)
171 }
172
173
174 if c.IsSet(kernelDirectArgName) {
175 options.KernelDirect = c.Bool(kernelDirectArgName)
176 }
177 if c.IsSet(kernelFileArgName) {
178 switch strings.ToLower(c.String(kernelFileArgName)) {
179 case uvm.KernelFile:
180 options.KernelFile = uvm.KernelFile
181 case uvm.UncompressedKernelFile:
182 options.KernelFile = uvm.UncompressedKernelFile
183 default:
184 return nil, unrecognizedError(c.String(kernelFileArgName), kernelFileArgName)
185 }
186 }
187 if c.IsSet(kernelArgsArgName) {
188 options.KernelBootOptions = c.String(kernelArgsArgName)
189 }
190
191
192 if c.IsSet(rootFSTypeArgName) {
193 switch strings.ToLower(c.String(rootFSTypeArgName)) {
194 case "initrd":
195 options.RootFSFile = uvm.InitrdFile
196 options.PreferredRootFSType = uvm.PreferredRootFSTypeInitRd
197 case "vhd":
198 options.RootFSFile = uvm.VhdFile
199 options.PreferredRootFSType = uvm.PreferredRootFSTypeVHD
200 case "none":
201 options.RootFSFile = ""
202 options.PreferredRootFSType = uvm.PreferredRootFSTypeNA
203 default:
204 return nil, unrecognizedError(c.String(rootFSTypeArgName), rootFSTypeArgName)
205 }
206 }
207
208 if c.IsSet(vpMemMaxCountArgName) {
209 options.VPMemDeviceCount = uint32(c.Uint(vpMemMaxCountArgName))
210 }
211 if c.IsSet(vpMemMaxSizeArgName) {
212 options.VPMemSizeBytes = c.Uint64(vpMemMaxSizeArgName) * memory.MiB
213 }
214
215
216 options.UseGuestConnection = useGCS
217 if !useGCS {
218 if c.IsSet(execCommandLineArgName) {
219 options.ExecCommandLine = c.String(execCommandLineArgName)
220 }
221 if c.IsSet(forwardStdoutArgName) {
222 options.ForwardStdout = c.Bool(forwardStdoutArgName)
223 }
224 if c.IsSet(forwardStderrArgName) {
225 options.ForwardStderr = c.Bool(forwardStderrArgName)
226 }
227 if c.IsSet(outputHandlingArgName) {
228 switch strings.ToLower(c.String(outputHandlingArgName)) {
229 case "stdout":
230 options.OutputHandler = uvm.OutputHandler(func(r io.Reader) {
231 _, _ = io.Copy(os.Stdout, r)
232 })
233 default:
234 return nil, unrecognizedError(c.String(outputHandlingArgName), outputHandlingArgName)
235 }
236 }
237 }
238 if c.IsSet(consolePipeArgName) {
239 options.ConsolePipe = c.String(consolePipeArgName)
240 }
241
242
243 if lcowDisableTimeSync {
244 options.DisableTimeSyncService = true
245 }
246
247
248
249 openPolicy, err := securitypolicy.NewOpenDoorPolicy().EncodeToString()
250 if err != nil {
251 return nil, fmt.Errorf("failed to encode open door policy: %s", err)
252 }
253 options.SecurityPolicy = openPolicy
254 if c.IsSet(securityPolicyArgName) {
255 options.SecurityPolicy = c.String(securityPolicyArgName)
256 }
257 if c.IsSet(securityPolicyEnforcerArgName) {
258 options.SecurityPolicyEnforcer = c.String(securityPolicyEnforcerArgName)
259 }
260 if c.IsSet(securityHardwareFlag) {
261 options.GuestStateFile = uvm.GuestStateFile
262 options.SecurityPolicyEnabled = true
263 options.AllowOvercommit = false
264 }
265
266 return options, nil
267 }
268
269 func runLCOW(ctx context.Context, options *uvm.OptionsLCOW, c *cli.Context) error {
270 vm, err := uvm.CreateLCOW(ctx, options)
271 if err != nil {
272 return err
273 }
274 defer vm.Close()
275
276 if err := vm.Start(ctx); err != nil {
277 return err
278 }
279
280 if err := mountSCSI(ctx, c, vm); err != nil {
281 return err
282 }
283
284 if err := shareFiles(ctx, c, vm); err != nil {
285 return err
286 }
287
288 if err := mountVPMem(ctx, c, vm); err != nil {
289 return err
290 }
291
292 if options.UseGuestConnection {
293 if err := execViaGcs(vm, c); err != nil {
294 return err
295 }
296 _ = vm.Terminate(ctx)
297 _ = vm.Wait()
298
299 return vm.ExitError()
300 }
301
302 return vm.Wait()
303 }
304
305 func execViaGcs(vm *uvm.UtilityVM, c *cli.Context) error {
306 cmd := cmd.Command(vm, "/bin/sh", "-c", c.String(execCommandLineArgName))
307 cmd.Log = log.L.Dup()
308 if lcowUseTerminal {
309 cmd.Spec.Terminal = true
310 cmd.Stdin = os.Stdin
311 cmd.Stdout = os.Stdout
312 con, err := console.ConsoleFromFile(os.Stdin)
313 if err == nil {
314 err = con.SetRaw()
315 if err != nil {
316 return err
317 }
318 defer func() {
319 _ = con.Reset()
320 }()
321 }
322 } else if c.String(outputHandlingArgName) == "stdout" {
323 if c.Bool(forwardStdoutArgName) {
324 cmd.Stdout = os.Stdout
325 }
326 if c.Bool(forwardStderrArgName) {
327 cmd.Stderr = os.Stdout
328 }
329 }
330
331 return cmd.Run()
332 }
333
View as plain text