Update archive package to support overlay whiteouts

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
This commit is contained in:
Derek McGowan 2016-04-06 15:07:29 -07:00
parent 60dbea6c25
commit 9bb13f8f51
5 changed files with 200 additions and 78 deletions

View file

@ -33,6 +33,8 @@ type (
Reader io.Reader Reader io.Reader
// Compression is the state represents if compressed or not. // Compression is the state represents if compressed or not.
Compression int Compression int
// WhiteoutFormat is the format of whiteouts unpacked
WhiteoutFormat int
// TarChownOptions wraps the chown options UID and GID. // TarChownOptions wraps the chown options UID and GID.
TarChownOptions struct { TarChownOptions struct {
UID, GID int UID, GID int
@ -47,9 +49,10 @@ type (
GIDMaps []idtools.IDMap GIDMaps []idtools.IDMap
ChownOpts *TarChownOptions ChownOpts *TarChownOptions
IncludeSourceDir bool IncludeSourceDir bool
// When unpacking convert whiteouts and opaque dirs from aufs format to overlayfs format // WhiteoutFormat is the expected on disk format for whiteout files.
// When packing convert whiteouts and opaque dirs from overlayfs format to aufs format // This format will be converted to the standard format on pack
OverlayFormat bool // and from the standard format on unpack.
WhiteoutFormat WhiteoutFormat
// When unpacking, specifies whether overwriting a directory with a // When unpacking, specifies whether overwriting a directory with a
// non-directory is allowed and vice versa. // non-directory is allowed and vice versa.
NoOverwriteDirNonDir bool NoOverwriteDirNonDir bool
@ -96,6 +99,14 @@ const (
Xz Xz
) )
const (
// AUFSWhiteoutFormat is the default format for whitesouts
AUFSWhiteoutFormat WhiteoutFormat = iota
// OverlayWhiteoutFormat formats whiteout according to the overlay
// standard.
OverlayWhiteoutFormat
)
// IsArchive checks for the magic bytes of a tar or any supported compression // IsArchive checks for the magic bytes of a tar or any supported compression
// algorithm. // algorithm.
func IsArchive(header []byte) bool { func IsArchive(header []byte) bool {
@ -231,6 +242,11 @@ func (compression *Compression) Extension() string {
return "" return ""
} }
type tarWhiteoutConverter interface {
ConvertWrite(*tar.Header, string, os.FileInfo) error
ConvertRead(*tar.Header, string) (bool, error)
}
type tarAppender struct { type tarAppender struct {
TarWriter *tar.Writer TarWriter *tar.Writer
Buffer *bufio.Writer Buffer *bufio.Writer
@ -240,10 +256,11 @@ type tarAppender struct {
UIDMaps []idtools.IDMap UIDMaps []idtools.IDMap
GIDMaps []idtools.IDMap GIDMaps []idtools.IDMap
// `overlayFormat` controls whether to interpret character devices with numbers 0,0 // For packing and unpacking whiteout files in the
// and directories with the attribute `trusted.overlay.opaque` using their overlayfs // non standard format. The whiteout files defined
// meanings and remap them to AUFS format // by the AUFS standard are used as the tar whiteout
OverlayFormat bool // standard.
WhiteoutConverter tarWhiteoutConverter
} }
// canonicalTarName provides a platform-independent and consistent posix-style // canonicalTarName provides a platform-independent and consistent posix-style
@ -332,13 +349,9 @@ func (ta *tarAppender) addTarFile(path, name string) error {
hdr.Gid = xGID hdr.Gid = xGID
} }
if ta.OverlayFormat { if ta.WhiteoutConverter != nil {
// convert whiteouts to AUFS format if err := ta.WhiteoutConverter.ConvertWrite(hdr, path, fi); err != nil {
if fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 { return err
// we just rename the file and make it normal
hdr.Name = WhiteoutPrefix + hdr.Name
hdr.Mode = 0600
hdr.Typeflag = tar.TypeReg
} }
} }
@ -365,30 +378,6 @@ func (ta *tarAppender) addTarFile(path, name string) error {
} }
} }
if ta.OverlayFormat {
// convert opaque dirs to AUFS format by writing an empty file with the prefix
opaque, _ := system.Lgetxattr(path, "trusted.overlay.opaque")
if opaque != nil && len(opaque) == 1 && opaque[0] == 'y' {
// create a header for the whiteout file
// it should inherit some properties from the parent, but be a regular file
whHdr := &tar.Header{
Typeflag: tar.TypeReg,
Mode: hdr.Mode & int64(os.ModePerm),
Name: filepath.Join(name, WhiteoutOpaqueDir),
Size: 0,
Uid: hdr.Uid,
Uname: hdr.Uname,
Gid: hdr.Gid,
Gname: hdr.Gname,
AccessTime: hdr.AccessTime,
ChangeTime: hdr.ChangeTime,
}
if err := ta.TarWriter.WriteHeader(whHdr); err != nil {
return err
}
}
}
return nil return nil
} }
@ -544,12 +533,12 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
go func() { go func() {
ta := &tarAppender{ ta := &tarAppender{
TarWriter: tar.NewWriter(compressWriter), TarWriter: tar.NewWriter(compressWriter),
Buffer: pools.BufioWriter32KPool.Get(nil), Buffer: pools.BufioWriter32KPool.Get(nil),
SeenFiles: make(map[uint64]string), SeenFiles: make(map[uint64]string),
UIDMaps: options.UIDMaps, UIDMaps: options.UIDMaps,
GIDMaps: options.GIDMaps, GIDMaps: options.GIDMaps,
OverlayFormat: options.OverlayFormat, WhiteoutConverter: getWhiteoutConverter(options.WhiteoutFormat),
} }
defer func() { defer func() {
@ -711,6 +700,7 @@ func Unpack(decompressedArchive io.Reader, dest string, options *TarOptions) err
if err != nil { if err != nil {
return err return err
} }
whiteoutConverter := getWhiteoutConverter(options.WhiteoutFormat)
// Iterate through the files in the archive. // Iterate through the files in the archive.
loop: loop:
@ -810,33 +800,12 @@ loop:
hdr.Gid = xGID hdr.Gid = xGID
} }
base := filepath.Base(path) if whiteoutConverter != nil {
dir := filepath.Dir(path) writeFile, err := whiteoutConverter.ConvertRead(hdr, path)
if err != nil {
if options.OverlayFormat { return err
// if a directory is marked as opaque by the AUFS special file, we need to translate that to overlay
if base == WhiteoutOpaqueDir {
if err := syscall.Setxattr(dir, "trusted.overlay.opaque", []byte{'y'}, 0); err != nil {
return err
}
// don't write the file itself
continue
} }
if !writeFile {
// if a file was deleted and we are using overlay, we need to create a character device
if strings.HasPrefix(base, WhiteoutPrefix) {
originalBase := base[len(WhiteoutPrefix):]
originalPath := filepath.Join(dir, originalBase)
if err := syscall.Mknod(originalPath, syscall.S_IFCHR, 0); err != nil {
return err
}
if err := os.Chown(originalPath, hdr.Uid, hdr.Gid); err != nil {
return err
}
// don't write the file itself
continue continue
} }
} }

89
archive/archive_linux.go Normal file
View file

@ -0,0 +1,89 @@
package archive
import (
"archive/tar"
"os"
"path/filepath"
"strings"
"syscall"
"github.com/docker/docker/pkg/system"
)
func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter {
if format == OverlayWhiteoutFormat {
return overlayWhiteoutConverter{}
}
return nil
}
type overlayWhiteoutConverter struct{}
func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os.FileInfo) error {
// convert whiteouts to AUFS format
if fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 {
// we just rename the file and make it normal
hdr.Name = WhiteoutPrefix + hdr.Name
hdr.Mode = 0600
hdr.Typeflag = tar.TypeReg
}
if fi.Mode()&os.ModeDir != 0 {
// convert opaque dirs to AUFS format by writing an empty file with the prefix
opaque, err := system.Lgetxattr(path, "trusted.overlay.opaque")
if err != nil {
return err
}
if opaque != nil && len(opaque) == 1 && opaque[0] == 'y' {
// create a header for the whiteout file
// it should inherit some properties from the parent, but be a regular file
*hdr = tar.Header{
Typeflag: tar.TypeReg,
Mode: hdr.Mode & int64(os.ModePerm),
Name: filepath.Join(hdr.Name, WhiteoutOpaqueDir),
Size: 0,
Uid: hdr.Uid,
Uname: hdr.Uname,
Gid: hdr.Gid,
Gname: hdr.Gname,
AccessTime: hdr.AccessTime,
ChangeTime: hdr.ChangeTime,
}
}
}
return nil
}
func (overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, path string) (bool, error) {
base := filepath.Base(path)
dir := filepath.Dir(path)
// if a directory is marked as opaque by the AUFS special file, we need to translate that to overlay
if base == WhiteoutOpaqueDir {
if err := syscall.Setxattr(dir, "trusted.overlay.opaque", []byte{'y'}, 0); err != nil {
return false, err
}
// don't write the file itself
return false, nil
}
// if a file was deleted and we are using overlay, we need to create a character device
if strings.HasPrefix(base, WhiteoutPrefix) {
originalBase := base[len(WhiteoutPrefix):]
originalPath := filepath.Join(dir, originalBase)
if err := syscall.Mknod(originalPath, syscall.S_IFCHR, 0); err != nil {
return false, err
}
if err := os.Chown(originalPath, hdr.Uid, hdr.Gid); err != nil {
return false, err
}
// don't write the file itself
return false, nil
}
return true, nil
}

7
archive/archive_other.go Normal file
View file

@ -0,0 +1,7 @@
// +build !linux
package archive
func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter {
return nil
}

View file

@ -81,6 +81,33 @@ func sameFsTimeSpec(a, b syscall.Timespec) bool {
// Changes walks the path rw and determines changes for the files in the path, // Changes walks the path rw and determines changes for the files in the path,
// with respect to the parent layers // with respect to the parent layers
func Changes(layers []string, rw string) ([]Change, error) { func Changes(layers []string, rw string) ([]Change, error) {
return changes(layers, rw, aufsDeletedFile, aufsMetadataSkip)
}
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
}
type skipChange func(string) (bool, error)
type deleteChange func(string, string, os.FileInfo) (string, error)
func changes(layers []string, rw string, dc deleteChange, sc skipChange) ([]Change, error) {
var ( var (
changes []Change changes []Change
changedDirs = make(map[string]struct{}) changedDirs = make(map[string]struct{})
@ -105,21 +132,24 @@ func Changes(layers []string, rw string) ([]Change, error) {
return nil return nil
} }
// Skip AUFS metadata if sc != nil {
if matched, err := filepath.Match(string(os.PathSeparator)+WhiteoutMetaPrefix+"*", path); err != nil || matched { if skip, err := sc(path); skip {
return err return err
}
} }
change := Change{ change := Change{
Path: path, Path: path,
} }
deletedFile, err := dc(rw, path, f)
if err != nil {
return err
}
// Find out what kind of modification happened // Find out what kind of modification happened
file := filepath.Base(path) if deletedFile != "" {
// If there is a whiteout, then the file was removed change.Path = deletedFile
if strings.HasPrefix(file, WhiteoutPrefix) {
originalFile := file[len(WhiteoutPrefix):]
change.Path = filepath.Join(filepath.Dir(path), originalFile)
change.Kind = ChangeDelete change.Kind = ChangeDelete
} else { } else {
// Otherwise, the file was added // Otherwise, the file was added

View file

@ -283,3 +283,30 @@ func clen(n []byte) int {
} }
return len(n) return len(n)
} }
// OverlayChanges walks the path rw and determines changes for the files in the path,
// with respect to the parent layers
func OverlayChanges(layers []string, rw string) ([]Change, error) {
return changes(layers, rw, overlayDeletedFile, nil)
}
func overlayDeletedFile(root, path string, fi os.FileInfo) (string, error) {
if fi.Mode()&os.ModeCharDevice != 0 {
s := fi.Sys().(*syscall.Stat_t)
if major(uint64(s.Rdev)) == 0 && minor(uint64(s.Rdev)) == 0 {
return path, nil
}
}
if fi.Mode()&os.ModeDir != 0 {
opaque, err := system.Lgetxattr(filepath.Join(root, path), "trusted.overlay.opaque")
if err != nil {
return "", err
}
if opaque != nil && len(opaque) == 1 && opaque[0] == 'y' {
return path, nil
}
}
return "", nil
}