archiver.go 5.7 KB

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