github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/daemon/archive_windows.go (about)

     1  package daemon // import "github.com/docker/docker/daemon"
     2  
     3  import (
     4  	"errors"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/docker/docker/api/types"
    11  	containertypes "github.com/docker/docker/api/types/container"
    12  	"github.com/docker/docker/container"
    13  	"github.com/docker/docker/errdefs"
    14  	"github.com/docker/docker/pkg/archive"
    15  	"github.com/docker/docker/pkg/chrootarchive"
    16  	"github.com/docker/docker/pkg/ioutils"
    17  	"github.com/docker/docker/pkg/system"
    18  )
    19  
    20  // containerStatPath stats the filesystem resource at the specified path in this
    21  // container. Returns stat info about the resource.
    22  func (daemon *Daemon) containerStatPath(container *container.Container, path string) (stat *types.ContainerPathStat, err error) {
    23  	container.Lock()
    24  	defer container.Unlock()
    25  
    26  	// Make sure an online file-system operation is permitted.
    27  	if err := daemon.isOnlineFSOperationPermitted(container); err != nil {
    28  		return nil, err
    29  	}
    30  
    31  	if err = daemon.Mount(container); err != nil {
    32  		return nil, err
    33  	}
    34  	defer daemon.Unmount(container)
    35  
    36  	err = daemon.mountVolumes(container)
    37  	defer container.DetachAndUnmount(daemon.LogVolumeEvent)
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  
    42  	// Normalize path before sending to rootfs
    43  	path = filepath.FromSlash(path)
    44  
    45  	resolvedPath, absPath, err := container.ResolvePath(path)
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  
    50  	return container.StatPath(resolvedPath, absPath)
    51  }
    52  
    53  // containerArchivePath creates an archive of the filesystem resource at the specified
    54  // path in this container. Returns a tar archive of the resource and stat info
    55  // about the resource.
    56  func (daemon *Daemon) containerArchivePath(container *container.Container, path string) (content io.ReadCloser, stat *types.ContainerPathStat, err error) {
    57  	container.Lock()
    58  
    59  	defer func() {
    60  		if err != nil {
    61  			// Wait to unlock the container until the archive is fully read
    62  			// (see the ReadCloseWrapper func below) or if there is an error
    63  			// before that occurs.
    64  			container.Unlock()
    65  		}
    66  	}()
    67  
    68  	// Make sure an online file-system operation is permitted.
    69  	if err := daemon.isOnlineFSOperationPermitted(container); err != nil {
    70  		return nil, nil, err
    71  	}
    72  
    73  	if err = daemon.Mount(container); err != nil {
    74  		return nil, nil, err
    75  	}
    76  
    77  	defer func() {
    78  		if err != nil {
    79  			// unmount any volumes
    80  			container.DetachAndUnmount(daemon.LogVolumeEvent)
    81  			// unmount the container's rootfs
    82  			daemon.Unmount(container)
    83  		}
    84  	}()
    85  
    86  	if err = daemon.mountVolumes(container); err != nil {
    87  		return nil, nil, err
    88  	}
    89  
    90  	// Normalize path before sending to rootfs
    91  	path = filepath.FromSlash(path)
    92  
    93  	resolvedPath, absPath, err := container.ResolvePath(path)
    94  	if err != nil {
    95  		return nil, nil, err
    96  	}
    97  
    98  	stat, err = container.StatPath(resolvedPath, absPath)
    99  	if err != nil {
   100  		return nil, nil, err
   101  	}
   102  
   103  	// We need to rebase the archive entries if the last element of the
   104  	// resolved path was a symlink that was evaluated and is now different
   105  	// than the requested path. For example, if the given path was "/foo/bar/",
   106  	// but it resolved to "/var/lib/docker/containers/{id}/foo/baz/", we want
   107  	// to ensure that the archive entries start with "bar" and not "baz". This
   108  	// also catches the case when the root directory of the container is
   109  	// requested: we want the archive entries to start with "/" and not the
   110  	// container ID.
   111  
   112  	// Get the source and the base paths of the container resolved path in order
   113  	// to get the proper tar options for the rebase tar.
   114  	resolvedPath = filepath.Clean(resolvedPath)
   115  	if filepath.Base(resolvedPath) == "." {
   116  		resolvedPath += string(filepath.Separator) + "."
   117  	}
   118  
   119  	sourceDir := resolvedPath
   120  	sourceBase := "."
   121  
   122  	if stat.Mode&os.ModeDir == 0 { // not dir
   123  		sourceDir, sourceBase = filepath.Split(resolvedPath)
   124  	}
   125  	opts := archive.TarResourceRebaseOpts(sourceBase, filepath.Base(absPath))
   126  
   127  	data, err := chrootarchive.Tar(sourceDir, opts, container.BaseFS)
   128  	if err != nil {
   129  		return nil, nil, err
   130  	}
   131  
   132  	content = ioutils.NewReadCloserWrapper(data, func() error {
   133  		err := data.Close()
   134  		container.DetachAndUnmount(daemon.LogVolumeEvent)
   135  		daemon.Unmount(container)
   136  		container.Unlock()
   137  		return err
   138  	})
   139  
   140  	daemon.LogContainerEvent(container, "archive-path")
   141  
   142  	return content, stat, nil
   143  }
   144  
   145  // containerExtractToDir extracts the given tar archive to the specified location in the
   146  // filesystem of this container. The given path must be of a directory in the
   147  // container. If it is not, the error will be an errdefs.InvalidParameter. If
   148  // noOverwriteDirNonDir is true then it will be an error if unpacking the
   149  // given content would cause an existing directory to be replaced with a non-
   150  // directory and vice versa.
   151  func (daemon *Daemon) containerExtractToDir(container *container.Container, path string, copyUIDGID, noOverwriteDirNonDir bool, content io.Reader) (err error) {
   152  	container.Lock()
   153  	defer container.Unlock()
   154  
   155  	// Make sure an online file-system operation is permitted.
   156  	if err := daemon.isOnlineFSOperationPermitted(container); err != nil {
   157  		return err
   158  	}
   159  
   160  	if err = daemon.Mount(container); err != nil {
   161  		return err
   162  	}
   163  	defer daemon.Unmount(container)
   164  
   165  	err = daemon.mountVolumes(container)
   166  	defer container.DetachAndUnmount(daemon.LogVolumeEvent)
   167  	if err != nil {
   168  		return err
   169  	}
   170  
   171  	// Normalize path before sending to rootfs'
   172  	path = filepath.FromSlash(path)
   173  
   174  	// Check if a drive letter supplied, it must be the system drive. No-op except on Windows
   175  	path, err = system.CheckSystemDriveAndRemoveDriveLetter(path)
   176  	if err != nil {
   177  		return err
   178  	}
   179  
   180  	// The destination path needs to be resolved to a host path, with all
   181  	// symbolic links followed in the scope of the container's rootfs. Note
   182  	// that we do not use `container.ResolvePath(path)` here because we need
   183  	// to also evaluate the last path element if it is a symlink. This is so
   184  	// that you can extract an archive to a symlink that points to a directory.
   185  
   186  	// Consider the given path as an absolute path in the container.
   187  	absPath := archive.PreserveTrailingDotOrSeparator(filepath.Join(string(filepath.Separator), path), path)
   188  
   189  	// This will evaluate the last path element if it is a symlink.
   190  	resolvedPath, err := container.GetResourcePath(absPath)
   191  	if err != nil {
   192  		return err
   193  	}
   194  
   195  	stat, err := os.Lstat(resolvedPath)
   196  	if err != nil {
   197  		return err
   198  	}
   199  
   200  	if !stat.IsDir() {
   201  		return errdefs.InvalidParameter(errors.New("extraction point is not a directory"))
   202  	}
   203  
   204  	// Need to check if the path is in a volume. If it is, it cannot be in a
   205  	// read-only volume. If it is not in a volume, the container cannot be
   206  	// configured with a read-only rootfs.
   207  
   208  	// Use the resolved path relative to the container rootfs as the new
   209  	// absPath. This way we fully follow any symlinks in a volume that may
   210  	// lead back outside the volume.
   211  	//
   212  	// The Windows implementation of filepath.Rel in golang 1.4 does not
   213  	// support volume style file path semantics. On Windows when using the
   214  	// filter driver, we are guaranteed that the path will always be
   215  	// a volume file path.
   216  	var baseRel string
   217  	if strings.HasPrefix(resolvedPath, `\\?\Volume{`) {
   218  		if strings.HasPrefix(resolvedPath, container.BaseFS) {
   219  			baseRel = resolvedPath[len(container.BaseFS):]
   220  			if baseRel[:1] == `\` {
   221  				baseRel = baseRel[1:]
   222  			}
   223  		}
   224  	} else {
   225  		baseRel, err = filepath.Rel(container.BaseFS, resolvedPath)
   226  	}
   227  	if err != nil {
   228  		return err
   229  	}
   230  	// Make it an absolute path.
   231  	absPath = filepath.Join(string(filepath.Separator), baseRel)
   232  
   233  	toVolume, err := checkIfPathIsInAVolume(container, absPath)
   234  	if err != nil {
   235  		return err
   236  	}
   237  
   238  	if !toVolume && container.HostConfig.ReadonlyRootfs {
   239  		return errdefs.InvalidParameter(errors.New("container rootfs is marked read-only"))
   240  	}
   241  
   242  	options := daemon.defaultTarCopyOptions(noOverwriteDirNonDir)
   243  
   244  	if copyUIDGID {
   245  		var err error
   246  		// tarCopyOptions will appropriately pull in the right uid/gid for the
   247  		// user/group and will set the options.
   248  		options, err = daemon.tarCopyOptions(container, noOverwriteDirNonDir)
   249  		if err != nil {
   250  			return err
   251  		}
   252  	}
   253  
   254  	if err := chrootarchive.UntarWithRoot(content, resolvedPath, options, container.BaseFS); err != nil {
   255  		return err
   256  	}
   257  
   258  	daemon.LogContainerEvent(container, "extract-to-dir")
   259  
   260  	return nil
   261  }
   262  
   263  func (daemon *Daemon) containerCopy(container *container.Container, resource string) (rc io.ReadCloser, err error) {
   264  	if resource[0] == '/' || resource[0] == '\\' {
   265  		resource = resource[1:]
   266  	}
   267  	container.Lock()
   268  
   269  	defer func() {
   270  		if err != nil {
   271  			// Wait to unlock the container until the archive is fully read
   272  			// (see the ReadCloseWrapper func below) or if there is an error
   273  			// before that occurs.
   274  			container.Unlock()
   275  		}
   276  	}()
   277  
   278  	// Make sure an online file-system operation is permitted.
   279  	if err := daemon.isOnlineFSOperationPermitted(container); err != nil {
   280  		return nil, err
   281  	}
   282  
   283  	if err := daemon.Mount(container); err != nil {
   284  		return nil, err
   285  	}
   286  
   287  	defer func() {
   288  		if err != nil {
   289  			// unmount any volumes
   290  			container.DetachAndUnmount(daemon.LogVolumeEvent)
   291  			// unmount the container's rootfs
   292  			daemon.Unmount(container)
   293  		}
   294  	}()
   295  
   296  	if err := daemon.mountVolumes(container); err != nil {
   297  		return nil, err
   298  	}
   299  
   300  	// Normalize path before sending to rootfs
   301  	resource = filepath.FromSlash(resource)
   302  
   303  	basePath, err := container.GetResourcePath(resource)
   304  	if err != nil {
   305  		return nil, err
   306  	}
   307  	stat, err := os.Stat(basePath)
   308  	if err != nil {
   309  		return nil, err
   310  	}
   311  	var filter []string
   312  	if !stat.IsDir() {
   313  		d, f := filepath.Split(basePath)
   314  		basePath = d
   315  		filter = []string{f}
   316  	}
   317  	archv, err := chrootarchive.Tar(basePath, &archive.TarOptions{
   318  		Compression:  archive.Uncompressed,
   319  		IncludeFiles: filter,
   320  	}, container.BaseFS)
   321  	if err != nil {
   322  		return nil, err
   323  	}
   324  
   325  	reader := ioutils.NewReadCloserWrapper(archv, func() error {
   326  		err := archv.Close()
   327  		container.DetachAndUnmount(daemon.LogVolumeEvent)
   328  		daemon.Unmount(container)
   329  		container.Unlock()
   330  		return err
   331  	})
   332  	daemon.LogContainerEvent(container, "copy")
   333  	return reader, nil
   334  }
   335  
   336  // checkIfPathIsInAVolume checks if the path is in a volume. If it is, it
   337  // cannot be in a read-only volume. If it  is not in a volume, the container
   338  // cannot be configured with a read-only rootfs.
   339  //
   340  // This is a no-op on Windows which does not support read-only volumes, or
   341  // extracting to a mount point inside a volume. TODO Windows: FIXME Post-TP5
   342  func checkIfPathIsInAVolume(container *container.Container, absPath string) (bool, error) {
   343  	return false, nil
   344  }
   345  
   346  // isOnlineFSOperationPermitted returns an error if an online filesystem operation
   347  // is not permitted (such as stat or for copying). Running Hyper-V containers
   348  // cannot have their file-system interrogated from the host as the filter is
   349  // loaded inside the utility VM, not the host.
   350  // IMPORTANT: The container lock MUST be held when calling this function.
   351  func (daemon *Daemon) isOnlineFSOperationPermitted(container *container.Container) error {
   352  	if !container.Running {
   353  		return nil
   354  	}
   355  
   356  	// Determine isolation. If not specified in the hostconfig, use daemon default.
   357  	actualIsolation := container.HostConfig.Isolation
   358  	if containertypes.Isolation.IsDefault(containertypes.Isolation(actualIsolation)) {
   359  		actualIsolation = daemon.defaultIsolation
   360  	}
   361  	if containertypes.Isolation.IsHyperV(actualIsolation) {
   362  		return errors.New("filesystem operations against a running Hyper-V container are not supported")
   363  	}
   364  	return nil
   365  }