github.com/ssdev-go/moby@v17.12.1-ce-rc2+incompatible/pkg/containerfs/archiver.go (about)

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