Skip to content

Commit a74799b

Browse files
author
Josh Hawn
committed
pkg/archive: new utilities for copying resources
Adds TarResource and CopyTo functions to be used for creating archives for use with the new `docker cp` behavior. Adds multiple test cases for the CopyFrom and CopyTo functions in the pkg/archive package. Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn)
1 parent 3ee15ac commit a74799b

File tree

5 files changed

+1031
-32
lines changed

5 files changed

+1031
-32
lines changed

pkg/archive/archive.go

Lines changed: 83 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,23 @@ import (
2525
)
2626

2727
type (
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

pkg/archive/archive_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -719,7 +719,7 @@ func TestTypeXGlobalHeaderDoesNotFail(t *testing.T) {
719719
t.Fatal(err)
720720
}
721721
defer os.RemoveAll(tmpDir)
722-
err = createTarFile(filepath.Join(tmpDir, "pax_global_header"), tmpDir, &hdr, nil, true)
722+
err = createTarFile(filepath.Join(tmpDir, "pax_global_header"), tmpDir, &hdr, nil, true, nil)
723723
if err != nil {
724724
t.Fatal(err)
725725
}

0 commit comments

Comments
 (0)