github.com/rumpl/bof@v23.0.0-rc.2+incompatible/pkg/containerfs/archiver.go (about)

     1  package containerfs // import "github.com/docker/docker/pkg/containerfs"
     2  
     3  import (
     4  	"archive/tar"
     5  	"errors"
     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  		IDMap: archiver.IDMapping,
    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  		IDMap: archiver.IDMapping,
    56  	}
    57  	return archiver.Untar(tarArchive, dst, options)
    58  }
    59  
    60  // CopyWithTar creates a tar archive of filesystem path `src`, and
    61  // unpacks it at filesystem path `dst`.
    62  // The archive is streamed directly with fixed buffering and no
    63  // intermediary disk IO.
    64  func (archiver *Archiver) CopyWithTar(src, dst string) error {
    65  	srcSt, err := archiver.SrcDriver.Stat(src)
    66  	if err != nil {
    67  		return err
    68  	}
    69  	if !srcSt.IsDir() {
    70  		return archiver.CopyFileWithTar(src, dst)
    71  	}
    72  
    73  	// if this archiver is set up with ID mapping we need to create
    74  	// the new destination directory with the remapped root UID/GID pair
    75  	// as owner
    76  
    77  	identity := idtools.Identity{UID: archiver.IDMapping.RootPair().UID, GID: archiver.IDMapping.RootPair().GID}
    78  
    79  	// Create dst, copy src's content into it
    80  	if err := idtools.MkdirAllAndChownNew(dst, 0755, identity); err != nil {
    81  		return err
    82  	}
    83  	logrus.Debugf("Calling TarUntar(%s, %s)", src, dst)
    84  	return archiver.TarUntar(src, dst)
    85  }
    86  
    87  // CopyFileWithTar emulates the behavior of the 'cp' command-line
    88  // for a single file. It copies a regular file from path `src` to
    89  // path `dst`, and preserves all its metadata.
    90  func (archiver *Archiver) CopyFileWithTar(src, dst string) (retErr error) {
    91  	logrus.Debugf("CopyFileWithTar(%s, %s)", src, dst)
    92  	srcDriver := archiver.SrcDriver
    93  	dstDriver := archiver.DstDriver
    94  
    95  	srcSt, retErr := srcDriver.Stat(src)
    96  	if retErr != nil {
    97  		return retErr
    98  	}
    99  
   100  	if srcSt.IsDir() {
   101  		return errors.New("cannot copy a directory")
   102  	}
   103  
   104  	// Clean up the trailing slash. This must be done in an operating
   105  	// system specific manner.
   106  	if dst[len(dst)-1] == dstDriver.Separator() {
   107  		dst = dstDriver.Join(dst, srcDriver.Base(src))
   108  	}
   109  
   110  	// The original call was system.MkdirAll, which is just
   111  	// os.MkdirAll on not-Windows and changed for Windows.
   112  	if dstDriver.OS() == "windows" {
   113  		// Now we are WCOW
   114  		if err := system.MkdirAll(filepath.Dir(dst), 0700); err != nil {
   115  			return err
   116  		}
   117  	} else {
   118  		// We can just use the driver.MkdirAll function
   119  		if err := dstDriver.MkdirAll(dstDriver.Dir(dst), 0700); err != nil {
   120  			return err
   121  		}
   122  	}
   123  
   124  	r, w := io.Pipe()
   125  	errC := make(chan error, 1)
   126  
   127  	go func() {
   128  		defer close(errC)
   129  		errC <- func() error {
   130  			defer w.Close()
   131  
   132  			srcF, err := srcDriver.Open(src)
   133  			if err != nil {
   134  				return err
   135  			}
   136  			defer srcF.Close()
   137  
   138  			hdr, err := archive.FileInfoHeaderNoLookups(srcSt, "")
   139  			if err != nil {
   140  				return err
   141  			}
   142  			hdr.Format = tar.FormatPAX
   143  			hdr.ModTime = hdr.ModTime.Truncate(time.Second)
   144  			hdr.AccessTime = time.Time{}
   145  			hdr.ChangeTime = time.Time{}
   146  			hdr.Name = dstDriver.Base(dst)
   147  			if dstDriver.OS() == "windows" {
   148  				hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode)))
   149  			} else {
   150  				hdr.Mode = int64(os.FileMode(hdr.Mode))
   151  			}
   152  
   153  			if err := remapIDs(archiver.IDMapping, hdr); err != nil {
   154  				return err
   155  			}
   156  
   157  			tw := tar.NewWriter(w)
   158  			defer tw.Close()
   159  			if err := tw.WriteHeader(hdr); err != nil {
   160  				return err
   161  			}
   162  			if _, err := io.Copy(tw, srcF); err != nil {
   163  				return err
   164  			}
   165  			return nil
   166  		}()
   167  	}()
   168  	defer func() {
   169  		if err := <-errC; retErr == nil && err != nil {
   170  			retErr = err
   171  		}
   172  	}()
   173  
   174  	retErr = archiver.Untar(r, dstDriver.Dir(dst), nil)
   175  	if retErr != nil {
   176  		r.CloseWithError(retErr)
   177  	}
   178  	return retErr
   179  }
   180  
   181  // IdentityMapping returns the IdentityMapping of the archiver.
   182  func (archiver *Archiver) IdentityMapping() idtools.IdentityMapping {
   183  	return archiver.IDMapping
   184  }
   185  
   186  func remapIDs(idMapping idtools.IdentityMapping, hdr *tar.Header) error {
   187  	ids, err := idMapping.ToHost(idtools.Identity{UID: hdr.Uid, GID: hdr.Gid})
   188  	hdr.Uid, hdr.Gid = ids.UID, ids.GID
   189  	return err
   190  }
   191  
   192  // chmodTarEntry is used to adjust the file permissions used in tar header based
   193  // on the platform the archival is done.
   194  func chmodTarEntry(perm os.FileMode) os.FileMode {
   195  	// perm &= 0755 // this 0-ed out tar flags (like link, regular file, directory marker etc.)
   196  	permPart := perm & os.ModePerm
   197  	noPermPart := perm &^ os.ModePerm
   198  	// Add the x bit: make everything +x from windows
   199  	permPart |= 0111
   200  	permPart &= 0755
   201  
   202  	return noPermPart | permPart
   203  }