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