Skip to content

Commit 0f1f8f6

Browse files
committed
Add parent directories to tar
Alternate solution which better accounts for hard links. Signed-off-by: Derek McGowan <[email protected]>
1 parent eeef202 commit 0f1f8f6

2 files changed

Lines changed: 103 additions & 3 deletions

File tree

archive/tar.go

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ type changeWriter struct {
291291
whiteoutT time.Time
292292
inodeSrc map[uint64]string
293293
inodeRefs map[uint64][]string
294+
addedDirs map[string]struct{}
294295
}
295296

296297
func newChangeWriter(w io.Writer, source string) *changeWriter {
@@ -300,6 +301,7 @@ func newChangeWriter(w io.Writer, source string) *changeWriter {
300301
whiteoutT: time.Now(),
301302
inodeSrc: map[uint64]string{},
302303
inodeRefs: map[uint64][]string{},
304+
addedDirs: map[string]struct{}{},
303305
}
304306
}
305307

@@ -312,6 +314,7 @@ func (cw *changeWriter) HandleChange(k fs.ChangeKind, p string, f os.FileInfo, e
312314
whiteOutBase := filepath.Base(p)
313315
whiteOut := filepath.Join(whiteOutDir, whiteoutPrefix+whiteOutBase)
314316
hdr := &tar.Header{
317+
Typeflag: tar.TypeReg,
315318
Name: whiteOut[1:],
316319
Size: 0,
317320
ModTime: cw.whiteoutT,
@@ -381,10 +384,8 @@ func (cw *changeWriter) HandleChange(k fs.ChangeKind, p string, f os.FileInfo, e
381384
additionalLinks = cw.inodeRefs[inode]
382385
delete(cw.inodeRefs, inode)
383386
}
384-
} else if k == fs.ChangeKindUnmodified && !f.IsDir() {
387+
} else if k == fs.ChangeKindUnmodified {
385388
// Nothing to write to diff
386-
// Unmodified directories should still be written to keep
387-
// directory permissions correct on direct unpack
388389
return nil
389390
}
390391

@@ -397,6 +398,9 @@ func (cw *changeWriter) HandleChange(k fs.ChangeKind, p string, f os.FileInfo, e
397398
hdr.PAXRecords[paxSchilyXattr+"security.capability"] = string(capability)
398399
}
399400

401+
if err := cw.includeParents(hdr); err != nil {
402+
return err
403+
}
400404
if err := cw.tw.WriteHeader(hdr); err != nil {
401405
return errors.Wrap(err, "failed to write file header")
402406
}
@@ -426,6 +430,10 @@ func (cw *changeWriter) HandleChange(k fs.ChangeKind, p string, f os.FileInfo, e
426430
hdr.Typeflag = tar.TypeLink
427431
hdr.Linkname = source
428432
hdr.Size = 0
433+
434+
if err := cw.includeParents(hdr); err != nil {
435+
return err
436+
}
429437
if err := cw.tw.WriteHeader(hdr); err != nil {
430438
return errors.Wrap(err, "failed to write file header")
431439
}
@@ -535,6 +543,29 @@ func createTarFile(ctx context.Context, path, extractDir string, hdr *tar.Header
535543
return chtimes(path, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime))
536544
}
537545

546+
func (cw *changeWriter) includeParents(hdr *tar.Header) error {
547+
name := strings.TrimRight(hdr.Name, "/")
548+
fname := filepath.Join(cw.source, name)
549+
parent := filepath.Dir(name)
550+
pname := filepath.Join(cw.source, parent)
551+
552+
// Do not include root directory as parent
553+
if fname != cw.source && pname != cw.source {
554+
_, ok := cw.addedDirs[parent]
555+
if !ok {
556+
cw.addedDirs[parent] = struct{}{}
557+
fi, err := os.Stat(pname)
558+
if err != nil {
559+
return err
560+
}
561+
if err := cw.HandleChange(fs.ChangeKindModify, parent, fi, nil); err != nil {
562+
return err
563+
}
564+
}
565+
}
566+
return nil
567+
}
568+
538569
func copyBuffered(ctx context.Context, dst io.Writer, src io.Reader) (written int64, err error) {
539570
buf := bufferPool.Get().(*[]byte)
540571
defer bufferPool.Put(buf)

archive/tar_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,44 @@ func TestDiffTar(t *testing.T) {
698698
fstest.CreateFile("/d2/f", []byte("ok"), 0644),
699699
),
700700
},
701+
{
702+
name: "HardlinkParentInclusion",
703+
validators: []tarEntryValidator{
704+
dirEntry("d2/", 0755),
705+
fileEntry("d2/l1", []byte("link me"), 0644),
706+
// d1/f1 and its parent is included after the new link,
707+
// before the new link was included, these files would
708+
// not habe needed
709+
dirEntry("d1/", 0755),
710+
linkEntry("d1/f1", "d2/l1"),
711+
dirEntry("d3/", 0755),
712+
fileEntry("d3/l1", []byte("link me"), 0644),
713+
dirEntry("d4/", 0755),
714+
linkEntry("d4/f1", "d3/l1"),
715+
whiteoutEntry("d6/l1"),
716+
whiteoutEntry("d6/l2"),
717+
},
718+
a: fstest.Apply(
719+
fstest.CreateDir("/d1/", 0755),
720+
fstest.CreateFile("/d1/f1", []byte("link me"), 0644),
721+
fstest.CreateDir("/d2/", 0755),
722+
fstest.CreateFile("/d2/f1", []byte("link me"), 0644),
723+
fstest.CreateDir("/d3/", 0755),
724+
fstest.CreateDir("/d4/", 0755),
725+
fstest.CreateFile("/d4/f1", []byte("link me"), 0644),
726+
fstest.CreateDir("/d5/", 0755),
727+
fstest.CreateFile("/d5/f1", []byte("link me"), 0644),
728+
fstest.CreateDir("/d6/", 0755),
729+
fstest.Link("/d1/f1", "/d6/l1"),
730+
fstest.Link("/d5/f1", "/d6/l2"),
731+
),
732+
b: fstest.Apply(
733+
fstest.Link("/d1/f1", "/d2/l1"),
734+
fstest.Link("/d4/f1", "/d3/l1"),
735+
fstest.Remove("/d6/l1"),
736+
fstest.Remove("/d6/l2"),
737+
),
738+
},
701739
}
702740

