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