From 132cb111c2c7cc2f0510fa4c1ac959d393a2865a Mon Sep 17 00:00:00 2001 From: Hamza El-Saawy Date: Tue, 19 Apr 2022 15:20:12 -0400 Subject: [PATCH] GCS tests and features/bugfixes to support them Exposed data and functionality for testing GCS: * `internal\guest\runtime\hcsv2.Container.InitProcess()` * `internal\guest\runtime\hcsv2.GetOrAddNetworkNamespace()` * `internal\guest\runtime\hcsv2.RemoveNetworkNamespace()` * `internal\guest\runtime\hcsv2.Host.SecurityPolicyEnforcer()` * `internal\guest\runtime\hcsv2.Host.Transport()` Fixed bug where `host.RemoveContainer` did not remove the network namespace for standalone and pod containers. Updated go-winio version to include bugfixes for closing hvsockets, specifically to close a socket for writing (needed by internal\cmd to signal that the stdin stream has finished). Added doc.go files to guest packages to prevent linter/compiler errors under windows. The tests themselves are broken out here: https://github.com/microsoft/hcsshim/pull/1352 https://github.com/microsoft/hcsshim/pull/1351 Signed-off-by: Hamza El-Saawy --- .gitignore | 7 +- functional_tests.ps1 | 12 - go.mod | 2 +- go.sum | 3 +- internal/guest/linux/doc.go | 2 + internal/guest/linux/ioctl.go | 1 - internal/guest/policy/doc.go | 1 + internal/guest/runtime/hcsv2/container.go | 11 +- internal/guest/runtime/hcsv2/network.go | 10 +- internal/guest/runtime/hcsv2/network_test.go | 26 +- internal/guest/runtime/hcsv2/process.go | 4 + .../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/guest/spec/doc.go | 3 + internal/guest/spec/spec.go | 4 +- internal/guest/storage/vmbus/doc.go | 1 + 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 + .../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 | 7 +- .../Microsoft/hcsshim/functional_tests.ps1 | 12 - .../hcsshim/internal/guest/spec/doc.go | 3 + .../hcsshim/internal/guest/spec/spec.go | 4 +- 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 + .../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 +- 50 files changed, 1213 insertions(+), 858 deletions(-) delete mode 100644 functional_tests.ps1 create mode 100644 internal/guest/linux/doc.go create mode 100644 internal/guest/policy/doc.go create mode 100644 internal/guest/runtime/runc/container.go create mode 100644 internal/guest/runtime/runc/process.go create mode 100644 internal/guest/spec/doc.go create mode 100644 internal/guest/storage/vmbus/doc.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/guest/spec/doc.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..e81c4f97e3 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ service/pkg/ *.img *.vhd *.tar.gz +*.tar # Make stuff .rootfs-done @@ -36,5 +37,9 @@ rootfs-conv/* deps/* out/* +# test results +test/results + +# go workspace files 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/internal/guest/linux/doc.go b/internal/guest/linux/doc.go new file mode 100644 index 0000000000..9b05ff49a9 --- /dev/null +++ b/internal/guest/linux/doc.go @@ -0,0 +1,2 @@ +// Package linux contains definitions required for making a linux ioctl. +package linux diff --git a/internal/guest/linux/ioctl.go b/internal/guest/linux/ioctl.go index c87a3da9f5..03ad923502 100644 --- a/internal/guest/linux/ioctl.go +++ b/internal/guest/linux/ioctl.go @@ -1,7 +1,6 @@ //go:build linux // +build linux -// Package linux contains definitions required for making a linux ioctl. package linux import ( diff --git a/internal/guest/policy/doc.go b/internal/guest/policy/doc.go new file mode 100644 index 0000000000..8cbf7ee3fc --- /dev/null +++ b/internal/guest/policy/doc.go @@ -0,0 +1 @@ +package policy diff --git a/internal/guest/runtime/hcsv2/container.go b/internal/guest/runtime/hcsv2/container.go index cc4304c2f3..415dc613e1 100644 --- a/internal/guest/runtime/hcsv2/container.go +++ b/internal/guest/runtime/hcsv2/container.go @@ -123,6 +123,11 @@ 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 { + 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) { @@ -130,7 +135,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 } @@ -245,3 +250,7 @@ func (c *Container) getStatus() containerStatus { func (c *Container) setStatus(st containerStatus) { atomic.StoreUint32((*uint32)(&c.status), uint32(st)) } + +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..5edc7ff7a1 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 %d to network namespace %q", n.pid, n.id) } 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 e68a63070c..f39a177ae5 100644 --- a/internal/guest/runtime/hcsv2/process.go +++ b/internal/guest/runtime/hcsv2/process.go @@ -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 = &externalProcess{} + 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 380112a00d..4725ae16f9 100644 --- a/internal/guest/runtime/hcsv2/uvm.go +++ b/internal/guest/runtime/hcsv2/uvm.go @@ -115,10 +115,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) } @@ -580,7 +599,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 } @@ -588,7 +607,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/guest/spec/doc.go b/internal/guest/spec/doc.go new file mode 100644 index 0000000000..abc59d664f --- /dev/null +++ b/internal/guest/spec/doc.go @@ -0,0 +1,3 @@ +// Package spec encapsulates a number of GCS specific oci spec modifications, e.g., +// networking mounts, sandbox path substitutions in guest etc. +package spec diff --git a/internal/guest/spec/spec.go b/internal/guest/spec/spec.go index 842e1822c4..3057a1a351 100644 --- a/internal/guest/spec/spec.go +++ b/internal/guest/spec/spec.go @@ -1,10 +1,8 @@ //go:build linux // +build linux -// Package spec encapsulates a number of GCS specific oci spec modifications, e.g., -// networking mounts, sandbox path substitutions in guest etc. -// // TODO: consider moving oci spec specific code from /internal/guest/runtime/hcsv2/spec.go + package spec import ( diff --git a/internal/guest/storage/vmbus/doc.go b/internal/guest/storage/vmbus/doc.go new file mode 100644 index 0000000000..eeacbb6a72 --- /dev/null +++ b/internal/guest/storage/vmbus/doc.go @@ -0,0 +1 @@ +package vmbus 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/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..e81c4f97e3 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 @@ -36,5 +37,9 @@ rootfs-conv/* deps/* out/* +# test results +test/results + +# go workspace files 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/internal/guest/spec/doc.go b/test/vendor/github.com/Microsoft/hcsshim/internal/guest/spec/doc.go new file mode 100644 index 0000000000..abc59d664f --- /dev/null +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/guest/spec/doc.go @@ -0,0 +1,3 @@ +// Package spec encapsulates a number of GCS specific oci spec modifications, e.g., +// networking mounts, sandbox path substitutions in guest etc. +package spec diff --git a/test/vendor/github.com/Microsoft/hcsshim/internal/guest/spec/spec.go b/test/vendor/github.com/Microsoft/hcsshim/internal/guest/spec/spec.go index 842e1822c4..3057a1a351 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/internal/guest/spec/spec.go +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/guest/spec/spec.go @@ -1,10 +1,8 @@ //go:build linux // +build linux -// Package spec encapsulates a number of GCS specific oci spec modifications, e.g., -// networking mounts, sandbox path substitutions in guest etc. -// // TODO: consider moving oci spec specific code from /internal/guest/runtime/hcsv2/spec.go + package spec import ( diff --git a/test/vendor/modules.txt b/test/vendor/modules.txt index e91c03c2c1..edc9350ba0 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/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 a90aef84a6..e37f45e276 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