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