github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+incompatible/daemon/archive.go (about)

     1  package daemon // import "github.com/docker/docker/daemon"
     2  
     3  import (
     4  	"io"
     5  	"os"
     6  	"strings"
     7  
     8  	"github.com/docker/docker/api/types"
     9  	"github.com/docker/docker/container"
    10  	"github.com/docker/docker/errdefs"
    11  	"github.com/docker/docker/pkg/archive"
    12  	"github.com/docker/docker/pkg/chrootarchive"
    13  	"github.com/docker/docker/pkg/ioutils"
    14  	"github.com/docker/docker/pkg/system"
    15  	"github.com/pkg/errors"
    16  )
    17  
    18  // ErrExtractPointNotDirectory is used to convey that the operation to extract
    19  // a tar archive to a directory in a container has failed because the specified
    20  // path does not refer to a directory.
    21  var ErrExtractPointNotDirectory = errors.New("extraction point is not a directory")
    22  
    23  // The daemon will use the following interfaces if the container fs implements
    24  // these for optimized copies to and from the container.
    25  type extractor interface {
    26  	ExtractArchive(src io.Reader, dst string, opts *archive.TarOptions) error
    27  }
    28  
    29  type archiver interface {
    30  	ArchivePath(src string, opts *archive.TarOptions) (io.ReadCloser, error)
    31  }
    32  
    33  // helper functions to extract or archive
    34  func extractArchive(i interface{}, src io.Reader, dst string, opts *archive.TarOptions, root string) error {
    35  	if ea, ok := i.(extractor); ok {
    36  		return ea.ExtractArchive(src, dst, opts)
    37  	}
    38  
    39  	return chrootarchive.UntarWithRoot(src, dst, opts, root)
    40  }
    41  
    42  func archivePath(i interface{}, src string, opts *archive.TarOptions, root string) (io.ReadCloser, error) {
    43  	if ap, ok := i.(archiver); ok {
    44  		return ap.ArchivePath(src, opts)
    45  	}
    46  	return chrootarchive.Tar(src, opts, root)
    47  }
    48  
    49  // ContainerCopy performs a deprecated operation of archiving the resource at
    50  // the specified path in the container identified by the given name.
    51  func (daemon *Daemon) ContainerCopy(name string, res string) (io.ReadCloser, error) {
    52  	ctr, err := daemon.GetContainer(name)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	// Make sure an online file-system operation is permitted.
    58  	if err := daemon.isOnlineFSOperationPermitted(ctr); err != nil {
    59  		return nil, errdefs.System(err)
    60  	}
    61  
    62  	data, err := daemon.containerCopy(ctr, res)
    63  	if err == nil {
    64  		return data, nil
    65  	}
    66  
    67  	if os.IsNotExist(err) {
    68  		return nil, containerFileNotFound{res, name}
    69  	}
    70  	return nil, errdefs.System(err)
    71  }
    72  
    73  // ContainerStatPath stats the filesystem resource at the specified path in the
    74  // container identified by the given name.
    75  func (daemon *Daemon) ContainerStatPath(name string, path string) (stat *types.ContainerPathStat, err error) {
    76  	ctr, err := daemon.GetContainer(name)
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	// Make sure an online file-system operation is permitted.
    82  	if err := daemon.isOnlineFSOperationPermitted(ctr); err != nil {
    83  		return nil, errdefs.System(err)
    84  	}
    85  
    86  	stat, err = daemon.containerStatPath(ctr, path)
    87  	if err == nil {
    88  		return stat, nil
    89  	}
    90  
    91  	if os.IsNotExist(err) {
    92  		return nil, containerFileNotFound{path, name}
    93  	}
    94  	return nil, errdefs.System(err)
    95  }
    96  
    97  // ContainerArchivePath creates an archive of the filesystem resource at the
    98  // specified path in the container identified by the given name. Returns a
    99  // tar archive of the resource and whether it was a directory or a single file.
   100  func (daemon *Daemon) ContainerArchivePath(name string, path string) (content io.ReadCloser, stat *types.ContainerPathStat, err error) {
   101  	ctr, err := daemon.GetContainer(name)
   102  	if err != nil {
   103  		return nil, nil, err
   104  	}
   105  
   106  	// Make sure an online file-system operation is permitted.
   107  	if err := daemon.isOnlineFSOperationPermitted(ctr); err != nil {
   108  		return nil, nil, errdefs.System(err)
   109  	}
   110  
   111  	content, stat, err = daemon.containerArchivePath(ctr, path)
   112  	if err == nil {
   113  		return content, stat, nil
   114  	}
   115  
   116  	if os.IsNotExist(err) {
   117  		return nil, nil, containerFileNotFound{path, name}
   118  	}
   119  	return nil, nil, errdefs.System(err)
   120  }
   121  
   122  // ContainerExtractToDir extracts the given archive to the specified location
   123  // in the filesystem of the container identified by the given name. The given
   124  // path must be of a directory in the container. If it is not, the error will
   125  // be ErrExtractPointNotDirectory. If noOverwriteDirNonDir is true then it will
   126  // be an error if unpacking the given content would cause an existing directory
   127  // to be replaced with a non-directory and vice versa.
   128  func (daemon *Daemon) ContainerExtractToDir(name, path string, copyUIDGID, noOverwriteDirNonDir bool, content io.Reader) error {
   129  	ctr, err := daemon.GetContainer(name)
   130  	if err != nil {
   131  		return err
   132  	}
   133  
   134  	// Make sure an online file-system operation is permitted.
   135  	if err := daemon.isOnlineFSOperationPermitted(ctr); err != nil {
   136  		return errdefs.System(err)
   137  	}
   138  
   139  	err = daemon.containerExtractToDir(ctr, path, copyUIDGID, noOverwriteDirNonDir, content)
   140  	if err == nil {
   141  		return nil
   142  	}
   143  
   144  	if os.IsNotExist(err) {
   145  		return containerFileNotFound{path, name}
   146  	}
   147  	return errdefs.System(err)
   148  }
   149  
   150  // containerStatPath stats the filesystem resource at the specified path in this
   151  // container. Returns stat info about the resource.
   152  func (daemon *Daemon) containerStatPath(container *container.Container, path string) (stat *types.ContainerPathStat, err error) {
   153  	container.Lock()
   154  	defer container.Unlock()
   155  
   156  	if err = daemon.Mount(container); err != nil {
   157  		return nil, err
   158  	}
   159  	defer daemon.Unmount(container)
   160  
   161  	err = daemon.mountVolumes(container)
   162  	defer container.DetachAndUnmount(daemon.LogVolumeEvent)
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  
   167  	// Normalize path before sending to rootfs
   168  	path = container.BaseFS.FromSlash(path)
   169  
   170  	resolvedPath, absPath, err := container.ResolvePath(path)
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  
   175  	return container.StatPath(resolvedPath, absPath)
   176  }
   177  
   178  // containerArchivePath creates an archive of the filesystem resource at the specified
   179  // path in this container. Returns a tar archive of the resource and stat info
   180  // about the resource.
   181  func (daemon *Daemon) containerArchivePath(container *container.Container, path string) (content io.ReadCloser, stat *types.ContainerPathStat, err error) {
   182  	container.Lock()
   183  
   184  	defer func() {
   185  		if err != nil {
   186  			// Wait to unlock the container until the archive is fully read
   187  			// (see the ReadCloseWrapper func below) or if there is an error
   188  			// before that occurs.
   189  			container.Unlock()
   190  		}
   191  	}()
   192  
   193  	if err = daemon.Mount(container); err != nil {
   194  		return nil, nil, err
   195  	}
   196  
   197  	defer func() {
   198  		if err != nil {
   199  			// unmount any volumes
   200  			container.DetachAndUnmount(daemon.LogVolumeEvent)
   201  			// unmount the container's rootfs
   202  			daemon.Unmount(container)
   203  		}
   204  	}()
   205  
   206  	if err = daemon.mountVolumes(container); err != nil {
   207  		return nil, nil, err
   208  	}
   209  
   210  	// Normalize path before sending to rootfs
   211  	path = container.BaseFS.FromSlash(path)
   212  
   213  	resolvedPath, absPath, err := container.ResolvePath(path)
   214  	if err != nil {
   215  		return nil, nil, err
   216  	}
   217  
   218  	stat, err = container.StatPath(resolvedPath, absPath)
   219  	if err != nil {
   220  		return nil, nil, err
   221  	}
   222  
   223  	// We need to rebase the archive entries if the last element of the
   224  	// resolved path was a symlink that was evaluated and is now different
   225  	// than the requested path. For example, if the given path was "/foo/bar/",
   226  	// but it resolved to "/var/lib/docker/containers/{id}/foo/baz/", we want
   227  	// to ensure that the archive entries start with "bar" and not "baz". This
   228  	// also catches the case when the root directory of the container is
   229  	// requested: we want the archive entries to start with "/" and not the
   230  	// container ID.
   231  	driver := container.BaseFS
   232  
   233  	// Get the source and the base paths of the container resolved path in order
   234  	// to get the proper tar options for the rebase tar.
   235  	resolvedPath = driver.Clean(resolvedPath)
   236  	if driver.Base(resolvedPath) == "." {
   237  		resolvedPath += string(driver.Separator()) + "."
   238  	}
   239  
   240  	sourceDir := resolvedPath
   241  	sourceBase := "."
   242  
   243  	if stat.Mode&os.ModeDir == 0 { // not dir
   244  		sourceDir, sourceBase = driver.Split(resolvedPath)
   245  	}
   246  	opts := archive.TarResourceRebaseOpts(sourceBase, driver.Base(absPath))
   247  
   248  	data, err := archivePath(driver, sourceDir, opts, container.BaseFS.Path())
   249  	if err != nil {
   250  		return nil, nil, err
   251  	}
   252  
   253  	content = ioutils.NewReadCloserWrapper(data, func() error {
   254  		err := data.Close()
   255  		container.DetachAndUnmount(daemon.LogVolumeEvent)
   256  		daemon.Unmount(container)
   257  		container.Unlock()
   258  		return err
   259  	})
   260  
   261  	daemon.LogContainerEvent(container, "archive-path")
   262  
   263  	return content, stat, nil
   264  }
   265  
   266  // containerExtractToDir extracts the given tar archive to the specified location in the
   267  // filesystem of this container. The given path must be of a directory in the
   268  // container. If it is not, the error will be ErrExtractPointNotDirectory. If
   269  // noOverwriteDirNonDir is true then it will be an error if unpacking the
   270  // given content would cause an existing directory to be replaced with a non-
   271  // directory and vice versa.
   272  func (daemon *Daemon) containerExtractToDir(container *container.Container, path string, copyUIDGID, noOverwriteDirNonDir bool, content io.Reader) (err error) {
   273  	container.Lock()
   274  	defer container.Unlock()
   275  
   276  	if err = daemon.Mount(container); err != nil {
   277  		return err
   278  	}
   279  	defer daemon.Unmount(container)
   280  
   281  	err = daemon.mountVolumes(container)
   282  	defer container.DetachAndUnmount(daemon.LogVolumeEvent)
   283  	if err != nil {
   284  		return err
   285  	}
   286  
   287  	// Normalize path before sending to rootfs'
   288  	path = container.BaseFS.FromSlash(path)
   289  	driver := container.BaseFS
   290  
   291  	// Check if a drive letter supplied, it must be the system drive. No-op except on Windows
   292  	path, err = system.CheckSystemDriveAndRemoveDriveLetter(path, driver)
   293  	if err != nil {
   294  		return err
   295  	}
   296  
   297  	// The destination path needs to be resolved to a host path, with all
   298  	// symbolic links followed in the scope of the container's rootfs. Note
   299  	// that we do not use `container.ResolvePath(path)` here because we need
   300  	// to also evaluate the last path element if it is a symlink. This is so
   301  	// that you can extract an archive to a symlink that points to a directory.
   302  
   303  	// Consider the given path as an absolute path in the container.
   304  	absPath := archive.PreserveTrailingDotOrSeparator(
   305  		driver.Join(string(driver.Separator()), path),
   306  		path,
   307  		driver.Separator())
   308  
   309  	// This will evaluate the last path element if it is a symlink.
   310  	resolvedPath, err := container.GetResourcePath(absPath)
   311  	if err != nil {
   312  		return err
   313  	}
   314  
   315  	stat, err := driver.Lstat(resolvedPath)
   316  	if err != nil {
   317  		return err
   318  	}
   319  
   320  	if !stat.IsDir() {
   321  		return ErrExtractPointNotDirectory
   322  	}
   323  
   324  	// Need to check if the path is in a volume. If it is, it cannot be in a
   325  	// read-only volume. If it is not in a volume, the container cannot be
   326  	// configured with a read-only rootfs.
   327  
   328  	// Use the resolved path relative to the container rootfs as the new
   329  	// absPath. This way we fully follow any symlinks in a volume that may
   330  	// lead back outside the volume.
   331  	//
   332  	// The Windows implementation of filepath.Rel in golang 1.4 does not
   333  	// support volume style file path semantics. On Windows when using the
   334  	// filter driver, we are guaranteed that the path will always be
   335  	// a volume file path.
   336  	var baseRel string
   337  	if strings.HasPrefix(resolvedPath, `\\?\Volume{`) {
   338  		if strings.HasPrefix(resolvedPath, driver.Path()) {
   339  			baseRel = resolvedPath[len(driver.Path()):]
   340  			if baseRel[:1] == `\` {
   341  				baseRel = baseRel[1:]
   342  			}
   343  		}
   344  	} else {
   345  		baseRel, err = driver.Rel(driver.Path(), resolvedPath)
   346  	}
   347  	if err != nil {
   348  		return err
   349  	}
   350  	// Make it an absolute path.
   351  	absPath = driver.Join(string(driver.Separator()), baseRel)
   352  
   353  	// @ TODO: gupta-ak: Technically, this works since it no-ops
   354  	// on Windows and the file system is local anyway on linux.
   355  	// But eventually, it should be made driver aware.
   356  	toVolume, err := checkIfPathIsInAVolume(container, absPath)
   357  	if err != nil {
   358  		return err
   359  	}
   360  
   361  	if !toVolume && container.HostConfig.ReadonlyRootfs {
   362  		return ErrRootFSReadOnly
   363  	}
   364  
   365  	options := daemon.defaultTarCopyOptions(noOverwriteDirNonDir)
   366  
   367  	if copyUIDGID {
   368  		var err error
   369  		// tarCopyOptions will appropriately pull in the right uid/gid for the
   370  		// user/group and will set the options.
   371  		options, err = daemon.tarCopyOptions(container, noOverwriteDirNonDir)
   372  		if err != nil {
   373  			return err
   374  		}
   375  	}
   376  
   377  	if err := extractArchive(driver, content, resolvedPath, options, container.BaseFS.Path()); err != nil {
   378  		return err
   379  	}
   380  
   381  	daemon.LogContainerEvent(container, "extract-to-dir")
   382  
   383  	return nil
   384  }
   385  
   386  func (daemon *Daemon) containerCopy(container *container.Container, resource string) (rc io.ReadCloser, err error) {
   387  	if resource[0] == '/' || resource[0] == '\\' {
   388  		resource = resource[1:]
   389  	}
   390  	container.Lock()
   391  
   392  	defer func() {
   393  		if err != nil {
   394  			// Wait to unlock the container until the archive is fully read
   395  			// (see the ReadCloseWrapper func below) or if there is an error
   396  			// before that occurs.
   397  			container.Unlock()
   398  		}
   399  	}()
   400  
   401  	if err := daemon.Mount(container); err != nil {
   402  		return nil, err
   403  	}
   404  
   405  	defer func() {
   406  		if err != nil {
   407  			// unmount any volumes
   408  			container.DetachAndUnmount(daemon.LogVolumeEvent)
   409  			// unmount the container's rootfs
   410  			daemon.Unmount(container)
   411  		}
   412  	}()
   413  
   414  	if err := daemon.mountVolumes(container); err != nil {
   415  		return nil, err
   416  	}
   417  
   418  	// Normalize path before sending to rootfs
   419  	resource = container.BaseFS.FromSlash(resource)
   420  	driver := container.BaseFS
   421  
   422  	basePath, err := container.GetResourcePath(resource)
   423  	if err != nil {
   424  		return nil, err
   425  	}
   426  	stat, err := driver.Stat(basePath)
   427  	if err != nil {
   428  		return nil, err
   429  	}
   430  	var filter []string
   431  	if !stat.IsDir() {
   432  		d, f := driver.Split(basePath)
   433  		basePath = d
   434  		filter = []string{f}
   435  	}
   436  	archv, err := archivePath(driver, basePath, &archive.TarOptions{
   437  		Compression:  archive.Uncompressed,
   438  		IncludeFiles: filter,
   439  	}, container.BaseFS.Path())
   440  	if err != nil {
   441  		return nil, err
   442  	}
   443  
   444  	reader := ioutils.NewReadCloserWrapper(archv, func() error {
   445  		err := archv.Close()
   446  		container.DetachAndUnmount(daemon.LogVolumeEvent)
   447  		daemon.Unmount(container)
   448  		container.Unlock()
   449  		return err
   450  	})
   451  	daemon.LogContainerEvent(container, "copy")
   452  	return reader, nil
   453  }