internal/pkg: automatic overlay mount on root
All checks were successful
Test / Create distribution (push) Successful in 53s
Test / Sandbox (push) Successful in 3m9s
Test / ShareFS (push) Successful in 4m48s
Test / Sandbox (race detector) (push) Successful in 5m24s
Test / Hakurei (push) Successful in 5m37s
Test / Hpkg (push) Successful in 5m34s
Test / Hakurei (race detector) (push) Successful in 7m24s
Test / Flake checks (push) Successful in 1m46s

This makes it possible to use an Artifact as root without arranging for directory creation in the Artifact ahead of time.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
2026-01-06 22:19:47 +09:00
parent 53d80f4b66
commit ce0064384d
4 changed files with 126 additions and 23 deletions

View File

@@ -278,7 +278,7 @@ func TestFlatten(t *testing.T) {
"checksum": {Mode: fs.ModeDir | 0700},
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9": {Mode: fs.ModeDir | 0500},
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check": {Mode: 0400, Data: []byte{0x0}},
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check": {Mode: 0400, Data: []byte{0}},
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
"checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400, Data: []byte{}},
@@ -294,7 +294,7 @@ func TestFlatten(t *testing.T) {
{Mode: fs.ModeDir | 0700, Path: "checksum"},
{Mode: fs.ModeDir | 0500, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9"},
{Mode: 0400, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check", Data: []byte{0x0}},
{Mode: 0400, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check", Data: []byte{0}},
{Mode: fs.ModeDir | 0500, Path: "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU"},
{Mode: 0400, Path: "checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb", Data: []byte{}},
@@ -350,6 +350,36 @@ func TestFlatten(t *testing.T) {
{Mode: fs.ModeDir | 0700, Path: "temp"},
{Mode: fs.ModeDir | 0700, Path: "work"},
}, pkg.MustDecode("bBQVFIt0FnOulljgpLnGtuzHSFgwiCMjc4pmc4rHRqXKQ60Q5aBVYp5f6aH9VdZi")},
{"sample exec container overlay root", fstest.MapFS{
".": {Mode: fs.ModeDir | 0700},
"checksum": {Mode: fs.ModeDir | 0700},
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9": {Mode: fs.ModeDir | 0500},
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check": {Mode: 0400, Data: []byte{0}},
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
"identifier": {Mode: fs.ModeDir | 0700},
"identifier/cIjP14zs5el6W_BQhufL_c0vWg-V6Z6pDpsbEa3sYtZ1381u1bKnH3N16RIrw-1S": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
"identifier/nfeISfLeFDr1k-g3hpE1oZ440kTqDdfF8TDpoLdbTPqaMMIl95oiqcvqjRkMjubA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
"temp": {Mode: fs.ModeDir | 0700},
"work": {Mode: fs.ModeDir | 0700},
}, []pkg.FlatEntry{
{Mode: fs.ModeDir | 0700, Path: "."},
{Mode: fs.ModeDir | 0700, Path: "checksum"},
{Mode: fs.ModeDir | 0500, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9"},
{Mode: 0400, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check", Data: []byte{0}},
{Mode: fs.ModeDir | 0500, Path: "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU"},
{Mode: fs.ModeDir | 0700, Path: "identifier"},
{Mode: fs.ModeSymlink | 0777, Path: "identifier/cIjP14zs5el6W_BQhufL_c0vWg-V6Z6pDpsbEa3sYtZ1381u1bKnH3N16RIrw-1S", Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
{Mode: fs.ModeSymlink | 0777, Path: "identifier/nfeISfLeFDr1k-g3hpE1oZ440kTqDdfF8TDpoLdbTPqaMMIl95oiqcvqjRkMjubA", Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
{Mode: fs.ModeDir | 0700, Path: "temp"},
{Mode: fs.ModeDir | 0700, Path: "work"},
}, pkg.MustDecode("gFT9kprYBqEJKifJIl2sHn_3TgULWVLTU4DrYAHiGcRmcdFRZ0YtjiROW820cAEc")},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {

View File

@@ -91,6 +91,13 @@ func (a *execNetArtifact) Cure(c *CureContext) error {
// The working and temporary directories are both created and mounted writable
// on /work and /tmp respectively.
//
// If the first path targets [fhs.AbsRoot], it is made writable via an overlay
// mount with writes going to an ephemeral tmpfs bound to the lifetime of the
// container. This is primarily to make it possible for [container] to set up
// mount points targeting paths not available in the [Artifact] backing root,
// and to accommodate poorly written programs that insist on writing to awkward
// paths, it must not be used as scratch space.
//
// If checksum is non-nil, the resulting [Artifact] implements [KnownChecksum]
// and its container runs in the host net namespace.
//
@@ -262,6 +269,10 @@ func (a *execArtifact) cure(c *CureContext, hostNet bool) (err error) {
z.Dir, z.Env, z.Path, z.Args = a.dir, a.env, a.path, a.args
z.Grow(len(paths) + 4)
if len(paths) > 0 && paths[0][1].Is(fhs.AbsRoot) {
z.OverlayEphemeral(fhs.AbsRoot, paths[0][0])
paths = paths[1:]
}
for _, b := range paths {
z.Bind(b[0], b[1], 0)
}

View File

@@ -26,6 +26,10 @@ var testtoolBin []byte
func TestExec(t *testing.T) {
t.Parallel()
wantChecksumOffline := pkg.MustDecode(
"GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9",
)
checkWithCache(t, []cacheTestCase{
{"offline", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
c.SetStrict(true)
@@ -59,9 +63,7 @@ func TestExec(t *testing.T) {
},
}),
pkg.MustPath("/opt", testtool),
), ignorePathname, pkg.MustDecode(
"GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9",
), nil},
), ignorePathname, wantChecksumOffline, nil},
{"error passthrough", pkg.NewExec(
t.Context(),
@@ -155,6 +157,38 @@ func TestExec(t *testing.T) {
testtoolDestroy(t, base, c)
}, pkg.MustDecode("bBQVFIt0FnOulljgpLnGtuzHSFgwiCMjc4pmc4rHRqXKQ60Q5aBVYp5f6aH9VdZi")},
{"overlay root", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
c.SetStrict(true)
testtool, testtoolDestroy := newTesttool()
msg := message.New(log.New(os.Stderr, "container: ", 0))
msg.SwapVerbose(testing.Verbose())
cureMany(t, c, []cureStep{
{"container", pkg.NewExec(
t.Context(),
msg,
0,
nil,
check.MustAbs("/work"),
[]string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"},
check.MustAbs("/opt/bin/testtool"),
[]string{"testtool"},
pkg.MustPath("/", stubArtifact{
kind: pkg.KindTar,
params: []byte("empty directory"),
cure: func(c *pkg.CureContext) error {
return os.MkdirAll(c.GetWorkDir().String(), 0700)
},
}),
pkg.MustPath("/opt", testtool),
), ignorePathname, wantChecksumOffline, nil},
})
testtoolDestroy(t, base, c)
}, pkg.MustDecode("gFT9kprYBqEJKifJIl2sHn_3TgULWVLTU4DrYAHiGcRmcdFRZ0YtjiROW820cAEc")},
})
}

View File

@@ -9,6 +9,7 @@ import (
"os"
"path"
"slices"
"strings"
"syscall"
"hakurei.app/container/fhs"
@@ -28,9 +29,14 @@ func main() {
if !slices.Equal(os.Args, wantArgs) {
log.Fatalf("Args: %q, want %q", os.Args, wantArgs)
}
if wantEnv := []string{
"HAKUREI_TEST=1",
}; !slices.Equal(wantEnv, os.Environ()) {
var overlayRoot bool
wantEnv := []string{"HAKUREI_TEST=1"}
if len(os.Environ()) == 2 {
overlayRoot = true
wantEnv = []string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"}
}
if !slices.Equal(wantEnv, os.Environ()) {
log.Fatalf("Environ: %q, want %q", os.Environ(), wantEnv)
}
const wantExec = "/opt/bin/testtool"
@@ -51,11 +57,6 @@ func main() {
log.Fatalf("Hostname: %q, want %q", hostname, wantHostname)
}
ident := "oHuqV7p0v1Vd8IdAzjyYM8sfCS0P2LR5tfv5cb6Gbf2ZWUm8Ec-7hYPJ_qr183m7"
if hostNet {
ident = "I3T53NtN6HPAyyodHtq2B0clcsoS1nPdvCEb-Zc5K-hoqFGL2od1mftHhwG7gX1S"
}
var m *vfs.MountInfo
if f, err := os.Open(fhs.Proc + "self/mountinfo"); err != nil {
log.Fatalf("Open: error = %v", err)
@@ -82,20 +83,47 @@ func main() {
}
}
const checksumEmptyDir = "MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU"
ident := "oHuqV7p0v1Vd8IdAzjyYM8sfCS0P2LR5tfv5cb6Gbf2ZWUm8Ec-7hYPJ_qr183m7"
log.Println(m)
if m.Root != "/sysroot" || m.Target != "/" {
log.Fatal("unexpected root mount entry")
}
next := func() { m = m.Next; log.Println(m) }
next()
if path.Base(m.Root) != "OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb" {
log.Fatal("unexpected file artifact checksum")
}
if overlayRoot {
ident = "cIjP14zs5el6W_BQhufL_c0vWg-V6Z6pDpsbEa3sYtZ1381u1bKnH3N16RIrw-1S"
next()
if path.Base(m.Root) != "MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU" {
log.Fatal("unexpected artifact checksum")
if m.Root != "/" || m.Target != "/" ||
m.Source != "overlay" || m.FsType != "overlay" {
log.Fatal("unexpected root mount entry")
}
var lowerdir string
for _, o := range strings.Split(m.FsOptstr, ",") {
const lowerdirKey = "lowerdir="
if strings.HasPrefix(o, lowerdirKey) {
lowerdir = o[len(lowerdirKey):]
}
}
if path.Base(lowerdir) != checksumEmptyDir {
log.Fatal("unexpected artifact checksum")
}
} else {
if hostNet {
ident = "I3T53NtN6HPAyyodHtq2B0clcsoS1nPdvCEb-Zc5K-hoqFGL2od1mftHhwG7gX1S"
}
if m.Root != "/sysroot" || m.Target != "/" {
log.Fatal("unexpected root mount entry")
}
next()
if path.Base(m.Root) != "OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb" {
log.Fatal("unexpected file artifact checksum")
}
next()
if path.Base(m.Root) != checksumEmptyDir {
log.Fatal("unexpected artifact checksum")
}
}
next() // testtool artifact