github.com/kunnos/engine@v1.13.1/daemon/archive.go (about)

     1  package daemon
     2  
     3  import (
     4  	"errors"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/docker/docker/api/types"
    11  	"github.com/docker/docker/builder"
    12  	"github.com/docker/docker/container"
    13  	"github.com/docker/docker/pkg/archive"
    14  	"github.com/docker/docker/pkg/chrootarchive"
    15  	"github.com/docker/docker/pkg/idtools"
    16  	"github.com/docker/docker/pkg/ioutils"
    17  	"github.com/docker/docker/pkg/system"
    18  )
    19  
    20  // ErrExtractPointNotDirectory is used to convey that the operation to extract
    21  // a tar archive to a directory in a container has failed because the specified
    22  // path does not refer to a directory.
    23  var ErrExtractPointNotDirectory = errors.New("extraction point is not a directory")
    24  
    25  // ContainerCopy performs a deprecated operation of archiving the resource at
    26  // the specified path in the container identified by the given name.
    27  func (daemon *Daemon) ContainerCopy(name string, res string) (io.ReadCloser, error) {
    28  	container, err := daemon.GetContainer(name)
    29  	if err != nil {
    30  		return nil, err
    31  	}
    32  
    33  	if res[0] == '/' || res[0] == '\\' {
    34  		res = res[1:]
    35  	}
    36  
    37  	return daemon.containerCopy(container, res)
    38  }
    39  
    40  // ContainerStatPath stats the filesystem resource at the specified path in the
    41  // container identified by the given name.
    42  func (daemon *Daemon) ContainerStatPath(name string, path string) (stat *types.ContainerPathStat, err error) {
    43  	container, err := daemon.GetContainer(name)
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  
    48  	return daemon.containerStatPath(container, path)
    49  }
    50  
    51  // ContainerArchivePath creates an archive of the filesystem resource at the
    52  // specified path in the container identified by the given name. Returns a
    53  // tar archive of the resource and whether it was a directory or a single file.
    54  func (daemon *Daemon) ContainerArchivePath(name string, path string) (content io.ReadCloser, stat *types.ContainerPathStat, err error) {
    55  	container, err := daemon.GetContainer(name)
    56  	if err != nil {
    57  		return nil, nil, err
    58  	}
    59  
    60  	return daemon.containerArchivePath(container, path)
    61  }
    62  
    63  // ContainerExtractToDir extracts the given archive to the specified location
    64  // in the filesystem of the container identified by the given name. The given
    65  // path must be of a directory in the container. If it is not, the error will
    66  // be ErrExtractPointNotDirectory. If noOverwriteDirNonDir is true then it will
    67  // be an error if unpacking the given content would cause an existing directory
    68  // to be replaced with a non-directory and vice versa.
    69  func (daemon *Daemon) ContainerExtractToDir(name, path string, noOverwriteDirNonDir bool, content io.Reader) error {
    70  	container, err := daemon.GetContainer(name)
    71  	if err != nil {
    72  		return err
    73  	}
    74  
    75  	return daemon.containerExtractToDir(container, path, noOverwriteDirNonDir, content)
    76  }
    77  
    78  // containerStatPath stats the filesystem resource at the specified path in this
    79  // container. Returns stat info about the resource.
    80  func (daemon *Daemon) containerStatPath(container *container.Container, path string) (stat *types.ContainerPathStat, err error) {
    81  	container.Lock()
    82  	defer container.Unlock()
    83  
    84  	if err = daemon.Mount(container); err != nil {
    85  		return nil, err
    86  	}
    87  	defer daemon.Unmount(container)
    88  
    89  	err = daemon.mountVolumes(container)
    90  	defer container.DetachAndUnmount(daemon.LogVolumeEvent)
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	resolvedPath, absPath, err := container.ResolvePath(path)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	return container.StatPath(resolvedPath, absPath)
   101  }
   102  
   103  // containerArchivePath creates an archive of the filesystem resource at the specified
   104  // path in this container. Returns a tar archive of the resource and stat info
   105  // about the resource.
   106  func (daemon *Daemon) containerArchivePath(container *container.Container, path string) (content io.ReadCloser, stat *types.ContainerPathStat, err error) {
   107  	container.Lock()
   108  
   109  	defer func() {
   110  		if err != nil {
   111  			// Wait to unlock the container until the archive is fully read
   112  			// (see the ReadCloseWrapper func below) or if there is an error
   113  			// before that occurs.
   114  			container.Unlock()
   115  		}
   116  	}()
   117  
   118  	if err = daemon.Mount(container); err != nil {
   119  		return nil, nil, err
   120  	}
   121  
   122  	defer func() {
   123  		if err != nil {
   124  			// unmount any volumes
   125  			container.DetachAndUnmount(daemon.LogVolumeEvent)
   126  			// unmount the container's rootfs
   127  			daemon.Unmount(container)
   128  		}
   129  	}()
   130  
   131  	if err = daemon.mountVolumes(container); err != nil {
   132  		return nil, nil, err
   133  	}
   134  
   135  	resolvedPath, absPath, err := container.ResolvePath(path)
   136  	if err != nil {
   137  		return nil, nil, err
   138  	}
   139  
   140  	stat, err = container.StatPath(resolvedPath, absPath)
   141  	if err != nil {
   142  		return nil, nil, err
   143  	}
   144  
   145  	// We need to rebase the archive entries if the last element of the
   146  	// resolved path was a symlink that was evaluated and is now different
   147  	// than the requested path. For example, if the given path was "/foo/bar/",
   148  	// but it resolved to "/var/lib/docker/containers/{id}/foo/baz/", we want
   149  	// to ensure that the archive entries start with "bar" and not "baz". This
   150  	// also catches the case when the root directory of the container is
   151  	// requested: we want the archive entries to start with "/" and not the
   152  	// container ID.
   153  	data, err := archive.TarResourceRebase(resolvedPath, filepath.Base(absPath))
   154  	if err != nil {
   155  		return nil, nil, err
   156  	}
   157  
   158  	content = ioutils.NewReadCloserWrapper(data, func() error {
   159  		err := data.Close()
   160  		container.DetachAndUnmount(daemon.LogVolumeEvent)
   161  		daemon.Unmount(container)
   162  		container.Unlock()
   163  		return err
   164  	})
   165  
   166  	daemon.LogContainerEvent(container, "archive-path")
   167  
   168  	return content, stat, nil
   169  }
   170  
   171  // containerExtractToDir extracts the given tar archive to the specified location in the
   172  // filesystem of this container. The given path must be of a directory in the
   173  // container. If it is not, the error will be ErrExtractPointNotDirectory. If
   174  // noOverwriteDirNonDir is true then it will be an error if unpacking the
   175  // given content would cause an existing directory to be replaced with a non-
   176  // directory and vice versa.
   177  func (daemon *Daemon) containerExtractToDir(container *container.Container, path string, noOverwriteDirNonDir bool, content io.Reader) (err error) {
   178  	container.Lock()
   179  	defer container.Unlock()
   180  
   181  	if err = daemon.Mount(container); err != nil {
   182  		return err
   183  	}
   184  	defer daemon.Unmount(container)
   185  
   186  	err = daemon.mountVolumes(container)
   187  	defer container.DetachAndUnmount(daemon.LogVolumeEvent)
   188  	if err != nil {
   189  		return err
   190  	}
   191  
   192  	// Check if a drive letter supplied, it must be the system drive. No-op except on Windows
   193  	path, err = system.CheckSystemDriveAndRemoveDriveLetter(path)
   194  	if err != nil {
   195  		return err
   196  	}
   197  
   198  	// The destination path needs to be resolved to a host path, with all
   199  	// symbolic links followed in the scope of the container's rootfs. Note
   200  	// that we do not use `container.ResolvePath(path)` here because we need
   201  	// to also evaluate the last path element if it is a symlink. This is so
   202  	// that you can extract an archive to a symlink that points to a directory.
   203  
   204  	// Consider the given path as an absolute path in the container.
   205  	absPath := archive.PreserveTrailingDotOrSeparator(filepath.Join(string(filepath.Separator), path), path)
   206  
   207  	// This will evaluate the last path element if it is a symlink.
   208  	resolvedPath, err := container.GetResourcePath(absPath)
   209  	if err != nil {
   210  		return err
   211  	}
   212  
   213  	stat, err := os.Lstat(resolvedPath)
   214  	if err != nil {
   215  		return err
   216  	}
   217  
   218  	if !stat.IsDir() {
   219  		return ErrExtractPointNotDirectory
   220  	}
   221  
   222  	// Need to check if the path is in a volume. If it is, it cannot be in a
   223  	// read-only volume. If it is not in a volume, the container cannot be
   224  	// configured with a read-only rootfs.
   225  
   226  	// Use the resolved path relative to the container rootfs as the new
   227  	// absPath. This way we fully follow any symlinks in a volume that may
   228  	// lead back outside the volume.
   229  	//
   230  	// The Windows implementation of filepath.Rel in golang 1.4 does not
   231  	// support volume style file path semantics. On Windows when using the
   232  	// filter driver, we are guaranteed that the path will always be
   233  	// a volume file path.
   234  	var baseRel string
   235  	if strings.HasPrefix(resolvedPath, `\\?\Volume{`) {
   236  		if strings.HasPrefix(resolvedPath, container.BaseFS) {
   237  			baseRel = resolvedPath[len(container.BaseFS):]
   238  			if baseRel[:1] == `\` {
   239  				baseRel = baseRel[1:]
   240  			}
   241  		}
   242  	} else {
   243  		baseRel, err = filepath.Rel(container.BaseFS, resolvedPath)
   244  	}
   245  	if err != nil {
   246  		return err
   247  	}
   248  	// Make it an absolute path.
   249  	absPath = filepath.Join(string(filepath.Separator), baseRel)
   250  
   251  	toVolume, err := checkIfPathIsInAVolume(container, absPath)
   252  	if err != nil {
   253  		return err
   254  	}
   255  
   256  	if !toVolume && container.HostConfig.ReadonlyRootfs {
   257  		return ErrRootFSReadOnly
   258  	}
   259  
   260  	uid, gid := daemon.GetRemappedUIDGID()
   261  	options := &archive.TarOptions{
   262  		NoOverwriteDirNonDir: noOverwriteDirNonDir,
   263  		ChownOpts: &archive.TarChownOptions{
   264  			UID: uid, GID: gid, // TODO: should all ownership be set to root (either real or remapped)?
   265  		},
   266  	}
   267  	if err := chrootarchive.Untar(content, resolvedPath, options); err != nil {
   268  		return err
   269  	}
   270  
   271  	daemon.LogContainerEvent(container, "extract-to-dir")
   272  
   273  	return nil
   274  }
   275  
   276  func (daemon *Daemon) containerCopy(container *container.Container, resource string) (rc io.ReadCloser, err error) {
   277  	container.Lock()
   278  
   279  	defer func() {
   280  		if err != nil {
   281  			// Wait to unlock the container until the archive is fully read
   282  			// (see the ReadCloseWrapper func below) or if there is an error
   283  			// before that occurs.
   284  			container.Unlock()
   285  		}
   286  	}()
   287  
   288  	if err := daemon.Mount(container); err != nil {
   289  		return nil, err
   290  	}
   291  
   292  	defer func() {
   293  		if err != nil {
   294  			// unmount any volumes
   295  			container.DetachAndUnmount(daemon.LogVolumeEvent)
   296  			// unmount the container's rootfs
   297  			daemon.Unmount(container)
   298  		}
   299  	}()
   300  
   301  	if err := daemon.mountVolumes(container); err != nil {
   302  		return nil, err
   303  	}
   304  
   305  	basePath, err := container.GetResourcePath(resource)
   306  	if err != nil {
   307  		return nil, err
   308  	}
   309  	stat, err := os.Stat(basePath)
   310  	if err != nil {
   311  		return nil, err
   312  	}
   313  	var filter []string
   314  	if !stat.IsDir() {
   315  		d, f := filepath.Split(basePath)
   316  		basePath = d
   317  		filter = []string{f}
   318  	} else {
   319  		filter = []string{filepath.Base(basePath)}
   320  		basePath = filepath.Dir(basePath)
   321  	}
   322  	archive, err := archive.TarWithOptions(basePath, &archive.TarOptions{
   323  		Compression:  archive.Uncompressed,
   324  		IncludeFiles: filter,
   325  	})
   326  	if err != nil {
   327  		return nil, err
   328  	}
   329  
   330  	reader := ioutils.NewReadCloserWrapper(archive, func() error {
   331  		err := archive.Close()
   332  		container.DetachAndUnmount(daemon.LogVolumeEvent)
   333  		daemon.Unmount(container)
   334  		container.Unlock()
   335  		return err
   336  	})
   337  	daemon.LogContainerEvent(container, "copy")
   338  	return reader, nil
   339  }
   340  
   341  // CopyOnBuild copies/extracts a source FileInfo to a destination path inside a container
   342  // specified by a container object.
   343  // TODO: make sure callers don't unnecessarily convert destPath with filepath.FromSlash (Copy does it already).
   344  // CopyOnBuild should take in abstract paths (with slashes) and the implementation should convert it to OS-specific paths.
   345  func (daemon *Daemon) CopyOnBuild(cID string, destPath string, src builder.FileInfo, decompress bool) error {
   346  	srcPath := src.Path()
   347  	destExists := true
   348  	destDir := false
   349  	rootUID, rootGID := daemon.GetRemappedUIDGID()
   350  
   351  	// Work in daemon-local OS specific file paths
   352  	destPath = filepath.FromSlash(destPath)
   353  
   354  	c, err := daemon.GetContainer(cID)
   355  	if err != nil {
   356  		return err
   357  	}
   358  	err = daemon.Mount(c)
   359  	if err != nil {
   360  		return err
   361  	}
   362  	defer daemon.Unmount(c)
   363  
   364  	dest, err := c.GetResourcePath(destPath)
   365  	if err != nil {
   366  		return err
   367  	}
   368  
   369  	// Preserve the trailing slash
   370  	// TODO: why are we appending another path separator if there was already one?
   371  	if strings.HasSuffix(destPath, string(os.PathSeparator)) || destPath == "." {
   372  		destDir = true
   373  		dest += string(os.PathSeparator)
   374  	}
   375  
   376  	destPath = dest
   377  
   378  	destStat, err := os.Stat(destPath)
   379  	if err != nil {
   380  		if !os.IsNotExist(err) {
   381  			//logrus.Errorf("Error performing os.Stat on %s. %s", destPath, err)
   382  			return err
   383  		}
   384  		destExists = false
   385  	}
   386  
   387  	uidMaps, gidMaps := daemon.GetUIDGIDMaps()
   388  	archiver := &archive.Archiver{
   389  		Untar:   chrootarchive.Untar,
   390  		UIDMaps: uidMaps,
   391  		GIDMaps: gidMaps,
   392  	}
   393  
   394  	if src.IsDir() {
   395  		// copy as directory
   396  		if err := archiver.CopyWithTar(srcPath, destPath); err != nil {
   397  			return err
   398  		}
   399  		return fixPermissions(srcPath, destPath, rootUID, rootGID, destExists)
   400  	}
   401  	if decompress && archive.IsArchivePath(srcPath) {
   402  		// Only try to untar if it is a file and that we've been told to decompress (when ADD-ing a remote file)
   403  
   404  		// First try to unpack the source as an archive
   405  		// to support the untar feature we need to clean up the path a little bit
   406  		// because tar is very forgiving.  First we need to strip off the archive's
   407  		// filename from the path but this is only added if it does not end in slash
   408  		tarDest := destPath
   409  		if strings.HasSuffix(tarDest, string(os.PathSeparator)) {
   410  			tarDest = filepath.Dir(destPath)
   411  		}
   412  
   413  		// try to successfully untar the orig
   414  		err := archiver.UntarPath(srcPath, tarDest)
   415  		/*
   416  			if err != nil {
   417  				logrus.Errorf("Couldn't untar to %s: %v", tarDest, err)
   418  			}
   419  		*/
   420  		return err
   421  	}
   422  
   423  	// only needed for fixPermissions, but might as well put it before CopyFileWithTar
   424  	if destDir || (destExists && destStat.IsDir()) {
   425  		destPath = filepath.Join(destPath, src.Name())
   426  	}
   427  
   428  	if err := idtools.MkdirAllNewAs(filepath.Dir(destPath), 0755, rootUID, rootGID); err != nil {
   429  		return err
   430  	}
   431  	if err := archiver.CopyFileWithTar(srcPath, destPath); err != nil {
   432  		return err
   433  	}
   434  
   435  	return fixPermissions(srcPath, destPath, rootUID, rootGID, destExists)
   436  }