| //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 | |
| } |