github.com/skanehira/moby@v17.12.1-ce-rc2+incompatible/daemon/archive.go (about)

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