@@ -25,15 +25,23 @@ import (
2525)
2626
2727type (
28- Archive io.ReadCloser
29- ArchiveReader io.Reader
30- Compression int
31- TarOptions struct {
32- IncludeFiles []string
33- ExcludePatterns []string
34- Compression Compression
35- NoLchown bool
36- Name string
28+ Archive io.ReadCloser
29+ ArchiveReader io.Reader
30+ Compression int
31+ TarChownOptions struct {
32+ UID , GID int
33+ }
34+ TarOptions struct {
35+ IncludeFiles []string
36+ ExcludePatterns []string
37+ Compression Compression
38+ NoLchown bool
39+ ChownOpts * TarChownOptions
40+ Name string
41+ IncludeSourceDir bool
42+ // When unpacking, specifies whether overwriting a directory with a
43+ // non-directory is allowed and vice versa.
44+ NoOverwriteDirNonDir bool
3745 }
3846
3947 // Archiver allows the reuse of most utility functions of this package
@@ -262,7 +270,7 @@ func (ta *tarAppender) addTarFile(path, name string) error {
262270 return nil
263271}
264272
265- func createTarFile (path , extractDir string , hdr * tar.Header , reader io.Reader , Lchown bool ) error {
273+ func createTarFile (path , extractDir string , hdr * tar.Header , reader io.Reader , Lchown bool , chownOpts * TarChownOptions ) error {
266274 // hdr.Mode is in linux format, which we can use for sycalls,
267275 // but for os.Foo() calls we need the mode converted to os.FileMode,
268276 // so use hdrInfo.Mode() (they differ for e.g. setuid bits)
@@ -328,9 +336,12 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L
328336 return fmt .Errorf ("Unhandled tar header type %d\n " , hdr .Typeflag )
329337 }
330338
331- // Lchown is not supported on Windows
332- if runtime .GOOS != "windows" {
333- if err := os .Lchown (path , hdr .Uid , hdr .Gid ); err != nil && Lchown {
339+ // Lchown is not supported on Windows.
340+ if Lchown && runtime .GOOS != "windows" {
341+ if chownOpts == nil {
342+ chownOpts = & TarChownOptions {UID : hdr .Uid , GID : hdr .Gid }
343+ }
344+ if err := os .Lchown (path , chownOpts .UID , chownOpts .GID ); err != nil {
334345 return err
335346 }
336347 }
@@ -396,6 +407,20 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
396407 Buffer : pools .BufioWriter32KPool .Get (nil ),
397408 SeenFiles : make (map [uint64 ]string ),
398409 }
410+
411+ defer func () {
412+ // Make sure to check the error on Close.
413+ if err := ta .TarWriter .Close (); err != nil {
414+ logrus .Debugf ("Can't close tar writer: %s" , err )
415+ }
416+ if err := compressWriter .Close (); err != nil {
417+ logrus .Debugf ("Can't close compress writer: %s" , err )
418+ }
419+ if err := pipeWriter .Close (); err != nil {
420+ logrus .Debugf ("Can't close pipe writer: %s" , err )
421+ }
422+ }()
423+
399424 // this buffer is needed for the duration of this piped stream
400425 defer pools .BufioWriter32KPool .Put (ta .Buffer )
401426
@@ -404,27 +429,53 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
404429 // mutating the filesystem and we can see transient errors
405430 // from this
406431
407- if options .IncludeFiles == nil {
432+ stat , err := os .Lstat (srcPath )
433+ if err != nil {
434+ return
435+ }
436+
437+ if ! stat .IsDir () {
438+ // We can't later join a non-dir with any includes because the
439+ // 'walk' will error if "file/." is stat-ed and "file" is not a
440+ // directory. So, we must split the source path and use the
441+ // basename as the include.
442+ if len (options .IncludeFiles ) > 0 {
443+ logrus .Warn ("Tar: Can't archive a file with includes" )
444+ }
445+
446+ dir , base := SplitPathDirEntry (srcPath )
447+ srcPath = dir
448+ options .IncludeFiles = []string {base }
449+ }
450+
451+ if len (options .IncludeFiles ) == 0 {
408452 options .IncludeFiles = []string {"." }
409453 }
410454
411455 seen := make (map [string ]bool )
412456
413457 var renamedRelFilePath string // For when tar.Options.Name is set
414458 for _ , include := range options .IncludeFiles {
415- filepath .Walk (filepath .Join (srcPath , include ), func (filePath string , f os.FileInfo , err error ) error {
459+ // We can't use filepath.Join(srcPath, include) because this will
460+ // clean away a trailing "." or "/" which may be important.
461+ walkRoot := strings .Join ([]string {srcPath , include }, string (filepath .Separator ))
462+ filepath .Walk (walkRoot , func (filePath string , f os.FileInfo , err error ) error {
416463 if err != nil {
417464 logrus .Debugf ("Tar: Can't stat file %s to tar: %s" , srcPath , err )
418465 return nil
419466 }
420467
421468 relFilePath , err := filepath .Rel (srcPath , filePath )
422- if err != nil || (relFilePath == "." && f .IsDir ()) {
469+ if err != nil || (! options . IncludeSourceDir && relFilePath == "." && f .IsDir ()) {
423470 // Error getting relative path OR we are looking
424- // at the root path. Skip in both situations.
471+ // at the source directory path. Skip in both situations.
425472 return nil
426473 }
427474
475+ if options .IncludeSourceDir && include == "." && relFilePath != "." {
476+ relFilePath = strings .Join ([]string {"." , relFilePath }, string (filepath .Separator ))
477+ }
478+
428479 skip := false
429480
430481 // If "include" is an exact match for the current file
@@ -468,17 +519,6 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
468519 return nil
469520 })
470521 }
471-
472- // Make sure to check the error on Close.
473- if err := ta .TarWriter .Close (); err != nil {
474- logrus .Debugf ("Can't close tar writer: %s" , err )
475- }
476- if err := compressWriter .Close (); err != nil {
477- logrus .Debugf ("Can't close compress writer: %s" , err )
478- }
479- if err := pipeWriter .Close (); err != nil {
480- logrus .Debugf ("Can't close pipe writer: %s" , err )
481- }
482522 }()
483523
484524 return pipeReader , nil
@@ -543,17 +583,31 @@ loop:
543583 // the layer is also a directory. Then we want to merge them (i.e.
544584 // just apply the metadata from the layer).
545585 if fi , err := os .Lstat (path ); err == nil {
586+ if options .NoOverwriteDirNonDir && fi .IsDir () && hdr .Typeflag != tar .TypeDir {
587+ // If NoOverwriteDirNonDir is true then we cannot replace
588+ // an existing directory with a non-directory from the archive.
589+ return fmt .Errorf ("cannot overwrite directory %q with non-directory %q" , path , dest )
590+ }
591+
592+ if options .NoOverwriteDirNonDir && ! fi .IsDir () && hdr .Typeflag == tar .TypeDir {
593+ // If NoOverwriteDirNonDir is true then we cannot replace
594+ // an existing non-directory with a directory from the archive.
595+ return fmt .Errorf ("cannot overwrite non-directory %q with directory %q" , path , dest )
596+ }
597+
546598 if fi .IsDir () && hdr .Name == "." {
547599 continue
548600 }
601+
549602 if ! (fi .IsDir () && hdr .Typeflag == tar .TypeDir ) {
550603 if err := os .RemoveAll (path ); err != nil {
551604 return err
552605 }
553606 }
554607 }
555608 trBuf .Reset (tr )
556- if err := createTarFile (path , dest , hdr , trBuf , ! options .NoLchown ); err != nil {
609+
610+ if err := createTarFile (path , dest , hdr , trBuf , ! options .NoLchown , options .ChownOpts ); err != nil {
557611 return err
558612 }
559613
0 commit comments