
Previously hardlinking to an unmodified file or linking to a file which was touched by not detected as modified caused a new file to be created on unpack. This new file and the original source file were not linked since no link record was created in the tar. This change addresses this by adding links for all hardlinks to a file when it is detected as changed. These links will be written after the source file is written and may occur out of order in regard to file name. Signed-off-by: Derek McGowan <derek@mcgstyle.net>
101 lines
3.0 KiB
Go
101 lines
3.0 KiB
Go
package fs
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/stevvooe/continuity/sysx"
|
|
)
|
|
|
|
// whiteouts are files with a special meaning for the layered filesystem.
|
|
// Docker uses AUFS whiteout files inside exported archives. In other
|
|
// filesystems these files are generated/handled on tar creation/extraction.
|
|
|
|
// whiteoutPrefix prefix means file is a whiteout. If this is followed by a
|
|
// filename this means that file has been removed from the base layer.
|
|
const whiteoutPrefix = ".wh."
|
|
|
|
// whiteoutMetaPrefix prefix means whiteout has a special meaning and is not
|
|
// for removing an actual file. Normally these files are excluded from exported
|
|
// archives.
|
|
const whiteoutMetaPrefix = whiteoutPrefix + whiteoutPrefix
|
|
|
|
// whiteoutLinkDir is a directory AUFS uses for storing hardlink links to other
|
|
// layers. Normally these should not go into exported archives and all changed
|
|
// hardlinks should be copied to the top layer.
|
|
const whiteoutLinkDir = whiteoutMetaPrefix + "plnk"
|
|
|
|
// whiteoutOpaqueDir file means directory has been made opaque - meaning
|
|
// readdir calls to this directory do not follow to lower layers.
|
|
const whiteoutOpaqueDir = whiteoutMetaPrefix + ".opq"
|
|
|
|
// detectDirDiff returns diff dir options if a directory could
|
|
// be found in the mount info for upper which is the direct
|
|
// diff with the provided lower directory
|
|
func detectDirDiff(upper, lower string) *diffDirOptions {
|
|
// TODO: get mount options for upper
|
|
// TODO: detect AUFS
|
|
// TODO: detect overlay
|
|
return nil
|
|
}
|
|
|
|
func aufsMetadataSkip(path string) (skip bool, err error) {
|
|
skip, err = filepath.Match(string(os.PathSeparator)+whiteoutMetaPrefix+"*", path)
|
|
if err != nil {
|
|
skip = true
|
|
}
|
|
return
|
|
}
|
|
|
|
func aufsDeletedFile(root, path string, fi os.FileInfo) (string, error) {
|
|
f := filepath.Base(path)
|
|
|
|
// If there is a whiteout, then the file was removed
|
|
if strings.HasPrefix(f, whiteoutPrefix) {
|
|
originalFile := f[len(whiteoutPrefix):]
|
|
return filepath.Join(filepath.Dir(path), originalFile), nil
|
|
}
|
|
|
|
return "", nil
|
|
}
|
|
|
|
// compareSysStat returns whether the stats are equivalent,
|
|
// whether the files are considered the same file, and
|
|
// an error
|
|
func compareSysStat(s1, s2 interface{}) (bool, error) {
|
|
ls1, ok := s1.(*syscall.Stat_t)
|
|
if !ok {
|
|
return false, nil
|
|
}
|
|
ls2, ok := s2.(*syscall.Stat_t)
|
|
if !ok {
|
|
return false, nil
|
|
}
|
|
|
|
return ls1.Mode == ls2.Mode && ls1.Uid == ls2.Uid && ls1.Gid == ls2.Gid && ls1.Rdev == ls2.Rdev, nil
|
|
}
|
|
|
|
func compareCapabilities(p1, p2 string) (bool, error) {
|
|
c1, err := sysx.LGetxattr(p1, "security.capability")
|
|
if err != nil && err != syscall.ENODATA {
|
|
return false, errors.Wrapf(err, "failed to get xattr for %s", p1)
|
|
}
|
|
c2, err := sysx.LGetxattr(p2, "security.capability")
|
|
if err != nil && err != syscall.ENODATA {
|
|
return false, errors.Wrapf(err, "failed to get xattr for %s", p2)
|
|
}
|
|
return bytes.Equal(c1, c2), nil
|
|
}
|
|
|
|
func isLinked(f os.FileInfo) bool {
|
|
s, ok := f.Sys().(*syscall.Stat_t)
|
|
if !ok {
|
|
return false
|
|
}
|
|
return !f.IsDir() && s.Nlink > 1
|
|
}
|