Add parent directories to tar

Alternate solution which better accounts for hard links.

Signed-off-by: Derek McGowan <derek@mcgstyle.net>
This commit is contained in:
Derek McGowan 2017-12-18 16:56:37 -08:00
parent e479165a38
commit c9dd974c27
No known key found for this signature in database
GPG Key ID: F58C5D0A4405ACDB
2 changed files with 103 additions and 3 deletions

View File

@ -395,6 +395,7 @@ type changeWriter struct {
whiteoutT time.Time
inodeSrc map[uint64]string
inodeRefs map[uint64][]string
addedDirs map[string]struct{}
}
func newChangeWriter(w io.Writer, source string) *changeWriter {
@ -404,6 +405,7 @@ func newChangeWriter(w io.Writer, source string) *changeWriter {
whiteoutT: time.Now(),
inodeSrc: map[uint64]string{},
inodeRefs: map[uint64][]string{},
addedDirs: map[string]struct{}{},
}
}
@ -416,6 +418,7 @@ func (cw *changeWriter) HandleChange(k fs.ChangeKind, p string, f os.FileInfo, e
whiteOutBase := filepath.Base(p)
whiteOut := filepath.Join(whiteOutDir, whiteoutPrefix+whiteOutBase)
hdr := &tar.Header{
Typeflag: tar.TypeReg,
Name: whiteOut[1:],
Size: 0,
ModTime: cw.whiteoutT,
@ -485,10 +488,8 @@ func (cw *changeWriter) HandleChange(k fs.ChangeKind, p string, f os.FileInfo, e
additionalLinks = cw.inodeRefs[inode]
delete(cw.inodeRefs, inode)
}
} else if k == fs.ChangeKindUnmodified && !f.IsDir() {
} else if k == fs.ChangeKindUnmodified {
// Nothing to write to diff
// Unmodified directories should still be written to keep
// directory permissions correct on direct unpack
return nil
}
@ -501,6 +502,9 @@ func (cw *changeWriter) HandleChange(k fs.ChangeKind, p string, f os.FileInfo, e
hdr.PAXRecords[paxSchilyXattr+"security.capability"] = string(capability)
}
if err := cw.includeParents(hdr); err != nil {
return err
}
if err := cw.tw.WriteHeader(hdr); err != nil {
return errors.Wrap(err, "failed to write file header")
}
@ -530,6 +534,10 @@ func (cw *changeWriter) HandleChange(k fs.ChangeKind, p string, f os.FileInfo, e
hdr.Typeflag = tar.TypeLink
hdr.Linkname = source
hdr.Size = 0
if err := cw.includeParents(hdr); err != nil {
return err
}
if err := cw.tw.WriteHeader(hdr); err != nil {
return errors.Wrap(err, "failed to write file header")
}
@ -546,6 +554,29 @@ func (cw *changeWriter) Close() error {
return nil
}
func (cw *changeWriter) includeParents(hdr *tar.Header) error {
name := strings.TrimRight(hdr.Name, "/")
fname := filepath.Join(cw.source, name)
parent := filepath.Dir(name)
pname := filepath.Join(cw.source, parent)
// Do not include root directory as parent
if fname != cw.source && pname != cw.source {
_, ok := cw.addedDirs[parent]
if !ok {
cw.addedDirs[parent] = struct{}{}
fi, err := os.Stat(pname)
if err != nil {
return err
}
if err := cw.HandleChange(fs.ChangeKindModify, parent, fi, nil); err != nil {
return err
}
}
}
return nil
}
func copyBuffered(ctx context.Context, dst io.Writer, src io.Reader) (written int64, err error) {
buf := bufferPool.Get().(*[]byte)
defer bufferPool.Put(buf)

View File

@ -698,6 +698,44 @@ func TestDiffTar(t *testing.T) {
fstest.CreateFile("/d2/f", []byte("ok"), 0644),
),
},
{
name: "HardlinkParentInclusion",
validators: []tarEntryValidator{
dirEntry("d2/", 0755),
fileEntry("d2/l1", []byte("link me"), 0644),
// d1/f1 and its parent is included after the new link,
// before the new link was included, these files would
// not habe needed
dirEntry("d1/", 0755),
linkEntry("d1/f1", "d2/l1"),
dirEntry("d3/", 0755),
fileEntry("d3/l1", []byte("link me"), 0644),
dirEntry("d4/", 0755),
linkEntry("d4/f1", "d3/l1"),
whiteoutEntry("d6/l1"),
whiteoutEntry("d6/l2"),
},
a: fstest.Apply(
fstest.CreateDir("/d1/", 0755),
fstest.CreateFile("/d1/f1", []byte("link me"), 0644),
fstest.CreateDir("/d2/", 0755),
fstest.CreateFile("/d2/f1", []byte("link me"), 0644),
fstest.CreateDir("/d3/", 0755),
fstest.CreateDir("/d4/", 0755),
fstest.CreateFile("/d4/f1", []byte("link me"), 0644),
fstest.CreateDir("/d5/", 0755),
fstest.CreateFile("/d5/f1", []byte("link me"), 0644),
fstest.CreateDir("/d6/", 0755),
fstest.Link("/d1/f1", "/d6/l1"),
fstest.Link("/d5/f1", "/d6/l2"),
),
b: fstest.Apply(
fstest.Link("/d1/f1", "/d2/l1"),
fstest.Link("/d4/f1", "/d3/l1"),
fstest.Remove("/d6/l1"),
fstest.Remove("/d6/l2"),
),
},
}
for _, at := range tests {
@ -740,6 +778,37 @@ func fileEntry(name string, expected []byte, mode int) tarEntryValidator {
}
}
func linkEntry(name, link string) tarEntryValidator {
return func(hdr *tar.Header, b []byte) error {
if hdr.Typeflag != tar.TypeLink {
return errors.New("not link type")
}
if hdr.Name != name {
return errors.Errorf("wrong name %q, expected %q", hdr.Name, name)
}
if hdr.Linkname != link {
return errors.Errorf("wrong link %q, expected %q", hdr.Linkname, link)
}
return nil
}
}
func whiteoutEntry(name string) tarEntryValidator {
whiteOutDir := filepath.Dir(name)
whiteOutBase := filepath.Base(name)
whiteOut := filepath.Join(whiteOutDir, whiteoutPrefix+whiteOutBase)
return func(hdr *tar.Header, b []byte) error {
if hdr.Typeflag != tar.TypeReg {
return errors.Errorf("not file type: %q", hdr.Typeflag)
}
if hdr.Name != whiteOut {
return errors.Errorf("wrong name %q, expected whiteout %q", hdr.Name, name)
}
return nil
}
}
func makeDiffTarTest(validators []tarEntryValidator, a, b fstest.Applier) func(*testing.T) {
return func(t *testing.T) {
ad, err := ioutil.TempDir("", "test-make-diff-tar-")