diff --git a/internal/pkg/dir_test.go b/internal/pkg/dir_test.go index 87683fb..798d0d0 100644 --- a/internal/pkg/dir_test.go +++ b/internal/pkg/dir_test.go @@ -411,6 +411,36 @@ func TestFlatten(t *testing.T) { {Mode: fs.ModeDir | 0700, Path: "work"}, }, pkg.MustDecode("_r1IBeMWCkLwQ9Im9w0tV9_CWIOfQlXkkP2CogPHLmZp_AB6W3_8HVZqDV00dNAm")}, + {"sample exec container overlay work", 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/VMCurZKCA_MV80zb-ZBWVytfl3rhYOKJeo2u9l-OuaytQ_w_r4EsqgJ2nfO93x5_": {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/VMCurZKCA_MV80zb-ZBWVytfl3rhYOKJeo2u9l-OuaytQ_w_r4EsqgJ2nfO93x5_", 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("-DrfvuB9gUAT-Tgw6V1KjFyosYGMGKJW7KMZFF1Ew8jZ9LJ82FtXf0wTgM3fO0oD")}, + {"sample file short", fstest.MapFS{ ".": {Mode: fs.ModeDir | 0700}, diff --git a/internal/pkg/exec.go b/internal/pkg/exec.go index 6e050b1..8d736b5 100644 --- a/internal/pkg/exec.go +++ b/internal/pkg/exec.go @@ -16,6 +16,9 @@ import ( "hakurei.app/message" ) +// AbsWork is the container pathname [CureContext.GetWorkDir] is mounted on. +var AbsWork = fhs.AbsRoot.Append("work/") + // ExecContainerPath is an [Artifact] and the [check.Absolute] pathname to make // it available under in the container. type ExecContainerPath struct { @@ -90,9 +93,14 @@ 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. 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. +// on [AbsWork] and [fhs.AbsTmp] 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 one or more paths target +// [AbsWork], the final entry is set up as a writable overlay mount on /work for +// which the upperdir is the host side work directory. In this configuration, +// the program must avoid causing whiteout files to be created, cure fails if +// upperdir ends up with anything other than directory, regular or symlink +// entries. // // 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 @@ -186,7 +194,7 @@ func (a *execArtifact) cure(c *CureContext, hostNet bool) (err error) { cures = runtime.NumCPU() } - overlayTempIndex := -1 + overlayTempIndex, overlayWorkIndex := -1, -1 paths := make([][2]*check.Absolute, len(a.paths)) for i, p := range a.paths { if p.P == nil || p.A == nil { @@ -194,6 +202,8 @@ func (a *execArtifact) cure(c *CureContext, hostNet bool) (err error) { } if p.P.Is(fhs.AbsTmp) { overlayTempIndex = i + } else if p.P.Is(AbsWork) { + overlayWorkIndex = i } paths[i][1] = p.P } @@ -280,11 +290,12 @@ func (a *execArtifact) cure(c *CureContext, hostNet bool) (err error) { z.OverlayEphemeral(fhs.AbsRoot, paths[0][0]) paths = paths[1:] overlayTempIndex-- + overlayWorkIndex-- } + temp, work := c.GetTempDir(), c.GetWorkDir() for i, b := range paths { if i == overlayTempIndex { - temp := c.GetTempDir() tempUpper := temp.Append("upper") if err = os.MkdirAll(tempUpper.String(), 0700); err != nil { return @@ -301,14 +312,31 @@ func (a *execArtifact) cure(c *CureContext, hostNet bool) (err error) { ) continue } + if i == overlayWorkIndex { + if err = os.MkdirAll(work.String(), 0700); err != nil { + return + } + tempWork := temp.Append(".work") + if err = os.MkdirAll(tempWork.String(), 0700); err != nil { + return + } + z.Overlay( + AbsWork, + work, + tempWork, + b[0], + ) + continue + } z.Bind(b[0], b[1], 0) } - work := c.GetWorkDir() - z.Bind( - work, - fhs.AbsRoot.Append("work"), - std.BindWritable|std.BindEnsure, - ) + if overlayWorkIndex < 0 { + z.Bind( + work, + AbsWork, + std.BindWritable|std.BindEnsure, + ) + } if overlayTempIndex < 0 { z.Bind( c.GetTempDir(), diff --git a/internal/pkg/exec_test.go b/internal/pkg/exec_test.go index d1b8876..680f0ce 100644 --- a/internal/pkg/exec_test.go +++ b/internal/pkg/exec_test.go @@ -45,7 +45,7 @@ func TestExec(t *testing.T) { msg, 0, nil, - check.MustAbs("/work"), + pkg.AbsWork, []string{"HAKUREI_TEST=1"}, check.MustAbs("/opt/bin/testtool"), []string{"testtool"}, @@ -71,7 +71,7 @@ func TestExec(t *testing.T) { msg, 0, nil, - check.MustAbs("/work"), + pkg.AbsWork, []string{"HAKUREI_TEST=1"}, check.MustAbs("/opt/bin/testtool"), []string{"testtool"}, @@ -90,7 +90,7 @@ func TestExec(t *testing.T) { msg, 0, nil, - check.MustAbs("/work"), + pkg.AbsWork, []string{"HAKUREI_TEST=1"}, check.MustAbs("/opt/bin/testtool"), []string{"testtool"}, @@ -106,7 +106,7 @@ func TestExec(t *testing.T) { msg, 0, nil, - check.MustAbs("/work"), + pkg.AbsWork, nil, check.MustAbs("/opt/bin/testtool"), []string{"testtool"}, @@ -134,7 +134,7 @@ func TestExec(t *testing.T) { msg, 0, &wantChecksum, - check.MustAbs("/work"), + pkg.AbsWork, []string{"HAKUREI_TEST=1"}, check.MustAbs("/opt/bin/testtool"), []string{"testtool", "net"}, @@ -172,7 +172,7 @@ func TestExec(t *testing.T) { msg, 0, nil, - check.MustAbs("/work"), + pkg.AbsWork, []string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"}, check.MustAbs("/opt/bin/testtool"), []string{"testtool"}, @@ -204,7 +204,7 @@ func TestExec(t *testing.T) { msg, 0, nil, - check.MustAbs("/work"), + pkg.AbsWork, []string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"}, check.MustAbs("/tmp/bin/testtool"), []string{"testtool"}, @@ -232,6 +232,48 @@ func TestExec(t *testing.T) { testtoolDestroy(t, base, c) }, pkg.MustDecode("_r1IBeMWCkLwQ9Im9w0tV9_CWIOfQlXkkP2CogPHLmZp_AB6W3_8HVZqDV00dNAm")}, + + {"overlay work", 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, + pkg.AbsWork, + []string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"}, + check.MustAbs("/work/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("/work/", stubArtifact{ + kind: pkg.KindTar, + params: []byte("empty directory"), + cure: func(c *pkg.CureContext) error { + return os.MkdirAll(c.GetWorkDir().String(), 0700) + }, + }), + pkg.ExecContainerPath{ + P: pkg.AbsWork, + A: testtool, + }, + ), ignorePathname, wantChecksumOffline, nil}, + }) + + testtoolDestroy(t, base, c) + }, pkg.MustDecode("-DrfvuB9gUAT-Tgw6V1KjFyosYGMGKJW7KMZFF1Ew8jZ9LJ82FtXf0wTgM3fO0oD")}, }) } diff --git a/internal/pkg/testdata/main.go b/internal/pkg/testdata/main.go index 6a884a3..b3ce494 100644 --- a/internal/pkg/testdata/main.go +++ b/internal/pkg/testdata/main.go @@ -40,18 +40,25 @@ func main() { log.Fatalf("Environ: %q, want %q", os.Environ(), wantEnv) } - var overlayTmp bool + var overlayTmp, overlayWork bool const ( - wantExec = "/opt/bin/testtool" - wantExecOverlay = "/tmp/bin/testtool" + wantExec = "/opt/bin/testtool" + wantExecTmp = "/tmp/bin/testtool" + wantExecWork = "/work/bin/testtool" ) if got, err := os.Executable(); err != nil { log.Fatalf("Executable: error = %v", err) } else if got != wantExec { - if got != wantExecOverlay { + switch got { + case wantExecTmp: + overlayTmp = true + + case wantExecWork: + overlayWork = true + + default: log.Fatalf("Executable: %q, want %q", got, wantExec) } - overlayTmp = true } wantHostname := "cure" @@ -153,8 +160,16 @@ func main() { } next() - if path.Base(m.Root) != ident || m.Target != "/work" { - log.Fatal("unexpected work mount entry") + if overlayWork { + ident = "VMCurZKCA_MV80zb-ZBWVytfl3rhYOKJeo2u9l-OuaytQ_w_r4EsqgJ2nfO93x5_" + if m.Root != "/" || m.Target != "/work" || + m.Source != "overlay" || m.FsType != "overlay" { + log.Fatal("unexpected work mount entry") + } + } else { + if path.Base(m.Root) != ident || m.Target != "/work" { + log.Fatal("unexpected work mount entry") + } } if !overlayTmp {