github.com/toplink-cn/moby@v0.0.0-20240305205811-460b4aebdf81/daemon/archive_unix.go (about)

     1  //go:build !windows
     2  
     3  package daemon // import "github.com/docker/docker/daemon"
     4  
     5  import (
     6  	"context"
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  
    11  	"github.com/docker/docker/api/types"
    12  	"github.com/docker/docker/api/types/events"
    13  	"github.com/docker/docker/container"
    14  	"github.com/docker/docker/errdefs"
    15  	"github.com/docker/docker/pkg/archive"
    16  	"github.com/docker/docker/pkg/ioutils"
    17  	volumemounts "github.com/docker/docker/volume/mounts"
    18  	"github.com/pkg/errors"
    19  )
    20  
    21  // containerStatPath stats the filesystem resource at the specified path in this
    22  // container. Returns stat info about the resource.
    23  func (daemon *Daemon) containerStatPath(container *container.Container, path string) (stat *types.ContainerPathStat, err error) {
    24  	container.Lock()
    25  	defer container.Unlock()
    26  
    27  	cfs, err := daemon.openContainerFS(container)
    28  	if err != nil {
    29  		return nil, err
    30  	}
    31  	defer cfs.Close()
    32  
    33  	return cfs.Stat(context.TODO(), path)
    34  }
    35  
    36  // containerArchivePath creates an archive of the filesystem resource at the specified
    37  // path in this container. Returns a tar archive of the resource and stat info
    38  // about the resource.
    39  func (daemon *Daemon) containerArchivePath(container *container.Container, path string) (content io.ReadCloser, stat *types.ContainerPathStat, err error) {
    40  	container.Lock()
    41  
    42  	defer func() {
    43  		if err != nil {
    44  			// Wait to unlock the container until the archive is fully read
    45  			// (see the ReadCloseWrapper func below) or if there is an error
    46  			// before that occurs.
    47  			container.Unlock()
    48  		}
    49  	}()
    50  
    51  	cfs, err := daemon.openContainerFS(container)
    52  	if err != nil {
    53  		return nil, nil, err
    54  	}
    55  
    56  	defer func() {
    57  		if err != nil {
    58  			cfs.Close()
    59  		}
    60  	}()
    61  
    62  	absPath := archive.PreserveTrailingDotOrSeparator(filepath.Join("/", path), path)
    63  
    64  	stat, err = cfs.Stat(context.TODO(), absPath)
    65  	if err != nil {
    66  		return nil, nil, err
    67  	}
    68  
    69  	sourceDir, sourceBase := absPath, "."
    70  	if stat.Mode&os.ModeDir == 0 { // not dir
    71  		sourceDir, sourceBase = filepath.Split(absPath)
    72  	}
    73  	opts := archive.TarResourceRebaseOpts(sourceBase, filepath.Base(absPath))
    74  
    75  	tb, err := archive.NewTarballer(sourceDir, opts)
    76  	if err != nil {
    77  		return nil, nil, err
    78  	}
    79  
    80  	cfs.GoInFS(context.TODO(), tb.Do)
    81  	data := tb.Reader()
    82  	content = ioutils.NewReadCloserWrapper(data, func() error {
    83  		err := data.Close()
    84  		_ = cfs.Close()
    85  		container.Unlock()
    86  		return err
    87  	})
    88  
    89  	daemon.LogContainerEvent(container, events.ActionArchivePath)
    90  
    91  	return content, stat, nil
    92  }
    93  
    94  // containerExtractToDir extracts the given tar archive to the specified location in the
    95  // filesystem of this container. The given path must be of a directory in the
    96  // container. If it is not, the error will be an errdefs.InvalidParameter. If
    97  // noOverwriteDirNonDir is true then it will be an error if unpacking the
    98  // given content would cause an existing directory to be replaced with a non-
    99  // directory and vice versa.
   100  func (daemon *Daemon) containerExtractToDir(container *container.Container, path string, copyUIDGID, noOverwriteDirNonDir bool, content io.Reader) (err error) {
   101  	container.Lock()
   102  	defer container.Unlock()
   103  
   104  	cfs, err := daemon.openContainerFS(container)
   105  	if err != nil {
   106  		return err
   107  	}
   108  	defer cfs.Close()
   109  
   110  	err = cfs.RunInFS(context.TODO(), func() error {
   111  		// The destination path needs to be resolved with all symbolic links
   112  		// followed. Note that we need to also evaluate the last path element if
   113  		// it is a symlink. This is so that you can extract an archive to a
   114  		// symlink that points to a directory.
   115  		absPath, err := filepath.EvalSymlinks(filepath.Join("/", path))
   116  		if err != nil {
   117  			return err
   118  		}
   119  		absPath = archive.PreserveTrailingDotOrSeparator(absPath, path)
   120  
   121  		stat, err := os.Lstat(absPath)
   122  		if err != nil {
   123  			return err
   124  		}
   125  		if !stat.IsDir() {
   126  			return errdefs.InvalidParameter(errors.New("extraction point is not a directory"))
   127  		}
   128  
   129  		// Need to check if the path is in a volume. If it is, it cannot be in a
   130  		// read-only volume. If it is not in a volume, the container cannot be
   131  		// configured with a read-only rootfs.
   132  		toVolume, err := checkIfPathIsInAVolume(container, absPath)
   133  		if err != nil {
   134  			return err
   135  		}
   136  
   137  		if !toVolume && container.HostConfig.ReadonlyRootfs {
   138  			return errdefs.InvalidParameter(errors.New("container rootfs is marked read-only"))
   139  		}
   140  
   141  		options := daemon.defaultTarCopyOptions(noOverwriteDirNonDir)
   142  
   143  		if copyUIDGID {
   144  			var err error
   145  			// tarCopyOptions will appropriately pull in the right uid/gid for the
   146  			// user/group and will set the options.
   147  			options, err = daemon.tarCopyOptions(container, noOverwriteDirNonDir)
   148  			if err != nil {
   149  				return err
   150  			}
   151  		}
   152  
   153  		return archive.Untar(content, absPath, options)
   154  	})
   155  	if err != nil {
   156  		return err
   157  	}
   158  
   159  	daemon.LogContainerEvent(container, events.ActionExtractToDir)
   160  
   161  	return nil
   162  }
   163  
   164  // checkIfPathIsInAVolume checks if the path is in a volume. If it is, it
   165  // cannot be in a read-only volume. If it  is not in a volume, the container
   166  // cannot be configured with a read-only rootfs.
   167  func checkIfPathIsInAVolume(container *container.Container, absPath string) (bool, error) {
   168  	var toVolume bool
   169  	parser := volumemounts.NewParser()
   170  	for _, mnt := range container.MountPoints {
   171  		if toVolume = parser.HasResource(mnt, absPath); toVolume {
   172  			if mnt.RW {
   173  				break
   174  			}
   175  			return false, errdefs.InvalidParameter(errors.New("mounted volume is marked read-only"))
   176  		}
   177  	}
   178  	return toVolume, nil
   179  }