Skip to content

Commit a1c62e0

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

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
@@ -148,6 +163,7 @@ type pendingLink struct {
148163
//
149164
// Use [WithMerge] to resolve whiteouts structurally instead (flat merged image).
150165
// Use [WithPreserveWhiteouts] to keep whiteout entries as plain files.
166+
// Use [WithStripWhiteouts] to drop whiteout entries entirely.
151167
//
152168
// Hard links may appear in any order. Links whose targets have not yet appeared
153169
// are queued and resolved as subsequent entries are processed. An unresolved
@@ -203,6 +219,8 @@ func Apply(w *erofs.Writer, r io.Reader, opts ...Option) error {
203219
if err := setOpaqueXattr(w, dir, hdr); err != nil {
204220
return fmt.Errorf("tarconv: opaque %s: %w", dir, err)
205221
}
222+
case whiteoutStrip:
223+
// Nothing below this layer to hide; drop the marker.
206224
}
207225
} else {
208226
target := path.Join(dir, base[len(whiteoutPrefix):])
@@ -228,6 +246,8 @@ func Apply(w *erofs.Writer, r io.Reader, opts ...Option) error {
228246
}
229247
pendingOrigin[dir] = true
230248
}
249+
case whiteoutStrip:
250+
// Nothing below this layer to hide; drop the marker.
231251
}
232252
}
233253
// 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
@@ -442,6 +442,52 @@ func TestConvertWhiteouts(t *testing.T) {
442442
}
443443
}
444444

445+
// TestStripWhiteouts checks that WithStripWhiteouts drops whiteout entries
446+
// entirely: no overlay char device, no opaque xattr, no origin xattr.
447+
func TestStripWhiteouts(t *testing.T) {
448+
tarData := makeTar(t, func(tw *tar.Writer) {
449+
tw.WriteHeader(&tar.Header{Typeflag: tar.TypeDir, Name: "lib/", Mode: 0o755, ModTime: epoch})
450+
tw.WriteHeader(&tar.Header{Typeflag: tar.TypeReg, Name: "lib/.wh..wh..opq", Size: 0, ModTime: epoch})
451+
tw.WriteHeader(&tar.Header{Typeflag: tar.TypeReg, Name: "lib/.wh.removed.so", Size: 0, ModTime: epoch})
452+
tw.WriteHeader(&tar.Header{Typeflag: tar.TypeReg, Name: "lib/kept.so", Size: 4, Mode: 0o644, ModTime: epoch})
453+
tw.Write([]byte("keep"))
454+
})
455+
out := &buf{}
456+
w := erofs.Create(out)
457+
if err := tarconv.Apply(w, bytes.NewReader(tarData), tarconv.WithStripWhiteouts()); err != nil {
458+
t.Fatalf("Apply(WithStripWhiteouts): %v", err)
459+
}
460+
if err := w.Close(); err != nil {
461+
t.Fatalf("Writer.Close: %v", err)
462+
}
463+
img := out.b
464+
fsckImage(t, img)
465+
fsys := openImage(t, img)
466+
467+
// Neither whiteout name should exist as an entry of any kind.
468+
if _, err := fs.Stat(fsys, "lib/removed.so"); !errors.Is(err, fs.ErrNotExist) {
469+
t.Errorf("lib/removed.so: expected ErrNotExist, got %v", err)
470+
}
471+
if _, err := fs.Stat(fsys, "lib/.wh.removed.so"); !errors.Is(err, fs.ErrNotExist) {
472+
t.Errorf("lib/.wh.removed.so: expected ErrNotExist (not preserved), got %v", err)
473+
}
474+
if _, err := fs.Stat(fsys, "lib/.wh..wh..opq"); !errors.Is(err, fs.ErrNotExist) {
475+
t.Errorf("lib/.wh..wh..opq: expected ErrNotExist (not preserved), got %v", err)
476+
}
477+
478+
// lib itself should carry neither the opaque nor the origin xattr.
479+
info := checkStat(t, fsys, "lib")
480+
st := info.Sys().(*erofs.Stat)
481+
if _, ok := st.Xattrs[overlayOpaqueXattr]; ok {
482+
t.Errorf("lib: unexpected opaque xattr, xattrs=%v", st.Xattrs)
483+
}
484+
if _, ok := st.Xattrs["trusted.overlay.origin"]; ok {
485+
t.Errorf("lib: unexpected trusted.overlay.origin, xattrs=%v", st.Xattrs)
486+
}
487+
488+
checkFile(t, fsys, "lib/kept.so", "keep")
489+
}
490+
445491
// TestConvertOpaqueBeforeDir tests that the opaque xattr is applied even when
446492
// the .wh..wh..opq entry appears before the directory entry itself.
447493
func TestConvertOpaqueBeforeDir(t *testing.T) {

0 commit comments

Comments
 (0)