archive: preserve hardlinks in Tar and Untar
* integration test for preserving hardlinks Signed-off-by: Vincent Batts <vbatts@redhat.com> Signed-off-by: Vincent Batts <vbatts@hashbangbash.com>
This commit is contained in:
parent
0f0aef0590
commit
b17f754fff
3 changed files with 108 additions and 20 deletions
|
@ -153,7 +153,15 @@ func (compression *Compression) Extension() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func addTarFile(path, name string, tw *tar.Writer, twBuf *bufio.Writer) error {
|
||||
type tarAppender struct {
|
||||
TarWriter *tar.Writer
|
||||
Buffer *bufio.Writer
|
||||
|
||||
// for hardlink mapping
|
||||
SeenFiles map[uint64]string
|
||||
}
|
||||
|
||||
func (ta *tarAppender) addTarFile(path, name string) error {
|
||||
fi, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -188,13 +196,28 @@ func addTarFile(path, name string, tw *tar.Writer, twBuf *bufio.Writer) error {
|
|||
|
||||
}
|
||||
|
||||
// if it's a regular file and has more than 1 link,
|
||||
// it's hardlinked, so set the type flag accordingly
|
||||
if fi.Mode().IsRegular() && stat.Nlink > 1 {
|
||||
// a link should have a name that it links too
|
||||
// and that linked name should be first in the tar archive
|
||||
ino := uint64(stat.Ino)
|
||||
if oldpath, ok := ta.SeenFiles[ino]; ok {
|
||||
hdr.Typeflag = tar.TypeLink
|
||||
hdr.Linkname = oldpath
|
||||
hdr.Size = 0 // This Must be here for the writer math to add up!
|
||||
} else {
|
||||
ta.SeenFiles[ino] = name
|
||||
}
|
||||
}
|
||||
|
||||
capability, _ := system.Lgetxattr(path, "security.capability")
|
||||
if capability != nil {
|
||||
hdr.Xattrs = make(map[string]string)
|
||||
hdr.Xattrs["security.capability"] = string(capability)
|
||||
}
|
||||
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
if err := ta.TarWriter.WriteHeader(hdr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -204,17 +227,17 @@ func addTarFile(path, name string, tw *tar.Writer, twBuf *bufio.Writer) error {
|
|||
return err
|
||||
}
|
||||
|
||||
twBuf.Reset(tw)
|
||||
_, err = io.Copy(twBuf, file)
|
||||
ta.Buffer.Reset(ta.TarWriter)
|
||||
_, err = io.Copy(ta.Buffer, file)
|
||||
file.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = twBuf.Flush()
|
||||
err = ta.Buffer.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
twBuf.Reset(nil)
|
||||
ta.Buffer.Reset(nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -345,9 +368,15 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
|
|||
return nil, err
|
||||
}
|
||||
|
||||
tw := tar.NewWriter(compressWriter)
|
||||
|
||||
go func() {
|
||||
ta := &tarAppender{
|
||||
TarWriter: tar.NewWriter(compressWriter),
|
||||
Buffer: pools.BufioWriter32KPool.Get(nil),
|
||||
SeenFiles: make(map[uint64]string),
|
||||
}
|
||||
// this buffer is needed for the duration of this piped stream
|
||||
defer pools.BufioWriter32KPool.Put(ta.Buffer)
|
||||
|
||||
// In general we log errors here but ignore them because
|
||||
// during e.g. a diff operation the container can continue
|
||||
// mutating the filesystem and we can see transient errors
|
||||
|
@ -357,9 +386,6 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
|
|||
options.Includes = []string{"."}
|
||||
}
|
||||
|
||||
twBuf := pools.BufioWriter32KPool.Get(nil)
|
||||
defer pools.BufioWriter32KPool.Put(twBuf)
|
||||
|
||||
var renamedRelFilePath string // For when tar.Options.Name is set
|
||||
for _, include := range options.Includes {
|
||||
filepath.Walk(filepath.Join(srcPath, include), func(filePath string, f os.FileInfo, err error) error {
|
||||
|
@ -395,7 +421,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
|
|||
relFilePath = strings.Replace(relFilePath, renamedRelFilePath, options.Name, 1)
|
||||
}
|
||||
|
||||
if err := addTarFile(filePath, relFilePath, tw, twBuf); err != nil {
|
||||
if err := ta.addTarFile(filePath, relFilePath); err != nil {
|
||||
log.Debugf("Can't add file %s to tar: %s", srcPath, err)
|
||||
}
|
||||
return nil
|
||||
|
@ -403,7 +429,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
|
|||
}
|
||||
|
||||
// Make sure to check the error on Close.
|
||||
if err := tw.Close(); err != nil {
|
||||
if err := ta.TarWriter.Close(); err != nil {
|
||||
log.Debugf("Can't close tar writer: %s", err)
|
||||
}
|
||||
if err := compressWriter.Close(); err != nil {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue