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 +}