| package containerfs // import "github.com/docker/docker/pkg/containerfs" |
| |
| import ( |
| "archive/tar" |
| "fmt" |
| "io" |
| "os" |
| "path/filepath" |
| "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{ |
| UIDMaps: archiver.IDMapping.UIDs(), |
| GIDMaps: archiver.IDMapping.GIDs(), |
| } |
| 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{ |
| UIDMaps: archiver.IDMapping.UIDs(), |
| GIDMaps: archiver.IDMapping.GIDs(), |
| } |
| 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 fmt.Errorf("Can't 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 dstDriver.OS() == "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 := tar.FileInfoHeader(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 dstDriver.OS() == "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 |
| } |