github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+incompatible/pkg/containerfs/archiver.go (about)

     1  package containerfs // import "github.com/docker/docker/pkg/containerfs"
     2  
     3  import (
     4  	"archive/tar"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"time"
    10  
    11  	"github.com/docker/docker/pkg/archive"
    12  	"github.com/docker/docker/pkg/idtools"
    13  	"github.com/docker/docker/pkg/system"
    14  	"github.com/sirupsen/logrus"
    15  )
    16  
    17  // TarFunc provides a function definition for a custom Tar function
    18  type TarFunc func(string, *archive.TarOptions) (io.ReadCloser, error)
    19  
    20  // UntarFunc provides a function definition for a custom Untar function
    21  type UntarFunc func(io.Reader, string, *archive.TarOptions) error
    22  
    23  // Archiver provides a similar implementation of the archive.Archiver package with the rootfs abstraction
    24  type Archiver struct {
    25  	SrcDriver Driver
    26  	DstDriver Driver
    27  	Tar       TarFunc
    28  	Untar     UntarFunc
    29  	IDMapping *idtools.IdentityMapping
    30  }
    31  
    32  // TarUntar is a convenience function which calls Tar and Untar, with the output of one piped into the other.
    33  // If either Tar or Untar fails, TarUntar aborts and returns the error.
    34  func (archiver *Archiver) TarUntar(src, dst string) error {
    35  	logrus.Debugf("TarUntar(%s %s)", src, dst)
    36  	tarArchive, err := archiver.Tar(src, &archive.TarOptions{Compression: archive.Uncompressed})
    37  	if err != nil {
    38  		return err
    39  	}
    40  	defer tarArchive.Close()
    41  	options := &archive.TarOptions{
    42  		UIDMaps: archiver.IDMapping.UIDs(),
    43  		GIDMaps: archiver.IDMapping.GIDs(),
    44  	}
    45  	return archiver.Untar(tarArchive, dst, options)
    46  }
    47  
    48  // UntarPath untar a file from path to a destination, src is the source tar file path.
    49  func (archiver *Archiver) UntarPath(src, dst string) error {
    50  	tarArchive, err := archiver.SrcDriver.Open(src)
    51  	if err != nil {
    52  		return err
    53  	}
    54  	defer tarArchive.Close()
    55  	options := &archive.TarOptions{
    56  		UIDMaps: archiver.IDMapping.UIDs(),
    57  		GIDMaps: archiver.IDMapping.GIDs(),
    58  	}
    59  	return archiver.Untar(tarArchive, dst, options)
    60  }
    61  
    62  // CopyWithTar creates a tar archive of filesystem path `src`, and
    63  // unpacks it at filesystem path `dst`.
    64  // The archive is streamed directly with fixed buffering and no
    65  // intermediary disk IO.
    66  func (archiver *Archiver) CopyWithTar(src, dst string) error {
    67  	srcSt, err := archiver.SrcDriver.Stat(src)
    68  	if err != nil {
    69  		return err
    70  	}
    71  	if !srcSt.IsDir() {
    72  		return archiver.CopyFileWithTar(src, dst)
    73  	}
    74  
    75  	// if this archiver is set up with ID mapping we need to create
    76  	// the new destination directory with the remapped root UID/GID pair
    77  	// as owner
    78  
    79  	identity := idtools.Identity{UID: archiver.IDMapping.RootPair().UID, GID: archiver.IDMapping.RootPair().GID}
    80  
    81  	// Create dst, copy src's content into it
    82  	if err := idtools.MkdirAllAndChownNew(dst, 0755, identity); err != nil {
    83  		return err
    84  	}
    85  	logrus.Debugf("Calling TarUntar(%s, %s)", src, dst)
    86  	return archiver.TarUntar(src, dst)
    87  }
    88  
    89  // CopyFileWithTar emulates the behavior of the 'cp' command-line
    90  // for a single file. It copies a regular file from path `src` to
    91  // path `dst`, and preserves all its metadata.
    92  func (archiver *Archiver) CopyFileWithTar(src, dst string) (retErr error) {
    93  	logrus.Debugf("CopyFileWithTar(%s, %s)", src, dst)
    94  	srcDriver := archiver.SrcDriver
    95  	dstDriver := archiver.DstDriver
    96  
    97  	srcSt, retErr := srcDriver.Stat(src)
    98  	if retErr != nil {
    99  		return retErr
   100  	}
   101  
   102  	if srcSt.IsDir() {
   103  		return fmt.Errorf("Can't copy a directory")
   104  	}
   105  
   106  	// Clean up the trailing slash. This must be done in an operating
   107  	// system specific manner.
   108  	if dst[len(dst)-1] == dstDriver.Separator() {
   109  		dst = dstDriver.Join(dst, srcDriver.Base(src))
   110  	}
   111  
   112  	// The original call was system.MkdirAll, which is just
   113  	// os.MkdirAll on not-Windows and changed for Windows.
   114  	if dstDriver.OS() == "windows" {
   115  		// Now we are WCOW
   116  		if err := system.MkdirAll(filepath.Dir(dst), 0700); err != nil {
   117  			return err
   118  		}
   119  	} else {
   120  		// We can just use the driver.MkdirAll function
   121  		if err := dstDriver.MkdirAll(dstDriver.Dir(dst), 0700); err != nil {
   122  			return err
   123  		}
   124  	}
   125  
   126  	r, w := io.Pipe()
   127  	errC := make(chan error, 1)
   128  
   129  	go func() {
   130  		defer close(errC)
   131  		errC <- func() error {
   132  			defer w.Close()
   133  
   134  			srcF, err := srcDriver.Open(src)
   135  			if err != nil {
   136  				return err
   137  			}
   138  			defer srcF.Close()
   139  
   140  			hdr, err := tar.FileInfoHeader(srcSt, "")
   141  			if err != nil {
   142  				return err
   143  			}
   144  			hdr.Format = tar.FormatPAX
   145  			hdr.ModTime = hdr.ModTime.Truncate(time.Second)
   146  			hdr.AccessTime = time.Time{}
   147  			hdr.ChangeTime = time.Time{}
   148  			hdr.Name = dstDriver.Base(dst)
   149  			if dstDriver.OS() == "windows" {
   150  				hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode)))
   151  			} else {
   152  				hdr.Mode = int64(os.FileMode(hdr.Mode))
   153  			}
   154  
   155  			if err := remapIDs(archiver.IDMapping, hdr); err != nil {
   156  				return err
   157  			}
   158  
   159  			tw := tar.NewWriter(w)
   160  			defer tw.Close()
   161  			if err := tw.WriteHeader(hdr); err != nil {
   162  				return err
   163  			}
   164  			if _, err := io.Copy(tw, srcF); err != nil {
   165  				return err
   166  			}
   167  			return nil
   168  		}()
   169  	}()
   170  	defer func() {
   171  		if err := <-errC; retErr == nil && err != nil {
   172  			retErr = err
   173  		}
   174  	}()
   175  
   176  	retErr = archiver.Untar(r, dstDriver.Dir(dst), nil)
   177  	if retErr != nil {
   178  		r.CloseWithError(retErr)
   179  	}
   180  	return retErr
   181  }
   182  
   183  // IdentityMapping returns the IdentityMapping of the archiver.
   184  func (archiver *Archiver) IdentityMapping() *idtools.IdentityMapping {
   185  	return archiver.IDMapping
   186  }
   187  
   188  func remapIDs(idMapping *idtools.IdentityMapping, hdr *tar.Header) error {
   189  	ids, err := idMapping.ToHost(idtools.Identity{UID: hdr.Uid, GID: hdr.Gid})
   190  	hdr.Uid, hdr.Gid = ids.UID, ids.GID
   191  	return err
   192  }
   193  
   194  // chmodTarEntry is used to adjust the file permissions used in tar header based
   195  // on the platform the archival is done.
   196  func chmodTarEntry(perm os.FileMode) os.FileMode {
   197  	// perm &= 0755 // this 0-ed out tar flags (like link, regular file, directory marker etc.)
   198  	permPart := perm & os.ModePerm
   199  	noPermPart := perm &^ os.ModePerm
   200  	// Add the x bit: make everything +x from windows
   201  	permPart |= 0111
   202  	permPart &= 0755
   203  
   204  	return noPermPart | permPart
   205  }