github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/pkg/archive/archive.go (about) 1 // Package archive provides helper functions for dealing with archive files. 2 package archive // import "github.com/docker/docker/pkg/archive" 3 4 import ( 5 "archive/tar" 6 "bufio" 7 "bytes" 8 "compress/bzip2" 9 "compress/gzip" 10 "context" 11 "encoding/binary" 12 "fmt" 13 "io" 14 "os" 15 "os/exec" 16 "path/filepath" 17 "runtime" 18 "strconv" 19 "strings" 20 "syscall" 21 "time" 22 23 "github.com/containerd/containerd/pkg/userns" 24 "github.com/docker/docker/pkg/idtools" 25 "github.com/docker/docker/pkg/ioutils" 26 "github.com/docker/docker/pkg/pools" 27 "github.com/docker/docker/pkg/system" 28 "github.com/klauspost/compress/zstd" 29 "github.com/moby/patternmatcher" 30 "github.com/moby/sys/sequential" 31 "github.com/pkg/errors" 32 "github.com/sirupsen/logrus" 33 ) 34 35 // ImpliedDirectoryMode represents the mode (Unix permissions) applied to directories that are implied by files in a 36 // tar, but that do not have their own header entry. 37 // 38 // The permissions mask is stored in a constant instead of locally to ensure that magic numbers do not 39 // proliferate in the codebase. The default value 0755 has been selected based on the default umask of 0022, and 40 // a convention of mkdir(1) calling mkdir(2) with permissions of 0777, resulting in a final value of 0755. 41 // 42 // This value is currently implementation-defined, and not captured in any cross-runtime specification. Thus, it is 43 // subject to change in Moby at any time -- image authors who require consistent or known directory permissions 44 // should explicitly control them by ensuring that header entries exist for any applicable path. 45 const ImpliedDirectoryMode = 0755 46 47 type ( 48 // Compression is the state represents if compressed or not. 49 Compression int 50 // WhiteoutFormat is the format of whiteouts unpacked 51 WhiteoutFormat int 52 53 // TarOptions wraps the tar options. 54 TarOptions struct { 55 IncludeFiles []string 56 ExcludePatterns []string 57 Compression Compression 58 NoLchown bool 59 IDMap idtools.IdentityMapping 60 ChownOpts *idtools.Identity 61 IncludeSourceDir bool 62 // WhiteoutFormat is the expected on disk format for whiteout files. 63 // This format will be converted to the standard format on pack 64 // and from the standard format on unpack. 65 WhiteoutFormat WhiteoutFormat 66 // When unpacking, specifies whether overwriting a directory with a 67 // non-directory is allowed and vice versa. 68 NoOverwriteDirNonDir bool 69 // For each include when creating an archive, the included name will be 70 // replaced with the matching name from this map. 71 RebaseNames map[string]string 72 InUserNS bool 73 } 74 ) 75 76 // Archiver implements the Archiver interface and allows the reuse of most utility functions of 77 // this package with a pluggable Untar function. Also, to facilitate the passing of specific id 78 // mappings for untar, an Archiver can be created with maps which will then be passed to Untar operations. 79 type Archiver struct { 80 Untar func(io.Reader, string, *TarOptions) error 81 IDMapping idtools.IdentityMapping 82 } 83 84 // NewDefaultArchiver returns a new Archiver without any IdentityMapping 85 func NewDefaultArchiver() *Archiver { 86 return &Archiver{Untar: Untar} 87 } 88 89 // breakoutError is used to differentiate errors related to breaking out 90 // When testing archive breakout in the unit tests, this error is expected 91 // in order for the test to pass. 92 type breakoutError error 93 94 const ( 95 // Uncompressed represents the uncompressed. 96 Uncompressed Compression = iota 97 // Bzip2 is bzip2 compression algorithm. 98 Bzip2 99 // Gzip is gzip compression algorithm. 100 Gzip 101 // Xz is xz compression algorithm. 102 Xz 103 // Zstd is zstd compression algorithm. 104 Zstd 105 ) 106 107 const ( 108 // AUFSWhiteoutFormat is the default format for whiteouts 109 AUFSWhiteoutFormat WhiteoutFormat = iota 110 // OverlayWhiteoutFormat formats whiteout according to the overlay 111 // standard. 112 OverlayWhiteoutFormat 113 ) 114 115 // IsArchivePath checks if the (possibly compressed) file at the given path 116 // starts with a tar file header. 117 func IsArchivePath(path string) bool { 118 file, err := os.Open(path) 119 if err != nil { 120 return false 121 } 122 defer file.Close() 123 rdr, err := DecompressStream(file) 124 if err != nil { 125 return false 126 } 127 defer rdr.Close() 128 r := tar.NewReader(rdr) 129 _, err = r.Next() 130 return err == nil 131 } 132 133 const ( 134 zstdMagicSkippableStart = 0x184D2A50 135 zstdMagicSkippableMask = 0xFFFFFFF0 136 ) 137 138 var ( 139 bzip2Magic = []byte{0x42, 0x5A, 0x68} 140 gzipMagic = []byte{0x1F, 0x8B, 0x08} 141 xzMagic = []byte{0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00} 142 zstdMagic = []byte{0x28, 0xb5, 0x2f, 0xfd} 143 ) 144 145 type matcher = func([]byte) bool 146 147 func magicNumberMatcher(m []byte) matcher { 148 return func(source []byte) bool { 149 return bytes.HasPrefix(source, m) 150 } 151 } 152 153 // zstdMatcher detects zstd compression algorithm. 154 // Zstandard compressed data is made of one or more frames. 155 // There are two frame formats defined by Zstandard: Zstandard frames and Skippable frames. 156 // See https://tools.ietf.org/id/draft-kucherawy-dispatch-zstd-00.html#rfc.section.2 for more details. 157 func zstdMatcher() matcher { 158 return func(source []byte) bool { 159 if bytes.HasPrefix(source, zstdMagic) { 160 // Zstandard frame 161 return true 162 } 163 // skippable frame 164 if len(source) < 8 { 165 return false 166 } 167 // magic number from 0x184D2A50 to 0x184D2A5F. 168 if binary.LittleEndian.Uint32(source[:4])&zstdMagicSkippableMask == zstdMagicSkippableStart { 169 return true 170 } 171 return false 172 } 173 } 174 175 // DetectCompression detects the compression algorithm of the source. 176 func DetectCompression(source []byte) Compression { 177 compressionMap := map[Compression]matcher{ 178 Bzip2: magicNumberMatcher(bzip2Magic), 179 Gzip: magicNumberMatcher(gzipMagic), 180 Xz: magicNumberMatcher(xzMagic), 181 Zstd: zstdMatcher(), 182 } 183 for _, compression := range []Compression{Bzip2, Gzip, Xz, Zstd} { 184 fn := compressionMap[compression] 185 if fn(source) { 186 return compression 187 } 188 } 189 return Uncompressed 190 } 191 192 func xzDecompress(ctx context.Context, archive io.Reader) (io.ReadCloser, error) { 193 args := []string{"xz", "-d", "-c", "-q"} 194 195 return cmdStream(exec.CommandContext(ctx, args[0], args[1:]...), archive) 196 } 197 198 func gzDecompress(ctx context.Context, buf io.Reader) (io.ReadCloser, error) { 199 if noPigzEnv := os.Getenv("MOBY_DISABLE_PIGZ"); noPigzEnv != "" { 200 noPigz, err := strconv.ParseBool(noPigzEnv) 201 if err != nil { 202 logrus.WithError(err).Warn("invalid value in MOBY_DISABLE_PIGZ env var") 203 } 204 if noPigz { 205 logrus.Debugf("Use of pigz is disabled due to MOBY_DISABLE_PIGZ=%s", noPigzEnv) 206 return gzip.NewReader(buf) 207 } 208 } 209 210 unpigzPath, err := exec.LookPath("unpigz") 211 if err != nil { 212 logrus.Debugf("unpigz binary not found, falling back to go gzip library") 213 return gzip.NewReader(buf) 214 } 215 216 logrus.Debugf("Using %s to decompress", unpigzPath) 217 218 return cmdStream(exec.CommandContext(ctx, unpigzPath, "-d", "-c"), buf) 219 } 220 221 func wrapReadCloser(readBuf io.ReadCloser, cancel context.CancelFunc) io.ReadCloser { 222 return ioutils.NewReadCloserWrapper(readBuf, func() error { 223 cancel() 224 return readBuf.Close() 225 }) 226 } 227 228 // DecompressStream decompresses the archive and returns a ReaderCloser with the decompressed archive. 229 func DecompressStream(archive io.Reader) (io.ReadCloser, error) { 230 p := pools.BufioReader32KPool 231 buf := p.Get(archive) 232 bs, err := buf.Peek(10) 233 if err != nil && err != io.EOF { 234 // Note: we'll ignore any io.EOF error because there are some odd 235 // cases where the layer.tar file will be empty (zero bytes) and 236 // that results in an io.EOF from the Peek() call. So, in those 237 // cases we'll just treat it as a non-compressed stream and 238 // that means just create an empty layer. 239 // See Issue 18170 240 return nil, err 241 } 242 243 compression := DetectCompression(bs) 244 switch compression { 245 case Uncompressed: 246 readBufWrapper := p.NewReadCloserWrapper(buf, buf) 247 return readBufWrapper, nil 248 case Gzip: 249 ctx, cancel := context.WithCancel(context.Background()) 250 251 gzReader, err := gzDecompress(ctx, buf) 252 if err != nil { 253 cancel() 254 return nil, err 255 } 256 readBufWrapper := p.NewReadCloserWrapper(buf, gzReader) 257 return wrapReadCloser(readBufWrapper, cancel), nil 258 case Bzip2: 259 bz2Reader := bzip2.NewReader(buf) 260 readBufWrapper := p.NewReadCloserWrapper(buf, bz2Reader) 261 return readBufWrapper, nil 262 case Xz: 263 ctx, cancel := context.WithCancel(context.Background()) 264 265 xzReader, err := xzDecompress(ctx, buf) 266 if err != nil { 267 cancel() 268 return nil, err 269 } 270 readBufWrapper := p.NewReadCloserWrapper(buf, xzReader) 271 return wrapReadCloser(readBufWrapper, cancel), nil 272 case Zstd: 273 zstdReader, err := zstd.NewReader(buf) 274 if err != nil { 275 return nil, err 276 } 277 readBufWrapper := p.NewReadCloserWrapper(buf, zstdReader) 278 return readBufWrapper, nil 279 default: 280 return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension()) 281 } 282 } 283 284 // CompressStream compresses the dest with specified compression algorithm. 285 func CompressStream(dest io.Writer, compression Compression) (io.WriteCloser, error) { 286 p := pools.BufioWriter32KPool 287 buf := p.Get(dest) 288 switch compression { 289 case Uncompressed: 290 writeBufWrapper := p.NewWriteCloserWrapper(buf, buf) 291 return writeBufWrapper, nil 292 case Gzip: 293 gzWriter := gzip.NewWriter(dest) 294 writeBufWrapper := p.NewWriteCloserWrapper(buf, gzWriter) 295 return writeBufWrapper, nil 296 case Bzip2, Xz: 297 // archive/bzip2 does not support writing, and there is no xz support at all 298 // However, this is not a problem as docker only currently generates gzipped tars 299 return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension()) 300 default: 301 return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension()) 302 } 303 } 304 305 // TarModifierFunc is a function that can be passed to ReplaceFileTarWrapper to 306 // modify the contents or header of an entry in the archive. If the file already 307 // exists in the archive the TarModifierFunc will be called with the Header and 308 // a reader which will return the files content. If the file does not exist both 309 // header and content will be nil. 310 type TarModifierFunc func(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) 311 312 // ReplaceFileTarWrapper converts inputTarStream to a new tar stream. Files in the 313 // tar stream are modified if they match any of the keys in mods. 314 func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModifierFunc) io.ReadCloser { 315 pipeReader, pipeWriter := io.Pipe() 316 317 go func() { 318 tarReader := tar.NewReader(inputTarStream) 319 tarWriter := tar.NewWriter(pipeWriter) 320 defer inputTarStream.Close() 321 defer tarWriter.Close() 322 323 modify := func(name string, original *tar.Header, modifier TarModifierFunc, tarReader io.Reader) error { 324 header, data, err := modifier(name, original, tarReader) 325 switch { 326 case err != nil: 327 return err 328 case header == nil: 329 return nil 330 } 331 332 if header.Name == "" { 333 header.Name = name 334 } 335 header.Size = int64(len(data)) 336 if err := tarWriter.WriteHeader(header); err != nil { 337 return err 338 } 339 if len(data) != 0 { 340 if _, err := tarWriter.Write(data); err != nil { 341 return err 342 } 343 } 344 return nil 345 } 346 347 var err error 348 var originalHeader *tar.Header 349 for { 350 originalHeader, err = tarReader.Next() 351 if err == io.EOF { 352 break 353 } 354 if err != nil { 355 pipeWriter.CloseWithError(err) 356 return 357 } 358 359 modifier, ok := mods[originalHeader.Name] 360 if !ok { 361 // No modifiers for this file, copy the header and data 362 if err := tarWriter.WriteHeader(originalHeader); err != nil { 363 pipeWriter.CloseWithError(err) 364 return 365 } 366 if _, err := pools.Copy(tarWriter, tarReader); err != nil { 367 pipeWriter.CloseWithError(err) 368 return 369 } 370 continue 371 } 372 delete(mods, originalHeader.Name) 373 374 if err := modify(originalHeader.Name, originalHeader, modifier, tarReader); err != nil { 375 pipeWriter.CloseWithError(err) 376 return 377 } 378 } 379 380 // Apply the modifiers that haven't matched any files in the archive 381 for name, modifier := range mods { 382 if err := modify(name, nil, modifier, nil); err != nil { 383 pipeWriter.CloseWithError(err) 384 return 385 } 386 } 387 388 pipeWriter.Close() 389 }() 390 return pipeReader 391 } 392 393 // Extension returns the extension of a file that uses the specified compression algorithm. 394 func (compression *Compression) Extension() string { 395 switch *compression { 396 case Uncompressed: 397 return "tar" 398 case Bzip2: 399 return "tar.bz2" 400 case Gzip: 401 return "tar.gz" 402 case Xz: 403 return "tar.xz" 404 case Zstd: 405 return "tar.zst" 406 } 407 return "" 408 } 409 410 // nosysFileInfo hides the system-dependent info of the wrapped FileInfo to 411 // prevent tar.FileInfoHeader from introspecting it and potentially calling into 412 // glibc. 413 type nosysFileInfo struct { 414 os.FileInfo 415 } 416 417 func (fi nosysFileInfo) Sys() interface{} { 418 // A Sys value of type *tar.Header is safe as it is system-independent. 419 // The tar.FileInfoHeader function copies the fields into the returned 420 // header without performing any OS lookups. 421 if sys, ok := fi.FileInfo.Sys().(*tar.Header); ok { 422 return sys 423 } 424 return nil 425 } 426 427 // sysStat, if non-nil, populates hdr from system-dependent fields of fi. 428 var sysStat func(fi os.FileInfo, hdr *tar.Header) error 429 430 // FileInfoHeaderNoLookups creates a partially-populated tar.Header from fi. 431 // 432 // Compared to the archive/tar.FileInfoHeader function, this function is safe to 433 // call from a chrooted process as it does not populate fields which would 434 // require operating system lookups. It behaves identically to 435 // tar.FileInfoHeader when fi is a FileInfo value returned from 436 // tar.Header.FileInfo(). 437 // 438 // When fi is a FileInfo for a native file, such as returned from os.Stat() and 439 // os.Lstat(), the returned Header value differs from one returned from 440 // tar.FileInfoHeader in the following ways. The Uname and Gname fields are not 441 // set as OS lookups would be required to populate them. The AccessTime and 442 // ChangeTime fields are not currently set (not yet implemented) although that 443 // is subject to change. Callers which require the AccessTime or ChangeTime 444 // fields to be zeroed should explicitly zero them out in the returned Header 445 // value to avoid any compatibility issues in the future. 446 func FileInfoHeaderNoLookups(fi os.FileInfo, link string) (*tar.Header, error) { 447 hdr, err := tar.FileInfoHeader(nosysFileInfo{fi}, link) 448 if err != nil { 449 return nil, err 450 } 451 if sysStat != nil { 452 return hdr, sysStat(fi, hdr) 453 } 454 return hdr, nil 455 } 456 457 // FileInfoHeader creates a populated Header from fi. 458 // 459 // Compared to the archive/tar package, this function fills in less information 460 // but is safe to call from a chrooted process. The AccessTime and ChangeTime 461 // fields are not set in the returned header, ModTime is truncated to one-second 462 // precision, and the Uname and Gname fields are only set when fi is a FileInfo 463 // value returned from tar.Header.FileInfo(). 464 func FileInfoHeader(name string, fi os.FileInfo, link string) (*tar.Header, error) { 465 hdr, err := FileInfoHeaderNoLookups(fi, link) 466 if err != nil { 467 return nil, err 468 } 469 hdr.Format = tar.FormatPAX 470 hdr.ModTime = hdr.ModTime.Truncate(time.Second) 471 hdr.AccessTime = time.Time{} 472 hdr.ChangeTime = time.Time{} 473 hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode))) 474 hdr.Name = canonicalTarName(name, fi.IsDir()) 475 return hdr, nil 476 } 477 478 // ReadSecurityXattrToTarHeader reads security.capability xattr from filesystem 479 // to a tar header 480 func ReadSecurityXattrToTarHeader(path string, hdr *tar.Header) error { 481 const ( 482 // Values based on linux/include/uapi/linux/capability.h 483 xattrCapsSz2 = 20 484 versionOffset = 3 485 vfsCapRevision2 = 2 486 vfsCapRevision3 = 3 487 ) 488 capability, _ := system.Lgetxattr(path, "security.capability") 489 if capability != nil { 490 length := len(capability) 491 if capability[versionOffset] == vfsCapRevision3 { 492 // Convert VFS_CAP_REVISION_3 to VFS_CAP_REVISION_2 as root UID makes no 493 // sense outside the user namespace the archive is built in. 494 capability[versionOffset] = vfsCapRevision2 495 length = xattrCapsSz2 496 } 497 hdr.Xattrs = make(map[string]string) 498 hdr.Xattrs["security.capability"] = string(capability[:length]) 499 } 500 return nil 501 } 502 503 type tarWhiteoutConverter interface { 504 ConvertWrite(*tar.Header, string, os.FileInfo) (*tar.Header, error) 505 ConvertRead(*tar.Header, string) (bool, error) 506 } 507 508 type tarAppender struct { 509 TarWriter *tar.Writer 510 Buffer *bufio.Writer 511 512 // for hardlink mapping 513 SeenFiles map[uint64]string 514 IdentityMapping idtools.IdentityMapping 515 ChownOpts *idtools.Identity 516 517 // For packing and unpacking whiteout files in the 518 // non standard format. The whiteout files defined 519 // by the AUFS standard are used as the tar whiteout 520 // standard. 521 WhiteoutConverter tarWhiteoutConverter 522 } 523 524 func newTarAppender(idMapping idtools.IdentityMapping, writer io.Writer, chownOpts *idtools.Identity) *tarAppender { 525 return &tarAppender{ 526 SeenFiles: make(map[uint64]string), 527 TarWriter: tar.NewWriter(writer), 528 Buffer: pools.BufioWriter32KPool.Get(nil), 529 IdentityMapping: idMapping, 530 ChownOpts: chownOpts, 531 } 532 } 533 534 // CanonicalTarNameForPath canonicalizes relativePath to a POSIX-style path using 535 // forward slashes. It is an alias for filepath.ToSlash, which is a no-op on 536 // Linux and Unix. 537 func CanonicalTarNameForPath(relativePath string) string { 538 return filepath.ToSlash(relativePath) 539 } 540 541 // canonicalTarName provides a platform-independent and consistent POSIX-style 542 // path for files and directories to be archived regardless of the platform. 543 func canonicalTarName(name string, isDir bool) string { 544 name = filepath.ToSlash(name) 545 546 // suffix with '/' for directories 547 if isDir && !strings.HasSuffix(name, "/") { 548 name += "/" 549 } 550 return name 551 } 552 553 // addTarFile adds to the tar archive a file from `path` as `name` 554 func (ta *tarAppender) addTarFile(path, name string) error { 555 fi, err := os.Lstat(path) 556 if err != nil { 557 return err 558 } 559 560 var link string 561 if fi.Mode()&os.ModeSymlink != 0 { 562 var err error 563 link, err = os.Readlink(path) 564 if err != nil { 565 return err 566 } 567 } 568 569 hdr, err := FileInfoHeader(name, fi, link) 570 if err != nil { 571 return err 572 } 573 if err := ReadSecurityXattrToTarHeader(path, hdr); err != nil { 574 return err 575 } 576 577 // if it's not a directory and has more than 1 link, 578 // it's hard linked, so set the type flag accordingly 579 if !fi.IsDir() && hasHardlinks(fi) { 580 inode, err := getInodeFromStat(fi.Sys()) 581 if err != nil { 582 return err 583 } 584 // a link should have a name that it links too 585 // and that linked name should be first in the tar archive 586 if oldpath, ok := ta.SeenFiles[inode]; ok { 587 hdr.Typeflag = tar.TypeLink 588 hdr.Linkname = oldpath 589 hdr.Size = 0 // This Must be here for the writer math to add up! 590 } else { 591 ta.SeenFiles[inode] = name 592 } 593 } 594 595 // check whether the file is overlayfs whiteout 596 // if yes, skip re-mapping container ID mappings. 597 isOverlayWhiteout := fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 598 599 // handle re-mapping container ID mappings back to host ID mappings before 600 // writing tar headers/files. We skip whiteout files because they were written 601 // by the kernel and already have proper ownership relative to the host 602 if !isOverlayWhiteout && !strings.HasPrefix(filepath.Base(hdr.Name), WhiteoutPrefix) && !ta.IdentityMapping.Empty() { 603 fileIDPair, err := getFileUIDGID(fi.Sys()) 604 if err != nil { 605 return err 606 } 607 hdr.Uid, hdr.Gid, err = ta.IdentityMapping.ToContainer(fileIDPair) 608 if err != nil { 609 return err 610 } 611 } 612 613 // explicitly override with ChownOpts 614 if ta.ChownOpts != nil { 615 hdr.Uid = ta.ChownOpts.UID 616 hdr.Gid = ta.ChownOpts.GID 617 } 618 619 if ta.WhiteoutConverter != nil { 620 wo, err := ta.WhiteoutConverter.ConvertWrite(hdr, path, fi) 621 if err != nil { 622 return err 623 } 624 625 // If a new whiteout file exists, write original hdr, then 626 // replace hdr with wo to be written after. Whiteouts should 627 // always be written after the original. Note the original 628 // hdr may have been updated to be a whiteout with returning 629 // a whiteout header 630 if wo != nil { 631 if err := ta.TarWriter.WriteHeader(hdr); err != nil { 632 return err 633 } 634 if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 { 635 return fmt.Errorf("tar: cannot use whiteout for non-empty file") 636 } 637 hdr = wo 638 } 639 } 640 641 if err := ta.TarWriter.WriteHeader(hdr); err != nil { 642 return err 643 } 644 645 if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 { 646 // We use sequential file access to avoid depleting the standby list on 647 // Windows. On Linux, this equates to a regular os.Open. 648 file, err := sequential.Open(path) 649 if err != nil { 650 return err 651 } 652 653 ta.Buffer.Reset(ta.TarWriter) 654 defer ta.Buffer.Reset(nil) 655 _, err = io.Copy(ta.Buffer, file) 656 file.Close() 657 if err != nil { 658 return err 659 } 660 err = ta.Buffer.Flush() 661 if err != nil { 662 return err 663 } 664 } 665 666 return nil 667 } 668 669 func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, Lchown bool, chownOpts *idtools.Identity, inUserns bool) error { 670 // hdr.Mode is in linux format, which we can use for sycalls, 671 // but for os.Foo() calls we need the mode converted to os.FileMode, 672 // so use hdrInfo.Mode() (they differ for e.g. setuid bits) 673 hdrInfo := hdr.FileInfo() 674 675 switch hdr.Typeflag { 676 case tar.TypeDir: 677 // Create directory unless it exists as a directory already. 678 // In that case we just want to merge the two 679 if fi, err := os.Lstat(path); !(err == nil && fi.IsDir()) { 680 if err := os.Mkdir(path, hdrInfo.Mode()); err != nil { 681 return err 682 } 683 } 684 685 case tar.TypeReg, tar.TypeRegA: 686 // Source is regular file. We use sequential file access to avoid depleting 687 // the standby list on Windows. On Linux, this equates to a regular os.OpenFile. 688 file, err := sequential.OpenFile(path, os.O_CREATE|os.O_WRONLY, hdrInfo.Mode()) 689 if err != nil { 690 return err 691 } 692 if _, err := io.Copy(file, reader); err != nil { 693 file.Close() 694 return err 695 } 696 file.Close() 697 698 case tar.TypeBlock, tar.TypeChar: 699 if inUserns { // cannot create devices in a userns 700 return nil 701 } 702 // Handle this is an OS-specific way 703 if err := handleTarTypeBlockCharFifo(hdr, path); err != nil { 704 return err 705 } 706 707 case tar.TypeFifo: 708 // Handle this is an OS-specific way 709 if err := handleTarTypeBlockCharFifo(hdr, path); err != nil { 710 return err 711 } 712 713 case tar.TypeLink: 714 // #nosec G305 -- The target path is checked for path traversal. 715 targetPath := filepath.Join(extractDir, hdr.Linkname) 716 // check for hardlink breakout 717 if !strings.HasPrefix(targetPath, extractDir) { 718 return breakoutError(fmt.Errorf("invalid hardlink %q -> %q", targetPath, hdr.Linkname)) 719 } 720 if err := os.Link(targetPath, path); err != nil { 721 return err 722 } 723 724 case tar.TypeSymlink: 725 // path -> hdr.Linkname = targetPath 726 // e.g. /extractDir/path/to/symlink -> ../2/file = /extractDir/path/2/file 727 targetPath := filepath.Join(filepath.Dir(path), hdr.Linkname) // #nosec G305 -- The target path is checked for path traversal. 728 729 // the reason we don't need to check symlinks in the path (with FollowSymlinkInScope) is because 730 // that symlink would first have to be created, which would be caught earlier, at this very check: 731 if !strings.HasPrefix(targetPath, extractDir) { 732 return breakoutError(fmt.Errorf("invalid symlink %q -> %q", path, hdr.Linkname)) 733 } 734 if err := os.Symlink(hdr.Linkname, path); err != nil { 735 return err 736 } 737 738 case tar.TypeXGlobalHeader: 739 logrus.Debug("PAX Global Extended Headers found and ignored") 740 return nil 741 742 default: 743 return fmt.Errorf("unhandled tar header type %d", hdr.Typeflag) 744 } 745 746 // Lchown is not supported on Windows. 747 if Lchown && runtime.GOOS != "windows" { 748 if chownOpts == nil { 749 chownOpts = &idtools.Identity{UID: hdr.Uid, GID: hdr.Gid} 750 } 751 if err := os.Lchown(path, chownOpts.UID, chownOpts.GID); err != nil { 752 msg := "failed to Lchown %q for UID %d, GID %d" 753 if errors.Is(err, syscall.EINVAL) && userns.RunningInUserNS() { 754 msg += " (try increasing the number of subordinate IDs in /etc/subuid and /etc/subgid)" 755 } 756 return errors.Wrapf(err, msg, path, hdr.Uid, hdr.Gid) 757 } 758 } 759 760 var errors []string 761 for key, value := range hdr.Xattrs { 762 if err := system.Lsetxattr(path, key, []byte(value), 0); err != nil { 763 if err == syscall.ENOTSUP || err == syscall.EPERM { 764 // We ignore errors here because not all graphdrivers support 765 // xattrs *cough* old versions of AUFS *cough*. However only 766 // ENOTSUP should be emitted in that case, otherwise we still 767 // bail. 768 // EPERM occurs if modifying xattrs is not allowed. This can 769 // happen when running in userns with restrictions (ChromeOS). 770 errors = append(errors, err.Error()) 771 continue 772 } 773 return err 774 } 775 } 776 777 if len(errors) > 0 { 778 logrus.WithFields(logrus.Fields{ 779 "errors": errors, 780 }).Warn("ignored xattrs in archive: underlying filesystem doesn't support them") 781 } 782 783 // There is no LChmod, so ignore mode for symlink. Also, this 784 // must happen after chown, as that can modify the file mode 785 if err := handleLChmod(hdr, path, hdrInfo); err != nil { 786 return err 787 } 788 789 aTime := hdr.AccessTime 790 if aTime.Before(hdr.ModTime) { 791 // Last access time should never be before last modified time. 792 aTime = hdr.ModTime 793 } 794 795 // system.Chtimes doesn't support a NOFOLLOW flag atm 796 if hdr.Typeflag == tar.TypeLink { 797 if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) { 798 if err := system.Chtimes(path, aTime, hdr.ModTime); err != nil { 799 return err 800 } 801 } 802 } else if hdr.Typeflag != tar.TypeSymlink { 803 if err := system.Chtimes(path, aTime, hdr.ModTime); err != nil { 804 return err 805 } 806 } else { 807 ts := []syscall.Timespec{timeToTimespec(aTime), timeToTimespec(hdr.ModTime)} 808 if err := system.LUtimesNano(path, ts); err != nil && err != system.ErrNotSupportedPlatform { 809 return err 810 } 811 } 812 return nil 813 } 814 815 // Tar creates an archive from the directory at `path`, and returns it as a 816 // stream of bytes. 817 func Tar(path string, compression Compression) (io.ReadCloser, error) { 818 return TarWithOptions(path, &TarOptions{Compression: compression}) 819 } 820 821 // TarWithOptions creates an archive from the directory at `path`, only including files whose relative 822 // paths are included in `options.IncludeFiles` (if non-nil) or not in `options.ExcludePatterns`. 823 func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) { 824 tb, err := NewTarballer(srcPath, options) 825 if err != nil { 826 return nil, err 827 } 828 go tb.Do() 829 return tb.Reader(), nil 830 } 831 832 // Tarballer is a lower-level interface to TarWithOptions which gives the caller 833 // control over which goroutine the archiving operation executes on. 834 type Tarballer struct { 835 srcPath string 836 options *TarOptions 837 pm *patternmatcher.PatternMatcher 838 pipeReader *io.PipeReader 839 pipeWriter *io.PipeWriter 840 compressWriter io.WriteCloser 841 whiteoutConverter tarWhiteoutConverter 842 } 843 844 // NewTarballer constructs a new tarballer. The arguments are the same as for 845 // TarWithOptions. 846 func NewTarballer(srcPath string, options *TarOptions) (*Tarballer, error) { 847 pm, err := patternmatcher.New(options.ExcludePatterns) 848 if err != nil { 849 return nil, err 850 } 851 852 pipeReader, pipeWriter := io.Pipe() 853 854 compressWriter, err := CompressStream(pipeWriter, options.Compression) 855 if err != nil { 856 return nil, err 857 } 858 859 whiteoutConverter, err := getWhiteoutConverter(options.WhiteoutFormat, options.InUserNS) 860 if err != nil { 861 return nil, err 862 } 863 864 return &Tarballer{ 865 // Fix the source path to work with long path names. This is a no-op 866 // on platforms other than Windows. 867 srcPath: fixVolumePathPrefix(srcPath), 868 options: options, 869 pm: pm, 870 pipeReader: pipeReader, 871 pipeWriter: pipeWriter, 872 compressWriter: compressWriter, 873 whiteoutConverter: whiteoutConverter, 874 }, nil 875 } 876 877 // Reader returns the reader for the created archive. 878 func (t *Tarballer) Reader() io.ReadCloser { 879 return t.pipeReader 880 } 881 882 // Do performs the archiving operation in the background. The resulting archive 883 // can be read from t.Reader(). Do should only be called once on each Tarballer 884 // instance. 885 func (t *Tarballer) Do() { 886 ta := newTarAppender( 887 t.options.IDMap, 888 t.compressWriter, 889 t.options.ChownOpts, 890 ) 891 ta.WhiteoutConverter = t.whiteoutConverter 892 893 defer func() { 894 // Make sure to check the error on Close. 895 if err := ta.TarWriter.Close(); err != nil { 896 logrus.Errorf("Can't close tar writer: %s", err) 897 } 898 if err := t.compressWriter.Close(); err != nil { 899 logrus.Errorf("Can't close compress writer: %s", err) 900 } 901 if err := t.pipeWriter.Close(); err != nil { 902 logrus.Errorf("Can't close pipe writer: %s", err) 903 } 904 }() 905 906 // this buffer is needed for the duration of this piped stream 907 defer pools.BufioWriter32KPool.Put(ta.Buffer) 908 909 // In general we log errors here but ignore them because 910 // during e.g. a diff operation the container can continue 911 // mutating the filesystem and we can see transient errors 912 // from this 913 914 stat, err := os.Lstat(t.srcPath) 915 if err != nil { 916 return 917 } 918 919 if !stat.IsDir() { 920 // We can't later join a non-dir with any includes because the 921 // 'walk' will error if "file/." is stat-ed and "file" is not a 922 // directory. So, we must split the source path and use the 923 // basename as the include. 924 if len(t.options.IncludeFiles) > 0 { 925 logrus.Warn("Tar: Can't archive a file with includes") 926 } 927 928 dir, base := SplitPathDirEntry(t.srcPath) 929 t.srcPath = dir 930 t.options.IncludeFiles = []string{base} 931 } 932 933 if len(t.options.IncludeFiles) == 0 { 934 t.options.IncludeFiles = []string{"."} 935 } 936 937 seen := make(map[string]bool) 938 939 for _, include := range t.options.IncludeFiles { 940 rebaseName := t.options.RebaseNames[include] 941 942 var ( 943 parentMatchInfo []patternmatcher.MatchInfo 944 parentDirs []string 945 ) 946 947 walkRoot := getWalkRoot(t.srcPath, include) 948 filepath.WalkDir(walkRoot, func(filePath string, f os.DirEntry, err error) error { 949 if err != nil { 950 logrus.Errorf("Tar: Can't stat file %s to tar: %s", t.srcPath, err) 951 return nil 952 } 953 954 relFilePath, err := filepath.Rel(t.srcPath, filePath) 955 if err != nil || (!t.options.IncludeSourceDir && relFilePath == "." && f.IsDir()) { 956 // Error getting relative path OR we are looking 957 // at the source directory path. Skip in both situations. 958 return nil 959 } 960 961 if t.options.IncludeSourceDir && include == "." && relFilePath != "." { 962 relFilePath = strings.Join([]string{".", relFilePath}, string(filepath.Separator)) 963 } 964 965 skip := false 966 967 // If "include" is an exact match for the current file 968 // then even if there's an "excludePatterns" pattern that 969 // matches it, don't skip it. IOW, assume an explicit 'include' 970 // is asking for that file no matter what - which is true 971 // for some files, like .dockerignore and Dockerfile (sometimes) 972 if include != relFilePath { 973 for len(parentDirs) != 0 { 974 lastParentDir := parentDirs[len(parentDirs)-1] 975 if strings.HasPrefix(relFilePath, lastParentDir+string(os.PathSeparator)) { 976 break 977 } 978 parentDirs = parentDirs[:len(parentDirs)-1] 979 parentMatchInfo = parentMatchInfo[:len(parentMatchInfo)-1] 980 } 981 982 var matchInfo patternmatcher.MatchInfo 983 if len(parentMatchInfo) != 0 { 984 skip, matchInfo, err = t.pm.MatchesUsingParentResults(relFilePath, parentMatchInfo[len(parentMatchInfo)-1]) 985 } else { 986 skip, matchInfo, err = t.pm.MatchesUsingParentResults(relFilePath, patternmatcher.MatchInfo{}) 987 } 988 if err != nil { 989 logrus.Errorf("Error matching %s: %v", relFilePath, err) 990 return err 991 } 992 993 if f.IsDir() { 994 parentDirs = append(parentDirs, relFilePath) 995 parentMatchInfo = append(parentMatchInfo, matchInfo) 996 } 997 } 998 999 if skip { 1000 // If we want to skip this file and its a directory 1001 // then we should first check to see if there's an 1002 // excludes pattern (e.g. !dir/file) that starts with this 1003 // dir. If so then we can't skip this dir. 1004 1005 // Its not a dir then so we can just return/skip. 1006 if !f.IsDir() { 1007 return nil 1008 } 1009 1010 // No exceptions (!...) in patterns so just skip dir 1011 if !t.pm.Exclusions() { 1012 return filepath.SkipDir 1013 } 1014 1015 dirSlash := relFilePath + string(filepath.Separator) 1016 1017 for _, pat := range t.pm.Patterns() { 1018 if !pat.Exclusion() { 1019 continue 1020 } 1021 if strings.HasPrefix(pat.String()+string(filepath.Separator), dirSlash) { 1022 // found a match - so can't skip this dir 1023 return nil 1024 } 1025 } 1026 1027 // No matching exclusion dir so just skip dir 1028 return filepath.SkipDir 1029 } 1030 1031 if seen[relFilePath] { 1032 return nil 1033 } 1034 seen[relFilePath] = true 1035 1036 // Rename the base resource. 1037 if rebaseName != "" { 1038 var replacement string 1039 if rebaseName != string(filepath.Separator) { 1040 // Special case the root directory to replace with an 1041 // empty string instead so that we don't end up with 1042 // double slashes in the paths. 1043 replacement = rebaseName 1044 } 1045 1046 relFilePath = strings.Replace(relFilePath, include, replacement, 1) 1047 } 1048 1049 if err := ta.addTarFile(filePath, relFilePath); err != nil { 1050 logrus.Errorf("Can't add file %s to tar: %s", filePath, err) 1051 // if pipe is broken, stop writing tar stream to it 1052 if err == io.ErrClosedPipe { 1053 return err 1054 } 1055 } 1056 return nil 1057 }) 1058 } 1059 } 1060 1061 // Unpack unpacks the decompressedArchive to dest with options. 1062 func Unpack(decompressedArchive io.Reader, dest string, options *TarOptions) error { 1063 tr := tar.NewReader(decompressedArchive) 1064 trBuf := pools.BufioReader32KPool.Get(nil) 1065 defer pools.BufioReader32KPool.Put(trBuf) 1066 1067 var dirs []*tar.Header 1068 whiteoutConverter, err := getWhiteoutConverter(options.WhiteoutFormat, options.InUserNS) 1069 if err != nil { 1070 return err 1071 } 1072 1073 // Iterate through the files in the archive. 1074 loop: 1075 for { 1076 hdr, err := tr.Next() 1077 if err == io.EOF { 1078 // end of tar archive 1079 break 1080 } 1081 if err != nil { 1082 return err 1083 } 1084 1085 // ignore XGlobalHeader early to avoid creating parent directories for them 1086 if hdr.Typeflag == tar.TypeXGlobalHeader { 1087 logrus.Debugf("PAX Global Extended Headers found for %s and ignored", hdr.Name) 1088 continue 1089 } 1090 1091 // Normalize name, for safety and for a simple is-root check 1092 // This keeps "../" as-is, but normalizes "/../" to "/". Or Windows: 1093 // This keeps "..\" as-is, but normalizes "\..\" to "\". 1094 hdr.Name = filepath.Clean(hdr.Name) 1095 1096 for _, exclude := range options.ExcludePatterns { 1097 if strings.HasPrefix(hdr.Name, exclude) { 1098 continue loop 1099 } 1100 } 1101 1102 // Ensure that the parent directory exists. 1103 err = createImpliedDirectories(dest, hdr, options) 1104 if err != nil { 1105 return err 1106 } 1107 1108 // #nosec G305 -- The joined path is checked for path traversal. 1109 path := filepath.Join(dest, hdr.Name) 1110 rel, err := filepath.Rel(dest, path) 1111 if err != nil { 1112 return err 1113 } 1114 if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { 1115 return breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest)) 1116 } 1117 1118 // If path exits we almost always just want to remove and replace it 1119 // The only exception is when it is a directory *and* the file from 1120 // the layer is also a directory. Then we want to merge them (i.e. 1121 // just apply the metadata from the layer). 1122 if fi, err := os.Lstat(path); err == nil { 1123 if options.NoOverwriteDirNonDir && fi.IsDir() && hdr.Typeflag != tar.TypeDir { 1124 // If NoOverwriteDirNonDir is true then we cannot replace 1125 // an existing directory with a non-directory from the archive. 1126 return fmt.Errorf("cannot overwrite directory %q with non-directory %q", path, dest) 1127 } 1128 1129 if options.NoOverwriteDirNonDir && !fi.IsDir() && hdr.Typeflag == tar.TypeDir { 1130 // If NoOverwriteDirNonDir is true then we cannot replace 1131 // an existing non-directory with a directory from the archive. 1132 return fmt.Errorf("cannot overwrite non-directory %q with directory %q", path, dest) 1133 } 1134 1135 if fi.IsDir() && hdr.Name == "." { 1136 continue 1137 } 1138 1139 if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) { 1140 if err := os.RemoveAll(path); err != nil { 1141 return err 1142 } 1143 } 1144 } 1145 trBuf.Reset(tr) 1146 1147 if err := remapIDs(options.IDMap, hdr); err != nil { 1148 return err 1149 } 1150 1151 if whiteoutConverter != nil { 1152 writeFile, err := whiteoutConverter.ConvertRead(hdr, path) 1153 if err != nil { 1154 return err 1155 } 1156 if !writeFile { 1157 continue 1158 } 1159 } 1160 1161 if err := createTarFile(path, dest, hdr, trBuf, !options.NoLchown, options.ChownOpts, options.InUserNS); err != nil { 1162 return err 1163 } 1164 1165 // Directory mtimes must be handled at the end to avoid further 1166 // file creation in them to modify the directory mtime 1167 if hdr.Typeflag == tar.TypeDir { 1168 dirs = append(dirs, hdr) 1169 } 1170 } 1171 1172 for _, hdr := range dirs { 1173 // #nosec G305 -- The header was checked for path traversal before it was appended to the dirs slice. 1174 path := filepath.Join(dest, hdr.Name) 1175 1176 if err := system.Chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil { 1177 return err 1178 } 1179 } 1180 return nil 1181 } 1182 1183 // createImpliedDirectories will create all parent directories of the current path with default permissions, if they do 1184 // not already exist. This is possible as the tar format supports 'implicit' directories, where their existence is 1185 // defined by the paths of files in the tar, but there are no header entries for the directories themselves, and thus 1186 // we most both create them and choose metadata like permissions. 1187 // 1188 // The caller should have performed filepath.Clean(hdr.Name), so hdr.Name will now be in the filepath format for the OS 1189 // on which the daemon is running. This precondition is required because this function assumes a OS-specific path 1190 // separator when checking that a path is not the root. 1191 func createImpliedDirectories(dest string, hdr *tar.Header, options *TarOptions) error { 1192 // Not the root directory, ensure that the parent directory exists 1193 if !strings.HasSuffix(hdr.Name, string(os.PathSeparator)) { 1194 parent := filepath.Dir(hdr.Name) 1195 parentPath := filepath.Join(dest, parent) 1196 if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) { 1197 // RootPair() is confined inside this loop as most cases will not require a call, so we can spend some 1198 // unneeded function calls in the uncommon case to encapsulate logic -- implied directories are a niche 1199 // usage that reduces the portability of an image. 1200 rootIDs := options.IDMap.RootPair() 1201 1202 err = idtools.MkdirAllAndChownNew(parentPath, ImpliedDirectoryMode, rootIDs) 1203 if err != nil { 1204 return err 1205 } 1206 } 1207 } 1208 1209 return nil 1210 } 1211 1212 // Untar reads a stream of bytes from `archive`, parses it as a tar archive, 1213 // and unpacks it into the directory at `dest`. 1214 // The archive may be compressed with one of the following algorithms: 1215 // identity (uncompressed), gzip, bzip2, xz. 1216 // 1217 // FIXME: specify behavior when target path exists vs. doesn't exist. 1218 func Untar(tarArchive io.Reader, dest string, options *TarOptions) error { 1219 return untarHandler(tarArchive, dest, options, true) 1220 } 1221 1222 // UntarUncompressed reads a stream of bytes from `archive`, parses it as a tar archive, 1223 // and unpacks it into the directory at `dest`. 1224 // The archive must be an uncompressed stream. 1225 func UntarUncompressed(tarArchive io.Reader, dest string, options *TarOptions) error { 1226 return untarHandler(tarArchive, dest, options, false) 1227 } 1228 1229 // Handler for teasing out the automatic decompression 1230 func untarHandler(tarArchive io.Reader, dest string, options *TarOptions, decompress bool) error { 1231 if tarArchive == nil { 1232 return fmt.Errorf("Empty archive") 1233 } 1234 dest = filepath.Clean(dest) 1235 if options == nil { 1236 options = &TarOptions{} 1237 } 1238 if options.ExcludePatterns == nil { 1239 options.ExcludePatterns = []string{} 1240 } 1241 1242 r := tarArchive 1243 if decompress { 1244 decompressedArchive, err := DecompressStream(tarArchive) 1245 if err != nil { 1246 return err 1247 } 1248 defer decompressedArchive.Close() 1249 r = decompressedArchive 1250 } 1251 1252 return Unpack(r, dest, options) 1253 } 1254 1255 // TarUntar is a convenience function which calls Tar and Untar, with the output of one piped into the other. 1256 // If either Tar or Untar fails, TarUntar aborts and returns the error. 1257 func (archiver *Archiver) TarUntar(src, dst string) error { 1258 archive, err := TarWithOptions(src, &TarOptions{Compression: Uncompressed}) 1259 if err != nil { 1260 return err 1261 } 1262 defer archive.Close() 1263 options := &TarOptions{ 1264 IDMap: archiver.IDMapping, 1265 } 1266 return archiver.Untar(archive, dst, options) 1267 } 1268 1269 // UntarPath untar a file from path to a destination, src is the source tar file path. 1270 func (archiver *Archiver) UntarPath(src, dst string) error { 1271 archive, err := os.Open(src) 1272 if err != nil { 1273 return err 1274 } 1275 defer archive.Close() 1276 options := &TarOptions{ 1277 IDMap: archiver.IDMapping, 1278 } 1279 return archiver.Untar(archive, dst, options) 1280 } 1281 1282 // CopyWithTar creates a tar archive of filesystem path `src`, and 1283 // unpacks it at filesystem path `dst`. 1284 // The archive is streamed directly with fixed buffering and no 1285 // intermediary disk IO. 1286 func (archiver *Archiver) CopyWithTar(src, dst string) error { 1287 srcSt, err := os.Stat(src) 1288 if err != nil { 1289 return err 1290 } 1291 if !srcSt.IsDir() { 1292 return archiver.CopyFileWithTar(src, dst) 1293 } 1294 1295 // if this Archiver is set up with ID mapping we need to create 1296 // the new destination directory with the remapped root UID/GID pair 1297 // as owner 1298 rootIDs := archiver.IDMapping.RootPair() 1299 // Create dst, copy src's content into it 1300 if err := idtools.MkdirAllAndChownNew(dst, 0755, rootIDs); err != nil { 1301 return err 1302 } 1303 return archiver.TarUntar(src, dst) 1304 } 1305 1306 // CopyFileWithTar emulates the behavior of the 'cp' command-line 1307 // for a single file. It copies a regular file from path `src` to 1308 // path `dst`, and preserves all its metadata. 1309 func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { 1310 srcSt, err := os.Stat(src) 1311 if err != nil { 1312 return err 1313 } 1314 1315 if srcSt.IsDir() { 1316 return fmt.Errorf("Can't copy a directory") 1317 } 1318 1319 // Clean up the trailing slash. This must be done in an operating 1320 // system specific manner. 1321 if dst[len(dst)-1] == os.PathSeparator { 1322 dst = filepath.Join(dst, filepath.Base(src)) 1323 } 1324 // Create the holding directory if necessary 1325 if err := system.MkdirAll(filepath.Dir(dst), 0700); err != nil { 1326 return err 1327 } 1328 1329 r, w := io.Pipe() 1330 errC := make(chan error, 1) 1331 1332 go func() { 1333 defer close(errC) 1334 1335 errC <- func() error { 1336 defer w.Close() 1337 1338 srcF, err := os.Open(src) 1339 if err != nil { 1340 return err 1341 } 1342 defer srcF.Close() 1343 1344 hdr, err := FileInfoHeaderNoLookups(srcSt, "") 1345 if err != nil { 1346 return err 1347 } 1348 hdr.Format = tar.FormatPAX 1349 hdr.ModTime = hdr.ModTime.Truncate(time.Second) 1350 hdr.AccessTime = time.Time{} 1351 hdr.ChangeTime = time.Time{} 1352 hdr.Name = filepath.Base(dst) 1353 hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode))) 1354 1355 if err := remapIDs(archiver.IDMapping, hdr); err != nil { 1356 return err 1357 } 1358 1359 tw := tar.NewWriter(w) 1360 defer tw.Close() 1361 if err := tw.WriteHeader(hdr); err != nil { 1362 return err 1363 } 1364 if _, err := io.Copy(tw, srcF); err != nil { 1365 return err 1366 } 1367 return nil 1368 }() 1369 }() 1370 defer func() { 1371 if er := <-errC; err == nil && er != nil { 1372 err = er 1373 } 1374 }() 1375 1376 err = archiver.Untar(r, filepath.Dir(dst), nil) 1377 if err != nil { 1378 r.CloseWithError(err) 1379 } 1380 return err 1381 } 1382 1383 // IdentityMapping returns the IdentityMapping of the archiver. 1384 func (archiver *Archiver) IdentityMapping() idtools.IdentityMapping { 1385 return archiver.IDMapping 1386 } 1387 1388 func remapIDs(idMapping idtools.IdentityMapping, hdr *tar.Header) error { 1389 ids, err := idMapping.ToHost(idtools.Identity{UID: hdr.Uid, GID: hdr.Gid}) 1390 hdr.Uid, hdr.Gid = ids.UID, ids.GID 1391 return err 1392 } 1393 1394 // cmdStream executes a command, and returns its stdout as a stream. 1395 // If the command fails to run or doesn't complete successfully, an error 1396 // will be returned, including anything written on stderr. 1397 func cmdStream(cmd *exec.Cmd, input io.Reader) (io.ReadCloser, error) { 1398 cmd.Stdin = input 1399 pipeR, pipeW := io.Pipe() 1400 cmd.Stdout = pipeW 1401 var errBuf bytes.Buffer 1402 cmd.Stderr = &errBuf 1403 1404 // Run the command and return the pipe 1405 if err := cmd.Start(); err != nil { 1406 return nil, err 1407 } 1408 1409 // Ensure the command has exited before we clean anything up 1410 done := make(chan struct{}) 1411 1412 // Copy stdout to the returned pipe 1413 go func() { 1414 if err := cmd.Wait(); err != nil { 1415 pipeW.CloseWithError(fmt.Errorf("%s: %s", err, errBuf.String())) 1416 } else { 1417 pipeW.Close() 1418 } 1419 close(done) 1420 }() 1421 1422 return ioutils.NewReadCloserWrapper(pipeR, func() error { 1423 // Close pipeR, and then wait for the command to complete before returning. We have to close pipeR first, as 1424 // cmd.Wait waits for any non-file stdout/stderr/stdin to close. 1425 err := pipeR.Close() 1426 <-done 1427 return err 1428 }), nil 1429 } 1430 1431 // NewTempArchive reads the content of src into a temporary file, and returns the contents 1432 // of that file as an archive. The archive can only be read once - as soon as reading completes, 1433 // the file will be deleted. 1434 func NewTempArchive(src io.Reader, dir string) (*TempArchive, error) { 1435 f, err := os.CreateTemp(dir, "") 1436 if err != nil { 1437 return nil, err 1438 } 1439 if _, err := io.Copy(f, src); err != nil { 1440 return nil, err 1441 } 1442 if _, err := f.Seek(0, 0); err != nil { 1443 return nil, err 1444 } 1445 st, err := f.Stat() 1446 if err != nil { 1447 return nil, err 1448 } 1449 size := st.Size() 1450 return &TempArchive{File: f, Size: size}, nil 1451 } 1452 1453 // TempArchive is a temporary archive. The archive can only be read once - as soon as reading completes, 1454 // the file will be deleted. 1455 type TempArchive struct { 1456 *os.File 1457 Size int64 // Pre-computed from Stat().Size() as a convenience 1458 read int64 1459 closed bool 1460 } 1461 1462 // Close closes the underlying file if it's still open, or does a no-op 1463 // to allow callers to try to close the TempArchive multiple times safely. 1464 func (archive *TempArchive) Close() error { 1465 if archive.closed { 1466 return nil 1467 } 1468 1469 archive.closed = true 1470 1471 return archive.File.Close() 1472 } 1473 1474 func (archive *TempArchive) Read(data []byte) (int, error) { 1475 n, err := archive.File.Read(data) 1476 archive.read += int64(n) 1477 if err != nil || archive.read == archive.Size { 1478 archive.Close() 1479 os.Remove(archive.File.Name()) 1480 } 1481 return n, err 1482 }