pkg/archive: Add support for file flags on FreeBSD

This encodes flag information into the tar stream using
ReadFileFlagsToTarHeader and decodes with WriteFileFlagsFromTarHeader.

To support applying diffs to trees with flags, this adds logic to
reset immutable flags during the UnpackLayer process.  To support
immutable directories, we also need to defer setting flags on
directories until after all modifications to the directory contents.
Fortunately, something similar is already in place for setting
directory modify times.

Signed-off-by: Doug Rabson <dfr@rabson.org>
This commit is contained in:
Doug Rabson 2022-09-23 11:26:31 +01:00
parent 2baf25d67c
commit 056232e4e5
3 changed files with 125 additions and 0 deletions

View File

@ -527,6 +527,9 @@ func (ta *tarAppender) addTarFile(path, name string) error {
if err := ReadUserXattrToTarHeader(path, hdr); err != nil {
return err
}
if err := ReadFileFlagsToTarHeader(path, hdr); err != nil {
return err
}
if ta.CopyPass {
copyPassHeader(hdr)
}
@ -770,6 +773,15 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L
}
// We defer setting flags on directories until the end of
// Unpack or UnpackLayer in case setting them makes the
// directory immutable.
if hdr.Typeflag != tar.TypeDir {
if err := WriteFileFlagsFromTarHeader(path, hdr); err != nil {
return err
}
}
if len(errs) > 0 {
logrus.WithFields(logrus.Fields{
"errors": errs,
@ -1101,6 +1113,9 @@ loop:
if err := system.Chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil {
return err
}
if err := WriteFileFlagsFromTarHeader(path, hdr); err != nil {
return err
}
}
return nil
}

View File

@ -145,6 +145,9 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
return nil
}
if _, exists := unpackedPaths[path]; !exists {
if err := resetImmutable(path, nil); err != nil {
return err
}
err := os.RemoveAll(path)
return err
}
@ -156,6 +159,9 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
} else {
originalBase := base[len(WhiteoutPrefix):]
originalPath := filepath.Join(dir, originalBase)
if err := resetImmutable(originalPath, nil); err != nil {
return 0, err
}
if err := os.RemoveAll(originalPath); err != nil {
return 0, err
}
@ -165,7 +171,15 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
// The only exception is when it is a directory *and* the file from
// the layer is also a directory. Then we want to merge them (i.e.
// just apply the metadata from the layer).
//
// We always reset the immutable flag (if present) to allow metadata
// changes and to allow directory modification. The flag will be
// re-applied based on the contents of hdr either at the end for
// directories or in createTarFile otherwise.
if fi, err := os.Lstat(path); err == nil {
if err := resetImmutable(path, &fi); err != nil {
return 0, err
}
if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) {
if err := os.RemoveAll(path); err != nil {
return 0, err
@ -215,6 +229,9 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
if err := system.Chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil {
return 0, err
}
if err := WriteFileFlagsFromTarHeader(path, hdr); err != nil {
return 0, err
}
}
return size, nil

View File

@ -41,3 +41,96 @@ load helpers
checkchanges
checkdiffs
}
set_immutable() {
chflags schg $1
}
reset_immutable() {
chflags noschg $1
}
is_immutable() {
local flags=$(stat -f %#Xf $1)
[ "$((($flags & 0x20000) == 0x20000))" -ne 0 ]
}
@test "import-layer-with-immutable" {
if [ "$OS" != "FreeBSD" ]; then
skip "not supported on $OS"
fi
# Create a layer with a directory containing two files, both
# immutable. The directory is also set as immutablr.
run storage --debug=false create-layer
echo $output
[ "$status" -eq 0 ]
[ "$output" != "" ]
lowerlayer="$output"
run storage --debug=false mount $lowerlayer
[ "$status" -eq 0 ]
[ "$output" != "" ]
local m="$output"
mkdir $m/dir
createrandom $m/dir/layer1file1
createrandom $m/dir/layer1file2
set_immutable $m/dir/layer1file1
set_immutable $m/dir/layer1file2
set_immutable $m/dir
storage unmount $lowerlayer
# Create a second layer which deletes one file and removes immutable from the other
run storage --debug=false create-layer "$lowerlayer"
[ "$status" -eq 0 ]
[ "$output" != "" ]
upperlayer="$output"
run storage --debug=false mount $upperlayer
[ "$status" -eq 0 ]
[ "$output" != "" ]
m="$output"
reset_immutable $m/dir
reset_immutable $m/dir/layer1file1
rm $m/dir/layer1file1
reset_immutable $m/dir/layer1file2
set_immutable $m/dir
storage unmount $upperlayer
# Extract the layers.
storage diff -u -f $TESTDIR/lower.tar $lowerlayer
storage diff -u -f $TESTDIR/upper.tar $upperlayer
# Delete the layers.
storage delete-layer $upperlayer
storage delete-layer $lowerlayer
# Import new layers using the layer diffs.
run storage --debug=false import-layer -f $TESTDIR/lower.tar
[ "$status" -eq 0 ]
[ "$output" != "" ]
lowerlayer="$output"
run storage --debug=false import-layer -f $TESTDIR/upper.tar "$lowerlayer"
[ "$status" -eq 0 ]
[ "$output" != "" ]
upperlayer="$output"
# Verify layer contents
run storage --debug=false mount $lowerlayer
[ "$status" -eq 0 ]
[ "$output" != "" ]
m="$output"
is_immutable $m/dir/layer1file1
is_immutable $m/dir/layer1file2
storage unmount $lowerlayer
run storage --debug=false mount $upperlayer
[ "$status" -eq 0 ]
[ "$output" != "" ]
m="$output"
[ ! -f $m/dir/layer1file1 ]
! is_immutable $m/dir/layer1file2
storage unmount $upperlayer
storage delete-layer $upperlayer
storage delete-layer $lowerlayer
}