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