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