From e047d984dcc7998dea299e6285ed46e51f120ab8 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 18 Mar 2021 21:01:46 +0100 Subject: [PATCH] Remove LCOW code (step 1) The LCOW implementation in dockerd has been deprecated in favor of re-implementation in containerd (in progress). Microsoft started removing the LCOW V1 code from the build dependencies we use in Microsoft/opengcs (soon to be part of Microsoft/hcshhim), which means that we need to start removing this code. This first step removes the lcow graphdriver, the LCOW initialization code, and some LCOW-related utilities. Signed-off-by: Sebastiaan van Stijn --- .github/CODEOWNERS | 1 - api/server/router/image/image_routes.go | 4 - builder/builder-next/builder.go | 6 +- builder/dockerfile/builder.go | 3 - builder/dockerfile/dispatchers.go | 8 +- cmd/dockerd/daemon.go | 2 - container/container.go | 2 +- daemon/create.go | 6 - daemon/daemon_windows.go | 15 +- daemon/graphdriver/lcow/lcow.go | 1174 --------------- daemon/graphdriver/lcow/lcow_svm.go | 421 ------ daemon/graphdriver/lcow/remotefs.go | 139 -- daemon/graphdriver/lcow/remotefs_file.go | 211 --- .../graphdriver/lcow/remotefs_filedriver.go | 123 -- .../graphdriver/lcow/remotefs_pathdriver.go | 212 --- .../graphdriver/register/register_windows.go | 1 - hack/ci/windows.ps1 | 395 ++--- image/tarexport/load.go | 8 +- pkg/system/lcow.go | 48 - pkg/system/lcow_unsupported.go | 13 - pkg/system/path.go | 32 +- pkg/system/path_unix.go | 6 + pkg/system/path_windows.go | 23 +- project/PACKAGERS.md | 6 - .../ext4/internal/compactext4/compact.go | 1325 ----------------- .../hcsshim/ext4/internal/format/format.go | 411 ----- .../hcsshim/ext4/tar2ext4/tar2ext4.go | 209 --- .../hcsshim/ext4/tar2ext4/vhdfooter.go | 76 - .../opengcs/service/gcsutils/remotefs/defs.go | 109 -- .../service/gcsutils/remotefs/remotefs.go | 578 ------- .../service/gcsutils/remotefs/utils.go | 170 --- 31 files changed, 191 insertions(+), 5546 deletions(-) delete mode 100644 daemon/graphdriver/lcow/lcow.go delete mode 100644 daemon/graphdriver/lcow/lcow_svm.go delete mode 100644 daemon/graphdriver/lcow/remotefs.go delete mode 100644 daemon/graphdriver/lcow/remotefs_file.go delete mode 100644 daemon/graphdriver/lcow/remotefs_filedriver.go delete mode 100644 daemon/graphdriver/lcow/remotefs_pathdriver.go delete mode 100644 pkg/system/lcow.go delete mode 100644 vendor/github.com/Microsoft/hcsshim/ext4/internal/compactext4/compact.go delete mode 100644 vendor/github.com/Microsoft/hcsshim/ext4/internal/format/format.go delete mode 100644 vendor/github.com/Microsoft/hcsshim/ext4/tar2ext4/tar2ext4.go delete mode 100644 vendor/github.com/Microsoft/hcsshim/ext4/tar2ext4/vhdfooter.go delete mode 100644 vendor/github.com/Microsoft/opengcs/service/gcsutils/remotefs/defs.go delete mode 100644 vendor/github.com/Microsoft/opengcs/service/gcsutils/remotefs/remotefs.go delete mode 100644 vendor/github.com/Microsoft/opengcs/service/gcsutils/remotefs/utils.go diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a94b9da292..26e94ba4df 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -6,7 +6,6 @@ builder/** @tonistiigi contrib/mkimage/** @tianon daemon/graphdriver/devmapper/** @rhvgoyal -daemon/graphdriver/lcow/** @johnstep daemon/graphdriver/overlay/** @dmcgowan daemon/graphdriver/overlay2/** @dmcgowan daemon/graphdriver/windows/** @johnstep diff --git a/api/server/router/image/image_routes.go b/api/server/router/image/image_routes.go index f34fe56073..728ba6ef09 100644 --- a/api/server/router/image/image_routes.go +++ b/api/server/router/image/image_routes.go @@ -16,7 +16,6 @@ import ( "github.com/docker/docker/errdefs" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/streamformatter" - "github.com/docker/docker/pkg/system" "github.com/docker/docker/registry" specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" @@ -50,9 +49,6 @@ func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrite if err != nil { return err } - if err := system.ValidatePlatform(sp); err != nil { - return err - } platform = &sp } } diff --git a/builder/builder-next/builder.go b/builder/builder-next/builder.go index 98a52d39df..5ee9d8e30d 100644 --- a/builder/builder-next/builder.go +++ b/builder/builder-next/builder.go @@ -20,7 +20,6 @@ import ( "github.com/docker/docker/libnetwork" "github.com/docker/docker/pkg/idtools" "github.com/docker/docker/pkg/streamformatter" - "github.com/docker/docker/pkg/system" controlapi "github.com/moby/buildkit/api/services/control" "github.com/moby/buildkit/client" "github.com/moby/buildkit/control" @@ -299,13 +298,10 @@ func (b *Builder) Build(ctx context.Context, opt backend.BuildConfig) (*builder. if opt.Options.Platform != "" { // same as in newBuilder in builder/dockerfile.builder.go // TODO: remove once opt.Options.Platform is of type specs.Platform - sp, err := platforms.Parse(opt.Options.Platform) + _, err := platforms.Parse(opt.Options.Platform) if err != nil { return nil, err } - if err := system.ValidatePlatform(sp); err != nil { - return nil, err - } frontendAttrs["platform"] = opt.Options.Platform } diff --git a/builder/dockerfile/builder.go b/builder/dockerfile/builder.go index a0bfb289c2..0369a1d82d 100644 --- a/builder/dockerfile/builder.go +++ b/builder/dockerfile/builder.go @@ -159,9 +159,6 @@ func newBuilder(clientCtx context.Context, options builderOptions) (*Builder, er if err != nil { return nil, err } - if err := system.ValidatePlatform(sp); err != nil { - return nil, err - } b.platform = &sp } diff --git a/builder/dockerfile/dispatchers.go b/builder/dockerfile/dispatchers.go index f755f12650..2968c87195 100644 --- a/builder/dockerfile/dispatchers.go +++ b/builder/dockerfile/dispatchers.go @@ -172,9 +172,6 @@ func initializeStage(d dispatchRequest, cmd *instructions.Stage) error { if err != nil { return errors.Wrapf(err, "failed to parse platform %s", v) } - if err := system.ValidatePlatform(p); err != nil { - return err - } platform = &p } @@ -264,10 +261,7 @@ func (d *dispatchRequest) getImageOrStage(name string, platform *specs.Platform) // from it. if runtime.GOOS == "windows" { if platform == nil || platform.OS == "linux" { - if !system.LCOWSupported() { - return nil, errors.New("Linux containers are not supported on this system") - } - imageImage.OS = "linux" + return nil, errors.New("Linux containers are not supported on this system") } else if platform.OS == "windows" { return nil, errors.New("Windows does not support FROM scratch") } else { diff --git a/cmd/dockerd/daemon.go b/cmd/dockerd/daemon.go index bb3d72ab38..5d213ad120 100644 --- a/cmd/dockerd/daemon.go +++ b/cmd/dockerd/daemon.go @@ -117,8 +117,6 @@ func (cli *DaemonCli) start(opts *daemonOptions) (err error) { return fmt.Errorf("dockerd needs to be started with root. To see how to run dockerd in rootless mode with unprivileged user, see the documentation") } - system.InitLCOW(cli.Config.Experimental) - if err := setDefaultUmask(); err != nil { return err } diff --git a/container/container.go b/container/container.go index f6c7d51c55..84ce8e853a 100644 --- a/container/container.go +++ b/container/container.go @@ -746,7 +746,7 @@ func (container *Container) CreateDaemonEnvironment(tty bool, linkedEnv []string } env := make([]string, 0, envSize) - if runtime.GOOS != "windows" || (runtime.GOOS == "windows" && os == "linux") { + if runtime.GOOS != "windows" { env = append(env, "PATH="+system.DefaultPathEnv(os)) env = append(env, "HOSTNAME="+container.Config.Hostname) if tty { diff --git a/daemon/create.go b/daemon/create.go index 503869654b..918db07b06 100644 --- a/daemon/create.go +++ b/daemon/create.go @@ -69,12 +69,6 @@ func (daemon *Daemon) containerCreate(opts createOpts) (containertypes.Container if err == nil { os = img.OS } - } else { - // This mean scratch. On Windows, we can safely assume that this is a linux - // container. On other platforms, it's the host OS (which it already is) - if isWindows && system.LCOWSupported() { - os = "linux" - } } warnings, err := daemon.verifyContainerSettings(os, opts.params.HostConfig, opts.params.Config, false) diff --git a/daemon/daemon_windows.go b/daemon/daemon_windows.go index 740a127a61..76e3701f8a 100644 --- a/daemon/daemon_windows.go +++ b/daemon/daemon_windows.go @@ -499,19 +499,12 @@ func (daemon *Daemon) runAsHyperVContainer(hostConfig *containertypes.HostConfig // conditionalMountOnStart is a platform specific helper function during the // container start to call mount. func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error { - - // Bail out now for Linux containers. We cannot mount the containers filesystem on the - // host as it is a non-Windows filesystem. - if system.LCOWSupported() && container.OS != "windows" { + if daemon.runAsHyperVContainer(container.HostConfig) { + // We do not mount if a Hyper-V container as it needs to be mounted inside the + // utility VM, not the host. return nil } - - // We do not mount if a Hyper-V container as it needs to be mounted inside the - // utility VM, not the host. - if !daemon.runAsHyperVContainer(container.HostConfig) { - return daemon.Mount(container) - } - return nil + return daemon.Mount(container) } // conditionalUnmountOnCleanup is a platform specific helper function called diff --git a/daemon/graphdriver/lcow/lcow.go b/daemon/graphdriver/lcow/lcow.go deleted file mode 100644 index f7af11ed26..0000000000 --- a/daemon/graphdriver/lcow/lcow.go +++ /dev/null @@ -1,1174 +0,0 @@ -// +build windows - -// Locale: en-gb -// About: Graph-driver for Linux Containers On Windows (LCOW) -// -// This graphdriver runs in two modes. Yet to be determined which one will -// be the shipping mode. The global mode is where a single utility VM -// is used for all service VM tool operations. This isn't safe security-wise -// as it's attaching a sandbox of multiple containers to it, containing -// untrusted data. This may be fine for client devops scenarios. In -// safe mode, a unique utility VM is instantiated for all service VM tool -// operations. The downside of safe-mode is that operations are slower as -// a new service utility VM has to be started and torn-down when needed. -// -// Options: -// -// The following options are read by the graphdriver itself: -// -// * lcow.globalmode - Enables global service VM Mode -// -- Possible values: true/false -// -- Default if omitted: false -// -// * lcow.sandboxsize - Specifies a custom sandbox size in GB for starting a container -// -- Possible values: >= default sandbox size (opengcs defined, currently 20) -// -- Default if omitted: 20 -// -// The following options are read by opengcs: -// -// * lcow.kirdpath - Specifies a custom path to a kernel/initrd pair -// -- Possible values: Any local path that is not a mapped drive -// -- Default if omitted: %ProgramFiles%\Linux Containers -// -// * lcow.bootparameters - Specifies additional boot parameters for booting in kernel+initrd mode -// -- Possible values: Any valid linux kernel boot options -// -- Default if omitted: -// -// * lcow.timeout - Specifies a timeout for utility VM operations in seconds -// -- Possible values: >=0 -// -- Default if omitted: 300 - -// TODO: Grab logs from SVM at terminate or errors - -package lcow // import "github.com/docker/docker/daemon/graphdriver/lcow" - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "os" - "path" - "path/filepath" - "strconv" - "strings" - "sync" - "syscall" - "time" - - "github.com/Microsoft/go-winio/pkg/security" - "github.com/Microsoft/hcsshim" - "github.com/Microsoft/hcsshim/ext4/tar2ext4" - "github.com/Microsoft/opengcs/client" - "github.com/docker/docker/daemon/graphdriver" - "github.com/docker/docker/pkg/archive" - "github.com/docker/docker/pkg/containerfs" - "github.com/docker/docker/pkg/idtools" - "github.com/docker/docker/pkg/ioutils" - "github.com/docker/docker/pkg/reexec" - "github.com/sirupsen/logrus" -) - -// noreexec controls reexec functionality. Off by default, on for debugging purposes. -var noreexec = false - -// init registers this driver to the register. It gets initialised by the -// function passed in the second parameter, implemented in this file. -func init() { - graphdriver.Register("lcow", InitDriver) - // DOCKER_LCOW_NOREEXEC allows for inline processing which makes - // debugging issues in the re-exec codepath significantly easier. - if os.Getenv("DOCKER_LCOW_NOREEXEC") != "" { - logrus.Warnf("LCOW Graphdriver is set to not re-exec. This is intended for debugging purposes only.") - noreexec = true - } else { - reexec.Register("docker-lcow-tar2ext4", tar2ext4Reexec) - } -} - -const ( - // sandboxFilename is the name of the file containing a layer's sandbox (read-write layer). - sandboxFilename = "sandbox.vhdx" - - // scratchFilename is the name of the scratch-space used by an SVM to avoid running out of memory. - scratchFilename = "scratch.vhdx" - - // layerFilename is the name of the file containing a layer's read-only contents. - // Note this really is VHD format, not VHDX. - layerFilename = "layer.vhd" - - // toolsScratchPath is a location in a service utility VM that the tools can use as a - // scratch space to avoid running out of memory. - toolsScratchPath = "/tmp/scratch" - - // svmGlobalID is the ID used in the serviceVMs map for the global service VM when running in "global" mode. - svmGlobalID = "_lcow_global_svm_" - - // cacheDirectory is the sub-folder under the driver's data-root used to cache blank sandbox and scratch VHDs. - cacheDirectory = "cache" - - // scratchDirectory is the sub-folder under the driver's data-root used for scratch VHDs in service VMs - scratchDirectory = "scratch" - - // errOperationPending is the HRESULT returned by the HCS when the VM termination operation is still pending. - errOperationPending syscall.Errno = 0xc0370103 -) - -// Driver represents an LCOW graph driver. -type Driver struct { - dataRoot string // Root path on the host where we are storing everything. - cachedSandboxFile string // Location of the local default-sized cached sandbox. - cachedSandboxMutex sync.Mutex // Protects race conditions from multiple threads creating the cached sandbox. - cachedScratchFile string // Location of the local cached empty scratch space. - cachedScratchMutex sync.Mutex // Protects race conditions from multiple threads creating the cached scratch. - options []string // Graphdriver options we are initialised with. - globalMode bool // Indicates if running in an unsafe/global service VM mode. - defaultSandboxSize uint64 // The default sandbox size to use if one is not specified - - // NOTE: It is OK to use a cache here because Windows does not support - // restoring containers when the daemon dies. - serviceVms *serviceVMMap // Map of the configs representing the service VM(s) we are running. -} - -// layerDetails is the structure returned by a helper function `getLayerDetails` -// for getting information about a layer folder -type layerDetails struct { - filename string // \path\to\sandbox.vhdx or \path\to\layer.vhd - size int64 // size of the above file - isSandbox bool // true if sandbox.vhdx -} - -// deletefiles is a helper function for initialisation where we delete any -// left-over scratch files in case we were previously forcibly terminated. -func deletefiles(path string, f os.FileInfo, err error) error { - if strings.HasSuffix(f.Name(), ".vhdx") { - logrus.Warnf("lcowdriver: init: deleting stale scratch file %s", path) - return os.Remove(path) - } - return nil -} - -// InitDriver returns a new LCOW storage driver. -func InitDriver(dataRoot string, options []string, _, _ []idtools.IDMap) (graphdriver.Driver, error) { - title := "lcowdriver: init:" - - cd := filepath.Join(dataRoot, cacheDirectory) - sd := filepath.Join(dataRoot, scratchDirectory) - - d := &Driver{ - dataRoot: dataRoot, - options: options, - cachedSandboxFile: filepath.Join(cd, sandboxFilename), - cachedScratchFile: filepath.Join(cd, scratchFilename), - serviceVms: &serviceVMMap{ - svms: make(map[string]*serviceVMMapItem), - }, - globalMode: false, - defaultSandboxSize: client.DefaultVhdxSizeGB, - } - - // Looks for relevant options - for _, v := range options { - opt := strings.SplitN(v, "=", 2) - if len(opt) == 2 { - switch strings.ToLower(opt[0]) { - case "lcow.globalmode": - var err error - d.globalMode, err = strconv.ParseBool(opt[1]) - if err != nil { - return nil, fmt.Errorf("%s failed to parse value for 'lcow.globalmode' - must be 'true' or 'false'", title) - } - break - case "lcow.sandboxsize": - var err error - d.defaultSandboxSize, err = strconv.ParseUint(opt[1], 10, 32) - if err != nil { - return nil, fmt.Errorf("%s failed to parse value '%s' for 'lcow.sandboxsize'", title, v) - } - if d.defaultSandboxSize < client.DefaultVhdxSizeGB { - return nil, fmt.Errorf("%s 'lcow.sandboxsize' option cannot be less than %d", title, client.DefaultVhdxSizeGB) - } - break - } - } - } - - // Make sure the dataRoot directory is created - if err := idtools.MkdirAllAndChown(dataRoot, 0700, idtools.Identity{UID: 0, GID: 0}); err != nil { - return nil, fmt.Errorf("%s failed to create '%s': %v", title, dataRoot, err) - } - - // Make sure the cache directory is created under dataRoot - if err := idtools.MkdirAllAndChown(cd, 0700, idtools.Identity{UID: 0, GID: 0}); err != nil { - return nil, fmt.Errorf("%s failed to create '%s': %v", title, cd, err) - } - - // Make sure the scratch directory is created under dataRoot - if err := idtools.MkdirAllAndChown(sd, 0700, idtools.Identity{UID: 0, GID: 0}); err != nil { - return nil, fmt.Errorf("%s failed to create '%s': %v", title, sd, err) - } - - // Delete any items in the scratch directory - filepath.Walk(sd, deletefiles) - - logrus.Infof("%s dataRoot: %s globalMode: %t", title, dataRoot, d.globalMode) - - return d, nil -} - -func (d *Driver) getVMID(id string) string { - if d.globalMode { - return svmGlobalID - } - return id -} - -// remapLongToShortContainerPath does the mapping of a long container path for a -// SCSI attached disk, to a short container path where it's actually mounted. -func remapLongToShortContainerPath(longContainerPath string, attachCounter uint64, svmName string) string { - shortContainerPath := longContainerPath - if shortContainerPath != "" && shortContainerPath != toolsScratchPath { - shortContainerPath = fmt.Sprintf("/tmp/d%d", attachCounter) - logrus.Debugf("lcowdriver: UVM %s: remapping %s --> %s", svmName, longContainerPath, shortContainerPath) - } - return shortContainerPath -} - -// startServiceVMIfNotRunning starts a service utility VM if it is not currently running. -// It can optionally be started with a mapped virtual disk. Returns a opengcs config structure -// representing the VM. -func (d *Driver) startServiceVMIfNotRunning(id string, mvdToAdd []hcsshim.MappedVirtualDisk, context string) (_ *serviceVM, err error) { - // Use the global ID if in global mode - id = d.getVMID(id) - - title := "lcowdriver: startServiceVMIfNotRunning " + id - - // Attempt to add ID to the service vm map - logrus.Debugf("%s: adding entry to service vm map", title) - svm, exists, err := d.serviceVms.add(id) - if err != nil && err == errVMisTerminating { - // VM is in the process of terminating. Wait until it's done and then try again - logrus.Debugf("%s: VM with current ID still in the process of terminating", title) - if err := svm.getStopError(); err != nil { - logrus.Debugf("%s: VM did not stop successfully: %s", title, err) - return nil, err - } - return d.startServiceVMIfNotRunning(id, mvdToAdd, context) - } else if err != nil { - logrus.Debugf("%s: failed to add service vm to map: %s", title, err) - return nil, fmt.Errorf("%s: failed to add to service vm map: %s", title, err) - } - - if exists { - // Service VM is already up and running. In this case, just hot add the vhds. - // Note that hotAddVHDs will remap long to short container paths, so no need - // for us to that here. - logrus.Debugf("%s: service vm already exists. Just hot adding: %+v", title, mvdToAdd) - if err := svm.hotAddVHDs(mvdToAdd...); err != nil { - logrus.Debugf("%s: failed to hot add vhds on service vm creation: %s", title, err) - return nil, fmt.Errorf("%s: failed to hot add vhds on service vm: %s", title, err) - } - return svm, nil - } - - // We are the first service for this id, so we need to start it - logrus.Debugf("%s: service vm doesn't exist. Now starting it up", title) - - defer func() { - // Signal that start has finished, passing in the error if any. - svm.signalStartFinished(err) - if err != nil { - // We added a ref to the VM, since we failed, we should delete the ref. - d.terminateServiceVM(id, "error path on startServiceVMIfNotRunning", false) - } - }() - - // Generate a default configuration - if err := svm.config.GenerateDefault(d.options); err != nil { - return nil, fmt.Errorf("%s: failed to generate default gogcs configuration for global svm (%s): %s", title, context, err) - } - - // For the name, we deliberately suffix if safe-mode to ensure that it doesn't - // clash with another utility VM which may be running for the container itself. - // This also makes it easier to correlate through Get-ComputeProcess. - if id == svmGlobalID { - svm.config.Name = svmGlobalID - } else { - svm.config.Name = fmt.Sprintf("%s_svm", id) - } - - // Ensure we take the cached scratch mutex around the check to ensure the file is complete - // and not in the process of being created by another thread. - scratchTargetFile := filepath.Join(d.dataRoot, scratchDirectory, fmt.Sprintf("%s.vhdx", id)) - - logrus.Debugf("%s: locking cachedScratchMutex", title) - d.cachedScratchMutex.Lock() - if _, err := os.Stat(d.cachedScratchFile); err == nil { - // Make a copy of cached scratch to the scratch directory - logrus.Debugf("%s: (%s) cloning cached scratch for mvd", title, context) - if err := client.CopyFile(d.cachedScratchFile, scratchTargetFile, true); err != nil { - logrus.Debugf("%s: releasing cachedScratchMutex on err: %s", title, err) - d.cachedScratchMutex.Unlock() - return nil, err - } - - // Add the cached clone as a mapped virtual disk - logrus.Debugf("%s: (%s) adding cloned scratch as mvd", title, context) - mvd := hcsshim.MappedVirtualDisk{ - HostPath: scratchTargetFile, - ContainerPath: toolsScratchPath, - CreateInUtilityVM: true, - } - svm.config.MappedVirtualDisks = append(svm.config.MappedVirtualDisks, mvd) - svm.scratchAttached = true - } - - logrus.Debugf("%s: releasing cachedScratchMutex", title) - d.cachedScratchMutex.Unlock() - - // Add mapped virtual disks. First those that are already in the configuration. Generally, - // the only one that will be here is the service VMs scratch. The exception is when invoked - // via the graphdrivers DiffGetter implementation. - for i, mvd := range svm.config.MappedVirtualDisks { - svm.attachCounter++ - svm.attachedVHDs[mvd.HostPath] = &attachedVHD{refCount: 1, attachCounter: svm.attachCounter} - - // No-op for the service VMs scratch disk. Only applicable in the DiffGetter interface invocation. - svm.config.MappedVirtualDisks[i].ContainerPath = remapLongToShortContainerPath(mvd.ContainerPath, svm.attachCounter, svm.config.Name) - } - - // Then the remaining ones to add, and adding them to the startup configuration. - for _, mvd := range mvdToAdd { - svm.attachCounter++ - svm.attachedVHDs[mvd.HostPath] = &attachedVHD{refCount: 1, attachCounter: svm.attachCounter} - mvd.ContainerPath = remapLongToShortContainerPath(mvd.ContainerPath, svm.attachCounter, svm.config.Name) - svm.config.MappedVirtualDisks = append(svm.config.MappedVirtualDisks, mvd) - } - - // Start it. - logrus.Debugf("%s: (%s) starting %s", title, context, svm.config.Name) - if err := svm.config.StartUtilityVM(); err != nil { - return nil, fmt.Errorf("failed to start service utility VM (%s): %s", context, err) - } - - // defer function to terminate the VM if the next steps fail - defer func() { - if err != nil { - waitTerminate(svm, fmt.Sprintf("%s: (%s)", title, context)) - } - }() - - // Now we have a running service VM, we can create the cached scratch file if it doesn't exist. - logrus.Debugf("%s: locking cachedScratchMutex", title) - d.cachedScratchMutex.Lock() - if _, err := os.Stat(d.cachedScratchFile); err != nil { - logrus.Debugf("%s: (%s) creating an SVM scratch", title, context) - - // Don't use svm.CreateExt4Vhdx since that only works when the service vm is setup, - // but we're still in that process right now. - if err := svm.config.CreateExt4Vhdx(scratchTargetFile, client.DefaultVhdxSizeGB, d.cachedScratchFile); err != nil { - logrus.Debugf("%s: (%s) releasing cachedScratchMutex on error path", title, context) - d.cachedScratchMutex.Unlock() - logrus.Debugf("%s: failed to create vm scratch %s: %s", title, scratchTargetFile, err) - return nil, fmt.Errorf("failed to create SVM scratch VHDX (%s): %s", context, err) - } - } - logrus.Debugf("%s: (%s) releasing cachedScratchMutex", title, context) - d.cachedScratchMutex.Unlock() - - // Hot-add the scratch-space if not already attached - if !svm.scratchAttached { - logrus.Debugf("%s: (%s) hot-adding scratch %s", title, context, scratchTargetFile) - if err := svm.hotAddVHDsAtStart(hcsshim.MappedVirtualDisk{ - HostPath: scratchTargetFile, - ContainerPath: toolsScratchPath, - CreateInUtilityVM: true, - }); err != nil { - logrus.Debugf("%s: failed to hot-add scratch %s: %s", title, scratchTargetFile, err) - return nil, fmt.Errorf("failed to hot-add %s failed: %s", scratchTargetFile, err) - } - svm.scratchAttached = true - // Don't need to ref-count here as it will be done via hotAddVHDsAtStart() call above. - } - - logrus.Debugf("%s: (%s) success", title, context) - return svm, nil -} - -// terminateServiceVM terminates a service utility VM if its running if it's, -// not being used by any goroutine, but does nothing when in global mode as it's -// lifetime is limited to that of the daemon. If the force flag is set, then -// the VM will be killed regardless of the ref count or if it's global. -func (d *Driver) terminateServiceVM(id, context string, force bool) (err error) { - // We don't do anything in safe mode unless the force flag has been passed, which - // is only the case for cleanup at driver termination. - if d.globalMode && !force { - logrus.Debugf("lcowdriver: terminateservicevm: %s (%s) - doing nothing as in global mode", id, context) - return nil - } - - id = d.getVMID(id) - - var svm *serviceVM - var lastRef bool - if !force { - // In the not force case, we ref count - svm, lastRef, err = d.serviceVms.decrementRefCount(id) - } else { - // In the force case, we ignore the ref count and just set it to 0 - svm, err = d.serviceVms.setRefCountZero(id) - lastRef = true - } - - if err == errVMUnknown { - return nil - } else if err == errVMisTerminating { - return svm.getStopError() - } else if !lastRef { - return nil - } - - // We run the deletion of the scratch as a deferred function to at least attempt - // clean-up in case of errors. - defer func() { - if svm.scratchAttached { - scratchTargetFile := filepath.Join(d.dataRoot, scratchDirectory, fmt.Sprintf("%s.vhdx", id)) - logrus.Debugf("lcowdriver: terminateservicevm: %s (%s) - deleting scratch %s", id, context, scratchTargetFile) - if errRemove := os.Remove(scratchTargetFile); errRemove != nil { - logrus.Warnf("failed to remove scratch file %s (%s): %s", scratchTargetFile, context, errRemove) - err = errRemove - } - } - - // This function shouldn't actually return error unless there is a bug - if errDelete := d.serviceVms.deleteID(id); errDelete != nil { - logrus.Warnf("failed to service vm from svm map %s (%s): %s", id, context, errDelete) - } - - // Signal that this VM has stopped - svm.signalStopFinished(err) - }() - - // Now it's possible that the service VM failed to start and now we are trying to terminate it. - // In this case, we will relay the error to the goroutines waiting for this vm to stop. - if err := svm.getStartError(); err != nil { - logrus.Debugf("lcowdriver: terminateservicevm: %s had failed to start up: %s", id, err) - return err - } - - if err := waitTerminate(svm, fmt.Sprintf("terminateservicevm: %s (%s)", id, context)); err != nil { - return err - } - - logrus.Debugf("lcowdriver: terminateservicevm: %s (%s) - success", id, context) - return nil -} - -func waitTerminate(svm *serviceVM, context string) error { - if svm.config == nil { - return fmt.Errorf("lcowdriver: waitTermiante: Nil utility VM. %s", context) - } - - logrus.Debugf("lcowdriver: waitTerminate: Calling terminate: %s", context) - if err := svm.config.Uvm.Terminate(); err != nil { - // We might get operation still pending from the HCS. In that case, we shouldn't return - // an error since we call wait right after. - underlyingError := err - if conterr, ok := err.(*hcsshim.ContainerError); ok { - underlyingError = conterr.Err - } - - if syscallErr, ok := underlyingError.(syscall.Errno); ok { - underlyingError = syscallErr - } - - if underlyingError != errOperationPending { - return fmt.Errorf("failed to terminate utility VM (%s): %s", context, err) - } - logrus.Debugf("lcowdriver: waitTerminate: uvm.Terminate() returned operation pending (%s)", context) - } - - logrus.Debugf("lcowdriver: waitTerminate: (%s) - waiting for utility VM to terminate", context) - if err := svm.config.Uvm.WaitTimeout(time.Duration(svm.config.UvmTimeoutSeconds) * time.Second); err != nil { - return fmt.Errorf("failed waiting for utility VM to terminate (%s): %s", context, err) - } - return nil -} - -// String returns the string representation of a driver. This should match -// the name the graph driver has been registered with. -func (d *Driver) String() string { - return "lcow" -} - -// Status returns the status of the driver. -func (d *Driver) Status() [][2]string { - return [][2]string{ - {"LCOW", ""}, - // TODO: Add some more info here - mode, home, .... - } -} - -// Exists returns true if the given id is registered with this driver. -func (d *Driver) Exists(id string) bool { - _, err := os.Lstat(d.dir(id)) - logrus.Debugf("lcowdriver: exists: id %s %t", id, err == nil) - return err == nil -} - -// CreateReadWrite creates a layer that is writable for use as a container -// file system. That equates to creating a sandbox. -func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error { - title := fmt.Sprintf("lcowdriver: createreadwrite: id %s", id) - logrus.Debugf(title) - - // First we need to create the folder - if err := d.Create(id, parent, opts); err != nil { - return err - } - - // Look for an explicit sandbox size option. - sandboxSize := d.defaultSandboxSize - for k, v := range opts.StorageOpt { - switch strings.ToLower(k) { - case "lcow.sandboxsize": - var err error - sandboxSize, err = strconv.ParseUint(v, 10, 32) - if err != nil { - return fmt.Errorf("%s failed to parse value '%s' for 'lcow.sandboxsize'", title, v) - } - if sandboxSize < client.DefaultVhdxSizeGB { - return fmt.Errorf("%s 'lcow.sandboxsize' option cannot be less than %d", title, client.DefaultVhdxSizeGB) - } - break - } - } - - // Massive perf optimisation here. If we know that the RW layer is the default size, - // and that the cached sandbox already exists, and we are running in safe mode, we - // can just do a simple copy into the layers sandbox file without needing to start a - // unique service VM. For a global service VM, it doesn't really matter. Of course, - // this is only the case where the sandbox is the default size. - // - // Make sure we have the sandbox mutex taken while we are examining it. - if sandboxSize == client.DefaultVhdxSizeGB { - logrus.Debugf("%s: locking cachedSandboxMutex", title) - d.cachedSandboxMutex.Lock() - _, err := os.Stat(d.cachedSandboxFile) - logrus.Debugf("%s: releasing cachedSandboxMutex", title) - d.cachedSandboxMutex.Unlock() - if err == nil { - logrus.Debugf("%s: using cached sandbox to populate", title) - if err := client.CopyFile(d.cachedSandboxFile, filepath.Join(d.dir(id), sandboxFilename), true); err != nil { - return err - } - return nil - } - } - - logrus.Debugf("%s: creating SVM to create sandbox", title) - svm, err := d.startServiceVMIfNotRunning(id, nil, "createreadwrite") - if err != nil { - return err - } - defer d.terminateServiceVM(id, "createreadwrite", false) - - // So the sandbox needs creating. If default size ensure we are the only thread populating the cache. - // Non-default size we don't store, just create them one-off so no need to lock the cachedSandboxMutex. - if sandboxSize == client.DefaultVhdxSizeGB { - logrus.Debugf("%s: locking cachedSandboxMutex for creation", title) - d.cachedSandboxMutex.Lock() - defer func() { - logrus.Debugf("%s: releasing cachedSandboxMutex for creation", title) - d.cachedSandboxMutex.Unlock() - }() - } - - // Make sure we don't write to our local cached copy if this is for a non-default size request. - targetCacheFile := d.cachedSandboxFile - if sandboxSize != client.DefaultVhdxSizeGB { - targetCacheFile = "" - } - - // Create the ext4 vhdx - logrus.Debugf("%s: creating sandbox ext4 vhdx", title) - if err := svm.createExt4VHDX(filepath.Join(d.dir(id), sandboxFilename), uint32(sandboxSize), targetCacheFile); err != nil { - logrus.Debugf("%s: failed to create sandbox vhdx for %s: %s", title, id, err) - return err - } - return nil -} - -// Create creates the folder for the layer with the given id, and -// adds it to the layer chain. -func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error { - logrus.Debugf("lcowdriver: create: id %s parent: %s", id, parent) - - parentChain, err := d.getLayerChain(parent) - if err != nil { - return err - } - - var layerChain []string - if parent != "" { - if !d.Exists(parent) { - return fmt.Errorf("lcowdriver: cannot create layer folder with missing parent %s", parent) - } - layerChain = []string{d.dir(parent)} - } - layerChain = append(layerChain, parentChain...) - - layerPath := d.dir(id) - logrus.Debugf("lcowdriver: create: id %s: creating %s", id, layerPath) - // Standard mkdir here, not with SDDL as the dataroot was created with - // inheritance to just local system and administrators. - if err := os.MkdirAll(layerPath, 0700); err != nil { - return err - } - - if err := d.setLayerChain(id, layerChain); err != nil { - if err2 := os.RemoveAll(layerPath); err2 != nil { - logrus.Warnf("failed to remove layer %s: %s", layerPath, err2) - } - return err - } - logrus.Debugf("lcowdriver: create: id %s: success", id) - - return nil -} - -// Remove unmounts and removes the dir information. -func (d *Driver) Remove(id string) error { - logrus.Debugf("lcowdriver: remove: id %s", id) - tmpID := fmt.Sprintf("%s-removing", id) - tmpLayerPath := d.dir(tmpID) - layerPath := d.dir(id) - - logrus.Debugf("lcowdriver: remove: id %s: layerPath %s", id, layerPath) - - // Unmount all the layers - err := d.Put(id) - if err != nil { - logrus.Debugf("lcowdriver: remove id %s: failed to unmount: %s", id, err) - return err - } - - // for non-global case just kill the vm - if !d.globalMode { - if err := d.terminateServiceVM(id, fmt.Sprintf("Remove %s", id), true); err != nil { - return err - } - } - - if err := os.Rename(layerPath, tmpLayerPath); err != nil && !os.IsNotExist(err) { - return err - } - - if err := os.RemoveAll(tmpLayerPath); err != nil { - return err - } - - logrus.Debugf("lcowdriver: remove: id %s: layerPath %s succeeded", id, layerPath) - return nil -} - -// Get returns the rootfs path for the id. It is reference counted and -// effectively can be thought of as a "mount the layer into the utility -// vm if it isn't already". The contract from the caller of this is that -// all Gets and Puts are matched. It -should- be the case that on cleanup, -// nothing is mounted. -// -// For optimisation, we don't actually mount the filesystem (which in our -// case means [hot-]adding it to a service VM. But we track that and defer -// the actual adding to the point we need to access it. -func (d *Driver) Get(id, mountLabel string) (containerfs.ContainerFS, error) { - title := fmt.Sprintf("lcowdriver: get: %s", id) - logrus.Debugf(title) - - // Generate the mounts needed for the deferred operation. - disks, err := d.getAllMounts(id) - if err != nil { - logrus.Debugf("%s failed to get all layer details for %s: %s", title, d.dir(id), err) - return nil, fmt.Errorf("%s failed to get layer details for %s: %s", title, d.dir(id), err) - } - - logrus.Debugf("%s: got layer mounts: %+v", title, disks) - return &lcowfs{ - root: unionMountName(disks), - d: d, - mappedDisks: disks, - vmID: d.getVMID(id), - }, nil -} - -// Put does the reverse of get. If there are no more references to -// the layer, it unmounts it from the utility VM. -func (d *Driver) Put(id string) error { - title := fmt.Sprintf("lcowdriver: put: %s", id) - - // Get the service VM that we need to remove from - svm, err := d.serviceVms.get(d.getVMID(id)) - if err == errVMUnknown { - return nil - } else if err == errVMisTerminating { - return svm.getStopError() - } - - // Generate the mounts that Get() might have mounted - disks, err := d.getAllMounts(id) - if err != nil { - logrus.Debugf("%s failed to get all layer details for %s: %s", title, d.dir(id), err) - return fmt.Errorf("%s failed to get layer details for %s: %s", title, d.dir(id), err) - } - - // Now, we want to perform the unmounts, hot-remove and stop the service vm. - // We want to go though all the steps even if we have an error to clean up properly - err = svm.deleteUnionMount(unionMountName(disks), disks...) - if err != nil { - logrus.Debugf("%s failed to delete union mount %s: %s", title, id, err) - } - - err1 := svm.hotRemoveVHDs(disks...) - if err1 != nil { - logrus.Debugf("%s failed to hot remove vhds %s: %s", title, id, err) - if err == nil { - err = err1 - } - } - - err1 = d.terminateServiceVM(id, fmt.Sprintf("Put %s", id), false) - if err1 != nil { - logrus.Debugf("%s failed to terminate service vm %s: %s", title, id, err1) - if err == nil { - err = err1 - } - } - logrus.Debugf("Put succeeded on id %s", id) - return err -} - -// Cleanup ensures the information the driver stores is properly removed. -// We use this opportunity to cleanup any -removing folders which may be -// still left if the daemon was killed while it was removing a layer. -func (d *Driver) Cleanup() error { - title := "lcowdriver: cleanup" - - items, err := ioutil.ReadDir(d.dataRoot) - if err != nil { - if os.IsNotExist(err) { - return nil - } - return err - } - - // Note we don't return an error below - it's possible the files - // are locked. However, next time around after the daemon exits, - // we likely will be able to cleanup successfully. Instead we log - // warnings if there are errors. - for _, item := range items { - if item.IsDir() && strings.HasSuffix(item.Name(), "-removing") { - if err := os.RemoveAll(filepath.Join(d.dataRoot, item.Name())); err != nil { - logrus.Warnf("%s failed to cleanup %s: %s", title, item.Name(), err) - } else { - logrus.Infof("%s cleaned up %s", title, item.Name()) - } - } - } - - // Cleanup any service VMs we have running, along with their scratch spaces. - // We don't take the lock for this as it's taken in terminateServiceVm. - for k, v := range d.serviceVms.svms { - logrus.Debugf("%s svm entry: %s: %+v", title, k, v) - d.terminateServiceVM(k, "cleanup", true) - } - - return nil -} - -// Diff takes a layer (and it's parent layer which may be null, but -// is ignored by this implementation below) and returns a reader for -// a tarstream representing the layers contents. The id could be -// a read-only "layer.vhd" or a read-write "sandbox.vhdx". The semantics -// of this function dictate that the layer is already mounted. -// However, as we do lazy mounting as a performance optimisation, -// this will likely not be the case. -func (d *Driver) Diff(id, parent string) (io.ReadCloser, error) { - title := fmt.Sprintf("lcowdriver: diff: %s", id) - - // Get VHDX info - ld, err := getLayerDetails(d.dir(id)) - if err != nil { - logrus.Debugf("%s: failed to get vhdx information of %s: %s", title, d.dir(id), err) - return nil, err - } - - // Start the SVM with a mapped virtual disk. Note that if the SVM is - // already running and we are in global mode, this will be - // hot-added. - mvd := hcsshim.MappedVirtualDisk{ - HostPath: ld.filename, - ContainerPath: hostToGuest(ld.filename), - CreateInUtilityVM: true, - ReadOnly: true, - } - - logrus.Debugf("%s: starting service VM", title) - svm, err := d.startServiceVMIfNotRunning(id, []hcsshim.MappedVirtualDisk{mvd}, fmt.Sprintf("diff %s", id)) - if err != nil { - return nil, err - } - - logrus.Debugf("lcowdriver: diff: waiting for svm to finish booting") - err = svm.getStartError() - if err != nil { - d.terminateServiceVM(id, fmt.Sprintf("diff %s", id), false) - return nil, fmt.Errorf("lcowdriver: diff: svm failed to boot: %s", err) - } - - // Obtain the tar stream for it - // The actual container path will have be remapped to a short name, so use that. - actualContainerPath := svm.getShortContainerPath(&mvd) - if actualContainerPath == "" { - return nil, fmt.Errorf("failed to get short container path for %+v in SVM %s", mvd, svm.config.Name) - } - logrus.Debugf("%s: %s %s, size %d, ReadOnly %t", title, ld.filename, actualContainerPath, ld.size, ld.isSandbox) - tarReadCloser, err := svm.config.VhdToTar(mvd.HostPath, actualContainerPath, ld.isSandbox, ld.size) - if err != nil { - svm.hotRemoveVHDs(mvd) - d.terminateServiceVM(id, fmt.Sprintf("diff %s", id), false) - return nil, fmt.Errorf("%s failed to export layer to tar stream for id: %s, parent: %s : %s", title, id, parent, err) - } - - logrus.Debugf("%s id %s parent %s completed successfully", title, id, parent) - - // In safe/non-global mode, we can't tear down the service VM until things have been read. - return ioutils.NewReadCloserWrapper(tarReadCloser, func() error { - tarReadCloser.Close() - svm.hotRemoveVHDs(mvd) - d.terminateServiceVM(id, fmt.Sprintf("diff %s", id), false) - return nil - }), nil -} - -// ApplyDiff extracts the changeset from the given diff into the -// layer with the specified id and parent, returning the size of the -// new layer in bytes. The layer should not be mounted when calling -// this function. Another way of describing this is that ApplyDiff writes -// to a new layer (a VHD in LCOW) the contents of a tarstream it's given. -func (d *Driver) ApplyDiff(id, parent string, diff io.Reader) (int64, error) { - logrus.Debugf("lcowdriver: applydiff: id %s", id) - - // Log failures here as it's undiagnosable sometimes, due to a possible panic. - // See https://github.com/moby/moby/issues/37955 for more information. - - dest := filepath.Join(d.dataRoot, id, layerFilename) - if !noreexec { - cmd := reexec.Command([]string{"docker-lcow-tar2ext4", dest}...) - stdout := bytes.NewBuffer(nil) - stderr := bytes.NewBuffer(nil) - cmd.Stdin = diff - cmd.Stdout = stdout - cmd.Stderr = stderr - - if err := cmd.Start(); err != nil { - logrus.Warnf("lcowdriver: applydiff: id %s failed to start re-exec: %s", id, err) - return 0, err - } - - if err := cmd.Wait(); err != nil { - logrus.Warnf("lcowdriver: applydiff: id %s failed %s", id, err) - return 0, fmt.Errorf("re-exec error: %v: stderr: %s", err, stderr) - } - - size, err := strconv.ParseInt(stdout.String(), 10, 64) - if err != nil { - logrus.Warnf("lcowdriver: applydiff: id %s failed to parse output %s", id, err) - return 0, fmt.Errorf("re-exec error: %v: stdout: %s", err, stdout) - } - return applySID(id, size, dest) - - } - // The inline case - size, err := tar2ext4Actual(dest, diff) - if err != nil { - logrus.Warnf("lcowdriver: applydiff: id %s failed %s", id, err) - } - return applySID(id, size, dest) -} - -// applySID adds the VM Group SID read-only access. -func applySID(id string, size int64, dest string) (int64, error) { - if err := security.GrantVmGroupAccess(dest); err != nil { - logrus.Warnf("lcowdriver: applySIDs: id %s failed %s", id, err) - return 0, err - } - return size, nil -} - -// tar2ext4Reexec is the re-exec entry point for writing a layer from a tar file -func tar2ext4Reexec() { - size, err := tar2ext4Actual(os.Args[1], os.Stdin) - if err != nil { - fmt.Fprint(os.Stderr, err) - os.Exit(1) - } - fmt.Fprint(os.Stdout, size) -} - -// tar2ext4Actual is the implementation of tar2ext to write a layer from a tar file. -// It can be called through re-exec (default), or inline for debugging. -func tar2ext4Actual(dest string, diff io.Reader) (int64, error) { - // maxDiskSize is not relating to the sandbox size - this is the - // maximum possible size a layer VHD generated can be from an EXT4 - // layout perspective. - const maxDiskSize = 128 * 1024 * 1024 * 1024 // 128GB - out, err := os.Create(dest) - if err != nil { - return 0, err - } - defer out.Close() - if err := tar2ext4.Convert( - diff, - out, - tar2ext4.AppendVhdFooter, - tar2ext4.ConvertWhiteout, - tar2ext4.MaximumDiskSize(maxDiskSize)); err != nil { - return 0, err - } - fi, err := os.Stat(dest) - if err != nil { - return 0, err - } - return fi.Size(), nil -} - -// Changes produces a list of changes between the specified layer -// and its parent layer. If parent is "", then all changes will be ADD changes. -// The layer should not be mounted when calling this function. -func (d *Driver) Changes(id, parent string) ([]archive.Change, error) { - logrus.Debugf("lcowdriver: changes: id %s parent %s", id, parent) - // TODO @gupta-ak. Needs implementation with assistance from service VM - return nil, nil -} - -// DiffSize calculates the changes between the specified layer -// and its parent and returns the size in bytes of the changes -// relative to its base filesystem directory. -func (d *Driver) DiffSize(id, parent string) (size int64, err error) { - logrus.Debugf("lcowdriver: diffsize: id %s", id) - // TODO @gupta-ak. Needs implementation with assistance from service VM - return 0, nil -} - -// GetMetadata returns custom driver information. -func (d *Driver) GetMetadata(id string) (map[string]string, error) { - logrus.Debugf("lcowdriver: getmetadata: id %s", id) - m := make(map[string]string) - m["dir"] = d.dir(id) - return m, nil -} - -// GetLayerPath gets the layer path on host (path to VHD/VHDX) -func (d *Driver) GetLayerPath(id string) (string, error) { - return d.dir(id), nil -} - -// dir returns the absolute path to the layer. -func (d *Driver) dir(id string) string { - return filepath.Join(d.dataRoot, filepath.Base(id)) -} - -// getLayerChain returns the layer chain information. -func (d *Driver) getLayerChain(id string) ([]string, error) { - jPath := filepath.Join(d.dir(id), "layerchain.json") - logrus.Debugf("lcowdriver: getlayerchain: id %s json %s", id, jPath) - content, err := ioutil.ReadFile(jPath) - if os.IsNotExist(err) { - return nil, nil - } else if err != nil { - return nil, fmt.Errorf("lcowdriver: getlayerchain: %s unable to read layerchain file %s: %s", id, jPath, err) - } - - var layerChain []string - err = json.Unmarshal(content, &layerChain) - if err != nil { - return nil, fmt.Errorf("lcowdriver: getlayerchain: %s failed to unmarshall layerchain file %s: %s", id, jPath, err) - } - return layerChain, nil -} - -// setLayerChain stores the layer chain information on disk. -func (d *Driver) setLayerChain(id string, chain []string) error { - content, err := json.Marshal(&chain) - if err != nil { - return fmt.Errorf("lcowdriver: setlayerchain: %s failed to marshall layerchain json: %s", id, err) - } - - jPath := filepath.Join(d.dir(id), "layerchain.json") - logrus.Debugf("lcowdriver: setlayerchain: id %s json %s", id, jPath) - err = ioutil.WriteFile(jPath, content, 0600) - if err != nil { - return fmt.Errorf("lcowdriver: setlayerchain: %s failed to write layerchain file: %s", id, err) - } - return nil -} - -// getLayerDetails is a utility for getting a file name, size and indication of -// sandbox for a VHD(x) in a folder. A read-only layer will be layer.vhd. A -// read-write layer will be sandbox.vhdx. -func getLayerDetails(folder string) (*layerDetails, error) { - var fileInfo os.FileInfo - ld := &layerDetails{ - isSandbox: false, - filename: filepath.Join(folder, layerFilename), - } - - fileInfo, err := os.Stat(ld.filename) - if err != nil { - ld.filename = filepath.Join(folder, sandboxFilename) - if fileInfo, err = os.Stat(ld.filename); err != nil { - return nil, fmt.Errorf("failed to locate layer or sandbox in %s", folder) - } - ld.isSandbox = true - } - ld.size = fileInfo.Size() - - return ld, nil -} - -func (d *Driver) getAllMounts(id string) ([]hcsshim.MappedVirtualDisk, error) { - layerChain, err := d.getLayerChain(id) - if err != nil { - return nil, err - } - layerChain = append([]string{d.dir(id)}, layerChain...) - - logrus.Debugf("getting all layers: %v", layerChain) - disks := make([]hcsshim.MappedVirtualDisk, len(layerChain), len(layerChain)) - for i := range layerChain { - ld, err := getLayerDetails(layerChain[i]) - if err != nil { - logrus.Debugf("Failed to get LayerVhdDetails from %s: %s", layerChain[i], err) - return nil, err - } - disks[i].HostPath = ld.filename - disks[i].ContainerPath = hostToGuest(ld.filename) - disks[i].CreateInUtilityVM = true - disks[i].ReadOnly = !ld.isSandbox - } - return disks, nil -} - -func hostToGuest(hostpath string) string { - // This is the "long" container path. At the point of which we are - // calculating this, we don't know which service VM we're going to be - // using, so we can't translate this to a short path yet, instead - // deferring until the point of which it's added to an SVM. We don't - // use long container paths in SVMs for SCSI disks, otherwise it can cause - // command line operations that we invoke to fail due to being over ~4200 - // characters when there are ~47 layers involved. An example of this is - // the mount call to create the overlay across multiple SCSI-attached disks. - // It doesn't affect VPMem attached layers during container creation as - // these get mapped by openGCS to /tmp/N/M where N is a container instance - // number, and M is a layer number. - return fmt.Sprintf("/tmp/%s", filepath.Base(filepath.Dir(hostpath))) -} - -func unionMountName(disks []hcsshim.MappedVirtualDisk) string { - return fmt.Sprintf("%s-mount", disks[0].ContainerPath) -} - -type nopCloser struct { - io.Reader -} - -func (nopCloser) Close() error { - return nil -} - -type fileGetCloserFromSVM struct { - id string - svm *serviceVM - mvd *hcsshim.MappedVirtualDisk - d *Driver -} - -func (fgc *fileGetCloserFromSVM) Close() error { - if fgc.svm != nil { - if fgc.mvd != nil { - if err := fgc.svm.hotRemoveVHDs(*fgc.mvd); err != nil { - // We just log this as we're going to tear down the SVM imminently unless in global mode - logrus.Errorf("failed to remove mvd %s: %s", fgc.mvd.ContainerPath, err) - } - } - } - if fgc.d != nil && fgc.svm != nil && fgc.id != "" { - if err := fgc.d.terminateServiceVM(fgc.id, fmt.Sprintf("diffgetter %s", fgc.id), false); err != nil { - return err - } - } - return nil -} - -func (fgc *fileGetCloserFromSVM) Get(filename string) (io.ReadCloser, error) { - errOut := &bytes.Buffer{} - outOut := &bytes.Buffer{} - // Must map to the actual "short" container path where the SCSI disk was mounted - actualContainerPath := fgc.svm.getShortContainerPath(fgc.mvd) - if actualContainerPath == "" { - return nil, fmt.Errorf("inconsistency detected: couldn't get short container path for %+v in utility VM %s", fgc.mvd, fgc.svm.config.Name) - } - file := path.Join(actualContainerPath, filename) - - // Ugly fix for MSFT internal bug VSO#19696554 - // If a file name contains a space, pushing an image fails. - // Using solution from https://groups.google.com/forum/#!topic/Golang-Nuts/DpldsmrhPio to escape for shell execution - file = "'" + strings.Join(strings.Split(file, "'"), `'"'"'`) + "'" - if err := fgc.svm.runProcess(fmt.Sprintf("cat %s", file), nil, outOut, errOut); err != nil { - logrus.Debugf("cat %s failed: %s", file, errOut.String()) - return nil, err - } - return nopCloser{bytes.NewReader(outOut.Bytes())}, nil -} - -// DiffGetter returns a FileGetCloser that can read files from the directory that -// contains files for the layer differences. Used for direct access for tar-split. -func (d *Driver) DiffGetter(id string) (graphdriver.FileGetCloser, error) { - title := fmt.Sprintf("lcowdriver: diffgetter: %s", id) - logrus.Debugf(title) - - ld, err := getLayerDetails(d.dir(id)) - if err != nil { - logrus.Debugf("%s: failed to get vhdx information of %s: %s", title, d.dir(id), err) - return nil, err - } - - // Start the SVM with a mapped virtual disk. Note that if the SVM is - // already running and we are in global mode, this will be hot-added. - mvd := hcsshim.MappedVirtualDisk{ - HostPath: ld.filename, - ContainerPath: hostToGuest(ld.filename), - CreateInUtilityVM: true, - ReadOnly: true, - } - - logrus.Debugf("%s: starting service VM", title) - svm, err := d.startServiceVMIfNotRunning(id, []hcsshim.MappedVirtualDisk{mvd}, fmt.Sprintf("diffgetter %s", id)) - if err != nil { - return nil, err - } - - logrus.Debugf("%s: waiting for svm to finish booting", title) - err = svm.getStartError() - if err != nil { - d.terminateServiceVM(id, fmt.Sprintf("diff %s", id), false) - return nil, fmt.Errorf("%s: svm failed to boot: %s", title, err) - } - - return &fileGetCloserFromSVM{ - id: id, - svm: svm, - mvd: &mvd, - d: d}, nil -} diff --git a/daemon/graphdriver/lcow/lcow_svm.go b/daemon/graphdriver/lcow/lcow_svm.go deleted file mode 100644 index a70e1b2486..0000000000 --- a/daemon/graphdriver/lcow/lcow_svm.go +++ /dev/null @@ -1,421 +0,0 @@ -// +build windows - -package lcow // import "github.com/docker/docker/daemon/graphdriver/lcow" - -import ( - "bytes" - "fmt" - "io" - "strings" - "sync" - "time" - - "github.com/Microsoft/hcsshim" - "github.com/Microsoft/opengcs/client" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -// Code for all the service VM management for the LCOW graphdriver - -var errVMisTerminating = errors.New("service VM is shutting down") -var errVMUnknown = errors.New("service vm id is unknown") -var errVMStillHasReference = errors.New("Attemping to delete a VM that is still being used") - -// serviceVMMap is the struct representing the id -> service VM mapping. -type serviceVMMap struct { - sync.Mutex - svms map[string]*serviceVMMapItem -} - -// serviceVMMapItem is our internal structure representing an item in our -// map of service VMs we are maintaining. -type serviceVMMapItem struct { - svm *serviceVM // actual service vm object - refCount int // refcount for VM -} - -// attachedVHD is for reference counting SCSI disks attached to a service VM, -// and for a counter used to generate a short path name for the container path. -type attachedVHD struct { - refCount int - attachCounter uint64 -} - -type serviceVM struct { - sync.Mutex // Serialises operations being performed in this service VM. - scratchAttached bool // Has a scratch been attached? - config *client.Config // Represents the service VM item. - - // Indicates that the vm is started - startStatus chan interface{} - startError error - - // Indicates that the vm is stopped - stopStatus chan interface{} - stopError error - - attachCounter uint64 // Increasing counter for each add - attachedVHDs map[string]*attachedVHD // Map ref counting all the VHDS we've hot-added/hot-removed. - unionMounts map[string]int // Map ref counting all the union filesystems we mounted. -} - -// add will add an id to the service vm map. There are three cases: -// - entry doesn't exist: -// - add id to map and return a new vm that the caller can manually configure+start -// - entry does exist -// - return vm in map and increment ref count -// - entry does exist but the ref count is 0 -// - return the svm and errVMisTerminating. Caller can call svm.getStopError() to wait for stop -func (svmMap *serviceVMMap) add(id string) (svm *serviceVM, alreadyExists bool, err error) { - svmMap.Lock() - defer svmMap.Unlock() - if svm, ok := svmMap.svms[id]; ok { - if svm.refCount == 0 { - return svm.svm, true, errVMisTerminating - } - svm.refCount++ - return svm.svm, true, nil - } - - // Doesn't exist, so create an empty svm to put into map and return - newSVM := &serviceVM{ - startStatus: make(chan interface{}), - stopStatus: make(chan interface{}), - attachedVHDs: make(map[string]*attachedVHD), - unionMounts: make(map[string]int), - config: &client.Config{}, - } - svmMap.svms[id] = &serviceVMMapItem{ - svm: newSVM, - refCount: 1, - } - return newSVM, false, nil -} - -// get will get the service vm from the map. There are three cases: -// - entry doesn't exist: -// - return errVMUnknown -// - entry does exist -// - return vm with no error -// - entry does exist but the ref count is 0 -// - return the svm and errVMisTerminating. Caller can call svm.getStopError() to wait for stop -func (svmMap *serviceVMMap) get(id string) (*serviceVM, error) { - svmMap.Lock() - defer svmMap.Unlock() - svm, ok := svmMap.svms[id] - if !ok { - return nil, errVMUnknown - } - if svm.refCount == 0 { - return svm.svm, errVMisTerminating - } - return svm.svm, nil -} - -// decrementRefCount decrements the ref count of the given ID from the map. There are four cases: -// - entry doesn't exist: -// - return errVMUnknown -// - entry does exist but the ref count is 0 -// - return the svm and errVMisTerminating. Caller can call svm.getStopError() to wait for stop -// - entry does exist but ref count is 1 -// - return vm and set lastRef to true. The caller can then stop the vm, delete the id from this map -// - and execute svm.signalStopFinished to signal the threads that the svm has been terminated. -// - entry does exist and ref count > 1 -// - just reduce ref count and return svm -func (svmMap *serviceVMMap) decrementRefCount(id string) (_ *serviceVM, lastRef bool, _ error) { - svmMap.Lock() - defer svmMap.Unlock() - - svm, ok := svmMap.svms[id] - if !ok { - return nil, false, errVMUnknown - } - if svm.refCount == 0 { - return svm.svm, false, errVMisTerminating - } - svm.refCount-- - return svm.svm, svm.refCount == 0, nil -} - -// setRefCountZero works the same way as decrementRefCount, but sets ref count to 0 instead of decrementing it. -func (svmMap *serviceVMMap) setRefCountZero(id string) (*serviceVM, error) { - svmMap.Lock() - defer svmMap.Unlock() - - svm, ok := svmMap.svms[id] - if !ok { - return nil, errVMUnknown - } - if svm.refCount == 0 { - return svm.svm, errVMisTerminating - } - svm.refCount = 0 - return svm.svm, nil -} - -// deleteID deletes the given ID from the map. If the refcount is not 0 or the -// VM does not exist, then this function returns an error. -func (svmMap *serviceVMMap) deleteID(id string) error { - svmMap.Lock() - defer svmMap.Unlock() - svm, ok := svmMap.svms[id] - if !ok { - return errVMUnknown - } - if svm.refCount != 0 { - return errVMStillHasReference - } - delete(svmMap.svms, id) - return nil -} - -func (svm *serviceVM) signalStartFinished(err error) { - svm.Lock() - svm.startError = err - svm.Unlock() - close(svm.startStatus) -} - -func (svm *serviceVM) getStartError() error { - <-svm.startStatus - svm.Lock() - defer svm.Unlock() - return svm.startError -} - -func (svm *serviceVM) signalStopFinished(err error) { - svm.Lock() - svm.stopError = err - svm.Unlock() - close(svm.stopStatus) -} - -func (svm *serviceVM) getStopError() error { - <-svm.stopStatus - svm.Lock() - defer svm.Unlock() - return svm.stopError -} - -// hotAddVHDs waits for the service vm to start and then attaches the vhds. -func (svm *serviceVM) hotAddVHDs(mvds ...hcsshim.MappedVirtualDisk) error { - if err := svm.getStartError(); err != nil { - return err - } - return svm.hotAddVHDsAtStart(mvds...) -} - -// hotAddVHDsAtStart works the same way as hotAddVHDs but does not wait for the VM to start. -func (svm *serviceVM) hotAddVHDsAtStart(mvds ...hcsshim.MappedVirtualDisk) error { - svm.Lock() - defer svm.Unlock() - for i, mvd := range mvds { - if _, ok := svm.attachedVHDs[mvd.HostPath]; ok { - svm.attachedVHDs[mvd.HostPath].refCount++ - logrus.Debugf("lcowdriver: UVM %s: %s already present, refCount now %d", svm.config.Name, mvd.HostPath, svm.attachedVHDs[mvd.HostPath].refCount) - continue - } - - svm.attachCounter++ - shortContainerPath := remapLongToShortContainerPath(mvd.ContainerPath, svm.attachCounter, svm.config.Name) - if err := svm.config.HotAddVhd(mvd.HostPath, shortContainerPath, mvd.ReadOnly, !mvd.AttachOnly); err != nil { - svm.hotRemoveVHDsNoLock(mvds[:i]...) - return err - } - svm.attachedVHDs[mvd.HostPath] = &attachedVHD{refCount: 1, attachCounter: svm.attachCounter} - } - return nil -} - -// hotRemoveVHDs waits for the service vm to start and then removes the vhds. -// The service VM must not be locked when calling this function. -func (svm *serviceVM) hotRemoveVHDs(mvds ...hcsshim.MappedVirtualDisk) error { - if err := svm.getStartError(); err != nil { - return err - } - svm.Lock() - defer svm.Unlock() - return svm.hotRemoveVHDsNoLock(mvds...) -} - -// hotRemoveVHDsNoLock removes VHDs from a service VM. When calling this function, -// the contract is the service VM lock must be held. -func (svm *serviceVM) hotRemoveVHDsNoLock(mvds ...hcsshim.MappedVirtualDisk) error { - var retErr error - for _, mvd := range mvds { - if _, ok := svm.attachedVHDs[mvd.HostPath]; !ok { - // We continue instead of returning an error if we try to hot remove a non-existent VHD. - // This is because one of the callers of the function is graphdriver.Put(). Since graphdriver.Get() - // defers the VM start to the first operation, it's possible that nothing have been hot-added - // when Put() is called. To avoid Put returning an error in that case, we simply continue if we - // don't find the vhd attached. - logrus.Debugf("lcowdriver: UVM %s: %s is not attached, not doing anything", svm.config.Name, mvd.HostPath) - continue - } - - if svm.attachedVHDs[mvd.HostPath].refCount > 1 { - svm.attachedVHDs[mvd.HostPath].refCount-- - logrus.Debugf("lcowdriver: UVM %s: %s refCount dropped to %d. not removing from UVM", svm.config.Name, mvd.HostPath, svm.attachedVHDs[mvd.HostPath].refCount) - continue - } - - // last reference to VHD, so remove from VM and map - if err := svm.config.HotRemoveVhd(mvd.HostPath); err == nil { - delete(svm.attachedVHDs, mvd.HostPath) - } else { - // Take note of the error, but still continue to remove the other VHDs - logrus.Warnf("Failed to hot remove %s: %s", mvd.HostPath, err) - if retErr == nil { - retErr = err - } - } - } - return retErr -} - -func (svm *serviceVM) createExt4VHDX(destFile string, sizeGB uint32, cacheFile string) error { - if err := svm.getStartError(); err != nil { - return err - } - - svm.Lock() - defer svm.Unlock() - return svm.config.CreateExt4Vhdx(destFile, sizeGB, cacheFile) -} - -// getShortContainerPath looks up where a SCSI disk was actually mounted -// in a service VM when we remapped a long path name to a short name. -func (svm *serviceVM) getShortContainerPath(mvd *hcsshim.MappedVirtualDisk) string { - if mvd.ContainerPath == "" { - return "" - } - avhd, ok := svm.attachedVHDs[mvd.HostPath] - if !ok { - return "" - } - return fmt.Sprintf("/tmp/d%d", avhd.attachCounter) -} - -func (svm *serviceVM) createUnionMount(mountName string, mvds ...hcsshim.MappedVirtualDisk) (err error) { - if len(mvds) == 0 { - return fmt.Errorf("createUnionMount: error must have at least 1 layer") - } - - if err = svm.getStartError(); err != nil { - return err - } - - svm.Lock() - defer svm.Unlock() - if _, ok := svm.unionMounts[mountName]; ok { - svm.unionMounts[mountName]++ - return nil - } - - var lowerLayers []string - if mvds[0].ReadOnly { - lowerLayers = append(lowerLayers, svm.getShortContainerPath(&mvds[0])) - } - - for i := 1; i < len(mvds); i++ { - lowerLayers = append(lowerLayers, svm.getShortContainerPath(&mvds[i])) - } - - logrus.Debugf("Doing the overlay mount with union directory=%s", mountName) - errOut := &bytes.Buffer{} - if err = svm.runProcess(fmt.Sprintf("mkdir -p %s", mountName), nil, nil, errOut); err != nil { - return errors.Wrapf(err, "mkdir -p %s failed (%s)", mountName, errOut.String()) - } - - var cmd string - if len(mvds) == 1 { - // `FROM SCRATCH` case and the only layer. No overlay required. - cmd = fmt.Sprintf("mount %s %s", svm.getShortContainerPath(&mvds[0]), mountName) - } else if mvds[0].ReadOnly { - // Readonly overlay - cmd = fmt.Sprintf("mount -t overlay overlay -olowerdir=%s %s", - strings.Join(lowerLayers, ","), - mountName) - } else { - upper := fmt.Sprintf("%s/upper", svm.getShortContainerPath(&mvds[0])) - work := fmt.Sprintf("%s/work", svm.getShortContainerPath(&mvds[0])) - - errOut := &bytes.Buffer{} - if err = svm.runProcess(fmt.Sprintf("mkdir -p %s %s", upper, work), nil, nil, errOut); err != nil { - return errors.Wrapf(err, "mkdir -p %s failed (%s)", mountName, errOut.String()) - } - - cmd = fmt.Sprintf("mount -t overlay overlay -olowerdir=%s,upperdir=%s,workdir=%s %s", - strings.Join(lowerLayers, ":"), - upper, - work, - mountName) - } - - logrus.Debugf("createUnionMount: Executing mount=%s", cmd) - errOut = &bytes.Buffer{} - if err = svm.runProcess(cmd, nil, nil, errOut); err != nil { - return errors.Wrapf(err, "%s failed (%s)", cmd, errOut.String()) - } - - svm.unionMounts[mountName] = 1 - return nil -} - -func (svm *serviceVM) deleteUnionMount(mountName string, disks ...hcsshim.MappedVirtualDisk) error { - if err := svm.getStartError(); err != nil { - return err - } - - svm.Lock() - defer svm.Unlock() - if _, ok := svm.unionMounts[mountName]; !ok { - return nil - } - - if svm.unionMounts[mountName] > 1 { - svm.unionMounts[mountName]-- - return nil - } - - logrus.Debugf("Removing union mount %s", mountName) - if err := svm.runProcess(fmt.Sprintf("umount %s", mountName), nil, nil, nil); err != nil { - return err - } - - delete(svm.unionMounts, mountName) - return nil -} - -func (svm *serviceVM) runProcess(command string, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { - var process hcsshim.Process - var err error - errOut := &bytes.Buffer{} - - if stderr != nil { - process, err = svm.config.RunProcess(command, stdin, stdout, stderr) - } else { - process, err = svm.config.RunProcess(command, stdin, stdout, errOut) - } - if err != nil { - return err - } - defer process.Close() - - process.WaitTimeout(time.Duration(int(time.Second) * svm.config.UvmTimeoutSeconds)) - exitCode, err := process.ExitCode() - if err != nil { - return err - } - - if exitCode != 0 { - // If the caller isn't explicitly capturing stderr output, then capture it here instead. - e := fmt.Sprintf("svm.runProcess: command %s failed with exit code %d", command, exitCode) - if stderr == nil { - e = fmt.Sprintf("%s. (%s)", e, errOut.String()) - } - return fmt.Errorf(e) - } - return nil -} diff --git a/daemon/graphdriver/lcow/remotefs.go b/daemon/graphdriver/lcow/remotefs.go deleted file mode 100644 index 29f15fd24c..0000000000 --- a/daemon/graphdriver/lcow/remotefs.go +++ /dev/null @@ -1,139 +0,0 @@ -// +build windows - -package lcow // import "github.com/docker/docker/daemon/graphdriver/lcow" - -import ( - "bytes" - "fmt" - "io" - "runtime" - "strings" - "sync" - - "github.com/Microsoft/hcsshim" - "github.com/Microsoft/opengcs/service/gcsutils/remotefs" - "github.com/docker/docker/pkg/archive" - "github.com/docker/docker/pkg/containerfs" - "github.com/sirupsen/logrus" -) - -type lcowfs struct { - root string - d *Driver - mappedDisks []hcsshim.MappedVirtualDisk - vmID string - currentSVM *serviceVM - sync.Mutex -} - -var _ containerfs.ContainerFS = &lcowfs{} - -// ErrNotSupported is an error for unsupported operations in the remotefs -var ErrNotSupported = fmt.Errorf("not supported") - -// Functions to implement the ContainerFS interface -func (l *lcowfs) Path() string { - return l.root -} - -func (l *lcowfs) ResolveScopedPath(path string, rawPath bool) (string, error) { - logrus.Debugf("remotefs.resolvescopedpath inputs: %s %s ", path, l.root) - - arg1 := l.Join(l.root, path) - if !rawPath { - // The l.Join("/", path) will make path an absolute path and then clean it - // so if path = ../../X, it will become /X. - arg1 = l.Join(l.root, l.Join("/", path)) - } - arg2 := l.root - - output := &bytes.Buffer{} - if err := l.runRemoteFSProcess(nil, output, remotefs.ResolvePathCmd, arg1, arg2); err != nil { - return "", err - } - - logrus.Debugf("remotefs.resolvescopedpath success. Output: %s\n", output.String()) - return output.String(), nil -} - -func (l *lcowfs) OS() string { - return "linux" -} - -func (l *lcowfs) Architecture() string { - return runtime.GOARCH -} - -// Other functions that are used by docker like the daemon Archiver/Extractor -func (l *lcowfs) ExtractArchive(src io.Reader, dst string, opts *archive.TarOptions) error { - logrus.Debugf("remotefs.ExtractArchve inputs: %s %+v", dst, opts) - - tarBuf := &bytes.Buffer{} - if err := remotefs.WriteTarOptions(tarBuf, opts); err != nil { - return fmt.Errorf("failed to marshall tar opts: %s", err) - } - - input := io.MultiReader(tarBuf, src) - if err := l.runRemoteFSProcess(input, nil, remotefs.ExtractArchiveCmd, dst); err != nil { - return fmt.Errorf("failed to extract archive to %s: %s", dst, err) - } - return nil -} - -func (l *lcowfs) ArchivePath(src string, opts *archive.TarOptions) (io.ReadCloser, error) { - logrus.Debugf("remotefs.ArchivePath: %s %+v", src, opts) - - tarBuf := &bytes.Buffer{} - if err := remotefs.WriteTarOptions(tarBuf, opts); err != nil { - return nil, fmt.Errorf("failed to marshall tar opts: %s", err) - } - - r, w := io.Pipe() - go func() { - defer w.Close() - if err := l.runRemoteFSProcess(tarBuf, w, remotefs.ArchivePathCmd, src); err != nil { - logrus.Debugf("REMOTEFS: Failed to extract archive: %s %+v %s", src, opts, err) - } - }() - return r, nil -} - -// Helper functions -func (l *lcowfs) startVM() error { - l.Lock() - defer l.Unlock() - if l.currentSVM != nil { - return nil - } - - svm, err := l.d.startServiceVMIfNotRunning(l.vmID, l.mappedDisks, fmt.Sprintf("lcowfs.startVM")) - if err != nil { - return err - } - - if err = svm.createUnionMount(l.root, l.mappedDisks...); err != nil { - return err - } - l.currentSVM = svm - return nil -} - -func (l *lcowfs) runRemoteFSProcess(stdin io.Reader, stdout io.Writer, args ...string) error { - if err := l.startVM(); err != nil { - return err - } - - // Append remotefs prefix and setup as a command line string - cmd := fmt.Sprintf("%s %s", remotefs.RemotefsCmd, strings.Join(args, " ")) - stderr := &bytes.Buffer{} - if err := l.currentSVM.runProcess(cmd, stdin, stdout, stderr); err != nil { - return err - } - - eerr, err := remotefs.ReadError(stderr) - if eerr != nil { - // Process returned an error so return that. - return remotefs.ExportedToError(eerr) - } - return err -} diff --git a/daemon/graphdriver/lcow/remotefs_file.go b/daemon/graphdriver/lcow/remotefs_file.go deleted file mode 100644 index 1f00bfff46..0000000000 --- a/daemon/graphdriver/lcow/remotefs_file.go +++ /dev/null @@ -1,211 +0,0 @@ -// +build windows - -package lcow // import "github.com/docker/docker/daemon/graphdriver/lcow" - -import ( - "bytes" - "encoding/binary" - "encoding/json" - "fmt" - "io" - "os" - "strconv" - - "github.com/Microsoft/hcsshim" - "github.com/Microsoft/opengcs/service/gcsutils/remotefs" - "github.com/containerd/continuity/driver" -) - -type lcowfile struct { - process hcsshim.Process - stdin io.WriteCloser - stdout io.ReadCloser - stderr io.ReadCloser - fs *lcowfs - guestPath string -} - -func (l *lcowfs) Open(path string) (driver.File, error) { - return l.OpenFile(path, os.O_RDONLY, 0) -} - -func (l *lcowfs) OpenFile(path string, flag int, perm os.FileMode) (_ driver.File, err error) { - flagStr := strconv.FormatInt(int64(flag), 10) - permStr := strconv.FormatUint(uint64(perm), 8) - - commandLine := fmt.Sprintf("%s %s %s %s %s", remotefs.RemotefsCmd, remotefs.OpenFileCmd, path, flagStr, permStr) - env := make(map[string]string) - env["PATH"] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:" - processConfig := &hcsshim.ProcessConfig{ - EmulateConsole: false, - CreateStdInPipe: true, - CreateStdOutPipe: true, - CreateStdErrPipe: true, - CreateInUtilityVm: true, - WorkingDirectory: "/bin", - Environment: env, - CommandLine: commandLine, - } - - process, err := l.currentSVM.config.Uvm.CreateProcess(processConfig) - if err != nil { - return nil, fmt.Errorf("failed to open file %s: %s", path, err) - } - - stdin, stdout, stderr, err := process.Stdio() - if err != nil { - process.Kill() - process.Close() - return nil, fmt.Errorf("failed to open file pipes %s: %s", path, err) - } - - lf := &lcowfile{ - process: process, - stdin: stdin, - stdout: stdout, - stderr: stderr, - fs: l, - guestPath: path, - } - - if _, err := lf.getResponse(); err != nil { - return nil, fmt.Errorf("failed to open file %s: %s", path, err) - } - return lf, nil -} - -func (l *lcowfile) Read(b []byte) (int, error) { - hdr := &remotefs.FileHeader{ - Cmd: remotefs.Read, - Size: uint64(len(b)), - } - - if err := remotefs.WriteFileHeader(l.stdin, hdr, nil); err != nil { - return 0, err - } - - buf, err := l.getResponse() - if err != nil { - return 0, err - } - - n := copy(b, buf) - return n, nil -} - -func (l *lcowfile) Write(b []byte) (int, error) { - hdr := &remotefs.FileHeader{ - Cmd: remotefs.Write, - Size: uint64(len(b)), - } - - if err := remotefs.WriteFileHeader(l.stdin, hdr, b); err != nil { - return 0, err - } - - _, err := l.getResponse() - if err != nil { - return 0, err - } - - return len(b), nil -} - -func (l *lcowfile) Seek(offset int64, whence int) (int64, error) { - seekHdr := &remotefs.SeekHeader{ - Offset: offset, - Whence: int32(whence), - } - - buf := &bytes.Buffer{} - if err := binary.Write(buf, binary.BigEndian, seekHdr); err != nil { - return 0, err - } - - hdr := &remotefs.FileHeader{ - Cmd: remotefs.Write, - Size: uint64(buf.Len()), - } - if err := remotefs.WriteFileHeader(l.stdin, hdr, buf.Bytes()); err != nil { - return 0, err - } - - resBuf, err := l.getResponse() - if err != nil { - return 0, err - } - - var res int64 - if err := binary.Read(bytes.NewBuffer(resBuf), binary.BigEndian, &res); err != nil { - return 0, err - } - return res, nil -} - -func (l *lcowfile) Close() error { - hdr := &remotefs.FileHeader{ - Cmd: remotefs.Close, - Size: 0, - } - - if err := remotefs.WriteFileHeader(l.stdin, hdr, nil); err != nil { - return err - } - - _, err := l.getResponse() - return err -} - -func (l *lcowfile) Readdir(n int) ([]os.FileInfo, error) { - nStr := strconv.FormatInt(int64(n), 10) - - // Unlike the other File functions, this one can just be run without maintaining state, - // so just do the normal runRemoteFSProcess way. - buf := &bytes.Buffer{} - if err := l.fs.runRemoteFSProcess(nil, buf, remotefs.ReadDirCmd, l.guestPath, nStr); err != nil { - return nil, err - } - - var info []remotefs.FileInfo - if err := json.Unmarshal(buf.Bytes(), &info); err != nil { - return nil, err - } - - osInfo := make([]os.FileInfo, len(info)) - for i := range info { - osInfo[i] = &info[i] - } - return osInfo, nil -} - -func (l *lcowfile) getResponse() ([]byte, error) { - hdr, err := remotefs.ReadFileHeader(l.stdout) - if err != nil { - return nil, err - } - - if hdr.Cmd != remotefs.CmdOK { - // Something went wrong during the openfile in the server. - // Parse stderr and return that as an error - eerr, err := remotefs.ReadError(l.stderr) - if eerr != nil { - return nil, remotefs.ExportedToError(eerr) - } - - // Maybe the parsing went wrong? - if err != nil { - return nil, err - } - - // At this point, we know something went wrong in the remotefs program, but - // we we don't know why. - return nil, fmt.Errorf("unknown error") - } - - // Successful command, we might have some data to read (for Read + Seek) - buf := make([]byte, hdr.Size, hdr.Size) - if _, err := io.ReadFull(l.stdout, buf); err != nil { - return nil, err - } - return buf, nil -} diff --git a/daemon/graphdriver/lcow/remotefs_filedriver.go b/daemon/graphdriver/lcow/remotefs_filedriver.go deleted file mode 100644 index f335868af6..0000000000 --- a/daemon/graphdriver/lcow/remotefs_filedriver.go +++ /dev/null @@ -1,123 +0,0 @@ -// +build windows - -package lcow // import "github.com/docker/docker/daemon/graphdriver/lcow" - -import ( - "bytes" - "encoding/json" - "os" - "strconv" - - "github.com/Microsoft/opengcs/service/gcsutils/remotefs" - - "github.com/containerd/continuity/driver" - "github.com/sirupsen/logrus" -) - -var _ driver.Driver = &lcowfs{} - -func (l *lcowfs) Readlink(p string) (string, error) { - logrus.Debugf("removefs.readlink args: %s", p) - - result := &bytes.Buffer{} - if err := l.runRemoteFSProcess(nil, result, remotefs.ReadlinkCmd, p); err != nil { - return "", err - } - return result.String(), nil -} - -func (l *lcowfs) Mkdir(path string, mode os.FileMode) error { - return l.mkdir(path, mode, remotefs.MkdirCmd) -} - -func (l *lcowfs) MkdirAll(path string, mode os.FileMode) error { - return l.mkdir(path, mode, remotefs.MkdirAllCmd) -} - -func (l *lcowfs) mkdir(path string, mode os.FileMode, cmd string) error { - modeStr := strconv.FormatUint(uint64(mode), 8) - logrus.Debugf("remotefs.%s args: %s %s", cmd, path, modeStr) - return l.runRemoteFSProcess(nil, nil, cmd, path, modeStr) -} - -func (l *lcowfs) Remove(path string) error { - return l.remove(path, remotefs.RemoveCmd) -} - -func (l *lcowfs) RemoveAll(path string) error { - return l.remove(path, remotefs.RemoveAllCmd) -} - -func (l *lcowfs) remove(path string, cmd string) error { - logrus.Debugf("remotefs.%s args: %s", cmd, path) - return l.runRemoteFSProcess(nil, nil, cmd, path) -} - -func (l *lcowfs) Link(oldname, newname string) error { - return l.link(oldname, newname, remotefs.LinkCmd) -} - -func (l *lcowfs) Symlink(oldname, newname string) error { - return l.link(oldname, newname, remotefs.SymlinkCmd) -} - -func (l *lcowfs) link(oldname, newname, cmd string) error { - logrus.Debugf("remotefs.%s args: %s %s", cmd, oldname, newname) - return l.runRemoteFSProcess(nil, nil, cmd, oldname, newname) -} - -func (l *lcowfs) Lchown(name string, uid, gid int64) error { - uidStr := strconv.FormatInt(uid, 10) - gidStr := strconv.FormatInt(gid, 10) - - logrus.Debugf("remotefs.lchown args: %s %s %s", name, uidStr, gidStr) - return l.runRemoteFSProcess(nil, nil, remotefs.LchownCmd, name, uidStr, gidStr) -} - -// Lchmod changes the mode of an file not following symlinks. -func (l *lcowfs) Lchmod(path string, mode os.FileMode) error { - modeStr := strconv.FormatUint(uint64(mode), 8) - logrus.Debugf("remotefs.lchmod args: %s %s", path, modeStr) - return l.runRemoteFSProcess(nil, nil, remotefs.LchmodCmd, path, modeStr) -} - -func (l *lcowfs) Mknod(path string, mode os.FileMode, major, minor int) error { - modeStr := strconv.FormatUint(uint64(mode), 8) - majorStr := strconv.FormatUint(uint64(major), 10) - minorStr := strconv.FormatUint(uint64(minor), 10) - - logrus.Debugf("remotefs.mknod args: %s %s %s %s", path, modeStr, majorStr, minorStr) - return l.runRemoteFSProcess(nil, nil, remotefs.MknodCmd, path, modeStr, majorStr, minorStr) -} - -func (l *lcowfs) Mkfifo(path string, mode os.FileMode) error { - modeStr := strconv.FormatUint(uint64(mode), 8) - logrus.Debugf("remotefs.mkfifo args: %s %s", path, modeStr) - return l.runRemoteFSProcess(nil, nil, remotefs.MkfifoCmd, path, modeStr) -} - -func (l *lcowfs) Stat(p string) (os.FileInfo, error) { - return l.stat(p, remotefs.StatCmd) -} - -func (l *lcowfs) Lstat(p string) (os.FileInfo, error) { - return l.stat(p, remotefs.LstatCmd) -} - -func (l *lcowfs) stat(path string, cmd string) (os.FileInfo, error) { - logrus.Debugf("remotefs.stat inputs: %s %s", cmd, path) - - output := &bytes.Buffer{} - err := l.runRemoteFSProcess(nil, output, cmd, path) - if err != nil { - return nil, err - } - - var fi remotefs.FileInfo - if err := json.Unmarshal(output.Bytes(), &fi); err != nil { - return nil, err - } - - logrus.Debugf("remotefs.stat success. got: %v\n", fi) - return &fi, nil -} diff --git a/daemon/graphdriver/lcow/remotefs_pathdriver.go b/daemon/graphdriver/lcow/remotefs_pathdriver.go deleted file mode 100644 index 74895b0465..0000000000 --- a/daemon/graphdriver/lcow/remotefs_pathdriver.go +++ /dev/null @@ -1,212 +0,0 @@ -// +build windows - -package lcow // import "github.com/docker/docker/daemon/graphdriver/lcow" - -import ( - "errors" - "os" - pathpkg "path" - "path/filepath" - "sort" - "strings" - - "github.com/containerd/continuity/pathdriver" -) - -var _ pathdriver.PathDriver = &lcowfs{} - -// Continuity Path functions can be done locally -func (l *lcowfs) Join(path ...string) string { - return pathpkg.Join(path...) -} - -func (l *lcowfs) IsAbs(path string) bool { - return pathpkg.IsAbs(path) -} - -func sameWord(a, b string) bool { - return a == b -} - -// Implementation taken from the Go standard library -func (l *lcowfs) Rel(basepath, targpath string) (string, error) { - baseVol := "" - targVol := "" - base := l.Clean(basepath) - targ := l.Clean(targpath) - if sameWord(targ, base) { - return ".", nil - } - base = base[len(baseVol):] - targ = targ[len(targVol):] - if base == "." { - base = "" - } - // Can't use IsAbs - `\a` and `a` are both relative in Windows. - baseSlashed := len(base) > 0 && base[0] == l.Separator() - targSlashed := len(targ) > 0 && targ[0] == l.Separator() - if baseSlashed != targSlashed || !sameWord(baseVol, targVol) { - return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath) - } - // Position base[b0:bi] and targ[t0:ti] at the first differing elements. - bl := len(base) - tl := len(targ) - var b0, bi, t0, ti int - for { - for bi < bl && base[bi] != l.Separator() { - bi++ - } - for ti < tl && targ[ti] != l.Separator() { - ti++ - } - if !sameWord(targ[t0:ti], base[b0:bi]) { - break - } - if bi < bl { - bi++ - } - if ti < tl { - ti++ - } - b0 = bi - t0 = ti - } - if base[b0:bi] == ".." { - return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath) - } - if b0 != bl { - // Base elements left. Must go up before going down. - seps := strings.Count(base[b0:bl], string(l.Separator())) - size := 2 + seps*3 - if tl != t0 { - size += 1 + tl - t0 - } - buf := make([]byte, size) - n := copy(buf, "..") - for i := 0; i < seps; i++ { - buf[n] = l.Separator() - copy(buf[n+1:], "..") - n += 3 - } - if t0 != tl { - buf[n] = l.Separator() - copy(buf[n+1:], targ[t0:]) - } - return string(buf), nil - } - return targ[t0:], nil -} - -func (l *lcowfs) Base(path string) string { - return pathpkg.Base(path) -} - -func (l *lcowfs) Dir(path string) string { - return pathpkg.Dir(path) -} - -func (l *lcowfs) Clean(path string) string { - return pathpkg.Clean(path) -} - -func (l *lcowfs) Split(path string) (dir, file string) { - return pathpkg.Split(path) -} - -func (l *lcowfs) Separator() byte { - return '/' -} - -func (l *lcowfs) Abs(path string) (string, error) { - // Abs is supposed to add the current working directory, which is meaningless in lcow. - // So, return an error. - return "", ErrNotSupported -} - -// Implementation taken from the Go standard library -func (l *lcowfs) Walk(root string, walkFn filepath.WalkFunc) error { - info, err := l.Lstat(root) - if err != nil { - err = walkFn(root, nil, err) - } else { - err = l.walk(root, info, walkFn) - } - if err == filepath.SkipDir { - return nil - } - return err -} - -// walk recursively descends path, calling w. -func (l *lcowfs) walk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error { - err := walkFn(path, info, nil) - if err != nil { - if info.IsDir() && err == filepath.SkipDir { - return nil - } - return err - } - - if !info.IsDir() { - return nil - } - - names, err := l.readDirNames(path) - if err != nil { - return walkFn(path, info, err) - } - - for _, name := range names { - filename := l.Join(path, name) - fileInfo, err := l.Lstat(filename) - if err != nil { - if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir { - return err - } - } else { - err = l.walk(filename, fileInfo, walkFn) - if err != nil { - if !fileInfo.IsDir() || err != filepath.SkipDir { - return err - } - } - } - } - return nil -} - -// readDirNames reads the directory named by dirname and returns -// a sorted list of directory entries. -func (l *lcowfs) readDirNames(dirname string) ([]string, error) { - f, err := l.Open(dirname) - if err != nil { - return nil, err - } - files, err := f.Readdir(-1) - f.Close() - if err != nil { - return nil, err - } - - names := make([]string, len(files), len(files)) - for i := range files { - names[i] = files[i].Name() - } - - sort.Strings(names) - return names, nil -} - -// Note that Go's filepath.FromSlash/ToSlash convert between OS paths and '/'. Since the path separator -// for LCOW (and Unix) is '/', they are no-ops. -func (l *lcowfs) FromSlash(path string) string { - return path -} - -func (l *lcowfs) ToSlash(path string) string { - return path -} - -func (l *lcowfs) Match(pattern, name string) (matched bool, err error) { - return pathpkg.Match(pattern, name) -} diff --git a/daemon/graphdriver/register/register_windows.go b/daemon/graphdriver/register/register_windows.go index cd612cbea9..2b6fb6911d 100644 --- a/daemon/graphdriver/register/register_windows.go +++ b/daemon/graphdriver/register/register_windows.go @@ -2,6 +2,5 @@ package register // import "github.com/docker/docker/daemon/graphdriver/register import ( // register the windows graph drivers - _ "github.com/docker/docker/daemon/graphdriver/lcow" _ "github.com/docker/docker/daemon/graphdriver/windows" ) diff --git a/hack/ci/windows.ps1 b/hack/ci/windows.ps1 index bc58bd858b..00d8f905c6 100644 --- a/hack/ci/windows.ps1 +++ b/hack/ci/windows.ps1 @@ -9,12 +9,6 @@ $ErrorActionPreference = 'Stop' $StartTime=Get-Date -# Put up top to be blindingly obvious. The production jenkins.dockerproject.org Linux-container -# CI job is "Docker-PRs-LoW-RS3". Force into LCOW mode for this run, or not. -if ($env:BUILD_TAG -match "-LoW") { $env:LCOW_MODE=1 } -if ($env:BUILD_TAG -match "-WoW") { $env:LCOW_MODE="" } - - Write-Host -ForegroundColor Red "DEBUG: print all environment variables to check how Jenkins runs this script" $allArgs = [Environment]::GetCommandLineArgs() Write-Host -ForegroundColor Red $allArgs @@ -100,11 +94,6 @@ Write-Host -ForegroundColor Red "----------------------------------------------- # WINDOWS_BASE_IMAGE_TAG if defined, uses that as the tag name for the base image. # if no set, defaults to latest # -# LCOW_BASIC_MODE if defined, does very basic LCOW verification. Ultimately we -# want to run the entire CI suite from docker, but that's a way off. -# -# LCOW_MODE if defined, runs the entire CI suite -# # ------------------------------------------------------------------------------------------- # # Jenkins Integration. Add a Windows Powershell build step as follows: @@ -628,11 +617,6 @@ Try { Write-Host -ForegroundColor Green "INFO: Args: $dutArgs" New-Item -ItemType Directory $env:TEMP\daemon -ErrorAction SilentlyContinue | Out-Null - # In LCOW mode, for now we need to set an environment variable before starting the daemon under test - if (($null -ne $env:LCOW_MODE) -or ($null -ne $env:LCOW_BASIC_MODE)) { - $env:LCOW_SUPPORTED=1 - } - # Cannot fathom why, but always writes to stderr.... Start-Process "$env:TEMP\binary\dockerd-$COMMITHASH" ` -ArgumentList $dutArgs ` @@ -641,12 +625,6 @@ Try { Write-Host -ForegroundColor Green "INFO: Process started successfully." $daemonStarted=1 - # In LCOW mode, turn off that variable - if (($null -ne $env:LCOW_MODE) -or ($null -ne $env:LCOW_BASIC_MODE)) { - $env:LCOW_SUPPORTED="" - } - - # Start tailing the daemon under test if the command is installed if ($null -ne (Get-Command "tail" -ErrorAction SilentlyContinue)) { Write-Host -ForegroundColor green "INFO: Start tailing logs of the daemon under tests" @@ -706,63 +684,59 @@ Try { } Write-Host - # Don't need Windows images when in LCOW mode. - if (($null -eq $env:LCOW_MODE) -and ($null -eq $env:LCOW_BASIC_MODE)) { - - # Default to windowsservercore for the base image used for the tests. The "docker" image - # and the control daemon use microsoft/windowsservercore regardless. This is *JUST* for the tests. - if ($null -eq $env:WINDOWS_BASE_IMAGE) { - $env:WINDOWS_BASE_IMAGE="microsoft/windowsservercore" - } - if ($null -eq $env:WINDOWS_BASE_IMAGE_TAG) { - $env:WINDOWS_BASE_IMAGE_TAG="latest" - } - - # Lowercase and make sure it has a microsoft/ prefix - $env:WINDOWS_BASE_IMAGE = $env:WINDOWS_BASE_IMAGE.ToLower() - if (! $($env:WINDOWS_BASE_IMAGE -Split "/")[0] -match "microsoft") { - Throw "ERROR: WINDOWS_BASE_IMAGE should start microsoft/ or mcr.microsoft.com/" - } - - Write-Host -ForegroundColor Green "INFO: Base image for tests is $env:WINDOWS_BASE_IMAGE" - - $ErrorActionPreference = "SilentlyContinue" - if ($((& "$env:TEMP\binary\docker-$COMMITHASH" "-H=$($DASHH_CUT)" images --format "{{.Repository}}:{{.Tag}}" | Select-String "$($env:WINDOWS_BASE_IMAGE):$env:WINDOWS_BASE_IMAGE_TAG" | Measure-Object -Line).Lines) -eq 0) { - # Try the internal azure CI image version or Microsoft internal corpnet where the base image is already pre-prepared on the disk, - # either through Invoke-DockerCI or, in the case of Azure CI servers, baked into the VHD at the same location. - if (Test-Path $("c:\baseimages\"+$($env:WINDOWS_BASE_IMAGE -Split "/")[1]+".tar")) { - Write-Host -ForegroundColor Green "INFO: Loading"$($env:WINDOWS_BASE_IMAGE -Split "/")[1]".tar from disk into the daemon under test. This may take some time..." - $ErrorActionPreference = "SilentlyContinue" - & "$env:TEMP\binary\docker-$COMMITHASH" "-H=$($DASHH_CUT)" load -i $("$readBaseFrom`:\baseimages\"+$($env:WINDOWS_BASE_IMAGE -Split "/")[1]+".tar") - $ErrorActionPreference = "Stop" - if (-not $LastExitCode -eq 0) { - Throw $("ERROR: Failed to load $readBaseFrom`:\baseimages\"+$($env:WINDOWS_BASE_IMAGE -Split "/")[1]+".tar into daemon under test") - } - Write-Host -ForegroundColor Green "INFO: docker load of"$($env:WINDOWS_BASE_IMAGE -Split "/")[1]" into daemon under test completed successfully" - } else { - # We need to docker pull it instead. It will come in directly as microsoft/imagename:tagname - Write-Host -ForegroundColor Green $("INFO: Pulling "+$env:WINDOWS_BASE_IMAGE+":"+$env:WINDOWS_BASE_IMAGE_TAG+" from docker hub into daemon under test. This may take some time...") - $ErrorActionPreference = "SilentlyContinue" - & "$env:TEMP\binary\docker-$COMMITHASH" "-H=$($DASHH_CUT)" pull "$($env:WINDOWS_BASE_IMAGE):$env:WINDOWS_BASE_IMAGE_TAG" - $ErrorActionPreference = "Stop" - if (-not $LastExitCode -eq 0) { - Throw $("ERROR: Failed to docker pull $($env:WINDOWS_BASE_IMAGE):$env:WINDOWS_BASE_IMAGE_TAG into daemon under test.") - } - Write-Host -ForegroundColor Green $("INFO: docker pull of $($env:WINDOWS_BASE_IMAGE):$env:WINDOWS_BASE_IMAGE_TAG into daemon under test completed successfully") - Write-Host -ForegroundColor Green $("INFO: Tagging $($env:WINDOWS_BASE_IMAGE):$env:WINDOWS_BASE_IMAGE_TAG as microsoft/$ControlDaemonBaseImage in daemon under test") - & "$env:TEMP\binary\docker-$COMMITHASH" "-H=$($DASHH_CUT)" tag "$($env:WINDOWS_BASE_IMAGE):$env:WINDOWS_BASE_IMAGE_TAG" microsoft/$ControlDaemonBaseImage - } - } else { - Write-Host -ForegroundColor Green "INFO: Image $($env:WINDOWS_BASE_IMAGE):$env:WINDOWS_BASE_IMAGE_TAG is already loaded in the daemon under test" - } - - - # Inspect the pulled or loaded image to get the version directly - $ErrorActionPreference = "SilentlyContinue" - $dutimgVersion = $(&"$env:TEMP\binary\docker-$COMMITHASH" "-H=$($DASHH_CUT)" inspect "$($env:WINDOWS_BASE_IMAGE):$env:WINDOWS_BASE_IMAGE_TAG" --format "{{.OsVersion}}") - $ErrorActionPreference = "Stop" - Write-Host -ForegroundColor Green $("INFO: Version of $($env:WINDOWS_BASE_IMAGE):$env:WINDOWS_BASE_IMAGE_TAG is '"+$dutimgVersion+"'") + # Default to windowsservercore for the base image used for the tests. The "docker" image + # and the control daemon use microsoft/windowsservercore regardless. This is *JUST* for the tests. + if ($null -eq $env:WINDOWS_BASE_IMAGE) { + $env:WINDOWS_BASE_IMAGE="microsoft/windowsservercore" } + if ($null -eq $env:WINDOWS_BASE_IMAGE_TAG) { + $env:WINDOWS_BASE_IMAGE_TAG="latest" + } + + # Lowercase and make sure it has a microsoft/ prefix + $env:WINDOWS_BASE_IMAGE = $env:WINDOWS_BASE_IMAGE.ToLower() + if (! $($env:WINDOWS_BASE_IMAGE -Split "/")[0] -match "microsoft") { + Throw "ERROR: WINDOWS_BASE_IMAGE should start microsoft/ or mcr.microsoft.com/" + } + + Write-Host -ForegroundColor Green "INFO: Base image for tests is $env:WINDOWS_BASE_IMAGE" + + $ErrorActionPreference = "SilentlyContinue" + if ($((& "$env:TEMP\binary\docker-$COMMITHASH" "-H=$($DASHH_CUT)" images --format "{{.Repository}}:{{.Tag}}" | Select-String "$($env:WINDOWS_BASE_IMAGE):$env:WINDOWS_BASE_IMAGE_TAG" | Measure-Object -Line).Lines) -eq 0) { + # Try the internal azure CI image version or Microsoft internal corpnet where the base image is already pre-prepared on the disk, + # either through Invoke-DockerCI or, in the case of Azure CI servers, baked into the VHD at the same location. + if (Test-Path $("c:\baseimages\"+$($env:WINDOWS_BASE_IMAGE -Split "/")[1]+".tar")) { + Write-Host -ForegroundColor Green "INFO: Loading"$($env:WINDOWS_BASE_IMAGE -Split "/")[1]".tar from disk into the daemon under test. This may take some time..." + $ErrorActionPreference = "SilentlyContinue" + & "$env:TEMP\binary\docker-$COMMITHASH" "-H=$($DASHH_CUT)" load -i $("$readBaseFrom`:\baseimages\"+$($env:WINDOWS_BASE_IMAGE -Split "/")[1]+".tar") + $ErrorActionPreference = "Stop" + if (-not $LastExitCode -eq 0) { + Throw $("ERROR: Failed to load $readBaseFrom`:\baseimages\"+$($env:WINDOWS_BASE_IMAGE -Split "/")[1]+".tar into daemon under test") + } + Write-Host -ForegroundColor Green "INFO: docker load of"$($env:WINDOWS_BASE_IMAGE -Split "/")[1]" into daemon under test completed successfully" + } else { + # We need to docker pull it instead. It will come in directly as microsoft/imagename:tagname + Write-Host -ForegroundColor Green $("INFO: Pulling "+$env:WINDOWS_BASE_IMAGE+":"+$env:WINDOWS_BASE_IMAGE_TAG+" from docker hub into daemon under test. This may take some time...") + $ErrorActionPreference = "SilentlyContinue" + & "$env:TEMP\binary\docker-$COMMITHASH" "-H=$($DASHH_CUT)" pull "$($env:WINDOWS_BASE_IMAGE):$env:WINDOWS_BASE_IMAGE_TAG" + $ErrorActionPreference = "Stop" + if (-not $LastExitCode -eq 0) { + Throw $("ERROR: Failed to docker pull $($env:WINDOWS_BASE_IMAGE):$env:WINDOWS_BASE_IMAGE_TAG into daemon under test.") + } + Write-Host -ForegroundColor Green $("INFO: docker pull of $($env:WINDOWS_BASE_IMAGE):$env:WINDOWS_BASE_IMAGE_TAG into daemon under test completed successfully") + Write-Host -ForegroundColor Green $("INFO: Tagging $($env:WINDOWS_BASE_IMAGE):$env:WINDOWS_BASE_IMAGE_TAG as microsoft/$ControlDaemonBaseImage in daemon under test") + & "$env:TEMP\binary\docker-$COMMITHASH" "-H=$($DASHH_CUT)" tag "$($env:WINDOWS_BASE_IMAGE):$env:WINDOWS_BASE_IMAGE_TAG" microsoft/$ControlDaemonBaseImage + } + } else { + Write-Host -ForegroundColor Green "INFO: Image $($env:WINDOWS_BASE_IMAGE):$env:WINDOWS_BASE_IMAGE_TAG is already loaded in the daemon under test" + } + + + # Inspect the pulled or loaded image to get the version directly + $ErrorActionPreference = "SilentlyContinue" + $dutimgVersion = $(&"$env:TEMP\binary\docker-$COMMITHASH" "-H=$($DASHH_CUT)" inspect "$($env:WINDOWS_BASE_IMAGE):$env:WINDOWS_BASE_IMAGE_TAG" --format "{{.OsVersion}}") + $ErrorActionPreference = "Stop" + Write-Host -ForegroundColor Green $("INFO: Version of $($env:WINDOWS_BASE_IMAGE):$env:WINDOWS_BASE_IMAGE_TAG is '"+$dutimgVersion+"'") # Run the validation tests unless SKIP_VALIDATION_TESTS is defined. if ($null -eq $env:SKIP_VALIDATION_TESTS) { @@ -778,193 +752,122 @@ Try { Write-Host -ForegroundColor Magenta "WARN: Skipping validation tests" } - # Note the unit tests won't work in LCOW mode as I turned off loading the base images above. # Run the unit tests inside a container unless SKIP_UNIT_TESTS is defined - if (($null -eq $env:LCOW_MODE) -and ($null -eq $env:LCOW_BASIC_MODE)) { - if ($null -eq $env:SKIP_UNIT_TESTS) { - $ContainerNameForUnitTests = $COMMITHASH + "_UnitTests" - Write-Host -ForegroundColor Cyan "INFO: Running unit tests at $(Get-Date)..." - $ErrorActionPreference = "SilentlyContinue" - $Duration=$(Measure-Command {docker run --name $ContainerNameForUnitTests -e DOCKER_GITCOMMIT=$COMMITHASH$CommitUnsupported docker hack\make.ps1 -TestUnit | Out-Host }) - $TestRunExitCode = $LastExitCode - $ErrorActionPreference = "Stop" + if ($null -eq $env:SKIP_UNIT_TESTS) { + $ContainerNameForUnitTests = $COMMITHASH + "_UnitTests" + Write-Host -ForegroundColor Cyan "INFO: Running unit tests at $(Get-Date)..." + $ErrorActionPreference = "SilentlyContinue" + $Duration=$(Measure-Command {docker run --name $ContainerNameForUnitTests -e DOCKER_GITCOMMIT=$COMMITHASH$CommitUnsupported docker hack\make.ps1 -TestUnit | Out-Host }) + $TestRunExitCode = $LastExitCode + $ErrorActionPreference = "Stop" - # Saving where jenkins will take a look at..... - New-Item -Force -ItemType Directory bundles | Out-Null - $unitTestsContPath="$ContainerNameForUnitTests`:c`:\gopath\src\github.com\docker\docker\bundles" - $JunitExpectedContFilePath = "$unitTestsContPath\junit-report-unit-tests.xml" - docker cp $JunitExpectedContFilePath "bundles" - if (-not($LastExitCode -eq 0)) { - Throw "ERROR: Failed to docker cp the unit tests report ($JunitExpectedContFilePath) to bundles" - } - - if (Test-Path "bundles\junit-report-unit-tests.xml") { - Write-Host -ForegroundColor Magenta "INFO: Unit tests results(bundles\junit-report-unit-tests.xml) exist. pwd=$pwd" - } else { - Write-Host -ForegroundColor Magenta "ERROR: Unit tests results(bundles\junit-report-unit-tests.xml) do not exist. pwd=$pwd" - } - - if (-not($TestRunExitCode -eq 0)) { - Throw "ERROR: Unit tests failed" - } - Write-Host -ForegroundColor Green "INFO: Unit tests ended at $(Get-Date). Duration`:$Duration" - } else { - Write-Host -ForegroundColor Magenta "WARN: Skipping unit tests" + # Saving where jenkins will take a look at..... + New-Item -Force -ItemType Directory bundles | Out-Null + $unitTestsContPath="$ContainerNameForUnitTests`:c`:\gopath\src\github.com\docker\docker\bundles" + $JunitExpectedContFilePath = "$unitTestsContPath\junit-report-unit-tests.xml" + docker cp $JunitExpectedContFilePath "bundles" + if (-not($LastExitCode -eq 0)) { + Throw "ERROR: Failed to docker cp the unit tests report ($JunitExpectedContFilePath) to bundles" } + + if (Test-Path "bundles\junit-report-unit-tests.xml") { + Write-Host -ForegroundColor Magenta "INFO: Unit tests results(bundles\junit-report-unit-tests.xml) exist. pwd=$pwd" + } else { + Write-Host -ForegroundColor Magenta "ERROR: Unit tests results(bundles\junit-report-unit-tests.xml) do not exist. pwd=$pwd" + } + + if (-not($TestRunExitCode -eq 0)) { + Throw "ERROR: Unit tests failed" + } + Write-Host -ForegroundColor Green "INFO: Unit tests ended at $(Get-Date). Duration`:$Duration" + } else { + Write-Host -ForegroundColor Magenta "WARN: Skipping unit tests" } # Add the Windows busybox image. Needed for WCOW integration tests - if (($null -eq $env:LCOW_MODE) -and ($null -eq $env:LCOW_BASIC_MODE)) { - if ($null -eq $env:SKIP_INTEGRATION_TESTS) { - Write-Host -ForegroundColor Green "INFO: Building busybox" - $ErrorActionPreference = "SilentlyContinue" - $(& "$env:TEMP\binary\docker-$COMMITHASH" "-H=$($DASHH_CUT)" build -t busybox --build-arg WINDOWS_BASE_IMAGE --build-arg WINDOWS_BASE_IMAGE_TAG "$env:SOURCES_DRIVE`:\$env:SOURCES_SUBDIR\src\github.com\docker\docker\contrib\busybox\" | Out-Host) - $ErrorActionPreference = "Stop" - if (-not($LastExitCode -eq 0)) { - Throw "ERROR: Failed to build busybox image" - } - - Write-Host -ForegroundColor Green "INFO: Docker images of the daemon under test" - Write-Host - $ErrorActionPreference = "SilentlyContinue" - & "$env:TEMP\binary\docker-$COMMITHASH" "-H=$($DASHH_CUT)" images - $ErrorActionPreference = "Stop" - if ($LastExitCode -ne 0) { - Throw "ERROR: The daemon under test does not appear to be running." - } - Write-Host + if ($null -eq $env:SKIP_INTEGRATION_TESTS) { + Write-Host -ForegroundColor Green "INFO: Building busybox" + $ErrorActionPreference = "SilentlyContinue" + $(& "$env:TEMP\binary\docker-$COMMITHASH" "-H=$($DASHH_CUT)" build -t busybox --build-arg WINDOWS_BASE_IMAGE --build-arg WINDOWS_BASE_IMAGE_TAG "$env:SOURCES_DRIVE`:\$env:SOURCES_SUBDIR\src\github.com\docker\docker\contrib\busybox\" | Out-Host) + $ErrorActionPreference = "Stop" + if (-not($LastExitCode -eq 0)) { + Throw "ERROR: Failed to build busybox image" } + + Write-Host -ForegroundColor Green "INFO: Docker images of the daemon under test" + Write-Host + $ErrorActionPreference = "SilentlyContinue" + & "$env:TEMP\binary\docker-$COMMITHASH" "-H=$($DASHH_CUT)" images + $ErrorActionPreference = "Stop" + if ($LastExitCode -ne 0) { + Throw "ERROR: The daemon under test does not appear to be running." + } + Write-Host } # Run the WCOW integration tests unless SKIP_INTEGRATION_TESTS is defined - if (($null -eq $env:LCOW_MODE) -and ($null -eq $env:LCOW_BASIC_MODE)) { - if ($null -eq $env:SKIP_INTEGRATION_TESTS) { - Write-Host -ForegroundColor Cyan "INFO: Running integration tests at $(Get-Date)..." - $ErrorActionPreference = "SilentlyContinue" - - # Location of the daemon under test. - $env:OrigDOCKER_HOST="$env:DOCKER_HOST" - - #https://blogs.technet.microsoft.com/heyscriptingguy/2011/09/20/solve-problems-with-external-command-lines-in-powershell/ is useful to see tokenising - $jsonFilePath = "..\\bundles\\go-test-report-intcli-tests.json" - $xmlFilePath = "..\\bundles\\junit-report-intcli-tests.xml" - $c = "gotestsum --format=standard-verbose --jsonfile=$jsonFilePath --junitfile=$xmlFilePath -- " - if ($null -ne $env:INTEGRATION_TEST_NAME) { # Makes is quicker for debugging to be able to run only a subset of the integration tests - $c += "`"-test.run`" " - $c += "`"$env:INTEGRATION_TEST_NAME`" " - Write-Host -ForegroundColor Magenta "WARN: Only running integration tests matching $env:INTEGRATION_TEST_NAME" - } - $c += "`"-tags`" " + "`"autogen`" " - $c += "`"-test.timeout`" " + "`"200m`" " - - if ($null -ne $env:INTEGRATION_IN_CONTAINER) { - Write-Host -ForegroundColor Green "INFO: Integration tests being run inside a container" - # Note we talk back through the containers gateway address - # And the ridiculous lengths we have to go to get the default gateway address... (GetNetIPConfiguration doesn't work in nanoserver) - # I just could not get the escaping to work in a single command, so output $c to a file and run that in the container instead... - # Not the prettiest, but it works. - $c | Out-File -Force "$env:TEMP\binary\runIntegrationCLI.ps1" - $Duration= $(Measure-Command { & docker run ` - --rm ` - -e c=$c ` - --workdir "c`:\gopath\src\github.com\docker\docker\integration-cli" ` - -v "$env:TEMP\binary`:c:\target" ` - docker ` - "`$env`:PATH`='c`:\target;'+`$env:PATH`; `$env:DOCKER_HOST`='tcp`://'+(ipconfig | select -last 1).Substring(39)+'`:2357'; c:\target\runIntegrationCLI.ps1" | Out-Host } ) - } else { - $env:DOCKER_HOST=$DASHH_CUT - $env:PATH="$env:TEMP\binary;$env:PATH;" # Force to use the test binaries, not the host ones. - $env:GO111MODULE="off" - Write-Host -ForegroundColor Green "INFO: DOCKER_HOST at $DASHH_CUT" + if ($null -eq $env:SKIP_INTEGRATION_TESTS) { + Write-Host -ForegroundColor Cyan "INFO: Running integration tests at $(Get-Date)..." + $ErrorActionPreference = "SilentlyContinue" - $ErrorActionPreference = "SilentlyContinue" - Write-Host -ForegroundColor Cyan "INFO: Integration API tests being run from the host:" - $start=(Get-Date); Invoke-Expression ".\hack\make.ps1 -TestIntegration"; $Duration=New-Timespan -Start $start -End (Get-Date) - $IntTestsRunResult = $LastExitCode - $ErrorActionPreference = "Stop" - if (-not($IntTestsRunResult -eq 0)) { - Throw "ERROR: Integration API tests failed at $(Get-Date). Duration`:$Duration" - } + # Location of the daemon under test. + $env:OrigDOCKER_HOST="$env:DOCKER_HOST" - $ErrorActionPreference = "SilentlyContinue" - Write-Host -ForegroundColor Green "INFO: Integration CLI tests being run from the host:" - Write-Host -ForegroundColor Green "INFO: $c" - Set-Location "$env:SOURCES_DRIVE`:\$env:SOURCES_SUBDIR\src\github.com\docker\docker\integration-cli" - # Explicit to not use measure-command otherwise don't get output as it goes - $start=(Get-Date); Invoke-Expression $c; $Duration=New-Timespan -Start $start -End (Get-Date) - } - $ErrorActionPreference = "Stop" - if (-not($LastExitCode -eq 0)) { - Throw "ERROR: Integration CLI tests failed at $(Get-Date). Duration`:$Duration" - } - Write-Host -ForegroundColor Green "INFO: Integration tests ended at $(Get-Date). Duration`:$Duration" - } else { - Write-Host -ForegroundColor Magenta "WARN: Skipping integration tests" + #https://blogs.technet.microsoft.com/heyscriptingguy/2011/09/20/solve-problems-with-external-command-lines-in-powershell/ is useful to see tokenising + $jsonFilePath = "..\\bundles\\go-test-report-intcli-tests.json" + $xmlFilePath = "..\\bundles\\junit-report-intcli-tests.xml" + $c = "gotestsum --format=standard-verbose --jsonfile=$jsonFilePath --junitfile=$xmlFilePath -- " + if ($null -ne $env:INTEGRATION_TEST_NAME) { # Makes is quicker for debugging to be able to run only a subset of the integration tests + $c += "`"-test.run`" " + $c += "`"$env:INTEGRATION_TEST_NAME`" " + Write-Host -ForegroundColor Magenta "WARN: Only running integration tests matching $env:INTEGRATION_TEST_NAME" } - } else { - # The LCOW version of the tests here - if ($null -eq $env:SKIP_INTEGRATION_TESTS) { - Write-Host -ForegroundColor Cyan "INFO: Running LCOW tests at $(Get-Date)..." + $c += "`"-tags`" " + "`"autogen`" " + $c += "`"-test.timeout`" " + "`"200m`" " - $ErrorActionPreference = "SilentlyContinue" - - # Location of the daemon under test. - $env:OrigDOCKER_HOST="$env:DOCKER_HOST" - - # Make sure we are pointing at the DUT - $env:DOCKER_HOST=$DASHH_CUT + if ($null -ne $env:INTEGRATION_IN_CONTAINER) { + Write-Host -ForegroundColor Green "INFO: Integration tests being run inside a container" + # Note we talk back through the containers gateway address + # And the ridiculous lengths we have to go to get the default gateway address... (GetNetIPConfiguration doesn't work in nanoserver) + # I just could not get the escaping to work in a single command, so output $c to a file and run that in the container instead... + # Not the prettiest, but it works. + $c | Out-File -Force "$env:TEMP\binary\runIntegrationCLI.ps1" + $Duration= $(Measure-Command { & docker run ` + --rm ` + -e c=$c ` + --workdir "c`:\gopath\src\github.com\docker\docker\integration-cli" ` + -v "$env:TEMP\binary`:c:\target" ` + docker ` + "`$env`:PATH`='c`:\target;'+`$env:PATH`; `$env:DOCKER_HOST`='tcp`://'+(ipconfig | select -last 1).Substring(39)+'`:2357'; c:\target\runIntegrationCLI.ps1" | Out-Host } ) + } else { + $env:DOCKER_HOST=$DASHH_CUT + $env:PATH="$env:TEMP\binary;$env:PATH;" # Force to use the test binaries, not the host ones. + $env:GO111MODULE="off" Write-Host -ForegroundColor Green "INFO: DOCKER_HOST at $DASHH_CUT" - # Force to use the test binaries, not the host ones. - $env:PATH="$env:TEMP\binary;$env:PATH;" - - if ($null -ne $env:LCOW_BASIC_MODE) { - $wc = New-Object net.webclient - try { - Write-Host -ForegroundColor green "INFO: Downloading latest execution script..." - $wc.Downloadfile("https://raw.githubusercontent.com/kevpar/docker-w2wCIScripts/master/runCI/lcowbasicvalidation.ps1", "$env:TEMP\binary\lcowbasicvalidation.ps1") - } - catch [System.Net.WebException] - { - Throw ("Failed to download: $_") - } - - # Explicit to not use measure-command otherwise don't get output as it goes - $ErrorActionPreference = "Stop" - $start=(Get-Date); Invoke-Expression "powershell $env:TEMP\binary\lcowbasicvalidation.ps1"; $lec=$lastExitCode; $Duration=New-Timespan -Start $start -End (Get-Date) - $Duration=New-Timespan -Start $start -End (Get-Date) - Write-Host -ForegroundColor Green "INFO: LCOW tests ended at $(Get-Date). Duration`:$Duration" - if ($lec -ne 0) { - Throw "LCOW validation tests failed" - } - } else { - #https://blogs.technet.microsoft.com/heyscriptingguy/2011/09/20/solve-problems-with-external-command-lines-in-powershell/ is useful to see tokenising - $c = "go test " - $c += "`"-test.v`" " - if ($null -ne $env:INTEGRATION_TEST_NAME) { # Makes is quicker for debugging to be able to run only a subset of the integration tests - $c += "`"-test.run`" " - $c += "`"$env:INTEGRATION_TEST_NAME`" " - Write-Host -ForegroundColor Magenta "WARN: Only running LCOW integration tests matching $env:INTEGRATION_TEST_NAME" - } - $c += "`"-tags`" " + "`"autogen`" " - $c += "`"-test.timeout`" " + "`"200m`" " - - Write-Host -ForegroundColor Green "INFO: LCOW Integration tests being run from the host:" - Set-Location "$env:SOURCES_DRIVE`:\$env:SOURCES_SUBDIR\src\github.com\docker\docker\integration-cli" - Write-Host -ForegroundColor Green "INFO: $c" - Write-Host -ForegroundColor Green "INFO: DOCKER_HOST at $DASHH_CUT" - # Explicit to not use measure-command otherwise don't get output as it goes - $start=(Get-Date); Invoke-Expression $c; $Duration=New-Timespan -Start $start -End (Get-Date) - - } + $ErrorActionPreference = "SilentlyContinue" + Write-Host -ForegroundColor Cyan "INFO: Integration API tests being run from the host:" + $start=(Get-Date); Invoke-Expression ".\hack\make.ps1 -TestIntegration"; $Duration=New-Timespan -Start $start -End (Get-Date) + $IntTestsRunResult = $LastExitCode $ErrorActionPreference = "Stop" - if (-not($LastExitCode -eq 0)) { - Throw "ERROR: Integration tests failed at $(Get-Date). Duration`:$Duration" + if (-not($IntTestsRunResult -eq 0)) { + Throw "ERROR: Integration API tests failed at $(Get-Date). Duration`:$Duration" } - Write-Host -ForegroundColor Green "INFO: Integration tests ended at $(Get-Date). Duration`:$Duration" - } else { - Write-Host -ForegroundColor Magenta "WARN: Skipping LCOW tests" + + $ErrorActionPreference = "SilentlyContinue" + Write-Host -ForegroundColor Green "INFO: Integration CLI tests being run from the host:" + Write-Host -ForegroundColor Green "INFO: $c" + Set-Location "$env:SOURCES_DRIVE`:\$env:SOURCES_SUBDIR\src\github.com\docker\docker\integration-cli" + # Explicit to not use measure-command otherwise don't get output as it goes + $start=(Get-Date); Invoke-Expression $c; $Duration=New-Timespan -Start $start -End (Get-Date) } + $ErrorActionPreference = "Stop" + if (-not($LastExitCode -eq 0)) { + Throw "ERROR: Integration CLI tests failed at $(Get-Date). Duration`:$Duration" + } + Write-Host -ForegroundColor Green "INFO: Integration tests ended at $(Get-Date). Duration`:$Duration" + } else { + Write-Host -ForegroundColor Magenta "WARN: Skipping integration tests" } # Docker info now to get counts (after or if jjh/containercounts is merged) diff --git a/image/tarexport/load.go b/image/tarexport/load.go index 1ff2844ae2..63385e1a1c 100644 --- a/image/tarexport/load.go +++ b/image/tarexport/load.go @@ -426,12 +426,8 @@ func checkCompatibleOS(imageOS string) error { return fmt.Errorf("cannot load %s image on %s", imageOS, runtime.GOOS) } - p, err := platforms.Parse(imageOS) - if err != nil { - return err - } - - return system.ValidatePlatform(p) + _, err := platforms.Parse(imageOS) + return err } func validateManifest(manifest []manifestItem) error { diff --git a/pkg/system/lcow.go b/pkg/system/lcow.go deleted file mode 100644 index 0f00028fbd..0000000000 --- a/pkg/system/lcow.go +++ /dev/null @@ -1,48 +0,0 @@ -// +build windows,!no_lcow - -package system // import "github.com/docker/docker/pkg/system" - -import ( - "strings" - - "github.com/Microsoft/hcsshim/osversion" - specs "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/pkg/errors" -) - -var ( - // lcowSupported determines if Linux Containers on Windows are supported. - lcowSupported = false -) - -// InitLCOW sets whether LCOW is supported or not. Requires RS5+ -func InitLCOW(experimental bool) { - if experimental && osversion.Build() >= osversion.RS5 { - lcowSupported = true - } -} - -func LCOWSupported() bool { - return lcowSupported -} - -// ValidatePlatform determines if a platform structure is valid. -// TODO This is a temporary windows-only function, should be replaced by -// comparison of worker capabilities -func ValidatePlatform(platform specs.Platform) error { - if !IsOSSupported(platform.OS) { - return errors.Errorf("unsupported os %s", platform.OS) - } - return nil -} - -// IsOSSupported determines if an operating system is supported by the host -func IsOSSupported(os string) bool { - if strings.EqualFold("windows", os) { - return true - } - if LCOWSupported() && strings.EqualFold(os, "linux") { - return true - } - return false -} diff --git a/pkg/system/lcow_unsupported.go b/pkg/system/lcow_unsupported.go index 3d3cf775a7..09150f1282 100644 --- a/pkg/system/lcow_unsupported.go +++ b/pkg/system/lcow_unsupported.go @@ -1,27 +1,14 @@ -// +build !windows windows,no_lcow - package system // import "github.com/docker/docker/pkg/system" import ( "runtime" "strings" - - specs "github.com/opencontainers/image-spec/specs-go/v1" ) -// InitLCOW does nothing since LCOW is a windows only feature -func InitLCOW(_ bool) {} - // LCOWSupported returns true if Linux containers on Windows are supported. func LCOWSupported() bool { return false } -// ValidatePlatform determines if a platform structure is valid. This function -// is used for LCOW, and is a no-op on non-windows platforms. -func ValidatePlatform(_ specs.Platform) error { - return nil -} - // IsOSSupported determines if an operating system is supported by the host. func IsOSSupported(os string) bool { return strings.EqualFold(runtime.GOOS, os) diff --git a/pkg/system/path.go b/pkg/system/path.go index 64e892289a..4d81906b9d 100644 --- a/pkg/system/path.go +++ b/pkg/system/path.go @@ -1,24 +1,15 @@ package system // import "github.com/docker/docker/pkg/system" -import ( - "fmt" - "path/filepath" - "runtime" - "strings" -) - const defaultUnixPathEnv = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" // DefaultPathEnv is unix style list of directories to search for // executables. Each directory is separated from the next by a colon // ':' character . +// For Windows containers, an empty string is returned as the default +// path will be set by the container, and Docker has no context of what the +// default path should be. func DefaultPathEnv(os string) string { - if runtime.GOOS == "windows" { - if os != runtime.GOOS { - return defaultUnixPathEnv - } - // Deliberately empty on Windows containers on Windows as the default path will be set by - // the container. Docker has no context of what the default path should be. + if os == "windows" { return "" } return defaultUnixPathEnv @@ -47,18 +38,5 @@ type PathVerifier interface { // /a --> \a // d:\ --> Fail func CheckSystemDriveAndRemoveDriveLetter(path string, driver PathVerifier) (string, error) { - if runtime.GOOS != "windows" || LCOWSupported() { - return path, nil - } - - if len(path) == 2 && string(path[1]) == ":" { - return "", fmt.Errorf("No relative path specified in %q", path) - } - if !driver.IsAbs(path) || len(path) < 2 { - return filepath.FromSlash(path), nil - } - if string(path[1]) == ":" && !strings.EqualFold(string(path[0]), "c") { - return "", fmt.Errorf("The specified path is not on the system drive (C:)") - } - return filepath.FromSlash(path[2:]), nil + return checkSystemDriveAndRemoveDriveLetter(path, driver) } diff --git a/pkg/system/path_unix.go b/pkg/system/path_unix.go index b0b93196a1..19681985da 100644 --- a/pkg/system/path_unix.go +++ b/pkg/system/path_unix.go @@ -8,3 +8,9 @@ package system // import "github.com/docker/docker/pkg/system" func GetLongPathName(path string) (string, error) { return path, nil } + +// checkSystemDriveAndRemoveDriveLetter is the non-Windows implementation +// of CheckSystemDriveAndRemoveDriveLetter +func checkSystemDriveAndRemoveDriveLetter(path string, driver PathVerifier) (string, error) { + return path, nil +} diff --git a/pkg/system/path_windows.go b/pkg/system/path_windows.go index 22a56136c8..7d375b0ddc 100644 --- a/pkg/system/path_windows.go +++ b/pkg/system/path_windows.go @@ -1,6 +1,12 @@ package system // import "github.com/docker/docker/pkg/system" -import "golang.org/x/sys/windows" +import ( + "fmt" + "path/filepath" + "strings" + + "golang.org/x/sys/windows" +) // GetLongPathName converts Windows short pathnames to full pathnames. // For example C:\Users\ADMIN~1 --> C:\Users\Administrator. @@ -25,3 +31,18 @@ func GetLongPathName(path string) (string, error) { } return windows.UTF16ToString(b), nil } + +// checkSystemDriveAndRemoveDriveLetter is the Windows implementation +// of CheckSystemDriveAndRemoveDriveLetter +func checkSystemDriveAndRemoveDriveLetter(path string, driver PathVerifier) (string, error) { + if len(path) == 2 && string(path[1]) == ":" { + return "", fmt.Errorf("No relative path specified in %q", path) + } + if !driver.IsAbs(path) || len(path) < 2 { + return filepath.FromSlash(path), nil + } + if string(path[1]) == ":" && !strings.EqualFold(string(path[0]), "c") { + return "", fmt.Errorf("The specified path is not on the system drive (C:)") + } + return filepath.FromSlash(path[2:]), nil +} diff --git a/project/PACKAGERS.md b/project/PACKAGERS.md index 90447fe289..d49776803c 100644 --- a/project/PACKAGERS.md +++ b/project/PACKAGERS.md @@ -185,12 +185,6 @@ NOTE: if you need to set more than one build tag, space separate them: export DOCKER_BUILDTAGS='apparmor exclude_graphdriver_aufs' ``` -### LCOW (Linux Containers On Windows) - -LCOW is an experimental feature on Windows, and requires the daemon to run with -experimental features enabled. Use the `no_lcow` build tag to disable the LCOW -feature at compile time, - ### Static Daemon If it is feasible within the constraints of your distribution, you should diff --git a/vendor/github.com/Microsoft/hcsshim/ext4/internal/compactext4/compact.go b/vendor/github.com/Microsoft/hcsshim/ext4/internal/compactext4/compact.go deleted file mode 100644 index d9a959ffeb..0000000000 --- a/vendor/github.com/Microsoft/hcsshim/ext4/internal/compactext4/compact.go +++ /dev/null @@ -1,1325 +0,0 @@ -package compactext4 - -import ( - "bufio" - "bytes" - "encoding/binary" - "errors" - "fmt" - "io" - "path" - "sort" - "strings" - "time" - - "github.com/Microsoft/hcsshim/ext4/internal/format" -) - -// Writer writes a compact ext4 file system. -type Writer struct { - f io.ReadWriteSeeker - bw *bufio.Writer - inodes []*inode - curName string - curInode *inode - pos int64 - dataWritten, dataMax int64 - err error - initialized bool - supportInlineData bool - maxDiskSize int64 - gdBlocks uint32 -} - -// Mode flags for Linux files. -const ( - S_IXOTH = format.S_IXOTH - S_IWOTH = format.S_IWOTH - S_IROTH = format.S_IROTH - S_IXGRP = format.S_IXGRP - S_IWGRP = format.S_IWGRP - S_IRGRP = format.S_IRGRP - S_IXUSR = format.S_IXUSR - S_IWUSR = format.S_IWUSR - S_IRUSR = format.S_IRUSR - S_ISVTX = format.S_ISVTX - S_ISGID = format.S_ISGID - S_ISUID = format.S_ISUID - S_IFIFO = format.S_IFIFO - S_IFCHR = format.S_IFCHR - S_IFDIR = format.S_IFDIR - S_IFBLK = format.S_IFBLK - S_IFREG = format.S_IFREG - S_IFLNK = format.S_IFLNK - S_IFSOCK = format.S_IFSOCK - - TypeMask = format.TypeMask -) - -type inode struct { - Size int64 - Atime, Ctime, Mtime, Crtime uint64 - Number format.InodeNumber - Mode uint16 - Uid, Gid uint32 - LinkCount uint32 - XattrBlock uint32 - BlockCount uint32 - Devmajor, Devminor uint32 - Flags format.InodeFlag - Data []byte - XattrInline []byte - Children directory -} - -func (node *inode) FileType() uint16 { - return node.Mode & format.TypeMask -} - -func (node *inode) IsDir() bool { - return node.FileType() == S_IFDIR -} - -// A File represents a file to be added to an ext4 file system. -type File struct { - Linkname string - Size int64 - Mode uint16 - Uid, Gid uint32 - Atime, Ctime, Mtime, Crtime time.Time - Devmajor, Devminor uint32 - Xattrs map[string][]byte -} - -const ( - inodeFirst = 11 - inodeLostAndFound = inodeFirst - - blockSize = 4096 - blocksPerGroup = blockSize * 8 - inodeSize = 256 - maxInodesPerGroup = blockSize * 8 // Limited by the inode bitmap - inodesPerGroupIncrement = blockSize / inodeSize - - defaultMaxDiskSize = 16 * 1024 * 1024 * 1024 // 16GB - maxMaxDiskSize = 16 * 1024 * 1024 * 1024 * 1024 // 16TB - - groupDescriptorSize = 32 // Use the small group descriptor - groupsPerDescriptorBlock = blockSize / groupDescriptorSize - - maxFileSize = 128 * 1024 * 1024 * 1024 // 128GB file size maximum for now - smallSymlinkSize = 59 // max symlink size that goes directly in the inode - maxBlocksPerExtent = 0x8000 // maximum number of blocks in an extent - inodeDataSize = 60 - inodeUsedSize = 152 // fields through CrtimeExtra - inodeExtraSize = inodeSize - inodeUsedSize - xattrInodeOverhead = 4 + 4 // magic number + empty next entry value - xattrBlockOverhead = 32 + 4 // header + empty next entry value - inlineDataXattrOverhead = xattrInodeOverhead + 16 + 4 // entry + "data" - inlineDataSize = inodeDataSize + inodeExtraSize - inlineDataXattrOverhead -) - -type exceededMaxSizeError struct { - Size int64 -} - -func (err exceededMaxSizeError) Error() string { - return fmt.Sprintf("disk exceeded maximum size of %d bytes", err.Size) -} - -var directoryEntrySize = binary.Size(format.DirectoryEntry{}) -var extraIsize = uint16(inodeUsedSize - 128) - -type directory map[string]*inode - -func splitFirst(p string) (string, string) { - n := strings.IndexByte(p, '/') - if n >= 0 { - return p[:n], p[n+1:] - } - return p, "" -} - -func (w *Writer) findPath(root *inode, p string) *inode { - inode := root - for inode != nil && len(p) != 0 { - name, rest := splitFirst(p) - p = rest - inode = inode.Children[name] - } - return inode -} - -func timeToFsTime(t time.Time) uint64 { - if t.IsZero() { - return 0 - } - s := t.Unix() - if s < -0x80000000 { - return 0x80000000 - } - if s > 0x37fffffff { - return 0x37fffffff - } - return uint64(s) | uint64(t.Nanosecond())<<34 -} - -func fsTimeToTime(t uint64) time.Time { - if t == 0 { - return time.Time{} - } - s := int64(t & 0x3ffffffff) - if s > 0x7fffffff && s < 0x100000000 { - s = int64(int32(uint32(s))) - } - return time.Unix(s, int64(t>>34)) -} - -func (w *Writer) getInode(i format.InodeNumber) *inode { - if i == 0 || int(i) > len(w.inodes) { - return nil - } - return w.inodes[i-1] -} - -var xattrPrefixes = []struct { - Index uint8 - Prefix string -}{ - {2, "system.posix_acl_access"}, - {3, "system.posix_acl_default"}, - {8, "system.richacl"}, - {7, "system."}, - {1, "user."}, - {4, "trusted."}, - {6, "security."}, -} - -func compressXattrName(name string) (uint8, string) { - for _, p := range xattrPrefixes { - if strings.HasPrefix(name, p.Prefix) { - return p.Index, name[len(p.Prefix):] - } - } - return 0, name -} - -func decompressXattrName(index uint8, name string) string { - for _, p := range xattrPrefixes { - if index == p.Index { - return p.Prefix + name - } - } - return name -} - -func hashXattrEntry(name string, value []byte) uint32 { - var hash uint32 - for i := 0; i < len(name); i++ { - hash = (hash << 5) ^ (hash >> 27) ^ uint32(name[i]) - } - - for i := 0; i+3 < len(value); i += 4 { - hash = (hash << 16) ^ (hash >> 16) ^ binary.LittleEndian.Uint32(value[i:i+4]) - } - - if len(value)%4 != 0 { - var last [4]byte - copy(last[:], value[len(value)&^3:]) - hash = (hash << 16) ^ (hash >> 16) ^ binary.LittleEndian.Uint32(last[:]) - } - return hash -} - -type xattr struct { - Name string - Index uint8 - Value []byte -} - -func (x *xattr) EntryLen() int { - return (len(x.Name)+3)&^3 + 16 -} - -func (x *xattr) ValueLen() int { - return (len(x.Value) + 3) &^ 3 -} - -type xattrState struct { - inode, block []xattr - inodeLeft, blockLeft int -} - -func (s *xattrState) init() { - s.inodeLeft = inodeExtraSize - xattrInodeOverhead - s.blockLeft = blockSize - xattrBlockOverhead -} - -func (s *xattrState) addXattr(name string, value []byte) bool { - index, name := compressXattrName(name) - x := xattr{ - Index: index, - Name: name, - Value: value, - } - length := x.EntryLen() + x.ValueLen() - if s.inodeLeft >= length { - s.inode = append(s.inode, x) - s.inodeLeft -= length - } else if s.blockLeft >= length { - s.block = append(s.block, x) - s.blockLeft -= length - } else { - return false - } - return true -} - -func putXattrs(xattrs []xattr, b []byte, offsetDelta uint16) { - offset := uint16(len(b)) + offsetDelta - eb := b - db := b - for _, xattr := range xattrs { - vl := xattr.ValueLen() - offset -= uint16(vl) - eb[0] = uint8(len(xattr.Name)) - eb[1] = xattr.Index - binary.LittleEndian.PutUint16(eb[2:], offset) - binary.LittleEndian.PutUint32(eb[8:], uint32(len(xattr.Value))) - binary.LittleEndian.PutUint32(eb[12:], hashXattrEntry(xattr.Name, xattr.Value)) - copy(eb[16:], xattr.Name) - eb = eb[xattr.EntryLen():] - copy(db[len(db)-vl:], xattr.Value) - db = db[:len(db)-vl] - } -} - -func getXattrs(b []byte, xattrs map[string][]byte, offsetDelta uint16) { - eb := b - for len(eb) != 0 { - nameLen := eb[0] - if nameLen == 0 { - break - } - index := eb[1] - offset := binary.LittleEndian.Uint16(eb[2:]) - offsetDelta - valueLen := binary.LittleEndian.Uint32(eb[8:]) - attr := xattr{ - Index: index, - Name: string(eb[16 : 16+nameLen]), - Value: b[offset : uint32(offset)+valueLen], - } - xattrs[decompressXattrName(index, attr.Name)] = attr.Value - eb = eb[attr.EntryLen():] - } -} - -func (w *Writer) writeXattrs(inode *inode, state *xattrState) error { - // Write the inline attributes. - if len(state.inode) != 0 { - inode.XattrInline = make([]byte, inodeExtraSize) - binary.LittleEndian.PutUint32(inode.XattrInline[0:], format.XAttrHeaderMagic) // Magic - putXattrs(state.inode, inode.XattrInline[4:], 0) - } - - // Write the block attributes. If there was previously an xattr block, then - // rewrite it even if it is now empty. - if len(state.block) != 0 || inode.XattrBlock != 0 { - sort.Slice(state.block, func(i, j int) bool { - return state.block[i].Index < state.block[j].Index || - len(state.block[i].Name) < len(state.block[j].Name) || - state.block[i].Name < state.block[j].Name - }) - - var b [blockSize]byte - binary.LittleEndian.PutUint32(b[0:], format.XAttrHeaderMagic) // Magic - binary.LittleEndian.PutUint32(b[4:], 1) // ReferenceCount - binary.LittleEndian.PutUint32(b[8:], 1) // Blocks - putXattrs(state.block, b[32:], 32) - - orig := w.block() - if inode.XattrBlock == 0 { - inode.XattrBlock = orig - inode.BlockCount++ - } else { - // Reuse the original block. - w.seekBlock(inode.XattrBlock) - defer w.seekBlock(orig) - } - - if _, err := w.write(b[:]); err != nil { - return err - } - } - - return nil -} - -func (w *Writer) write(b []byte) (int, error) { - if w.err != nil { - return 0, w.err - } - if w.pos+int64(len(b)) > w.maxDiskSize { - w.err = exceededMaxSizeError{w.maxDiskSize} - return 0, w.err - } - n, err := w.bw.Write(b) - w.pos += int64(n) - w.err = err - return n, err -} - -func (w *Writer) zero(n int64) (int64, error) { - if w.err != nil { - return 0, w.err - } - if w.pos+int64(n) > w.maxDiskSize { - w.err = exceededMaxSizeError{w.maxDiskSize} - return 0, w.err - } - n, err := io.CopyN(w.bw, zero, n) - w.pos += n - w.err = err - return n, err -} - -func (w *Writer) makeInode(f *File, node *inode) (*inode, error) { - mode := f.Mode - if mode&format.TypeMask == 0 { - mode |= format.S_IFREG - } - typ := mode & format.TypeMask - ino := format.InodeNumber(len(w.inodes) + 1) - if node == nil { - node = &inode{ - Number: ino, - } - if typ == S_IFDIR { - node.Children = make(directory) - node.LinkCount = 1 // A directory is linked to itself. - } - } else if node.Flags&format.InodeFlagExtents != 0 { - // Since we cannot deallocate or reuse blocks, don't allow updates that - // would invalidate data that has already been written. - return nil, errors.New("cannot overwrite file with non-inline data") - } - node.Mode = mode - node.Uid = f.Uid - node.Gid = f.Gid - node.Flags = format.InodeFlagHugeFile - node.Atime = timeToFsTime(f.Atime) - node.Ctime = timeToFsTime(f.Ctime) - node.Mtime = timeToFsTime(f.Mtime) - node.Crtime = timeToFsTime(f.Crtime) - node.Devmajor = f.Devmajor - node.Devminor = f.Devminor - node.Data = nil - node.XattrInline = nil - - var xstate xattrState - xstate.init() - - var size int64 - switch typ { - case format.S_IFREG: - size = f.Size - if f.Size > maxFileSize { - return nil, fmt.Errorf("file too big: %d > %d", f.Size, int64(maxFileSize)) - } - if f.Size <= inlineDataSize && w.supportInlineData { - node.Data = make([]byte, f.Size) - extra := 0 - if f.Size > inodeDataSize { - extra = int(f.Size - inodeDataSize) - } - // Add a dummy entry for now. - if !xstate.addXattr("system.data", node.Data[:extra]) { - panic("not enough room for inline data") - } - node.Flags |= format.InodeFlagInlineData - } - case format.S_IFLNK: - node.Mode |= 0777 // Symlinks should appear as ugw rwx - size = int64(len(f.Linkname)) - if size <= smallSymlinkSize { - // Special case: small symlinks go directly in Block without setting - // an inline data flag. - node.Data = make([]byte, len(f.Linkname)) - copy(node.Data, f.Linkname) - } - case format.S_IFDIR, format.S_IFIFO, format.S_IFSOCK, format.S_IFCHR, format.S_IFBLK: - default: - return nil, fmt.Errorf("invalid mode %o", mode) - } - - // Accumulate the extended attributes. - if len(f.Xattrs) != 0 { - // Sort the xattrs to avoid non-determinism in map iteration. - var xattrs []string - for name := range f.Xattrs { - xattrs = append(xattrs, name) - } - sort.Strings(xattrs) - for _, name := range xattrs { - if !xstate.addXattr(name, f.Xattrs[name]) { - return nil, fmt.Errorf("could not fit xattr %s", name) - } - } - } - - if err := w.writeXattrs(node, &xstate); err != nil { - return nil, err - } - - node.Size = size - if typ == format.S_IFLNK && size > smallSymlinkSize { - // Write the link name as data. - w.startInode("", node, size) - if _, err := w.Write([]byte(f.Linkname)); err != nil { - return nil, err - } - if err := w.finishInode(); err != nil { - return nil, err - } - } - - if int(node.Number-1) >= len(w.inodes) { - w.inodes = append(w.inodes, node) - } - return node, nil -} - -func (w *Writer) root() *inode { - return w.getInode(format.InodeRoot) -} - -func (w *Writer) lookup(name string, mustExist bool) (*inode, *inode, string, error) { - root := w.root() - cleanname := path.Clean("/" + name)[1:] - if len(cleanname) == 0 { - return root, root, "", nil - } - dirname, childname := path.Split(cleanname) - if len(childname) == 0 || len(childname) > 0xff { - return nil, nil, "", fmt.Errorf("%s: invalid name", name) - } - dir := w.findPath(root, dirname) - if dir == nil || !dir.IsDir() { - return nil, nil, "", fmt.Errorf("%s: path not found", name) - } - child := dir.Children[childname] - if child == nil && mustExist { - return nil, nil, "", fmt.Errorf("%s: file not found", name) - } - return dir, child, childname, nil -} - -// CreateWithParents adds a file to the file system creating the parent directories in the path if -// they don't exist (like `mkdir -p`). These non existing parent directories are created -// with the same permissions as that of it's parent directory. It is expected that the a -// call to make these parent directories will be made at a later point with the correct -// permissions, at that time the permissions of these directories will be updated. -func (w *Writer) CreateWithParents(name string, f *File) error { - // go through the directories in the path one by one and create the - // parent directories if they don't exist. - cleanname := path.Clean("/" + name)[1:] - parentDirs, _ := path.Split(cleanname) - currentPath := "" - root := w.root() - dirname := "" - for parentDirs != "" { - dirname, parentDirs = splitFirst(parentDirs) - currentPath += "/" + dirname - if _, ok := root.Children[dirname]; !ok { - f := &File{ - Mode: root.Mode, - Atime: time.Now(), - Mtime: time.Now(), - Ctime: time.Now(), - Crtime: time.Now(), - Size: 0, - Uid: root.Uid, - Gid: root.Gid, - Devmajor: root.Devmajor, - Devminor: root.Devminor, - Xattrs: make(map[string][]byte), - } - if err := w.Create(currentPath, f); err != nil { - return fmt.Errorf("failed while creating parent directories: %w", err) - } - } - root = root.Children[dirname] - } - return w.Create(name, f) -} - -// Create adds a file to the file system. -func (w *Writer) Create(name string, f *File) error { - if err := w.finishInode(); err != nil { - return err - } - dir, existing, childname, err := w.lookup(name, false) - if err != nil { - return err - } - var reuse *inode - if existing != nil { - if existing.IsDir() { - if f.Mode&TypeMask != S_IFDIR { - return fmt.Errorf("%s: cannot replace a directory with a file", name) - } - reuse = existing - } else if f.Mode&TypeMask == S_IFDIR { - return fmt.Errorf("%s: cannot replace a file with a directory", name) - } else if existing.LinkCount < 2 { - reuse = existing - } - } else { - if f.Mode&TypeMask == S_IFDIR && dir.LinkCount >= format.MaxLinks { - return fmt.Errorf("%s: exceeded parent directory maximum link count", name) - } - } - child, err := w.makeInode(f, reuse) - if err != nil { - return fmt.Errorf("%s: %s", name, err) - } - if existing != child { - if existing != nil { - existing.LinkCount-- - } - dir.Children[childname] = child - child.LinkCount++ - if child.IsDir() { - dir.LinkCount++ - } - } - if child.Mode&format.TypeMask == format.S_IFREG { - w.startInode(name, child, f.Size) - } - return nil -} - -// Link adds a hard link to the file system. -func (w *Writer) Link(oldname, newname string) error { - if err := w.finishInode(); err != nil { - return err - } - newdir, existing, newchildname, err := w.lookup(newname, false) - if err != nil { - return err - } - if existing != nil && (existing.IsDir() || existing.LinkCount < 2) { - return fmt.Errorf("%s: cannot orphan existing file or directory", newname) - } - - _, oldfile, _, err := w.lookup(oldname, true) - if err != nil { - return err - } - switch oldfile.Mode & format.TypeMask { - case format.S_IFDIR, format.S_IFLNK: - return fmt.Errorf("%s: link target cannot be a directory or symlink: %s", newname, oldname) - } - - if existing != oldfile && oldfile.LinkCount >= format.MaxLinks { - return fmt.Errorf("%s: link target would exceed maximum link count: %s", newname, oldname) - } - - if existing != nil { - existing.LinkCount-- - } - oldfile.LinkCount++ - newdir.Children[newchildname] = oldfile - return nil -} - -// Stat returns information about a file that has been written. -func (w *Writer) Stat(name string) (*File, error) { - if err := w.finishInode(); err != nil { - return nil, err - } - _, node, _, err := w.lookup(name, true) - if err != nil { - return nil, err - } - f := &File{ - Size: node.Size, - Mode: node.Mode, - Uid: node.Uid, - Gid: node.Gid, - Atime: fsTimeToTime(node.Atime), - Ctime: fsTimeToTime(node.Ctime), - Mtime: fsTimeToTime(node.Mtime), - Crtime: fsTimeToTime(node.Crtime), - Devmajor: node.Devmajor, - Devminor: node.Devminor, - } - f.Xattrs = make(map[string][]byte) - if node.XattrBlock != 0 || len(node.XattrInline) != 0 { - if node.XattrBlock != 0 { - orig := w.block() - w.seekBlock(node.XattrBlock) - if w.err != nil { - return nil, w.err - } - var b [blockSize]byte - _, err := w.f.Read(b[:]) - w.seekBlock(orig) - if err != nil { - return nil, err - } - getXattrs(b[32:], f.Xattrs, 32) - } - if len(node.XattrInline) != 0 { - getXattrs(node.XattrInline[4:], f.Xattrs, 0) - delete(f.Xattrs, "system.data") - } - } - if node.FileType() == S_IFLNK { - if node.Size > smallSymlinkSize { - return nil, fmt.Errorf("%s: cannot retrieve link information", name) - } - f.Linkname = string(node.Data) - } - return f, nil -} - -func (w *Writer) Write(b []byte) (int, error) { - if len(b) == 0 { - return 0, nil - } - if w.dataWritten+int64(len(b)) > w.dataMax { - return 0, fmt.Errorf("%s: wrote too much: %d > %d", w.curName, w.dataWritten+int64(len(b)), w.dataMax) - } - - if w.curInode.Flags&format.InodeFlagInlineData != 0 { - copy(w.curInode.Data[w.dataWritten:], b) - w.dataWritten += int64(len(b)) - return len(b), nil - } - - n, err := w.write(b) - w.dataWritten += int64(n) - return n, err -} - -func (w *Writer) startInode(name string, inode *inode, size int64) { - if w.curInode != nil { - panic("inode already in progress") - } - w.curName = name - w.curInode = inode - w.dataWritten = 0 - w.dataMax = size -} - -func (w *Writer) block() uint32 { - return uint32(w.pos / blockSize) -} - -func (w *Writer) seekBlock(block uint32) { - w.pos = int64(block) * blockSize - if w.err != nil { - return - } - w.err = w.bw.Flush() - if w.err != nil { - return - } - _, w.err = w.f.Seek(w.pos, io.SeekStart) -} - -func (w *Writer) nextBlock() { - if w.pos%blockSize != 0 { - // Simplify callers; w.err is updated on failure. - _, _ = w.zero(blockSize - w.pos%blockSize) - } -} - -func fillExtents(hdr *format.ExtentHeader, extents []format.ExtentLeafNode, startBlock, offset, inodeSize uint32) { - *hdr = format.ExtentHeader{ - Magic: format.ExtentHeaderMagic, - Entries: uint16(len(extents)), - Max: uint16(cap(extents)), - Depth: 0, - } - for i := range extents { - block := offset + uint32(i)*maxBlocksPerExtent - length := inodeSize - block - if length > maxBlocksPerExtent { - length = maxBlocksPerExtent - } - start := startBlock + block - extents[i] = format.ExtentLeafNode{ - Block: block, - Length: uint16(length), - StartLow: start, - } - } -} - -func (w *Writer) writeExtents(inode *inode) error { - start := w.pos - w.dataWritten - if start%blockSize != 0 { - panic("unaligned") - } - w.nextBlock() - - startBlock := uint32(start / blockSize) - blocks := w.block() - startBlock - usedBlocks := blocks - - const extentNodeSize = 12 - const extentsPerBlock = blockSize/extentNodeSize - 1 - - extents := (blocks + maxBlocksPerExtent - 1) / maxBlocksPerExtent - var b bytes.Buffer - if extents == 0 { - // Nothing to do. - } else if extents <= 4 { - var root struct { - hdr format.ExtentHeader - extents [4]format.ExtentLeafNode - } - fillExtents(&root.hdr, root.extents[:extents], startBlock, 0, blocks) - _ = binary.Write(&b, binary.LittleEndian, root) - } else if extents <= 4*extentsPerBlock { - const extentsPerBlock = blockSize/extentNodeSize - 1 - extentBlocks := extents/extentsPerBlock + 1 - usedBlocks += extentBlocks - var b2 bytes.Buffer - - var root struct { - hdr format.ExtentHeader - nodes [4]format.ExtentIndexNode - } - root.hdr = format.ExtentHeader{ - Magic: format.ExtentHeaderMagic, - Entries: uint16(extentBlocks), - Max: 4, - Depth: 1, - } - for i := uint32(0); i < extentBlocks; i++ { - root.nodes[i] = format.ExtentIndexNode{ - Block: i * extentsPerBlock * maxBlocksPerExtent, - LeafLow: w.block(), - } - extentsInBlock := extents - i*extentBlocks - if extentsInBlock > extentsPerBlock { - extentsInBlock = extentsPerBlock - } - - var node struct { - hdr format.ExtentHeader - extents [extentsPerBlock]format.ExtentLeafNode - _ [blockSize - (extentsPerBlock+1)*extentNodeSize]byte - } - - offset := i * extentsPerBlock * maxBlocksPerExtent - fillExtents(&node.hdr, node.extents[:extentsInBlock], startBlock+offset, offset, blocks) - _ = binary.Write(&b2, binary.LittleEndian, node) - if _, err := w.write(b2.Next(blockSize)); err != nil { - return err - } - } - _ = binary.Write(&b, binary.LittleEndian, root) - } else { - panic("file too big") - } - - inode.Data = b.Bytes() - inode.Flags |= format.InodeFlagExtents - inode.BlockCount += usedBlocks - return w.err -} - -func (w *Writer) finishInode() error { - if !w.initialized { - if err := w.init(); err != nil { - return err - } - } - if w.curInode == nil { - return nil - } - if w.dataWritten != w.dataMax { - return fmt.Errorf("did not write the right amount: %d != %d", w.dataWritten, w.dataMax) - } - - if w.dataMax != 0 && w.curInode.Flags&format.InodeFlagInlineData == 0 { - if err := w.writeExtents(w.curInode); err != nil { - return err - } - } - - w.dataWritten = 0 - w.dataMax = 0 - w.curInode = nil - return w.err -} - -func modeToFileType(mode uint16) format.FileType { - switch mode & format.TypeMask { - default: - return format.FileTypeUnknown - case format.S_IFREG: - return format.FileTypeRegular - case format.S_IFDIR: - return format.FileTypeDirectory - case format.S_IFCHR: - return format.FileTypeCharacter - case format.S_IFBLK: - return format.FileTypeBlock - case format.S_IFIFO: - return format.FileTypeFIFO - case format.S_IFSOCK: - return format.FileTypeSocket - case format.S_IFLNK: - return format.FileTypeSymbolicLink - } -} - -type constReader byte - -var zero = constReader(0) - -func (r constReader) Read(b []byte) (int, error) { - for i := range b { - b[i] = byte(r) - } - return len(b), nil -} - -func (w *Writer) writeDirectory(dir, parent *inode) error { - if err := w.finishInode(); err != nil { - return err - } - - // The size of the directory is not known yet. - w.startInode("", dir, 0x7fffffffffffffff) - left := blockSize - finishBlock := func() error { - if left > 0 { - e := format.DirectoryEntry{ - RecordLength: uint16(left), - } - err := binary.Write(w, binary.LittleEndian, e) - if err != nil { - return err - } - left -= directoryEntrySize - if left < 4 { - panic("not enough space for trailing entry") - } - _, err = io.CopyN(w, zero, int64(left)) - if err != nil { - return err - } - } - left = blockSize - return nil - } - - writeEntry := func(ino format.InodeNumber, name string) error { - rlb := directoryEntrySize + len(name) - rl := (rlb + 3) & ^3 - if left < rl+12 { - if err := finishBlock(); err != nil { - return err - } - } - e := format.DirectoryEntry{ - Inode: ino, - RecordLength: uint16(rl), - NameLength: uint8(len(name)), - FileType: modeToFileType(w.getInode(ino).Mode), - } - err := binary.Write(w, binary.LittleEndian, e) - if err != nil { - return err - } - _, err = w.Write([]byte(name)) - if err != nil { - return err - } - var zero [4]byte - _, err = w.Write(zero[:rl-rlb]) - if err != nil { - return err - } - left -= rl - return nil - } - if err := writeEntry(dir.Number, "."); err != nil { - return err - } - if err := writeEntry(parent.Number, ".."); err != nil { - return err - } - - // Follow e2fsck's convention and sort the children by inode number. - var children []string - for name := range dir.Children { - children = append(children, name) - } - sort.Slice(children, func(i, j int) bool { - left_num := dir.Children[children[i]].Number - right_num := dir.Children[children[j]].Number - - if left_num == right_num { - return children[i] < children[j] - } - return left_num < right_num - }) - - for _, name := range children { - child := dir.Children[name] - if err := writeEntry(child.Number, name); err != nil { - return err - } - } - if err := finishBlock(); err != nil { - return err - } - w.curInode.Size = w.dataWritten - w.dataMax = w.dataWritten - return nil -} - -func (w *Writer) writeDirectoryRecursive(dir, parent *inode) error { - if err := w.writeDirectory(dir, parent); err != nil { - return err - } - - // Follow e2fsck's convention and sort the children by inode number. - var children []string - for name := range dir.Children { - children = append(children, name) - } - sort.Slice(children, func(i, j int) bool { - left_num := dir.Children[children[i]].Number - right_num := dir.Children[children[j]].Number - - if left_num == right_num { - return children[i] < children[j] - } - return left_num < right_num - }) - - for _, name := range children { - child := dir.Children[name] - if child.IsDir() { - if err := w.writeDirectoryRecursive(child, dir); err != nil { - return err - } - } - } - return nil -} - -func (w *Writer) writeInodeTable(tableSize uint32) error { - var b bytes.Buffer - for _, inode := range w.inodes { - if inode != nil { - binode := format.Inode{ - Mode: inode.Mode, - Uid: uint16(inode.Uid & 0xffff), - Gid: uint16(inode.Gid & 0xffff), - SizeLow: uint32(inode.Size & 0xffffffff), - SizeHigh: uint32(inode.Size >> 32), - LinksCount: uint16(inode.LinkCount), - BlocksLow: inode.BlockCount, - Flags: inode.Flags, - XattrBlockLow: inode.XattrBlock, - UidHigh: uint16(inode.Uid >> 16), - GidHigh: uint16(inode.Gid >> 16), - ExtraIsize: uint16(inodeUsedSize - 128), - Atime: uint32(inode.Atime), - AtimeExtra: uint32(inode.Atime >> 32), - Ctime: uint32(inode.Ctime), - CtimeExtra: uint32(inode.Ctime >> 32), - Mtime: uint32(inode.Mtime), - MtimeExtra: uint32(inode.Mtime >> 32), - Crtime: uint32(inode.Crtime), - CrtimeExtra: uint32(inode.Crtime >> 32), - } - switch inode.Mode & format.TypeMask { - case format.S_IFDIR, format.S_IFREG, format.S_IFLNK: - n := copy(binode.Block[:], inode.Data) - if n < len(inode.Data) { - // Rewrite the first xattr with the data. - xattr := [1]xattr{{ - Name: "data", - Index: 7, // "system." - Value: inode.Data[n:], - }} - putXattrs(xattr[:], inode.XattrInline[4:], 0) - } - case format.S_IFBLK, format.S_IFCHR: - dev := inode.Devminor&0xff | inode.Devmajor<<8 | (inode.Devminor&0xffffff00)<<12 - binary.LittleEndian.PutUint32(binode.Block[4:], dev) - } - - _ = binary.Write(&b, binary.LittleEndian, binode) - b.Truncate(inodeUsedSize) - n, _ := b.Write(inode.XattrInline) - _, _ = io.CopyN(&b, zero, int64(inodeExtraSize-n)) - } else { - _, _ = io.CopyN(&b, zero, inodeSize) - } - if _, err := w.write(b.Next(inodeSize)); err != nil { - return err - } - } - rest := tableSize - uint32(len(w.inodes)*inodeSize) - if _, err := w.zero(int64(rest)); err != nil { - return err - } - return nil -} - -// NewWriter returns a Writer that writes an ext4 file system to the provided -// WriteSeeker. -func NewWriter(f io.ReadWriteSeeker, opts ...Option) *Writer { - w := &Writer{ - f: f, - bw: bufio.NewWriterSize(f, 65536*8), - maxDiskSize: defaultMaxDiskSize, - } - for _, opt := range opts { - opt(w) - } - return w -} - -// An Option provides extra options to NewWriter. -type Option func(*Writer) - -// InlineData instructs the Writer to write small files into the inode -// structures directly. This creates smaller images but currently is not -// compatible with DAX. -func InlineData(w *Writer) { - w.supportInlineData = true -} - -// MaximumDiskSize instructs the writer to reserve enough metadata space for the -// specified disk size. If not provided, then 16GB is the default. -func MaximumDiskSize(size int64) Option { - return func(w *Writer) { - if size < 0 || size > maxMaxDiskSize { - w.maxDiskSize = maxMaxDiskSize - } else if size == 0 { - w.maxDiskSize = defaultMaxDiskSize - } else { - w.maxDiskSize = (size + blockSize - 1) &^ (blockSize - 1) - } - } -} - -func (w *Writer) init() error { - // Skip the defective block inode. - w.inodes = make([]*inode, 1, 32) - // Create the root directory. - root, _ := w.makeInode(&File{ - Mode: format.S_IFDIR | 0755, - }, nil) - root.LinkCount++ // The root is linked to itself. - // Skip until the first non-reserved inode. - w.inodes = append(w.inodes, make([]*inode, inodeFirst-len(w.inodes)-1)...) - maxBlocks := (w.maxDiskSize-1)/blockSize + 1 - maxGroups := (maxBlocks-1)/blocksPerGroup + 1 - w.gdBlocks = uint32((maxGroups-1)/groupsPerDescriptorBlock + 1) - - // Skip past the superblock and block descriptor table. - w.seekBlock(1 + w.gdBlocks) - w.initialized = true - - // The lost+found directory is required to exist for e2fsck to pass. - if err := w.Create("lost+found", &File{Mode: format.S_IFDIR | 0700}); err != nil { - return err - } - return w.err -} - -func groupCount(blocks uint32, inodes uint32, inodesPerGroup uint32) uint32 { - inodeBlocksPerGroup := inodesPerGroup * inodeSize / blockSize - dataBlocksPerGroup := blocksPerGroup - inodeBlocksPerGroup - 2 // save room for the bitmaps - - // Increase the block count to ensure there are enough groups for all the - // inodes. - minBlocks := (inodes-1)/inodesPerGroup*dataBlocksPerGroup + 1 - if blocks < minBlocks { - blocks = minBlocks - } - - return (blocks + dataBlocksPerGroup - 1) / dataBlocksPerGroup -} - -func bestGroupCount(blocks uint32, inodes uint32) (groups uint32, inodesPerGroup uint32) { - groups = 0xffffffff - for ipg := uint32(inodesPerGroupIncrement); ipg <= maxInodesPerGroup; ipg += inodesPerGroupIncrement { - g := groupCount(blocks, inodes, ipg) - if g < groups { - groups = g - inodesPerGroup = ipg - } - } - return -} - -func (w *Writer) Close() error { - if err := w.finishInode(); err != nil { - return err - } - root := w.root() - if err := w.writeDirectoryRecursive(root, root); err != nil { - return err - } - // Finish the last inode (probably a directory). - if err := w.finishInode(); err != nil { - return err - } - - // Write the inode table - inodeTableOffset := w.block() - groups, inodesPerGroup := bestGroupCount(inodeTableOffset, uint32(len(w.inodes))) - err := w.writeInodeTable(groups * inodesPerGroup * inodeSize) - if err != nil { - return err - } - - // Write the bitmaps. - bitmapOffset := w.block() - bitmapSize := groups * 2 - validDataSize := bitmapOffset + bitmapSize - diskSize := validDataSize - minSize := (groups-1)*blocksPerGroup + 1 - if diskSize < minSize { - diskSize = minSize - } - - usedGdBlocks := (groups-1)/groupsPerDescriptorBlock + 1 - if usedGdBlocks > w.gdBlocks { - return exceededMaxSizeError{w.maxDiskSize} - } - - gds := make([]format.GroupDescriptor, w.gdBlocks*groupsPerDescriptorBlock) - inodeTableSizePerGroup := inodesPerGroup * inodeSize / blockSize - var totalUsedBlocks, totalUsedInodes uint32 - for g := uint32(0); g < groups; g++ { - var b [blockSize * 2]byte - var dirCount, usedInodeCount, usedBlockCount uint16 - - // Block bitmap - if (g+1)*blocksPerGroup <= validDataSize { - // This group is fully allocated. - for j := range b[:blockSize] { - b[j] = 0xff - } - usedBlockCount = blocksPerGroup - } else if g*blocksPerGroup < validDataSize { - for j := uint32(0); j < validDataSize-g*blocksPerGroup; j++ { - b[j/8] |= 1 << (j % 8) - usedBlockCount++ - } - } - if g == 0 { - // Unused group descriptor blocks should be cleared. - for j := 1 + usedGdBlocks; j < 1+w.gdBlocks; j++ { - b[j/8] &^= 1 << (j % 8) - usedBlockCount-- - } - } - if g == groups-1 && diskSize%blocksPerGroup != 0 { - // Blocks that aren't present in the disk should be marked as - // allocated. - for j := diskSize % blocksPerGroup; j < blocksPerGroup; j++ { - b[j/8] |= 1 << (j % 8) - usedBlockCount++ - } - } - // Inode bitmap - for j := uint32(0); j < inodesPerGroup; j++ { - ino := format.InodeNumber(1 + g*inodesPerGroup + j) - inode := w.getInode(ino) - if ino < inodeFirst || inode != nil { - b[blockSize+j/8] |= 1 << (j % 8) - usedInodeCount++ - } - if inode != nil && inode.Mode&format.TypeMask == format.S_IFDIR { - dirCount++ - } - } - _, err := w.write(b[:]) - if err != nil { - return err - } - gds[g] = format.GroupDescriptor{ - BlockBitmapLow: bitmapOffset + 2*g, - InodeBitmapLow: bitmapOffset + 2*g + 1, - InodeTableLow: inodeTableOffset + g*inodeTableSizePerGroup, - UsedDirsCountLow: dirCount, - FreeInodesCountLow: uint16(inodesPerGroup) - usedInodeCount, - FreeBlocksCountLow: blocksPerGroup - usedBlockCount, - } - - totalUsedBlocks += uint32(usedBlockCount) - totalUsedInodes += uint32(usedInodeCount) - } - - // Zero up to the disk size. - _, err = w.zero(int64(diskSize-bitmapOffset-bitmapSize) * blockSize) - if err != nil { - return err - } - - // Write the block descriptors - w.seekBlock(1) - if w.err != nil { - return w.err - } - err = binary.Write(w.bw, binary.LittleEndian, gds) - if err != nil { - return err - } - - // Write the super block - var blk [blockSize]byte - b := bytes.NewBuffer(blk[:1024]) - sb := &format.SuperBlock{ - InodesCount: inodesPerGroup * groups, - BlocksCountLow: diskSize, - FreeBlocksCountLow: blocksPerGroup*groups - totalUsedBlocks, - FreeInodesCount: inodesPerGroup*groups - totalUsedInodes, - FirstDataBlock: 0, - LogBlockSize: 2, // 2^(10 + 2) - LogClusterSize: 2, - BlocksPerGroup: blocksPerGroup, - ClustersPerGroup: blocksPerGroup, - InodesPerGroup: inodesPerGroup, - Magic: format.SuperBlockMagic, - State: 1, // cleanly unmounted - Errors: 1, // continue on error? - CreatorOS: 0, // Linux - RevisionLevel: 1, // dynamic inode sizes - FirstInode: inodeFirst, - LpfInode: inodeLostAndFound, - InodeSize: inodeSize, - FeatureCompat: format.CompatSparseSuper2 | format.CompatExtAttr, - FeatureIncompat: format.IncompatFiletype | format.IncompatExtents | format.IncompatFlexBg, - FeatureRoCompat: format.RoCompatLargeFile | format.RoCompatHugeFile | format.RoCompatExtraIsize | format.RoCompatReadonly, - MinExtraIsize: extraIsize, - WantExtraIsize: extraIsize, - LogGroupsPerFlex: 31, - } - if w.supportInlineData { - sb.FeatureIncompat |= format.IncompatInlineData - } - _ = binary.Write(b, binary.LittleEndian, sb) - w.seekBlock(0) - if _, err := w.write(blk[:]); err != nil { - return err - } - w.seekBlock(diskSize) - return w.err -} diff --git a/vendor/github.com/Microsoft/hcsshim/ext4/internal/format/format.go b/vendor/github.com/Microsoft/hcsshim/ext4/internal/format/format.go deleted file mode 100644 index 9dc4c4e164..0000000000 --- a/vendor/github.com/Microsoft/hcsshim/ext4/internal/format/format.go +++ /dev/null @@ -1,411 +0,0 @@ -package format - -type SuperBlock struct { - InodesCount uint32 - BlocksCountLow uint32 - RootBlocksCountLow uint32 - FreeBlocksCountLow uint32 - FreeInodesCount uint32 - FirstDataBlock uint32 - LogBlockSize uint32 - LogClusterSize uint32 - BlocksPerGroup uint32 - ClustersPerGroup uint32 - InodesPerGroup uint32 - Mtime uint32 - Wtime uint32 - MountCount uint16 - MaxMountCount uint16 - Magic uint16 - State uint16 - Errors uint16 - MinorRevisionLevel uint16 - LastCheck uint32 - CheckInterval uint32 - CreatorOS uint32 - RevisionLevel uint32 - DefaultReservedUid uint16 - DefaultReservedGid uint16 - FirstInode uint32 - InodeSize uint16 - BlockGroupNr uint16 - FeatureCompat CompatFeature - FeatureIncompat IncompatFeature - FeatureRoCompat RoCompatFeature - UUID [16]uint8 - VolumeName [16]byte - LastMounted [64]byte - AlgorithmUsageBitmap uint32 - PreallocBlocks uint8 - PreallocDirBlocks uint8 - ReservedGdtBlocks uint16 - JournalUUID [16]uint8 - JournalInum uint32 - JournalDev uint32 - LastOrphan uint32 - HashSeed [4]uint32 - DefHashVersion uint8 - JournalBackupType uint8 - DescSize uint16 - DefaultMountOpts uint32 - FirstMetaBg uint32 - MkfsTime uint32 - JournalBlocks [17]uint32 - BlocksCountHigh uint32 - RBlocksCountHigh uint32 - FreeBlocksCountHigh uint32 - MinExtraIsize uint16 - WantExtraIsize uint16 - Flags uint32 - RaidStride uint16 - MmpInterval uint16 - MmpBlock uint64 - RaidStripeWidth uint32 - LogGroupsPerFlex uint8 - ChecksumType uint8 - ReservedPad uint16 - KbytesWritten uint64 - SnapshotInum uint32 - SnapshotID uint32 - SnapshotRBlocksCount uint64 - SnapshotList uint32 - ErrorCount uint32 - FirstErrorTime uint32 - FirstErrorInode uint32 - FirstErrorBlock uint64 - FirstErrorFunc [32]uint8 - FirstErrorLine uint32 - LastErrorTime uint32 - LastErrorInode uint32 - LastErrorLine uint32 - LastErrorBlock uint64 - LastErrorFunc [32]uint8 - MountOpts [64]uint8 - UserQuotaInum uint32 - GroupQuotaInum uint32 - OverheadBlocks uint32 - BackupBgs [2]uint32 - EncryptAlgos [4]uint8 - EncryptPwSalt [16]uint8 - LpfInode uint32 - ProjectQuotaInum uint32 - ChecksumSeed uint32 - WtimeHigh uint8 - MtimeHigh uint8 - MkfsTimeHigh uint8 - LastcheckHigh uint8 - FirstErrorTimeHigh uint8 - LastErrorTimeHigh uint8 - Pad [2]uint8 - Reserved [96]uint32 - Checksum uint32 -} - -const SuperBlockMagic uint16 = 0xef53 - -type CompatFeature uint32 -type IncompatFeature uint32 -type RoCompatFeature uint32 - -const ( - CompatDirPrealloc CompatFeature = 0x1 - CompatImagicInodes CompatFeature = 0x2 - CompatHasJournal CompatFeature = 0x4 - CompatExtAttr CompatFeature = 0x8 - CompatResizeInode CompatFeature = 0x10 - CompatDirIndex CompatFeature = 0x20 - CompatLazyBg CompatFeature = 0x40 - CompatExcludeInode CompatFeature = 0x80 - CompatExcludeBitmap CompatFeature = 0x100 - CompatSparseSuper2 CompatFeature = 0x200 - - IncompatCompression IncompatFeature = 0x1 - IncompatFiletype IncompatFeature = 0x2 - IncompatRecover IncompatFeature = 0x4 - IncompatJournalDev IncompatFeature = 0x8 - IncompatMetaBg IncompatFeature = 0x10 - IncompatExtents IncompatFeature = 0x40 - Incompat_64Bit IncompatFeature = 0x80 - IncompatMmp IncompatFeature = 0x100 - IncompatFlexBg IncompatFeature = 0x200 - IncompatEaInode IncompatFeature = 0x400 - IncompatDirdata IncompatFeature = 0x1000 - IncompatCsumSeed IncompatFeature = 0x2000 - IncompatLargedir IncompatFeature = 0x4000 - IncompatInlineData IncompatFeature = 0x8000 - IncompatEncrypt IncompatFeature = 0x10000 - - RoCompatSparseSuper RoCompatFeature = 0x1 - RoCompatLargeFile RoCompatFeature = 0x2 - RoCompatBtreeDir RoCompatFeature = 0x4 - RoCompatHugeFile RoCompatFeature = 0x8 - RoCompatGdtCsum RoCompatFeature = 0x10 - RoCompatDirNlink RoCompatFeature = 0x20 - RoCompatExtraIsize RoCompatFeature = 0x40 - RoCompatHasSnapshot RoCompatFeature = 0x80 - RoCompatQuota RoCompatFeature = 0x100 - RoCompatBigalloc RoCompatFeature = 0x200 - RoCompatMetadataCsum RoCompatFeature = 0x400 - RoCompatReplica RoCompatFeature = 0x800 - RoCompatReadonly RoCompatFeature = 0x1000 - RoCompatProject RoCompatFeature = 0x2000 -) - -type BlockGroupFlag uint16 - -const ( - BlockGroupInodeUninit BlockGroupFlag = 0x1 - BlockGroupBlockUninit BlockGroupFlag = 0x2 - BlockGroupInodeZeroed BlockGroupFlag = 0x4 -) - -type GroupDescriptor struct { - BlockBitmapLow uint32 - InodeBitmapLow uint32 - InodeTableLow uint32 - FreeBlocksCountLow uint16 - FreeInodesCountLow uint16 - UsedDirsCountLow uint16 - Flags BlockGroupFlag - ExcludeBitmapLow uint32 - BlockBitmapCsumLow uint16 - InodeBitmapCsumLow uint16 - ItableUnusedLow uint16 - Checksum uint16 -} - -type GroupDescriptor64 struct { - GroupDescriptor - BlockBitmapHigh uint32 - InodeBitmapHigh uint32 - InodeTableHigh uint32 - FreeBlocksCountHigh uint16 - FreeInodesCountHigh uint16 - UsedDirsCountHigh uint16 - ItableUnusedHigh uint16 - ExcludeBitmapHigh uint32 - BlockBitmapCsumHigh uint16 - InodeBitmapCsumHigh uint16 - Reserved uint32 -} - -const ( - S_IXOTH = 0x1 - S_IWOTH = 0x2 - S_IROTH = 0x4 - S_IXGRP = 0x8 - S_IWGRP = 0x10 - S_IRGRP = 0x20 - S_IXUSR = 0x40 - S_IWUSR = 0x80 - S_IRUSR = 0x100 - S_ISVTX = 0x200 - S_ISGID = 0x400 - S_ISUID = 0x800 - S_IFIFO = 0x1000 - S_IFCHR = 0x2000 - S_IFDIR = 0x4000 - S_IFBLK = 0x6000 - S_IFREG = 0x8000 - S_IFLNK = 0xA000 - S_IFSOCK = 0xC000 - - TypeMask uint16 = 0xF000 -) - -type InodeNumber uint32 - -const ( - InodeRoot = 2 -) - -type Inode struct { - Mode uint16 - Uid uint16 - SizeLow uint32 - Atime uint32 - Ctime uint32 - Mtime uint32 - Dtime uint32 - Gid uint16 - LinksCount uint16 - BlocksLow uint32 - Flags InodeFlag - Version uint32 - Block [60]byte - Generation uint32 - XattrBlockLow uint32 - SizeHigh uint32 - ObsoleteFragmentAddr uint32 - BlocksHigh uint16 - XattrBlockHigh uint16 - UidHigh uint16 - GidHigh uint16 - ChecksumLow uint16 - Reserved uint16 - ExtraIsize uint16 - ChecksumHigh uint16 - CtimeExtra uint32 - MtimeExtra uint32 - AtimeExtra uint32 - Crtime uint32 - CrtimeExtra uint32 - VersionHigh uint32 - Projid uint32 -} - -type InodeFlag uint32 - -const ( - InodeFlagSecRm InodeFlag = 0x1 - InodeFlagUnRm InodeFlag = 0x2 - InodeFlagCompressed InodeFlag = 0x4 - InodeFlagSync InodeFlag = 0x8 - InodeFlagImmutable InodeFlag = 0x10 - InodeFlagAppend InodeFlag = 0x20 - InodeFlagNoDump InodeFlag = 0x40 - InodeFlagNoAtime InodeFlag = 0x80 - InodeFlagDirtyCompressed InodeFlag = 0x100 - InodeFlagCompressedClusters InodeFlag = 0x200 - InodeFlagNoCompress InodeFlag = 0x400 - InodeFlagEncrypted InodeFlag = 0x800 - InodeFlagHashedIndex InodeFlag = 0x1000 - InodeFlagMagic InodeFlag = 0x2000 - InodeFlagJournalData InodeFlag = 0x4000 - InodeFlagNoTail InodeFlag = 0x8000 - InodeFlagDirSync InodeFlag = 0x10000 - InodeFlagTopDir InodeFlag = 0x20000 - InodeFlagHugeFile InodeFlag = 0x40000 - InodeFlagExtents InodeFlag = 0x80000 - InodeFlagEaInode InodeFlag = 0x200000 - InodeFlagEOFBlocks InodeFlag = 0x400000 - InodeFlagSnapfile InodeFlag = 0x01000000 - InodeFlagSnapfileDeleted InodeFlag = 0x04000000 - InodeFlagSnapfileShrunk InodeFlag = 0x08000000 - InodeFlagInlineData InodeFlag = 0x10000000 - InodeFlagProjectIDInherit InodeFlag = 0x20000000 - InodeFlagReserved InodeFlag = 0x80000000 -) - -const ( - MaxLinks = 65000 -) - -type ExtentHeader struct { - Magic uint16 - Entries uint16 - Max uint16 - Depth uint16 - Generation uint32 -} - -const ExtentHeaderMagic uint16 = 0xf30a - -type ExtentIndexNode struct { - Block uint32 - LeafLow uint32 - LeafHigh uint16 - Unused uint16 -} - -type ExtentLeafNode struct { - Block uint32 - Length uint16 - StartHigh uint16 - StartLow uint32 -} - -type ExtentTail struct { - Checksum uint32 -} - -type DirectoryEntry struct { - Inode InodeNumber - RecordLength uint16 - NameLength uint8 - FileType FileType - //Name []byte -} - -type FileType uint8 - -const ( - FileTypeUnknown FileType = 0x0 - FileTypeRegular FileType = 0x1 - FileTypeDirectory FileType = 0x2 - FileTypeCharacter FileType = 0x3 - FileTypeBlock FileType = 0x4 - FileTypeFIFO FileType = 0x5 - FileTypeSocket FileType = 0x6 - FileTypeSymbolicLink FileType = 0x7 -) - -type DirectoryEntryTail struct { - ReservedZero1 uint32 - RecordLength uint16 - ReservedZero2 uint8 - FileType uint8 - Checksum uint32 -} - -type DirectoryTreeRoot struct { - Dot DirectoryEntry - DotName [4]byte - DotDot DirectoryEntry - DotDotName [4]byte - ReservedZero uint32 - HashVersion uint8 - InfoLength uint8 - IndirectLevels uint8 - UnusedFlags uint8 - Limit uint16 - Count uint16 - Block uint32 - //Entries []DirectoryTreeEntry -} - -type DirectoryTreeNode struct { - FakeInode uint32 - FakeRecordLength uint16 - NameLength uint8 - FileType uint8 - Limit uint16 - Count uint16 - Block uint32 - //Entries []DirectoryTreeEntry -} - -type DirectoryTreeEntry struct { - Hash uint32 - Block uint32 -} - -type DirectoryTreeTail struct { - Reserved uint32 - Checksum uint32 -} - -type XAttrInodeBodyHeader struct { - Magic uint32 -} - -type XAttrHeader struct { - Magic uint32 - ReferenceCount uint32 - Blocks uint32 - Hash uint32 - Checksum uint32 - Reserved [3]uint32 -} - -const XAttrHeaderMagic uint32 = 0xea020000 - -type XAttrEntry struct { - NameLength uint8 - NameIndex uint8 - ValueOffset uint16 - ValueInum uint32 - ValueSize uint32 - Hash uint32 - //Name []byte -} diff --git a/vendor/github.com/Microsoft/hcsshim/ext4/tar2ext4/tar2ext4.go b/vendor/github.com/Microsoft/hcsshim/ext4/tar2ext4/tar2ext4.go deleted file mode 100644 index 1aeae290b8..0000000000 --- a/vendor/github.com/Microsoft/hcsshim/ext4/tar2ext4/tar2ext4.go +++ /dev/null @@ -1,209 +0,0 @@ -package tar2ext4 - -import ( - "archive/tar" - "bufio" - "encoding/binary" - "io" - "os" - "path" - "strings" - - "github.com/Microsoft/hcsshim/ext4/internal/compactext4" - "github.com/Microsoft/hcsshim/ext4/internal/format" -) - -type params struct { - convertWhiteout bool - appendVhdFooter bool - ext4opts []compactext4.Option -} - -// Option is the type for optional parameters to Convert. -type Option func(*params) - -// ConvertWhiteout instructs the converter to convert OCI-style whiteouts -// (beginning with .wh.) to overlay-style whiteouts. -func ConvertWhiteout(p *params) { - p.convertWhiteout = true -} - -// AppendVhdFooter instructs the converter to add a fixed VHD footer to the -// file. -func AppendVhdFooter(p *params) { - p.appendVhdFooter = true -} - -// InlineData instructs the converter to write small files into the inode -// structures directly. This creates smaller images but currently is not -// compatible with DAX. -func InlineData(p *params) { - p.ext4opts = append(p.ext4opts, compactext4.InlineData) -} - -// MaximumDiskSize instructs the writer to limit the disk size to the specified -// value. This also reserves enough metadata space for the specified disk size. -// If not provided, then 16GB is the default. -func MaximumDiskSize(size int64) Option { - return func(p *params) { - p.ext4opts = append(p.ext4opts, compactext4.MaximumDiskSize(size)) - } -} - -const ( - whiteoutPrefix = ".wh." - opaqueWhiteout = ".wh..wh..opq" -) - -// Convert writes a compact ext4 file system image that contains the files in the -// input tar stream. -func Convert(r io.Reader, w io.ReadWriteSeeker, options ...Option) error { - var p params - for _, opt := range options { - opt(&p) - } - t := tar.NewReader(bufio.NewReader(r)) - fs := compactext4.NewWriter(w, p.ext4opts...) - for { - hdr, err := t.Next() - if err == io.EOF { - break - } - if err != nil { - return err - } - - if p.convertWhiteout { - dir, name := path.Split(hdr.Name) - if strings.HasPrefix(name, whiteoutPrefix) { - if name == opaqueWhiteout { - // Update the directory with the appropriate xattr. - f, err := fs.Stat(dir) - if err != nil { - return err - } - f.Xattrs["trusted.overlay.opaque"] = []byte("y") - err = fs.Create(dir, f) - if err != nil { - return err - } - } else { - // Create an overlay-style whiteout. - f := &compactext4.File{ - Mode: compactext4.S_IFCHR, - Devmajor: 0, - Devminor: 0, - } - err = fs.Create(path.Join(dir, name[len(whiteoutPrefix):]), f) - if err != nil { - return err - } - } - - continue - } - } - - if hdr.Typeflag == tar.TypeLink { - err = fs.Link(hdr.Linkname, hdr.Name) - if err != nil { - return err - } - } else { - f := &compactext4.File{ - Mode: uint16(hdr.Mode), - Atime: hdr.AccessTime, - Mtime: hdr.ModTime, - Ctime: hdr.ChangeTime, - Crtime: hdr.ModTime, - Size: hdr.Size, - Uid: uint32(hdr.Uid), - Gid: uint32(hdr.Gid), - Linkname: hdr.Linkname, - Devmajor: uint32(hdr.Devmajor), - Devminor: uint32(hdr.Devminor), - Xattrs: make(map[string][]byte), - } - for key, value := range hdr.PAXRecords { - const xattrPrefix = "SCHILY.xattr." - if strings.HasPrefix(key, xattrPrefix) { - f.Xattrs[key[len(xattrPrefix):]] = []byte(value) - } - } - - var typ uint16 - switch hdr.Typeflag { - case tar.TypeReg, tar.TypeRegA: - typ = compactext4.S_IFREG - case tar.TypeSymlink: - typ = compactext4.S_IFLNK - case tar.TypeChar: - typ = compactext4.S_IFCHR - case tar.TypeBlock: - typ = compactext4.S_IFBLK - case tar.TypeDir: - typ = compactext4.S_IFDIR - case tar.TypeFifo: - typ = compactext4.S_IFIFO - } - f.Mode &= ^compactext4.TypeMask - f.Mode |= typ - err = fs.CreateWithParents(hdr.Name, f) - if err != nil { - return err - } - _, err = io.Copy(fs, t) - if err != nil { - return err - } - } - } - err := fs.Close() - if err != nil { - return err - } - if p.appendVhdFooter { - size, err := w.Seek(0, io.SeekEnd) - if err != nil { - return err - } - err = binary.Write(w, binary.BigEndian, makeFixedVHDFooter(size)) - if err != nil { - return err - } - } - return nil -} - -// ReadExt4SuperBlock reads and returns ext4 super block from VHD -// -// The layout on disk is as follows: -// | Group 0 padding | - 1024 bytes -// | ext4 SuperBlock | - 1 block -// | Group Descriptors | - many blocks -// | Reserved GDT Blocks | - many blocks -// | Data Block Bitmap | - 1 block -// | inode Bitmap | - 1 block -// | inode Table | - many blocks -// | Data Blocks | - many blocks -// -// More details can be found here https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout -// -// Our goal is to skip the Group 0 padding, read and return the ext4 SuperBlock -func ReadExt4SuperBlock(vhdPath string) (*format.SuperBlock, error) { - vhd, err := os.OpenFile(vhdPath, os.O_RDONLY, 0) - if err != nil { - return nil, err - } - defer vhd.Close() - - // Skip padding at the start - if _, err := vhd.Seek(1024, io.SeekStart); err != nil { - return nil, err - } - var sb format.SuperBlock - if err := binary.Read(vhd, binary.LittleEndian, &sb); err != nil { - return nil, err - } - return &sb, nil -} diff --git a/vendor/github.com/Microsoft/hcsshim/ext4/tar2ext4/vhdfooter.go b/vendor/github.com/Microsoft/hcsshim/ext4/tar2ext4/vhdfooter.go deleted file mode 100644 index 99f6e3a304..0000000000 --- a/vendor/github.com/Microsoft/hcsshim/ext4/tar2ext4/vhdfooter.go +++ /dev/null @@ -1,76 +0,0 @@ -package tar2ext4 - -import ( - "bytes" - "crypto/rand" - "encoding/binary" -) - -// Constants for the VHD footer -const ( - cookieMagic = "conectix" - featureMask = 0x2 - fileFormatVersionMagic = 0x00010000 - fixedDataOffset = -1 - creatorVersionMagic = 0x000a0000 - diskTypeFixed = 2 -) - -type vhdFooter struct { - Cookie [8]byte - Features uint32 - FileFormatVersion uint32 - DataOffset int64 - TimeStamp uint32 - CreatorApplication [4]byte - CreatorVersion uint32 - CreatorHostOS [4]byte - OriginalSize int64 - CurrentSize int64 - DiskGeometry uint32 - DiskType uint32 - Checksum uint32 - UniqueID [16]uint8 - SavedState uint8 - Reserved [427]uint8 -} - -func makeFixedVHDFooter(size int64) *vhdFooter { - footer := &vhdFooter{ - Features: featureMask, - FileFormatVersion: fileFormatVersionMagic, - DataOffset: fixedDataOffset, - CreatorVersion: creatorVersionMagic, - OriginalSize: size, - CurrentSize: size, - DiskType: diskTypeFixed, - UniqueID: generateUUID(), - } - copy(footer.Cookie[:], cookieMagic) - footer.Checksum = calculateCheckSum(footer) - return footer -} - -func calculateCheckSum(footer *vhdFooter) uint32 { - oldchk := footer.Checksum - footer.Checksum = 0 - - buf := &bytes.Buffer{} - _ = binary.Write(buf, binary.BigEndian, footer) - - var chk uint32 - bufBytes := buf.Bytes() - for i := 0; i < len(bufBytes); i++ { - chk += uint32(bufBytes[i]) - } - footer.Checksum = oldchk - return uint32(^chk) -} - -func generateUUID() [16]byte { - res := [16]byte{} - if _, err := rand.Read(res[:]); err != nil { - panic(err) - } - return res -} diff --git a/vendor/github.com/Microsoft/opengcs/service/gcsutils/remotefs/defs.go b/vendor/github.com/Microsoft/opengcs/service/gcsutils/remotefs/defs.go deleted file mode 100644 index f1f2c04a45..0000000000 --- a/vendor/github.com/Microsoft/opengcs/service/gcsutils/remotefs/defs.go +++ /dev/null @@ -1,109 +0,0 @@ -package remotefs - -import ( - "errors" - "os" - "time" -) - -// RemotefsCmd is the name of the remotefs meta command -const RemotefsCmd = "remotefs" - -// Name of the commands when called from the cli context (remotefs ...) -const ( - StatCmd = "stat" - LstatCmd = "lstat" - ReadlinkCmd = "readlink" - MkdirCmd = "mkdir" - MkdirAllCmd = "mkdirall" - RemoveCmd = "remove" - RemoveAllCmd = "removeall" - LinkCmd = "link" - SymlinkCmd = "symlink" - LchmodCmd = "lchmod" - LchownCmd = "lchown" - MknodCmd = "mknod" - MkfifoCmd = "mkfifo" - OpenFileCmd = "openfile" - ReadFileCmd = "readfile" - WriteFileCmd = "writefile" - ReadDirCmd = "readdir" - ResolvePathCmd = "resolvepath" - ExtractArchiveCmd = "extractarchive" - ArchivePathCmd = "archivepath" -) - -// ErrInvalid is returned if the parameters are invalid -var ErrInvalid = errors.New("invalid arguments") - -// ErrUnknown is returned for an unknown remotefs command -var ErrUnknown = errors.New("unkown command") - -// ExportedError is the serialized version of the a Go error. -// It also provides a trivial implementation of the error interface. -type ExportedError struct { - ErrString string - ErrNum int `json:",omitempty"` -} - -// Error returns an error string -func (ee *ExportedError) Error() string { - return ee.ErrString -} - -// FileInfo is the stat struct returned by the remotefs system. It -// fulfills the os.FileInfo interface. -type FileInfo struct { - NameVar string - SizeVar int64 - ModeVar os.FileMode - ModTimeVar int64 // Serialization of time.Time breaks in travis, so use an int - IsDirVar bool -} - -var _ os.FileInfo = &FileInfo{} - -// Name returns the filename from a FileInfo structure -func (f *FileInfo) Name() string { return f.NameVar } - -// Size returns the size from a FileInfo structure -func (f *FileInfo) Size() int64 { return f.SizeVar } - -// Mode returns the mode from a FileInfo structure -func (f *FileInfo) Mode() os.FileMode { return f.ModeVar } - -// ModTime returns the modification time from a FileInfo structure -func (f *FileInfo) ModTime() time.Time { return time.Unix(0, f.ModTimeVar) } - -// IsDir returns the is-directory indicator from a FileInfo structure -func (f *FileInfo) IsDir() bool { return f.IsDirVar } - -// Sys provides an interface to a FileInfo structure -func (f *FileInfo) Sys() interface{} { return nil } - -// FileHeader is a header for remote *os.File operations for remotefs.OpenFile -type FileHeader struct { - Cmd uint32 - Size uint64 -} - -const ( - // Read request command. - Read uint32 = iota - // Write request command. - Write - // Seek request command. - Seek - // Close request command. - Close - // CmdOK is a response meaning request succeeded. - CmdOK - // CmdFailed is a response meaning request failed. - CmdFailed -) - -// SeekHeader is header for the Seek operation for remotefs.OpenFile -type SeekHeader struct { - Offset int64 - Whence int32 -} diff --git a/vendor/github.com/Microsoft/opengcs/service/gcsutils/remotefs/remotefs.go b/vendor/github.com/Microsoft/opengcs/service/gcsutils/remotefs/remotefs.go deleted file mode 100644 index ebc9e01df2..0000000000 --- a/vendor/github.com/Microsoft/opengcs/service/gcsutils/remotefs/remotefs.go +++ /dev/null @@ -1,578 +0,0 @@ -// +build !windows - -package remotefs - -import ( - "bytes" - "encoding/binary" - "encoding/json" - "io" - "os" - "path/filepath" - "strconv" - - "github.com/docker/docker/pkg/archive" - "github.com/docker/docker/pkg/symlink" - "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" -) - -// Func is the function definition for a generic remote fs function -// The input to the function is any serialized structs / data from in and the string slice -// from args. The output of the function will be serialized and written to out. -type Func func(stdin io.Reader, stdout io.Writer, args []string) error - -// Commands provide a string -> remotefs function mapping. -// This is useful for commandline programs that will receive a string -// as the function to execute. -var Commands = map[string]Func{ - StatCmd: Stat, - LstatCmd: Lstat, - ReadlinkCmd: Readlink, - MkdirCmd: Mkdir, - MkdirAllCmd: MkdirAll, - RemoveCmd: Remove, - RemoveAllCmd: RemoveAll, - LinkCmd: Link, - SymlinkCmd: Symlink, - LchmodCmd: Lchmod, - LchownCmd: Lchown, - MknodCmd: Mknod, - MkfifoCmd: Mkfifo, - OpenFileCmd: OpenFile, - ReadFileCmd: ReadFile, - WriteFileCmd: WriteFile, - ReadDirCmd: ReadDir, - ResolvePathCmd: ResolvePath, - ExtractArchiveCmd: ExtractArchive, - ArchivePathCmd: ArchivePath, -} - -// Stat functions like os.Stat. -// Args: -// - args[0] is the path -// Out: -// - out = FileInfo object -func Stat(in io.Reader, out io.Writer, args []string) error { - return stat(in, out, args, os.Stat) -} - -// Lstat functions like os.Lstat. -// Args: -// - args[0] is the path -// Out: -// - out = FileInfo object -func Lstat(in io.Reader, out io.Writer, args []string) error { - return stat(in, out, args, os.Lstat) -} - -func stat(in io.Reader, out io.Writer, args []string, statfunc func(string) (os.FileInfo, error)) error { - if len(args) < 1 { - return ErrInvalid - } - - fi, err := statfunc(args[0]) - if err != nil { - return err - } - - info := FileInfo{ - NameVar: fi.Name(), - SizeVar: fi.Size(), - ModeVar: fi.Mode(), - ModTimeVar: fi.ModTime().UnixNano(), - IsDirVar: fi.IsDir(), - } - - buf, err := json.Marshal(info) - if err != nil { - return err - } - - if _, err := out.Write(buf); err != nil { - return err - } - return nil -} - -// Readlink works like os.Readlink -// In: -// - args[0] is path -// Out: -// - Write link result to out -func Readlink(in io.Reader, out io.Writer, args []string) error { - if len(args) < 1 { - return ErrInvalid - } - - l, err := os.Readlink(args[0]) - if err != nil { - return err - } - - if _, err := out.Write([]byte(l)); err != nil { - return err - } - return nil -} - -// Mkdir works like os.Mkdir -// Args: -// - args[0] is the path -// - args[1] is the permissions in octal (like 0755) -func Mkdir(in io.Reader, out io.Writer, args []string) error { - return mkdir(in, out, args, os.Mkdir) -} - -// MkdirAll works like os.MkdirAll. -// Args: -// - args[0] is the path -// - args[1] is the permissions in octal (like 0755) -func MkdirAll(in io.Reader, out io.Writer, args []string) error { - return mkdir(in, out, args, os.MkdirAll) -} - -func mkdir(in io.Reader, out io.Writer, args []string, mkdirFunc func(string, os.FileMode) error) error { - if len(args) < 2 { - return ErrInvalid - } - - perm, err := strconv.ParseUint(args[1], 8, 32) - if err != nil { - return err - } - return mkdirFunc(args[0], os.FileMode(perm)) -} - -// Remove works like os.Remove -// Args: -// - args[0] is the path -func Remove(in io.Reader, out io.Writer, args []string) error { - return remove(in, out, args, os.Remove) -} - -// RemoveAll works like os.RemoveAll -// Args: -// - args[0] is the path -func RemoveAll(in io.Reader, out io.Writer, args []string) error { - return remove(in, out, args, os.RemoveAll) -} - -func remove(in io.Reader, out io.Writer, args []string, removefunc func(string) error) error { - if len(args) < 1 { - return ErrInvalid - } - return removefunc(args[0]) -} - -// Link works like os.Link -// Args: -// - args[0] = old path name (link source) -// - args[1] = new path name (link dest) -func Link(in io.Reader, out io.Writer, args []string) error { - return link(in, out, args, os.Link) -} - -// Symlink works like os.Symlink -// Args: -// - args[0] = old path name (link source) -// - args[1] = new path name (link dest) -func Symlink(in io.Reader, out io.Writer, args []string) error { - return link(in, out, args, os.Symlink) -} - -func link(in io.Reader, out io.Writer, args []string, linkfunc func(string, string) error) error { - if len(args) < 2 { - return ErrInvalid - } - return linkfunc(args[0], args[1]) -} - -// Lchmod changes permission of the given file without following symlinks -// Args: -// - args[0] = path -// - args[1] = permission mode in octal (like 0755) -func Lchmod(in io.Reader, out io.Writer, args []string) error { - if len(args) < 2 { - return ErrInvalid - } - - perm, err := strconv.ParseUint(args[1], 8, 32) - if err != nil { - return err - } - - path := args[0] - if !filepath.IsAbs(path) { - path, err = filepath.Abs(path) - if err != nil { - return err - } - } - return unix.Fchmodat(0, path, uint32(perm), unix.AT_SYMLINK_NOFOLLOW) -} - -// Lchown works like os.Lchown -// Args: -// - args[0] = path -// - args[1] = uid in base 10 -// - args[2] = gid in base 10 -func Lchown(in io.Reader, out io.Writer, args []string) error { - if len(args) < 3 { - return ErrInvalid - } - - uid, err := strconv.ParseInt(args[1], 10, 64) - if err != nil { - return err - } - - gid, err := strconv.ParseInt(args[2], 10, 64) - if err != nil { - return err - } - return os.Lchown(args[0], int(uid), int(gid)) -} - -// Mknod works like syscall.Mknod -// Args: -// - args[0] = path -// - args[1] = permission mode in octal (like 0755) -// - args[2] = major device number in base 10 -// - args[3] = minor device number in base 10 -func Mknod(in io.Reader, out io.Writer, args []string) error { - if len(args) < 4 { - return ErrInvalid - } - - perm, err := strconv.ParseUint(args[1], 8, 32) - if err != nil { - return err - } - - major, err := strconv.ParseInt(args[2], 10, 32) - if err != nil { - return err - } - - minor, err := strconv.ParseInt(args[3], 10, 32) - if err != nil { - return err - } - - dev := unix.Mkdev(uint32(major), uint32(minor)) - return unix.Mknod(args[0], uint32(perm), int(dev)) -} - -// Mkfifo creates a FIFO special file with the given path name and permissions -// Args: -// - args[0] = path -// - args[1] = permission mode in octal (like 0755) -func Mkfifo(in io.Reader, out io.Writer, args []string) error { - if len(args) < 2 { - return ErrInvalid - } - - perm, err := strconv.ParseUint(args[1], 8, 32) - if err != nil { - return err - } - return unix.Mkfifo(args[0], uint32(perm)) -} - -// OpenFile works like os.OpenFile. To manage the file pointer state, -// this function acts as a single file "file server" with Read/Write/Close -// being serialized control codes from in. -// Args: -// - args[0] = path -// - args[1] = flag in base 10 -// - args[2] = permission mode in octal (like 0755) -func OpenFile(in io.Reader, out io.Writer, args []string) (err error) { - logrus.Debugf("OpenFile: %v", args) - - defer func() { - if err != nil { - logrus.Errorf("OpenFile: return is non-nil, so writing cmdFailed back: %v", err) - // error code will be serialized by the caller, so don't write it here - WriteFileHeader(out, &FileHeader{Cmd: CmdFailed}, nil) - } - }() - - if len(args) < 3 { - logrus.Errorf("OpenFile: Not enough parameters") - return ErrInvalid - } - - flag, err := strconv.ParseInt(args[1], 10, 32) - if err != nil { - logrus.Errorf("OpenFile: Invalid flag: %v", err) - return err - } - - perm, err := strconv.ParseUint(args[2], 8, 32) - if err != nil { - logrus.Errorf("OpenFile: Invalid permission: %v", err) - return err - } - - f, err := os.OpenFile(args[0], int(flag), os.FileMode(perm)) - if err != nil { - logrus.Errorf("OpenFile: Failed to open: %v", err) - return err - } - - // Signal the client that OpenFile succeeded - logrus.Debugf("OpenFile: Sending OK header") - if err := WriteFileHeader(out, &FileHeader{Cmd: CmdOK}, nil); err != nil { - return err - } - - for { - logrus.Debugf("OpenFile: reading header") - hdr, err := ReadFileHeader(in) - if err != nil { - logrus.Errorf("OpenFile: Failed to ReadFileHeader: %v", err) - return err - } - logrus.Debugf("OpenFile: Header: %+v", hdr) - - var buf []byte - switch hdr.Cmd { - case Read: - logrus.Debugf("OpenFile: Read command") - buf = make([]byte, hdr.Size, hdr.Size) - n, err := f.Read(buf) - logrus.Debugf("OpenFile: Issued a read for %d, got %d bytes and error %v", hdr.Size, n, err) - if err != nil { - logrus.Errorf("OpenFile: Read failed: %v", err) - return err - } - buf = buf[:n] - case Write: - logrus.Debugf("OpenFile: Write command") - if _, err := io.CopyN(f, in, int64(hdr.Size)); err != nil { - logrus.Errorf("OpenFile: Write CopyN() failed: %v", err) - return err - } - case Seek: - logrus.Debugf("OpenFile: Seek command") - seekHdr := &SeekHeader{} - if err := binary.Read(in, binary.BigEndian, seekHdr); err != nil { - logrus.Errorf("OpenFile: Seek Read() failed: %v", err) - return err - } - res, err := f.Seek(seekHdr.Offset, int(seekHdr.Whence)) - if err != nil { - logrus.Errorf("OpenFile: Seek Seek() failed: %v", err) - return err - } - buffer := &bytes.Buffer{} - if err := binary.Write(buffer, binary.BigEndian, res); err != nil { - logrus.Errorf("OpenFile: Seek Write() failed: %v", err) - return err - } - buf = buffer.Bytes() - case Close: - logrus.Debugf("OpenFile: Close command") - if err := f.Close(); err != nil { - return err - } - default: - logrus.Errorf("OpenFile: unknown command") - return ErrUnknown - } - - logrus.Debugf("OpenFile: Writing back OK header of size %d", len(buf)) - retHdr := &FileHeader{ - Cmd: CmdOK, - Size: uint64(len(buf)), - } - if err := WriteFileHeader(out, retHdr, buf); err != nil { - logrus.Errorf("OpenFile: WriteFileHeader() failed: %v", err) - return err - } - - if hdr.Cmd == Close { - break - } - } - logrus.Debugf("OpenFile: Done, no error") - return nil -} - -// ReadFile works like ioutil.ReadFile but instead writes the file to a writer -// Args: -// - args[0] = path -// Out: -// - Write file contents to out -func ReadFile(in io.Reader, out io.Writer, args []string) error { - if len(args) < 1 { - return ErrInvalid - } - - f, err := os.Open(args[0]) - if err != nil { - return err - } - defer f.Close() - - if _, err := io.Copy(out, f); err != nil { - return nil - } - return nil -} - -// WriteFile works like ioutil.WriteFile but instead reads the file from a reader -// Args: -// - args[0] = path -// - args[1] = permission mode in octal (like 0755) -// - input data stream from in -func WriteFile(in io.Reader, out io.Writer, args []string) error { - if len(args) < 2 { - return ErrInvalid - } - - perm, err := strconv.ParseUint(args[1], 8, 32) - if err != nil { - return err - } - - f, err := os.OpenFile(args[0], os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(perm)) - if err != nil { - return err - } - defer f.Close() - - if _, err := io.Copy(f, in); err != nil { - return err - } - return nil -} - -// ReadDir works like *os.File.Readdir but instead writes the result to a writer -// Args: -// - args[0] = path -// - args[1] = number of directory entries to return. If <= 0, return all entries in directory -func ReadDir(in io.Reader, out io.Writer, args []string) error { - if len(args) < 2 { - return ErrInvalid - } - - n, err := strconv.ParseInt(args[1], 10, 32) - if err != nil { - return err - } - - f, err := os.Open(args[0]) - if err != nil { - return err - } - defer f.Close() - - infos, err := f.Readdir(int(n)) - if err != nil { - return err - } - - fileInfos := make([]FileInfo, len(infos)) - for i := range infos { - fileInfos[i] = FileInfo{ - NameVar: infos[i].Name(), - SizeVar: infos[i].Size(), - ModeVar: infos[i].Mode(), - ModTimeVar: infos[i].ModTime().UnixNano(), - IsDirVar: infos[i].IsDir(), - } - } - - buf, err := json.Marshal(fileInfos) - if err != nil { - return err - } - - if _, err := out.Write(buf); err != nil { - return err - } - return nil -} - -// ResolvePath works like docker's symlink.FollowSymlinkInScope. -// It takens in a `path` and a `root` and evaluates symlinks in `path` -// as if they were scoped in `root`. `path` must be a child path of `root`. -// In other words, `path` must have `root` as a prefix. -// Example: -// path=/foo/bar -> /baz -// root=/foo, -// Expected result = /foo/baz -// -// Args: -// - args[0] is `path` -// - args[1] is `root` -// Out: -// - Write resolved path to stdout -func ResolvePath(in io.Reader, out io.Writer, args []string) error { - if len(args) < 2 { - return ErrInvalid - } - res, err := symlink.FollowSymlinkInScope(args[0], args[1]) - if err != nil { - return err - } - if _, err = out.Write([]byte(res)); err != nil { - return err - } - return nil -} - -// ExtractArchive extracts the archive read from in. -// Args: -// - in = size of json | json of archive.TarOptions | input tar stream -// - args[0] = extract directory name -func ExtractArchive(in io.Reader, out io.Writer, args []string) error { - logrus.Debugln("ExtractArchive:", args) - if len(args) < 1 { - logrus.Errorln("ExtractArchive: invalid args") - return ErrInvalid - } - - opts, err := ReadTarOptions(in) - if err != nil { - logrus.Errorf("ExtractArchive: Failed to read tar options: %v", err) - return err - } - - logrus.Debugf("ExtractArchive: Tar options: %+v", opts) - if err := archive.Untar(in, args[0], opts); err != nil { - logrus.Errorf("ExtractArchive: Failed to Untar: %v", err) - return err - } - logrus.Debugf("ExtractArchive: Success") - return nil -} - -// ArchivePath archives the given directory and writes it to out. -// Args: -// - in = size of json | json of archive.TarOptions -// - args[0] = source directory name -// Out: -// - out = tar file of the archive -func ArchivePath(in io.Reader, out io.Writer, args []string) error { - if len(args) < 1 { - return ErrInvalid - } - - opts, err := ReadTarOptions(in) - if err != nil { - return err - } - - r, err := archive.TarWithOptions(args[0], opts) - if err != nil { - return err - } - - if _, err := io.Copy(out, r); err != nil { - return err - } - return nil -} diff --git a/vendor/github.com/Microsoft/opengcs/service/gcsutils/remotefs/utils.go b/vendor/github.com/Microsoft/opengcs/service/gcsutils/remotefs/utils.go deleted file mode 100644 index 727fd50e5a..0000000000 --- a/vendor/github.com/Microsoft/opengcs/service/gcsutils/remotefs/utils.go +++ /dev/null @@ -1,170 +0,0 @@ -package remotefs - -import ( - "bytes" - "encoding/binary" - "encoding/json" - "io" - "io/ioutil" - "os" - "syscall" - - "github.com/docker/docker/pkg/archive" -) - -// ReadError is an utility function that reads a serialized error from the given reader -// and deserializes it. -func ReadError(in io.Reader) (*ExportedError, error) { - b, err := ioutil.ReadAll(in) - if err != nil { - return nil, err - } - - // No error - if len(b) == 0 { - return nil, nil - } - - var exportedErr ExportedError - if err := json.Unmarshal(b, &exportedErr); err != nil { - return nil, err - } - - return &exportedErr, nil -} - -// ExportedToError will convert a ExportedError to an error. It will try to match -// the error to any existing known error like os.ErrNotExist. Otherwise, it will just -// return an implementation of the error interface. -func ExportedToError(ee *ExportedError) error { - if ee.Error() == os.ErrNotExist.Error() { - return os.ErrNotExist - } else if ee.Error() == os.ErrExist.Error() { - return os.ErrExist - } else if ee.Error() == os.ErrPermission.Error() { - return os.ErrPermission - } else if ee.Error() == io.EOF.Error() { - return io.EOF - } - return ee -} - -// WriteError is an utility function that serializes the error -// and writes it to the output writer. -func WriteError(err error, out io.Writer) error { - if err == nil { - return nil - } - err = fixOSError(err) - - var errno int - switch typedError := err.(type) { - case *os.PathError: - if se, ok := typedError.Err.(syscall.Errno); ok { - errno = int(se) - } - case *os.LinkError: - if se, ok := typedError.Err.(syscall.Errno); ok { - errno = int(se) - } - case *os.SyscallError: - if se, ok := typedError.Err.(syscall.Errno); ok { - errno = int(se) - } - } - - exportedError := &ExportedError{ - ErrString: err.Error(), - ErrNum: errno, - } - - b, err1 := json.Marshal(exportedError) - if err1 != nil { - return err1 - } - - _, err1 = out.Write(b) - if err1 != nil { - return err1 - } - return nil -} - -// fixOSError converts possible platform dependent error into the portable errors in the -// Go os package if possible. -func fixOSError(err error) error { - // The os.IsExist, os.IsNotExist, and os.IsPermissions functions are platform - // dependent, so sending the raw error might break those functions on a different OS. - // Go defines portable errors for these. - if os.IsExist(err) { - return os.ErrExist - } else if os.IsNotExist(err) { - return os.ErrNotExist - } else if os.IsPermission(err) { - return os.ErrPermission - } - return err -} - -// ReadTarOptions reads from the specified reader and deserializes an archive.TarOptions struct. -func ReadTarOptions(r io.Reader) (*archive.TarOptions, error) { - var size uint64 - if err := binary.Read(r, binary.BigEndian, &size); err != nil { - return nil, err - } - - rawJSON := make([]byte, size) - if _, err := io.ReadFull(r, rawJSON); err != nil { - return nil, err - } - - var opts archive.TarOptions - if err := json.Unmarshal(rawJSON, &opts); err != nil { - return nil, err - } - return &opts, nil -} - -// WriteTarOptions serializes a archive.TarOptions struct and writes it to the writer. -func WriteTarOptions(w io.Writer, opts *archive.TarOptions) error { - optsBuf, err := json.Marshal(opts) - if err != nil { - return err - } - - optsSize := uint64(len(optsBuf)) - optsSizeBuf := &bytes.Buffer{} - if err := binary.Write(optsSizeBuf, binary.BigEndian, optsSize); err != nil { - return err - } - - if _, err := optsSizeBuf.WriteTo(w); err != nil { - return err - } - - if _, err := w.Write(optsBuf); err != nil { - return err - } - - return nil -} - -// ReadFileHeader reads from r and returns a deserialized FileHeader -func ReadFileHeader(r io.Reader) (*FileHeader, error) { - hdr := &FileHeader{} - if err := binary.Read(r, binary.BigEndian, hdr); err != nil { - return nil, err - } - return hdr, nil -} - -// WriteFileHeader serializes a FileHeader and writes it to w, along with any extra data -func WriteFileHeader(w io.Writer, hdr *FileHeader, extraData []byte) error { - if err := binary.Write(w, binary.BigEndian, hdr); err != nil { - return err - } - if _, err := w.Write(extraData); err != nil { - return err - } - return nil -}