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