| //go:build !windows |
| |
| package daemon |
| |
| import ( |
| "context" |
| "io" |
| "os" |
| "path/filepath" |
| |
| containertypes "github.com/docker/docker/api/types/container" |
| "github.com/docker/docker/api/types/events" |
| "github.com/docker/docker/daemon/container" |
| "github.com/docker/docker/errdefs" |
| "github.com/docker/docker/pkg/ioutils" |
| volumemounts "github.com/docker/docker/volume/mounts" |
| "github.com/moby/go-archive" |
| "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) (*containertypes.PathStat, 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 *containertypes.PathStat, retErr error) { |
| container.Lock() |
| |
| defer func() { |
| if retErr != 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 retErr != 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) 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")) |
| } |
| |
| // Check that the destination is not a read-only filesystem. |
| if err := checkWritablePath(container, absPath); err != nil { |
| return err |
| } |
| |
| 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 |
| } |
| |
| // checkWritablePath checks if the path is in a writable location inside the |
| // container. If the path is within a location mounted from a volume, it checks |
| // if the volume is mounted read-only. If it is not in a volume, it checks whether |
| // the container's rootfs is mounted read-only. |
| func checkWritablePath(ctr *container.Container, absPath string) error { |
| parser := volumemounts.NewParser() |
| for _, mnt := range ctr.MountPoints { |
| if isVolumePath := parser.HasResource(mnt, absPath); isVolumePath { |
| if mnt.RW { |
| return nil |
| } |
| return errdefs.InvalidParameter(errors.New("mounted volume is marked read-only")) |
| } |
| } |
| if ctr.HostConfig.ReadonlyRootfs { |
| return errdefs.InvalidParameter(errors.New("container rootfs is marked read-only")) |
| } |
| return nil |
| } |