703741
for _, at := range tests {
@@ -740,6 +778,37 @@ func fileEntry(name string, expected []byte, mode int) tarEntryValidator {
740778
}
741779
}
742780

781+
func linkEntry(name, link string) tarEntryValidator {
782+
return func(hdr *tar.Header, b []byte) error {
783+
if hdr.Typeflag != tar.TypeLink {
784+
return errors.New("not link type")
785+
}
786+
if hdr.Name != name {
787+
return errors.Errorf("wrong name %q, expected %q", hdr.Name, name)
788+
}
789+
if hdr.Linkname != link {
790+
return errors.Errorf("wrong link %q, expected %q", hdr.Linkname, link)
791+
}
792+
return nil
793+
}
794+
}
795+
796+
func whiteoutEntry(name string) tarEntryValidator {
797+
whiteOutDir := filepath.Dir(name)
798+
whiteOutBase := filepath.Base(name)
799+
whiteOut := filepath.Join(whiteOutDir, whiteoutPrefix+whiteOutBase)
800+
801+
return func(hdr *tar.Header, b []byte) error {
802+
if hdr.Typeflag != tar.TypeReg {
803+
return errors.Errorf("not file type: %q", hdr.Typeflag)
804+
}
805+
if hdr.Name != whiteOut {
806+
return errors.Errorf("wrong name %q, expected whiteout %q", hdr.Name, name)
807+
}
808+
return nil
809+
}
810+
}
811+
743812
func makeDiffTarTest(validators []tarEntryValidator, a, b fstest.Applier) func(*testing.T) {
744813
return func(t *testing.T) {
745814
ad, err := ioutil.TempDir("", "test-make-diff-tar-")

0 commit comments

Comments
 (0)