| //go:build !windows |
| // +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/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, "archive-path") |
| |
| 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, "extract-to-dir") |
| |
| 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, "copy") |
| 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 |
| } |