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