From bc646a66045be595d31909060a6f1b496b98a18f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Payen=20de=20La=20Garanderie?= Date: Tue, 2 Jan 2024 12:12:06 +0100 Subject: [PATCH] Fix hardlink issue with whiteout deletes in the merge snapshotter. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Whiteouts may share the same inode which cause the snapshotter to attempt to create a hardlink between two whiteouts in the destination layer. The problem is that the first whiteout is only created if it is part of the lower layer from the base snapshot. Otherwise, the whiteout is not created in the destination layer because it is not needed. If the first whiteout is not created in the destination, attempting to create a hardlink for the second whiteout will fail. To avoid this issue, this fix disables hardlinks for whiteouts. Signed-off-by: Grégoire Payen de La Garanderie --- client/mergediff_test.go | 27 +++++++++++++++++++++++++++ snapshot/diffapply_unix.go | 5 ++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/client/mergediff_test.go b/client/mergediff_test.go index 4aa0cf39adf6..adeeeb81bc99 100644 --- a/client/mergediff_test.go +++ b/client/mergediff_test.go @@ -428,6 +428,33 @@ func diffOpTestCases() (tests []integration.Test) { ), }) + // Check that deleting together a file from the base and another one from a merge + // does not result in a crash. + deleteFileAfterMergeCmds := []string{ + "rm /unmodifiedDir/deleteFile1 /unmodifiedDir/deleteFile2", + } + + extraContent := llb.Scratch(). + File(llb.Mkdir("/unmodifiedDir", 0755)). + File(llb.Mkfile("/unmodifiedDir/deleteFile2", 0644, []byte("foo"))) + + tests = append(tests, verifyContents{ + name: "TestDiffDeleteFilesAfterMerge", + state: llb.Diff( + base(), + runShell(llb.Merge([]llb.State{ + base(), + extraContent, + }), + joinCmds( + deleteFileAfterMergeCmds, + )...)), + contents: mergeContents( + apply( + fstest.CreateDir("/unmodifiedDir", 0755)), + ), + }) + // Opaque dirs should be converted to the "explicit whiteout" format, as described in the OCI image spec: // https://github.com/opencontainers/image-spec/blob/main/layer.md#opaque-whiteout opaqueDirCmds := []string{ diff --git a/snapshot/diffapply_unix.go b/snapshot/diffapply_unix.go index 72395d33a668..ddba5d117f7a 100644 --- a/snapshot/diffapply_unix.go +++ b/snapshot/diffapply_unix.go @@ -724,7 +724,10 @@ func (d *differ) overlayChanges(ctx context.Context, handle func(context.Context return errors.Errorf("unhandled stat type for %+v", srcfi) } - if !srcfi.IsDir() && c.srcStat.Nlink > 1 { + // Changes with Delete kind may share the same inode even if they are unrelated. + // Skip them to avoid creating hardlinks between whiteouts as whiteouts are not + // always created and may leave the hardlink dangling. + if !srcfi.IsDir() && c.srcStat.Nlink > 1 && c.kind != fs.ChangeKindDelete { if linkSubPath, ok := d.inodes[statInode(c.srcStat)]; ok { c.linkSubPath = linkSubPath } else {