From 368e644058030b933379d63e8be2d990afd9463f Mon Sep 17 00:00:00 2001 From: Hamza El-Saawy Date: Wed, 13 Apr 2022 12:41:35 -0400 Subject: [PATCH 1/2] Support for LCOW tests and benchmarks Updated go-winio to include bugfixes for closing hvsockets, specifically for writing (which is needed by `internal\cmd` to signal that the stdin stream has finished. Expose or add certain functionality needed by tests and benchmarks both in the functional directory and uploaded directly onto a Linux UVM. For example, ability to access an hcsv2 containers init process and ID, as well creating and deleting network namespaces on the Linux UVM. Added more functionality to `internal\tools\uvmboot`, to include adding SCSI VHD and file mounts, as well as disabling the time sync, and setting the uVM security policy. Added winapi call to check if process is elevated, to allow returning a clear error message before attempting to start uVMs. Split out `internal\guest\runtime\runc\runc.go` into `runc.go`, `container.go` and `process.go`. Reorganized makefile to read from top to bottom, and added additional files to LCOW rootfs that include the time stamp of of the vhd creation, the image build date, and its full name (pulled from a `*.testdata.json` file in the LSG release, that appears to be the only location of that information). Signed-off-by: Hamza El-Saawy --- .gitignore | 12 +- functional_tests.ps1 | 12 - go.mod | 2 +- go.sum | 3 +- hcn/doc.go | 2 +- hcn/hcnnamespace.go | 8 +- internal/guest/runtime/hcsv2/container.go | 16 +- internal/guest/runtime/hcsv2/network.go | 10 +- internal/guest/runtime/hcsv2/network_test.go | 26 +- internal/guest/runtime/hcsv2/process.go | 6 +- .../runtime/hcsv2/standalone_container.go | 2 +- internal/guest/runtime/hcsv2/uvm.go | 23 +- internal/guest/runtime/runc/container.go | 460 +++++++++++++++ internal/guest/runtime/runc/process.go | 94 +++ internal/guest/runtime/runc/runc.go | 534 +----------------- internal/lcow/scratch.go | 2 +- internal/tools/uvmboot/lcow.go | 216 +++++-- internal/tools/uvmboot/main.go | 44 +- internal/tools/uvmboot/mounts.go | 105 ++++ internal/uvm/create_lcow.go | 2 +- internal/winapi/elevation.go | 11 + test/go.mod | 2 +- test/go.sum | 3 +- .../github.com/Microsoft/go-winio/README.md | 29 +- .../Microsoft/go-winio/backuptar/tar.go | 189 +++++-- .../github.com/Microsoft/go-winio/file.go | 6 + .../github.com/Microsoft/go-winio/hvsock.go | 17 +- .../Microsoft/go-winio/pkg/guid/guid.go | 9 - .../go-winio/pkg/guid/guid_nonwindows.go | 15 + .../go-winio/pkg/guid/guid_windows.go | 10 + .../pkg/security/grantvmgroupaccess.go | 15 +- .../go-winio/pkg/security/syscall_windows.go | 6 +- .../go-winio/pkg/security/zsyscall_windows.go | 24 +- .../Microsoft/go-winio/privilege.go | 5 +- .../github.com/Microsoft/go-winio/vhd/vhd.go | 67 ++- .../Microsoft/go-winio/vhd/zvhd_windows.go | 52 +- .../github.com/Microsoft/hcsshim/.gitignore | 12 +- .../Microsoft/hcsshim/functional_tests.ps1 | 12 - .../github.com/Microsoft/hcsshim/hcn/doc.go | 2 +- .../Microsoft/hcsshim/hcn/hcnnamespace.go | 8 +- .../hcsshim/internal/lcow/scratch.go | 2 +- .../hcsshim/internal/uvm/create_lcow.go | 2 +- .../hcsshim/internal/winapi/elevation.go | 11 + test/vendor/modules.txt | 4 +- .../github.com/Microsoft/go-winio/README.md | 29 +- .../Microsoft/go-winio/backuptar/tar.go | 189 +++++-- vendor/github.com/Microsoft/go-winio/file.go | 6 + .../github.com/Microsoft/go-winio/hvsock.go | 17 +- .../Microsoft/go-winio/pkg/etw/provider.go | 9 - .../Microsoft/go-winio/pkg/etw/wrapper_32.go | 15 + .../Microsoft/go-winio/pkg/etw/wrapper_64.go | 10 + .../Microsoft/go-winio/pkg/guid/guid.go | 9 - .../go-winio/pkg/guid/guid_nonwindows.go | 15 + .../go-winio/pkg/guid/guid_windows.go | 10 + .../pkg/security/grantvmgroupaccess.go | 15 +- .../go-winio/pkg/security/syscall_windows.go | 6 +- .../go-winio/pkg/security/zsyscall_windows.go | 24 +- .../Microsoft/go-winio/privilege.go | 5 +- .../github.com/Microsoft/go-winio/vhd/vhd.go | 67 ++- .../Microsoft/go-winio/vhd/zvhd_windows.go | 52 +- vendor/modules.txt | 4 +- 61 files changed, 1586 insertions(+), 988 deletions(-) delete mode 100644 functional_tests.ps1 create mode 100644 internal/guest/runtime/runc/container.go create mode 100644 internal/guest/runtime/runc/process.go create mode 100644 internal/tools/uvmboot/mounts.go create mode 100644 internal/winapi/elevation.go create mode 100644 test/vendor/github.com/Microsoft/go-winio/pkg/guid/guid_nonwindows.go create mode 100644 test/vendor/github.com/Microsoft/go-winio/pkg/guid/guid_windows.go delete mode 100644 test/vendor/github.com/Microsoft/hcsshim/functional_tests.ps1 create mode 100644 test/vendor/github.com/Microsoft/hcsshim/internal/winapi/elevation.go create mode 100644 vendor/github.com/Microsoft/go-winio/pkg/guid/guid_nonwindows.go create mode 100644 vendor/github.com/Microsoft/go-winio/pkg/guid/guid_windows.go diff --git a/.gitignore b/.gitignore index 292630f9ea..8c32a3a6ec 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ service/pkg/ *.img *.vhd *.tar.gz +*.tar # Make stuff .rootfs-done @@ -32,9 +33,16 @@ rootfs/* rootfs-conv/* *.o /build/ - deps/* out/* +# test results +test/results + +# ninja build +.ninja_log +build.ninja + +# go workspaces go.work -go.work.sum \ No newline at end of file +go.work.sum diff --git a/functional_tests.ps1 b/functional_tests.ps1 deleted file mode 100644 index ce6edbcf32..0000000000 --- a/functional_tests.ps1 +++ /dev/null @@ -1,12 +0,0 @@ -# Requirements so far: -# dockerd running -# - image microsoft/nanoserver (matching host base image) docker load -i c:\baseimages\nanoserver.tar -# - image alpine (linux) docker pull --platform=linux alpine - - -# TODO: Add this a parameter for debugging. ie "functional-tests -debug=$true" -#$env:HCSSHIM_FUNCTIONAL_TESTS_DEBUG="yes please" - -#pushd uvm -go test -v -tags "functional uvmcreate uvmscratch uvmscsi uvmvpmem uvmvsmb uvmp9" ./... -#popd \ No newline at end of file diff --git a/go.mod b/go.mod index cabbfe58e5..2eb1078231 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.17 require ( github.com/BurntSushi/toml v0.3.1 - github.com/Microsoft/go-winio v0.4.17 + github.com/Microsoft/go-winio v0.5.2 github.com/cenkalti/backoff/v4 v4.1.1 github.com/containerd/cgroups v1.0.1 github.com/containerd/console v1.0.2 diff --git a/go.sum b/go.sum index 2e89f181d7..c9b456a29c 100644 --- a/go.sum +++ b/go.sum @@ -44,8 +44,9 @@ github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.4.17 h1:iT12IBVClFevaf8PuVyi3UmZOVh4OqnaLxDTW2O6j3w= github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= diff --git a/hcn/doc.go b/hcn/doc.go index 83b2fffb02..33f67ac9c6 100644 --- a/hcn/doc.go +++ b/hcn/doc.go @@ -1,3 +1,3 @@ // Package hcn is a shim for the Host Compute Networking (HCN) service, which manages networking for Windows Server -// containers and Hyper-V containers. Previous to RS5, HCN was referred to as Host Networking Service (HNS). +// containers and Hyper-V containers. Prior to RS5, HCN was referred to as Host Networking Service (HNS). package hcn diff --git a/hcn/hcnnamespace.go b/hcn/hcnnamespace.go index 7539e39fa8..44ba2fa1fd 100644 --- a/hcn/hcnnamespace.go +++ b/hcn/hcnnamespace.go @@ -296,11 +296,11 @@ func GetNamespaceContainerIds(namespaceId string) ([]string, error) { var containerIds []string for _, resource := range namespace.Resources { if resource.Type == "Container" { - var contaienrResource NamespaceResourceContainer - if err := json.Unmarshal([]byte(resource.Data), &contaienrResource); err != nil { + var containerResource NamespaceResourceContainer + if err := json.Unmarshal([]byte(resource.Data), &containerResource); err != nil { return nil, err } - containerIds = append(containerIds, contaienrResource.Id) + containerIds = append(containerIds, containerResource.Id) } } return containerIds, nil @@ -377,7 +377,7 @@ func (namespace *HostComputeNamespace) Sync() error { } shimPath := runhcs.VMPipePath(cfg.HostUniqueID) if err := runhcs.IssueVMRequest(shimPath, &req); err != nil { - // The shim is likey gone. Simply ignore the sync as if it didn't exist. + // The shim is likely gone. Simply ignore the sync as if it didn't exist. if perr, ok := err.(*os.PathError); ok && perr.Err == syscall.ERROR_FILE_NOT_FOUND { // Remove the reg key there is no point to try again _ = cfg.Remove() diff --git a/internal/guest/runtime/hcsv2/container.go b/internal/guest/runtime/hcsv2/container.go index d890d0c2b0..5de401a558 100644 --- a/internal/guest/runtime/hcsv2/container.go +++ b/internal/guest/runtime/hcsv2/container.go @@ -107,6 +107,16 @@ func (c *Container) ExecProcess(ctx context.Context, process *oci.Process, conSe return pid, nil } +// InitProcess returns the container's init process +func (c *Container) InitProcess() Process { + //todo: thread a context to this function call + logrus.WithFields(logrus.Fields{ + logfields.ContainerID: c.id, + }).Info("opengcs::Container::InitProcess") + + return c.initProcess +} + // GetProcess returns the Process with the matching 'pid'. If the 'pid' does // not exit returns error. func (c *Container) GetProcess(pid uint32) (Process, error) { @@ -114,7 +124,7 @@ func (c *Container) GetProcess(pid uint32) (Process, error) { logrus.WithFields(logrus.Fields{ logfields.ContainerID: c.id, logfields.ProcessID: pid, - }).Info("opengcs::Container::GetProcesss") + }).Info("opengcs::Container::GetProcess") if c.initProcess.pid == pid { return c.initProcess, nil } @@ -220,3 +230,7 @@ func (c *Container) GetStats(ctx context.Context) (*v1.Metrics, error) { func (c *Container) modifyContainerConstraints(ctx context.Context, rt guestrequest.RequestType, cc *guestresource.LCOWContainerConstraints) (err error) { return c.Update(ctx, cc.Linux) } + +func (c *Container) ID() string { + return c.id +} diff --git a/internal/guest/runtime/hcsv2/network.go b/internal/guest/runtime/hcsv2/network.go index 9feb7afaed..145d150497 100644 --- a/internal/guest/runtime/hcsv2/network.go +++ b/internal/guest/runtime/hcsv2/network.go @@ -51,9 +51,9 @@ func getNetworkNamespace(id string) (*namespace, error) { return ns, nil } -// getOrAddNetworkNamespace returns the namespace found by `id` or creates a new +// GetOrAddNetworkNamespace returns the namespace found by `id` or creates a new // one and assigns `id. -func getOrAddNetworkNamespace(id string) *namespace { +func GetOrAddNetworkNamespace(id string) *namespace { id = strings.ToLower(id) namespaceSync.Lock() @@ -69,8 +69,8 @@ func getOrAddNetworkNamespace(id string) *namespace { return ns } -// removeNetworkNamespace removes the in-memory `namespace` found by `id`. -func removeNetworkNamespace(ctx context.Context, id string) (err error) { +// RemoveNetworkNamespace removes the in-memory `namespace` found by `id`. +func RemoveNetworkNamespace(ctx context.Context, id string) (err error) { _, span := trace.StartSpan(ctx, "hcsv2::removeNetworkNamespace") defer span.End() defer func() { oc.SetSpanStatus(span, err) }() @@ -123,7 +123,7 @@ func (n *namespace) AssignContainerPid(ctx context.Context, pid int) (err error) defer n.m.Unlock() if n.pid != 0 { - return errors.Errorf("previously assigned container pid: %d", n.pid) + return errors.Errorf("previously assigned container pid to network namespace %q: %d", n.id, n.pid) } n.pid = pid diff --git a/internal/guest/runtime/hcsv2/network_test.go b/internal/guest/runtime/hcsv2/network_test.go index 3ac4b9eed1..899bf08a0a 100644 --- a/internal/guest/runtime/hcsv2/network_test.go +++ b/internal/guest/runtime/hcsv2/network_test.go @@ -12,7 +12,7 @@ import ( func Test_getNetworkNamespace_NotExist(t *testing.T) { defer func() { - err := removeNetworkNamespace(context.Background(), t.Name()) + err := RemoveNetworkNamespace(context.Background(), t.Name()) if err != nil { t.Errorf("failed to remove ns with error: %v", err) } @@ -29,13 +29,13 @@ func Test_getNetworkNamespace_NotExist(t *testing.T) { func Test_getNetworkNamespace_PreviousExist(t *testing.T) { defer func() { - err := removeNetworkNamespace(context.Background(), t.Name()) + err := RemoveNetworkNamespace(context.Background(), t.Name()) if err != nil { t.Errorf("failed to remove ns with error: %v", err) } }() - ns1 := getOrAddNetworkNamespace(t.Name()) + ns1 := GetOrAddNetworkNamespace(t.Name()) if ns1 == nil { t.Fatal("namespace ns1 should not be nil") } @@ -50,13 +50,13 @@ func Test_getNetworkNamespace_PreviousExist(t *testing.T) { func Test_getOrAddNetworkNamespace_NotExist(t *testing.T) { defer func() { - err := removeNetworkNamespace(context.Background(), t.Name()) + err := RemoveNetworkNamespace(context.Background(), t.Name()) if err != nil { t.Errorf("failed to remove ns with error: %v", err) } }() - ns := getOrAddNetworkNamespace(t.Name()) + ns := GetOrAddNetworkNamespace(t.Name()) if ns == nil { t.Fatalf("namespace should not be nil") } @@ -64,21 +64,21 @@ func Test_getOrAddNetworkNamespace_NotExist(t *testing.T) { func Test_getOrAddNetworkNamespace_PreviousExist(t *testing.T) { defer func() { - err := removeNetworkNamespace(context.Background(), t.Name()) + err := RemoveNetworkNamespace(context.Background(), t.Name()) if err != nil { t.Errorf("failed to remove ns with error: %v", err) } }() - ns1 := getOrAddNetworkNamespace(t.Name()) - ns2 := getOrAddNetworkNamespace(t.Name()) + ns1 := GetOrAddNetworkNamespace(t.Name()) + ns2 := GetOrAddNetworkNamespace(t.Name()) if ns1 != ns2 { t.Fatalf("ns1 %+v != ns2 %+v", ns1, ns2) } } func Test_removeNetworkNamespace_NotExist(t *testing.T) { - err := removeNetworkNamespace(context.Background(), t.Name()) + err := RemoveNetworkNamespace(context.Background(), t.Name()) if err != nil { t.Fatalf("failed to remove non-existing ns with error: %v", err) } @@ -86,7 +86,7 @@ func Test_removeNetworkNamespace_NotExist(t *testing.T) { func Test_removeNetworkNamespace_HasAdapters(t *testing.T) { defer func() { - err := removeNetworkNamespace(context.Background(), t.Name()) + err := RemoveNetworkNamespace(context.Background(), t.Name()) if err != nil { t.Errorf("failed to remove ns with error: %v", err) } @@ -96,7 +96,7 @@ func Test_removeNetworkNamespace_HasAdapters(t *testing.T) { networkInstanceIDToName = nsOld }() - ns := getOrAddNetworkNamespace(t.Name()) + ns := GetOrAddNetworkNamespace(t.Name()) networkInstanceIDToName = func(ctx context.Context, id string, _ bool) (string, error) { return "/dev/sdz", nil @@ -105,7 +105,7 @@ func Test_removeNetworkNamespace_HasAdapters(t *testing.T) { if err != nil { t.Fatalf("failed to add adapter: %v", err) } - err = removeNetworkNamespace(context.Background(), t.Name()) + err = RemoveNetworkNamespace(context.Background(), t.Name()) if err == nil { t.Fatal("should have failed to delete namespace with adapters") } @@ -113,7 +113,7 @@ func Test_removeNetworkNamespace_HasAdapters(t *testing.T) { if err != nil { t.Fatalf("failed to remove adapter: %v", err) } - err = removeNetworkNamespace(context.Background(), t.Name()) + err = RemoveNetworkNamespace(context.Background(), t.Name()) if err != nil { t.Fatalf("should not have failed to delete empty namepace got: %v", err) } diff --git a/internal/guest/runtime/hcsv2/process.go b/internal/guest/runtime/hcsv2/process.go index 0a7dee8b30..5731932e70 100644 --- a/internal/guest/runtime/hcsv2/process.go +++ b/internal/guest/runtime/hcsv2/process.go @@ -55,7 +55,7 @@ type containerProcess struct { // (runtime.Process).Wait() call returns, and exitCode has been updated. exitWg sync.WaitGroup - // Used to allow addtion/removal to the writersWg after an initial wait has + // Used to allow addition/removal to the writersWg after an initial wait has // already been issued. It is not safe to call Add/Done without holding this // lock. writersSyncRoot sync.Mutex @@ -67,6 +67,8 @@ type containerProcess struct { writersCalled bool } +var _ Process = &containerProcess{} + // newProcess returns a containerProcess struct that has been initialized with // an outstanding wait for process exit, and post exit an outstanding wait for // process cleanup to release all resources once at least 1 waiter has @@ -262,6 +264,8 @@ type externalProcess struct { remove func(pid int) } +var _ Process = &containerProcess{} + func (ep *externalProcess) Kill(ctx context.Context, signal syscall.Signal) error { if err := syscall.Kill(int(ep.cmd.Process.Pid), signal); err != nil { if err == syscall.ESRCH { diff --git a/internal/guest/runtime/hcsv2/standalone_container.go b/internal/guest/runtime/hcsv2/standalone_container.go index 0e232e7eda..a3d2bc1ba9 100644 --- a/internal/guest/runtime/hcsv2/standalone_container.go +++ b/internal/guest/runtime/hcsv2/standalone_container.go @@ -103,7 +103,7 @@ func setupStandaloneContainerSpec(ctx context.Context, id string, spec *oci.Spec // Write resolv.conf if !specInternal.MountPresent("/etc/resolv.conf", spec.Mounts) { - ns := getOrAddNetworkNamespace(getNetworkNamespaceID(spec)) + ns := GetOrAddNetworkNamespace(getNetworkNamespaceID(spec)) var searches, servers []string for _, n := range ns.Adapters() { if len(n.DNSSuffix) > 0 { diff --git a/internal/guest/runtime/hcsv2/uvm.go b/internal/guest/runtime/hcsv2/uvm.go index 568f0ed448..c4f050acb2 100644 --- a/internal/guest/runtime/hcsv2/uvm.go +++ b/internal/guest/runtime/hcsv2/uvm.go @@ -116,10 +116,29 @@ func (h *Host) SetSecurityPolicy(base64Policy string) error { return nil } +func (h *Host) SecurityPolicyEnforcer() securitypolicy.SecurityPolicyEnforcer { + return h.securityPolicyEnforcer +} + +func (h *Host) Transport() transport.Transport { + return h.vsock +} + func (h *Host) RemoveContainer(id string) { h.containersMutex.Lock() defer h.containersMutex.Unlock() + c, ok := h.containers[id] + if !ok { + return + } + + // delete the network namespace for standalone and sandbox containers + criType, isCRI := c.spec.Annotations[annotations.KubernetesContainerType] + if !isCRI || criType == "sandbox" { + RemoveNetworkNamespace(context.Background(), id) + } + delete(h.containers, id) } @@ -567,7 +586,7 @@ func modifyCombinedLayers(ctx context.Context, rt guestrequest.RequestType, cl * func modifyNetwork(ctx context.Context, rt guestrequest.RequestType, na *guestresource.LCOWNetworkAdapter) (err error) { switch rt { case guestrequest.RequestTypeAdd: - ns := getOrAddNetworkNamespace(na.NamespaceID) + ns := GetOrAddNetworkNamespace(na.NamespaceID) if err := ns.AddAdapter(ctx, na); err != nil { return err } @@ -575,7 +594,7 @@ func modifyNetwork(ctx context.Context, rt guestrequest.RequestType, na *guestre // container or not so it must always call `Sync`. return ns.Sync(ctx) case guestrequest.RequestTypeRemove: - ns := getOrAddNetworkNamespace(na.ID) + ns := GetOrAddNetworkNamespace(na.ID) if err := ns.RemoveAdapter(ctx, na.ID); err != nil { return err } diff --git a/internal/guest/runtime/runc/container.go b/internal/guest/runtime/runc/container.go new file mode 100644 index 0000000000..92f79f7b78 --- /dev/null +++ b/internal/guest/runtime/runc/container.go @@ -0,0 +1,460 @@ +//go:build linux +// +build linux + +package runc + +import ( + "encoding/json" + "io/ioutil" + "net" + "os" + "path/filepath" + "strconv" + "strings" + "syscall" + + "github.com/Microsoft/hcsshim/internal/guest/runtime" + "github.com/Microsoft/hcsshim/internal/guest/stdio" + "github.com/Microsoft/hcsshim/internal/logfields" + oci "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +type container struct { + r *runcRuntime + id string + init *process + // ownsPidNamespace indicates whether the container's init process is also + // the init process for its pid namespace. + ownsPidNamespace bool +} + +var _ runtime.Container = &container{} + +func (c *container) ID() string { + return c.id +} + +func (c *container) Pid() int { + return c.init.Pid() +} + +func (c *container) Tty() *stdio.TtyRelay { + return c.init.ttyRelay +} + +func (c *container) PipeRelay() *stdio.PipeRelay { + return c.init.pipeRelay +} + +// Start unblocks the container's init process created by the call to +// CreateContainer. +func (c *container) Start() error { + logPath := c.r.getLogPath(c.id) + args := []string{"start", c.id} + cmd := createRuncCommand(logPath, args...) + out, err := cmd.CombinedOutput() + if err != nil { + runcErr := getRuncLogError(logPath) + c.r.cleanupContainer(c.id) + return errors.Wrapf(runcErr, "runc start failed with %v: %s", err, string(out)) + } + + return nil +} + +// ExecProcess executes a new process, represented as an OCI process struct, +// inside an already-running container. +func (c *container) ExecProcess(process *oci.Process, stdioSet *stdio.ConnectionSet) (p runtime.Process, err error) { + p, err = c.runExecCommand(process, stdioSet) + if err != nil { + return nil, err + } + + return p, nil +} + +// Kill sends the specified signal to the container's init process. +func (c *container) Kill(signal syscall.Signal) error { + logrus.WithField(logfields.ContainerID, c.id).Debug("runc::container::Kill") + logPath := c.r.getLogPath(c.id) + args := []string{"kill"} + if signal == syscall.SIGTERM || signal == syscall.SIGKILL { + args = append(args, "--all") + } + args = append(args, c.id, strconv.Itoa(int(signal))) + cmd := createRuncCommand(logPath, args...) + out, err := cmd.CombinedOutput() + if err != nil { + runcErr := getRuncLogError(logPath) + return errors.Wrapf(runcErr, "unknown runc error after kill %v: %s", err, string(out)) + } + + return nil +} + +// Delete deletes any state created for the container by either this wrapper or +// runC itself. +func (c *container) Delete() error { + logrus.WithField(logfields.ContainerID, c.id).Debug("runc::container::Delete") + logPath := c.r.getLogPath(c.id) + args := []string{"delete", c.id} + cmd := createRuncCommand(logPath, args...) + out, err := cmd.CombinedOutput() + if err != nil { + runcErr := getRuncLogError(logPath) + return errors.Wrapf(runcErr, "runc delete failed with %v: %s", err, string(out)) + } + if err := c.r.cleanupContainer(c.id); err != nil { + return err + } + + return nil +} + +// Pause suspends all processes running in the container. +func (c *container) Pause() error { + logPath := c.r.getLogPath(c.id) + args := []string{"pause", c.id} + cmd := createRuncCommand(logPath, args...) + out, err := cmd.CombinedOutput() + if err != nil { + runcErr := getRuncLogError(logPath) + return errors.Wrapf(runcErr, "runc pause failed with %v: %s", err, string(out)) + } + return nil +} + +// Resume unsuspends processes running in the container. +func (c *container) Resume() error { + logPath := c.r.getLogPath(c.id) + args := []string{"resume", c.id} + cmd := createRuncCommand(logPath, args...) + out, err := cmd.CombinedOutput() + if err != nil { + runcErr := getRuncLogError(logPath) + return errors.Wrapf(runcErr, "runc resume failed with %v: %s", err, string(out)) + } + return nil +} + +// GetState returns information about the given container. +func (c *container) GetState() (*runtime.ContainerState, error) { + logPath := c.r.getLogPath(c.id) + args := []string{"state", c.id} + cmd := createRuncCommand(logPath, args...) + out, err := cmd.CombinedOutput() + if err != nil { + runcErr := getRuncLogError(logPath) + return nil, errors.Wrapf(runcErr, "runc state failed with %v: %s", err, string(out)) + } + var state runtime.ContainerState + if err := json.Unmarshal(out, &state); err != nil { + return nil, errors.Wrapf(err, "failed to unmarshal the state for container %s", c.id) + } + return &state, nil +} + +// Exists returns true if the container exists, false if it doesn't +// exist. +// It should be noted that containers that have stopped but have not been +// deleted are still considered to exist. +func (c *container) Exists() (bool, error) { + // use global path because container may not exist + logPath := c.r.getGlobalLogPath() + args := []string{"state", c.id} + cmd := createRuncCommand(logPath, args...) + out, err := cmd.CombinedOutput() + if err != nil { + runcErr := getRuncLogError(logPath) + if errors.Is(runcErr, runtime.ErrContainerDoesNotExist) { + return false, nil + } + return false, errors.Wrapf(runcErr, "runc state failed with %v: %s", err, string(out)) + } + return true, nil +} + +// GetRunningProcesses gets only the running processes associated with the given +// container. This excludes zombie processes. +func (c *container) GetRunningProcesses() ([]runtime.ContainerProcessState, error) { + pids, err := c.r.getRunningPids(c.id) + if err != nil { + return nil, err + } + + pidMap := map[int]*runtime.ContainerProcessState{} + // Initialize all processes with a pid and command, and mark correctly that + // none of them are zombies. Default CreatedByRuntime to false. + for _, pid := range pids { + command, err := c.r.getProcessCommand(pid) + if err != nil { + if errors.Is(err, unix.ENOENT) { + // process has exited between getting the running pids above + // and now, ignore error + continue + } + return nil, err + } + pidMap[pid] = &runtime.ContainerProcessState{Pid: pid, Command: command, CreatedByRuntime: false, IsZombie: false} + } + + // For each process state directory which corresponds to a running pid, set + // that the process was created by the Runtime. + processDirs, err := ioutil.ReadDir(filepath.Join(containerFilesDir, c.id)) + if err != nil { + return nil, errors.Wrapf(err, "failed to read the contents of container directory %s", filepath.Join(containerFilesDir, c.id)) + } + for _, processDir := range processDirs { + if processDir.Name() != initPidFilename { + pid, err := strconv.Atoi(processDir.Name()) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse string \"%s\" as pid", processDir.Name()) + } + if _, ok := pidMap[pid]; ok { + pidMap[pid].CreatedByRuntime = true + } + } + } + + return c.r.pidMapToProcessStates(pidMap), nil +} + +// GetAllProcesses gets all processes associated with the given container, +// including both running and zombie processes. +func (c *container) GetAllProcesses() ([]runtime.ContainerProcessState, error) { + runningPids, err := c.r.getRunningPids(c.id) + if err != nil { + return nil, err + } + + logrus.WithFields(logrus.Fields{ + "cid": c.id, + "pids": runningPids, + }).Debug("running container pids") + + pidMap := map[int]*runtime.ContainerProcessState{} + // Initialize all processes with a pid and command, leaving CreatedByRuntime + // and IsZombie at the default value of false. + for _, pid := range runningPids { + command, err := c.r.getProcessCommand(pid) + if err != nil { + if errors.Is(err, unix.ENOENT) { + // process has exited between getting the running pids above + // and now, ignore error + continue + } + return nil, err + } + pidMap[pid] = &runtime.ContainerProcessState{Pid: pid, Command: command, CreatedByRuntime: false, IsZombie: false} + } + + processDirs, err := ioutil.ReadDir(filepath.Join(containerFilesDir, c.id)) + if err != nil { + return nil, errors.Wrapf(err, "failed to read the contents of container directory %s", filepath.Join(containerFilesDir, c.id)) + } + // Loop over every process state directory. Since these processes have + // process state directories, CreatedByRuntime will be true for all of them. + for _, processDir := range processDirs { + if processDir.Name() != initPidFilename { + pid, err := strconv.Atoi(processDir.Name()) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse string \"%s\" into pid", processDir.Name()) + } + if c.r.processExists(pid) { + // If the process exists in /proc and is in the pidMap, it must + // be a running non-zombie. + if _, ok := pidMap[pid]; ok { + pidMap[pid].CreatedByRuntime = true + } else { + // Otherwise, since it's in /proc but not running, it must + // be a zombie. + command, err := c.r.getProcessCommand(pid) + if err != nil { + if errors.Is(err, unix.ENOENT) { + // process has exited between checking that it exists and now, ignore error + continue + } + return nil, err + } + pidMap[pid] = &runtime.ContainerProcessState{Pid: pid, Command: command, CreatedByRuntime: true, IsZombie: true} + } + } + } + } + return c.r.pidMapToProcessStates(pidMap), nil +} + +// GetInitProcess gets the init processes associated with the given container, +// including both running and zombie processes. +func (c *container) GetInitProcess() (runtime.Process, error) { + if c.init == nil { + return nil, errors.New("container has no init process") + } + return c.init, nil +} + +// Wait waits on every non-init process in the container, and then performs a +// final wait on the init process. The exit code returned is the exit code +// acquired from waiting on the init process. +func (c *container) Wait() (int, error) { + entity := logrus.WithField(logfields.ContainerID, c.id) + processes, err := c.GetAllProcesses() + if err != nil { + return -1, err + } + for _, process := range processes { + // Only wait on non-init processes that were created with exec. + if process.Pid != c.init.pid && process.CreatedByRuntime { + // FUTURE-jstarks: Consider waiting on the child process's relays as + // well (as in p.Wait()). This may not matter as long as the relays + // finish "soon" after Wait() returns since HCS expects the stdio + // connections to close before container shutdown can complete. + entity.WithField(logfields.ProcessID, process.Pid).Debug("waiting on container exec process") + c.r.waitOnProcess(process.Pid) + } + } + exitCode, err := c.init.Wait() + entity.Debug("runc::container::init process wait completed") + if err != nil { + return -1, err + } + return exitCode, nil +} + +// runExecCommand sets up the arguments for calling runc exec. +func (c *container) runExecCommand(processDef *oci.Process, stdioSet *stdio.ConnectionSet) (p runtime.Process, err error) { + // Create a temporary random directory to store the process's files. + tempProcessDir, err := ioutil.TempDir(containerFilesDir, c.id) + if err != nil { + return nil, err + } + + f, err := os.Create(filepath.Join(tempProcessDir, "process.json")) + if err != nil { + return nil, errors.Wrapf(err, "failed to create process.json file at %s", filepath.Join(tempProcessDir, "process.json")) + } + defer f.Close() + if err := json.NewEncoder(f).Encode(processDef); err != nil { + return nil, errors.Wrap(err, "failed to encode JSON into process.json file") + } + + args := []string{"exec"} + args = append(args, "-d", "--process", filepath.Join(tempProcessDir, "process.json")) + return c.startProcess(tempProcessDir, processDef.Terminal, stdioSet, args...) +} + +// startProcess performs the operations necessary to start a container process +// and properly handle its stdio. This function is used by both CreateContainer +// and ExecProcess. For V2 container creation stdioSet will be nil, in this case +// it is expected that the caller starts the relay previous to calling Start on +// the container. +func (c *container) startProcess(tempProcessDir string, hasTerminal bool, stdioSet *stdio.ConnectionSet, initialArgs ...string) (p *process, err error) { + args := initialArgs + + if err := setSubreaper(1); err != nil { + return nil, errors.Wrapf(err, "failed to set process as subreaper for process in container %s", c.id) + } + if err := c.r.makeLogDir(c.id); err != nil { + return nil, err + } + + logPath := c.r.getLogPath(c.id) + args = append(args, "--pid-file", filepath.Join(tempProcessDir, "pid")) + + var sockListener *net.UnixListener + if hasTerminal { + var consoleSockPath string + sockListener, consoleSockPath, err = c.r.createConsoleSocket(tempProcessDir) + if err != nil { + return nil, errors.Wrapf(err, "failed to create console socket for container %s", c.id) + } + defer sockListener.Close() + args = append(args, "--console-socket", consoleSockPath) + } + args = append(args, c.id) + + cmd := createRuncCommand(logPath, args...) + + var pipeRelay *stdio.PipeRelay + if !hasTerminal { + pipeRelay, err = stdio.NewPipeRelay(stdioSet) + if err != nil { + return nil, errors.Wrapf(err, "failed to create a pipe relay connection set for container %s", c.id) + } + fileSet, err := pipeRelay.Files() + if err != nil { + return nil, errors.Wrapf(err, "failed to get files for connection set for container %s", c.id) + } + // Closing the FileSet here is fine as that end of the pipes will have + // already been copied into the child process. + defer fileSet.Close() + if fileSet.In != nil { + cmd.Stdin = fileSet.In + } + if fileSet.Out != nil { + cmd.Stdout = fileSet.Out + } + if fileSet.Err != nil { + cmd.Stderr = fileSet.Err + } + } + + if err := cmd.Run(); err != nil { + runcErr := getRuncLogError(logPath) + return nil, errors.Wrapf(runcErr, "failed to run runc create/exec call for container %s with %v", c.id, err) + } + + var ttyRelay *stdio.TtyRelay + if hasTerminal { + var master *os.File + master, err = c.r.getMasterFromSocket(sockListener) + if err != nil { + cmd.Process.Kill() + return nil, errors.Wrapf(err, "failed to get pty master for process in container %s", c.id) + } + // Keep master open for the relay unless there is an error. + defer func() { + if err != nil { + master.Close() + } + }() + ttyRelay = stdio.NewTtyRelay(stdioSet, master) + } + + // Rename the process's directory to its pid. + pid, err := c.r.readPidFile(filepath.Join(tempProcessDir, "pid")) + if err != nil { + return nil, err + } + if err := os.Rename(tempProcessDir, c.r.getProcessDir(c.id, pid)); err != nil { + return nil, err + } + + if ttyRelay != nil && stdioSet != nil { + ttyRelay.Start() + } + if pipeRelay != nil && stdioSet != nil { + pipeRelay.Start() + } + return &process{c: c, pid: pid, ttyRelay: ttyRelay, pipeRelay: pipeRelay}, nil +} + +func (c *container) Update(resources interface{}) error { + jsonResources, err := json.Marshal(resources) + if err != nil { + return err + } + logPath := c.r.getLogPath(c.id) + args := []string{"update", "--resources", "-", c.id} + cmd := createRuncCommand(logPath, args...) + cmd.Stdin = strings.NewReader(string(jsonResources)) + out, err := cmd.CombinedOutput() + if err != nil { + runcErr := getRuncLogError(logPath) + return errors.Wrapf(runcErr, "runc update request %s failed with %v: %s", string(jsonResources), err, string(out)) + } + return nil +} diff --git a/internal/guest/runtime/runc/process.go b/internal/guest/runtime/runc/process.go new file mode 100644 index 0000000000..05e8525e4a --- /dev/null +++ b/internal/guest/runtime/runc/process.go @@ -0,0 +1,94 @@ +//go:build linux +// +build linux + +package runc + +import ( + "syscall" + + "github.com/Microsoft/hcsshim/internal/guest/runtime" + "github.com/Microsoft/hcsshim/internal/guest/stdio" + "github.com/Microsoft/hcsshim/internal/logfields" + "github.com/sirupsen/logrus" +) + +// process represents a process running in a container. It can either be a +// container's init process, or an exec process in a container. +type process struct { + c *container + pid int + ttyRelay *stdio.TtyRelay + pipeRelay *stdio.PipeRelay +} + +var _ runtime.Process = &process{} + +func (p *process) Pid() int { + return p.pid +} + +func (p *process) Tty() *stdio.TtyRelay { + return p.ttyRelay +} + +func (p *process) PipeRelay() *stdio.PipeRelay { + return p.pipeRelay +} + +// Delete deletes any state created for the process by either this wrapper or +// runC itself. +func (p *process) Delete() error { + if err := p.c.r.cleanupProcess(p.c.id, p.pid); err != nil { + return err + } + return nil +} + +func (p *process) Wait() (int, error) { + exitCode, err := p.c.r.waitOnProcess(p.pid) + + l := logrus.WithField(logfields.ContainerID, p.c.id) + l.WithField(logfields.ContainerID, p.pid).Debug("process wait completed") + + // If the init process for the container has exited, kill everything else in + // the container. Runc uses the devices cgroup of the container ot determine + // what other processes to kill. + // + // We don't issue the kill if the container owns its own pid namespace, + // because in that case the container kernel will kill everything in the pid + // namespace automatically (as the container init will be the pid namespace + // init). This prevents a potential issue where two containers share cgroups + // but have their own pid namespaces. If we didn't handle this case, runc + // would kill the processes in both containers when trying to kill + // either one of them. + if p == p.c.init && !p.c.ownsPidNamespace { + // If the init process of a pid namespace terminates, the kernel + // terminates all other processes in the namespace with SIGKILL. We + // simulate the same behavior. + if err := p.c.Kill(syscall.SIGKILL); err != nil { + l.WithError(err).Error("failed to terminate container after process wait") + } + } + + // Wait on the relay to drain any output that was already buffered. + // + // At this point, if this is the init process for the container, everything + // else in the container has been killed, so the write ends of the stdio + // relay will have been closed. + // + // If this is a container exec process instead, then it is possible the + // relay waits will hang waiting for the write ends to close. This can occur + // if the exec spawned any child processes that inherited its stdio. + // Currently we do not do anything to avoid hanging in this case, but in the + // future we could add special handling. + if p.ttyRelay != nil { + p.ttyRelay.Wait() + } + if p.pipeRelay != nil { + p.pipeRelay.Wait() + } + + l.WithField(logfields.ProcessID, p.pid).Debug("relay wait completed") + + return exitCode, err +} diff --git a/internal/guest/runtime/runc/runc.go b/internal/guest/runtime/runc/runc.go index 2e31c26cfb..633263c710 100644 --- a/internal/guest/runtime/runc/runc.go +++ b/internal/guest/runtime/runc/runc.go @@ -6,7 +6,6 @@ package runc import ( "encoding/json" "io/ioutil" - "net" "os" "path" "path/filepath" @@ -17,10 +16,8 @@ import ( "github.com/Microsoft/hcsshim/internal/guest/commonutils" "github.com/Microsoft/hcsshim/internal/guest/runtime" "github.com/Microsoft/hcsshim/internal/guest/stdio" - "github.com/Microsoft/hcsshim/internal/logfields" oci "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" - "github.com/sirupsen/logrus" "golang.org/x/sys/unix" ) @@ -33,67 +30,8 @@ func setSubreaper(i int) error { return unix.Prctl(unix.PR_SET_CHILD_SUBREAPER, uintptr(i), 0, 0, 0) } -// runcRuntime is an implementation of the Runtime interface which uses runC as -// the container runtime. -type runcRuntime struct { - runcLogBasePath string -} - -var _ runtime.Runtime = &runcRuntime{} - -type container struct { - r *runcRuntime - id string - init *process - // ownsPidNamespace indicates whether the container's init process is also - // the init process for its pid namespace. - ownsPidNamespace bool -} - -var _ runtime.Container = &container{} - -func (c *container) ID() string { - return c.id -} - -func (c *container) Pid() int { - return c.init.Pid() -} - -func (c *container) Tty() *stdio.TtyRelay { - return c.init.ttyRelay -} - -func (c *container) PipeRelay() *stdio.PipeRelay { - return c.init.pipeRelay -} - -// process represents a process running in a container. It can either be a -// container's init process, or an exec process in a container. -type process struct { - c *container - pid int - ttyRelay *stdio.TtyRelay - pipeRelay *stdio.PipeRelay -} - -var _ runtime.Process = &process{} - -func (p *process) Pid() int { - return p.pid -} - -func (p *process) Tty() *stdio.TtyRelay { - return p.ttyRelay -} - -func (p *process) PipeRelay() *stdio.PipeRelay { - return p.pipeRelay -} - // NewRuntime instantiates a new runcRuntime struct. func NewRuntime(logBasePath string) (runtime.Runtime, error) { - rtime := &runcRuntime{runcLogBasePath: logBasePath} if err := rtime.initialize(); err != nil { return nil, err @@ -101,6 +39,14 @@ func NewRuntime(logBasePath string) (runtime.Runtime, error) { return rtime, nil } +// runcRuntime is an implementation of the Runtime interface which uses runC as +// the container runtime. +type runcRuntime struct { + runcLogBasePath string +} + +var _ runtime.Runtime = &runcRuntime{} + // initialize sets up any state necessary for the runcRuntime to function. func (r *runcRuntime) initialize() error { paths := [2]string{containerFilesDir, r.runcLogBasePath} @@ -132,139 +78,6 @@ func (r *runcRuntime) CreateContainer(id string, bundlePath string, stdioSet *st return c, nil } -// Start unblocks the container's init process created by the call to -// CreateContainer. -func (c *container) Start() error { - logPath := c.r.getLogPath(c.id) - args := []string{"start", c.id} - cmd := createRuncCommand(logPath, args...) - out, err := cmd.CombinedOutput() - if err != nil { - runcErr := getRuncLogError(logPath) - c.r.cleanupContainer(c.id) - return errors.Wrapf(runcErr, "runc start failed with %v: %s", err, string(out)) - } - return nil -} - -// ExecProcess executes a new process, represented as an OCI process struct, -// inside an already-running container. -func (c *container) ExecProcess(process *oci.Process, stdioSet *stdio.ConnectionSet) (p runtime.Process, err error) { - p, err = c.runExecCommand(process, stdioSet) - if err != nil { - return nil, err - } - return p, nil -} - -// Kill sends the specified signal to the container's init process. -func (c *container) Kill(signal syscall.Signal) error { - logrus.WithField(logfields.ContainerID, c.id).Debug("runc::container::Kill") - logPath := c.r.getLogPath(c.id) - args := []string{"kill"} - if signal == syscall.SIGTERM || signal == syscall.SIGKILL { - args = append(args, "--all") - } - args = append(args, c.id, strconv.Itoa(int(signal))) - cmd := createRuncCommand(logPath, args...) - out, err := cmd.CombinedOutput() - if err != nil { - runcErr := getRuncLogError(logPath) - return errors.Wrapf(runcErr, "unknown runc error after kill %v: %s", err, string(out)) - } - return nil -} - -// Delete deletes any state created for the container by either this wrapper or -// runC itself. -func (c *container) Delete() error { - logrus.WithField(logfields.ContainerID, c.id).Debug("runc::container::Delete") - logPath := c.r.getLogPath(c.id) - args := []string{"delete", c.id} - cmd := createRuncCommand(logPath, args...) - out, err := cmd.CombinedOutput() - if err != nil { - runcErr := getRuncLogError(logPath) - return errors.Wrapf(runcErr, "runc delete failed with %v: %s", err, string(out)) - } - if err := c.r.cleanupContainer(c.id); err != nil { - return err - } - return nil -} - -// Delete deletes any state created for the process by either this wrapper or -// runC itself. -func (p *process) Delete() error { - if err := p.c.r.cleanupProcess(p.c.id, p.pid); err != nil { - return err - } - return nil -} - -// Pause suspends all processes running in the container. -func (c *container) Pause() error { - logPath := c.r.getLogPath(c.id) - args := []string{"pause", c.id} - cmd := createRuncCommand(logPath, args...) - out, err := cmd.CombinedOutput() - if err != nil { - runcErr := getRuncLogError(logPath) - return errors.Wrapf(runcErr, "runc pause failed with %v: %s", err, string(out)) - } - return nil -} - -// Resume unsuspends processes running in the container. -func (c *container) Resume() error { - logPath := c.r.getLogPath(c.id) - args := []string{"resume", c.id} - cmd := createRuncCommand(logPath, args...) - out, err := cmd.CombinedOutput() - if err != nil { - runcErr := getRuncLogError(logPath) - return errors.Wrapf(runcErr, "runc resume failed with %v: %s", err, string(out)) - } - return nil -} - -// GetState returns information about the given container. -func (c *container) GetState() (*runtime.ContainerState, error) { - logPath := c.r.getLogPath(c.id) - args := []string{"state", c.id} - cmd := createRuncCommand(logPath, args...) - out, err := cmd.CombinedOutput() - if err != nil { - runcErr := getRuncLogError(logPath) - return nil, errors.Wrapf(runcErr, "runc state failed with %v: %s", err, string(out)) - } - var state runtime.ContainerState - if err := json.Unmarshal(out, &state); err != nil { - return nil, errors.Wrapf(err, "failed to unmarshal the state for container %s", c.id) - } - return &state, nil -} - -// Exists returns true if the container exists, false if it doesn't -// exist. -// It should be noted that containers that have stopped but have not been -// deleted are still considered to exist. -func (c *container) Exists() (bool, error) { - // use global path because container may not exist - logPath := c.r.getGlobalLogPath() - args := []string{"state", c.id} - cmd := createRuncCommand(logPath, args...) - out, err := cmd.CombinedOutput() - if err != nil { - runcErr := getRuncLogError(logPath) - if errors.Is(runcErr, runtime.ErrContainerDoesNotExist) { - return false, nil - } - return false, errors.Wrapf(runcErr, "runc state failed with %v: %s", err, string(out)) - } - return true, nil -} - // ListContainerStates returns ContainerState structs for all existing // containers, whether they're running or not. func (r *runcRuntime) ListContainerStates() ([]runtime.ContainerState, error) { @@ -283,125 +96,6 @@ func (r *runcRuntime) ListContainerStates() ([]runtime.ContainerState, error) { return states, nil } -// GetRunningProcesses gets only the running processes associated with the given -// container. This excludes zombie processes. -func (c *container) GetRunningProcesses() ([]runtime.ContainerProcessState, error) { - pids, err := c.r.getRunningPids(c.id) - if err != nil { - return nil, err - } - - pidMap := map[int]*runtime.ContainerProcessState{} - // Initialize all processes with a pid and command, and mark correctly that - // none of them are zombies. Default CreatedByRuntime to false. - for _, pid := range pids { - command, err := c.r.getProcessCommand(pid) - if err != nil { - if errors.Is(err, unix.ENOENT) { - // process has exited between getting the running pids above - // and now, ignore error - continue - } - return nil, err - } - pidMap[pid] = &runtime.ContainerProcessState{Pid: pid, Command: command, CreatedByRuntime: false, IsZombie: false} - } - - // For each process state directory which corresponds to a running pid, set - // that the process was created by the Runtime. - processDirs, err := ioutil.ReadDir(filepath.Join(containerFilesDir, c.id)) - if err != nil { - return nil, errors.Wrapf(err, "failed to read the contents of container directory %s", filepath.Join(containerFilesDir, c.id)) - } - for _, processDir := range processDirs { - if processDir.Name() != initPidFilename { - pid, err := strconv.Atoi(processDir.Name()) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse string \"%s\" as pid", processDir.Name()) - } - if _, ok := pidMap[pid]; ok { - pidMap[pid].CreatedByRuntime = true - } - } - } - - return c.r.pidMapToProcessStates(pidMap), nil -} - -// GetAllProcesses gets all processes associated with the given container, -// including both running and zombie processes. -func (c *container) GetAllProcesses() ([]runtime.ContainerProcessState, error) { - runningPids, err := c.r.getRunningPids(c.id) - if err != nil { - return nil, err - } - - logrus.WithFields(logrus.Fields{ - "cid": c.id, - "pids": runningPids, - }).Debug("running container pids") - - pidMap := map[int]*runtime.ContainerProcessState{} - // Initialize all processes with a pid and command, leaving CreatedByRuntime - // and IsZombie at the default value of false. - for _, pid := range runningPids { - command, err := c.r.getProcessCommand(pid) - if err != nil { - if errors.Is(err, unix.ENOENT) { - // process has exited between getting the running pids above - // and now, ignore error - continue - } - return nil, err - } - pidMap[pid] = &runtime.ContainerProcessState{Pid: pid, Command: command, CreatedByRuntime: false, IsZombie: false} - } - - processDirs, err := ioutil.ReadDir(filepath.Join(containerFilesDir, c.id)) - if err != nil { - return nil, errors.Wrapf(err, "failed to read the contents of container directory %s", filepath.Join(containerFilesDir, c.id)) - } - // Loop over every process state directory. Since these processes have - // process state directories, CreatedByRuntime will be true for all of them. - for _, processDir := range processDirs { - if processDir.Name() != initPidFilename { - pid, err := strconv.Atoi(processDir.Name()) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse string \"%s\" into pid", processDir.Name()) - } - if c.r.processExists(pid) { - // If the process exists in /proc and is in the pidMap, it must - // be a running non-zombie. - if _, ok := pidMap[pid]; ok { - pidMap[pid].CreatedByRuntime = true - } else { - // Otherwise, since it's in /proc but not running, it must - // be a zombie. - command, err := c.r.getProcessCommand(pid) - if err != nil { - if errors.Is(err, unix.ENOENT) { - // process has exited between checking that it exists and now, ignore error - continue - } - return nil, err - } - pidMap[pid] = &runtime.ContainerProcessState{Pid: pid, Command: command, CreatedByRuntime: true, IsZombie: true} - } - } - } - } - return c.r.pidMapToProcessStates(pidMap), nil -} - -// GetInitProcess gets the init processes associated with the given container, -// including both running and zombie processes. -func (c *container) GetInitProcess() (runtime.Process, error) { - if c.init == nil { - return nil, errors.New("container has no init process") - } - return c.init, nil -} - // getRunningPids gets the pids of all processes which runC recognizes as // running. func (r *runcRuntime) getRunningPids(id string) ([]int, error) { @@ -464,83 +158,6 @@ func (r *runcRuntime) waitOnProcess(pid int) (int, error) { return status.ExitStatus(), nil } -func (p *process) Wait() (int, error) { - exitCode, err := p.c.r.waitOnProcess(p.pid) - - l := logrus.WithField(logfields.ContainerID, p.c.id) - l.WithField(logfields.ContainerID, p.pid).Debug("process wait completed") - - // If the init process for the container has exited, kill everything else in - // the container. Runc uses the devices cgroup of the container ot determine - // what other processes to kill. - // - // We don't issue the kill if the container owns its own pid namespace, - // because in that case the container kernel will kill everything in the pid - // namespace automatically (as the container init will be the pid namespace - // init). This prevents a potential issue where two containers share cgroups - // but have their own pid namespaces. If we didn't handle this case, runc - // would kill the processes in both containers when trying to kill - // either one of them. - if p == p.c.init && !p.c.ownsPidNamespace { - // If the init process of a pid namespace terminates, the kernel - // terminates all other processes in the namespace with SIGKILL. We - // simulate the same behavior. - if err := p.c.Kill(syscall.SIGKILL); err != nil { - l.WithError(err).Error("failed to terminate container after process wait") - } - } - - // Wait on the relay to drain any output that was already buffered. - // - // At this point, if this is the init process for the container, everything - // else in the container has been killed, so the write ends of the stdio - // relay will have been closed. - // - // If this is a container exec process instead, then it is possible the - // relay waits will hang waiting for the write ends to close. This can occur - // if the exec spawned any child processes that inherited its stdio. - // Currently we do not do anything to avoid hanging in this case, but in the - // future we could add special handling. - if p.ttyRelay != nil { - p.ttyRelay.Wait() - } - if p.pipeRelay != nil { - p.pipeRelay.Wait() - } - - l.WithField(logfields.ProcessID, p.pid).Debug("relay wait completed") - - return exitCode, err -} - -// Wait waits on every non-init process in the container, and then performs a -// final wait on the init process. The exit code returned is the exit code -// acquired from waiting on the init process. -func (c *container) Wait() (int, error) { - entity := logrus.WithField(logfields.ContainerID, c.id) - processes, err := c.GetAllProcesses() - if err != nil { - return -1, err - } - for _, process := range processes { - // Only wait on non-init processes that were created with exec. - if process.Pid != c.init.pid && process.CreatedByRuntime { - // FUTURE-jstarks: Consider waiting on the child process's relays as - // well (as in p.Wait()). This may not matter as long as the relays - // finish "soon" after Wait() returns since HCS expects the stdio - // connections to close before container shutdown can complete. - entity.WithField(logfields.ProcessID, process.Pid).Debug("waiting on container exec process") - c.r.waitOnProcess(process.Pid) - } - } - exitCode, err := c.init.Wait() - entity.Debug("runc::container::init process wait completed") - if err != nil { - return -1, err - } - return exitCode, nil -} - // runCreateCommand sets up the arguments for calling runc create. func (r *runcRuntime) runCreateCommand(id string, bundlePath string, stdioSet *stdio.ConnectionSet) (runtime.Container, error) { c := &container{r: r, id: id} @@ -609,138 +226,3 @@ func ociSpecFromBundle(bundlePath string) (*oci.Spec, error) { } return spec, nil } - -// runExecCommand sets up the arguments for calling runc exec. -func (c *container) runExecCommand(processDef *oci.Process, stdioSet *stdio.ConnectionSet) (p runtime.Process, err error) { - // Create a temporary random directory to store the process's files. - tempProcessDir, err := ioutil.TempDir(containerFilesDir, c.id) - if err != nil { - return nil, err - } - - f, err := os.Create(filepath.Join(tempProcessDir, "process.json")) - if err != nil { - return nil, errors.Wrapf(err, "failed to create process.json file at %s", filepath.Join(tempProcessDir, "process.json")) - } - defer f.Close() - if err := json.NewEncoder(f).Encode(processDef); err != nil { - return nil, errors.Wrap(err, "failed to encode JSON into process.json file") - } - - args := []string{"exec"} - args = append(args, "-d", "--process", filepath.Join(tempProcessDir, "process.json")) - return c.startProcess(tempProcessDir, processDef.Terminal, stdioSet, args...) -} - -// startProcess performs the operations necessary to start a container process -// and properly handle its stdio. This function is used by both CreateContainer -// and ExecProcess. For V2 container creation stdioSet will be nil, in this case -// it is expected that the caller starts the relay previous to calling Start on -// the container. -func (c *container) startProcess(tempProcessDir string, hasTerminal bool, stdioSet *stdio.ConnectionSet, initialArgs ...string) (p *process, err error) { - args := initialArgs - - if err := setSubreaper(1); err != nil { - return nil, errors.Wrapf(err, "failed to set process as subreaper for process in container %s", c.id) - } - if err := c.r.makeLogDir(c.id); err != nil { - return nil, err - } - - logPath := c.r.getLogPath(c.id) - args = append(args, "--pid-file", filepath.Join(tempProcessDir, "pid")) - - var sockListener *net.UnixListener - if hasTerminal { - var consoleSockPath string - sockListener, consoleSockPath, err = c.r.createConsoleSocket(tempProcessDir) - if err != nil { - return nil, errors.Wrapf(err, "failed to create console socket for container %s", c.id) - } - defer sockListener.Close() - args = append(args, "--console-socket", consoleSockPath) - } - args = append(args, c.id) - - cmd := createRuncCommand(logPath, args...) - - var pipeRelay *stdio.PipeRelay - if !hasTerminal { - pipeRelay, err = stdio.NewPipeRelay(stdioSet) - if err != nil { - return nil, errors.Wrapf(err, "failed to create a pipe relay connection set for container %s", c.id) - } - fileSet, err := pipeRelay.Files() - if err != nil { - return nil, errors.Wrapf(err, "failed to get files for connection set for container %s", c.id) - } - // Closing the FileSet here is fine as that end of the pipes will have - // already been copied into the child process. - defer fileSet.Close() - if fileSet.In != nil { - cmd.Stdin = fileSet.In - } - if fileSet.Out != nil { - cmd.Stdout = fileSet.Out - } - if fileSet.Err != nil { - cmd.Stderr = fileSet.Err - } - } - - if err := cmd.Run(); err != nil { - runcErr := getRuncLogError(logPath) - return nil, errors.Wrapf(runcErr, "failed to run runc create/exec call for container %s with %v", c.id, err) - } - - var ttyRelay *stdio.TtyRelay - if hasTerminal { - var master *os.File - master, err = c.r.getMasterFromSocket(sockListener) - if err != nil { - cmd.Process.Kill() - return nil, errors.Wrapf(err, "failed to get pty master for process in container %s", c.id) - } - // Keep master open for the relay unless there is an error. - defer func() { - if err != nil { - master.Close() - } - }() - ttyRelay = stdio.NewTtyRelay(stdioSet, master) - } - - // Rename the process's directory to its pid. - pid, err := c.r.readPidFile(filepath.Join(tempProcessDir, "pid")) - if err != nil { - return nil, err - } - if err := os.Rename(tempProcessDir, c.r.getProcessDir(c.id, pid)); err != nil { - return nil, err - } - - if ttyRelay != nil && stdioSet != nil { - ttyRelay.Start() - } - if pipeRelay != nil && stdioSet != nil { - pipeRelay.Start() - } - return &process{c: c, pid: pid, ttyRelay: ttyRelay, pipeRelay: pipeRelay}, nil -} - -func (c *container) Update(resources interface{}) error { - jsonResources, err := json.Marshal(resources) - if err != nil { - return err - } - logPath := c.r.getLogPath(c.id) - args := []string{"update", "--resources", "-", c.id} - cmd := createRuncCommand(logPath, args...) - cmd.Stdin = strings.NewReader(string(jsonResources)) - out, err := cmd.CombinedOutput() - if err != nil { - runcErr := getRuncLogError(logPath) - return errors.Wrapf(runcErr, "runc update request %s failed with %v: %s", string(jsonResources), err, string(out)) - } - return nil -} diff --git a/internal/lcow/scratch.go b/internal/lcow/scratch.go index 001f3347cd..c86d141adf 100644 --- a/internal/lcow/scratch.go +++ b/internal/lcow/scratch.go @@ -32,7 +32,7 @@ const ( // requested size. It has a caching capability. If the cacheFile exists, and the // request is for a default size, a copy of that is made to the target. If the // size is non-default, or the cache file does not exist, it uses a utility VM -// to create target. It is the responsibility of the caller to synchronise +// to create target. It is the responsibility of the caller to synchronize // simultaneous attempts to create the cache file. func CreateScratch(ctx context.Context, lcowUVM *uvm.UtilityVM, destFile string, sizeGB uint32, cacheFile string) error { if lcowUVM == nil { diff --git a/internal/tools/uvmboot/lcow.go b/internal/tools/uvmboot/lcow.go index 4d78c79848..6bb7679c08 100644 --- a/internal/tools/uvmboot/lcow.go +++ b/internal/tools/uvmboot/lcow.go @@ -4,6 +4,7 @@ package main import ( "context" + "fmt" "io" "os" "strings" @@ -17,8 +18,10 @@ import ( ) const ( + bootFilesPathArgName = "boot-files-path" consolePipeArgName = "console-pipe" kernelDirectArgName = "kernel-direct" + kernelFileArgName = "kernel-file" forwardStdoutArgName = "fwd-stdout" forwardStderrArgName = "fwd-stderr" outputHandlingArgName = "output-handling" @@ -26,10 +29,14 @@ const ( 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{ @@ -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: "Path to boot files directory", + }, cli.UintFlag{ Name: vpMemMaxCountArgName, Usage: "Number of VPMem devices on the UVM. Uses hcsshim default if not specified", @@ -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.", @@ -82,66 +106,32 @@ 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 the mount path inside the UVM, and `w` optionally mounts 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 , " + + "`guest` is the mount path inside the UVM, and `w` optionally mounts as 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 }) @@ -149,27 +139,130 @@ var lcowCommand = cli.Command{ }, } +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) + } + + 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 := 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 := uvm.Start(ctx); err != nil { + 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 { @@ -197,5 +290,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() } diff --git a/internal/tools/uvmboot/main.go b/internal/tools/uvmboot/main.go index 3d89af513d..157fe8bd35 100644 --- a/internal/tools/uvmboot/main.go +++ b/internal/tools/uvmboot/main.go @@ -4,11 +4,13 @@ package main import ( "fmt" + "log" "os" "sync" "time" "github.com/Microsoft/hcsshim/internal/uvm" + "github.com/Microsoft/hcsshim/internal/winapi" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -21,12 +23,17 @@ const ( measureArgName = "measure" parallelArgName = "parallel" countArgName = "count" - debugArgName = "debug" - gcsArgName = "gcs" execCommandLineArgName = "exec" ) +var ( + debug bool + useGCS bool +) + +type uvmRunFunc func(string) error + func main() { app := cli.NewApp() app.Name = "uvmboot" @@ -64,12 +71,14 @@ func main() { Usage: "Enable deferred commit on the UVM", }, cli.BoolFlag{ - Name: debugArgName, - Usage: "Enable debug level logging in HCSShim", + Name: "debug", + Usage: "Enable debug information", + Destination: &debug, }, cli.BoolFlag{ - Name: gcsArgName, - Usage: "Launch the GCS and perform requested operations via its RPC interface", + Name: "gcs", + Usage: "Launch the GCS and perform requested operations via its RPC interface", + Destination: &useGCS, }, } @@ -79,17 +88,21 @@ func main() { } app.Before = func(c *cli.Context) error { - if c.GlobalBool("debug") { + if !winapi.IsEvelated() { + log.Fatal(c.App.Name + " must be run in an elevated context") + } + + if debug { logrus.SetLevel(logrus.DebugLevel) } else { logrus.SetLevel(logrus.WarnLevel) } + return nil } - err := app.Run(os.Args) - if err != nil { - logrus.Fatal(err) + if err := app.Run(os.Args); err != nil { + logrus.Fatalf("%v\n", err) } } @@ -108,7 +121,9 @@ func setGlobalOptions(c *cli.Context, options *uvm.Options) { } } -func runMany(c *cli.Context, runFunc func(id string) error) { +// todo: add a context here to propagate cancel/timeouts to runFunc uvm + +func runMany(c *cli.Context, runFunc uvmRunFunc) { parallelCount := c.GlobalInt(parallelArgName) var wg sync.WaitGroup @@ -118,8 +133,7 @@ func runMany(c *cli.Context, runFunc func(id string) error) { go func() { for i := range workChan { id := fmt.Sprintf("uvmboot-%d", i) - err := runFunc(id) - if err != nil { + if err := runFunc(id); err != nil { logrus.WithField("uvm-id", id).WithError(err).Error("failed to run UVM") } } @@ -138,3 +152,7 @@ func runMany(c *cli.Context, runFunc func(id string) error) { fmt.Println("Elapsed time:", time.Since(start)) } } + +func unrecognizedError(name, value string) error { + return fmt.Errorf("unrecognized value '%s' for option %s", name, value) +} diff --git a/internal/tools/uvmboot/mounts.go b/internal/tools/uvmboot/mounts.go new file mode 100644 index 0000000000..ca1c277fa4 --- /dev/null +++ b/internal/tools/uvmboot/mounts.go @@ -0,0 +1,105 @@ +//go:build windows + +package main + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/Microsoft/hcsshim/internal/uvm" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" +) + +func mountSCSI(ctx context.Context, c *cli.Context, vm *uvm.UtilityVM) error { + for _, m := range parseMounts(c, scsiMountsArgName) { + if _, err := vm.AddSCSI( + ctx, + m.host, + m.guest, + !m.writable, + false, // encrypted + []string{}, + uvm.VMAccessTypeIndividual, + ); err != nil { + return fmt.Errorf("could not mount disk %s: %w", m.host, err) + } else { + logrus.WithFields(logrus.Fields{ + "host": m.host, + "guest": m.guest, + "writable": m.writable, + }).Debug("Mounted SCSI disk") + } + } + + return nil +} + +func shareFiles(ctx context.Context, c *cli.Context, vm *uvm.UtilityVM) error { + switch os := vm.OS(); os { + case "linux": + return shareFilesLCOW(ctx, c, vm) + default: + return fmt.Errorf("file shares are not supported for %s UVMs", os) + } +} + +func shareFilesLCOW(ctx context.Context, c *cli.Context, vm *uvm.UtilityVM) error { + for _, s := range parseMounts(c, shareFilesArgName) { + if err := vm.Share(ctx, s.host, s.guest, !s.writable); err != nil { + return fmt.Errorf("could not share file or directory %s: %w", s.host, err) + } else { + logrus.WithFields(logrus.Fields{ + "host": s.host, + "guest": s.guest, + "writable": s.writable, + }).Debug("Shared path") + } + } + + return nil +} + +type mount struct { + host string + guest string + writable bool +} + +// parseMounts parses the mounts stored under the cli StringSlice argument, `n` +func parseMounts(c *cli.Context, n string) []mount { + if c.IsSet(n) { + ss := c.StringSlice(n) + ms := make([]mount, 0, len(ss)) + for _, s := range ss { + logrus.Debugf("parsing %q", s) + + if m, err := mountFromString(s); err == nil { + ms = append(ms, m) + } + } + + return ms + } + + return nil +} + +func mountFromString(s string) (m mount, _ error) { + ps := strings.Split(s, ",") + + if len(ps) != 2 && len(ps) != 3 { + return m, errors.New("too many parts") + } + + m.host = ps[0] + m.guest = ps[1] + + if len(ps) == 3 && strings.ToLower(ps[2]) == "w" { + m.writable = true + } + + return m, nil +} diff --git a/internal/uvm/create_lcow.go b/internal/uvm/create_lcow.go index 79a065454e..f90cd7c0fe 100644 --- a/internal/uvm/create_lcow.go +++ b/internal/uvm/create_lcow.go @@ -664,7 +664,7 @@ func makeLCOWDoc(ctx context.Context, opts *OptionsLCOW, uvm *UtilityVM) (_ *hcs } if log.IsScrubbingEnabled() { - opts.ExecCommandLine += " --scrub-logs" + opts.ExecCommandLine += " -scrub-logs" } execCmdArgs += " " + opts.ExecCommandLine diff --git a/internal/winapi/elevation.go b/internal/winapi/elevation.go new file mode 100644 index 0000000000..a3f93b1d38 --- /dev/null +++ b/internal/winapi/elevation.go @@ -0,0 +1,11 @@ +//go:build windows + +package winapi + +import ( + "golang.org/x/sys/windows" +) + +func IsEvelated() bool { + return windows.GetCurrentProcessToken().IsElevated() +} diff --git a/test/go.mod b/test/go.mod index d15a83220c..ab0eee82e6 100644 --- a/test/go.mod +++ b/test/go.mod @@ -3,7 +3,7 @@ module github.com/Microsoft/hcsshim/test go 1.17 require ( - github.com/Microsoft/go-winio v0.4.17 + github.com/Microsoft/go-winio v0.5.2 github.com/Microsoft/hcsshim v0.8.23 github.com/containerd/containerd v1.5.10 github.com/containerd/go-runc v1.0.0 diff --git a/test/go.sum b/test/go.sum index 649036b833..07245d050c 100644 --- a/test/go.sum +++ b/test/go.sum @@ -42,8 +42,9 @@ github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.4.17 h1:iT12IBVClFevaf8PuVyi3UmZOVh4OqnaLxDTW2O6j3w= github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= diff --git a/test/vendor/github.com/Microsoft/go-winio/README.md b/test/vendor/github.com/Microsoft/go-winio/README.md index 5680010575..683be1dcf9 100644 --- a/test/vendor/github.com/Microsoft/go-winio/README.md +++ b/test/vendor/github.com/Microsoft/go-winio/README.md @@ -1,4 +1,4 @@ -# go-winio +# go-winio [![Build Status](https://github.com/microsoft/go-winio/actions/workflows/ci.yml/badge.svg)](https://github.com/microsoft/go-winio/actions/workflows/ci.yml) This repository contains utilities for efficiently performing Win32 IO operations in Go. Currently, this is focused on accessing named pipes and other file handles, and @@ -11,12 +11,27 @@ package. Please see the LICENSE file for licensing information. -This project has adopted the [Microsoft Open Source Code of -Conduct](https://opensource.microsoft.com/codeofconduct/). For more information -see the [Code of Conduct -FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact -[opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional -questions or comments. +## Contributing +This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) +declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com. + +When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR +appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA. + +We also require that contributors sign their commits using git commit -s or git commit --signoff to certify they either authored the work themselves +or otherwise have permission to use it in this project. Please see https://developercertificate.org/ for more info, as well as to make sure that you can +attest to the rules listed. Our CI uses the DCO Github app to ensure that all commits in a given PR are signed-off. + + +## Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or +contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + + + +## Special Thanks Thanks to natefinch for the inspiration for this library. See https://github.com/natefinch/npipe for another named pipe implementation. diff --git a/test/vendor/github.com/Microsoft/go-winio/backuptar/tar.go b/test/vendor/github.com/Microsoft/go-winio/backuptar/tar.go index cb461ca315..2342a7fcd6 100644 --- a/test/vendor/github.com/Microsoft/go-winio/backuptar/tar.go +++ b/test/vendor/github.com/Microsoft/go-winio/backuptar/tar.go @@ -5,7 +5,6 @@ package backuptar import ( "archive/tar" "encoding/base64" - "errors" "fmt" "io" "io/ioutil" @@ -42,19 +41,14 @@ const ( hdrCreationTime = "LIBARCHIVE.creationtime" ) -func writeZeroes(w io.Writer, count int64) error { - buf := make([]byte, 8192) - c := len(buf) - for i := int64(0); i < count; i += int64(c) { - if int64(c) > count-i { - c = int(count - i) - } - _, err := w.Write(buf[:c]) - if err != nil { - return err - } +// zeroReader is an io.Reader that always returns 0s. +type zeroReader struct{} + +func (zr zeroReader) Read(b []byte) (int, error) { + for i := range b { + b[i] = 0 } - return nil + return len(b), nil } func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error { @@ -71,16 +65,26 @@ func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error { return fmt.Errorf("unexpected stream %d", bhdr.Id) } + // We can't seek backwards, since we have already written that data to the tar.Writer. + if bhdr.Offset < curOffset { + return fmt.Errorf("cannot seek back from %d to %d", curOffset, bhdr.Offset) + } // archive/tar does not support writing sparse files // so just write zeroes to catch up to the current offset. - err = writeZeroes(t, bhdr.Offset-curOffset) + if _, err := io.CopyN(t, zeroReader{}, bhdr.Offset-curOffset); err != nil { + return fmt.Errorf("seek to offset %d: %s", bhdr.Offset, err) + } if bhdr.Size == 0 { + // A sparse block with size = 0 is used to mark the end of the sparse blocks. break } n, err := io.Copy(t, br) if err != nil { return err } + if n != bhdr.Size { + return fmt.Errorf("copied %d bytes instead of %d at offset %d", n, bhdr.Size, bhdr.Offset) + } curOffset = bhdr.Offset + n } return nil @@ -109,6 +113,69 @@ func BasicInfoHeader(name string, size int64, fileInfo *winio.FileBasicInfo) *ta return hdr } +// SecurityDescriptorFromTarHeader reads the SDDL associated with the header of the current file +// from the tar header and returns the security descriptor into a byte slice. +func SecurityDescriptorFromTarHeader(hdr *tar.Header) ([]byte, error) { + // Maintaining old SDDL-based behavior for backward + // compatibility. All new tar headers written by this library + // will have raw binary for the security descriptor. + var sd []byte + var err error + if sddl, ok := hdr.PAXRecords[hdrSecurityDescriptor]; ok { + sd, err = winio.SddlToSecurityDescriptor(sddl) + if err != nil { + return nil, err + } + } + if sdraw, ok := hdr.PAXRecords[hdrRawSecurityDescriptor]; ok { + sd, err = base64.StdEncoding.DecodeString(sdraw) + if err != nil { + return nil, err + } + } + return sd, nil +} + +// ExtendedAttributesFromTarHeader reads the EAs associated with the header of the +// current file from the tar header and returns it as a byte slice. +func ExtendedAttributesFromTarHeader(hdr *tar.Header) ([]byte, error) { + var eas []winio.ExtendedAttribute + var eadata []byte + var err error + for k, v := range hdr.PAXRecords { + if !strings.HasPrefix(k, hdrEaPrefix) { + continue + } + data, err := base64.StdEncoding.DecodeString(v) + if err != nil { + return nil, err + } + eas = append(eas, winio.ExtendedAttribute{ + Name: k[len(hdrEaPrefix):], + Value: data, + }) + } + if len(eas) != 0 { + eadata, err = winio.EncodeExtendedAttributes(eas) + if err != nil { + return nil, err + } + } + return eadata, nil +} + +// EncodeReparsePointFromTarHeader reads the ReparsePoint structure from the tar header +// and encodes it into a byte slice. The file for which this function is called must be a +// symlink. +func EncodeReparsePointFromTarHeader(hdr *tar.Header) []byte { + _, isMountPoint := hdr.PAXRecords[hdrMountPoint] + rp := winio.ReparsePoint{ + Target: filepath.FromSlash(hdr.Linkname), + IsMountPoint: isMountPoint, + } + return winio.EncodeReparsePoint(&rp) +} + // WriteTarFileFromBackupStream writes a file to a tar writer using data from a Win32 backup stream. // // This encodes Win32 metadata as tar pax vendor extensions starting with MSWINDOWS. @@ -221,20 +288,44 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size } } + // The logic for copying file contents is fairly complicated due to the need for handling sparse files, + // and the weird ways they are represented by BackupRead. A normal file will always either have a data stream + // with size and content, or no data stream at all (if empty). However, for a sparse file, the content can also + // be represented using a series of sparse block streams following the data stream. Additionally, the way sparse + // files are handled by BackupRead has changed in the OS recently. The specifics of the representation are described + // in the list at the bottom of this block comment. + // + // Sparse files can be represented in four different ways, based on the specifics of the file. + // - Size = 0: + // Previously: BackupRead yields no data stream and no sparse block streams. + // Recently: BackupRead yields a data stream with size = 0. There are no following sparse block streams. + // - Size > 0, no allocated ranges: + // BackupRead yields a data stream with size = 0. Following is a single sparse block stream with + // size = 0 and offset = . + // - Size > 0, one allocated range: + // BackupRead yields a data stream with size = containing the file contents. There are no + // sparse block streams. This is the case if you take a normal file with contents and simply set the + // sparse flag on it. + // - Size > 0, multiple allocated ranges: + // BackupRead yields a data stream with size = 0. Following are sparse block streams for each allocated + // range of the file containing the range contents. Finally there is a sparse block stream with + // size = 0 and offset = . + if dataHdr != nil { // A data stream was found. Copy the data. - if (dataHdr.Attributes & winio.StreamSparseAttributes) == 0 { + // We assume that we will either have a data stream size > 0 XOR have sparse block streams. + if dataHdr.Size > 0 || (dataHdr.Attributes&winio.StreamSparseAttributes) == 0 { if size != dataHdr.Size { return fmt.Errorf("%s: mismatch between file size %d and header size %d", name, size, dataHdr.Size) } - _, err = io.Copy(t, br) - if err != nil { - return err + if _, err = io.Copy(t, br); err != nil { + return fmt.Errorf("%s: copying contents from data stream: %s", name, err) } - } else { - err = copySparse(t, br) - if err != nil { - return err + } else if size > 0 { + // As of a recent OS change, BackupRead now returns a data stream for empty sparse files. + // These files have no sparse block streams, so skip the copySparse call if file size = 0. + if err = copySparse(t, br); err != nil { + return fmt.Errorf("%s: copying contents from sparse block stream: %s", name, err) } } } @@ -279,7 +370,7 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size } else { // Unsupported for now, since the size of the alternate stream is not present // in the backup stream until after the data has been read. - return errors.New("tar of sparse alternate data streams is unsupported") + return fmt.Errorf("%s: tar of sparse alternate data streams is unsupported", name) } case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData: // ignore these streams @@ -330,21 +421,10 @@ func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *win // tar file that was not processed, or io.EOF is there are no more. func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (*tar.Header, error) { bw := winio.NewBackupStreamWriter(w) - var sd []byte - var err error - // Maintaining old SDDL-based behavior for backward compatibility. All new tar headers written - // by this library will have raw binary for the security descriptor. - if sddl, ok := hdr.PAXRecords[hdrSecurityDescriptor]; ok { - sd, err = winio.SddlToSecurityDescriptor(sddl) - if err != nil { - return nil, err - } - } - if sdraw, ok := hdr.PAXRecords[hdrRawSecurityDescriptor]; ok { - sd, err = base64.StdEncoding.DecodeString(sdraw) - if err != nil { - return nil, err - } + + sd, err := SecurityDescriptorFromTarHeader(hdr) + if err != nil { + return nil, err } if len(sd) != 0 { bhdr := winio.BackupHeader{ @@ -360,25 +440,12 @@ func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) ( return nil, err } } - var eas []winio.ExtendedAttribute - for k, v := range hdr.PAXRecords { - if !strings.HasPrefix(k, hdrEaPrefix) { - continue - } - data, err := base64.StdEncoding.DecodeString(v) - if err != nil { - return nil, err - } - eas = append(eas, winio.ExtendedAttribute{ - Name: k[len(hdrEaPrefix):], - Value: data, - }) + + eadata, err := ExtendedAttributesFromTarHeader(hdr) + if err != nil { + return nil, err } - if len(eas) != 0 { - eadata, err := winio.EncodeExtendedAttributes(eas) - if err != nil { - return nil, err - } + if len(eadata) != 0 { bhdr := winio.BackupHeader{ Id: winio.BackupEaData, Size: int64(len(eadata)), @@ -392,13 +459,9 @@ func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) ( return nil, err } } + if hdr.Typeflag == tar.TypeSymlink { - _, isMountPoint := hdr.PAXRecords[hdrMountPoint] - rp := winio.ReparsePoint{ - Target: filepath.FromSlash(hdr.Linkname), - IsMountPoint: isMountPoint, - } - reparse := winio.EncodeReparsePoint(&rp) + reparse := EncodeReparsePointFromTarHeader(hdr) bhdr := winio.BackupHeader{ Id: winio.BackupReparseData, Size: int64(len(reparse)), @@ -411,7 +474,9 @@ func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) ( if err != nil { return nil, err } + } + if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA { bhdr := winio.BackupHeader{ Id: winio.BackupData, diff --git a/test/vendor/github.com/Microsoft/go-winio/file.go b/test/vendor/github.com/Microsoft/go-winio/file.go index 0385e41081..293ab54c80 100644 --- a/test/vendor/github.com/Microsoft/go-winio/file.go +++ b/test/vendor/github.com/Microsoft/go-winio/file.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package winio @@ -143,6 +144,11 @@ func (f *win32File) Close() error { return nil } +// IsClosed checks if the file has been closed +func (f *win32File) IsClosed() bool { + return f.closing.isSet() +} + // prepareIo prepares for a new IO operation. // The caller must call f.wg.Done() when the IO is finished, prior to Close() returning. func (f *win32File) prepareIo() (*ioOperation, error) { diff --git a/test/vendor/github.com/Microsoft/go-winio/hvsock.go b/test/vendor/github.com/Microsoft/go-winio/hvsock.go index b632f8f8bb..b2b644d002 100644 --- a/test/vendor/github.com/Microsoft/go-winio/hvsock.go +++ b/test/vendor/github.com/Microsoft/go-winio/hvsock.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package winio @@ -252,15 +253,23 @@ func (conn *HvsockConn) Close() error { return conn.sock.Close() } +func (conn *HvsockConn) IsClosed() bool { + return conn.sock.IsClosed() +} + func (conn *HvsockConn) shutdown(how int) error { - err := syscall.Shutdown(conn.sock.handle, syscall.SHUT_RD) + if conn.IsClosed() { + return ErrFileClosed + } + + err := syscall.Shutdown(conn.sock.handle, how) if err != nil { return os.NewSyscallError("shutdown", err) } return nil } -// CloseRead shuts down the read end of the socket. +// CloseRead shuts down the read end of the socket, preventing future read operations. func (conn *HvsockConn) CloseRead() error { err := conn.shutdown(syscall.SHUT_RD) if err != nil { @@ -269,8 +278,8 @@ func (conn *HvsockConn) CloseRead() error { return nil } -// CloseWrite shuts down the write end of the socket, notifying the other endpoint that -// no more data will be written. +// CloseWrite shuts down the write end of the socket, preventing future write operations and +// notifying the other endpoint that no more data will be written. func (conn *HvsockConn) CloseWrite() error { err := conn.shutdown(syscall.SHUT_WR) if err != nil { diff --git a/test/vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go b/test/vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go index f497c0e391..2d9161e2de 100644 --- a/test/vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go +++ b/test/vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go @@ -14,8 +14,6 @@ import ( "encoding/binary" "fmt" "strconv" - - "golang.org/x/sys/windows" ) // Variant specifies which GUID variant (or "type") of the GUID. It determines @@ -41,13 +39,6 @@ type Version uint8 var _ = (encoding.TextMarshaler)(GUID{}) var _ = (encoding.TextUnmarshaler)(&GUID{}) -// GUID represents a GUID/UUID. It has the same structure as -// golang.org/x/sys/windows.GUID so that it can be used with functions expecting -// that type. It is defined as its own type so that stringification and -// marshaling can be supported. The representation matches that used by native -// Windows code. -type GUID windows.GUID - // NewV4 returns a new version 4 (pseudorandom) GUID, as defined by RFC 4122. func NewV4() (GUID, error) { var b [16]byte diff --git a/test/vendor/github.com/Microsoft/go-winio/pkg/guid/guid_nonwindows.go b/test/vendor/github.com/Microsoft/go-winio/pkg/guid/guid_nonwindows.go new file mode 100644 index 0000000000..f64d828c0b --- /dev/null +++ b/test/vendor/github.com/Microsoft/go-winio/pkg/guid/guid_nonwindows.go @@ -0,0 +1,15 @@ +// +build !windows + +package guid + +// GUID represents a GUID/UUID. It has the same structure as +// golang.org/x/sys/windows.GUID so that it can be used with functions expecting +// that type. It is defined as its own type as that is only available to builds +// targeted at `windows`. The representation matches that used by native Windows +// code. +type GUID struct { + Data1 uint32 + Data2 uint16 + Data3 uint16 + Data4 [8]byte +} diff --git a/test/vendor/github.com/Microsoft/go-winio/pkg/guid/guid_windows.go b/test/vendor/github.com/Microsoft/go-winio/pkg/guid/guid_windows.go new file mode 100644 index 0000000000..83617f4eee --- /dev/null +++ b/test/vendor/github.com/Microsoft/go-winio/pkg/guid/guid_windows.go @@ -0,0 +1,10 @@ +package guid + +import "golang.org/x/sys/windows" + +// GUID represents a GUID/UUID. It has the same structure as +// golang.org/x/sys/windows.GUID so that it can be used with functions expecting +// that type. It is defined as its own type so that stringification and +// marshaling can be supported. The representation matches that used by native +// Windows code. +type GUID windows.GUID diff --git a/test/vendor/github.com/Microsoft/go-winio/pkg/security/grantvmgroupaccess.go b/test/vendor/github.com/Microsoft/go-winio/pkg/security/grantvmgroupaccess.go index fca241590c..602920786c 100644 --- a/test/vendor/github.com/Microsoft/go-winio/pkg/security/grantvmgroupaccess.go +++ b/test/vendor/github.com/Microsoft/go-winio/pkg/security/grantvmgroupaccess.go @@ -3,11 +3,10 @@ package security import ( + "fmt" "os" "syscall" "unsafe" - - "github.com/pkg/errors" ) type ( @@ -72,7 +71,7 @@ func GrantVmGroupAccess(name string) error { // Stat (to determine if `name` is a directory). s, err := os.Stat(name) if err != nil { - return errors.Wrapf(err, "%s os.Stat %s", gvmga, name) + return fmt.Errorf("%s os.Stat %s: %w", gvmga, name, err) } // Get a handle to the file/directory. Must defer Close on success. @@ -88,7 +87,7 @@ func GrantVmGroupAccess(name string) error { sd := uintptr(0) origDACL := uintptr(0) if err := getSecurityInfo(fd, uint32(ot), uint32(si), nil, nil, &origDACL, nil, &sd); err != nil { - return errors.Wrapf(err, "%s GetSecurityInfo %s", gvmga, name) + return fmt.Errorf("%s GetSecurityInfo %s: %w", gvmga, name, err) } defer syscall.LocalFree((syscall.Handle)(unsafe.Pointer(sd))) @@ -102,7 +101,7 @@ func GrantVmGroupAccess(name string) error { // And finally use SetSecurityInfo to apply the updated DACL. if err := setSecurityInfo(fd, uint32(ot), uint32(si), uintptr(0), uintptr(0), newDACL, uintptr(0)); err != nil { - return errors.Wrapf(err, "%s SetSecurityInfo %s", gvmga, name) + return fmt.Errorf("%s SetSecurityInfo %s: %w", gvmga, name, err) } return nil @@ -120,7 +119,7 @@ func createFile(name string, isDir bool) (syscall.Handle, error) { } fd, err := syscall.CreateFile(&namep[0], da, sm, nil, syscall.OPEN_EXISTING, fa, 0) if err != nil { - return 0, errors.Wrapf(err, "%s syscall.CreateFile %s", gvmga, name) + return 0, fmt.Errorf("%s syscall.CreateFile %s: %w", gvmga, name, err) } return fd, nil } @@ -131,7 +130,7 @@ func generateDACLWithAcesAdded(name string, isDir bool, origDACL uintptr) (uintp // Generate pointers to the SIDs based on the string SIDs sid, err := syscall.StringToSid(sidVmGroup) if err != nil { - return 0, errors.Wrapf(err, "%s syscall.StringToSid %s %s", gvmga, name, sidVmGroup) + return 0, fmt.Errorf("%s syscall.StringToSid %s %s: %w", gvmga, name, sidVmGroup, err) } inheritance := inheritModeNoInheritance @@ -154,7 +153,7 @@ func generateDACLWithAcesAdded(name string, isDir bool, origDACL uintptr) (uintp modifiedDACL := uintptr(0) if err := setEntriesInAcl(uintptr(uint32(1)), uintptr(unsafe.Pointer(&eaArray[0])), origDACL, &modifiedDACL); err != nil { - return 0, errors.Wrapf(err, "%s SetEntriesInAcl %s", gvmga, name) + return 0, fmt.Errorf("%s SetEntriesInAcl %s: %w", gvmga, name, err) } return modifiedDACL, nil diff --git a/test/vendor/github.com/Microsoft/go-winio/pkg/security/syscall_windows.go b/test/vendor/github.com/Microsoft/go-winio/pkg/security/syscall_windows.go index c40c2739b7..d7096716ce 100644 --- a/test/vendor/github.com/Microsoft/go-winio/pkg/security/syscall_windows.go +++ b/test/vendor/github.com/Microsoft/go-winio/pkg/security/syscall_windows.go @@ -2,6 +2,6 @@ package security //go:generate go run mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go -//sys getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (err error) [failretval!=0] = advapi32.GetSecurityInfo -//sys setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (err error) [failretval!=0] = advapi32.SetSecurityInfo -//sys setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (err error) [failretval!=0] = advapi32.SetEntriesInAclW +//sys getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (win32err error) = advapi32.GetSecurityInfo +//sys setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (win32err error) = advapi32.SetSecurityInfo +//sys setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (win32err error) = advapi32.SetEntriesInAclW diff --git a/test/vendor/github.com/Microsoft/go-winio/pkg/security/zsyscall_windows.go b/test/vendor/github.com/Microsoft/go-winio/pkg/security/zsyscall_windows.go index 4a90cb3cc8..4084680e0f 100644 --- a/test/vendor/github.com/Microsoft/go-winio/pkg/security/zsyscall_windows.go +++ b/test/vendor/github.com/Microsoft/go-winio/pkg/security/zsyscall_windows.go @@ -45,26 +45,26 @@ var ( procSetSecurityInfo = modadvapi32.NewProc("SetSecurityInfo") ) -func getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (err error) { - r1, _, e1 := syscall.Syscall9(procGetSecurityInfo.Addr(), 8, uintptr(handle), uintptr(objectType), uintptr(si), uintptr(unsafe.Pointer(ppsidOwner)), uintptr(unsafe.Pointer(ppsidGroup)), uintptr(unsafe.Pointer(ppDacl)), uintptr(unsafe.Pointer(ppSacl)), uintptr(unsafe.Pointer(ppSecurityDescriptor)), 0) - if r1 != 0 { - err = errnoErr(e1) +func getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (win32err error) { + r0, _, _ := syscall.Syscall9(procGetSecurityInfo.Addr(), 8, uintptr(handle), uintptr(objectType), uintptr(si), uintptr(unsafe.Pointer(ppsidOwner)), uintptr(unsafe.Pointer(ppsidGroup)), uintptr(unsafe.Pointer(ppDacl)), uintptr(unsafe.Pointer(ppSacl)), uintptr(unsafe.Pointer(ppSecurityDescriptor)), 0) + if r0 != 0 { + win32err = syscall.Errno(r0) } return } -func setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (err error) { - r1, _, e1 := syscall.Syscall6(procSetEntriesInAclW.Addr(), 4, uintptr(count), uintptr(pListOfEEs), uintptr(oldAcl), uintptr(unsafe.Pointer(newAcl)), 0, 0) - if r1 != 0 { - err = errnoErr(e1) +func setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (win32err error) { + r0, _, _ := syscall.Syscall6(procSetEntriesInAclW.Addr(), 4, uintptr(count), uintptr(pListOfEEs), uintptr(oldAcl), uintptr(unsafe.Pointer(newAcl)), 0, 0) + if r0 != 0 { + win32err = syscall.Errno(r0) } return } -func setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (err error) { - r1, _, e1 := syscall.Syscall9(procSetSecurityInfo.Addr(), 7, uintptr(handle), uintptr(objectType), uintptr(si), uintptr(psidOwner), uintptr(psidGroup), uintptr(pDacl), uintptr(pSacl), 0, 0) - if r1 != 0 { - err = errnoErr(e1) +func setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (win32err error) { + r0, _, _ := syscall.Syscall9(procSetSecurityInfo.Addr(), 7, uintptr(handle), uintptr(objectType), uintptr(si), uintptr(psidOwner), uintptr(psidGroup), uintptr(pDacl), uintptr(pSacl), 0, 0) + if r0 != 0 { + win32err = syscall.Errno(r0) } return } diff --git a/test/vendor/github.com/Microsoft/go-winio/privilege.go b/test/vendor/github.com/Microsoft/go-winio/privilege.go index 9c83d36fe5..c3dd7c2176 100644 --- a/test/vendor/github.com/Microsoft/go-winio/privilege.go +++ b/test/vendor/github.com/Microsoft/go-winio/privilege.go @@ -28,8 +28,9 @@ const ( ERROR_NOT_ALL_ASSIGNED syscall.Errno = 1300 - SeBackupPrivilege = "SeBackupPrivilege" - SeRestorePrivilege = "SeRestorePrivilege" + SeBackupPrivilege = "SeBackupPrivilege" + SeRestorePrivilege = "SeRestorePrivilege" + SeSecurityPrivilege = "SeSecurityPrivilege" ) const ( diff --git a/test/vendor/github.com/Microsoft/go-winio/vhd/vhd.go b/test/vendor/github.com/Microsoft/go-winio/vhd/vhd.go index b03b789e65..f7f78fc230 100644 --- a/test/vendor/github.com/Microsoft/go-winio/vhd/vhd.go +++ b/test/vendor/github.com/Microsoft/go-winio/vhd/vhd.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package vhd @@ -7,17 +8,16 @@ import ( "syscall" "github.com/Microsoft/go-winio/pkg/guid" - "github.com/pkg/errors" "golang.org/x/sys/windows" ) //go:generate go run mksyscall_windows.go -output zvhd_windows.go vhd.go -//sys createVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (err error) [failretval != 0] = virtdisk.CreateVirtualDisk -//sys openVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *OpenVirtualDiskParameters, handle *syscall.Handle) (err error) [failretval != 0] = virtdisk.OpenVirtualDisk -//sys attachVirtualDisk(handle syscall.Handle, securityDescriptor *uintptr, attachVirtualDiskFlag uint32, providerSpecificFlags uint32, parameters *AttachVirtualDiskParameters, overlapped *syscall.Overlapped) (err error) [failretval != 0] = virtdisk.AttachVirtualDisk -//sys detachVirtualDisk(handle syscall.Handle, detachVirtualDiskFlags uint32, providerSpecificFlags uint32) (err error) [failretval != 0] = virtdisk.DetachVirtualDisk -//sys getVirtualDiskPhysicalPath(handle syscall.Handle, diskPathSizeInBytes *uint32, buffer *uint16) (err error) [failretval != 0] = virtdisk.GetVirtualDiskPhysicalPath +//sys createVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (win32err error) = virtdisk.CreateVirtualDisk +//sys openVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *openVirtualDiskParameters, handle *syscall.Handle) (win32err error) = virtdisk.OpenVirtualDisk +//sys attachVirtualDisk(handle syscall.Handle, securityDescriptor *uintptr, attachVirtualDiskFlag uint32, providerSpecificFlags uint32, parameters *AttachVirtualDiskParameters, overlapped *syscall.Overlapped) (win32err error) = virtdisk.AttachVirtualDisk +//sys detachVirtualDisk(handle syscall.Handle, detachVirtualDiskFlags uint32, providerSpecificFlags uint32) (win32err error) = virtdisk.DetachVirtualDisk +//sys getVirtualDiskPhysicalPath(handle syscall.Handle, diskPathSizeInBytes *uint32, buffer *uint16) (win32err error) = virtdisk.GetVirtualDiskPhysicalPath type ( CreateVirtualDiskFlag uint32 @@ -62,13 +62,27 @@ type OpenVirtualDiskParameters struct { Version2 OpenVersion2 } +// The higher level `OpenVersion2` struct uses bools to refer to `GetInfoOnly` and `ReadOnly` for ease of use. However, +// the internal windows structure uses `BOOLS` aka int32s for these types. `openVersion2` is used for translating +// `OpenVersion2` fields to the correct windows internal field types on the `Open____` methods. +type openVersion2 struct { + getInfoOnly int32 + readOnly int32 + resiliencyGUID guid.GUID +} + +type openVirtualDiskParameters struct { + version uint32 + version2 openVersion2 +} + type AttachVersion2 struct { RestrictedOffset uint64 RestrictedLength uint64 } type AttachVirtualDiskParameters struct { - Version uint32 // Must always be set to 2 + Version uint32 Version2 AttachVersion2 } @@ -146,16 +160,13 @@ func CreateVhdx(path string, maxSizeInGb, blockSizeInMb uint32) error { return err } - if err := syscall.CloseHandle(handle); err != nil { - return err - } - return nil + return syscall.CloseHandle(handle) } // DetachVirtualDisk detaches a virtual hard disk by handle. func DetachVirtualDisk(handle syscall.Handle) (err error) { if err := detachVirtualDisk(handle, 0, 0); err != nil { - return errors.Wrap(err, "failed to detach virtual disk") + return fmt.Errorf("failed to detach virtual disk: %w", err) } return nil } @@ -185,7 +196,7 @@ func AttachVirtualDisk(handle syscall.Handle, attachVirtualDiskFlag AttachVirtua parameters, nil, ); err != nil { - return errors.Wrap(err, "failed to attach virtual disk") + return fmt.Errorf("failed to attach virtual disk: %w", err) } return nil } @@ -209,7 +220,7 @@ func AttachVhd(path string) (err error) { AttachVirtualDiskFlagNone, ¶ms, ); err != nil { - return errors.Wrap(err, "failed to attach virtual disk") + return fmt.Errorf("failed to attach virtual disk: %w", err) } return nil } @@ -234,19 +245,35 @@ func OpenVirtualDiskWithParameters(vhdPath string, virtualDiskAccessMask Virtual var ( handle syscall.Handle defaultType VirtualStorageType + getInfoOnly int32 + readOnly int32 ) if parameters.Version != 2 { return handle, fmt.Errorf("only version 2 VHDs are supported, found version: %d", parameters.Version) } + if parameters.Version2.GetInfoOnly { + getInfoOnly = 1 + } + if parameters.Version2.ReadOnly { + readOnly = 1 + } + params := &openVirtualDiskParameters{ + version: parameters.Version, + version2: openVersion2{ + getInfoOnly, + readOnly, + parameters.Version2.ResiliencyGUID, + }, + } if err := openVirtualDisk( &defaultType, vhdPath, uint32(virtualDiskAccessMask), uint32(openVirtualDiskFlags), - parameters, + params, &handle, ); err != nil { - return 0, errors.Wrap(err, "failed to open virtual disk") + return 0, fmt.Errorf("failed to open virtual disk: %w", err) } return handle, nil } @@ -272,7 +299,7 @@ func CreateVirtualDisk(path string, virtualDiskAccessMask VirtualDiskAccessMask, nil, &handle, ); err != nil { - return handle, errors.Wrap(err, "failed to create virtual disk") + return handle, fmt.Errorf("failed to create virtual disk: %w", err) } return handle, nil } @@ -290,7 +317,7 @@ func GetVirtualDiskPhysicalPath(handle syscall.Handle) (_ string, err error) { &diskPathSizeInBytes, &diskPhysicalPathBuf[0], ); err != nil { - return "", errors.Wrap(err, "failed to get disk physical path") + return "", fmt.Errorf("failed to get disk physical path: %w", err) } return windows.UTF16ToString(diskPhysicalPathBuf[:]), nil } @@ -314,10 +341,10 @@ func CreateDiffVhd(diffVhdPath, baseVhdPath string, blockSizeInMB uint32) error createParams, ) if err != nil { - return fmt.Errorf("failed to create differencing vhd: %s", err) + return fmt.Errorf("failed to create differencing vhd: %w", err) } if err := syscall.CloseHandle(vhdHandle); err != nil { - return fmt.Errorf("failed to close differencing vhd handle: %s", err) + return fmt.Errorf("failed to close differencing vhd handle: %w", err) } return nil } diff --git a/test/vendor/github.com/Microsoft/go-winio/vhd/zvhd_windows.go b/test/vendor/github.com/Microsoft/go-winio/vhd/zvhd_windows.go index 572f7b42f1..1d7498db3b 100644 --- a/test/vendor/github.com/Microsoft/go-winio/vhd/zvhd_windows.go +++ b/test/vendor/github.com/Microsoft/go-winio/vhd/zvhd_windows.go @@ -47,60 +47,60 @@ var ( procOpenVirtualDisk = modvirtdisk.NewProc("OpenVirtualDisk") ) -func attachVirtualDisk(handle syscall.Handle, securityDescriptor *uintptr, attachVirtualDiskFlag uint32, providerSpecificFlags uint32, parameters *AttachVirtualDiskParameters, overlapped *syscall.Overlapped) (err error) { - r1, _, e1 := syscall.Syscall6(procAttachVirtualDisk.Addr(), 6, uintptr(handle), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(attachVirtualDiskFlag), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(overlapped))) - if r1 != 0 { - err = errnoErr(e1) +func attachVirtualDisk(handle syscall.Handle, securityDescriptor *uintptr, attachVirtualDiskFlag uint32, providerSpecificFlags uint32, parameters *AttachVirtualDiskParameters, overlapped *syscall.Overlapped) (win32err error) { + r0, _, _ := syscall.Syscall6(procAttachVirtualDisk.Addr(), 6, uintptr(handle), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(attachVirtualDiskFlag), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(overlapped))) + if r0 != 0 { + win32err = syscall.Errno(r0) } return } -func createVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (err error) { +func createVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (win32err error) { var _p0 *uint16 - _p0, err = syscall.UTF16PtrFromString(path) - if err != nil { + _p0, win32err = syscall.UTF16PtrFromString(path) + if win32err != nil { return } return _createVirtualDisk(virtualStorageType, _p0, virtualDiskAccessMask, securityDescriptor, createVirtualDiskFlags, providerSpecificFlags, parameters, overlapped, handle) } -func _createVirtualDisk(virtualStorageType *VirtualStorageType, path *uint16, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (err error) { - r1, _, e1 := syscall.Syscall9(procCreateVirtualDisk.Addr(), 9, uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(createVirtualDiskFlags), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(handle))) - if r1 != 0 { - err = errnoErr(e1) +func _createVirtualDisk(virtualStorageType *VirtualStorageType, path *uint16, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (win32err error) { + r0, _, _ := syscall.Syscall9(procCreateVirtualDisk.Addr(), 9, uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(createVirtualDiskFlags), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(handle))) + if r0 != 0 { + win32err = syscall.Errno(r0) } return } -func detachVirtualDisk(handle syscall.Handle, detachVirtualDiskFlags uint32, providerSpecificFlags uint32) (err error) { - r1, _, e1 := syscall.Syscall(procDetachVirtualDisk.Addr(), 3, uintptr(handle), uintptr(detachVirtualDiskFlags), uintptr(providerSpecificFlags)) - if r1 != 0 { - err = errnoErr(e1) +func detachVirtualDisk(handle syscall.Handle, detachVirtualDiskFlags uint32, providerSpecificFlags uint32) (win32err error) { + r0, _, _ := syscall.Syscall(procDetachVirtualDisk.Addr(), 3, uintptr(handle), uintptr(detachVirtualDiskFlags), uintptr(providerSpecificFlags)) + if r0 != 0 { + win32err = syscall.Errno(r0) } return } -func getVirtualDiskPhysicalPath(handle syscall.Handle, diskPathSizeInBytes *uint32, buffer *uint16) (err error) { - r1, _, e1 := syscall.Syscall(procGetVirtualDiskPhysicalPath.Addr(), 3, uintptr(handle), uintptr(unsafe.Pointer(diskPathSizeInBytes)), uintptr(unsafe.Pointer(buffer))) - if r1 != 0 { - err = errnoErr(e1) +func getVirtualDiskPhysicalPath(handle syscall.Handle, diskPathSizeInBytes *uint32, buffer *uint16) (win32err error) { + r0, _, _ := syscall.Syscall(procGetVirtualDiskPhysicalPath.Addr(), 3, uintptr(handle), uintptr(unsafe.Pointer(diskPathSizeInBytes)), uintptr(unsafe.Pointer(buffer))) + if r0 != 0 { + win32err = syscall.Errno(r0) } return } -func openVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *OpenVirtualDiskParameters, handle *syscall.Handle) (err error) { +func openVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *openVirtualDiskParameters, handle *syscall.Handle) (win32err error) { var _p0 *uint16 - _p0, err = syscall.UTF16PtrFromString(path) - if err != nil { + _p0, win32err = syscall.UTF16PtrFromString(path) + if win32err != nil { return } return _openVirtualDisk(virtualStorageType, _p0, virtualDiskAccessMask, openVirtualDiskFlags, parameters, handle) } -func _openVirtualDisk(virtualStorageType *VirtualStorageType, path *uint16, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *OpenVirtualDiskParameters, handle *syscall.Handle) (err error) { - r1, _, e1 := syscall.Syscall6(procOpenVirtualDisk.Addr(), 6, uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(openVirtualDiskFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(handle))) - if r1 != 0 { - err = errnoErr(e1) +func _openVirtualDisk(virtualStorageType *VirtualStorageType, path *uint16, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *openVirtualDiskParameters, handle *syscall.Handle) (win32err error) { + r0, _, _ := syscall.Syscall6(procOpenVirtualDisk.Addr(), 6, uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(openVirtualDiskFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(handle))) + if r0 != 0 { + win32err = syscall.Errno(r0) } return } diff --git a/test/vendor/github.com/Microsoft/hcsshim/.gitignore b/test/vendor/github.com/Microsoft/hcsshim/.gitignore index 292630f9ea..8c32a3a6ec 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/.gitignore +++ b/test/vendor/github.com/Microsoft/hcsshim/.gitignore @@ -24,6 +24,7 @@ service/pkg/ *.img *.vhd *.tar.gz +*.tar # Make stuff .rootfs-done @@ -32,9 +33,16 @@ rootfs/* rootfs-conv/* *.o /build/ - deps/* out/* +# test results +test/results + +# ninja build +.ninja_log +build.ninja + +# go workspaces go.work -go.work.sum \ No newline at end of file +go.work.sum diff --git a/test/vendor/github.com/Microsoft/hcsshim/functional_tests.ps1 b/test/vendor/github.com/Microsoft/hcsshim/functional_tests.ps1 deleted file mode 100644 index ce6edbcf32..0000000000 --- a/test/vendor/github.com/Microsoft/hcsshim/functional_tests.ps1 +++ /dev/null @@ -1,12 +0,0 @@ -# Requirements so far: -# dockerd running -# - image microsoft/nanoserver (matching host base image) docker load -i c:\baseimages\nanoserver.tar -# - image alpine (linux) docker pull --platform=linux alpine - - -# TODO: Add this a parameter for debugging. ie "functional-tests -debug=$true" -#$env:HCSSHIM_FUNCTIONAL_TESTS_DEBUG="yes please" - -#pushd uvm -go test -v -tags "functional uvmcreate uvmscratch uvmscsi uvmvpmem uvmvsmb uvmp9" ./... -#popd \ No newline at end of file diff --git a/test/vendor/github.com/Microsoft/hcsshim/hcn/doc.go b/test/vendor/github.com/Microsoft/hcsshim/hcn/doc.go index 83b2fffb02..33f67ac9c6 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/hcn/doc.go +++ b/test/vendor/github.com/Microsoft/hcsshim/hcn/doc.go @@ -1,3 +1,3 @@ // Package hcn is a shim for the Host Compute Networking (HCN) service, which manages networking for Windows Server -// containers and Hyper-V containers. Previous to RS5, HCN was referred to as Host Networking Service (HNS). +// containers and Hyper-V containers. Prior to RS5, HCN was referred to as Host Networking Service (HNS). package hcn diff --git a/test/vendor/github.com/Microsoft/hcsshim/hcn/hcnnamespace.go b/test/vendor/github.com/Microsoft/hcsshim/hcn/hcnnamespace.go index 7539e39fa8..44ba2fa1fd 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/hcn/hcnnamespace.go +++ b/test/vendor/github.com/Microsoft/hcsshim/hcn/hcnnamespace.go @@ -296,11 +296,11 @@ func GetNamespaceContainerIds(namespaceId string) ([]string, error) { var containerIds []string for _, resource := range namespace.Resources { if resource.Type == "Container" { - var contaienrResource NamespaceResourceContainer - if err := json.Unmarshal([]byte(resource.Data), &contaienrResource); err != nil { + var containerResource NamespaceResourceContainer + if err := json.Unmarshal([]byte(resource.Data), &containerResource); err != nil { return nil, err } - containerIds = append(containerIds, contaienrResource.Id) + containerIds = append(containerIds, containerResource.Id) } } return containerIds, nil @@ -377,7 +377,7 @@ func (namespace *HostComputeNamespace) Sync() error { } shimPath := runhcs.VMPipePath(cfg.HostUniqueID) if err := runhcs.IssueVMRequest(shimPath, &req); err != nil { - // The shim is likey gone. Simply ignore the sync as if it didn't exist. + // The shim is likely gone. Simply ignore the sync as if it didn't exist. if perr, ok := err.(*os.PathError); ok && perr.Err == syscall.ERROR_FILE_NOT_FOUND { // Remove the reg key there is no point to try again _ = cfg.Remove() diff --git a/test/vendor/github.com/Microsoft/hcsshim/internal/lcow/scratch.go b/test/vendor/github.com/Microsoft/hcsshim/internal/lcow/scratch.go index 001f3347cd..c86d141adf 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/internal/lcow/scratch.go +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/lcow/scratch.go @@ -32,7 +32,7 @@ const ( // requested size. It has a caching capability. If the cacheFile exists, and the // request is for a default size, a copy of that is made to the target. If the // size is non-default, or the cache file does not exist, it uses a utility VM -// to create target. It is the responsibility of the caller to synchronise +// to create target. It is the responsibility of the caller to synchronize // simultaneous attempts to create the cache file. func CreateScratch(ctx context.Context, lcowUVM *uvm.UtilityVM, destFile string, sizeGB uint32, cacheFile string) error { if lcowUVM == nil { diff --git a/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/create_lcow.go b/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/create_lcow.go index 79a065454e..f90cd7c0fe 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/create_lcow.go +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/create_lcow.go @@ -664,7 +664,7 @@ func makeLCOWDoc(ctx context.Context, opts *OptionsLCOW, uvm *UtilityVM) (_ *hcs } if log.IsScrubbingEnabled() { - opts.ExecCommandLine += " --scrub-logs" + opts.ExecCommandLine += " -scrub-logs" } execCmdArgs += " " + opts.ExecCommandLine diff --git a/test/vendor/github.com/Microsoft/hcsshim/internal/winapi/elevation.go b/test/vendor/github.com/Microsoft/hcsshim/internal/winapi/elevation.go new file mode 100644 index 0000000000..a3f93b1d38 --- /dev/null +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/winapi/elevation.go @@ -0,0 +1,11 @@ +//go:build windows + +package winapi + +import ( + "golang.org/x/sys/windows" +) + +func IsEvelated() bool { + return windows.GetCurrentProcessToken().IsElevated() +} diff --git a/test/vendor/modules.txt b/test/vendor/modules.txt index b0d29873d5..f2cbdfb9f8 100644 --- a/test/vendor/modules.txt +++ b/test/vendor/modules.txt @@ -1,5 +1,5 @@ -# github.com/Microsoft/go-winio v0.4.17 -## explicit; go 1.12 +# github.com/Microsoft/go-winio v0.5.2 +## explicit; go 1.13 github.com/Microsoft/go-winio github.com/Microsoft/go-winio/backuptar github.com/Microsoft/go-winio/pkg/guid diff --git a/vendor/github.com/Microsoft/go-winio/README.md b/vendor/github.com/Microsoft/go-winio/README.md index 5680010575..683be1dcf9 100644 --- a/vendor/github.com/Microsoft/go-winio/README.md +++ b/vendor/github.com/Microsoft/go-winio/README.md @@ -1,4 +1,4 @@ -# go-winio +# go-winio [![Build Status](https://github.com/microsoft/go-winio/actions/workflows/ci.yml/badge.svg)](https://github.com/microsoft/go-winio/actions/workflows/ci.yml) This repository contains utilities for efficiently performing Win32 IO operations in Go. Currently, this is focused on accessing named pipes and other file handles, and @@ -11,12 +11,27 @@ package. Please see the LICENSE file for licensing information. -This project has adopted the [Microsoft Open Source Code of -Conduct](https://opensource.microsoft.com/codeofconduct/). For more information -see the [Code of Conduct -FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact -[opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional -questions or comments. +## Contributing +This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) +declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com. + +When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR +appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA. + +We also require that contributors sign their commits using git commit -s or git commit --signoff to certify they either authored the work themselves +or otherwise have permission to use it in this project. Please see https://developercertificate.org/ for more info, as well as to make sure that you can +attest to the rules listed. Our CI uses the DCO Github app to ensure that all commits in a given PR are signed-off. + + +## Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or +contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + + + +## Special Thanks Thanks to natefinch for the inspiration for this library. See https://github.com/natefinch/npipe for another named pipe implementation. diff --git a/vendor/github.com/Microsoft/go-winio/backuptar/tar.go b/vendor/github.com/Microsoft/go-winio/backuptar/tar.go index cb461ca315..2342a7fcd6 100644 --- a/vendor/github.com/Microsoft/go-winio/backuptar/tar.go +++ b/vendor/github.com/Microsoft/go-winio/backuptar/tar.go @@ -5,7 +5,6 @@ package backuptar import ( "archive/tar" "encoding/base64" - "errors" "fmt" "io" "io/ioutil" @@ -42,19 +41,14 @@ const ( hdrCreationTime = "LIBARCHIVE.creationtime" ) -func writeZeroes(w io.Writer, count int64) error { - buf := make([]byte, 8192) - c := len(buf) - for i := int64(0); i < count; i += int64(c) { - if int64(c) > count-i { - c = int(count - i) - } - _, err := w.Write(buf[:c]) - if err != nil { - return err - } +// zeroReader is an io.Reader that always returns 0s. +type zeroReader struct{} + +func (zr zeroReader) Read(b []byte) (int, error) { + for i := range b { + b[i] = 0 } - return nil + return len(b), nil } func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error { @@ -71,16 +65,26 @@ func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error { return fmt.Errorf("unexpected stream %d", bhdr.Id) } + // We can't seek backwards, since we have already written that data to the tar.Writer. + if bhdr.Offset < curOffset { + return fmt.Errorf("cannot seek back from %d to %d", curOffset, bhdr.Offset) + } // archive/tar does not support writing sparse files // so just write zeroes to catch up to the current offset. - err = writeZeroes(t, bhdr.Offset-curOffset) + if _, err := io.CopyN(t, zeroReader{}, bhdr.Offset-curOffset); err != nil { + return fmt.Errorf("seek to offset %d: %s", bhdr.Offset, err) + } if bhdr.Size == 0 { + // A sparse block with size = 0 is used to mark the end of the sparse blocks. break } n, err := io.Copy(t, br) if err != nil { return err } + if n != bhdr.Size { + return fmt.Errorf("copied %d bytes instead of %d at offset %d", n, bhdr.Size, bhdr.Offset) + } curOffset = bhdr.Offset + n } return nil @@ -109,6 +113,69 @@ func BasicInfoHeader(name string, size int64, fileInfo *winio.FileBasicInfo) *ta return hdr } +// SecurityDescriptorFromTarHeader reads the SDDL associated with the header of the current file +// from the tar header and returns the security descriptor into a byte slice. +func SecurityDescriptorFromTarHeader(hdr *tar.Header) ([]byte, error) { + // Maintaining old SDDL-based behavior for backward + // compatibility. All new tar headers written by this library + // will have raw binary for the security descriptor. + var sd []byte + var err error + if sddl, ok := hdr.PAXRecords[hdrSecurityDescriptor]; ok { + sd, err = winio.SddlToSecurityDescriptor(sddl) + if err != nil { + return nil, err + } + } + if sdraw, ok := hdr.PAXRecords[hdrRawSecurityDescriptor]; ok { + sd, err = base64.StdEncoding.DecodeString(sdraw) + if err != nil { + return nil, err + } + } + return sd, nil +} + +// ExtendedAttributesFromTarHeader reads the EAs associated with the header of the +// current file from the tar header and returns it as a byte slice. +func ExtendedAttributesFromTarHeader(hdr *tar.Header) ([]byte, error) { + var eas []winio.ExtendedAttribute + var eadata []byte + var err error + for k, v := range hdr.PAXRecords { + if !strings.HasPrefix(k, hdrEaPrefix) { + continue + } + data, err := base64.StdEncoding.DecodeString(v) + if err != nil { + return nil, err + } + eas = append(eas, winio.ExtendedAttribute{ + Name: k[len(hdrEaPrefix):], + Value: data, + }) + } + if len(eas) != 0 { + eadata, err = winio.EncodeExtendedAttributes(eas) + if err != nil { + return nil, err + } + } + return eadata, nil +} + +// EncodeReparsePointFromTarHeader reads the ReparsePoint structure from the tar header +// and encodes it into a byte slice. The file for which this function is called must be a +// symlink. +func EncodeReparsePointFromTarHeader(hdr *tar.Header) []byte { + _, isMountPoint := hdr.PAXRecords[hdrMountPoint] + rp := winio.ReparsePoint{ + Target: filepath.FromSlash(hdr.Linkname), + IsMountPoint: isMountPoint, + } + return winio.EncodeReparsePoint(&rp) +} + // WriteTarFileFromBackupStream writes a file to a tar writer using data from a Win32 backup stream. // // This encodes Win32 metadata as tar pax vendor extensions starting with MSWINDOWS. @@ -221,20 +288,44 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size } } + // The logic for copying file contents is fairly complicated due to the need for handling sparse files, + // and the weird ways they are represented by BackupRead. A normal file will always either have a data stream + // with size and content, or no data stream at all (if empty). However, for a sparse file, the content can also + // be represented using a series of sparse block streams following the data stream. Additionally, the way sparse + // files are handled by BackupRead has changed in the OS recently. The specifics of the representation are described + // in the list at the bottom of this block comment. + // + // Sparse files can be represented in four different ways, based on the specifics of the file. + // - Size = 0: + // Previously: BackupRead yields no data stream and no sparse block streams. + // Recently: BackupRead yields a data stream with size = 0. There are no following sparse block streams. + // - Size > 0, no allocated ranges: + // BackupRead yields a data stream with size = 0. Following is a single sparse block stream with + // size = 0 and offset = . + // - Size > 0, one allocated range: + // BackupRead yields a data stream with size = containing the file contents. There are no + // sparse block streams. This is the case if you take a normal file with contents and simply set the + // sparse flag on it. + // - Size > 0, multiple allocated ranges: + // BackupRead yields a data stream with size = 0. Following are sparse block streams for each allocated + // range of the file containing the range contents. Finally there is a sparse block stream with + // size = 0 and offset = . + if dataHdr != nil { // A data stream was found. Copy the data. - if (dataHdr.Attributes & winio.StreamSparseAttributes) == 0 { + // We assume that we will either have a data stream size > 0 XOR have sparse block streams. + if dataHdr.Size > 0 || (dataHdr.Attributes&winio.StreamSparseAttributes) == 0 { if size != dataHdr.Size { return fmt.Errorf("%s: mismatch between file size %d and header size %d", name, size, dataHdr.Size) } - _, err = io.Copy(t, br) - if err != nil { - return err + if _, err = io.Copy(t, br); err != nil { + return fmt.Errorf("%s: copying contents from data stream: %s", name, err) } - } else { - err = copySparse(t, br) - if err != nil { - return err + } else if size > 0 { + // As of a recent OS change, BackupRead now returns a data stream for empty sparse files. + // These files have no sparse block streams, so skip the copySparse call if file size = 0. + if err = copySparse(t, br); err != nil { + return fmt.Errorf("%s: copying contents from sparse block stream: %s", name, err) } } } @@ -279,7 +370,7 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size } else { // Unsupported for now, since the size of the alternate stream is not present // in the backup stream until after the data has been read. - return errors.New("tar of sparse alternate data streams is unsupported") + return fmt.Errorf("%s: tar of sparse alternate data streams is unsupported", name) } case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData: // ignore these streams @@ -330,21 +421,10 @@ func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *win // tar file that was not processed, or io.EOF is there are no more. func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (*tar.Header, error) { bw := winio.NewBackupStreamWriter(w) - var sd []byte - var err error - // Maintaining old SDDL-based behavior for backward compatibility. All new tar headers written - // by this library will have raw binary for the security descriptor. - if sddl, ok := hdr.PAXRecords[hdrSecurityDescriptor]; ok { - sd, err = winio.SddlToSecurityDescriptor(sddl) - if err != nil { - return nil, err - } - } - if sdraw, ok := hdr.PAXRecords[hdrRawSecurityDescriptor]; ok { - sd, err = base64.StdEncoding.DecodeString(sdraw) - if err != nil { - return nil, err - } + + sd, err := SecurityDescriptorFromTarHeader(hdr) + if err != nil { + return nil, err } if len(sd) != 0 { bhdr := winio.BackupHeader{ @@ -360,25 +440,12 @@ func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) ( return nil, err } } - var eas []winio.ExtendedAttribute - for k, v := range hdr.PAXRecords { - if !strings.HasPrefix(k, hdrEaPrefix) { - continue - } - data, err := base64.StdEncoding.DecodeString(v) - if err != nil { - return nil, err - } - eas = append(eas, winio.ExtendedAttribute{ - Name: k[len(hdrEaPrefix):], - Value: data, - }) + + eadata, err := ExtendedAttributesFromTarHeader(hdr) + if err != nil { + return nil, err } - if len(eas) != 0 { - eadata, err := winio.EncodeExtendedAttributes(eas) - if err != nil { - return nil, err - } + if len(eadata) != 0 { bhdr := winio.BackupHeader{ Id: winio.BackupEaData, Size: int64(len(eadata)), @@ -392,13 +459,9 @@ func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) ( return nil, err } } + if hdr.Typeflag == tar.TypeSymlink { - _, isMountPoint := hdr.PAXRecords[hdrMountPoint] - rp := winio.ReparsePoint{ - Target: filepath.FromSlash(hdr.Linkname), - IsMountPoint: isMountPoint, - } - reparse := winio.EncodeReparsePoint(&rp) + reparse := EncodeReparsePointFromTarHeader(hdr) bhdr := winio.BackupHeader{ Id: winio.BackupReparseData, Size: int64(len(reparse)), @@ -411,7 +474,9 @@ func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) ( if err != nil { return nil, err } + } + if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA { bhdr := winio.BackupHeader{ Id: winio.BackupData, diff --git a/vendor/github.com/Microsoft/go-winio/file.go b/vendor/github.com/Microsoft/go-winio/file.go index 0385e41081..293ab54c80 100644 --- a/vendor/github.com/Microsoft/go-winio/file.go +++ b/vendor/github.com/Microsoft/go-winio/file.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package winio @@ -143,6 +144,11 @@ func (f *win32File) Close() error { return nil } +// IsClosed checks if the file has been closed +func (f *win32File) IsClosed() bool { + return f.closing.isSet() +} + // prepareIo prepares for a new IO operation. // The caller must call f.wg.Done() when the IO is finished, prior to Close() returning. func (f *win32File) prepareIo() (*ioOperation, error) { diff --git a/vendor/github.com/Microsoft/go-winio/hvsock.go b/vendor/github.com/Microsoft/go-winio/hvsock.go index b632f8f8bb..b2b644d002 100644 --- a/vendor/github.com/Microsoft/go-winio/hvsock.go +++ b/vendor/github.com/Microsoft/go-winio/hvsock.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package winio @@ -252,15 +253,23 @@ func (conn *HvsockConn) Close() error { return conn.sock.Close() } +func (conn *HvsockConn) IsClosed() bool { + return conn.sock.IsClosed() +} + func (conn *HvsockConn) shutdown(how int) error { - err := syscall.Shutdown(conn.sock.handle, syscall.SHUT_RD) + if conn.IsClosed() { + return ErrFileClosed + } + + err := syscall.Shutdown(conn.sock.handle, how) if err != nil { return os.NewSyscallError("shutdown", err) } return nil } -// CloseRead shuts down the read end of the socket. +// CloseRead shuts down the read end of the socket, preventing future read operations. func (conn *HvsockConn) CloseRead() error { err := conn.shutdown(syscall.SHUT_RD) if err != nil { @@ -269,8 +278,8 @@ func (conn *HvsockConn) CloseRead() error { return nil } -// CloseWrite shuts down the write end of the socket, notifying the other endpoint that -// no more data will be written. +// CloseWrite shuts down the write end of the socket, preventing future write operations and +// notifying the other endpoint that no more data will be written. func (conn *HvsockConn) CloseWrite() error { err := conn.shutdown(syscall.SHUT_WR) if err != nil { diff --git a/vendor/github.com/Microsoft/go-winio/pkg/etw/provider.go b/vendor/github.com/Microsoft/go-winio/pkg/etw/provider.go index e912b51bdc..a5b90d037d 100644 --- a/vendor/github.com/Microsoft/go-winio/pkg/etw/provider.go +++ b/vendor/github.com/Microsoft/go-winio/pkg/etw/provider.go @@ -83,15 +83,6 @@ func providerCallback(sourceID guid.GUID, state ProviderState, level Level, matc } } -// providerCallbackAdapter acts as the first-level callback from the C/ETW side -// for provider notifications. Because Go has trouble with callback arguments of -// different size, it has only pointer-sized arguments, which are then cast to -// the appropriate types when calling providerCallback. -func providerCallbackAdapter(sourceID *guid.GUID, state uintptr, level uintptr, matchAnyKeyword uintptr, matchAllKeyword uintptr, filterData uintptr, i uintptr) uintptr { - providerCallback(*sourceID, ProviderState(state), Level(level), uint64(matchAnyKeyword), uint64(matchAllKeyword), filterData, i) - return 0 -} - // providerIDFromName generates a provider ID based on the provider name. It // uses the same algorithm as used by .NET's EventSource class, which is based // on RFC 4122. More information on the algorithm can be found here: diff --git a/vendor/github.com/Microsoft/go-winio/pkg/etw/wrapper_32.go b/vendor/github.com/Microsoft/go-winio/pkg/etw/wrapper_32.go index 5766d4d71d..6867a1f878 100644 --- a/vendor/github.com/Microsoft/go-winio/pkg/etw/wrapper_32.go +++ b/vendor/github.com/Microsoft/go-winio/pkg/etw/wrapper_32.go @@ -4,6 +4,7 @@ package etw import ( + "github.com/Microsoft/go-winio/pkg/guid" "golang.org/x/sys/windows" ) @@ -50,3 +51,17 @@ func eventSetInformation( information, length) } + +// providerCallbackAdapter acts as the first-level callback from the C/ETW side +// for provider notifications. Because Go has trouble with callback arguments of +// different size, it has only pointer-sized arguments, which are then cast to +// the appropriate types when calling providerCallback. +// For x86, the matchAny and matchAll keywords need to be assembled from two +// 32-bit integers, because the max size of an argument is uintptr, but those +// two arguments are actually 64-bit integers. +func providerCallbackAdapter(sourceID *guid.GUID, state uint32, level uint32, matchAnyKeyword_low uint32, matchAnyKeyword_high uint32, matchAllKeyword_low uint32, matchAllKeyword_high uint32, filterData uintptr, i uintptr) uintptr { + matchAnyKeyword := uint64(matchAnyKeyword_high)<<32 | uint64(matchAnyKeyword_low) + matchAllKeyword := uint64(matchAllKeyword_high)<<32 | uint64(matchAllKeyword_low) + providerCallback(*sourceID, ProviderState(state), Level(level), uint64(matchAnyKeyword), uint64(matchAllKeyword), filterData, i) + return 0 +} diff --git a/vendor/github.com/Microsoft/go-winio/pkg/etw/wrapper_64.go b/vendor/github.com/Microsoft/go-winio/pkg/etw/wrapper_64.go index c78d8d2b42..fe83df2bf0 100644 --- a/vendor/github.com/Microsoft/go-winio/pkg/etw/wrapper_64.go +++ b/vendor/github.com/Microsoft/go-winio/pkg/etw/wrapper_64.go @@ -4,6 +4,7 @@ package etw import ( + "github.com/Microsoft/go-winio/pkg/guid" "golang.org/x/sys/windows" ) @@ -40,3 +41,12 @@ func eventSetInformation( information, length) } + +// providerCallbackAdapter acts as the first-level callback from the C/ETW side +// for provider notifications. Because Go has trouble with callback arguments of +// different size, it has only pointer-sized arguments, which are then cast to +// the appropriate types when calling providerCallback. +func providerCallbackAdapter(sourceID *guid.GUID, state uintptr, level uintptr, matchAnyKeyword uintptr, matchAllKeyword uintptr, filterData uintptr, i uintptr) uintptr { + providerCallback(*sourceID, ProviderState(state), Level(level), uint64(matchAnyKeyword), uint64(matchAllKeyword), filterData, i) + return 0 +} diff --git a/vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go b/vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go index f497c0e391..2d9161e2de 100644 --- a/vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go +++ b/vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go @@ -14,8 +14,6 @@ import ( "encoding/binary" "fmt" "strconv" - - "golang.org/x/sys/windows" ) // Variant specifies which GUID variant (or "type") of the GUID. It determines @@ -41,13 +39,6 @@ type Version uint8 var _ = (encoding.TextMarshaler)(GUID{}) var _ = (encoding.TextUnmarshaler)(&GUID{}) -// GUID represents a GUID/UUID. It has the same structure as -// golang.org/x/sys/windows.GUID so that it can be used with functions expecting -// that type. It is defined as its own type so that stringification and -// marshaling can be supported. The representation matches that used by native -// Windows code. -type GUID windows.GUID - // NewV4 returns a new version 4 (pseudorandom) GUID, as defined by RFC 4122. func NewV4() (GUID, error) { var b [16]byte diff --git a/vendor/github.com/Microsoft/go-winio/pkg/guid/guid_nonwindows.go b/vendor/github.com/Microsoft/go-winio/pkg/guid/guid_nonwindows.go new file mode 100644 index 0000000000..f64d828c0b --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/pkg/guid/guid_nonwindows.go @@ -0,0 +1,15 @@ +// +build !windows + +package guid + +// GUID represents a GUID/UUID. It has the same structure as +// golang.org/x/sys/windows.GUID so that it can be used with functions expecting +// that type. It is defined as its own type as that is only available to builds +// targeted at `windows`. The representation matches that used by native Windows +// code. +type GUID struct { + Data1 uint32 + Data2 uint16 + Data3 uint16 + Data4 [8]byte +} diff --git a/vendor/github.com/Microsoft/go-winio/pkg/guid/guid_windows.go b/vendor/github.com/Microsoft/go-winio/pkg/guid/guid_windows.go new file mode 100644 index 0000000000..83617f4eee --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/pkg/guid/guid_windows.go @@ -0,0 +1,10 @@ +package guid + +import "golang.org/x/sys/windows" + +// GUID represents a GUID/UUID. It has the same structure as +// golang.org/x/sys/windows.GUID so that it can be used with functions expecting +// that type. It is defined as its own type so that stringification and +// marshaling can be supported. The representation matches that used by native +// Windows code. +type GUID windows.GUID diff --git a/vendor/github.com/Microsoft/go-winio/pkg/security/grantvmgroupaccess.go b/vendor/github.com/Microsoft/go-winio/pkg/security/grantvmgroupaccess.go index fca241590c..602920786c 100644 --- a/vendor/github.com/Microsoft/go-winio/pkg/security/grantvmgroupaccess.go +++ b/vendor/github.com/Microsoft/go-winio/pkg/security/grantvmgroupaccess.go @@ -3,11 +3,10 @@ package security import ( + "fmt" "os" "syscall" "unsafe" - - "github.com/pkg/errors" ) type ( @@ -72,7 +71,7 @@ func GrantVmGroupAccess(name string) error { // Stat (to determine if `name` is a directory). s, err := os.Stat(name) if err != nil { - return errors.Wrapf(err, "%s os.Stat %s", gvmga, name) + return fmt.Errorf("%s os.Stat %s: %w", gvmga, name, err) } // Get a handle to the file/directory. Must defer Close on success. @@ -88,7 +87,7 @@ func GrantVmGroupAccess(name string) error { sd := uintptr(0) origDACL := uintptr(0) if err := getSecurityInfo(fd, uint32(ot), uint32(si), nil, nil, &origDACL, nil, &sd); err != nil { - return errors.Wrapf(err, "%s GetSecurityInfo %s", gvmga, name) + return fmt.Errorf("%s GetSecurityInfo %s: %w", gvmga, name, err) } defer syscall.LocalFree((syscall.Handle)(unsafe.Pointer(sd))) @@ -102,7 +101,7 @@ func GrantVmGroupAccess(name string) error { // And finally use SetSecurityInfo to apply the updated DACL. if err := setSecurityInfo(fd, uint32(ot), uint32(si), uintptr(0), uintptr(0), newDACL, uintptr(0)); err != nil { - return errors.Wrapf(err, "%s SetSecurityInfo %s", gvmga, name) + return fmt.Errorf("%s SetSecurityInfo %s: %w", gvmga, name, err) } return nil @@ -120,7 +119,7 @@ func createFile(name string, isDir bool) (syscall.Handle, error) { } fd, err := syscall.CreateFile(&namep[0], da, sm, nil, syscall.OPEN_EXISTING, fa, 0) if err != nil { - return 0, errors.Wrapf(err, "%s syscall.CreateFile %s", gvmga, name) + return 0, fmt.Errorf("%s syscall.CreateFile %s: %w", gvmga, name, err) } return fd, nil } @@ -131,7 +130,7 @@ func generateDACLWithAcesAdded(name string, isDir bool, origDACL uintptr) (uintp // Generate pointers to the SIDs based on the string SIDs sid, err := syscall.StringToSid(sidVmGroup) if err != nil { - return 0, errors.Wrapf(err, "%s syscall.StringToSid %s %s", gvmga, name, sidVmGroup) + return 0, fmt.Errorf("%s syscall.StringToSid %s %s: %w", gvmga, name, sidVmGroup, err) } inheritance := inheritModeNoInheritance @@ -154,7 +153,7 @@ func generateDACLWithAcesAdded(name string, isDir bool, origDACL uintptr) (uintp modifiedDACL := uintptr(0) if err := setEntriesInAcl(uintptr(uint32(1)), uintptr(unsafe.Pointer(&eaArray[0])), origDACL, &modifiedDACL); err != nil { - return 0, errors.Wrapf(err, "%s SetEntriesInAcl %s", gvmga, name) + return 0, fmt.Errorf("%s SetEntriesInAcl %s: %w", gvmga, name, err) } return modifiedDACL, nil diff --git a/vendor/github.com/Microsoft/go-winio/pkg/security/syscall_windows.go b/vendor/github.com/Microsoft/go-winio/pkg/security/syscall_windows.go index c40c2739b7..d7096716ce 100644 --- a/vendor/github.com/Microsoft/go-winio/pkg/security/syscall_windows.go +++ b/vendor/github.com/Microsoft/go-winio/pkg/security/syscall_windows.go @@ -2,6 +2,6 @@ package security //go:generate go run mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go -//sys getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (err error) [failretval!=0] = advapi32.GetSecurityInfo -//sys setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (err error) [failretval!=0] = advapi32.SetSecurityInfo -//sys setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (err error) [failretval!=0] = advapi32.SetEntriesInAclW +//sys getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (win32err error) = advapi32.GetSecurityInfo +//sys setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (win32err error) = advapi32.SetSecurityInfo +//sys setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (win32err error) = advapi32.SetEntriesInAclW diff --git a/vendor/github.com/Microsoft/go-winio/pkg/security/zsyscall_windows.go b/vendor/github.com/Microsoft/go-winio/pkg/security/zsyscall_windows.go index 4a90cb3cc8..4084680e0f 100644 --- a/vendor/github.com/Microsoft/go-winio/pkg/security/zsyscall_windows.go +++ b/vendor/github.com/Microsoft/go-winio/pkg/security/zsyscall_windows.go @@ -45,26 +45,26 @@ var ( procSetSecurityInfo = modadvapi32.NewProc("SetSecurityInfo") ) -func getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (err error) { - r1, _, e1 := syscall.Syscall9(procGetSecurityInfo.Addr(), 8, uintptr(handle), uintptr(objectType), uintptr(si), uintptr(unsafe.Pointer(ppsidOwner)), uintptr(unsafe.Pointer(ppsidGroup)), uintptr(unsafe.Pointer(ppDacl)), uintptr(unsafe.Pointer(ppSacl)), uintptr(unsafe.Pointer(ppSecurityDescriptor)), 0) - if r1 != 0 { - err = errnoErr(e1) +func getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (win32err error) { + r0, _, _ := syscall.Syscall9(procGetSecurityInfo.Addr(), 8, uintptr(handle), uintptr(objectType), uintptr(si), uintptr(unsafe.Pointer(ppsidOwner)), uintptr(unsafe.Pointer(ppsidGroup)), uintptr(unsafe.Pointer(ppDacl)), uintptr(unsafe.Pointer(ppSacl)), uintptr(unsafe.Pointer(ppSecurityDescriptor)), 0) + if r0 != 0 { + win32err = syscall.Errno(r0) } return } -func setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (err error) { - r1, _, e1 := syscall.Syscall6(procSetEntriesInAclW.Addr(), 4, uintptr(count), uintptr(pListOfEEs), uintptr(oldAcl), uintptr(unsafe.Pointer(newAcl)), 0, 0) - if r1 != 0 { - err = errnoErr(e1) +func setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (win32err error) { + r0, _, _ := syscall.Syscall6(procSetEntriesInAclW.Addr(), 4, uintptr(count), uintptr(pListOfEEs), uintptr(oldAcl), uintptr(unsafe.Pointer(newAcl)), 0, 0) + if r0 != 0 { + win32err = syscall.Errno(r0) } return } -func setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (err error) { - r1, _, e1 := syscall.Syscall9(procSetSecurityInfo.Addr(), 7, uintptr(handle), uintptr(objectType), uintptr(si), uintptr(psidOwner), uintptr(psidGroup), uintptr(pDacl), uintptr(pSacl), 0, 0) - if r1 != 0 { - err = errnoErr(e1) +func setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (win32err error) { + r0, _, _ := syscall.Syscall9(procSetSecurityInfo.Addr(), 7, uintptr(handle), uintptr(objectType), uintptr(si), uintptr(psidOwner), uintptr(psidGroup), uintptr(pDacl), uintptr(pSacl), 0, 0) + if r0 != 0 { + win32err = syscall.Errno(r0) } return } diff --git a/vendor/github.com/Microsoft/go-winio/privilege.go b/vendor/github.com/Microsoft/go-winio/privilege.go index 9c83d36fe5..c3dd7c2176 100644 --- a/vendor/github.com/Microsoft/go-winio/privilege.go +++ b/vendor/github.com/Microsoft/go-winio/privilege.go @@ -28,8 +28,9 @@ const ( ERROR_NOT_ALL_ASSIGNED syscall.Errno = 1300 - SeBackupPrivilege = "SeBackupPrivilege" - SeRestorePrivilege = "SeRestorePrivilege" + SeBackupPrivilege = "SeBackupPrivilege" + SeRestorePrivilege = "SeRestorePrivilege" + SeSecurityPrivilege = "SeSecurityPrivilege" ) const ( diff --git a/vendor/github.com/Microsoft/go-winio/vhd/vhd.go b/vendor/github.com/Microsoft/go-winio/vhd/vhd.go index b03b789e65..f7f78fc230 100644 --- a/vendor/github.com/Microsoft/go-winio/vhd/vhd.go +++ b/vendor/github.com/Microsoft/go-winio/vhd/vhd.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package vhd @@ -7,17 +8,16 @@ import ( "syscall" "github.com/Microsoft/go-winio/pkg/guid" - "github.com/pkg/errors" "golang.org/x/sys/windows" ) //go:generate go run mksyscall_windows.go -output zvhd_windows.go vhd.go -//sys createVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (err error) [failretval != 0] = virtdisk.CreateVirtualDisk -//sys openVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *OpenVirtualDiskParameters, handle *syscall.Handle) (err error) [failretval != 0] = virtdisk.OpenVirtualDisk -//sys attachVirtualDisk(handle syscall.Handle, securityDescriptor *uintptr, attachVirtualDiskFlag uint32, providerSpecificFlags uint32, parameters *AttachVirtualDiskParameters, overlapped *syscall.Overlapped) (err error) [failretval != 0] = virtdisk.AttachVirtualDisk -//sys detachVirtualDisk(handle syscall.Handle, detachVirtualDiskFlags uint32, providerSpecificFlags uint32) (err error) [failretval != 0] = virtdisk.DetachVirtualDisk -//sys getVirtualDiskPhysicalPath(handle syscall.Handle, diskPathSizeInBytes *uint32, buffer *uint16) (err error) [failretval != 0] = virtdisk.GetVirtualDiskPhysicalPath +//sys createVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (win32err error) = virtdisk.CreateVirtualDisk +//sys openVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *openVirtualDiskParameters, handle *syscall.Handle) (win32err error) = virtdisk.OpenVirtualDisk +//sys attachVirtualDisk(handle syscall.Handle, securityDescriptor *uintptr, attachVirtualDiskFlag uint32, providerSpecificFlags uint32, parameters *AttachVirtualDiskParameters, overlapped *syscall.Overlapped) (win32err error) = virtdisk.AttachVirtualDisk +//sys detachVirtualDisk(handle syscall.Handle, detachVirtualDiskFlags uint32, providerSpecificFlags uint32) (win32err error) = virtdisk.DetachVirtualDisk +//sys getVirtualDiskPhysicalPath(handle syscall.Handle, diskPathSizeInBytes *uint32, buffer *uint16) (win32err error) = virtdisk.GetVirtualDiskPhysicalPath type ( CreateVirtualDiskFlag uint32 @@ -62,13 +62,27 @@ type OpenVirtualDiskParameters struct { Version2 OpenVersion2 } +// The higher level `OpenVersion2` struct uses bools to refer to `GetInfoOnly` and `ReadOnly` for ease of use. However, +// the internal windows structure uses `BOOLS` aka int32s for these types. `openVersion2` is used for translating +// `OpenVersion2` fields to the correct windows internal field types on the `Open____` methods. +type openVersion2 struct { + getInfoOnly int32 + readOnly int32 + resiliencyGUID guid.GUID +} + +type openVirtualDiskParameters struct { + version uint32 + version2 openVersion2 +} + type AttachVersion2 struct { RestrictedOffset uint64 RestrictedLength uint64 } type AttachVirtualDiskParameters struct { - Version uint32 // Must always be set to 2 + Version uint32 Version2 AttachVersion2 } @@ -146,16 +160,13 @@ func CreateVhdx(path string, maxSizeInGb, blockSizeInMb uint32) error { return err } - if err := syscall.CloseHandle(handle); err != nil { - return err - } - return nil + return syscall.CloseHandle(handle) } // DetachVirtualDisk detaches a virtual hard disk by handle. func DetachVirtualDisk(handle syscall.Handle) (err error) { if err := detachVirtualDisk(handle, 0, 0); err != nil { - return errors.Wrap(err, "failed to detach virtual disk") + return fmt.Errorf("failed to detach virtual disk: %w", err) } return nil } @@ -185,7 +196,7 @@ func AttachVirtualDisk(handle syscall.Handle, attachVirtualDiskFlag AttachVirtua parameters, nil, ); err != nil { - return errors.Wrap(err, "failed to attach virtual disk") + return fmt.Errorf("failed to attach virtual disk: %w", err) } return nil } @@ -209,7 +220,7 @@ func AttachVhd(path string) (err error) { AttachVirtualDiskFlagNone, ¶ms, ); err != nil { - return errors.Wrap(err, "failed to attach virtual disk") + return fmt.Errorf("failed to attach virtual disk: %w", err) } return nil } @@ -234,19 +245,35 @@ func OpenVirtualDiskWithParameters(vhdPath string, virtualDiskAccessMask Virtual var ( handle syscall.Handle defaultType VirtualStorageType + getInfoOnly int32 + readOnly int32 ) if parameters.Version != 2 { return handle, fmt.Errorf("only version 2 VHDs are supported, found version: %d", parameters.Version) } + if parameters.Version2.GetInfoOnly { + getInfoOnly = 1 + } + if parameters.Version2.ReadOnly { + readOnly = 1 + } + params := &openVirtualDiskParameters{ + version: parameters.Version, + version2: openVersion2{ + getInfoOnly, + readOnly, + parameters.Version2.ResiliencyGUID, + }, + } if err := openVirtualDisk( &defaultType, vhdPath, uint32(virtualDiskAccessMask), uint32(openVirtualDiskFlags), - parameters, + params, &handle, ); err != nil { - return 0, errors.Wrap(err, "failed to open virtual disk") + return 0, fmt.Errorf("failed to open virtual disk: %w", err) } return handle, nil } @@ -272,7 +299,7 @@ func CreateVirtualDisk(path string, virtualDiskAccessMask VirtualDiskAccessMask, nil, &handle, ); err != nil { - return handle, errors.Wrap(err, "failed to create virtual disk") + return handle, fmt.Errorf("failed to create virtual disk: %w", err) } return handle, nil } @@ -290,7 +317,7 @@ func GetVirtualDiskPhysicalPath(handle syscall.Handle) (_ string, err error) { &diskPathSizeInBytes, &diskPhysicalPathBuf[0], ); err != nil { - return "", errors.Wrap(err, "failed to get disk physical path") + return "", fmt.Errorf("failed to get disk physical path: %w", err) } return windows.UTF16ToString(diskPhysicalPathBuf[:]), nil } @@ -314,10 +341,10 @@ func CreateDiffVhd(diffVhdPath, baseVhdPath string, blockSizeInMB uint32) error createParams, ) if err != nil { - return fmt.Errorf("failed to create differencing vhd: %s", err) + return fmt.Errorf("failed to create differencing vhd: %w", err) } if err := syscall.CloseHandle(vhdHandle); err != nil { - return fmt.Errorf("failed to close differencing vhd handle: %s", err) + return fmt.Errorf("failed to close differencing vhd handle: %w", err) } return nil } diff --git a/vendor/github.com/Microsoft/go-winio/vhd/zvhd_windows.go b/vendor/github.com/Microsoft/go-winio/vhd/zvhd_windows.go index 572f7b42f1..1d7498db3b 100644 --- a/vendor/github.com/Microsoft/go-winio/vhd/zvhd_windows.go +++ b/vendor/github.com/Microsoft/go-winio/vhd/zvhd_windows.go @@ -47,60 +47,60 @@ var ( procOpenVirtualDisk = modvirtdisk.NewProc("OpenVirtualDisk") ) -func attachVirtualDisk(handle syscall.Handle, securityDescriptor *uintptr, attachVirtualDiskFlag uint32, providerSpecificFlags uint32, parameters *AttachVirtualDiskParameters, overlapped *syscall.Overlapped) (err error) { - r1, _, e1 := syscall.Syscall6(procAttachVirtualDisk.Addr(), 6, uintptr(handle), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(attachVirtualDiskFlag), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(overlapped))) - if r1 != 0 { - err = errnoErr(e1) +func attachVirtualDisk(handle syscall.Handle, securityDescriptor *uintptr, attachVirtualDiskFlag uint32, providerSpecificFlags uint32, parameters *AttachVirtualDiskParameters, overlapped *syscall.Overlapped) (win32err error) { + r0, _, _ := syscall.Syscall6(procAttachVirtualDisk.Addr(), 6, uintptr(handle), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(attachVirtualDiskFlag), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(overlapped))) + if r0 != 0 { + win32err = syscall.Errno(r0) } return } -func createVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (err error) { +func createVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (win32err error) { var _p0 *uint16 - _p0, err = syscall.UTF16PtrFromString(path) - if err != nil { + _p0, win32err = syscall.UTF16PtrFromString(path) + if win32err != nil { return } return _createVirtualDisk(virtualStorageType, _p0, virtualDiskAccessMask, securityDescriptor, createVirtualDiskFlags, providerSpecificFlags, parameters, overlapped, handle) } -func _createVirtualDisk(virtualStorageType *VirtualStorageType, path *uint16, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (err error) { - r1, _, e1 := syscall.Syscall9(procCreateVirtualDisk.Addr(), 9, uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(createVirtualDiskFlags), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(handle))) - if r1 != 0 { - err = errnoErr(e1) +func _createVirtualDisk(virtualStorageType *VirtualStorageType, path *uint16, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (win32err error) { + r0, _, _ := syscall.Syscall9(procCreateVirtualDisk.Addr(), 9, uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(createVirtualDiskFlags), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(handle))) + if r0 != 0 { + win32err = syscall.Errno(r0) } return } -func detachVirtualDisk(handle syscall.Handle, detachVirtualDiskFlags uint32, providerSpecificFlags uint32) (err error) { - r1, _, e1 := syscall.Syscall(procDetachVirtualDisk.Addr(), 3, uintptr(handle), uintptr(detachVirtualDiskFlags), uintptr(providerSpecificFlags)) - if r1 != 0 { - err = errnoErr(e1) +func detachVirtualDisk(handle syscall.Handle, detachVirtualDiskFlags uint32, providerSpecificFlags uint32) (win32err error) { + r0, _, _ := syscall.Syscall(procDetachVirtualDisk.Addr(), 3, uintptr(handle), uintptr(detachVirtualDiskFlags), uintptr(providerSpecificFlags)) + if r0 != 0 { + win32err = syscall.Errno(r0) } return } -func getVirtualDiskPhysicalPath(handle syscall.Handle, diskPathSizeInBytes *uint32, buffer *uint16) (err error) { - r1, _, e1 := syscall.Syscall(procGetVirtualDiskPhysicalPath.Addr(), 3, uintptr(handle), uintptr(unsafe.Pointer(diskPathSizeInBytes)), uintptr(unsafe.Pointer(buffer))) - if r1 != 0 { - err = errnoErr(e1) +func getVirtualDiskPhysicalPath(handle syscall.Handle, diskPathSizeInBytes *uint32, buffer *uint16) (win32err error) { + r0, _, _ := syscall.Syscall(procGetVirtualDiskPhysicalPath.Addr(), 3, uintptr(handle), uintptr(unsafe.Pointer(diskPathSizeInBytes)), uintptr(unsafe.Pointer(buffer))) + if r0 != 0 { + win32err = syscall.Errno(r0) } return } -func openVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *OpenVirtualDiskParameters, handle *syscall.Handle) (err error) { +func openVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *openVirtualDiskParameters, handle *syscall.Handle) (win32err error) { var _p0 *uint16 - _p0, err = syscall.UTF16PtrFromString(path) - if err != nil { + _p0, win32err = syscall.UTF16PtrFromString(path) + if win32err != nil { return } return _openVirtualDisk(virtualStorageType, _p0, virtualDiskAccessMask, openVirtualDiskFlags, parameters, handle) } -func _openVirtualDisk(virtualStorageType *VirtualStorageType, path *uint16, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *OpenVirtualDiskParameters, handle *syscall.Handle) (err error) { - r1, _, e1 := syscall.Syscall6(procOpenVirtualDisk.Addr(), 6, uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(openVirtualDiskFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(handle))) - if r1 != 0 { - err = errnoErr(e1) +func _openVirtualDisk(virtualStorageType *VirtualStorageType, path *uint16, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *openVirtualDiskParameters, handle *syscall.Handle) (win32err error) { + r0, _, _ := syscall.Syscall6(procOpenVirtualDisk.Addr(), 6, uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(openVirtualDiskFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(handle))) + if r0 != 0 { + win32err = syscall.Errno(r0) } return } diff --git a/vendor/modules.txt b/vendor/modules.txt index 6462b39e28..244a1d53f4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,8 +1,8 @@ # github.com/BurntSushi/toml v0.3.1 ## explicit github.com/BurntSushi/toml -# github.com/Microsoft/go-winio v0.4.17 -## explicit; go 1.12 +# github.com/Microsoft/go-winio v0.5.2 +## explicit; go 1.13 github.com/Microsoft/go-winio github.com/Microsoft/go-winio/backuptar github.com/Microsoft/go-winio/pkg/etw From c2984f8831e6ac3a913acae44bc0d700e1014085 Mon Sep 17 00:00:00 2001 From: Hamza El-Saawy Date: Tue, 19 Apr 2022 13:34:06 -0400 Subject: [PATCH 2/2] PR: scsi mount, security policy, typo Signed-off-by: Hamza El-Saawy --- .gitignore | 9 ++++++++- internal/guest/runtime/hcsv2/process.go | 2 +- internal/tools/uvmboot/lcow.go | 3 ++- internal/tools/uvmboot/mounts.go | 11 +++++++++-- test/vendor/github.com/Microsoft/hcsshim/.gitignore | 9 ++++++++- 5 files changed, 28 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 8c32a3a6ec..b7bde7caf3 100644 --- a/.gitignore +++ b/.gitignore @@ -32,9 +32,12 @@ bin/* rootfs/* rootfs-conv/* *.o -/build/ +build +protobuf + deps/* out/* +tmp # test results test/results @@ -46,3 +49,7 @@ build.ninja # go workspaces go.work go.work.sum + +# ninja build +.ninja_log +build.ninja diff --git a/internal/guest/runtime/hcsv2/process.go b/internal/guest/runtime/hcsv2/process.go index 5731932e70..f39a177ae5 100644 --- a/internal/guest/runtime/hcsv2/process.go +++ b/internal/guest/runtime/hcsv2/process.go @@ -264,7 +264,7 @@ type externalProcess struct { remove func(pid int) } -var _ Process = &containerProcess{} +var _ Process = &externalProcess{} func (ep *externalProcess) Kill(ctx context.Context, signal syscall.Signal) error { if err := syscall.Kill(int(ep.cmd.Process.Pid), signal); err != nil { diff --git a/internal/tools/uvmboot/lcow.go b/internal/tools/uvmboot/lcow.go index 6bb7679c08..7ebe84e2d7 100644 --- a/internal/tools/uvmboot/lcow.go +++ b/internal/tools/uvmboot/lcow.go @@ -109,7 +109,7 @@ var lcowCommand = cli.Command{ 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, " + + "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` optionally mounts as writeable", }, cli.StringSliceFlag{ @@ -221,6 +221,7 @@ func createLCOWOptions(_ context.Context, c *cli.Context, id string) (*uvm.Optio if c.IsSet(securityPolicyArgName) { options.SecurityPolicy = c.String(options.SecurityPolicy) + options.SecurityPolicyEnabled = true } return options, nil diff --git a/internal/tools/uvmboot/mounts.go b/internal/tools/uvmboot/mounts.go index ca1c277fa4..8e7145114d 100644 --- a/internal/tools/uvmboot/mounts.go +++ b/internal/tools/uvmboot/mounts.go @@ -48,6 +48,10 @@ func shareFiles(ctx context.Context, c *cli.Context, vm *uvm.UtilityVM) error { func shareFilesLCOW(ctx context.Context, c *cli.Context, vm *uvm.UtilityVM) error { for _, s := range parseMounts(c, shareFilesArgName) { + if s.guest == "" { + return fmt.Errorf("file shares %q has invalid quest destination: %q", s.host, s.guest) + } + if err := vm.Share(ctx, s.host, s.guest, !s.writable); err != nil { return fmt.Errorf("could not share file or directory %s: %w", s.host, err) } else { @@ -90,12 +94,15 @@ func parseMounts(c *cli.Context, n string) []mount { func mountFromString(s string) (m mount, _ error) { ps := strings.Split(s, ",") - if len(ps) != 2 && len(ps) != 3 { + if len(ps) >= 3 { return m, errors.New("too many parts") } m.host = ps[0] - m.guest = ps[1] + + if len(ps) == 2 { + m.guest = ps[1] + } if len(ps) == 3 && strings.ToLower(ps[2]) == "w" { m.writable = true diff --git a/test/vendor/github.com/Microsoft/hcsshim/.gitignore b/test/vendor/github.com/Microsoft/hcsshim/.gitignore index 8c32a3a6ec..b7bde7caf3 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/.gitignore +++ b/test/vendor/github.com/Microsoft/hcsshim/.gitignore @@ -32,9 +32,12 @@ bin/* rootfs/* rootfs-conv/* *.o -/build/ +build +protobuf + deps/* out/* +tmp # test results test/results @@ -46,3 +49,7 @@ build.ninja # go workspaces go.work go.work.sum + +# ninja build +.ninja_log +build.ninja