//go:build !windows package daemon // import "github.com/docker/docker/daemon" import ( "context" "io" "os" "path/filepath" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/events" "github.com/docker/docker/container" "github.com/docker/docker/errdefs" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/ioutils" volumemounts "github.com/docker/docker/volume/mounts" "github.com/pkg/errors" ) // containerStatPath stats the filesystem resource at the specified path in this // container. Returns stat info about the resource. func (daemon *Daemon) containerStatPath(container *container.Container, path string) (stat *types.ContainerPathStat, err error) { container.Lock() defer container.Unlock() cfs, err := daemon.openContainerFS(container) if err != nil { return nil, err } defer cfs.Close() return cfs.Stat(context.TODO(), path) } // containerArchivePath creates an archive of the filesystem resource at the specified // path in this container. Returns a tar archive of the resource and stat info // about the resource. func (daemon *Daemon) containerArchivePath(container *container.Container, path string) (content io.ReadCloser, stat *types.ContainerPathStat, err error) { container.Lock() defer func() { if err != nil { // Wait to unlock the container until the archive is fully read // (see the ReadCloseWrapper func below) or if there is an error // before that occurs. container.Unlock() } }() cfs, err := daemon.openContainerFS(container) if err != nil { return nil, nil, err } defer func() { if err != nil { cfs.Close() } }() absPath := archive.PreserveTrailingDotOrSeparator(filepath.Join("/", path), path) stat, err = cfs.Stat(context.TODO(), absPath) if err != nil { return nil, nil, err } sourceDir, sourceBase := absPath, "." if stat.Mode&os.ModeDir == 0 { // not dir sourceDir, sourceBase = filepath.Split(absPath) } opts := archive.TarResourceRebaseOpts(sourceBase, filepath.Base(absPath)) tb, err := archive.NewTarballer(sourceDir, opts) if err != nil { return nil, nil, err } cfs.GoInFS(context.TODO(), tb.Do) data := tb.Reader() content = ioutils.NewReadCloserWrapper(data, func() error { err := data.Close() _ = cfs.Close() container.Unlock() return err }) daemon.LogContainerEvent(container, events.ActionArchivePath) return content, stat, nil } // containerExtractToDir extracts the given tar archive to the specified location in the // filesystem of this container. The given path must be of a directory in the // container. If it is not, the error will be an errdefs.InvalidParameter. If // noOverwriteDirNonDir is true then it will be an error if unpacking the // given content would cause an existing directory to be replaced with a non- // directory and vice versa. func (daemon *Daemon) containerExtractToDir(container *container.Container, path string, copyUIDGID, noOverwriteDirNonDir bool, content io.Reader) (err error) { container.Lock() defer container.Unlock() cfs, err := daemon.openContainerFS(container) if err != nil { return err } defer cfs.Close() err = cfs.RunInFS(context.TODO(), func() error { // The destination path needs to be resolved with all symbolic links // followed. Note that we need to also evaluate the last path element if // it is a symlink. This is so that you can extract an archive to a // symlink that points to a directory. absPath, err := filepath.EvalSymlinks(filepath.Join("/", path)) if err != nil { return err } absPath = archive.PreserveTrailingDotOrSeparator(absPath, path) stat, err := os.Lstat(absPath) if err != nil { return err } if !stat.IsDir() { return errdefs.InvalidParameter(errors.New("extraction point is not a directory")) } // Need to check if the path is in a volume. If it is, it cannot be in a // read-only volume. If it is not in a volume, the container cannot be // configured with a read-only rootfs. toVolume, err := checkIfPathIsInAVolume(container, absPath) if err != nil { return err } if !toVolume && container.HostConfig.ReadonlyRootfs { return errdefs.InvalidParameter(errors.New("container rootfs is marked read-only")) } options := daemon.defaultTarCopyOptions(noOverwriteDirNonDir) if copyUIDGID { var err error // tarCopyOptions will appropriately pull in the right uid/gid for the // user/group and will set the options. options, err = daemon.tarCopyOptions(container, noOverwriteDirNonDir) if err != nil { return err } } return archive.Untar(content, absPath, options) }) if err != nil { return err } daemon.LogContainerEvent(container, events.ActionExtractToDir) return nil } func (daemon *Daemon) containerCopy(container *container.Container, resource string) (rc io.ReadCloser, err error) { container.Lock() defer func() { if err != nil { // Wait to unlock the container until the archive is fully read // (see the ReadCloseWrapper func below) or if there is an error // before that occurs. container.Unlock() } }() cfs, err := daemon.openContainerFS(container) if err != nil { return nil, err } defer func() { if err != nil { cfs.Close() } }() err = cfs.RunInFS(context.TODO(), func() error { _, err := os.Stat(resource) return err }) if err != nil { return nil, err } tb, err := archive.NewTarballer(resource, &archive.TarOptions{ Compression: archive.Uncompressed, }) if err != nil { return nil, err } cfs.GoInFS(context.TODO(), tb.Do) archv := tb.Reader() reader := ioutils.NewReadCloserWrapper(archv, func() error { err := archv.Close() _ = cfs.Close() container.Unlock() return err }) daemon.LogContainerEvent(container, events.ActionCopy) return reader, nil } // checkIfPathIsInAVolume checks if the path is in a volume. If it is, it // cannot be in a read-only volume. If it is not in a volume, the container // cannot be configured with a read-only rootfs. func checkIfPathIsInAVolume(container *container.Container, absPath string) (bool, error) { var toVolume bool parser := volumemounts.NewParser() for _, mnt := range container.MountPoints { if toVolume = parser.HasResource(mnt, absPath); toVolume { if mnt.RW { break } return false, errdefs.InvalidParameter(errors.New("mounted volume is marked read-only")) } } return toVolume, nil }