Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 161 additions & 61 deletions internal/tools/uvmboot/lcow.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package main

import (
"context"
"fmt"
"io"
"os"
"strings"
Expand All @@ -17,19 +18,25 @@ import (
)

const (
bootFilesPathArgName = "boot-files-path"
consolePipeArgName = "console-pipe"
kernelDirectArgName = "kernel-direct"
kernelFileArgName = "kernel-file"
forwardStdoutArgName = "fwd-stdout"
forwardStderrArgName = "fwd-stderr"
outputHandlingArgName = "output-handling"
kernelArgsArgName = "kernel-args"
rootFSTypeArgName = "root-fs-type"
vpMemMaxCountArgName = "vpmem-max-count"
vpMemMaxSizeArgName = "vpmem-max-size"
scsiMountsArgName = "mount"
shareFilesArgName = "share"
securityPolicyArgName = "security-policy"
)

var (
lcowUseTerminal bool
lcowUseTerminal bool
lcowDisableTimeSync bool
)

var lcowCommand = cli.Command{
Expand All @@ -45,6 +52,10 @@ var lcowCommand = cli.Command{
Name: rootFSTypeArgName,
Usage: "Either 'initrd' or 'vhd'. (default: 'vhd' if rootfs.vhd exists)",
},
cli.StringFlag{
Name: bootFilesPathArgName,
Usage: "The `path` to the boot files directory",
},
cli.UintFlag{
Name: vpMemMaxCountArgName,
Usage: "Number of VPMem devices on the UVM. Uses hcsshim default if not specified",
Expand All @@ -57,6 +68,19 @@ var lcowCommand = cli.Command{
Name: kernelDirectArgName,
Usage: "Use kernel direct booting for UVM (default: true on builds >= 18286)",
},
cli.StringFlag{
Name: kernelFileArgName,
Usage: "The kernel `file` to use; either 'kernel' or 'vmlinux'. (default: 'kernel')",
},
cli.BoolFlag{
Name: "disable-time-sync",
Usage: "Disable the time synchronization service",
Destination: &lcowDisableTimeSync,
},
cli.StringFlag{
Name: securityPolicyArgName,
Usage: "Security policy to set on the UVM. Leave empty to use an open door policy",
},
cli.StringFlag{
Name: execCommandLineArgName,
Usage: "Command to execute in the UVM.",
Expand All @@ -82,94 +106,169 @@ var lcowCommand = cli.Command{
Usage: "create the process in the UVM with a TTY enabled",
Destination: &lcowUseTerminal,
},
cli.StringSliceFlag{
Name: scsiMountsArgName,
Usage: "List of VHDs to SCSI mount into the UVM. Use repeat instances to add multiple. " +
"Value is of the form `'host[,guest[,w]]'`, where 'host' is path to the VHD, " +
`'guest' is an optional mount path inside the UVM, and 'w' mounts the VHD as writeable`,
},
cli.StringSliceFlag{
Name: shareFilesArgName,
Usage: "List of paths or files to plan9 share into the UVM. Use repeat instances to add multiple. " +
"Value is of the form `'host,guest[,w]' where 'host' is path to the VHD, " +
`'guest' is the mount path inside the UVM, and 'w' sets the shared files to writeable`,
},
},
Action: func(c *cli.Context) error {
runMany(c, func(id string) error {
options := uvm.NewDefaultOptionsLCOW(id, "")
setGlobalOptions(c, options.Options)
useGcs := c.GlobalBool(gcsArgName)
options.UseGuestConnection = useGcs
ctx := context.Background()

if c.IsSet(kernelDirectArgName) {
options.KernelDirect = c.Bool(kernelDirectArgName)
}
if c.IsSet(rootFSTypeArgName) {
switch strings.ToLower(c.String(rootFSTypeArgName)) {
case "initrd":
options.RootFSFile = uvm.InitrdFile
options.PreferredRootFSType = uvm.PreferredRootFSTypeInitRd
case "vhd":
options.RootFSFile = uvm.VhdFile
options.PreferredRootFSType = uvm.PreferredRootFSTypeVHD
default:
logrus.Fatalf("Unrecognized value '%s' for option %s", c.String(rootFSTypeArgName), rootFSTypeArgName)
}
}
if c.IsSet(kernelArgsArgName) {
options.KernelBootOptions = c.String(kernelArgsArgName)
}
if c.IsSet(vpMemMaxCountArgName) {
options.VPMemDeviceCount = uint32(c.Uint(vpMemMaxCountArgName))
}
if c.IsSet(vpMemMaxSizeArgName) {
options.VPMemSizeBytes = c.Uint64(vpMemMaxSizeArgName) * memory.MiB // convert from MB to bytes
}
if !useGcs {
if c.IsSet(execCommandLineArgName) {
options.ExecCommandLine = c.String(execCommandLineArgName)
}
if c.IsSet(forwardStdoutArgName) {
options.ForwardStdout = c.Bool(forwardStdoutArgName)
}
if c.IsSet(forwardStderrArgName) {
options.ForwardStderr = c.Bool(forwardStderrArgName)
}
if c.IsSet(outputHandlingArgName) {
switch strings.ToLower(c.String(outputHandlingArgName)) {
case "stdout":
options.OutputHandler = uvm.OutputHandler(func(r io.Reader) {
_, _ = io.Copy(os.Stdout, r)
})
default:
logrus.Fatalf("Unrecognized value '%s' for option %s", c.String(outputHandlingArgName), outputHandlingArgName)
}
}
}
if c.IsSet(consolePipeArgName) {
options.ConsolePipe = c.String(consolePipeArgName)
options, err := createLCOWOptions(ctx, c, id)
if err != nil {
return err
}

if err := runLCOW(context.TODO(), options, c); err != nil {
if err := runLCOW(ctx, options, c); err != nil {
return err
}

return nil
})

return nil
},
}

func init() {
lcowCommand.CustomHelpTemplate = cli.CommandHelpTemplate + "EXAMPLES:\n" +
`.\uvmboot.exe -gcs lcow -boot-files-path "C:\ContainerPlat\LinuxBootFiles" -root-fs-type vhd -t -exec "/bin/bash"`
}

func createLCOWOptions(_ context.Context, c *cli.Context, id string) (*uvm.OptionsLCOW, error) {
options := uvm.NewDefaultOptionsLCOW(id, "")
setGlobalOptions(c, options.Options)

// boot
if c.IsSet(bootFilesPathArgName) {
options.BootFilesPath = c.String(bootFilesPathArgName)
}

// kernel
if c.IsSet(kernelDirectArgName) {
options.KernelDirect = c.Bool(kernelDirectArgName)
}
if c.IsSet(kernelFileArgName) {
switch strings.ToLower(c.String(kernelFileArgName)) {
case uvm.KernelFile:
options.KernelFile = uvm.KernelFile
case uvm.UncompressedKernelFile:
options.KernelFile = uvm.UncompressedKernelFile
default:
return nil, unrecognizedError(c.String(kernelFileArgName), kernelFileArgName)
}
}
if c.IsSet(kernelArgsArgName) {
options.KernelBootOptions = c.String(kernelArgsArgName)
}

// rootfs
if c.IsSet(rootFSTypeArgName) {
switch strings.ToLower(c.String(rootFSTypeArgName)) {
case "initrd":
options.RootFSFile = uvm.InitrdFile
options.PreferredRootFSType = uvm.PreferredRootFSTypeInitRd
case "vhd":
options.RootFSFile = uvm.VhdFile
options.PreferredRootFSType = uvm.PreferredRootFSTypeVHD
default:
return nil, unrecognizedError(c.String(rootFSTypeArgName), rootFSTypeArgName)
}
}

if c.IsSet(vpMemMaxCountArgName) {
options.VPMemDeviceCount = uint32(c.Uint(vpMemMaxCountArgName))
}
if c.IsSet(vpMemMaxSizeArgName) {
options.VPMemSizeBytes = c.Uint64(vpMemMaxSizeArgName) * memory.MiB // convert from MB to bytes
}

// GCS
options.UseGuestConnection = useGCS
if !useGCS {
if c.IsSet(execCommandLineArgName) {
options.ExecCommandLine = c.String(execCommandLineArgName)
}
if c.IsSet(forwardStdoutArgName) {
options.ForwardStdout = c.Bool(forwardStdoutArgName)
}
if c.IsSet(forwardStderrArgName) {
options.ForwardStderr = c.Bool(forwardStderrArgName)
}
if c.IsSet(outputHandlingArgName) {
switch strings.ToLower(c.String(outputHandlingArgName)) {
case "stdout":
options.OutputHandler = uvm.OutputHandler(func(r io.Reader) {
_, _ = io.Copy(os.Stdout, r)
})
default:
return nil, unrecognizedError(c.String(outputHandlingArgName), outputHandlingArgName)
}
}
}
if c.IsSet(consolePipeArgName) {
options.ConsolePipe = c.String(consolePipeArgName)
}

// general settings
if lcowDisableTimeSync {
options.DisableTimeSyncService = true
}

if c.IsSet(securityPolicyArgName) {
options.SecurityPolicy = c.String(options.SecurityPolicy)
options.SecurityPolicyEnabled = true
}

return options, nil
}

func runLCOW(ctx context.Context, options *uvm.OptionsLCOW, c *cli.Context) error {
uvm, err := uvm.CreateLCOW(ctx, options)
vm, err := uvm.CreateLCOW(ctx, options)
if err != nil {
return err
}
defer uvm.Close()
defer vm.Close()

if err := uvm.Start(ctx); err != nil {
if err := vm.Start(ctx); err != nil {
return err
}

if c.IsSet(securityPolicyArgName) {
if err := vm.SetSecurityPolicy(ctx, options.SecurityPolicy); err != nil {
return fmt.Errorf("could not set UVM security policy: %w", err)
}
logrus.WithField("policy", options.SecurityPolicy).Debug("Set UVM security policy")
}

if err := mountSCSI(ctx, c, vm); err != nil {
return err
}

if err := shareFiles(ctx, c, vm); err != nil {
return err
}

if options.UseGuestConnection {
if err := execViaGcs(uvm, c); err != nil {
if err := execViaGcs(vm, c); err != nil {
return err
}
_ = uvm.Terminate(ctx)
_ = uvm.Wait()
return uvm.ExitError()
_ = vm.Terminate(ctx)
_ = vm.Wait()

return vm.ExitError()
}

return uvm.Wait()
return vm.Wait()
}

func execViaGcs(vm *uvm.UtilityVM, c *cli.Context) error {
Expand Down Expand Up @@ -197,5 +296,6 @@ func execViaGcs(vm *uvm.UtilityVM, c *cli.Context) error {
cmd.Stderr = os.Stdout // match non-GCS behavior and forward to stdout
}
}

return cmd.Run()
}
Loading