aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-01-06 22:19:47 +0900
committerOphestra <cat@gensokyo.uk>2026-01-06 22:19:47 +0900
commitce0064384d782166b2b4d9277be49fd52872f34f (patch)
treef4dfe1e5446f8b7bf41419306c560bef13d66964 /internal
parent53d80f4b66848b046c4852e502779796e260463d (diff)
internal/pkg: automatic overlay mount on root
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>
Diffstat (limited to 'internal')
-rw-r--r--internal/pkg/dir_test.go34
-rw-r--r--internal/pkg/exec.go11
-rw-r--r--internal/pkg/exec_test.go40
-rw-r--r--internal/pkg/testdata/main.go64
4 files changed, 126 insertions, 23 deletions
diff --git a/internal/pkg/dir_test.go b/internal/pkg/dir_test.go
index 3038526f..c6cf1c43 100644
--- a/internal/pkg/dir_test.go
+++ b/internal/pkg/dir_test.go
@@ -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) {
diff --git a/internal/pkg/exec.go b/internal/pkg/exec.go
index 930624fc..c1e0cc34 100644
--- a/internal/pkg/exec.go
+++ b/internal/pkg/exec.go
@@ -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)
}
diff --git a/internal/pkg/exec_test.go b/internal/pkg/exec_test.go
index 5a14f786..76bb7566 100644
--- a/internal/pkg/exec_test.go
+++ b/internal/pkg/exec_test.go
@@ -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")},
})
}
diff --git a/internal/pkg/testdata/main.go b/internal/pkg/testdata/main.go
index 257d748f..bbf23400 100644
--- a/internal/pkg/testdata/main.go
+++ b/internal/pkg/testdata/main.go
@@ -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