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