aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-01-06 23:34:43 +0900
committerOphestra <cat@gensokyo.uk>2026-01-06 23:45:08 +0900
commit0df87ab1117bf4da5163ad486a82628f1bada7ed (patch)
tree48745fe27c4c3694c1313bb9030188458fbdd328 /internal
parentaa0a949cefd8c1a1519d892f87aac3119389e271 (diff)
internal/pkg: automatic overlay mount on tmp
This sets up the last Artifact to target /tmp as a writable overlay mount backed by the host side temp directory. This is useful for an Artifact containing source code to be built for another Artifact for example. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal')
-rw-r--r--internal/pkg/dir_test.go30
-rw-r--r--internal/pkg/exec.go42
-rw-r--r--internal/pkg/exec_test.go43
-rw-r--r--internal/pkg/testdata/main.go36
4 files changed, 139 insertions, 12 deletions
diff --git a/internal/pkg/dir_test.go b/internal/pkg/dir_test.go
index c6cf1c43..ab456c95 100644
--- a/internal/pkg/dir_test.go
+++ b/internal/pkg/dir_test.go
@@ -380,6 +380,36 @@ func TestFlatten(t *testing.T) {
{Mode: fs.ModeDir | 0700, Path: "temp"},
{Mode: fs.ModeDir | 0700, Path: "work"},
}, pkg.MustDecode("gFT9kprYBqEJKifJIl2sHn_3TgULWVLTU4DrYAHiGcRmcdFRZ0YtjiROW820cAEc")},
+
+ {"sample exec container overlay temp", 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/3ELJ8l42g6XeIoLGR9LheVhMIwSIleD6VrhsliBuon5DAdBOwFSMqd7aiUI4fll7": {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/3ELJ8l42g6XeIoLGR9LheVhMIwSIleD6VrhsliBuon5DAdBOwFSMqd7aiUI4fll7", 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("_r1IBeMWCkLwQ9Im9w0tV9_CWIOfQlXkkP2CogPHLmZp_AB6W3_8HVZqDV00dNAm")},
}
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 c1e0cc34..8b12694f 100644
--- a/internal/pkg/exec.go
+++ b/internal/pkg/exec.go
@@ -89,7 +89,9 @@ func (a *execNetArtifact) Cure(c *CureContext) error {
// A private instance of /proc and /dev is made available to the container.
//
// The working and temporary directories are both created and mounted writable
-// on /work and /tmp respectively.
+// on /work and /tmp respectively. If one or more paths target [fhs.AbsTmp], the
+// final entry is set up as a writable overlay mount on /tmp backed by the host
+// side temporary directory.
//
// 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
@@ -273,18 +275,46 @@ func (a *execArtifact) cure(c *CureContext, hostNet bool) (err error) {
z.OverlayEphemeral(fhs.AbsRoot, paths[0][0])
paths = paths[1:]
}
+ var overlayTemp *check.Absolute
for _, b := range paths {
+ if b[1].Is(fhs.AbsTmp) {
+ if overlayTemp != nil {
+ z.Bind(overlayTemp, fhs.AbsTmp, 0)
+ }
+ overlayTemp = b[0]
+ continue
+ }
z.Bind(b[0], b[1], 0)
}
z.Bind(
c.GetWorkDir(),
fhs.AbsRoot.Append("work"),
std.BindWritable|std.BindEnsure,
- ).Bind(
- c.GetTempDir(),
- fhs.AbsTmp,
- std.BindWritable|std.BindEnsure,
- ).Proc(fhs.AbsProc).Dev(fhs.AbsDev, true)
+ )
+ if overlayTemp == nil {
+ z.Bind(
+ c.GetTempDir(),
+ fhs.AbsTmp,
+ std.BindWritable|std.BindEnsure,
+ )
+ } else {
+ temp := c.GetTempDir()
+ tempUpper := temp.Append("upper")
+ if err = os.MkdirAll(tempUpper.String(), 0700); err != nil {
+ return
+ }
+ tempWork := temp.Append("work")
+ if err = os.MkdirAll(tempWork.String(), 0700); err != nil {
+ return
+ }
+ z.Overlay(
+ fhs.AbsTmp,
+ tempUpper,
+ tempWork,
+ overlayTemp,
+ )
+ }
+ z.Proc(fhs.AbsProc).Dev(fhs.AbsDev, true)
if err = z.Start(); err != nil {
return
diff --git a/internal/pkg/exec_test.go b/internal/pkg/exec_test.go
index 76bb7566..d1b88766 100644
--- a/internal/pkg/exec_test.go
+++ b/internal/pkg/exec_test.go
@@ -11,6 +11,7 @@ import (
"testing"
"hakurei.app/container/check"
+ "hakurei.app/container/fhs"
"hakurei.app/container/stub"
"hakurei.app/hst"
"hakurei.app/internal/pkg"
@@ -189,6 +190,48 @@ func TestExec(t *testing.T) {
testtoolDestroy(t, base, c)
}, pkg.MustDecode("gFT9kprYBqEJKifJIl2sHn_3TgULWVLTU4DrYAHiGcRmcdFRZ0YtjiROW820cAEc")},
+
+ {"overlay temp", 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("/tmp/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("/tmp/", stubArtifact{
+ kind: pkg.KindTar,
+ params: []byte("empty directory"),
+ cure: func(c *pkg.CureContext) error {
+ return os.MkdirAll(c.GetWorkDir().String(), 0700)
+ },
+ }),
+ pkg.ExecContainerPath{
+ P: fhs.AbsTmp,
+ A: testtool,
+ },
+ ), ignorePathname, wantChecksumOffline, nil},
+ })
+
+ testtoolDestroy(t, base, c)
+ }, pkg.MustDecode("_r1IBeMWCkLwQ9Im9w0tV9_CWIOfQlXkkP2CogPHLmZp_AB6W3_8HVZqDV00dNAm")},
})
}
diff --git a/internal/pkg/testdata/main.go b/internal/pkg/testdata/main.go
index bbf23400..4f3d047e 100644
--- a/internal/pkg/testdata/main.go
+++ b/internal/pkg/testdata/main.go
@@ -39,11 +39,19 @@ func main() {
if !slices.Equal(wantEnv, os.Environ()) {
log.Fatalf("Environ: %q, want %q", os.Environ(), wantEnv)
}
- const wantExec = "/opt/bin/testtool"
+
+ var overlayTmp bool
+ const (
+ wantExec = "/opt/bin/testtool"
+ wantExecOverlay = "/tmp/bin/testtool"
+ )
if got, err := os.Executable(); err != nil {
log.Fatalf("Executable: error = %v", err)
} else if got != wantExec {
- log.Fatalf("Executable: %q, want %q", got, wantExec)
+ if got != wantExecOverlay {
+ log.Fatalf("Executable: %q, want %q", got, wantExec)
+ }
+ overlayTmp = true
}
wantHostname := "cure"
@@ -105,7 +113,6 @@ func main() {
if path.Base(lowerdir) != checksumEmptyDir {
log.Fatal("unexpected artifact checksum")
}
-
} else {
if hostNet {
ident = "I3T53NtN6HPAyyodHtq2B0clcsoS1nPdvCEb-Zc5K-hoqFGL2od1mftHhwG7gX1S"
@@ -126,7 +133,16 @@ func main() {
}
}
- next() // testtool artifact
+ if !overlayTmp {
+ next() // testtool artifact
+ } else {
+ ident = "3ELJ8l42g6XeIoLGR9LheVhMIwSIleD6VrhsliBuon5DAdBOwFSMqd7aiUI4fll7"
+
+ next()
+ if path.Base(m.Root) != checksumEmptyDir || m.Target != "/tmp" {
+ log.Fatal("unexpected artifact checksum")
+ }
+ }
next()
if path.Base(m.Root) != ident || m.Target != "/work" {
@@ -134,8 +150,16 @@ func main() {
}
next()
- if path.Base(m.Root) != ident || m.Target != "/tmp" {
- log.Fatal("unexpected temp mount entry")
+ if !overlayTmp {
+ if path.Base(m.Root) != ident || m.Target != "/tmp" {
+ log.Fatal("unexpected temp mount entry")
+ }
+ } else {
+ // testtool artifact
+ if m.Root != "/" || m.Target != "/tmp" ||
+ m.Source != "overlay" || m.FsType != "overlay" {
+ log.Fatal("unexpected temp mount entry")
+ }
}
next()