diff --git a/app-container/actool/build.go b/app-container/actool/build.go index a1730cc..5600639 100644 --- a/app-container/actool/build.go +++ b/app-container/actool/build.go @@ -37,6 +37,8 @@ func init() { } func buildWalker(root string, aw aci.ArchiveWriter, rootfs bool) filepath.WalkFunc { + // cache of inode -> filepath, used to leverage hard links in the archive + inos := map[uint64]string{} return func(path string, info os.FileInfo, err error) error { if err != nil { return err @@ -81,7 +83,12 @@ func buildWalker(root string, aw aci.ArchiveWriter, rootfs bool) filepath.WalkFu // modify the Name field of the returned header to provide the // full path name of the file. hdr.Name = relpath - tarheader.Populate(hdr, info) + tarheader.Populate(hdr, info, inos) + // If the file is a hard link we don't need the contents + if hdr.Typeflag == tar.TypeLink { + hdr.Size = 0 + file = nil + } aw.AddFile(relpath, hdr, file) return nil diff --git a/pkg/tarheader/pop_darwin.go b/pkg/tarheader/pop_darwin.go index 99144db..dd0176a 100644 --- a/pkg/tarheader/pop_darwin.go +++ b/pkg/tarheader/pop_darwin.go @@ -13,7 +13,7 @@ func init() { populateHeaderStat = append(populateHeaderStat, populateHeaderCtime) } -func populateHeaderCtime(h *tar.Header, fi os.FileInfo) { +func populateHeaderCtime(h *tar.Header, fi os.FileInfo, _ map[uint64]string) { st, ok := fi.Sys().(*syscall.Stat_t) if !ok { return diff --git a/pkg/tarheader/pop_linux.go b/pkg/tarheader/pop_linux.go index aec5c73..16c0c7e 100644 --- a/pkg/tarheader/pop_linux.go +++ b/pkg/tarheader/pop_linux.go @@ -11,7 +11,7 @@ func init() { populateHeaderStat = append(populateHeaderStat, populateHeaderCtime) } -func populateHeaderCtime(h *tar.Header, fi os.FileInfo) { +func populateHeaderCtime(h *tar.Header, fi os.FileInfo, _ map[uint64]string) { st, ok := fi.Sys().(*syscall.Stat_t) if !ok { return diff --git a/pkg/tarheader/pop_posix.go b/pkg/tarheader/pop_posix.go index 99dcc42..b454d7f 100644 --- a/pkg/tarheader/pop_posix.go +++ b/pkg/tarheader/pop_posix.go @@ -10,11 +10,19 @@ func init() { populateHeaderStat = append(populateHeaderStat, populateHeaderUnix) } -func populateHeaderUnix(h *tar.Header, fi os.FileInfo) { +func populateHeaderUnix(h *tar.Header, fi os.FileInfo, seen map[uint64]string) { st, ok := fi.Sys().(*syscall.Stat_t) if !ok { return } h.Uid = int(st.Uid) h.Gid = int(st.Gid) + // If we have already seen this inode, generate a hardlink + p, ok := seen[uint64(st.Ino)] + if ok { + h.Linkname = p + h.Typeflag = tar.TypeLink + } else { + seen[uint64(st.Ino)] = h.Name + } } diff --git a/pkg/tarheader/tarheader.go b/pkg/tarheader/tarheader.go index f570ac0..d74ec90 100644 --- a/pkg/tarheader/tarheader.go +++ b/pkg/tarheader/tarheader.go @@ -5,10 +5,10 @@ import ( "os" ) -var populateHeaderStat []func(h *tar.Header, fi os.FileInfo) +var populateHeaderStat []func(h *tar.Header, fi os.FileInfo, seen map[uint64]string) -func Populate(h *tar.Header, fi os.FileInfo) { +func Populate(h *tar.Header, fi os.FileInfo, seen map[uint64]string) { for _, pop := range populateHeaderStat { - pop(h, fi) + pop(h, fi, seen) } }