| //go:build windows | |
| // +build windows | |
| package windows // import "github.com/docker/docker/daemon/graphdriver/windows" | |
| import ( | |
| "archive/tar" | |
| "bufio" | |
| "bytes" | |
| "encoding/json" | |
| "fmt" | |
| "io" | |
| "os" | |
| "path" | |
| "path/filepath" | |
| "strconv" | |
| "strings" | |
| "sync" | |
| "time" | |
| winio "github.com/Microsoft/go-winio" | |
| "github.com/Microsoft/go-winio/backuptar" | |
| winiofs "github.com/Microsoft/go-winio/pkg/fs" | |
| "github.com/Microsoft/go-winio/vhd" | |
| "github.com/Microsoft/hcsshim" | |
| "github.com/Microsoft/hcsshim/osversion" | |
| "github.com/docker/docker/daemon/graphdriver" | |
| "github.com/docker/docker/pkg/archive" | |
| "github.com/docker/docker/pkg/idtools" | |
| "github.com/docker/docker/pkg/ioutils" | |
| "github.com/docker/docker/pkg/longpath" | |
| "github.com/docker/docker/pkg/reexec" | |
| "github.com/docker/docker/pkg/system" | |
| units "github.com/docker/go-units" | |
| "github.com/pkg/errors" | |
| "github.com/sirupsen/logrus" | |
| "golang.org/x/sys/windows" | |
| ) | |
| const ( | |
| // filterDriver is an HCSShim driver type for the Windows Filter driver. | |
| filterDriver = 1 | |
| // For WCOW, the default of 20GB hard-coded in the platform | |
| // is too small for builder scenarios where many users are | |
| // using RUN or COPY statements to install large amounts of data. | |
| // Use 127GB as that's the default size of a VHD in Hyper-V. | |
| defaultSandboxSize = "127GB" | |
| ) | |
| var ( | |
| // mutatedFiles is a list of files that are mutated by the import process | |
| // and must be backed up and restored. | |
| mutatedFiles = map[string]string{ | |
| "UtilityVM/Files/EFI/Microsoft/Boot/BCD": "bcd.bak", | |
| "UtilityVM/Files/EFI/Microsoft/Boot/BCD.LOG": "bcd.log.bak", | |
| "UtilityVM/Files/EFI/Microsoft/Boot/BCD.LOG1": "bcd.log1.bak", | |
| "UtilityVM/Files/EFI/Microsoft/Boot/BCD.LOG2": "bcd.log2.bak", | |
| } | |
| noreexec = false | |
| ) | |
| // init registers the windows graph drivers to the register. | |
| func init() { | |
| graphdriver.Register("windowsfilter", InitFilter) | |
| // DOCKER_WINDOWSFILTER_NOREEXEC allows for inline processing which makes | |
| // debugging issues in the re-exec codepath significantly easier. | |
| if os.Getenv("DOCKER_WINDOWSFILTER_NOREEXEC") != "" { | |
| logrus.Warnf("WindowsGraphDriver is set to not re-exec. This is intended for debugging purposes only.") | |
| noreexec = true | |
| } else { | |
| reexec.Register("docker-windows-write-layer", writeLayerReexec) | |
| } | |
| } | |
| type checker struct { | |
| } | |
| func (c *checker) IsMounted(path string) bool { | |
| return false | |
| } | |
| type storageOptions struct { | |
| size uint64 | |
| } | |
| // Driver represents a windows graph driver. | |
| type Driver struct { | |
| // info stores the shim driver information | |
| info hcsshim.DriverInfo | |
| ctr *graphdriver.RefCounter | |
| // it is safe for windows to use a cache here because it does not support | |
| // restoring containers when the daemon dies. | |
| cacheMu sync.Mutex | |
| cache map[string]string | |
| defaultStorageOpts *storageOptions | |
| } | |
| // InitFilter returns a new Windows storage filter driver. | |
| func InitFilter(home string, options []string, _ idtools.IdentityMapping) (graphdriver.Driver, error) { | |
| logrus.Debugf("WindowsGraphDriver InitFilter at %s", home) | |
| fsType, err := winiofs.GetFileSystemType(home) | |
| if err != nil { | |
| return nil, err | |
| } | |
| if strings.EqualFold(fsType, "refs") { | |
| return nil, fmt.Errorf("%s is on an ReFS volume - ReFS volumes are not supported", home) | |
| } | |
| // Setting file-mode is a no-op on Windows, so passing "0" to make it more | |
| // transparent that the filemode passed has no effect. | |
| if err = system.MkdirAll(home, 0); err != nil { | |
| return nil, errors.Wrapf(err, "windowsfilter failed to create '%s'", home) | |
| } | |
| storageOpt := map[string]string{ | |
| "size": defaultSandboxSize, | |
| } | |
| for _, o := range options { | |
| k, v, _ := strings.Cut(o, "=") | |
| storageOpt[strings.ToLower(k)] = v | |
| } | |
| opts, err := parseStorageOpt(storageOpt) | |
| if err != nil { | |
| return nil, errors.Wrap(err, "windowsfilter failed to parse default storage options") | |
| } | |
| d := &Driver{ | |
| info: hcsshim.DriverInfo{ | |
| HomeDir: home, | |
| Flavour: filterDriver, | |
| }, | |
| cache: make(map[string]string), | |
| ctr: graphdriver.NewRefCounter(&checker{}), | |
| defaultStorageOpts: opts, | |
| } | |
| return d, 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 "windowsfilter" | |
| } | |
| // Status returns the status of the driver. | |
| func (d *Driver) Status() [][2]string { | |
| return [][2]string{ | |
| {"Windows", ""}, | |
| } | |
| } | |
| // Exists returns true if the given id is registered with this driver. | |
| func (d *Driver) Exists(id string) bool { | |
| rID, err := d.resolveID(id) | |
| if err != nil { | |
| return false | |
| } | |
| result, err := hcsshim.LayerExists(d.info, rID) | |
| if err != nil { | |
| return false | |
| } | |
| return result | |
| } | |
| // CreateReadWrite creates a layer that is writable for use as a container | |
| // file system. | |
| func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error { | |
| if opts != nil { | |
| return d.create(id, parent, opts.MountLabel, false, opts.StorageOpt) | |
| } | |
| return d.create(id, parent, "", false, nil) | |
| } | |
| // Create creates a new read-only layer with the given id. | |
| func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error { | |
| if opts != nil { | |
| return d.create(id, parent, opts.MountLabel, true, opts.StorageOpt) | |
| } | |
| return d.create(id, parent, "", true, nil) | |
| } | |
| func (d *Driver) create(id, parent, mountLabel string, readOnly bool, storageOpt map[string]string) error { | |
| rPId, err := d.resolveID(parent) | |
| if err != nil { | |
| return err | |
| } | |
| parentChain, err := d.getLayerChain(rPId) | |
| if err != nil { | |
| return err | |
| } | |
| var layerChain []string | |
| if rPId != "" { | |
| parentPath, err := hcsshim.GetLayerMountPath(d.info, rPId) | |
| if err != nil { | |
| return err | |
| } | |
| if _, err := os.Stat(filepath.Join(parentPath, "Files")); err == nil { | |
| // This is a legitimate parent layer (not the empty "-init" layer), | |
| // so include it in the layer chain. | |
| layerChain = []string{parentPath} | |
| } | |
| } | |
| layerChain = append(layerChain, parentChain...) | |
| if readOnly { | |
| if err := hcsshim.CreateLayer(d.info, id, rPId); err != nil { | |
| return err | |
| } | |
| } else { | |
| var parentPath string | |
| if len(layerChain) != 0 { | |
| parentPath = layerChain[0] | |
| } | |
| if err := hcsshim.CreateSandboxLayer(d.info, id, parentPath, layerChain); err != nil { | |
| return err | |
| } | |
| storageOpts, err := parseStorageOpt(storageOpt) | |
| if err != nil { | |
| return errors.Wrap(err, "failed to parse storage options") | |
| } | |
| sandboxSize := d.defaultStorageOpts.size | |
| if storageOpts.size != 0 { | |
| sandboxSize = storageOpts.size | |
| } | |
| if sandboxSize != 0 { | |
| if err := hcsshim.ExpandSandboxSize(d.info, id, sandboxSize); err != nil { | |
| return err | |
| } | |
| } | |
| } | |
| if _, err := os.Lstat(d.dir(parent)); err != nil { | |
| if err2 := hcsshim.DestroyLayer(d.info, id); err2 != nil { | |
| logrus.Warnf("Failed to DestroyLayer %s: %s", id, err2) | |
| } | |
| return errors.Wrapf(err, "cannot create layer with missing parent %s", parent) | |
| } | |
| if err := d.setLayerChain(id, layerChain); err != nil { | |
| if err2 := hcsshim.DestroyLayer(d.info, id); err2 != nil { | |
| logrus.Warnf("Failed to DestroyLayer %s: %s", id, err2) | |
| } | |
| return err | |
| } | |
| return nil | |
| } | |
| // dir returns the absolute path to the layer. | |
| func (d *Driver) dir(id string) string { | |
| return filepath.Join(d.info.HomeDir, filepath.Base(id)) | |
| } | |
| // Remove unmounts and removes the dir information. | |
| func (d *Driver) Remove(id string) error { | |
| rID, err := d.resolveID(id) | |
| if err != nil { | |
| return err | |
| } | |
| // This retry loop is due to a bug in Windows (Internal bug #9432268) | |
| // if GetContainers fails with ErrVmcomputeOperationInvalidState | |
| // it is a transient error. Retry until it succeeds. | |
| var computeSystems []hcsshim.ContainerProperties | |
| retryCount := 0 | |
| for { | |
| // Get and terminate any template VMs that are currently using the layer. | |
| // Note: It is unfortunate that we end up in the graphdrivers Remove() call | |
| // for both containers and images, but the logic for template VMs is only | |
| // needed for images - specifically we are looking to see if a base layer | |
| // is in use by a template VM as a result of having started a Hyper-V | |
| // container at some point. | |
| // | |
| // We have a retry loop for ErrVmcomputeOperationInvalidState and | |
| // ErrVmcomputeOperationAccessIsDenied as there is a race condition | |
| // in RS1 and RS2 building during enumeration when a silo is going away | |
| // for example under it, in HCS. AccessIsDenied added to fix 30278. | |
| // | |
| // TODO: For RS3, we can remove the retries. Also consider | |
| // using platform APIs (if available) to get this more succinctly. Also | |
| // consider enhancing the Remove() interface to have context of why | |
| // the remove is being called - that could improve efficiency by not | |
| // enumerating compute systems during a remove of a container as it's | |
| // not required. | |
| computeSystems, err = hcsshim.GetContainers(hcsshim.ComputeSystemQuery{}) | |
| if err != nil { | |
| if osversion.Build() >= osversion.RS3 { | |
| return err | |
| } | |
| if (err == hcsshim.ErrVmcomputeOperationInvalidState) || (err == hcsshim.ErrVmcomputeOperationAccessIsDenied) { | |
| if retryCount >= 500 { | |
| break | |
| } | |
| retryCount++ | |
| time.Sleep(10 * time.Millisecond) | |
| continue | |
| } | |
| return err | |
| } | |
| break | |
| } | |
| for _, computeSystem := range computeSystems { | |
| if strings.Contains(computeSystem.RuntimeImagePath, id) && computeSystem.IsRuntimeTemplate { | |
| container, err := hcsshim.OpenContainer(computeSystem.ID) | |
| if err != nil { | |
| return err | |
| } | |
| err = container.Terminate() | |
| if hcsshim.IsPending(err) { | |
| err = container.Wait() | |
| } else if hcsshim.IsAlreadyStopped(err) { | |
| err = nil | |
| } | |
| _ = container.Close() | |
| if err != nil { | |
| return err | |
| } | |
| } | |
| } | |
| layerPath := filepath.Join(d.info.HomeDir, rID) | |
| tmpID := fmt.Sprintf("%s-removing", rID) | |
| tmpLayerPath := filepath.Join(d.info.HomeDir, tmpID) | |
| if err := os.Rename(layerPath, tmpLayerPath); err != nil && !os.IsNotExist(err) { | |
| if !os.IsPermission(err) { | |
| return err | |
| } | |
| // If permission denied, it's possible that the scratch is still mounted, an | |
| // artifact after a hard daemon crash for example. Worth a shot to try detaching it | |
| // before retrying the rename. | |
| sandbox := filepath.Join(layerPath, "sandbox.vhdx") | |
| if _, statErr := os.Stat(sandbox); statErr == nil { | |
| if detachErr := vhd.DetachVhd(sandbox); detachErr != nil { | |
| return errors.Wrapf(err, "failed to detach VHD: %s", detachErr) | |
| } | |
| if renameErr := os.Rename(layerPath, tmpLayerPath); renameErr != nil && !os.IsNotExist(renameErr) { | |
| return errors.Wrapf(err, "second rename attempt following detach failed: %s", renameErr) | |
| } | |
| } | |
| } | |
| if err := hcsshim.DestroyLayer(d.info, tmpID); err != nil { | |
| logrus.Errorf("Failed to DestroyLayer %s: %s", id, err) | |
| } | |
| return nil | |
| } | |
| // GetLayerPath gets the layer path on host | |
| func (d *Driver) GetLayerPath(id string) (string, error) { | |
| return d.dir(id), nil | |
| } | |
| // Get returns the rootfs path for the id. This will mount the dir at its given path. | |
| func (d *Driver) Get(id, mountLabel string) (string, error) { | |
| logrus.Debugf("WindowsGraphDriver Get() id %s mountLabel %s", id, mountLabel) | |
| var dir string | |
| rID, err := d.resolveID(id) | |
| if err != nil { | |
| return "", err | |
| } | |
| if count := d.ctr.Increment(rID); count > 1 { | |
| return d.cache[rID], nil | |
| } | |
| // Getting the layer paths must be done outside of the lock. | |
| layerChain, err := d.getLayerChain(rID) | |
| if err != nil { | |
| d.ctr.Decrement(rID) | |
| return "", err | |
| } | |
| if err := hcsshim.ActivateLayer(d.info, rID); err != nil { | |
| d.ctr.Decrement(rID) | |
| return "", err | |
| } | |
| if err := hcsshim.PrepareLayer(d.info, rID, layerChain); err != nil { | |
| d.ctr.Decrement(rID) | |
| if err2 := hcsshim.DeactivateLayer(d.info, rID); err2 != nil { | |
| logrus.Warnf("Failed to Deactivate %s: %s", id, err) | |
| } | |
| return "", err | |
| } | |
| mountPath, err := hcsshim.GetLayerMountPath(d.info, rID) | |
| if err != nil { | |
| d.ctr.Decrement(rID) | |
| if err := hcsshim.UnprepareLayer(d.info, rID); err != nil { | |
| logrus.Warnf("Failed to Unprepare %s: %s", id, err) | |
| } | |
| if err2 := hcsshim.DeactivateLayer(d.info, rID); err2 != nil { | |
| logrus.Warnf("Failed to Deactivate %s: %s", id, err) | |
| } | |
| return "", err | |
| } | |
| d.cacheMu.Lock() | |
| d.cache[rID] = mountPath | |
| d.cacheMu.Unlock() | |
| // If the layer has a mount path, use that. Otherwise, use the | |
| // folder path. | |
| if mountPath != "" { | |
| dir = mountPath | |
| } else { | |
| dir = d.dir(id) | |
| } | |
| return dir, nil | |
| } | |
| // Put adds a new layer to the driver. | |
| func (d *Driver) Put(id string) error { | |
| logrus.Debugf("WindowsGraphDriver Put() id %s", id) | |
| rID, err := d.resolveID(id) | |
| if err != nil { | |
| return err | |
| } | |
| if count := d.ctr.Decrement(rID); count > 0 { | |
| return nil | |
| } | |
| d.cacheMu.Lock() | |
| _, exists := d.cache[rID] | |
| delete(d.cache, rID) | |
| d.cacheMu.Unlock() | |
| // If the cache was not populated, then the layer was left unprepared and deactivated | |
| if !exists { | |
| return nil | |
| } | |
| if err := hcsshim.UnprepareLayer(d.info, rID); err != nil { | |
| return err | |
| } | |
| return hcsshim.DeactivateLayer(d.info, rID) | |
| } | |
| // 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 { | |
| items, err := os.ReadDir(d.info.HomeDir) | |
| 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 := hcsshim.DestroyLayer(d.info, item.Name()); err != nil { | |
| logrus.Warnf("Failed to cleanup %s: %s", item.Name(), err) | |
| } else { | |
| logrus.Infof("Cleaned up %s", item.Name()) | |
| } | |
| } | |
| } | |
| return nil | |
| } | |
| // Diff produces an archive of the changes between the specified | |
| // layer and its parent layer which may be "". | |
| // The layer should be mounted when calling this function | |
| func (d *Driver) Diff(id, _ string) (_ io.ReadCloser, err error) { | |
| rID, err := d.resolveID(id) | |
| if err != nil { | |
| return | |
| } | |
| layerChain, err := d.getLayerChain(rID) | |
| if err != nil { | |
| return | |
| } | |
| // this is assuming that the layer is unmounted | |
| if err := hcsshim.UnprepareLayer(d.info, rID); err != nil { | |
| return nil, err | |
| } | |
| prepare := func() { | |
| if err := hcsshim.PrepareLayer(d.info, rID, layerChain); err != nil { | |
| logrus.Warnf("Failed to Deactivate %s: %s", rID, err) | |
| } | |
| } | |
| arch, err := d.exportLayer(rID, layerChain) | |
| if err != nil { | |
| prepare() | |
| return | |
| } | |
| return ioutils.NewReadCloserWrapper(arch, func() error { | |
| err := arch.Close() | |
| prepare() | |
| return err | |
| }), 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, _ string) ([]archive.Change, error) { | |
| rID, err := d.resolveID(id) | |
| if err != nil { | |
| return nil, err | |
| } | |
| parentChain, err := d.getLayerChain(rID) | |
| if err != nil { | |
| return nil, err | |
| } | |
| if err := hcsshim.ActivateLayer(d.info, rID); err != nil { | |
| return nil, err | |
| } | |
| defer func() { | |
| if err2 := hcsshim.DeactivateLayer(d.info, rID); err2 != nil { | |
| logrus.Errorf("changes() failed to DeactivateLayer %s %s: %s", id, rID, err2) | |
| } | |
| }() | |
| var changes []archive.Change | |
| err = winio.RunWithPrivilege(winio.SeBackupPrivilege, func() error { | |
| r, err := hcsshim.NewLayerReader(d.info, id, parentChain) | |
| if err != nil { | |
| return err | |
| } | |
| defer r.Close() | |
| for { | |
| name, _, fileInfo, err := r.Next() | |
| if err == io.EOF { | |
| return nil | |
| } | |
| if err != nil { | |
| return err | |
| } | |
| name = filepath.ToSlash(name) | |
| if fileInfo == nil { | |
| changes = append(changes, archive.Change{Path: name, Kind: archive.ChangeDelete}) | |
| } else { | |
| // Currently there is no way to tell between an add and a modify. | |
| changes = append(changes, archive.Change{Path: name, Kind: archive.ChangeModify}) | |
| } | |
| } | |
| }) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return changes, 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 | |
| func (d *Driver) ApplyDiff(id, parent string, diff io.Reader) (int64, error) { | |
| var layerChain []string | |
| if parent != "" { | |
| rPId, err := d.resolveID(parent) | |
| if err != nil { | |
| return 0, err | |
| } | |
| parentChain, err := d.getLayerChain(rPId) | |
| if err != nil { | |
| return 0, err | |
| } | |
| parentPath, err := hcsshim.GetLayerMountPath(d.info, rPId) | |
| if err != nil { | |
| return 0, err | |
| } | |
| layerChain = append(layerChain, parentPath) | |
| layerChain = append(layerChain, parentChain...) | |
| } | |
| size, err := d.importLayer(id, diff, layerChain) | |
| if err != nil { | |
| return 0, err | |
| } | |
| if err = d.setLayerChain(id, layerChain); err != nil { | |
| return 0, err | |
| } | |
| return size, 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) { | |
| rPId, err := d.resolveID(parent) | |
| if err != nil { | |
| return | |
| } | |
| changes, err := d.Changes(id, rPId) | |
| if err != nil { | |
| return | |
| } | |
| layerFs, err := d.Get(id, "") | |
| if err != nil { | |
| return | |
| } | |
| defer d.Put(id) | |
| return archive.ChangesSize(layerFs, changes), nil | |
| } | |
| // GetMetadata returns custom driver information. | |
| func (d *Driver) GetMetadata(id string) (map[string]string, error) { | |
| return map[string]string{"dir": d.dir(id)}, nil | |
| } | |
| func writeTarFromLayer(r hcsshim.LayerReader, w io.Writer) error { | |
| t := tar.NewWriter(w) | |
| for { | |
| name, size, fileInfo, err := r.Next() | |
| if err == io.EOF { | |
| break | |
| } | |
| if err != nil { | |
| return err | |
| } | |
| if fileInfo == nil { | |
| // Write a whiteout file. | |
| err = t.WriteHeader(&tar.Header{ | |
| Name: filepath.ToSlash(filepath.Join(filepath.Dir(name), archive.WhiteoutPrefix+filepath.Base(name))), | |
| }) | |
| if err != nil { | |
| return err | |
| } | |
| } else { | |
| err = backuptar.WriteTarFileFromBackupStream(t, r, name, size, fileInfo) | |
| if err != nil { | |
| return err | |
| } | |
| } | |
| } | |
| return t.Close() | |
| } | |
| // exportLayer generates an archive from a layer based on the given ID. | |
| func (d *Driver) exportLayer(id string, parentLayerPaths []string) (io.ReadCloser, error) { | |
| archiveRdr, w := io.Pipe() | |
| go func() { | |
| err := winio.RunWithPrivilege(winio.SeBackupPrivilege, func() error { | |
| r, err := hcsshim.NewLayerReader(d.info, id, parentLayerPaths) | |
| if err != nil { | |
| return err | |
| } | |
| err = writeTarFromLayer(r, w) | |
| cerr := r.Close() | |
| if err == nil { | |
| err = cerr | |
| } | |
| return err | |
| }) | |
| w.CloseWithError(err) | |
| }() | |
| return archiveRdr, nil | |
| } | |
| // writeBackupStreamFromTarAndSaveMutatedFiles reads data from a tar stream and | |
| // writes it to a backup stream, and also saves any files that will be mutated | |
| // by the import layer process to a backup location. | |
| func writeBackupStreamFromTarAndSaveMutatedFiles(buf *bufio.Writer, w io.Writer, t *tar.Reader, hdr *tar.Header, root string) (nextHdr *tar.Header, err error) { | |
| var bcdBackup *os.File | |
| var bcdBackupWriter *winio.BackupFileWriter | |
| if backupPath, ok := mutatedFiles[hdr.Name]; ok { | |
| bcdBackup, err = os.Create(filepath.Join(root, backupPath)) | |
| if err != nil { | |
| return nil, err | |
| } | |
| defer func() { | |
| cerr := bcdBackup.Close() | |
| if err == nil { | |
| err = cerr | |
| } | |
| }() | |
| bcdBackupWriter = winio.NewBackupFileWriter(bcdBackup, false) | |
| defer func() { | |
| cerr := bcdBackupWriter.Close() | |
| if err == nil { | |
| err = cerr | |
| } | |
| }() | |
| buf.Reset(io.MultiWriter(w, bcdBackupWriter)) | |
| } else { | |
| buf.Reset(w) | |
| } | |
| defer func() { | |
| ferr := buf.Flush() | |
| if err == nil { | |
| err = ferr | |
| } | |
| }() | |
| return backuptar.WriteBackupStreamFromTarFile(buf, t, hdr) | |
| } | |
| func writeLayerFromTar(r io.Reader, w hcsshim.LayerWriter, root string) (int64, error) { | |
| t := tar.NewReader(r) | |
| hdr, err := t.Next() | |
| totalSize := int64(0) | |
| buf := bufio.NewWriter(nil) | |
| for err == nil { | |
| base := path.Base(hdr.Name) | |
| if strings.HasPrefix(base, archive.WhiteoutPrefix) { | |
| name := path.Join(path.Dir(hdr.Name), base[len(archive.WhiteoutPrefix):]) | |
| err = w.Remove(filepath.FromSlash(name)) | |
| if err != nil { | |
| return 0, err | |
| } | |
| hdr, err = t.Next() | |
| } else if hdr.Typeflag == tar.TypeLink { | |
| err = w.AddLink(filepath.FromSlash(hdr.Name), filepath.FromSlash(hdr.Linkname)) | |
| if err != nil { | |
| return 0, err | |
| } | |
| hdr, err = t.Next() | |
| } else { | |
| var ( | |
| name string | |
| size int64 | |
| fileInfo *winio.FileBasicInfo | |
| ) | |
| name, size, fileInfo, err = backuptar.FileInfoFromHeader(hdr) | |
| if err != nil { | |
| return 0, err | |
| } | |
| err = w.Add(filepath.FromSlash(name), fileInfo) | |
| if err != nil { | |
| return 0, err | |
| } | |
| hdr, err = writeBackupStreamFromTarAndSaveMutatedFiles(buf, w, t, hdr, root) | |
| totalSize += size | |
| } | |
| } | |
| if err != io.EOF { | |
| return 0, err | |
| } | |
| return totalSize, nil | |
| } | |
| // importLayer adds a new layer to the tag and graph store based on the given data. | |
| func (d *Driver) importLayer(id string, layerData io.Reader, parentLayerPaths []string) (size int64, err error) { | |
| if !noreexec { | |
| cmd := reexec.Command(append([]string{"docker-windows-write-layer", d.info.HomeDir, id}, parentLayerPaths...)...) | |
| output := bytes.NewBuffer(nil) | |
| cmd.Stdin = layerData | |
| cmd.Stdout = output | |
| cmd.Stderr = output | |
| if err = cmd.Start(); err != nil { | |
| return | |
| } | |
| if err = cmd.Wait(); err != nil { | |
| return 0, fmt.Errorf("re-exec error: %v: output: %s", err, output) | |
| } | |
| return strconv.ParseInt(output.String(), 10, 64) | |
| } | |
| return writeLayer(layerData, d.info.HomeDir, id, parentLayerPaths...) | |
| } | |
| // writeLayerReexec is the re-exec entry point for writing a layer from a tar file | |
| func writeLayerReexec() { | |
| size, err := writeLayer(os.Stdin, os.Args[1], os.Args[2], os.Args[3:]...) | |
| if err != nil { | |
| fmt.Fprint(os.Stderr, err) | |
| os.Exit(1) | |
| } | |
| fmt.Fprint(os.Stdout, size) | |
| } | |
| // writeLayer writes a layer from a tar file. | |
| func writeLayer(layerData io.Reader, home string, id string, parentLayerPaths ...string) (size int64, retErr error) { | |
| err := winio.EnableProcessPrivileges([]string{winio.SeSecurityPrivilege, winio.SeBackupPrivilege, winio.SeRestorePrivilege}) | |
| if err != nil { | |
| return 0, err | |
| } | |
| if noreexec { | |
| defer func() { | |
| if err := winio.DisableProcessPrivileges([]string{winio.SeSecurityPrivilege, winio.SeBackupPrivilege, winio.SeRestorePrivilege}); err != nil { | |
| // This should never happen, but just in case when in debugging mode. | |
| // See https://github.com/docker/docker/pull/28002#discussion_r86259241 for rationale. | |
| panic("Failed to disabled process privileges while in non re-exec mode") | |
| } | |
| }() | |
| } | |
| w, err := hcsshim.NewLayerWriter(hcsshim.DriverInfo{Flavour: filterDriver, HomeDir: home}, id, parentLayerPaths) | |
| if err != nil { | |
| return 0, err | |
| } | |
| defer func() { | |
| if err := w.Close(); err != nil { | |
| // This error should not be discarded as a failure here | |
| // could result in an invalid layer on disk | |
| if retErr == nil { | |
| retErr = err | |
| } | |
| } | |
| }() | |
| return writeLayerFromTar(layerData, w, filepath.Join(home, id)) | |
| } | |
| // resolveID computes the layerID information based on the given id. | |
| func (d *Driver) resolveID(id string) (string, error) { | |
| content, err := os.ReadFile(filepath.Join(d.dir(id), "layerID")) | |
| if os.IsNotExist(err) { | |
| return id, nil | |
| } else if err != nil { | |
| return "", err | |
| } | |
| return string(content), nil | |
| } | |
| // setID stores the layerId in disk. | |
| func (d *Driver) setID(id, altID string) error { | |
| return os.WriteFile(filepath.Join(d.dir(id), "layerId"), []byte(altID), 0600) | |
| } | |
| // getLayerChain returns the layer chain information. | |
| func (d *Driver) getLayerChain(id string) ([]string, error) { | |
| jPath := filepath.Join(d.dir(id), "layerchain.json") | |
| content, err := os.ReadFile(jPath) | |
| if os.IsNotExist(err) { | |
| return nil, nil | |
| } else if err != nil { | |
| return nil, errors.Wrapf(err, "read layerchain file") | |
| } | |
| var layerChain []string | |
| err = json.Unmarshal(content, &layerChain) | |
| if err != nil { | |
| return nil, errors.Wrapf(err, "failed to unmarshal layerchain JSON") | |
| } | |
| return layerChain, nil | |
| } | |
| // setLayerChain stores the layer chain information in disk. | |
| func (d *Driver) setLayerChain(id string, chain []string) error { | |
| content, err := json.Marshal(&chain) | |
| if err != nil { | |
| return errors.Wrap(err, "failed to marshal layerchain JSON") | |
| } | |
| jPath := filepath.Join(d.dir(id), "layerchain.json") | |
| err = os.WriteFile(jPath, content, 0o600) | |
| if err != nil { | |
| return errors.Wrap(err, "write layerchain file") | |
| } | |
| return nil | |
| } | |
| type fileGetCloserWithBackupPrivileges struct { | |
| path string | |
| } | |
| func (fg *fileGetCloserWithBackupPrivileges) Get(filename string) (io.ReadCloser, error) { | |
| if backupPath, ok := mutatedFiles[filename]; ok { | |
| return os.Open(filepath.Join(fg.path, backupPath)) | |
| } | |
| var f *os.File | |
| // Open the file while holding the Windows backup privilege. This ensures that the | |
| // file can be opened even if the caller does not actually have access to it according | |
| // to the security descriptor. Also use sequential file access to avoid depleting the | |
| // standby list - Microsoft VSO Bug Tracker #9900466 | |
| err := winio.RunWithPrivilege(winio.SeBackupPrivilege, func() error { | |
| longPath := longpath.AddPrefix(filepath.Join(fg.path, filename)) | |
| p, err := windows.UTF16FromString(longPath) | |
| if err != nil { | |
| return err | |
| } | |
| h, err := windows.CreateFile(&p[0], windows.GENERIC_READ, windows.FILE_SHARE_READ, nil, windows.OPEN_EXISTING, windows.FILE_FLAG_BACKUP_SEMANTICS|windows.FILE_FLAG_SEQUENTIAL_SCAN, 0) | |
| if err != nil { | |
| return &os.PathError{Op: "open", Path: longPath, Err: err} | |
| } | |
| f = os.NewFile(uintptr(h), longPath) | |
| return nil | |
| }) | |
| return f, err | |
| } | |
| func (fg *fileGetCloserWithBackupPrivileges) Close() error { | |
| return 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) { | |
| id, err := d.resolveID(id) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return &fileGetCloserWithBackupPrivileges{d.dir(id)}, nil | |
| } | |
| func parseStorageOpt(storageOpt map[string]string) (*storageOptions, error) { | |
| options := &storageOptions{} | |
| // Read size to change the block device size per container. | |
| for key, val := range storageOpt { | |
| // FIXME(thaJeztah): options should not be case-insensitive | |
| if strings.EqualFold(key, "size") { | |
| size, err := units.RAMInBytes(val) | |
| if err != nil { | |
| return nil, err | |
| } | |
| options.size = uint64(size) | |
| } | |
| } | |
| return options, nil | |
| } |