From 056232e4e5006ab8cd19f35ce7498f729db25b5c Mon Sep 17 00:00:00 2001 From: Doug Rabson Date: Fri, 23 Sep 2022 11:26:31 +0100 Subject: [PATCH] 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 --- pkg/archive/archive.go | 15 +++++++ pkg/archive/diff.go | 17 ++++++++ tests/import-layer.bats | 93 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+) diff --git a/pkg/archive/archive.go b/pkg/archive/archive.go index 4b397f7e3..6209205b3 100644 --- a/pkg/archive/archive.go +++ b/pkg/archive/archive.go @@ -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 } diff --git a/pkg/archive/diff.go b/pkg/archive/diff.go index bb029d861..8fec5af38 100644 --- a/pkg/archive/diff.go +++ b/pkg/archive/diff.go @@ -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 diff --git a/tests/import-layer.bats b/tests/import-layer.bats index 139f02807..ee33e381a 100644 --- a/tests/import-layer.bats +++ b/tests/import-layer.bats @@ -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 +}