123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- package containerfs // import "github.com/docker/docker/pkg/containerfs"
- import (
- "archive/tar"
- "errors"
- "io"
- "os"
- "path/filepath"
- "runtime"
- "time"
- "github.com/docker/docker/pkg/archive"
- "github.com/docker/docker/pkg/idtools"
- "github.com/docker/docker/pkg/system"
- "github.com/sirupsen/logrus"
- )
- // TarFunc provides a function definition for a custom Tar function
- type TarFunc func(string, *archive.TarOptions) (io.ReadCloser, error)
- // UntarFunc provides a function definition for a custom Untar function
- type UntarFunc func(io.Reader, string, *archive.TarOptions) error
- // Archiver provides a similar implementation of the archive.Archiver package with the rootfs abstraction
- type Archiver struct {
- SrcDriver Driver
- DstDriver Driver
- Tar TarFunc
- Untar UntarFunc
- IDMapping idtools.IdentityMapping
- }
- // TarUntar is a convenience function which calls Tar and Untar, with the output of one piped into the other.
- // If either Tar or Untar fails, TarUntar aborts and returns the error.
- func (archiver *Archiver) TarUntar(src, dst string) error {
- logrus.Debugf("TarUntar(%s %s)", src, dst)
- tarArchive, err := archiver.Tar(src, &archive.TarOptions{Compression: archive.Uncompressed})
- if err != nil {
- return err
- }
- defer tarArchive.Close()
- options := &archive.TarOptions{
- IDMap: archiver.IDMapping,
- }
- return archiver.Untar(tarArchive, dst, options)
- }
- // UntarPath untar a file from path to a destination, src is the source tar file path.
- func (archiver *Archiver) UntarPath(src, dst string) error {
- tarArchive, err := archiver.SrcDriver.Open(src)
- if err != nil {
- return err
- }
- defer tarArchive.Close()
- options := &archive.TarOptions{
- IDMap: archiver.IDMapping,
- }
- return archiver.Untar(tarArchive, dst, options)
- }
- // CopyWithTar creates a tar archive of filesystem path `src`, and
- // unpacks it at filesystem path `dst`.
- // The archive is streamed directly with fixed buffering and no
- // intermediary disk IO.
- func (archiver *Archiver) CopyWithTar(src, dst string) error {
- srcSt, err := archiver.SrcDriver.Stat(src)
- if err != nil {
- return err
- }
- if !srcSt.IsDir() {
- return archiver.CopyFileWithTar(src, dst)
- }
- // if this archiver is set up with ID mapping we need to create
- // the new destination directory with the remapped root UID/GID pair
- // as owner
- identity := idtools.Identity{UID: archiver.IDMapping.RootPair().UID, GID: archiver.IDMapping.RootPair().GID}
- // Create dst, copy src's content into it
- if err := idtools.MkdirAllAndChownNew(dst, 0755, identity); err != nil {
- return err
- }
- logrus.Debugf("Calling TarUntar(%s, %s)", src, dst)
- return archiver.TarUntar(src, dst)
- }
- // CopyFileWithTar emulates the behavior of the 'cp' command-line
- // for a single file. It copies a regular file from path `src` to
- // path `dst`, and preserves all its metadata.
- func (archiver *Archiver) CopyFileWithTar(src, dst string) (retErr error) {
- logrus.Debugf("CopyFileWithTar(%s, %s)", src, dst)
- srcDriver := archiver.SrcDriver
- dstDriver := archiver.DstDriver
- srcSt, retErr := srcDriver.Stat(src)
- if retErr != nil {
- return retErr
- }
- if srcSt.IsDir() {
- return errors.New("cannot copy a directory")
- }
- // Clean up the trailing slash. This must be done in an operating
- // system specific manner.
- if dst[len(dst)-1] == dstDriver.Separator() {
- dst = dstDriver.Join(dst, srcDriver.Base(src))
- }
- // The original call was system.MkdirAll, which is just
- // os.MkdirAll on not-Windows and changed for Windows.
- if runtime.GOOS == "windows" {
- // Now we are WCOW
- if err := system.MkdirAll(filepath.Dir(dst), 0700); err != nil {
- return err
- }
- } else {
- // We can just use the driver.MkdirAll function
- if err := dstDriver.MkdirAll(dstDriver.Dir(dst), 0700); err != nil {
- return err
- }
- }
- r, w := io.Pipe()
- errC := make(chan error, 1)
- go func() {
- defer close(errC)
- errC <- func() error {
- defer w.Close()
- srcF, err := srcDriver.Open(src)
- if err != nil {
- return err
- }
- defer srcF.Close()
- hdr, err := archive.FileInfoHeaderNoLookups(srcSt, "")
- if err != nil {
- return err
- }
- hdr.Format = tar.FormatPAX
- hdr.ModTime = hdr.ModTime.Truncate(time.Second)
- hdr.AccessTime = time.Time{}
- hdr.ChangeTime = time.Time{}
- hdr.Name = dstDriver.Base(dst)
- if runtime.GOOS == "windows" {
- hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode)))
- } else {
- hdr.Mode = int64(os.FileMode(hdr.Mode))
- }
- if err := remapIDs(archiver.IDMapping, hdr); err != nil {
- return err
- }
- tw := tar.NewWriter(w)
- defer tw.Close()
- if err := tw.WriteHeader(hdr); err != nil {
- return err
- }
- if _, err := io.Copy(tw, srcF); err != nil {
- return err
- }
- return nil
- }()
- }()
- defer func() {
- if err := <-errC; retErr == nil && err != nil {
- retErr = err
- }
- }()
- retErr = archiver.Untar(r, dstDriver.Dir(dst), nil)
- if retErr != nil {
- r.CloseWithError(retErr)
- }
- return retErr
- }
- // IdentityMapping returns the IdentityMapping of the archiver.
- func (archiver *Archiver) IdentityMapping() idtools.IdentityMapping {
- return archiver.IDMapping
- }
- func remapIDs(idMapping idtools.IdentityMapping, hdr *tar.Header) error {
- ids, err := idMapping.ToHost(idtools.Identity{UID: hdr.Uid, GID: hdr.Gid})
- hdr.Uid, hdr.Gid = ids.UID, ids.GID
- return err
- }
- // chmodTarEntry is used to adjust the file permissions used in tar header based
- // on the platform the archival is done.
- func chmodTarEntry(perm os.FileMode) os.FileMode {
- // perm &= 0755 // this 0-ed out tar flags (like link, regular file, directory marker etc.)
- permPart := perm & os.ModePerm
- noPermPart := perm &^ os.ModePerm
- // Add the x bit: make everything +x from windows
- permPart |= 0111
- permPart &= 0755
- return noPermPart | permPart
- }
|