Update archive package to support overlay whiteouts
Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
This commit is contained in:
parent
60dbea6c25
commit
9bb13f8f51
5 changed files with 200 additions and 78 deletions
|
@ -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
89
archive/archive_linux.go
Normal 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
7
archive/archive_other.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package archive
|
||||||
|
|
||||||
|
func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue