Skip to content

Commit d821c21

Browse files
committed
tarconv: add strip whiteout mode for bottom layers
Signed-off-by: Derek McGowan <derek@mcg.dev>
1 parent ced78eb commit d821c21

2 files changed

Lines changed: 67 additions & 1 deletion

File tree

tarconv/apply.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
// via direct writer calls, without staging an intermediate fs.FS.
1919
//
2020
// The single entry point is [Apply]. It handles all tar entry types (regular
21-
// files, directories, symlinks, hard links, device nodes, FIFOs) and three
21+
// files, directories, symlinks, hard links, device nodes, FIFOs) and four
2222
// whiteout strategies selectable via options:
2323
//
2424
// - Default (no option): translate AUFS/OCI whiteouts to overlayfs xattrs.
@@ -27,6 +27,9 @@
2727
// Suitable for flat merged images where all layers are applied in sequence.
2828
// - [WithPreserveWhiteouts]: keep .wh.* entries as plain files.
2929
// Suitable for tooling that needs the raw tar content.
30+
// - [WithStripWhiteouts]: drop .wh.* entries entirely.
31+
// Suitable for the bottommost layer of a chain, where no lower layer
32+
// exists for a whiteout to hide.
3033
//
3134
// Tar-index mode is enabled by creating the [erofs.Writer] with
3235
// [erofs.WithDataFile] pointing at a file that receives the raw tar bytes,
@@ -75,6 +78,9 @@ const (
7578
whiteoutMerge
7679
// whiteoutPreserve keeps whiteout entries as plain regular files.
7780
whiteoutPreserve
81+
// whiteoutStrip drops whiteout entries entirely: neither translated,
82+
// preserved, nor acted on.
83+
whiteoutStrip
7884
)
7985

8086
// config holds the parsed options for an Apply call.
@@ -106,6 +112,15 @@ func WithPreserveWhiteouts() Option {
106112
return func(c *config) { c.whiteouts = whiteoutPreserve }
107113
}
108114

115+
// WithStripWhiteouts makes Apply drop .wh.* and .wh..wh..opq entries
116+
// entirely: they are neither translated to overlayfs representation nor
117+
// preserved as plain files. Use this for the bottommost layer of a chain,
118+
// where no lower layer exists for a whiteout to hide, so the whiteout
119+
// carries no meaning.
120+
func WithStripWhiteouts() Option {
121+
return func(c *config) { c.whiteouts = whiteoutStrip }
122+
}
123+
109124
// WithTarIndexData enables tar-index mode.
110125
//
111126
// In tar-index mode Apply does not copy file payload bytes into the EROFS
@@ -147,6 +162,7 @@ type pendingLink struct {
147162
//
148163
// Use [WithMerge] to resolve whiteouts structurally instead (flat merged image).
149164
// Use [WithPreserveWhiteouts] to keep whiteout entries as plain files.
165+
// Use [WithStripWhiteouts] to drop whiteout entries entirely.
150166
//
151167
// Hard links may appear in any order. Links whose targets have not yet appeared
152168
// are queued and resolved as subsequent entries are processed. An unresolved
@@ -212,6 +228,8 @@ func Apply(w *erofs.Writer, r io.Reader, opts ...Option) error {
212228
if err := setOpaqueXattr(w, dir, hdr); err != nil {
213229
return fmt.Errorf("tarconv: opaque %s: %w", dir, err)
214230
}
231+
case whiteoutStrip:
232+
// Nothing below this layer to hide; drop the marker.
215233
}
216234
} else {
217235
target := path.Join(dir, base[len(whiteoutPrefix):])
@@ -237,6 +255,8 @@ func Apply(w *erofs.Writer, r io.Reader, opts ...Option) error {
237255
}
238256
pendingOrigin[dir] = true
239257
}
258+
case whiteoutStrip:
259+
// Nothing below this layer to hide; drop the marker.
240260
}
241261
}
242262
// Drain any data bytes (whiteouts are zero-size in practice but be safe).

tarconv/convert_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,52 @@ func TestConvertWhiteouts(t *testing.T) {
544544
}
545545
}
546546

547+
// TestStripWhiteouts checks that WithStripWhiteouts drops whiteout entries
548+
// entirely: no overlay char device, no opaque xattr, no origin xattr.
549+
func TestStripWhiteouts(t *testing.T) {
550+
tarData := makeTar(t, func(tw *tar.Writer) {
551+
tw.WriteHeader(&tar.Header{Typeflag: tar.TypeDir, Name: "lib/", Mode: 0o755, ModTime: epoch})
552+
tw.WriteHeader(&tar.Header{Typeflag: tar.TypeReg, Name: "lib/.wh..wh..opq", Size: 0, ModTime: epoch})
553+
tw.WriteHeader(&tar.Header{Typeflag: tar.TypeReg, Name: "lib/.wh.removed.so", Size: 0, ModTime: epoch})
554+
tw.WriteHeader(&tar.Header{Typeflag: tar.TypeReg, Name: "lib/kept.so", Size: 4, Mode: 0o644, ModTime: epoch})
555+
tw.Write([]byte("keep"))
556+
})
557+
out := &buf{}
558+
w := erofs.Create(out)
559+
if err := tarconv.Apply(w, bytes.NewReader(tarData), tarconv.WithStripWhiteouts()); err != nil {
560+
t.Fatalf("Apply(WithStripWhiteouts): %v", err)
561+
}
562+
if err := w.Close(); err != nil {
563+
t.Fatalf("Writer.Close: %v", err)
564+
}
565+
img := out.b
566+
fsckImage(t, img)
567+
fsys := openImage(t, img)
568+
569+
// Neither whiteout name should exist as an entry of any kind.
570+
if _, err := fs.Stat(fsys, "lib/removed.so"); !errors.Is(err, fs.ErrNotExist) {
571+
t.Errorf("lib/removed.so: expected ErrNotExist, got %v", err)
572+
}
573+
if _, err := fs.Stat(fsys, "lib/.wh.removed.so"); !errors.Is(err, fs.ErrNotExist) {
574+
t.Errorf("lib/.wh.removed.so: expected ErrNotExist (not preserved), got %v", err)
575+
}
576+
if _, err := fs.Stat(fsys, "lib/.wh..wh..opq"); !errors.Is(err, fs.ErrNotExist) {
577+
t.Errorf("lib/.wh..wh..opq: expected ErrNotExist (not preserved), got %v", err)
578+
}
579+
580+
// lib itself should carry neither the opaque nor the origin xattr.
581+
info := checkStat(t, fsys, "lib")
582+
st := info.Sys().(*erofs.Stat)
583+
if _, ok := st.Xattrs[overlayOpaqueXattr]; ok {
584+
t.Errorf("lib: unexpected opaque xattr, xattrs=%v", st.Xattrs)
585+
}
586+
if _, ok := st.Xattrs["trusted.overlay.origin"]; ok {
587+
t.Errorf("lib: unexpected trusted.overlay.origin, xattrs=%v", st.Xattrs)
588+
}
589+
590+
checkFile(t, fsys, "lib/kept.so", "keep")
591+
}
592+
547593
// TestConvertOpaqueBeforeDir tests that the opaque xattr is applied even when
548594
// the .wh..wh..opq entry appears before the directory entry itself.
549595
func TestConvertOpaqueBeforeDir(t *testing.T) {

0 commit comments

Comments
 (0)