github.com/clintkitson/docker@v1.9.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/pkg/archive"
    12  	"github.com/docker/docker/pkg/chrootarchive"
    13  	"github.com/docker/docker/pkg/ioutils"
    14  )
    15  
    16  // ErrExtractPointNotDirectory is used to convey that the operation to extract
    17  // a tar archive to a directory in a container has failed because the specified
    18  // path does not refer to a directory.
    19  var ErrExtractPointNotDirectory = errors.New("extraction point is not a directory")
    20  
    21  // ContainerCopy performs a deprecated operation of archiving the resource at
    22  // the specified path in the conatiner identified by the given name.
    23  func (daemon *Daemon) ContainerCopy(name string, res string) (io.ReadCloser, error) {
    24  	container, err := daemon.Get(name)
    25  	if err != nil {
    26  		return nil, err
    27  	}
    28  
    29  	if res[0] == '/' || res[0] == '\\' {
    30  		res = res[1:]
    31  	}
    32  
    33  	return container.copy(res)
    34  }
    35  
    36  // ContainerStatPath stats the filesystem resource at the specified path in the
    37  // container identified by the given name.
    38  func (daemon *Daemon) ContainerStatPath(name string, path string) (stat *types.ContainerPathStat, err error) {
    39  	container, err := daemon.Get(name)
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  
    44  	return container.StatPath(path)
    45  }
    46  
    47  // ContainerArchivePath creates an archive of the filesystem resource at the
    48  // specified path in the container identified by the given name. Returns a
    49  // tar archive of the resource and whether it was a directory or a single file.
    50  func (daemon *Daemon) ContainerArchivePath(name string, path string) (content io.ReadCloser, stat *types.ContainerPathStat, err error) {
    51  	container, err := daemon.Get(name)
    52  	if err != nil {
    53  		return nil, nil, err
    54  	}
    55  
    56  	return container.ArchivePath(path)
    57  }
    58  
    59  // ContainerExtractToDir extracts the given archive to the specified location
    60  // in the filesystem of the container identified by the given name. The given
    61  // path must be of a directory in the container. If it is not, the error will
    62  // be ErrExtractPointNotDirectory. If noOverwriteDirNonDir is true then it will
    63  // be an error if unpacking the given content would cause an existing directory
    64  // to be replaced with a non-directory and vice versa.
    65  func (daemon *Daemon) ContainerExtractToDir(name, path string, noOverwriteDirNonDir bool, content io.Reader) error {
    66  	container, err := daemon.Get(name)
    67  	if err != nil {
    68  		return err
    69  	}
    70  
    71  	return container.ExtractToDir(path, noOverwriteDirNonDir, content)
    72  }
    73  
    74  // resolvePath resolves the given path in the container to a resource on the
    75  // host. Returns a resolved path (absolute path to the resource on the host),
    76  // the absolute path to the resource relative to the container's rootfs, and
    77  // a error if the path points to outside the container's rootfs.
    78  func (container *Container) resolvePath(path string) (resolvedPath, absPath string, err error) {
    79  	// Consider the given path as an absolute path in the container.
    80  	absPath = archive.PreserveTrailingDotOrSeparator(filepath.Join(string(filepath.Separator), path), path)
    81  
    82  	// Split the absPath into its Directory and Base components. We will
    83  	// resolve the dir in the scope of the container then append the base.
    84  	dirPath, basePath := filepath.Split(absPath)
    85  
    86  	resolvedDirPath, err := container.GetResourcePath(dirPath)
    87  	if err != nil {
    88  		return "", "", err
    89  	}
    90  
    91  	// resolvedDirPath will have been cleaned (no trailing path separators) so
    92  	// we can manually join it with the base path element.
    93  	resolvedPath = resolvedDirPath + string(filepath.Separator) + basePath
    94  
    95  	return resolvedPath, absPath, nil
    96  }
    97  
    98  // statPath is the unexported version of StatPath. Locks and mounts should
    99  // be acquired before calling this method and the given path should be fully
   100  // resolved to a path on the host corresponding to the given absolute path
   101  // inside the container.
   102  func (container *Container) statPath(resolvedPath, absPath string) (stat *types.ContainerPathStat, err error) {
   103  	lstat, err := os.Lstat(resolvedPath)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  
   108  	var linkTarget string
   109  	if lstat.Mode()&os.ModeSymlink != 0 {
   110  		// Fully evaluate the symlink in the scope of the container rootfs.
   111  		hostPath, err := container.GetResourcePath(absPath)
   112  		if err != nil {
   113  			return nil, err
   114  		}
   115  
   116  		linkTarget, err = filepath.Rel(container.basefs, hostPath)
   117  		if err != nil {
   118  			return nil, err
   119  		}
   120  
   121  		// Make it an absolute path.
   122  		linkTarget = filepath.Join(string(filepath.Separator), linkTarget)
   123  	}
   124  
   125  	return &types.ContainerPathStat{
   126  		Name:       filepath.Base(absPath),
   127  		Size:       lstat.Size(),
   128  		Mode:       lstat.Mode(),
   129  		Mtime:      lstat.ModTime(),
   130  		LinkTarget: linkTarget,
   131  	}, nil
   132  }
   133  
   134  // StatPath stats the filesystem resource at the specified path in this
   135  // container. Returns stat info about the resource.
   136  func (container *Container) StatPath(path string) (stat *types.ContainerPathStat, err error) {
   137  	container.Lock()
   138  	defer container.Unlock()
   139  
   140  	if err = container.Mount(); err != nil {
   141  		return nil, err
   142  	}
   143  	defer container.Unmount()
   144  
   145  	err = container.mountVolumes()
   146  	defer container.unmountVolumes(true)
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  
   151  	resolvedPath, absPath, err := container.resolvePath(path)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	return container.statPath(resolvedPath, absPath)
   157  }
   158  
   159  // ArchivePath creates an archive of the filesystem resource at the specified
   160  // path in this container. Returns a tar archive of the resource and stat info
   161  // about the resource.
   162  func (container *Container) ArchivePath(path string) (content io.ReadCloser, stat *types.ContainerPathStat, err error) {
   163  	container.Lock()
   164  
   165  	defer func() {
   166  		if err != nil {
   167  			// Wait to unlock the container until the archive is fully read
   168  			// (see the ReadCloseWrapper func below) or if there is an error
   169  			// before that occurs.
   170  			container.Unlock()
   171  		}
   172  	}()
   173  
   174  	if err = container.Mount(); err != nil {
   175  		return nil, nil, err
   176  	}
   177  
   178  	defer func() {
   179  		if err != nil {
   180  			// unmount any volumes
   181  			container.unmountVolumes(true)
   182  			// unmount the container's rootfs
   183  			container.Unmount()
   184  		}
   185  	}()
   186  
   187  	if err = container.mountVolumes(); err != nil {
   188  		return nil, nil, err
   189  	}
   190  
   191  	resolvedPath, absPath, err := container.resolvePath(path)
   192  	if err != nil {
   193  		return nil, nil, err
   194  	}
   195  
   196  	stat, err = container.statPath(resolvedPath, absPath)
   197  	if err != nil {
   198  		return nil, nil, err
   199  	}
   200  
   201  	// We need to rebase the archive entries if the last element of the
   202  	// resolved path was a symlink that was evaluated and is now different
   203  	// than the requested path. For example, if the given path was "/foo/bar/",
   204  	// but it resolved to "/var/lib/docker/containers/{id}/foo/baz/", we want
   205  	// to ensure that the archive entries start with "bar" and not "baz". This
   206  	// also catches the case when the root directory of the container is
   207  	// requested: we want the archive entries to start with "/" and not the
   208  	// container ID.
   209  	data, err := archive.TarResourceRebase(resolvedPath, filepath.Base(absPath))
   210  	if err != nil {
   211  		return nil, nil, err
   212  	}
   213  
   214  	content = ioutils.NewReadCloserWrapper(data, func() error {
   215  		err := data.Close()
   216  		container.unmountVolumes(true)
   217  		container.Unmount()
   218  		container.Unlock()
   219  		return err
   220  	})
   221  
   222  	container.logEvent("archive-path")
   223  
   224  	return content, stat, nil
   225  }
   226  
   227  // ExtractToDir extracts the given tar archive to the specified location in the
   228  // filesystem of this container. The given path must be of a directory in the
   229  // container. If it is not, the error will be ErrExtractPointNotDirectory. If
   230  // noOverwriteDirNonDir is true then it will be an error if unpacking the
   231  // given content would cause an existing directory to be replaced with a non-
   232  // directory and vice versa.
   233  func (container *Container) ExtractToDir(path string, noOverwriteDirNonDir bool, content io.Reader) (err error) {
   234  	container.Lock()
   235  	defer container.Unlock()
   236  
   237  	if err = container.Mount(); err != nil {
   238  		return err
   239  	}
   240  	defer container.Unmount()
   241  
   242  	err = container.mountVolumes()
   243  	defer container.unmountVolumes(true)
   244  	if err != nil {
   245  		return err
   246  	}
   247  
   248  	// The destination path needs to be resolved to a host path, with all
   249  	// symbolic links followed in the scope of the container's rootfs. Note
   250  	// that we do not use `container.resolvePath(path)` here because we need
   251  	// to also evaluate the last path element if it is a symlink. This is so
   252  	// that you can extract an archive to a symlink that points to a directory.
   253  
   254  	// Consider the given path as an absolute path in the container.
   255  	absPath := archive.PreserveTrailingDotOrSeparator(filepath.Join(string(filepath.Separator), path), path)
   256  
   257  	// This will evaluate the last path element if it is a symlink.
   258  	resolvedPath, err := container.GetResourcePath(absPath)
   259  	if err != nil {
   260  		return err
   261  	}
   262  
   263  	stat, err := os.Lstat(resolvedPath)
   264  	if err != nil {
   265  		return err
   266  	}
   267  
   268  	if !stat.IsDir() {
   269  		return ErrExtractPointNotDirectory
   270  	}
   271  
   272  	// Need to check if the path is in a volume. If it is, it cannot be in a
   273  	// read-only volume. If it is not in a volume, the container cannot be
   274  	// configured with a read-only rootfs.
   275  
   276  	// Use the resolved path relative to the container rootfs as the new
   277  	// absPath. This way we fully follow any symlinks in a volume that may
   278  	// lead back outside the volume.
   279  	//
   280  	// The Windows implementation of filepath.Rel in golang 1.4 does not
   281  	// support volume style file path semantics. On Windows when using the
   282  	// filter driver, we are guaranteed that the path will always be
   283  	// a volume file path.
   284  	var baseRel string
   285  	if strings.HasPrefix(resolvedPath, `\\?\Volume{`) {
   286  		if strings.HasPrefix(resolvedPath, container.basefs) {
   287  			baseRel = resolvedPath[len(container.basefs):]
   288  			if baseRel[:1] == `\` {
   289  				baseRel = baseRel[1:]
   290  			}
   291  		}
   292  	} else {
   293  		baseRel, err = filepath.Rel(container.basefs, resolvedPath)
   294  	}
   295  	if err != nil {
   296  		return err
   297  	}
   298  	// Make it an absolute path.
   299  	absPath = filepath.Join(string(filepath.Separator), baseRel)
   300  
   301  	toVolume, err := checkIfPathIsInAVolume(container, absPath)
   302  	if err != nil {
   303  		return err
   304  	}
   305  
   306  	if !toVolume && container.hostConfig.ReadonlyRootfs {
   307  		return ErrRootFSReadOnly
   308  	}
   309  
   310  	options := &archive.TarOptions{
   311  		ChownOpts: &archive.TarChownOptions{
   312  			UID: 0, GID: 0, // TODO: use config.User? Remap to userns root?
   313  		},
   314  		NoOverwriteDirNonDir: noOverwriteDirNonDir,
   315  	}
   316  
   317  	if err := chrootarchive.Untar(content, resolvedPath, options); err != nil {
   318  		return err
   319  	}
   320  
   321  	container.logEvent("extract-to-dir")
   322  
   323  	return nil
   324  }