130 lines
4.1 KiB
Go
130 lines
4.1 KiB
Go
package archive
|
|
|
|
import (
|
|
"archive/tar"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/moby/sys/userns"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter {
|
|
if format == OverlayWhiteoutFormat {
|
|
return newOverlayWhiteoutConverter()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type overlayWhiteoutConverter struct {
|
|
opaqueXattr string
|
|
}
|
|
|
|
func newOverlayWhiteoutConverter() overlayWhiteoutConverter {
|
|
opaqueXattr := "trusted.overlay.opaque"
|
|
if userns.RunningInUserNS() {
|
|
opaqueXattr = "user.overlay.opaque"
|
|
}
|
|
return overlayWhiteoutConverter{
|
|
opaqueXattr: opaqueXattr,
|
|
}
|
|
}
|
|
|
|
func (c overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, filePath string, fi os.FileInfo) (wo *tar.Header, _ 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
|
|
dir, filename := path.Split(hdr.Name)
|
|
hdr.Name = path.Join(dir, WhiteoutPrefix+filename)
|
|
hdr.Mode = 0o600
|
|
hdr.Typeflag = tar.TypeReg
|
|
hdr.Size = 0
|
|
}
|
|
|
|
if !fi.IsDir() {
|
|
// FIXME(thaJeztah): return a sentinel error instead of nil, nil
|
|
return nil, nil
|
|
}
|
|
|
|
// convert opaque dirs to AUFS format by writing an empty file with the prefix
|
|
opaque, err := lgetxattr(filePath, c.opaqueXattr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(opaque) != 1 || opaque[0] != 'y' {
|
|
// FIXME(thaJeztah): return a sentinel error instead of nil, nil
|
|
return nil, nil
|
|
}
|
|
delete(hdr.PAXRecords, paxSchilyXattr+c.opaqueXattr)
|
|
|
|
// create a header for the whiteout file
|
|
// it should inherit some properties from the parent, but be a regular file
|
|
return &tar.Header{
|
|
Typeflag: tar.TypeReg,
|
|
Mode: hdr.Mode & int64(os.ModePerm),
|
|
Name: path.Join(hdr.Name, WhiteoutOpaqueDir), // #nosec G305 -- An archive is being created, not extracted.
|
|
Size: 0,
|
|
Uid: hdr.Uid,
|
|
Uname: hdr.Uname,
|
|
Gid: hdr.Gid,
|
|
Gname: hdr.Gname,
|
|
AccessTime: hdr.AccessTime,
|
|
ChangeTime: hdr.ChangeTime,
|
|
}, nil
|
|
}
|
|
|
|
func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, filePath string) (bool, error) {
|
|
base := filepath.Base(filePath)
|
|
dir := filepath.Dir(filePath)
|
|
|
|
switch base {
|
|
case WhiteoutPrefix, WhiteoutPrefix + ".", WhiteoutPrefix + "..":
|
|
return false, fmt.Errorf("invalid whiteout entry %q", hdr.Name)
|
|
|
|
case WhiteoutOpaqueDir:
|
|
// If a directory is marked as opaque by the AUFS special file, we need to translate that to overlay.
|
|
if err := unix.Setxattr(dir, c.opaqueXattr, []byte{'y'}, 0); err != nil {
|
|
return false, fmt.Errorf("setxattr('%s', %s=y): %w", dir, c.opaqueXattr, err)
|
|
}
|
|
// Don't write the whiteout file itself.
|
|
return false, nil
|
|
|
|
default:
|
|
originalBase, ok := strings.CutPrefix(base, WhiteoutPrefix)
|
|
if !ok {
|
|
// Regular file.
|
|
return true, nil
|
|
}
|
|
// If a file was deleted, and we are using overlay, we need to create a character device.
|
|
originalPath := filepath.Join(dir, originalBase)
|
|
if err := unix.Mknod(originalPath, unix.S_IFCHR, 0); err != nil {
|
|
return false, fmt.Errorf("failed to mknod('%s', S_IFCHR, 0): %w", originalPath, err)
|
|
}
|
|
|
|
// Header IDs have already been remapped. Optimize the common non-remapped
|
|
// root-owned (0:0) case by assuming the created whiteout has the expected
|
|
// ownership, rather than comparing against the effective UID/GID or stat'ing
|
|
// the created node to verify it.
|
|
if hdr.Uid != 0 || hdr.Gid != 0 {
|
|
// TODO(thaJeztah): Revisit whether whiteout ownership needs to be preserved.
|
|
//
|
|
// This was added in the original overlay whiteout implementation:
|
|
// https://github.com/moby/moby/pull/18560 / https://github.com/moby/moby/pull/22126
|
|
//
|
|
// OverlayFS documents whiteouts in terms of a character device with device
|
|
// number 0:0, not ownership: https://docs.kernel.org/filesystems/overlayfs.html#whiteouts-and-opaque-directories
|
|
//
|
|
// If ownership is not required, this Lchown can be removed to avoid the remaining TOCTOU window.
|
|
if err := os.Lchown(originalPath, hdr.Uid, hdr.Gid); err != nil {
|
|
return false, err
|
|
}
|
|
}
|
|
|
|
// Don't write the whiteout file itself.
|
|
return false, nil
|
|
}
|
|
}
|