Compare commits
No commits in common. "7638a44fa613f23434318bdf136723aa010e8638" and "a40d1827061650b5ab438e1cc10e12c31eb1391b" have entirely different histories.
7638a44fa6
...
a40d182706
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
@ -12,23 +13,19 @@ import (
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
_ "unsafe"
|
||||
|
||||
"hakurei.app/command"
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/container/fhs"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/app"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/message"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
//go:linkname optionalErrorUnwrap hakurei.app/container.optionalErrorUnwrap
|
||||
func optionalErrorUnwrap(_ error) error
|
||||
|
||||
func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErrs, out io.Writer) command.Command {
|
||||
func buildCommand(ctx context.Context, msg container.Msg, early *earlyHardeningErrs, out io.Writer) command.Command {
|
||||
var (
|
||||
flagVerbose bool
|
||||
flagJSON bool
|
||||
@ -118,7 +115,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
||||
progPath := shell
|
||||
if len(args) > 0 {
|
||||
if p, err := exec.LookPath(args[0]); err != nil {
|
||||
log.Fatal(optionalErrorUnwrap(err))
|
||||
log.Fatal(errors.Unwrap(err))
|
||||
return err
|
||||
} else if progPath, err = check.NewAbs(p); err != nil {
|
||||
log.Fatal(err.Error())
|
||||
|
@ -7,12 +7,10 @@ import (
|
||||
"testing"
|
||||
|
||||
"hakurei.app/command"
|
||||
"hakurei.app/message"
|
||||
"hakurei.app/container"
|
||||
)
|
||||
|
||||
func TestHelp(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
@ -70,10 +68,8 @@ Flags:
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
out := new(bytes.Buffer)
|
||||
c := buildCommand(t.Context(), message.NewMsg(nil), new(earlyHardeningErrs), out)
|
||||
c := buildCommand(t.Context(), container.NewMsg(nil), new(earlyHardeningErrs), out)
|
||||
if err := c.Parse(tc.args); !errors.Is(err, command.ErrHelp) && !errors.Is(err, flag.ErrHelp) {
|
||||
t.Errorf("Parse: error = %v; want %v",
|
||||
err, command.ErrHelp)
|
||||
|
@ -13,7 +13,6 @@ import (
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -32,7 +31,7 @@ func main() {
|
||||
|
||||
log.SetPrefix("hakurei: ")
|
||||
log.SetFlags(0)
|
||||
msg := message.NewMsg(log.Default())
|
||||
msg := container.NewMsg(log.Default())
|
||||
|
||||
early := earlyHardeningErrs{
|
||||
yamaLSM: container.SetPtracer(0),
|
||||
|
@ -10,13 +10,13 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
func tryPath(msg message.Msg, name string) (config *hst.Config) {
|
||||
func tryPath(msg container.Msg, name string) (config *hst.Config) {
|
||||
var r io.Reader
|
||||
config = new(hst.Config)
|
||||
|
||||
@ -49,7 +49,7 @@ func tryPath(msg message.Msg, name string) (config *hst.Config) {
|
||||
return
|
||||
}
|
||||
|
||||
func tryFd(msg message.Msg, name string) io.ReadCloser {
|
||||
func tryFd(msg container.Msg, name string) io.ReadCloser {
|
||||
if v, err := strconv.Atoi(name); err != nil {
|
||||
if !errors.Is(err, strconv.ErrSyntax) {
|
||||
msg.Verbosef("name cannot be interpreted as int64: %v", err)
|
||||
@ -68,7 +68,7 @@ func tryFd(msg message.Msg, name string) io.ReadCloser {
|
||||
}
|
||||
}
|
||||
|
||||
func tryShort(msg message.Msg, name string) (config *hst.Config, entry *state.State) {
|
||||
func tryShort(msg container.Msg, name string) (config *hst.Config, entry *state.State) {
|
||||
likePrefix := false
|
||||
if len(name) <= 32 {
|
||||
likePrefix = true
|
||||
|
@ -11,10 +11,10 @@ import (
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
func printShowSystem(output io.Writer, short, flagJSON bool) {
|
||||
@ -56,7 +56,7 @@ func printShowInstance(
|
||||
|
||||
if err := config.Validate(); err != nil {
|
||||
valid = false
|
||||
if m, ok := message.GetMessage(err); ok {
|
||||
if m, ok := container.GetErrorMessage(err); ok {
|
||||
mustPrint(output, "Error: "+m+"!\n\n")
|
||||
}
|
||||
}
|
||||
|
@ -27,8 +27,6 @@ var (
|
||||
)
|
||||
|
||||
func TestPrintShowInstance(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
instance *state.State
|
||||
@ -51,7 +49,8 @@ Filesystem
|
||||
autoroot:w:/var/lib/hakurei/base/org.debian
|
||||
autoetc:/etc/
|
||||
w+ephemeral(-rwxr-xr-x):/tmp/
|
||||
w*/nix/store:/var/lib/hakurei/nix/u0/org.chromium.Chromium/rw-store/upper:/var/lib/hakurei/nix/u0/org.chromium.Chromium/rw-store/work:/var/lib/hakurei/base/org.nixos/ro-store
|
||||
w*/nix/store:/mnt-root/nix/.rw-store/upper:/mnt-root/nix/.rw-store/work:/mnt-root/nix/.ro-store
|
||||
*/nix/store
|
||||
/run/current-system@
|
||||
/run/opengl-driver@
|
||||
w-/var/lib/hakurei/u0/org.chromium.Chromium:/data/data/org.chromium.Chromium
|
||||
@ -131,7 +130,8 @@ Filesystem
|
||||
autoroot:w:/var/lib/hakurei/base/org.debian
|
||||
autoetc:/etc/
|
||||
w+ephemeral(-rwxr-xr-x):/tmp/
|
||||
w*/nix/store:/var/lib/hakurei/nix/u0/org.chromium.Chromium/rw-store/upper:/var/lib/hakurei/nix/u0/org.chromium.Chromium/rw-store/work:/var/lib/hakurei/base/org.nixos/ro-store
|
||||
w*/nix/store:/mnt-root/nix/.rw-store/upper:/mnt-root/nix/.rw-store/work:/mnt-root/nix/.ro-store
|
||||
*/nix/store
|
||||
/run/current-system@
|
||||
/run/opengl-driver@
|
||||
w-/var/lib/hakurei/u0/org.chromium.Chromium:/data/data/org.chromium.Chromium
|
||||
@ -290,10 +290,14 @@ App
|
||||
"type": "overlay",
|
||||
"dst": "/nix/store",
|
||||
"lower": [
|
||||
"/var/lib/hakurei/base/org.nixos/ro-store"
|
||||
"/mnt-root/nix/.ro-store"
|
||||
],
|
||||
"upper": "/var/lib/hakurei/nix/u0/org.chromium.Chromium/rw-store/upper",
|
||||
"work": "/var/lib/hakurei/nix/u0/org.chromium.Chromium/rw-store/work"
|
||||
"upper": "/mnt-root/nix/.rw-store/upper",
|
||||
"work": "/mnt-root/nix/.rw-store/work"
|
||||
},
|
||||
{
|
||||
"type": "bind",
|
||||
"src": "/nix/store"
|
||||
},
|
||||
{
|
||||
"type": "link",
|
||||
@ -440,10 +444,14 @@ App
|
||||
"type": "overlay",
|
||||
"dst": "/nix/store",
|
||||
"lower": [
|
||||
"/var/lib/hakurei/base/org.nixos/ro-store"
|
||||
"/mnt-root/nix/.ro-store"
|
||||
],
|
||||
"upper": "/var/lib/hakurei/nix/u0/org.chromium.Chromium/rw-store/upper",
|
||||
"work": "/var/lib/hakurei/nix/u0/org.chromium.Chromium/rw-store/work"
|
||||
"upper": "/mnt-root/nix/.rw-store/upper",
|
||||
"work": "/mnt-root/nix/.rw-store/work"
|
||||
},
|
||||
{
|
||||
"type": "bind",
|
||||
"src": "/nix/store"
|
||||
},
|
||||
{
|
||||
"type": "link",
|
||||
@ -489,8 +497,6 @@ App
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
output := new(strings.Builder)
|
||||
gotValid := printShowInstance(output, testTime, tc.instance, tc.config, tc.short, tc.json)
|
||||
if got := output.String(); got != tc.want {
|
||||
@ -505,8 +511,6 @@ App
|
||||
}
|
||||
|
||||
func TestPrintPs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
entries state.Entries
|
||||
@ -650,10 +654,14 @@ func TestPrintPs(t *testing.T) {
|
||||
"type": "overlay",
|
||||
"dst": "/nix/store",
|
||||
"lower": [
|
||||
"/var/lib/hakurei/base/org.nixos/ro-store"
|
||||
"/mnt-root/nix/.ro-store"
|
||||
],
|
||||
"upper": "/var/lib/hakurei/nix/u0/org.chromium.Chromium/rw-store/upper",
|
||||
"work": "/var/lib/hakurei/nix/u0/org.chromium.Chromium/rw-store/work"
|
||||
"upper": "/mnt-root/nix/.rw-store/upper",
|
||||
"work": "/mnt-root/nix/.rw-store/work"
|
||||
},
|
||||
{
|
||||
"type": "bind",
|
||||
"src": "/nix/store"
|
||||
},
|
||||
{
|
||||
"type": "link",
|
||||
@ -704,8 +712,6 @@ func TestPrintPs(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
output := new(strings.Builder)
|
||||
printPs(output, testTime, stubStore(tc.entries), tc.short, tc.json)
|
||||
if got := output.String(); got != tc.want {
|
||||
|
@ -91,7 +91,7 @@ func (app *appInfo) toHst(pathSet *appPathSet, pathname *check.Absolute, argv []
|
||||
{FilesystemConfig: &hst.FSLink{Target: pathCurrentSystem, Linkname: app.CurrentSystem.String()}},
|
||||
{FilesystemConfig: &hst.FSLink{Target: pathBin, Linkname: pathSwBin.String()}},
|
||||
{FilesystemConfig: &hst.FSLink{Target: fhs.AbsUsrBin, Linkname: pathSwBin.String()}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: pathSet.metaPath, Target: hst.AbsPrivateTmp.Append("app")}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: pathSet.metaPath, Target: hst.AbsTmp.Append("app")}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsEtc.Append("resolv.conf"), Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("block"), Optional: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("bus"), Optional: true}},
|
||||
|
@ -11,10 +11,10 @@ import (
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/command"
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/container/fhs"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -24,7 +24,7 @@ var (
|
||||
func main() {
|
||||
log.SetPrefix("hpkg: ")
|
||||
log.SetFlags(0)
|
||||
msg := message.NewMsg(log.Default())
|
||||
msg := container.NewMsg(log.Default())
|
||||
|
||||
if err := os.Setenv("SHELL", pathShell.String()); err != nil {
|
||||
log.Fatalf("cannot set $SHELL: %v", err)
|
||||
@ -162,7 +162,7 @@ func main() {
|
||||
|
||||
withCacheDir(ctx, msg, "install", []string{
|
||||
// export inner bundle path in the environment
|
||||
"export BUNDLE=" + hst.PrivateTmp + "/bundle",
|
||||
"export BUNDLE=" + hst.Tmp + "/bundle",
|
||||
// replace inner /etc
|
||||
"mkdir -p etc",
|
||||
"chmod -R +w etc",
|
||||
@ -309,7 +309,7 @@ func main() {
|
||||
|
||||
if a.GPU {
|
||||
config.Container.Filesystem = append(config.Container.Filesystem,
|
||||
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{Source: pathSet.nixPath.Append(".nixGL"), Target: hst.AbsPrivateTmp.Append("nixGL")}})
|
||||
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{Source: pathSet.nixPath.Append(".nixGL"), Target: hst.AbsTmp.Append("nixGL")}})
|
||||
appendGPUFilesystem(config)
|
||||
}
|
||||
|
||||
|
@ -7,10 +7,10 @@ import (
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/container/fhs"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
const bash = "bash"
|
||||
@ -52,7 +52,7 @@ func lookPath(file string) string {
|
||||
|
||||
var beforeRunFail = new(atomic.Pointer[func()])
|
||||
|
||||
func mustRun(msg message.Msg, name string, arg ...string) {
|
||||
func mustRun(msg container.Msg, name string, arg ...string) {
|
||||
msg.Verbosef("spawning process: %q %q", name, arg)
|
||||
cmd := exec.Command(name, arg...)
|
||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||
|
@ -9,14 +9,14 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
var hakureiPath = internal.MustHakureiPath()
|
||||
|
||||
func mustRunApp(ctx context.Context, msg message.Msg, config *hst.Config, beforeFail func()) {
|
||||
func mustRunApp(ctx context.Context, msg container.Msg, config *hst.Config, beforeFail func()) {
|
||||
var (
|
||||
cmd *exec.Cmd
|
||||
st io.WriteCloser
|
||||
|
@ -5,15 +5,15 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/container/fhs"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
func withNixDaemon(
|
||||
ctx context.Context,
|
||||
msg message.Msg,
|
||||
msg container.Msg,
|
||||
action string, command []string, net bool, updateConfig func(config *hst.Config) *hst.Config,
|
||||
app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func(),
|
||||
) {
|
||||
@ -64,7 +64,7 @@ func withNixDaemon(
|
||||
|
||||
func withCacheDir(
|
||||
ctx context.Context,
|
||||
msg message.Msg,
|
||||
msg container.Msg,
|
||||
action string, command []string, workDir *check.Absolute,
|
||||
app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func()) {
|
||||
mustRunAppDropShell(ctx, msg, &hst.Config{
|
||||
@ -88,7 +88,7 @@ func withCacheDir(
|
||||
{FilesystemConfig: &hst.FSLink{Target: pathCurrentSystem, Linkname: app.CurrentSystem.String()}},
|
||||
{FilesystemConfig: &hst.FSLink{Target: pathBin, Linkname: pathSwBin.String()}},
|
||||
{FilesystemConfig: &hst.FSLink{Target: fhs.AbsUsrBin, Linkname: pathSwBin.String()}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: workDir, Target: hst.AbsPrivateTmp.Append("bundle")}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: workDir, Target: hst.AbsTmp.Append("bundle")}},
|
||||
{FilesystemConfig: &hst.FSBind{Target: pathDataData.Append(app.ID, "cache"), Source: pathSet.cacheDir, Write: true, Ensure: true}},
|
||||
},
|
||||
|
||||
@ -102,7 +102,7 @@ func withCacheDir(
|
||||
}, dropShell, beforeFail)
|
||||
}
|
||||
|
||||
func mustRunAppDropShell(ctx context.Context, msg message.Msg, config *hst.Config, dropShell bool, beforeFail func()) {
|
||||
func mustRunAppDropShell(ctx context.Context, msg container.Msg, config *hst.Config, dropShell bool, beforeFail func()) {
|
||||
if dropShell {
|
||||
if config.Container != nil {
|
||||
config.Container.Args = []string{bash, "-l"}
|
||||
|
@ -6,46 +6,32 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseUint32Fast(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
func Test_parseUint32Fast(t *testing.T) {
|
||||
t.Run("zero-length", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if _, err := parseUint32Fast(""); err == nil || err.Error() != "zero length string" {
|
||||
t.Errorf(`parseUint32Fast(""): error = %v`, err)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("overflow", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if _, err := parseUint32Fast("10000000000"); err == nil || err.Error() != "string too long" {
|
||||
t.Errorf("parseUint32Fast: error = %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid byte", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if _, err := parseUint32Fast("meow"); err == nil || err.Error() != "invalid character 'm' at index 0" {
|
||||
t.Errorf(`parseUint32Fast("meow"): error = %v`, err)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("full range", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testRange := func(i, end int) {
|
||||
for ; i < end; i++ {
|
||||
s := strconv.Itoa(i)
|
||||
w := i
|
||||
t.Run("parse "+s, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
v, err := parseUint32Fast(s)
|
||||
if err != nil {
|
||||
t.Errorf("parseUint32Fast(%q): error = %v",
|
||||
@ -69,9 +55,7 @@ func TestParseUint32Fast(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
func Test_parseConfig(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
puid, want int
|
||||
@ -87,8 +71,6 @@ func TestParseConfig(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fid, ok, err := parseConfig(bytes.NewBufferString(tc.rc), tc.puid)
|
||||
if err == nil && tc.wantErr != "" {
|
||||
t.Errorf("parseConfig: error = %v; wantErr %q",
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
)
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := command.New(nil, nil, "test", nil)
|
||||
stubHandler := func([]string) error { panic("unreachable") }
|
||||
|
||||
|
@ -14,8 +14,6 @@ import (
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
buildTree func(wout, wlog io.Writer) command.Command
|
||||
@ -253,7 +251,6 @@ Commands:
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
wout, wlog := new(bytes.Buffer), new(bytes.Buffer)
|
||||
c := tc.buildTree(wout, wlog)
|
||||
|
||||
|
@ -6,19 +6,15 @@ import (
|
||||
)
|
||||
|
||||
func TestParseUnreachable(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// top level bypasses name matching and recursive calls to Parse
|
||||
// returns when encountering zero-length args
|
||||
t.Run("zero-length args", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
defer checkRecover(t, "Parse", "attempted to parse with zero length args")
|
||||
_ = newNode(panicWriter{}, nil, " ", " ").Parse(nil)
|
||||
})
|
||||
|
||||
// top level must not have siblings
|
||||
t.Run("toplevel siblings", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
defer checkRecover(t, "Parse", "invalid toplevel state")
|
||||
n := newNode(panicWriter{}, nil, " ", "")
|
||||
n.append(newNode(panicWriter{}, nil, " ", " "))
|
||||
@ -27,7 +23,6 @@ func TestParseUnreachable(t *testing.T) {
|
||||
|
||||
// a node with descendents must not have a direct handler
|
||||
t.Run("sub handle conflict", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
defer checkRecover(t, "Parse", "invalid subcommand tree state")
|
||||
n := newNode(panicWriter{}, nil, " ", " ")
|
||||
n.adopt(newNode(panicWriter{}, nil, " ", " "))
|
||||
@ -37,7 +32,6 @@ func TestParseUnreachable(t *testing.T) {
|
||||
|
||||
// this would only happen if a node was matched twice
|
||||
t.Run("parsed flag set", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
defer checkRecover(t, "Parse", "invalid set state")
|
||||
n := newNode(panicWriter{}, nil, " ", "")
|
||||
set := flag.NewFlagSet("parsed", flag.ContinueOnError)
|
||||
|
@ -10,10 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func TestAutoEtcOp(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("nonrepeatable", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
wantErr := OpRepeatError("autoetc")
|
||||
if err := (&AutoEtcOp{Prefix: "81ceabb30d37bbdb3868004629cb84e9"}).apply(&setupState{nonrepeatable: nrAutoEtc}, nil); !errors.Is(err, wantErr) {
|
||||
t.Errorf("apply: error = %v, want %v", err, wantErr)
|
||||
@ -283,7 +280,6 @@ func TestAutoEtcOp(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("host path rel", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
op := &AutoEtcOp{Prefix: "048090b6ed8f9ebb10e275ff5d8c0659"}
|
||||
wantHostPath := "/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"
|
||||
wantHostRel := ".host/048090b6ed8f9ebb10e275ff5d8c0659"
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/container/fhs"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
func init() { gob.Register(new(AutoRootOp)) }
|
||||
@ -82,7 +81,7 @@ func (r *AutoRootOp) String() string {
|
||||
}
|
||||
|
||||
// IsAutoRootBindable returns whether a dir entry name is selected for AutoRoot.
|
||||
func IsAutoRootBindable(msg message.Msg, name string) bool {
|
||||
func IsAutoRootBindable(msg Msg, name string) bool {
|
||||
switch name {
|
||||
case "proc", "dev", "tmp", "mnt", "etc":
|
||||
|
||||
|
@ -8,12 +8,10 @@ import (
|
||||
"hakurei.app/container/bits"
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/container/stub"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
func TestAutoRootOp(t *testing.T) {
|
||||
t.Run("nonrepeatable", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
wantErr := OpRepeatError("autoroot")
|
||||
if err := new(AutoRootOp).apply(&setupState{nonrepeatable: nrAutoRoot}, nil); !errors.Is(err, wantErr) {
|
||||
t.Errorf("apply: error = %v, want %v", err, wantErr)
|
||||
@ -181,8 +179,6 @@ func TestAutoRootOp(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIsAutoRootBindable(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
want bool
|
||||
@ -199,8 +195,7 @@ func TestIsAutoRootBindable(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
var msg message.Msg
|
||||
var msg Msg
|
||||
if tc.log {
|
||||
msg = &kstub{nil, stub.New(t, func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { panic("unreachable") }, stub.Expect{Calls: []stub.Call{
|
||||
call("verbose", stub.ExpectArgs{[]any{"got unexpected root entry"}}, nil, nil),
|
||||
|
@ -3,8 +3,6 @@ package container
|
||||
import "testing"
|
||||
|
||||
func TestCapToIndex(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
cap uintptr
|
||||
@ -16,7 +14,6 @@ func TestCapToIndex(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := capToIndex(tc.cap); got != tc.want {
|
||||
t.Errorf("capToIndex: %#x, want %#x", got, tc.want)
|
||||
}
|
||||
@ -25,8 +22,6 @@ func TestCapToIndex(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCapToMask(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
cap uintptr
|
||||
@ -38,7 +33,6 @@ func TestCapToMask(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := capToMask(tc.cap); got != tc.want {
|
||||
t.Errorf("capToMask: %#x, want %#x", got, tc.want)
|
||||
}
|
||||
|
@ -18,8 +18,6 @@ import (
|
||||
func unsafeAbs(_ string) *Absolute
|
||||
|
||||
func TestAbsoluteError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
|
||||
@ -29,8 +27,8 @@ func TestAbsoluteError(t *testing.T) {
|
||||
}{
|
||||
{"EINVAL", new(AbsoluteError), syscall.EINVAL, true},
|
||||
{"not EINVAL", new(AbsoluteError), syscall.EBADE, false},
|
||||
{"ne val", new(AbsoluteError), &AbsoluteError{Pathname: "etc"}, false},
|
||||
{"equals", &AbsoluteError{Pathname: "etc"}, &AbsoluteError{Pathname: "etc"}, true},
|
||||
{"ne val", new(AbsoluteError), &AbsoluteError{"etc"}, false},
|
||||
{"equals", &AbsoluteError{"etc"}, &AbsoluteError{"etc"}, true},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@ -40,18 +38,14 @@ func TestAbsoluteError(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("string", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
want := `path "etc" is not absolute`
|
||||
if got := (&AbsoluteError{Pathname: "etc"}).Error(); got != want {
|
||||
if got := (&AbsoluteError{"etc"}).Error(); got != want {
|
||||
t.Errorf("Error: %q, want %q", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewAbs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
|
||||
@ -60,14 +54,12 @@ func TestNewAbs(t *testing.T) {
|
||||
wantErr error
|
||||
}{
|
||||
{"good", "/etc", MustAbs("/etc"), nil},
|
||||
{"not absolute", "etc", nil, &AbsoluteError{Pathname: "etc"}},
|
||||
{"zero", "", nil, &AbsoluteError{Pathname: ""}},
|
||||
{"not absolute", "etc", nil, &AbsoluteError{"etc"}},
|
||||
{"zero", "", nil, &AbsoluteError{""}},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got, err := NewAbs(tc.pathname)
|
||||
if !reflect.DeepEqual(got, tc.want) {
|
||||
t.Errorf("NewAbs: %#v, want %#v", got, tc.want)
|
||||
@ -79,8 +71,6 @@ func TestNewAbs(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("must", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defer func() {
|
||||
wantPanic := `path "etc" is not absolute`
|
||||
|
||||
@ -95,8 +85,6 @@ func TestNewAbs(t *testing.T) {
|
||||
|
||||
func TestAbsoluteString(t *testing.T) {
|
||||
t.Run("passthrough", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pathname := "/etc"
|
||||
if got := unsafeAbs(pathname).String(); got != pathname {
|
||||
t.Errorf("String: %q, want %q", got, pathname)
|
||||
@ -104,8 +92,6 @@ func TestAbsoluteString(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("zero", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defer func() {
|
||||
wantPanic := "attempted use of zero Absolute"
|
||||
|
||||
@ -119,8 +105,6 @@ func TestAbsoluteString(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAbsoluteIs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
a, v *Absolute
|
||||
@ -136,8 +120,6 @@ func TestAbsoluteIs(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if got := tc.a.Is(tc.v); got != tc.want {
|
||||
t.Errorf("Is: %v, want %v", got, tc.want)
|
||||
}
|
||||
@ -151,8 +133,6 @@ type sCheck struct {
|
||||
}
|
||||
|
||||
func TestCodecAbsolute(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
a *Absolute
|
||||
@ -173,7 +153,7 @@ func TestCodecAbsolute(t *testing.T) {
|
||||
|
||||
`"/etc"`, `{"val":"/etc","magic":3236757504}`},
|
||||
{"not absolute", nil,
|
||||
&AbsoluteError{Pathname: "etc"},
|
||||
&AbsoluteError{"etc"},
|
||||
"\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\a\xff\x80\x00\x03etc",
|
||||
",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x04\x00\x00\x00\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\x0f\xff\x84\x01\x03etc\x01\xfb\x01\x81\xda\x00\x00\x00",
|
||||
|
||||
@ -187,18 +167,13 @@ func TestCodecAbsolute(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("gob", func(t *testing.T) {
|
||||
if tc.gob == "\x00" && tc.sGob == "\x00" {
|
||||
// these values mark the current test to skip gob
|
||||
return
|
||||
}
|
||||
t.Parallel()
|
||||
|
||||
t.Run("encode", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// encode is unchecked
|
||||
if errors.Is(tc.wantErr, syscall.EINVAL) {
|
||||
return
|
||||
@ -235,8 +210,6 @@ func TestCodecAbsolute(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("decode", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
{
|
||||
var gotA *Absolute
|
||||
err := gob.NewDecoder(strings.NewReader(tc.gob)).Decode(&gotA)
|
||||
@ -271,11 +244,7 @@ func TestCodecAbsolute(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("json", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("marshal", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// marshal is unchecked
|
||||
if errors.Is(tc.wantErr, syscall.EINVAL) {
|
||||
return
|
||||
@ -310,8 +279,6 @@ func TestCodecAbsolute(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("unmarshal", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
{
|
||||
var gotA *Absolute
|
||||
err := json.Unmarshal([]byte(tc.json), &gotA)
|
||||
@ -347,8 +314,6 @@ func TestCodecAbsolute(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("json passthrough", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
wantErr := "invalid character ':' looking for beginning of value"
|
||||
if err := new(Absolute).UnmarshalJSON([]byte(":3")); err == nil || err.Error() != wantErr {
|
||||
t.Errorf("UnmarshalJSON: error = %v, want %s", err, wantErr)
|
||||
@ -357,11 +322,7 @@ func TestCodecAbsolute(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAbsoluteWrap(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("join", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
want := "/etc/nix/nix.conf"
|
||||
if got := MustAbs("/etc").Append("nix", "nix.conf"); got.String() != want {
|
||||
t.Errorf("Append: %q, want %q", got, want)
|
||||
@ -369,8 +330,6 @@ func TestAbsoluteWrap(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("dir", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
want := "/"
|
||||
if got := MustAbs("/etc").Dir(); got.String() != want {
|
||||
t.Errorf("Dir: %q, want %q", got, want)
|
||||
@ -378,8 +337,6 @@ func TestAbsoluteWrap(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("sort", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
want := []*Absolute{MustAbs("/etc"), MustAbs("/proc"), MustAbs("/sys")}
|
||||
got := []*Absolute{MustAbs("/proc"), MustAbs("/sys"), MustAbs("/etc")}
|
||||
SortAbs(got)
|
||||
@ -389,8 +346,6 @@ func TestAbsoluteWrap(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("compact", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
want := []*Absolute{MustAbs("/etc"), MustAbs("/proc"), MustAbs("/sys")}
|
||||
if got := CompactAbs([]*Absolute{MustAbs("/etc"), MustAbs("/proc"), MustAbs("/proc"), MustAbs("/sys")}); !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("CompactAbs: %#v, want %#v", got, want)
|
||||
|
@ -7,8 +7,6 @@ import (
|
||||
)
|
||||
|
||||
func TestEscapeOverlayDataSegment(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
s string
|
||||
@ -21,8 +19,6 @@ func TestEscapeOverlayDataSegment(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if got := check.EscapeOverlayDataSegment(tc.s); got != tc.want {
|
||||
t.Errorf("escapeOverlayDataSegment: %s, want %s", got, tc.want)
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ import (
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/container/fhs"
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -53,7 +52,7 @@ type (
|
||||
|
||||
cmd *exec.Cmd
|
||||
ctx context.Context
|
||||
msg message.Msg
|
||||
msg Msg
|
||||
Params
|
||||
}
|
||||
|
||||
@ -397,9 +396,9 @@ func (p *Container) ProcessState() *os.ProcessState {
|
||||
}
|
||||
|
||||
// New returns the address to a new instance of [Container] that requires further initialisation before use.
|
||||
func New(ctx context.Context, msg message.Msg) *Container {
|
||||
func New(ctx context.Context, msg Msg) *Container {
|
||||
if msg == nil {
|
||||
msg = message.NewMsg(nil)
|
||||
msg = NewMsg(nil)
|
||||
}
|
||||
|
||||
p := &Container{ctx: ctx, msg: msg, Params: Params{Ops: new(Ops)}}
|
||||
@ -410,7 +409,7 @@ func New(ctx context.Context, msg message.Msg) *Container {
|
||||
}
|
||||
|
||||
// NewCommand calls [New] and initialises the [Params.Path] and [Params.Args] fields.
|
||||
func NewCommand(ctx context.Context, msg message.Msg, pathname *check.Absolute, name string, args ...string) *Container {
|
||||
func NewCommand(ctx context.Context, msg Msg, pathname *check.Absolute, name string, args ...string) *Container {
|
||||
z := New(ctx, msg)
|
||||
z.Path = pathname
|
||||
z.Args = append([]string{name}, args...)
|
||||
|
@ -26,12 +26,9 @@ import (
|
||||
"hakurei.app/container/vfs"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/ldd"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
func TestStartError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
err error
|
||||
@ -139,8 +136,6 @@ func TestStartError(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("error", func(t *testing.T) {
|
||||
if got := tc.err.Error(); got != tc.s {
|
||||
t.Errorf("Error: %q, want %q", got, tc.s)
|
||||
@ -157,13 +152,13 @@ func TestStartError(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("msg", func(t *testing.T) {
|
||||
if got, ok := message.GetMessage(tc.err); !ok {
|
||||
if got, ok := container.GetErrorMessage(tc.err); !ok {
|
||||
if tc.msg != "" {
|
||||
t.Errorf("GetMessage: err does not implement MessageError")
|
||||
t.Errorf("GetErrorMessage: err does not implement MessageError")
|
||||
}
|
||||
return
|
||||
} else if got != tc.msg {
|
||||
t.Errorf("GetMessage: %q, want %q", got, tc.msg)
|
||||
t.Errorf("GetErrorMessage: %q, want %q", got, tc.msg)
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -223,10 +218,10 @@ var containerTestCases = []struct {
|
||||
|
||||
{"tmpfs", true, false, false, true,
|
||||
earlyOps(new(container.Ops).
|
||||
Tmpfs(hst.AbsPrivateTmp, 0, 0755),
|
||||
Tmpfs(hst.AbsTmp, 0, 0755),
|
||||
),
|
||||
earlyMnt(
|
||||
ent("/", hst.PrivateTmp, "rw,nosuid,nodev,relatime", "tmpfs", "ephemeral", ignore),
|
||||
ent("/", hst.Tmp, "rw,nosuid,nodev,relatime", "tmpfs", "ephemeral", ignore),
|
||||
),
|
||||
9, 9, nil, 0, bits.PresetStrict},
|
||||
|
||||
@ -280,7 +275,7 @@ var containerTestCases = []struct {
|
||||
}
|
||||
|
||||
return new(container.Ops).
|
||||
Overlay(hst.AbsPrivateTmp, upper, work, lower0, lower1),
|
||||
Overlay(hst.AbsTmp, upper, work, lower0, lower1),
|
||||
context.WithValue(context.WithValue(context.WithValue(context.WithValue(t.Context(),
|
||||
testVal("lower1"), lower1),
|
||||
testVal("lower0"), lower0),
|
||||
@ -289,7 +284,7 @@ var containerTestCases = []struct {
|
||||
},
|
||||
func(t *testing.T, ctx context.Context) []*vfs.MountInfoEntry {
|
||||
return []*vfs.MountInfoEntry{
|
||||
ent("/", hst.PrivateTmp, "rw", "overlay", "overlay",
|
||||
ent("/", hst.Tmp, "rw", "overlay", "overlay",
|
||||
"rw,lowerdir="+
|
||||
container.InternalToHostOvlEscape(ctx.Value(testVal("lower0")).(*check.Absolute).String())+":"+
|
||||
container.InternalToHostOvlEscape(ctx.Value(testVal("lower1")).(*check.Absolute).String())+
|
||||
@ -315,13 +310,13 @@ var containerTestCases = []struct {
|
||||
}
|
||||
|
||||
return new(container.Ops).
|
||||
OverlayEphemeral(hst.AbsPrivateTmp, lower0, lower1),
|
||||
OverlayEphemeral(hst.AbsTmp, lower0, lower1),
|
||||
t.Context()
|
||||
},
|
||||
func(t *testing.T, ctx context.Context) []*vfs.MountInfoEntry {
|
||||
return []*vfs.MountInfoEntry{
|
||||
// contains random suffix
|
||||
ent("/", hst.PrivateTmp, "rw", "overlay", "overlay", ignore),
|
||||
ent("/", hst.Tmp, "rw", "overlay", "overlay", ignore),
|
||||
}
|
||||
},
|
||||
1 << 3, 1 << 14, nil, 0, bits.PresetStrict},
|
||||
@ -338,14 +333,14 @@ var containerTestCases = []struct {
|
||||
}
|
||||
}
|
||||
return new(container.Ops).
|
||||
OverlayReadonly(hst.AbsPrivateTmp, lower0, lower1),
|
||||
OverlayReadonly(hst.AbsTmp, lower0, lower1),
|
||||
context.WithValue(context.WithValue(t.Context(),
|
||||
testVal("lower1"), lower1),
|
||||
testVal("lower0"), lower0)
|
||||
},
|
||||
func(t *testing.T, ctx context.Context) []*vfs.MountInfoEntry {
|
||||
return []*vfs.MountInfoEntry{
|
||||
ent("/", hst.PrivateTmp, "rw", "overlay", "overlay",
|
||||
ent("/", hst.Tmp, "rw", "overlay", "overlay",
|
||||
"ro,lowerdir="+
|
||||
container.InternalToHostOvlEscape(ctx.Value(testVal("lower0")).(*check.Absolute).String())+":"+
|
||||
container.InternalToHostOvlEscape(ctx.Value(testVal("lower1")).(*check.Absolute).String())+
|
||||
@ -356,8 +351,6 @@ var containerTestCases = []struct {
|
||||
}
|
||||
|
||||
func TestContainer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("cancel", testContainerCancel(nil, func(t *testing.T, c *container.Container) {
|
||||
wantErr := context.Canceled
|
||||
wantExitCode := 0
|
||||
@ -391,8 +384,6 @@ func TestContainer(t *testing.T) {
|
||||
|
||||
for i, tc := range containerTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
wantOps, wantOpsCtx := tc.ops(t)
|
||||
wantMnt := tc.mnt(t, wantOpsCtx)
|
||||
|
||||
@ -512,7 +503,6 @@ func testContainerCancel(
|
||||
waitCheck func(t *testing.T, c *container.Container),
|
||||
) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(t.Context(), helperDefaultTimeout)
|
||||
|
||||
c := helperNewContainer(ctx, "block")
|
||||
@ -555,8 +545,7 @@ func testContainerCancel(
|
||||
}
|
||||
|
||||
func TestContainerString(t *testing.T) {
|
||||
t.Parallel()
|
||||
msg := message.NewMsg(nil)
|
||||
msg := container.NewMsg(nil)
|
||||
c := container.NewCommand(t.Context(), msg, check.MustAbs("/run/current-system/sw/bin/ldd"), "ldd", "/usr/bin/env")
|
||||
c.SeccompFlags |= seccomp.AllowMultiarch
|
||||
c.SeccompRules = seccomp.Preset(
|
||||
@ -722,7 +711,7 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
|
||||
func helperNewContainerLibPaths(ctx context.Context, libPaths *[]*check.Absolute, args ...string) (c *container.Container) {
|
||||
msg := message.NewMsg(nil)
|
||||
msg := container.NewMsg(nil)
|
||||
c = container.NewCommand(ctx, msg, absHelperInnerPath, "helper", args...)
|
||||
c.Env = append(c.Env, envDoCheck+"=1")
|
||||
c.Bind(check.MustAbs(os.Args[0]), absHelperInnerPath, 0)
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
type osFile interface {
|
||||
@ -38,7 +37,7 @@ type syscallDispatcher interface {
|
||||
setNoNewPrivs() error
|
||||
|
||||
// lastcap provides [LastCap].
|
||||
lastcap(msg message.Msg) uintptr
|
||||
lastcap(msg Msg) uintptr
|
||||
// capset provides capset.
|
||||
capset(hdrp *capHeader, datap *[2]capData) error
|
||||
// capBoundingSetDrop provides capBoundingSetDrop.
|
||||
@ -53,9 +52,9 @@ type syscallDispatcher interface {
|
||||
receive(key string, e any, fdp *uintptr) (closeFunc func() error, err error)
|
||||
|
||||
// bindMount provides procPaths.bindMount.
|
||||
bindMount(msg message.Msg, source, target string, flags uintptr) error
|
||||
bindMount(msg Msg, source, target string, flags uintptr) error
|
||||
// remount provides procPaths.remount.
|
||||
remount(msg message.Msg, target string, flags uintptr) error
|
||||
remount(msg Msg, target string, flags uintptr) error
|
||||
// mountTmpfs provides mountTmpfs.
|
||||
mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error
|
||||
// ensureFile provides ensureFile.
|
||||
@ -123,11 +122,11 @@ type syscallDispatcher interface {
|
||||
wait4(pid int, wstatus *syscall.WaitStatus, options int, rusage *syscall.Rusage) (wpid int, err error)
|
||||
|
||||
// printf provides the Printf method of [log.Logger].
|
||||
printf(msg message.Msg, format string, v ...any)
|
||||
printf(msg Msg, format string, v ...any)
|
||||
// fatal provides the Fatal method of [log.Logger]
|
||||
fatal(msg message.Msg, v ...any)
|
||||
fatal(msg Msg, v ...any)
|
||||
// fatalf provides the Fatalf method of [log.Logger]
|
||||
fatalf(msg message.Msg, format string, v ...any)
|
||||
fatalf(msg Msg, format string, v ...any)
|
||||
}
|
||||
|
||||
// direct implements syscallDispatcher on the current kernel.
|
||||
@ -141,7 +140,7 @@ func (direct) setPtracer(pid uintptr) error { return SetPtracer(pid) }
|
||||
func (direct) setDumpable(dumpable uintptr) error { return SetDumpable(dumpable) }
|
||||
func (direct) setNoNewPrivs() error { return SetNoNewPrivs() }
|
||||
|
||||
func (direct) lastcap(msg message.Msg) uintptr { return LastCap(msg) }
|
||||
func (direct) lastcap(msg Msg) uintptr { return LastCap(msg) }
|
||||
func (direct) capset(hdrp *capHeader, datap *[2]capData) error { return capset(hdrp, datap) }
|
||||
func (direct) capBoundingSetDrop(cap uintptr) error { return capBoundingSetDrop(cap) }
|
||||
func (direct) capAmbientClearAll() error { return capAmbientClearAll() }
|
||||
@ -151,10 +150,10 @@ func (direct) receive(key string, e any, fdp *uintptr) (func() error, error) {
|
||||
return Receive(key, e, fdp)
|
||||
}
|
||||
|
||||
func (direct) bindMount(msg message.Msg, source, target string, flags uintptr) error {
|
||||
func (direct) bindMount(msg Msg, source, target string, flags uintptr) error {
|
||||
return hostProc.bindMount(msg, source, target, flags)
|
||||
}
|
||||
func (direct) remount(msg message.Msg, target string, flags uintptr) error {
|
||||
func (direct) remount(msg Msg, target string, flags uintptr) error {
|
||||
return hostProc.remount(msg, target, flags)
|
||||
}
|
||||
func (k direct) mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error {
|
||||
@ -222,6 +221,6 @@ func (direct) wait4(pid int, wstatus *syscall.WaitStatus, options int, rusage *s
|
||||
return syscall.Wait4(pid, wstatus, options, rusage)
|
||||
}
|
||||
|
||||
func (direct) printf(msg message.Msg, format string, v ...any) { msg.GetLogger().Printf(format, v...) }
|
||||
func (direct) fatal(msg message.Msg, v ...any) { msg.GetLogger().Fatal(v...) }
|
||||
func (direct) fatalf(msg message.Msg, format string, v ...any) { msg.GetLogger().Fatalf(format, v...) }
|
||||
func (direct) printf(msg Msg, format string, v ...any) { msg.GetLogger().Printf(format, v...) }
|
||||
func (direct) fatal(msg Msg, v ...any) { msg.GetLogger().Fatal(v...) }
|
||||
func (direct) fatalf(msg Msg, format string, v ...any) { msg.GetLogger().Fatalf(format, v...) }
|
||||
|
@ -18,7 +18,6 @@ import (
|
||||
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/container/stub"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
type opValidTestCase struct {
|
||||
@ -32,12 +31,10 @@ func checkOpsValid(t *testing.T, testCases []opValidTestCase) {
|
||||
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
t.Helper()
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Helper()
|
||||
t.Parallel()
|
||||
|
||||
if got := tc.op.Valid(); got != tc.want {
|
||||
t.Errorf("Valid: %v, want %v", got, tc.want)
|
||||
@ -58,12 +55,10 @@ func checkOpsBuilder(t *testing.T, testCases []opsBuilderTestCase) {
|
||||
|
||||
t.Run("build", func(t *testing.T) {
|
||||
t.Helper()
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Helper()
|
||||
t.Parallel()
|
||||
|
||||
if !slices.EqualFunc(*tc.ops, tc.want, func(op Op, v Op) bool { return op.Is(v) }) {
|
||||
t.Errorf("Ops: %#v, want %#v", tc.ops, tc.want)
|
||||
@ -84,12 +79,10 @@ func checkOpIs(t *testing.T, testCases []opIsTestCase) {
|
||||
|
||||
t.Run("is", func(t *testing.T) {
|
||||
t.Helper()
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Helper()
|
||||
t.Parallel()
|
||||
|
||||
if got := tc.op.Is(tc.v); got != tc.want {
|
||||
t.Errorf("Is: %v, want %v", got, tc.want)
|
||||
@ -112,12 +105,10 @@ func checkOpMeta(t *testing.T, testCases []opMetaTestCase) {
|
||||
|
||||
t.Run("meta", func(t *testing.T) {
|
||||
t.Helper()
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Helper()
|
||||
t.Parallel()
|
||||
|
||||
t.Run("prefix", func(t *testing.T) {
|
||||
t.Helper()
|
||||
@ -158,7 +149,6 @@ func checkSimple(t *testing.T, fname string, testCases []simpleTestCase) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Helper()
|
||||
t.Parallel()
|
||||
|
||||
wait4signal := make(chan struct{})
|
||||
k := &kstub{wait4signal, stub.New(t, func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { return &kstub{wait4signal, s} }, tc.want)}
|
||||
@ -192,12 +182,10 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
|
||||
|
||||
t.Run("behaviour", func(t *testing.T) {
|
||||
t.Helper()
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Helper()
|
||||
t.Parallel()
|
||||
|
||||
k := &kstub{nil, stub.New(t,
|
||||
func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { return &kstub{nil, s} },
|
||||
@ -341,7 +329,7 @@ func (k *kstub) setDumpable(dumpable uintptr) error {
|
||||
}
|
||||
|
||||
func (k *kstub) setNoNewPrivs() error { k.Helper(); return k.Expects("setNoNewPrivs").Err }
|
||||
func (k *kstub) lastcap(msg message.Msg) uintptr {
|
||||
func (k *kstub) lastcap(msg Msg) uintptr {
|
||||
k.Helper()
|
||||
k.checkMsg(msg)
|
||||
return k.Expects("lastcap").Ret.(uintptr)
|
||||
@ -421,7 +409,7 @@ func (k *kstub) receive(key string, e any, fdp *uintptr) (closeFunc func() error
|
||||
return
|
||||
}
|
||||
|
||||
func (k *kstub) bindMount(msg message.Msg, source, target string, flags uintptr) error {
|
||||
func (k *kstub) bindMount(msg Msg, source, target string, flags uintptr) error {
|
||||
k.Helper()
|
||||
k.checkMsg(msg)
|
||||
return k.Expects("bindMount").Error(
|
||||
@ -430,7 +418,7 @@ func (k *kstub) bindMount(msg message.Msg, source, target string, flags uintptr)
|
||||
stub.CheckArg(k.Stub, "flags", flags, 2))
|
||||
}
|
||||
|
||||
func (k *kstub) remount(msg message.Msg, target string, flags uintptr) error {
|
||||
func (k *kstub) remount(msg Msg, target string, flags uintptr) error {
|
||||
k.Helper()
|
||||
k.checkMsg(msg)
|
||||
return k.Expects("remount").Error(
|
||||
@ -714,7 +702,7 @@ func (k *kstub) wait4(pid int, wstatus *syscall.WaitStatus, options int, rusage
|
||||
return
|
||||
}
|
||||
|
||||
func (k *kstub) printf(_ message.Msg, format string, v ...any) {
|
||||
func (k *kstub) printf(_ Msg, format string, v ...any) {
|
||||
k.Helper()
|
||||
if k.Expects("printf").Error(
|
||||
stub.CheckArg(k.Stub, "format", format, 0),
|
||||
@ -723,7 +711,7 @@ func (k *kstub) printf(_ message.Msg, format string, v ...any) {
|
||||
}
|
||||
}
|
||||
|
||||
func (k *kstub) fatal(_ message.Msg, v ...any) {
|
||||
func (k *kstub) fatal(_ Msg, v ...any) {
|
||||
k.Helper()
|
||||
if k.Expects("fatal").Error(
|
||||
stub.CheckArgReflect(k.Stub, "v", v, 0)) != nil {
|
||||
@ -732,7 +720,7 @@ func (k *kstub) fatal(_ message.Msg, v ...any) {
|
||||
panic(stub.PanicExit)
|
||||
}
|
||||
|
||||
func (k *kstub) fatalf(_ message.Msg, format string, v ...any) {
|
||||
func (k *kstub) fatalf(_ Msg, format string, v ...any) {
|
||||
k.Helper()
|
||||
if k.Expects("fatalf").Error(
|
||||
stub.CheckArg(k.Stub, "format", format, 0),
|
||||
@ -742,7 +730,7 @@ func (k *kstub) fatalf(_ message.Msg, format string, v ...any) {
|
||||
panic(stub.PanicExit)
|
||||
}
|
||||
|
||||
func (k *kstub) checkMsg(msg message.Msg) {
|
||||
func (k *kstub) checkMsg(msg Msg) {
|
||||
k.Helper()
|
||||
var target *kstub
|
||||
|
||||
|
@ -59,7 +59,6 @@ func messagePrefixP[V any, T interface {
|
||||
return zeroString, false
|
||||
}
|
||||
|
||||
// MountError wraps errors returned by syscall.Mount.
|
||||
type MountError struct {
|
||||
Source, Target, Fstype string
|
||||
|
||||
@ -75,7 +74,6 @@ func (e *MountError) Unwrap() error {
|
||||
return e.Errno
|
||||
}
|
||||
|
||||
func (e *MountError) Message() string { return "cannot " + e.Error() }
|
||||
func (e *MountError) Error() string {
|
||||
if e.Flags&syscall.MS_BIND != 0 {
|
||||
if e.Flags&syscall.MS_REMOUNT != 0 {
|
||||
@ -92,15 +90,6 @@ func (e *MountError) Error() string {
|
||||
return "mount " + e.Target + ": " + e.Errno.Error()
|
||||
}
|
||||
|
||||
// optionalErrorUnwrap calls [errors.Unwrap] and returns the resulting value
|
||||
// if it is not nil, or the original value if it is.
|
||||
func optionalErrorUnwrap(err error) error {
|
||||
if underlyingErr := errors.Unwrap(err); underlyingErr != nil {
|
||||
return underlyingErr
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// errnoFallback returns the concrete errno from an error, or a [os.PathError] fallback.
|
||||
func errnoFallback(op, path string, err error) (syscall.Errno, *os.PathError) {
|
||||
var errno syscall.Errno
|
||||
|
@ -14,8 +14,6 @@ import (
|
||||
)
|
||||
|
||||
func TestMessageFromError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
err error
|
||||
@ -56,7 +54,6 @@ func TestMessageFromError(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got, ok := messageFromError(tc.err)
|
||||
if got != tc.want {
|
||||
t.Errorf("messageFromError: %q, want %q", got, tc.want)
|
||||
@ -69,8 +66,6 @@ func TestMessageFromError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMountError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
err error
|
||||
@ -116,7 +111,6 @@ func TestMountError(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("is", func(t *testing.T) {
|
||||
if !errors.Is(tc.err, tc.errno) {
|
||||
t.Errorf("Is: %#v is not %v", tc.err, tc.errno)
|
||||
@ -131,7 +125,6 @@ func TestMountError(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("zero", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if errors.Is(new(MountError), syscall.Errno(0)) {
|
||||
t.Errorf("Is: zero MountError unexpected true")
|
||||
}
|
||||
@ -139,8 +132,6 @@ func TestMountError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestErrnoFallback(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
err error
|
||||
@ -163,7 +154,6 @@ func TestErrnoFallback(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
errno, err := errnoFallback(tc.name, Nonexistent, tc.err)
|
||||
if errno != tc.wantErrno {
|
||||
t.Errorf("errnoFallback: errno = %v, want %v", errno, tc.wantErrno)
|
||||
|
@ -3,8 +3,6 @@ package container
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -12,7 +10,7 @@ var (
|
||||
executableOnce sync.Once
|
||||
)
|
||||
|
||||
func copyExecutable(msg message.Msg) {
|
||||
func copyExecutable(msg Msg) {
|
||||
if name, err := os.Executable(); err != nil {
|
||||
msg.BeforeExit()
|
||||
msg.GetLogger().Fatalf("cannot read executable path: %v", err)
|
||||
@ -21,7 +19,7 @@ func copyExecutable(msg message.Msg) {
|
||||
}
|
||||
}
|
||||
|
||||
func MustExecutable(msg message.Msg) string {
|
||||
func MustExecutable(msg Msg) string {
|
||||
executableOnce.Do(func() { copyExecutable(msg) })
|
||||
return executable
|
||||
}
|
||||
|
@ -5,14 +5,13 @@ import (
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
func TestExecutable(t *testing.T) {
|
||||
t.Parallel()
|
||||
for i := 0; i < 16; i++ {
|
||||
if got := container.MustExecutable(message.NewMsg(nil)); got != os.Args[0] {
|
||||
t.Errorf("MustExecutable: %q, want %q", got, os.Args[0])
|
||||
if got := container.MustExecutable(container.NewMsg(nil)); got != os.Args[0] {
|
||||
t.Errorf("MustExecutable: %q, want %q",
|
||||
got, os.Args[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ import (
|
||||
|
||||
"hakurei.app/container/fhs"
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -62,7 +61,7 @@ type (
|
||||
setupState struct {
|
||||
nonrepeatable uintptr
|
||||
*Params
|
||||
message.Msg
|
||||
Msg
|
||||
}
|
||||
)
|
||||
|
||||
@ -96,14 +95,14 @@ type initParams struct {
|
||||
}
|
||||
|
||||
// Init is called by [TryArgv0] if the current process is the container init.
|
||||
func Init(msg message.Msg) {
|
||||
func Init(msg Msg) {
|
||||
if msg == nil {
|
||||
panic("attempting to call initEntrypoint with nil msg")
|
||||
}
|
||||
initEntrypoint(direct{}, msg)
|
||||
}
|
||||
|
||||
func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
||||
func initEntrypoint(k syscallDispatcher, msg Msg) {
|
||||
k.lockOSThread()
|
||||
|
||||
if k.getpid() != 1 {
|
||||
@ -126,7 +125,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
||||
k.fatal(msg, "invalid setup descriptor")
|
||||
}
|
||||
if errors.Is(err, ErrReceiveEnv) {
|
||||
k.fatal(msg, setupEnv+" not set")
|
||||
k.fatal(msg, "HAKUREI_SETUP not set")
|
||||
}
|
||||
|
||||
k.fatalf(msg, "cannot decode init setup payload: %v", err)
|
||||
@ -178,7 +177,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
||||
lastcap := k.lastcap(msg)
|
||||
|
||||
if err := k.mount(zeroString, fhs.Root, zeroString, MS_SILENT|MS_SLAVE|MS_REC, zeroString); err != nil {
|
||||
k.fatalf(msg, "cannot make / rslave: %v", optionalErrorUnwrap(err))
|
||||
k.fatalf(msg, "cannot make / rslave: %v", err)
|
||||
}
|
||||
|
||||
state := &setupState{Params: ¶ms.Params, Msg: msg}
|
||||
@ -202,7 +201,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
||||
}
|
||||
|
||||
if err := k.mount(SourceTmpfsRootfs, intermediateHostPath, FstypeTmpfs, MS_NODEV|MS_NOSUID, zeroString); err != nil {
|
||||
k.fatalf(msg, "cannot mount intermediate root: %v", optionalErrorUnwrap(err))
|
||||
k.fatalf(msg, "cannot mount intermediate root: %v", err)
|
||||
}
|
||||
if err := k.chdir(intermediateHostPath); err != nil {
|
||||
k.fatalf(msg, "cannot enter intermediate host path: %v", err)
|
||||
@ -212,7 +211,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
||||
k.fatalf(msg, "%v", err)
|
||||
}
|
||||
if err := k.mount(sysrootDir, sysrootDir, zeroString, MS_SILENT|MS_BIND|MS_REC, zeroString); err != nil {
|
||||
k.fatalf(msg, "cannot bind sysroot: %v", optionalErrorUnwrap(err))
|
||||
k.fatalf(msg, "cannot bind sysroot: %v", err)
|
||||
}
|
||||
|
||||
if err := k.mkdir(hostDir, 0755); err != nil {
|
||||
@ -246,7 +245,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
||||
|
||||
// setup requiring host root complete at this point
|
||||
if err := k.mount(hostDir, hostDir, zeroString, MS_SILENT|MS_REC|MS_PRIVATE, zeroString); err != nil {
|
||||
k.fatalf(msg, "cannot make host root rprivate: %v", optionalErrorUnwrap(err))
|
||||
k.fatalf(msg, "cannot make host root rprivate: %v", err)
|
||||
}
|
||||
if err := k.unmount(hostDir, MNT_DETACH); err != nil {
|
||||
k.fatalf(msg, "cannot unmount host root: %v", err)
|
||||
@ -449,11 +448,11 @@ const initName = "init"
|
||||
|
||||
// TryArgv0 calls [Init] if the last element of argv0 is "init".
|
||||
// If a nil msg is passed, the system logger is used instead.
|
||||
func TryArgv0(msg message.Msg) {
|
||||
func TryArgv0(msg Msg) {
|
||||
if msg == nil {
|
||||
log.SetPrefix(initName + ": ")
|
||||
log.SetFlags(0)
|
||||
msg = message.NewMsg(log.Default())
|
||||
msg = NewMsg(log.Default())
|
||||
}
|
||||
|
||||
if len(os.Args) > 0 && path.Base(os.Args[0]) == initName {
|
||||
|
@ -13,7 +13,6 @@ import (
|
||||
)
|
||||
|
||||
func TestInitEntrypoint(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checkSimple(t, "initEntrypoint", []simpleTestCase{
|
||||
{"getpid", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
@ -2650,7 +2649,6 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestOpsGrow(t *testing.T) {
|
||||
t.Parallel()
|
||||
ops := new(Ops)
|
||||
ops.Grow(1 << 4)
|
||||
if got := cap(*ops); got == 0 {
|
||||
|
@ -12,8 +12,6 @@ import (
|
||||
)
|
||||
|
||||
func TestBindMountOp(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||
{"ENOENT not optional", new(Params), &BindMountOp{
|
||||
Source: check.MustAbs("/bin/"),
|
||||
@ -166,10 +164,7 @@ func TestBindMountOp(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("unreachable", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("nil sourceFinal not optional", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
wantErr := OpStateError("bind")
|
||||
if err := new(BindMountOp).apply(nil, nil); !errors.Is(err, wantErr) {
|
||||
t.Errorf("apply: error = %v, want %v", err, wantErr)
|
||||
|
@ -9,8 +9,6 @@ import (
|
||||
)
|
||||
|
||||
func TestMountDevOp(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||
{"mountTmpfs", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||
Target: check.MustAbs("/dev/"),
|
||||
|
@ -9,8 +9,6 @@ import (
|
||||
)
|
||||
|
||||
func TestMkdirOp(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||
{"success", new(Params), &MkdirOp{
|
||||
Path: check.MustAbs("/.hakurei"),
|
||||
|
@ -10,11 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func TestMountOverlayOp(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("argument error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
err *OverlayArgumentError
|
||||
@ -34,7 +30,6 @@ func TestMountOverlayOp(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := tc.err.Error(); got != tc.want {
|
||||
t.Errorf("Error: %q, want %q", got, tc.want)
|
||||
}
|
||||
@ -275,10 +270,7 @@ func TestMountOverlayOp(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("unreachable", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("nil Upper non-nil Work not ephemeral", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
wantErr := OpStateError("overlay")
|
||||
if err := (&MountOverlayOp{
|
||||
Work: check.MustAbs("/"),
|
||||
|
@ -22,6 +22,15 @@ func (f *Ops) Place(name *check.Absolute, data []byte) *Ops {
|
||||
return f
|
||||
}
|
||||
|
||||
// PlaceP is like Place but writes the address of [TmpfileOp.Data] to the pointer dataP points to.
|
||||
func (f *Ops) PlaceP(name *check.Absolute, dataP **[]byte) *Ops {
|
||||
t := &TmpfileOp{Path: name}
|
||||
*dataP = &t.Data
|
||||
|
||||
*f = append(*f, t)
|
||||
return f
|
||||
}
|
||||
|
||||
// TmpfileOp places a file on container Path containing Data.
|
||||
type TmpfileOp struct {
|
||||
Path *check.Absolute
|
||||
|
@ -14,7 +14,6 @@ func TestTmpfileOp(t *testing.T) {
|
||||
samplePath = check.MustAbs("/etc/passwd")
|
||||
sampleData = []byte(sampleDataString)
|
||||
)
|
||||
t.Parallel()
|
||||
|
||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||
{"createTemp", &Params{ParentPerm: 0700}, &TmpfileOp{
|
||||
@ -83,8 +82,18 @@ func TestTmpfileOp(t *testing.T) {
|
||||
})
|
||||
|
||||
checkOpsBuilder(t, []opsBuilderTestCase{
|
||||
{"full", new(Ops).Place(samplePath, sampleData), Ops{
|
||||
&TmpfileOp{Path: samplePath, Data: sampleData},
|
||||
{"noref", new(Ops).Place(samplePath, sampleData), Ops{
|
||||
&TmpfileOp{
|
||||
Path: samplePath,
|
||||
Data: sampleData,
|
||||
},
|
||||
}},
|
||||
|
||||
{"ref", new(Ops).PlaceP(samplePath, new(*[]byte)), Ops{
|
||||
&TmpfileOp{
|
||||
Path: samplePath,
|
||||
Data: []byte{},
|
||||
},
|
||||
}},
|
||||
})
|
||||
|
||||
|
@ -9,8 +9,6 @@ import (
|
||||
)
|
||||
|
||||
func TestMountProcOp(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||
{"mkdir", &Params{ParentPerm: 0755},
|
||||
&MountProcOp{
|
||||
|
@ -9,8 +9,6 @@ import (
|
||||
)
|
||||
|
||||
func TestRemountOp(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||
{"success", new(Params), &RemountOp{
|
||||
Target: check.MustAbs("/"),
|
||||
|
@ -9,8 +9,6 @@ import (
|
||||
)
|
||||
|
||||
func TestSymlinkOp(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||
{"mkdir", &Params{ParentPerm: 0700}, &SymlinkOp{
|
||||
Target: check.MustAbs("/etc/nixos"),
|
||||
|
@ -10,10 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func TestMountTmpfsOp(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("size error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmpfsSizeError := TmpfsSizeError(-1)
|
||||
want := "tmpfs size -1 out of bounds"
|
||||
if got := tmpfsSizeError.Error(); got != want {
|
||||
|
@ -8,8 +8,6 @@ import (
|
||||
)
|
||||
|
||||
func TestLandlockString(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
rulesetAttr *container.RulesetAttr
|
||||
@ -48,7 +46,6 @@ func TestLandlockString(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := tc.rulesetAttr.String(); got != tc.want {
|
||||
t.Errorf("String: %s, want %s", got, tc.want)
|
||||
}
|
||||
@ -57,7 +54,6 @@ func TestLandlockString(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLandlockAttrSize(t *testing.T) {
|
||||
t.Parallel()
|
||||
want := 24
|
||||
if got := unsafe.Sizeof(container.RulesetAttr{}); got != uintptr(want) {
|
||||
t.Errorf("Sizeof: %d, want %d", got, want)
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
. "syscall"
|
||||
|
||||
"hakurei.app/container/vfs"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
/*
|
||||
@ -88,7 +87,7 @@ const (
|
||||
)
|
||||
|
||||
// bindMount mounts source on target and recursively applies flags if MS_REC is set.
|
||||
func (p *procPaths) bindMount(msg message.Msg, source, target string, flags uintptr) error {
|
||||
func (p *procPaths) bindMount(msg Msg, source, target string, flags uintptr) error {
|
||||
// syscallDispatcher.bindMount and procPaths.remount must not be called from this function
|
||||
|
||||
if err := p.k.mount(source, target, FstypeNULL, MS_SILENT|MS_BIND|flags&MS_REC, zeroString); err != nil {
|
||||
@ -98,7 +97,7 @@ func (p *procPaths) bindMount(msg message.Msg, source, target string, flags uint
|
||||
}
|
||||
|
||||
// remount applies flags on target, recursively if MS_REC is set.
|
||||
func (p *procPaths) remount(msg message.Msg, target string, flags uintptr) error {
|
||||
func (p *procPaths) remount(msg Msg, target string, flags uintptr) error {
|
||||
// syscallDispatcher methods bindMount, remount must not be called from this function
|
||||
|
||||
var targetFinal string
|
||||
@ -160,7 +159,7 @@ func (p *procPaths) remount(msg message.Msg, target string, flags uintptr) error
|
||||
}
|
||||
|
||||
// remountWithFlags remounts mount point described by [vfs.MountInfoNode].
|
||||
func remountWithFlags(k syscallDispatcher, msg message.Msg, n *vfs.MountInfoNode, mf uintptr) error {
|
||||
func remountWithFlags(k syscallDispatcher, msg Msg, n *vfs.MountInfoNode, mf uintptr) error {
|
||||
// syscallDispatcher methods bindMount, remount must not be called from this function
|
||||
|
||||
kf, unmatched := n.Flags()
|
||||
|
@ -10,8 +10,6 @@ import (
|
||||
)
|
||||
|
||||
func TestBindMount(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checkSimple(t, "bindMount", []simpleTestCase{
|
||||
{"mount", func(k *kstub) error {
|
||||
return newProcPaths(k, hostPath).bindMount(nil, "/host/nix", "/sysroot/nix", syscall.MS_RDONLY)
|
||||
@ -36,8 +34,6 @@ func TestBindMount(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRemount(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const sampleMountinfoNix = `254 407 253:0 / /host rw,relatime master:1 - ext4 /dev/disk/by-label/nixos rw
|
||||
255 254 0:28 / /host/mnt/.ro-cwd ro,noatime master:2 - 9p cwd ro,access=client,msize=16384,trans=virtio
|
||||
256 254 0:29 / /host/nix/.ro-store rw,relatime master:3 - 9p nix-store rw,cache=f,access=client,msize=16384,trans=virtio
|
||||
@ -220,8 +216,6 @@ func TestRemount(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRemountWithFlags(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checkSimple(t, "remountWithFlags", []simpleTestCase{
|
||||
{"noop unmatched", func(k *kstub) error {
|
||||
return remountWithFlags(k, k, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime,cat"}}, 0)
|
||||
@ -242,8 +236,6 @@ func TestRemountWithFlags(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMountTmpfs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checkSimple(t, "mountTmpfs", []simpleTestCase{
|
||||
{"mkdirAll", func(k *kstub) error {
|
||||
return mountTmpfs(k, "ephemeral", "/sysroot/run/user/1000", 0, 1<<10, 0700)
|
||||
@ -268,8 +260,6 @@ func TestMountTmpfs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParentPerm(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
perm os.FileMode
|
||||
want os.FileMode
|
||||
@ -285,7 +275,6 @@ func TestParentPerm(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.perm.String(), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := parentPerm(tc.perm); got != tc.want {
|
||||
t.Errorf("parentPerm: %#o, want %#o", got, tc.want)
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
// Package message provides interfaces and a base implementation for extended reporting on top of [log.Logger]
|
||||
package message
|
||||
package container
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@ -7,19 +6,19 @@ import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Error is an error with a user-facing message.
|
||||
type Error interface {
|
||||
// MessageError is an error with a user-facing message.
|
||||
type MessageError interface {
|
||||
// Message returns a user-facing error message.
|
||||
Message() string
|
||||
|
||||
error
|
||||
}
|
||||
|
||||
// GetMessage returns whether an error implements [Error], and the message if it does.
|
||||
func GetMessage(err error) (string, bool) {
|
||||
var e Error
|
||||
// GetErrorMessage returns whether an error implements [MessageError], and the message if it does.
|
||||
func GetErrorMessage(err error) (string, bool) {
|
||||
var e MessageError
|
||||
if !errors.As(err, &e) || e == nil {
|
||||
return "", false
|
||||
return zeroString, false
|
||||
}
|
||||
return e.Message(), true
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package message_test
|
||||
package container_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -11,12 +11,9 @@ import (
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/stub"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
func TestMessageError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
err error
|
||||
@ -32,13 +29,12 @@ func TestMessageError(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got, ok := message.GetMessage(tc.err)
|
||||
got, ok := container.GetErrorMessage(tc.err)
|
||||
if got != tc.want {
|
||||
t.Errorf("GetMessage: %q, want %q", got, tc.want)
|
||||
t.Errorf("GetErrorMessage: %q, want %q", got, tc.want)
|
||||
}
|
||||
if ok != tc.wantOk {
|
||||
t.Errorf("GetMessage: ok = %v, want %v", ok, tc.wantOk)
|
||||
t.Errorf("GetErrorMessage: ok = %v, want %v", ok, tc.wantOk)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -47,13 +43,12 @@ func TestMessageError(t *testing.T) {
|
||||
func TestDefaultMsg(t *testing.T) {
|
||||
// copied from output.go
|
||||
const suspendBufMax = 1 << 24
|
||||
t.Parallel()
|
||||
|
||||
t.Run("logger", func(t *testing.T) {
|
||||
t.Run("nil", func(t *testing.T) {
|
||||
got := message.NewMsg(nil).GetLogger()
|
||||
got := container.NewMsg(nil).GetLogger()
|
||||
|
||||
if out := got.Writer().(*message.Suspendable).Downstream; out != log.Writer() {
|
||||
if out := got.Writer().(*container.Suspendable).Downstream; out != log.Writer() {
|
||||
t.Errorf("GetLogger: Downstream = %#v", out)
|
||||
}
|
||||
|
||||
@ -64,13 +59,13 @@ func TestDefaultMsg(t *testing.T) {
|
||||
|
||||
t.Run("takeover", func(t *testing.T) {
|
||||
l := log.New(io.Discard, "\x00", 0xdeadbeef)
|
||||
got := message.NewMsg(l)
|
||||
got := container.NewMsg(l)
|
||||
|
||||
if logger := got.GetLogger(); logger != l {
|
||||
t.Errorf("GetLogger: %#v, want %#v", logger, l)
|
||||
}
|
||||
|
||||
if ds := l.Writer().(*message.Suspendable).Downstream; ds != io.Discard {
|
||||
if ds := l.Writer().(*container.Suspendable).Downstream; ds != io.Discard {
|
||||
t.Errorf("GetLogger: Downstream = %#v", ds)
|
||||
}
|
||||
})
|
||||
@ -83,93 +78,93 @@ func TestDefaultMsg(t *testing.T) {
|
||||
pt, next []byte
|
||||
err error
|
||||
|
||||
f func(t *testing.T, msg message.Msg)
|
||||
f func(t *testing.T, msg container.Msg)
|
||||
}{
|
||||
{"zero verbose", nil, nil, nil, func(t *testing.T, msg message.Msg) {
|
||||
{"zero verbose", nil, nil, nil, func(t *testing.T, msg container.Msg) {
|
||||
if msg.IsVerbose() {
|
||||
t.Error("IsVerbose unexpected true")
|
||||
}
|
||||
}},
|
||||
|
||||
{"swap false", nil, nil, nil, func(t *testing.T, msg message.Msg) {
|
||||
{"swap false", nil, nil, nil, func(t *testing.T, msg container.Msg) {
|
||||
if msg.SwapVerbose(false) {
|
||||
t.Error("SwapVerbose unexpected true")
|
||||
}
|
||||
}},
|
||||
{"write discard", nil, nil, nil, func(_ *testing.T, msg message.Msg) {
|
||||
{"write discard", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||
msg.Verbose("\x00")
|
||||
msg.Verbosef("\x00")
|
||||
}},
|
||||
{"verbose false", nil, nil, nil, func(t *testing.T, msg message.Msg) {
|
||||
{"verbose false", nil, nil, nil, func(t *testing.T, msg container.Msg) {
|
||||
if msg.IsVerbose() {
|
||||
t.Error("IsVerbose unexpected true")
|
||||
}
|
||||
}},
|
||||
|
||||
{"swap true", nil, nil, nil, func(t *testing.T, msg message.Msg) {
|
||||
{"swap true", nil, nil, nil, func(t *testing.T, msg container.Msg) {
|
||||
if msg.SwapVerbose(true) {
|
||||
t.Error("SwapVerbose unexpected true")
|
||||
}
|
||||
}},
|
||||
{"write verbose", []byte("test: \x00\n"), nil, nil, func(_ *testing.T, msg message.Msg) {
|
||||
{"write verbose", []byte("test: \x00\n"), nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||
msg.Verbose("\x00")
|
||||
}},
|
||||
{"write verbosef", []byte(`test: "\x00"` + "\n"), nil, nil, func(_ *testing.T, msg message.Msg) {
|
||||
{"write verbosef", []byte(`test: "\x00"` + "\n"), nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||
msg.Verbosef("%q", "\x00")
|
||||
}},
|
||||
{"verbose true", nil, nil, nil, func(t *testing.T, msg message.Msg) {
|
||||
{"verbose true", nil, nil, nil, func(t *testing.T, msg container.Msg) {
|
||||
if !msg.IsVerbose() {
|
||||
t.Error("IsVerbose unexpected false")
|
||||
}
|
||||
}},
|
||||
|
||||
{"resume noop", nil, nil, nil, func(t *testing.T, msg message.Msg) {
|
||||
{"resume noop", nil, nil, nil, func(t *testing.T, msg container.Msg) {
|
||||
if msg.Resume() {
|
||||
t.Error("Resume unexpected success")
|
||||
}
|
||||
}},
|
||||
{"beforeExit noop", nil, nil, nil, func(_ *testing.T, msg message.Msg) {
|
||||
{"beforeExit noop", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||
msg.BeforeExit()
|
||||
}},
|
||||
|
||||
{"beforeExit suspend", nil, nil, nil, func(_ *testing.T, msg message.Msg) {
|
||||
{"beforeExit suspend", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||
msg.Suspend()
|
||||
}},
|
||||
{"beforeExit message", []byte("test: beforeExit reached on suspended output\n"), nil, nil, func(_ *testing.T, msg message.Msg) {
|
||||
{"beforeExit message", []byte("test: beforeExit reached on suspended output\n"), nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||
msg.BeforeExit()
|
||||
}},
|
||||
{"post beforeExit resume noop", nil, nil, nil, func(t *testing.T, msg message.Msg) {
|
||||
{"post beforeExit resume noop", nil, nil, nil, func(t *testing.T, msg container.Msg) {
|
||||
if msg.Resume() {
|
||||
t.Error("Resume unexpected success")
|
||||
}
|
||||
}},
|
||||
|
||||
{"suspend", nil, nil, nil, func(_ *testing.T, msg message.Msg) {
|
||||
{"suspend", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||
msg.Suspend()
|
||||
}},
|
||||
{"suspend write", nil, nil, nil, func(_ *testing.T, msg message.Msg) {
|
||||
{"suspend write", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||
msg.GetLogger().Print("\x00")
|
||||
}},
|
||||
{"resume error", []byte("test: \x00\n"), []byte("test: cannot dump buffer on resume: unique error 0 injected by the test suite\n"), stub.UniqueError(0), func(t *testing.T, msg message.Msg) {
|
||||
{"resume error", []byte("test: \x00\n"), []byte("test: cannot dump buffer on resume: unique error 0 injected by the test suite\n"), stub.UniqueError(0), func(t *testing.T, msg container.Msg) {
|
||||
if !msg.Resume() {
|
||||
t.Error("Resume unexpected failure")
|
||||
}
|
||||
}},
|
||||
|
||||
{"suspend drop", nil, nil, nil, func(_ *testing.T, msg message.Msg) {
|
||||
{"suspend drop", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||
msg.Suspend()
|
||||
}},
|
||||
{"suspend write fill", nil, nil, nil, func(_ *testing.T, msg message.Msg) {
|
||||
{"suspend write fill", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||
msg.GetLogger().Print(strings.Repeat("\x00", suspendBufMax))
|
||||
}},
|
||||
{"resume dropped", append([]byte("test: "), bytes.Repeat([]byte{0}, suspendBufMax-6)...), []byte("test: dropped 7 bytes while output is suspended\n"), nil, func(t *testing.T, msg message.Msg) {
|
||||
{"resume dropped", append([]byte("test: "), bytes.Repeat([]byte{0}, suspendBufMax-6)...), []byte("test: dropped 7 bytes while output is suspended\n"), nil, func(t *testing.T, msg container.Msg) {
|
||||
if !msg.Resume() {
|
||||
t.Error("Resume unexpected failure")
|
||||
}
|
||||
}},
|
||||
}
|
||||
|
||||
msg := message.NewMsg(log.New(&dw, "test: ", 0))
|
||||
msg := container.NewMsg(log.New(&dw, "test: ", 0))
|
||||
for _, step := range steps {
|
||||
// these share the same writer, so cannot be subtests
|
||||
t.Logf("running step %q", step.name)
|
@ -1,4 +1,4 @@
|
||||
package message
|
||||
package container
|
||||
|
||||
import (
|
||||
"bytes"
|
@ -1,4 +1,4 @@
|
||||
package message_test
|
||||
package container_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -8,14 +8,13 @@ import (
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/stub"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
func TestSuspendable(t *testing.T) {
|
||||
// copied from output.go
|
||||
const suspendBufMax = 1 << 24
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
// equivalent to len(want.pt)
|
||||
@ -75,7 +74,7 @@ func TestSuspendable(t *testing.T) {
|
||||
|
||||
var dw expectWriter
|
||||
|
||||
w := message.Suspendable{Downstream: &dw}
|
||||
w := container.Suspendable{Downstream: &dw}
|
||||
for _, step := range steps {
|
||||
// these share the same writer, so cannot be subtests
|
||||
t.Logf("writing step %q", step.name)
|
@ -31,7 +31,7 @@ func Receive(key string, e any, fdp *uintptr) (func() error, error) {
|
||||
return nil, ErrReceiveEnv
|
||||
} else {
|
||||
if fd, err := strconv.Atoi(s); err != nil {
|
||||
return nil, optionalErrorUnwrap(err)
|
||||
return nil, errors.Unwrap(err)
|
||||
} else {
|
||||
setup = os.NewFile(uintptr(fd), "setup")
|
||||
if setup == nil {
|
||||
|
@ -117,7 +117,7 @@ func Export(fd int, rules []NativeRule, flags ExportFlag) error {
|
||||
|
||||
var ret C.int
|
||||
|
||||
var rulesPinner runtime.Pinner
|
||||
rulesPinner := new(runtime.Pinner)
|
||||
for i := range rules {
|
||||
rule := &rules[i]
|
||||
rulesPinner.Pin(rule)
|
||||
@ -189,5 +189,6 @@ func syscallResolveName(s string) (trap int) {
|
||||
v := C.CString(s)
|
||||
trap = int(C.seccomp_syscall_resolve_name(v))
|
||||
C.free(unsafe.Pointer(v))
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -13,8 +13,6 @@ import (
|
||||
)
|
||||
|
||||
func TestExport(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
flags ExportFlag
|
||||
@ -34,15 +32,14 @@ func TestExport(t *testing.T) {
|
||||
{"hakurei tty", 0, PresetExt | PresetDenyNS | PresetDenyDevel, false},
|
||||
}
|
||||
|
||||
buf := make([]byte, 8)
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := New(Preset(tc.presets, tc.flags), tc.flags)
|
||||
want := bpfExpected[bpfPreset{tc.flags, tc.presets}]
|
||||
digest := sha512.New()
|
||||
|
||||
if _, err := io.Copy(digest, e); (err != nil) != tc.wantErr {
|
||||
if _, err := io.CopyBuffer(digest, e, buf); (err != nil) != tc.wantErr {
|
||||
t.Errorf("Exporter: error = %v, wantErr %v", err, tc.wantErr)
|
||||
return
|
||||
}
|
||||
@ -50,7 +47,7 @@ func TestExport(t *testing.T) {
|
||||
t.Errorf("Close: error = %v", err)
|
||||
}
|
||||
if got := digest.Sum(nil); !slices.Equal(got, want) {
|
||||
t.Fatalf("Export: hash = %x, want %x",
|
||||
t.Fatalf("Export() hash = %x, want %x",
|
||||
got, want)
|
||||
return
|
||||
}
|
||||
|
@ -21,7 +21,9 @@ Methods of Encoder are not safe for concurrent use.
|
||||
|
||||
An Encoder must not be copied after first use.
|
||||
*/
|
||||
type Encoder struct{ *exporter }
|
||||
type Encoder struct {
|
||||
*exporter
|
||||
}
|
||||
|
||||
func (e *Encoder) Read(p []byte) (n int, err error) {
|
||||
if err = e.prepare(); err != nil {
|
||||
|
@ -10,8 +10,6 @@ import (
|
||||
)
|
||||
|
||||
func TestLibraryError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
sample *seccomp.LibraryError
|
||||
@ -43,8 +41,6 @@ func TestLibraryError(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if errors.Is(tc.sample, tc.compare) != tc.wantIs {
|
||||
t.Errorf("errors.Is(%#v, %#v) did not return %v",
|
||||
tc.sample, tc.compare, tc.wantIs)
|
||||
@ -58,8 +54,6 @@ func TestLibraryError(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
wantPanic := "invalid libseccomp error"
|
||||
defer func() {
|
||||
if r := recover(); r != wantPanic {
|
||||
|
@ -5,17 +5,15 @@ import (
|
||||
)
|
||||
|
||||
func TestSyscallResolveName(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for name, want := range Syscalls() {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if got := syscallResolveName(name); got != want {
|
||||
t.Errorf("syscallResolveName(%q) = %d, want %d", name, got, want)
|
||||
t.Errorf("syscallResolveName(%q) = %d, want %d",
|
||||
name, got, want)
|
||||
}
|
||||
if got, ok := SyscallResolveName(name); !ok || got != want {
|
||||
t.Errorf("SyscallResolveName(%q) = %d, want %d", name, got, want)
|
||||
t.Errorf("SyscallResolveName(%q) = %d, want %d",
|
||||
name, got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -8,17 +8,13 @@ import (
|
||||
)
|
||||
|
||||
func TestCallError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("contains false", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := new(stub.Call).Error(true, false, true); !reflect.DeepEqual(err, stub.ErrCheck) {
|
||||
t.Errorf("Error: %#v, want %#v", err, stub.ErrCheck)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("passthrough", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
wantErr := stub.UniqueError(0xbabe)
|
||||
if err := (&stub.Call{Err: wantErr}).Error(true); !reflect.DeepEqual(err, wantErr) {
|
||||
t.Errorf("Error: %#v, want %#v", err, wantErr)
|
||||
|
@ -9,10 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func TestUniqueError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("format", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
want := "unique error 2989 injected by the test suite"
|
||||
if got := stub.UniqueError(0xbad).Error(); got != want {
|
||||
t.Errorf("Error: %q, want %q", got, want)
|
||||
@ -20,17 +17,13 @@ func TestUniqueError(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("is", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("type", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if errors.Is(stub.UniqueError(0), syscall.ENOTRECOVERABLE) {
|
||||
t.Error("Is: unexpected true")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("val", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if errors.Is(stub.UniqueError(0), stub.UniqueError(1)) {
|
||||
t.Error("Is: unexpected true")
|
||||
}
|
||||
|
@ -32,20 +32,13 @@ func (o *overrideTFailNow) Fail() {
|
||||
}
|
||||
|
||||
func TestHandleExit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("exit", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
defer stub.HandleExit(t)
|
||||
panic(stub.PanicExit)
|
||||
})
|
||||
|
||||
t.Run("goexit", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("FailNow", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ot := &overrideTFailNow{T: t}
|
||||
defer func() {
|
||||
if !ot.failNow {
|
||||
@ -57,8 +50,6 @@ func TestHandleExit(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Fail", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ot := &overrideTFailNow{T: t}
|
||||
defer func() {
|
||||
if !ot.fail {
|
||||
@ -71,16 +62,11 @@ func TestHandleExit(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("nil", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
defer stub.HandleExit(t)
|
||||
})
|
||||
|
||||
t.Run("passthrough", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("toplevel", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defer func() {
|
||||
want := 0xcafebabe
|
||||
if r := recover(); r != want {
|
||||
@ -93,8 +79,6 @@ func TestHandleExit(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("new", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defer func() {
|
||||
want := 0xcafe
|
||||
if r := recover(); r != want {
|
||||
|
@ -45,17 +45,12 @@ func New[K any](tb testing.TB, makeK func(s *Stub[K]) K, want Expect) *Stub[K] {
|
||||
return &Stub[K]{TB: tb, makeK: makeK, want: want, wg: new(sync.WaitGroup)}
|
||||
}
|
||||
|
||||
func (s *Stub[K]) FailNow() { s.Helper(); panic(panicFailNow) }
|
||||
func (s *Stub[K]) Fatal(args ...any) { s.Helper(); s.Error(args...); panic(panicFatal) }
|
||||
func (s *Stub[K]) Fatalf(format string, args ...any) {
|
||||
s.Helper()
|
||||
s.Errorf(format, args...)
|
||||
panic(panicFatalf)
|
||||
}
|
||||
|
||||
func (s *Stub[K]) SkipNow() { s.Helper(); panic("invalid call to SkipNow") }
|
||||
func (s *Stub[K]) Skip(...any) { s.Helper(); panic("invalid call to Skip") }
|
||||
func (s *Stub[K]) Skipf(string, ...any) { s.Helper(); panic("invalid call to Skipf") }
|
||||
func (s *Stub[K]) FailNow() { panic(panicFailNow) }
|
||||
func (s *Stub[K]) Fatal(args ...any) { s.Error(args...); panic(panicFatal) }
|
||||
func (s *Stub[K]) Fatalf(format string, args ...any) { s.Errorf(format, args...); panic(panicFatalf) }
|
||||
func (s *Stub[K]) SkipNow() { panic("invalid call to SkipNow") }
|
||||
func (s *Stub[K]) Skip(...any) { panic("invalid call to Skip") }
|
||||
func (s *Stub[K]) Skipf(string, ...any) { panic("invalid call to Skipf") }
|
||||
|
||||
// New calls f in a new goroutine
|
||||
func (s *Stub[K]) New(f func(k K)) {
|
||||
|
@ -36,65 +36,49 @@ func (t *overrideT) Errorf(format string, args ...any) {
|
||||
}
|
||||
|
||||
func TestStub(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("goexit", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("FailNow", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != panicFailNow {
|
||||
t.Errorf("recover: %v", r)
|
||||
}
|
||||
}()
|
||||
stubHolder{&Stub[stubHolder]{TB: t}}.FailNow()
|
||||
new(stubHolder).FailNow()
|
||||
})
|
||||
|
||||
t.Run("SkipNow", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defer func() {
|
||||
want := "invalid call to SkipNow"
|
||||
if r := recover(); r != want {
|
||||
t.Errorf("recover: %v, want %v", r, want)
|
||||
}
|
||||
}()
|
||||
stubHolder{&Stub[stubHolder]{TB: t}}.SkipNow()
|
||||
new(stubHolder).SkipNow()
|
||||
})
|
||||
|
||||
t.Run("Skip", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defer func() {
|
||||
want := "invalid call to Skip"
|
||||
if r := recover(); r != want {
|
||||
t.Errorf("recover: %v, want %v", r, want)
|
||||
}
|
||||
}()
|
||||
stubHolder{&Stub[stubHolder]{TB: t}}.Skip()
|
||||
new(stubHolder).Skip()
|
||||
})
|
||||
|
||||
t.Run("Skipf", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defer func() {
|
||||
want := "invalid call to Skipf"
|
||||
if r := recover(); r != want {
|
||||
t.Errorf("recover: %v, want %v", r, want)
|
||||
}
|
||||
}()
|
||||
stubHolder{&Stub[stubHolder]{TB: t}}.Skipf("")
|
||||
new(stubHolder).Skipf("")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("new", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := New(t, func(s *Stub[stubHolder]) stubHolder { return stubHolder{s} }, Expect{Calls: []Call{
|
||||
{"New", ExpectArgs{}, nil, nil},
|
||||
}, Tracks: []Expect{{Calls: []Call{
|
||||
@ -128,8 +112,6 @@ func TestStub(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("overrun", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ot := &overrideT{T: t}
|
||||
ot.error.Store(checkError(t, "New: track overrun"))
|
||||
s := New(ot, func(s *Stub[stubHolder]) stubHolder { return stubHolder{s} }, Expect{Calls: []Call{
|
||||
@ -153,11 +135,7 @@ func TestStub(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("expects", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("overrun", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ot := &overrideT{T: t}
|
||||
ot.error.Store(checkError(t, "Expects: advancing beyond expected calls"))
|
||||
s := New(ot, func(s *Stub[stubHolder]) stubHolder { return stubHolder{s} }, Expect{})
|
||||
@ -165,11 +143,7 @@ func TestStub(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("separator", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("overrun", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ot := &overrideT{T: t}
|
||||
ot.errorf.Store(checkErrorf(t, "Expects: func = %s, separator overrun", "meow"))
|
||||
s := New(ot, func(s *Stub[stubHolder]) stubHolder { return stubHolder{s} }, Expect{Calls: []Call{
|
||||
@ -179,8 +153,6 @@ func TestStub(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("mismatch", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ot := &overrideT{T: t}
|
||||
ot.errorf.Store(checkErrorf(t, "Expects: separator, want %s", "panic"))
|
||||
s := New(ot, func(s *Stub[stubHolder]) stubHolder { return stubHolder{s} }, Expect{Calls: []Call{
|
||||
@ -191,8 +163,6 @@ func TestStub(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("mismatch", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ot := &overrideT{T: t}
|
||||
ot.errorf.Store(checkErrorf(t, "Expects: func = %s, want %s", "meow", "nya"))
|
||||
s := New(ot, func(s *Stub[stubHolder]) stubHolder { return stubHolder{s} }, Expect{Calls: []Call{
|
||||
@ -206,8 +176,6 @@ func TestStub(t *testing.T) {
|
||||
|
||||
func TestCheckArg(t *testing.T) {
|
||||
t.Run("oob negative", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defer func() {
|
||||
want := "invalid call to CheckArg"
|
||||
if r := recover(); r != want {
|
||||
@ -223,14 +191,12 @@ func TestCheckArg(t *testing.T) {
|
||||
{"panic", ExpectArgs{PanicExit}, nil, nil},
|
||||
{"meow", ExpectArgs{-1}, nil, nil},
|
||||
}})
|
||||
|
||||
t.Run("match", func(t *testing.T) {
|
||||
s.Expects("panic")
|
||||
if !CheckArg(s, "v", PanicExit, 0) {
|
||||
t.Errorf("CheckArg: unexpected false")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("mismatch", func(t *testing.T) {
|
||||
defer HandleExit(t)
|
||||
s.Expects("meow")
|
||||
@ -239,7 +205,6 @@ func TestCheckArg(t *testing.T) {
|
||||
t.Errorf("CheckArg: unexpected true")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("oob", func(t *testing.T) {
|
||||
s.pos++
|
||||
defer func() {
|
||||
@ -253,11 +218,7 @@ func TestCheckArg(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCheckArgReflect(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("oob lower", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defer func() {
|
||||
want := "invalid call to CheckArgReflect"
|
||||
if r := recover(); r != want {
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"sync"
|
||||
|
||||
"hakurei.app/container/fhs"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -24,7 +23,7 @@ const (
|
||||
kernelCapLastCapPath = fhs.ProcSys + "kernel/cap_last_cap"
|
||||
)
|
||||
|
||||
func mustReadSysctl(msg message.Msg) {
|
||||
func mustReadSysctl(msg Msg) {
|
||||
sysctlOnce.Do(func() {
|
||||
if v, err := os.ReadFile(kernelOverflowuidPath); err != nil {
|
||||
msg.GetLogger().Fatalf("cannot read %q: %v", kernelOverflowuidPath, err)
|
||||
@ -46,6 +45,6 @@ func mustReadSysctl(msg message.Msg) {
|
||||
})
|
||||
}
|
||||
|
||||
func OverflowUid(msg message.Msg) int { mustReadSysctl(msg); return kernelOverflowuid }
|
||||
func OverflowGid(msg message.Msg) int { mustReadSysctl(msg); return kernelOverflowgid }
|
||||
func LastCap(msg message.Msg) uintptr { mustReadSysctl(msg); return uintptr(kernelCapLastCap) }
|
||||
func OverflowUid(msg Msg) int { mustReadSysctl(msg); return kernelOverflowuid }
|
||||
func OverflowGid(msg Msg) int { mustReadSysctl(msg); return kernelOverflowgid }
|
||||
func LastCap(msg Msg) uintptr { mustReadSysctl(msg); return uintptr(kernelCapLastCap) }
|
||||
|
@ -7,8 +7,6 @@ import (
|
||||
)
|
||||
|
||||
func TestUnmangle(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
want string
|
||||
sample string
|
||||
@ -19,7 +17,6 @@ func TestUnmangle(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.want, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := vfs.Unmangle(tc.sample)
|
||||
if got != tc.want {
|
||||
t.Errorf("Unmangle: %q, want %q",
|
||||
|
@ -17,8 +17,6 @@ import (
|
||||
)
|
||||
|
||||
func TestDecoderError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
err *vfs.DecoderError
|
||||
@ -37,17 +35,13 @@ func TestDecoderError(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := tc.err.Error(); got != tc.want {
|
||||
t.Errorf("Error: %s, want %s", got, tc.want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("is", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !errors.Is(tc.err, tc.target) {
|
||||
t.Errorf("Is: unexpected false")
|
||||
}
|
||||
@ -60,8 +54,6 @@ func TestDecoderError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMountInfo(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []mountInfoTest{
|
||||
{"count", sampleMountinfoBase + `
|
||||
21 20 0:53/ /mnt/test rw,relatime - tmpfs rw
|
||||
@ -195,10 +187,7 @@ id 20 0:53 / /mnt/test rw,relatime shared:212 - tmpfs rw
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("decode", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
var got *vfs.MountInfo
|
||||
d := vfs.NewMountInfoDecoder(strings.NewReader(tc.sample))
|
||||
err := d.Decode(&got)
|
||||
@ -217,7 +206,6 @@ id 20 0:53 / /mnt/test rw,relatime shared:212 - tmpfs rw
|
||||
})
|
||||
|
||||
t.Run("iter", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
d := vfs.NewMountInfoDecoder(strings.NewReader(tc.sample))
|
||||
tc.check(t, d, "Entries",
|
||||
d.Entries(), d.Err)
|
||||
@ -229,7 +217,6 @@ id 20 0:53 / /mnt/test rw,relatime shared:212 - tmpfs rw
|
||||
})
|
||||
|
||||
t.Run("yield", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
d := vfs.NewMountInfoDecoder(strings.NewReader(tc.sample))
|
||||
v := false
|
||||
d.Entries()(func(entry *vfs.MountInfoEntry) bool { v = !v; return v })
|
||||
|
@ -10,8 +10,6 @@ import (
|
||||
)
|
||||
|
||||
func TestUnfold(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
sample string
|
||||
@ -52,8 +50,6 @@ func TestUnfold(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
d := vfs.NewMountInfoDecoder(strings.NewReader(tc.sample))
|
||||
got, err := d.Unfold(tc.target)
|
||||
|
||||
|
@ -11,29 +11,27 @@ import (
|
||||
)
|
||||
|
||||
func TestArgsString(t *testing.T) {
|
||||
t.Parallel()
|
||||
wantString := strings.Join(wantArgs, " ")
|
||||
if got := argsWt.(fmt.Stringer).String(); got != wantString {
|
||||
t.Errorf("String: %q, want %q", got, wantString)
|
||||
t.Errorf("String: %q, want %q",
|
||||
got, wantString)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCheckedArgs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{"\x00"}
|
||||
if _, err := helper.NewCheckedArgs(args); !errors.Is(err, syscall.EINVAL) {
|
||||
t.Errorf("NewCheckedArgs: error = %v, wantErr %v", err, syscall.EINVAL)
|
||||
t.Errorf("NewCheckedArgs: error = %v, wantErr %v",
|
||||
err, syscall.EINVAL)
|
||||
}
|
||||
|
||||
t.Run("must panic", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
badPayload := []string{"\x00"}
|
||||
defer func() {
|
||||
wantPanic := "invalid argument"
|
||||
if r := recover(); r != wantPanic {
|
||||
t.Errorf("MustNewCheckedArgs: panic = %v, wantPanic %v", r, wantPanic)
|
||||
t.Errorf("MustNewCheckedArgs: panic = %v, wantPanic %v",
|
||||
r, wantPanic)
|
||||
}
|
||||
}()
|
||||
helper.MustNewCheckedArgs(badPayload)
|
||||
|
@ -12,13 +12,12 @@ import (
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/helper/proc"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
// New initialises a Helper instance with wt as the null-terminated argument writer.
|
||||
func New(
|
||||
ctx context.Context,
|
||||
msg message.Msg,
|
||||
msg container.Msg,
|
||||
pathname *check.Absolute, name string,
|
||||
wt io.WriterTo,
|
||||
stat bool,
|
||||
|
@ -68,35 +68,39 @@ func genericStub(argsFile, statFile *os.File) {
|
||||
}
|
||||
}
|
||||
|
||||
// simulate status pipe behaviour
|
||||
if statFile != nil {
|
||||
// simulate status pipe behaviour
|
||||
var epoll int
|
||||
if fd, err := syscall.EpollCreate1(0); err != nil {
|
||||
panic("cannot open epoll fd: " + err.Error())
|
||||
} else {
|
||||
defer func() {
|
||||
if err = syscall.Close(fd); err != nil {
|
||||
panic("cannot close epoll fd: " + err.Error())
|
||||
}
|
||||
}()
|
||||
epoll = fd
|
||||
}
|
||||
if err := syscall.EpollCtl(epoll, syscall.EPOLL_CTL_ADD, int(statFile.Fd()), &syscall.EpollEvent{}); err != nil {
|
||||
panic("cannot add status pipe to epoll: " + err.Error())
|
||||
}
|
||||
|
||||
if _, err := statFile.Write([]byte{'x'}); err != nil {
|
||||
panic("cannot write to status pipe: " + err.Error())
|
||||
}
|
||||
|
||||
// wait for status pipe close
|
||||
events := make([]syscall.EpollEvent, 1)
|
||||
if _, err := syscall.EpollWait(epoll, events, -1); err != nil {
|
||||
panic("cannot poll status pipe: " + err.Error())
|
||||
}
|
||||
if events[0].Events != syscall.EPOLLERR {
|
||||
panic(strconv.Itoa(int(events[0].Events)))
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
// wait for status pipe close
|
||||
var epoll int
|
||||
if fd, err := syscall.EpollCreate1(0); err != nil {
|
||||
panic("cannot open epoll fd: " + err.Error())
|
||||
} else {
|
||||
defer func() {
|
||||
if err = syscall.Close(fd); err != nil {
|
||||
panic("cannot close epoll fd: " + err.Error())
|
||||
}
|
||||
}()
|
||||
epoll = fd
|
||||
}
|
||||
if err := syscall.EpollCtl(epoll, syscall.EPOLL_CTL_ADD, int(statFile.Fd()), &syscall.EpollEvent{}); err != nil {
|
||||
panic("cannot add status pipe to epoll: " + err.Error())
|
||||
}
|
||||
events := make([]syscall.EpollEvent, 1)
|
||||
if _, err := syscall.EpollWait(epoll, events, -1); err != nil {
|
||||
panic("cannot poll status pipe: " + err.Error())
|
||||
}
|
||||
if events[0].Events != syscall.EPOLLERR {
|
||||
panic(strconv.Itoa(int(events[0].Events)))
|
||||
|
||||
}
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
<-done
|
||||
}
|
||||
}
|
||||
|
@ -8,4 +8,8 @@ import (
|
||||
"hakurei.app/helper"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) { container.TryArgv0(nil); helper.InternalHelperStub(); os.Exit(m.Run()) }
|
||||
func TestMain(m *testing.M) {
|
||||
container.TryArgv0(nil)
|
||||
helper.InternalHelperStub()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
@ -8,11 +8,9 @@ import (
|
||||
"hakurei.app/container/check"
|
||||
)
|
||||
|
||||
// PrivateTmp is a private writable path in a hakurei container.
|
||||
const PrivateTmp = "/.hakurei"
|
||||
const Tmp = "/.hakurei"
|
||||
|
||||
// AbsPrivateTmp is a [check.Absolute] representation of [PrivateTmp].
|
||||
var AbsPrivateTmp = check.MustAbs(PrivateTmp)
|
||||
var AbsTmp = check.MustAbs(Tmp)
|
||||
|
||||
const (
|
||||
// WaitDelayDefault is used when WaitDelay has its zero value.
|
||||
@ -59,18 +57,18 @@ type (
|
||||
// Init user namespace supplementary groups inherited by all container processes.
|
||||
Groups []string `json:"groups"`
|
||||
|
||||
// High level configuration applied to the underlying [container].
|
||||
// High level configuration applied to the underlying [container.Params].
|
||||
Container *ContainerConfig `json:"container"`
|
||||
}
|
||||
|
||||
// ContainerConfig describes the container configuration to be applied to an underlying [container].
|
||||
// ContainerConfig describes the container configuration to be applied to an underlying [container.Params].
|
||||
ContainerConfig struct {
|
||||
// Container UTS namespace hostname.
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
|
||||
// Duration in nanoseconds to wait for after interrupting the initial process.
|
||||
// Defaults to [WaitDelayDefault] if zero, or [WaitDelayMax] if greater than [WaitDelayMax].
|
||||
// Values lesser than zero is equivalent to zero, bypassing [WaitDelayDefault].
|
||||
// Defaults to [WaitDelayDefault] if less than or equals to zero,
|
||||
// or [WaitDelayMax] if greater than [WaitDelayMax].
|
||||
WaitDelay time.Duration `json:"wait_delay,omitempty"`
|
||||
|
||||
// Emit Flatpak-compatible seccomp filter programs.
|
||||
|
@ -9,8 +9,6 @@ import (
|
||||
)
|
||||
|
||||
func TestConfigValidate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
config *hst.Config
|
||||
@ -47,7 +45,6 @@ func TestConfigValidate(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := tc.config.Validate(); !reflect.DeepEqual(err, tc.wantErr) {
|
||||
t.Errorf("Validate: error = %#v, want %#v", err, tc.wantErr)
|
||||
}
|
||||
@ -56,8 +53,6 @@ func TestConfigValidate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestExtraPermConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
config *hst.ExtraPermConfig
|
||||
@ -67,8 +62,8 @@ func TestExtraPermConfig(t *testing.T) {
|
||||
{"nil path", &hst.ExtraPermConfig{Path: nil}, "<invalid>"},
|
||||
{"r", &hst.ExtraPermConfig{Path: fhs.AbsRoot, Read: true}, "r--:/"},
|
||||
{"r+", &hst.ExtraPermConfig{Ensure: true, Path: fhs.AbsRoot, Read: true}, "r--+:/"},
|
||||
{"w", &hst.ExtraPermConfig{Path: hst.AbsPrivateTmp, Write: true}, "-w-:/.hakurei"},
|
||||
{"w+", &hst.ExtraPermConfig{Ensure: true, Path: hst.AbsPrivateTmp, Write: true}, "-w-+:/.hakurei"},
|
||||
{"w", &hst.ExtraPermConfig{Path: hst.AbsTmp, Write: true}, "-w-:/.hakurei"},
|
||||
{"w+", &hst.ExtraPermConfig{Ensure: true, Path: hst.AbsTmp, Write: true}, "-w-+:/.hakurei"},
|
||||
{"x", &hst.ExtraPermConfig{Path: fhs.AbsRunUser, Execute: true}, "--x:/run/user/"},
|
||||
{"x+", &hst.ExtraPermConfig{Ensure: true, Path: fhs.AbsRunUser, Execute: true}, "--x+:/run/user/"},
|
||||
{"rwx", &hst.ExtraPermConfig{Path: fhs.AbsTmp, Read: true, Write: true, Execute: true}, "rwx:/tmp/"},
|
||||
@ -77,7 +72,6 @@ func TestExtraPermConfig(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := tc.config.String(); got != tc.want {
|
||||
t.Errorf("String: %q, want %q", got, tc.want)
|
||||
}
|
||||
|
@ -5,13 +5,11 @@ import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
func TestBadInterfaceError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
err error
|
||||
@ -25,22 +23,19 @@ func TestBadInterfaceError(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if gotError := tc.err.Error(); gotError != tc.want {
|
||||
t.Errorf("Error: %s, want %s", gotError, tc.want)
|
||||
}
|
||||
if gotMessage, ok := message.GetMessage(tc.err); !ok {
|
||||
t.Error("GetMessage: ok = false")
|
||||
if gotMessage, ok := container.GetErrorMessage(tc.err); !ok {
|
||||
t.Error("GetErrorMessage: ok = false")
|
||||
} else if gotMessage != tc.want {
|
||||
t.Errorf("GetMessage: %s, want %s", gotMessage, tc.want)
|
||||
t.Errorf("GetErrorMessage: %s, want %s", gotMessage, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBusConfigInterfaces(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
c *hst.BusConfig
|
||||
@ -68,7 +63,6 @@ func TestBusConfigInterfaces(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
var got []string
|
||||
if tc.cutoff > 0 {
|
||||
var i int
|
||||
@ -92,8 +86,6 @@ func TestBusConfigInterfaces(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBusConfigCheckInterfaces(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
c *hst.BusConfig
|
||||
@ -109,7 +101,6 @@ func TestBusConfigCheckInterfaces(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := tc.c.CheckInterfaces(tc.name); !reflect.DeepEqual(err, tc.err) {
|
||||
t.Errorf("CheckInterfaces: error = %#v, want %#v", err, tc.err)
|
||||
}
|
||||
|
@ -11,20 +11,14 @@ import (
|
||||
type Enablement byte
|
||||
|
||||
const (
|
||||
// EWayland exposes a wayland pathname socket via security-context-v1.
|
||||
EWayland Enablement = 1 << iota
|
||||
// EX11 adds the target user via X11 ChangeHosts and exposes the X11 pathname socket.
|
||||
EX11
|
||||
// EDBus enables the per-container xdg-dbus-proxy daemon.
|
||||
EDBus
|
||||
// EPulse copies the PulseAudio cookie to [hst.PrivateTmp] and exposes the PulseAudio socket.
|
||||
EPulse
|
||||
|
||||
// EM is a noop.
|
||||
EM
|
||||
)
|
||||
|
||||
// String returns a string representation of the flags set on [Enablement].
|
||||
func (e Enablement) String() string {
|
||||
switch e {
|
||||
case 0:
|
||||
@ -57,17 +51,17 @@ func (e Enablement) String() string {
|
||||
// NewEnablements returns the address of [Enablement] as [Enablements].
|
||||
func NewEnablements(e Enablement) *Enablements { return (*Enablements)(&e) }
|
||||
|
||||
// Enablements is the [json] adapter for [Enablement].
|
||||
type Enablements Enablement
|
||||
|
||||
// enablementsJSON is the [json] representation of [Enablements].
|
||||
type enablementsJSON = struct {
|
||||
// enablementsJSON is the [json] representation of the [Enablement] bit field.
|
||||
type enablementsJSON struct {
|
||||
Wayland bool `json:"wayland,omitempty"`
|
||||
X11 bool `json:"x11,omitempty"`
|
||||
DBus bool `json:"dbus,omitempty"`
|
||||
Pulse bool `json:"pulse,omitempty"`
|
||||
}
|
||||
|
||||
// Enablements is the [json] adapter for [Enablement].
|
||||
type Enablements Enablement
|
||||
|
||||
// Unwrap returns the underlying [Enablement].
|
||||
func (e *Enablements) Unwrap() Enablement {
|
||||
if e == nil {
|
||||
|
@ -10,8 +10,6 @@ import (
|
||||
)
|
||||
|
||||
func TestEnablementString(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
flags hst.Enablement
|
||||
want string
|
||||
@ -40,7 +38,6 @@ func TestEnablementString(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.want, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := tc.flags.String(); got != tc.want {
|
||||
t.Errorf("String: %q, want %q", got, tc.want)
|
||||
}
|
||||
@ -49,8 +46,6 @@ func TestEnablementString(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEnablements(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
e *hst.Enablements
|
||||
@ -68,10 +63,7 @@ func TestEnablements(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("marshal", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got, err := json.Marshal(tc.e); err != nil {
|
||||
t.Fatalf("Marshal: error = %v", err)
|
||||
} else if string(got) != tc.data {
|
||||
@ -89,8 +81,6 @@ func TestEnablements(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("unmarshal", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
{
|
||||
got := new(hst.Enablements)
|
||||
if err := json.Unmarshal([]byte(tc.data), &got); err != nil {
|
||||
@ -126,8 +116,6 @@ func TestEnablements(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("unwrap", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("nil", func(t *testing.T) {
|
||||
if got := (*hst.Enablements)(nil).Unwrap(); got != 0 {
|
||||
t.Errorf("Unwrap: %v", got)
|
||||
@ -142,8 +130,6 @@ func TestEnablements(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("passthrough", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if _, err := (*hst.Enablements)(nil).MarshalJSON(); !errors.Is(err, syscall.EINVAL) {
|
||||
t.Errorf("MarshalJSON: error = %v", err)
|
||||
}
|
||||
|
11
hst/fs.go
11
hst/fs.go
@ -47,16 +47,17 @@ type Ops interface {
|
||||
Etc(host *check.Absolute, prefix string) Ops
|
||||
}
|
||||
|
||||
// ApplyState holds the address of [Ops] and any relevant application state.
|
||||
// ApplyState holds the address of [container.Ops] and any relevant application state.
|
||||
type ApplyState struct {
|
||||
// AutoEtcPrefix is the prefix for [FSBind] in autoetc [FSBind.Special] condition.
|
||||
// AutoEtcPrefix is the prefix for [container.AutoEtcOp].
|
||||
AutoEtcPrefix string
|
||||
|
||||
Ops
|
||||
}
|
||||
|
||||
// ErrFSNull is returned by [json] on encountering a null [FilesystemConfig] value.
|
||||
var ErrFSNull = errors.New("unexpected null in mount point")
|
||||
var (
|
||||
ErrFSNull = errors.New("unexpected null in mount point")
|
||||
)
|
||||
|
||||
// FSTypeError is returned when [ContainerConfig.Filesystem] contains an entry with invalid type.
|
||||
type FSTypeError string
|
||||
@ -89,7 +90,7 @@ func (f *FilesystemConfigJSON) Valid() bool {
|
||||
return f != nil && f.FilesystemConfig != nil && f.FilesystemConfig.Valid()
|
||||
}
|
||||
|
||||
// fsType holds the string representation of the concrete type of [FilesystemConfig].
|
||||
// fsType holds the string representation of a [FilesystemConfig]'s concrete type.
|
||||
type fsType struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
@ -15,8 +15,6 @@ import (
|
||||
)
|
||||
|
||||
func TestFilesystemConfigJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
want hst.FilesystemConfigJSON
|
||||
@ -88,10 +86,7 @@ func TestFilesystemConfigJSON(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("marshal", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
wantErr := tc.wantErr
|
||||
if errors.As(wantErr, new(hst.FSTypeError)) {
|
||||
// for unsupported implementation tc
|
||||
@ -127,7 +122,6 @@ func TestFilesystemConfigJSON(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("unmarshal", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if tc.data == "\x00" && tc.sData == "\x00" {
|
||||
if errors.As(tc.wantErr, new(hst.FSImplError)) {
|
||||
// this error is only returned on marshal
|
||||
@ -169,8 +163,6 @@ func TestFilesystemConfigJSON(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if got := (*hst.FilesystemConfigJSON).Valid(nil); got {
|
||||
t.Errorf("Valid: %v, want false", got)
|
||||
}
|
||||
@ -185,7 +177,6 @@ func TestFilesystemConfigJSON(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("passthrough", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := new(hst.FilesystemConfigJSON).UnmarshalJSON(make([]byte, 0)); err == nil {
|
||||
t.Errorf("UnmarshalJSON: error = %v", err)
|
||||
}
|
||||
@ -193,10 +184,7 @@ func TestFilesystemConfigJSON(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFSErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("type", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
want := `invalid filesystem type "cat"`
|
||||
if got := hst.FSTypeError("cat").Error(); got != want {
|
||||
t.Errorf("Error: %q, want %q", got, want)
|
||||
@ -204,8 +192,6 @@ func TestFSErrors(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("impl", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
val hst.FilesystemConfig
|
||||
@ -219,7 +205,6 @@ func TestFSErrors(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := hst.FSImplError{Value: tc.val}
|
||||
if got := err.Error(); got != tc.want {
|
||||
t.Errorf("Error: %q, want %q", got, tc.want)
|
||||
@ -257,17 +242,13 @@ type fsTestCase struct {
|
||||
func checkFs(t *testing.T, testCases []fsTestCase) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := tc.fs.Valid(); got != tc.valid {
|
||||
t.Errorf("Valid: %v, want %v", got, tc.valid)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ops", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ops := new(container.Ops)
|
||||
tc.fs.Apply(&hst.ApplyState{AutoEtcPrefix: ":3", Ops: opsAdapter{ops}})
|
||||
if !reflect.DeepEqual(ops, &tc.ops) {
|
||||
@ -284,21 +265,18 @@ func checkFs(t *testing.T, testCases []fsTestCase) {
|
||||
})
|
||||
|
||||
t.Run("path", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := tc.fs.Path(); !reflect.DeepEqual(got, tc.path) {
|
||||
t.Errorf("Target: %q, want %q", got, tc.path)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("host", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := tc.fs.Host(); !reflect.DeepEqual(got, tc.host) {
|
||||
t.Errorf("Host: %q, want %q", got, tc.host)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("string", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if tc.str == "\x00" {
|
||||
return
|
||||
}
|
||||
|
@ -16,23 +16,22 @@ const FilesystemBind = "bind"
|
||||
|
||||
// FSBind represents a host to container bind mount.
|
||||
type FSBind struct {
|
||||
// Pathname in the container mount namespace. Same as Source if nil.
|
||||
// mount point in container, same as Source if empty
|
||||
Target *check.Absolute `json:"dst,omitempty"`
|
||||
// Pathname in the init mount namespace. Must not be nil.
|
||||
// host filesystem path to make available to the container
|
||||
Source *check.Absolute `json:"src"`
|
||||
// Do not remount Target read-only.
|
||||
// This has no effect if Source is mounted read-only in the init mount namespace.
|
||||
// do not mount Target read-only
|
||||
Write bool `json:"write,omitempty"`
|
||||
// Allow access to devices (special files) on Target, implies Write.
|
||||
// do not disable device files on Target, implies Write
|
||||
Device bool `json:"dev,omitempty"`
|
||||
// Create Source as a directory in the init mount namespace if it does not exist.
|
||||
// create Source as a directory if it does not exist
|
||||
Ensure bool `json:"ensure,omitempty"`
|
||||
// Silently skip this mount point if Source does not exist in the init mount namespace.
|
||||
// skip this mount point if Source does not exist
|
||||
Optional bool `json:"optional,omitempty"`
|
||||
|
||||
/* Enable special behaviour:
|
||||
For autoroot: Target must be [fhs.Root].
|
||||
For autoetc: Target must be [fhs.Etc]. */
|
||||
// enable special behaviour:
|
||||
// for autoroot, Target must be set to [fhs.AbsRoot];
|
||||
// for autoetc, Target must be set to [fhs.AbsEtc]
|
||||
Special bool `json:"special,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -9,8 +9,6 @@ import (
|
||||
)
|
||||
|
||||
func TestFSBind(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checkFs(t, []fsTestCase{
|
||||
{"nil", (*hst.FSBind)(nil), false, nil, nil, nil, "<invalid>"},
|
||||
{"ensure optional", &hst.FSBind{Source: m("/"), Ensure: true, Optional: true},
|
||||
|
@ -15,13 +15,13 @@ const FilesystemEphemeral = "ephemeral"
|
||||
|
||||
// FSEphemeral represents an ephemeral container mount point.
|
||||
type FSEphemeral struct {
|
||||
// Pathname in the container mount namespace.
|
||||
Target *check.Absolute `json:"dst"`
|
||||
// Do not mount filesystem read-only.
|
||||
// mount point in container
|
||||
Target *check.Absolute `json:"dst,omitempty"`
|
||||
// do not mount filesystem read-only
|
||||
Write bool `json:"write,omitempty"`
|
||||
// Upper limit on the size of the filesystem.
|
||||
// upper limit on the size of the filesystem
|
||||
Size int `json:"size,omitempty"`
|
||||
// Initial permission bits of the new filesystem.
|
||||
// initial permission bits of the new filesystem
|
||||
Perm os.FileMode `json:"perm,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -9,8 +9,6 @@ import (
|
||||
)
|
||||
|
||||
func TestFSEphemeral(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checkFs(t, []fsTestCase{
|
||||
{"nil", (*hst.FSEphemeral)(nil), false, nil, nil, nil, "<invalid>"},
|
||||
|
||||
@ -38,15 +36,15 @@ func TestFSEphemeral(t *testing.T) {
|
||||
"+ephemeral(-rwxr-xr-x):/run/nscd"},
|
||||
|
||||
{"negative size", &hst.FSEphemeral{
|
||||
Target: hst.AbsPrivateTmp,
|
||||
Target: hst.AbsTmp,
|
||||
Write: true,
|
||||
Size: -1,
|
||||
}, true, container.Ops{&container.MountTmpfsOp{
|
||||
FSName: "ephemeral",
|
||||
Path: hst.AbsPrivateTmp,
|
||||
Path: hst.AbsTmp,
|
||||
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
||||
Perm: 0755,
|
||||
}}, hst.AbsPrivateTmp, nil,
|
||||
}}, hst.AbsTmp, nil,
|
||||
"w+ephemeral(-rwxr-xr-x):/.hakurei"},
|
||||
})
|
||||
}
|
||||
|
@ -14,11 +14,11 @@ const FilesystemLink = "link"
|
||||
|
||||
// FSLink represents a symlink in the container filesystem.
|
||||
type FSLink struct {
|
||||
// Pathname in the container mount namespace.
|
||||
// link path in container
|
||||
Target *check.Absolute `json:"dst"`
|
||||
// Arbitrary linkname value store in the symlink.
|
||||
// linkname the symlink points to
|
||||
Linkname string `json:"linkname"`
|
||||
// Whether to treat Linkname as an absolute pathname and dereference before creating the link.
|
||||
// whether to dereference linkname before creating the link
|
||||
Dereference bool `json:"dereference,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -8,8 +8,6 @@ import (
|
||||
)
|
||||
|
||||
func TestFSLink(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checkFs(t, []fsTestCase{
|
||||
{"nil", (*hst.FSLink)(nil), false, nil, nil, nil, "<invalid>"},
|
||||
{"zero", new(hst.FSLink), false, nil, nil, nil, "<invalid>"},
|
||||
|
@ -14,14 +14,14 @@ const FilesystemOverlay = "overlay"
|
||||
|
||||
// FSOverlay represents an overlay mount point.
|
||||
type FSOverlay struct {
|
||||
// Pathname in the container mount namespace.
|
||||
// mount point in container
|
||||
Target *check.Absolute `json:"dst"`
|
||||
|
||||
// Any filesystem, does not need to be on a writable filesystem, must not be nil.
|
||||
// any filesystem, does not need to be on a writable filesystem, must not be nil
|
||||
Lower []*check.Absolute `json:"lower"`
|
||||
// The upperdir is normally on a writable filesystem, leave as nil to mount Lower readonly.
|
||||
// the upperdir is normally on a writable filesystem, leave as nil to mount Lower readonly
|
||||
Upper *check.Absolute `json:"upper,omitempty"`
|
||||
// The workdir needs to be an empty directory on the same filesystem as Upper, must not be nil if Upper is populated.
|
||||
// the workdir needs to be an empty directory on the same filesystem as Upper, must not be nil if Upper is populated
|
||||
Work *check.Absolute `json:"work,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -9,8 +9,6 @@ import (
|
||||
)
|
||||
|
||||
func TestFSOverlay(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checkFs(t, []fsTestCase{
|
||||
{"nil", (*hst.FSOverlay)(nil), false, nil, nil, nil, "<invalid>"},
|
||||
{"nil lower", &hst.FSOverlay{Target: m("/etc"), Lower: []*check.Absolute{nil}}, false, nil, nil, nil, "<invalid>"},
|
||||
|
24
hst/hst.go
24
hst/hst.go
@ -12,12 +12,9 @@ import (
|
||||
|
||||
// An AppError is returned while starting an app according to [hst.Config].
|
||||
type AppError struct {
|
||||
// A user-facing description of where the error occurred.
|
||||
Step string `json:"step"`
|
||||
// The underlying error value.
|
||||
Err error
|
||||
// An arbitrary error message, overriding the return value of Message if not empty.
|
||||
Msg string `json:"message,omitempty"`
|
||||
Step string
|
||||
Err error
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (e *AppError) Error() string { return e.Err.Error() }
|
||||
@ -41,13 +38,13 @@ func (e *AppError) Message() string {
|
||||
|
||||
// Paths contains environment-dependent paths used by hakurei.
|
||||
type Paths struct {
|
||||
// Temporary directory returned by [os.TempDir], usually equivalent to [fhs.AbsTmp].
|
||||
// temporary directory returned by [os.TempDir] (usually `/tmp`)
|
||||
TempDir *check.Absolute `json:"temp_dir"`
|
||||
// Shared directory specific to the hsu userid, usually (`/tmp/hakurei.%d`, [Info.User]).
|
||||
// path to shared directory (usually `/tmp/hakurei.%d`, [Info.User])
|
||||
SharePath *check.Absolute `json:"share_path"`
|
||||
// Checked XDG_RUNTIME_DIR value, usually (`/run/user/%d`, uid).
|
||||
// XDG_RUNTIME_DIR value (usually `/run/user/%d`, uid)
|
||||
RuntimePath *check.Absolute `json:"runtime_path"`
|
||||
// Shared directory specific to the hsu userid located in RuntimePath, usually (`/run/user/%d/hakurei`, uid).
|
||||
// application runtime directory (usually `/run/user/%d/hakurei`)
|
||||
RunDirPath *check.Absolute `json:"run_dir_path"`
|
||||
}
|
||||
|
||||
@ -120,10 +117,11 @@ func Template() *Config {
|
||||
{&FSEphemeral{Target: fhs.AbsTmp, Write: true, Perm: 0755}},
|
||||
{&FSOverlay{
|
||||
Target: check.MustAbs("/nix/store"),
|
||||
Lower: []*check.Absolute{fhs.AbsVarLib.Append("hakurei/base/org.nixos/ro-store")},
|
||||
Upper: fhs.AbsVarLib.Append("hakurei/nix/u0/org.chromium.Chromium/rw-store/upper"),
|
||||
Work: fhs.AbsVarLib.Append("hakurei/nix/u0/org.chromium.Chromium/rw-store/work"),
|
||||
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
||||
Upper: check.MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||
Work: check.MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||
}},
|
||||
{&FSBind{Source: check.MustAbs("/nix/store")}},
|
||||
{&FSLink{Target: fhs.AbsRun.Append("current-system"), Linkname: "/run/current-system", Dereference: true}},
|
||||
{&FSLink{Target: fhs.AbsRun.Append("opengl-driver"), Linkname: "/run/opengl-driver", Dereference: true}},
|
||||
{&FSBind{Source: fhs.AbsVarLib.Append("hakurei/u0/org.chromium.Chromium"),
|
||||
|
@ -8,14 +8,12 @@ import (
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/stub"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
func TestAppError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
err error
|
||||
@ -60,31 +58,26 @@ func TestAppError(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := tc.err.Error(); got != tc.s {
|
||||
t.Errorf("Error: %s, want %s", got, tc.s)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("message", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
gotMessage, gotMessageOk := message.GetMessage(tc.err)
|
||||
gotMessage, gotMessageOk := container.GetErrorMessage(tc.err)
|
||||
if want := tc.message != "\x00"; gotMessageOk != want {
|
||||
t.Errorf("GetMessage: ok = %v, want %v", gotMessage, want)
|
||||
t.Errorf("GetErrorMessage: ok = %v, want %v", gotMessage, want)
|
||||
}
|
||||
|
||||
if gotMessageOk {
|
||||
if gotMessage != tc.message {
|
||||
t.Errorf("GetMessage: %s, want %s", gotMessage, tc.message)
|
||||
t.Errorf("GetErrorMessage: %s, want %s", gotMessage, tc.message)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("is", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !errors.Is(tc.err, tc.is) {
|
||||
t.Errorf("Is: unexpected false for %v", tc.is)
|
||||
}
|
||||
@ -97,8 +90,6 @@ func TestAppError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTemplate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const want = `{
|
||||
"id": "org.chromium.Chromium",
|
||||
"enablements": {
|
||||
@ -202,10 +193,14 @@ func TestTemplate(t *testing.T) {
|
||||
"type": "overlay",
|
||||
"dst": "/nix/store",
|
||||
"lower": [
|
||||
"/var/lib/hakurei/base/org.nixos/ro-store"
|
||||
"/mnt-root/nix/.ro-store"
|
||||
],
|
||||
"upper": "/var/lib/hakurei/nix/u0/org.chromium.Chromium/rw-store/upper",
|
||||
"work": "/var/lib/hakurei/nix/u0/org.chromium.Chromium/rw-store/work"
|
||||
"upper": "/mnt-root/nix/.rw-store/upper",
|
||||
"work": "/mnt-root/nix/.rw-store/work"
|
||||
},
|
||||
{
|
||||
"type": "bind",
|
||||
"src": "/nix/store"
|
||||
},
|
||||
{
|
||||
"type": "link",
|
||||
|
@ -6,13 +6,13 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
// Main runs an app according to [hst.Config] and terminates. Main does not return.
|
||||
func Main(ctx context.Context, msg message.Msg, config *hst.Config) {
|
||||
func Main(ctx context.Context, msg container.Msg, config *hst.Config) {
|
||||
var id state.ID
|
||||
if err := state.NewAppID(&id); err != nil {
|
||||
log.Fatal(err)
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"maps"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"reflect"
|
||||
@ -22,14 +23,12 @@ import (
|
||||
"hakurei.app/container/fhs"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/message"
|
||||
"hakurei.app/system"
|
||||
"hakurei.app/system/acl"
|
||||
)
|
||||
|
||||
func TestApp(t *testing.T) {
|
||||
t.Parallel()
|
||||
msg := message.NewMsg(nil)
|
||||
msg := container.NewMsg(nil)
|
||||
msg.SwapVerbose(testing.Verbose())
|
||||
|
||||
testCases := []struct {
|
||||
@ -99,7 +98,7 @@ func TestApp(t *testing.T) {
|
||||
Ops: new(container.Ops).
|
||||
Root(m("/"), bits.BindWritable).
|
||||
Proc(m("/proc/")).
|
||||
Tmpfs(hst.AbsPrivateTmp, 4096, 0755).
|
||||
Tmpfs(hst.AbsTmp, 4096, 0755).
|
||||
DevWritable(m("/dev/"), true).
|
||||
Tmpfs(m("/dev/shm"), 0, 01777).
|
||||
Bind(m("/dev/kvm"), m("/dev/kvm"), bits.BindWritable|bits.BindDevice|bits.BindOptional).
|
||||
@ -251,9 +250,9 @@ func TestApp(t *testing.T) {
|
||||
Args: []string{"zsh", "-c", "exec chromium "},
|
||||
Env: []string{
|
||||
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus",
|
||||
"DBUS_SYSTEM_BUS_ADDRESS=unix:path=/var/run/dbus/system_bus_socket",
|
||||
"DBUS_SYSTEM_BUS_ADDRESS=unix:path=/run/dbus/system_bus_socket",
|
||||
"HOME=/home/chronos",
|
||||
"PULSE_COOKIE=" + hst.PrivateTmp + "/pulse-cookie",
|
||||
"PULSE_COOKIE=" + hst.Tmp + "/pulse-cookie",
|
||||
"PULSE_SERVER=unix:/run/user/65534/pulse/native",
|
||||
"SHELL=/run/current-system/sw/bin/zsh",
|
||||
"TERM=xterm-256color",
|
||||
@ -266,7 +265,7 @@ func TestApp(t *testing.T) {
|
||||
Ops: new(container.Ops).
|
||||
Root(m("/"), bits.BindWritable).
|
||||
Proc(m("/proc/")).
|
||||
Tmpfs(hst.AbsPrivateTmp, 4096, 0755).
|
||||
Tmpfs(hst.AbsTmp, 4096, 0755).
|
||||
DevWritable(m("/dev/"), true).
|
||||
Tmpfs(m("/dev/shm"), 0, 01777).
|
||||
Bind(m("/dev/dri"), m("/dev/dri"), bits.BindWritable|bits.BindDevice|bits.BindOptional).
|
||||
@ -283,9 +282,9 @@ func TestApp(t *testing.T) {
|
||||
Place(m("/etc/group"), []byte("hakurei:x:65534:\n")).
|
||||
Bind(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/wayland"), m("/run/user/65534/wayland-0"), 0).
|
||||
Bind(m("/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c/pulse"), m("/run/user/65534/pulse/native"), 0).
|
||||
Place(m(hst.PrivateTmp+"/pulse-cookie"), bytes.Repeat([]byte{0}, pulseCookieSizeMax)).
|
||||
Place(m(hst.Tmp+"/pulse-cookie"), bytes.Repeat([]byte{0}, pulseCookieSizeMax)).
|
||||
Bind(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/bus"), m("/run/user/65534/bus"), 0).
|
||||
Bind(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/system_bus_socket"), m("/var/run/dbus/system_bus_socket"), 0).
|
||||
Bind(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/system_bus_socket"), m("/run/dbus/system_bus_socket"), 0).
|
||||
Remount(m("/"), syscall.MS_RDONLY),
|
||||
SeccompPresets: bits.PresetExt | bits.PresetDenyDevel,
|
||||
HostNet: true,
|
||||
@ -395,9 +394,9 @@ func TestApp(t *testing.T) {
|
||||
Args: []string{"/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"},
|
||||
Env: []string{
|
||||
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1971/bus",
|
||||
"DBUS_SYSTEM_BUS_ADDRESS=unix:path=/var/run/dbus/system_bus_socket",
|
||||
"DBUS_SYSTEM_BUS_ADDRESS=unix:path=/run/dbus/system_bus_socket",
|
||||
"HOME=/var/lib/persist/module/hakurei/0/1",
|
||||
"PULSE_COOKIE=" + hst.PrivateTmp + "/pulse-cookie",
|
||||
"PULSE_COOKIE=" + hst.Tmp + "/pulse-cookie",
|
||||
"PULSE_SERVER=unix:/run/user/1971/pulse/native",
|
||||
"SHELL=/run/current-system/sw/bin/zsh",
|
||||
"TERM=xterm-256color",
|
||||
@ -409,7 +408,7 @@ func TestApp(t *testing.T) {
|
||||
},
|
||||
Ops: new(container.Ops).
|
||||
Proc(m("/proc/")).
|
||||
Tmpfs(hst.AbsPrivateTmp, 4096, 0755).
|
||||
Tmpfs(hst.AbsTmp, 4096, 0755).
|
||||
DevWritable(m("/dev/"), true).
|
||||
Tmpfs(m("/dev/shm"), 0, 01777).
|
||||
Bind(m("/bin"), m("/bin"), 0).
|
||||
@ -433,9 +432,9 @@ func TestApp(t *testing.T) {
|
||||
Place(m("/etc/group"), []byte("hakurei:x:100:\n")).
|
||||
Bind(m("/run/user/1971/wayland-0"), m("/run/user/1971/wayland-0"), 0).
|
||||
Bind(m("/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1/pulse"), m("/run/user/1971/pulse/native"), 0).
|
||||
Place(m(hst.PrivateTmp+"/pulse-cookie"), bytes.Repeat([]byte{0}, pulseCookieSizeMax)).
|
||||
Place(m(hst.Tmp+"/pulse-cookie"), bytes.Repeat([]byte{0}, pulseCookieSizeMax)).
|
||||
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus"), m("/run/user/1971/bus"), 0).
|
||||
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket"), m("/var/run/dbus/system_bus_socket"), 0).
|
||||
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket"), m("/run/dbus/system_bus_socket"), 0).
|
||||
Remount(m("/"), syscall.MS_RDONLY),
|
||||
SeccompPresets: bits.PresetExt | bits.PresetDenyTTY | bits.PresetDenyDevel,
|
||||
HostNet: true,
|
||||
@ -446,19 +445,29 @@ func TestApp(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
gr, gw := io.Pipe()
|
||||
|
||||
var gotSys *system.I
|
||||
{
|
||||
sPriv := newOutcomeState(tc.k, msg, &tc.id, tc.config, &Hsu{k: tc.k})
|
||||
sPriv := outcomeState{
|
||||
ID: &tc.id,
|
||||
Identity: tc.config.Identity,
|
||||
UserID: (&Hsu{k: tc.k}).MustIDMsg(msg),
|
||||
EnvPaths: copyPaths(tc.k),
|
||||
Container: tc.config.Container,
|
||||
}
|
||||
|
||||
sPriv.populateEarly(tc.k, msg, tc.config)
|
||||
if err := sPriv.populateLocal(tc.k, msg); err != nil {
|
||||
t.Fatalf("populateLocal: error = %#v", err)
|
||||
}
|
||||
|
||||
gotSys = system.New(t.Context(), msg, sPriv.uid.unwrap())
|
||||
if err := sPriv.newSys(tc.config, gotSys).toSystem(); err != nil {
|
||||
t.Fatalf("toSystem: error = %#v", err)
|
||||
stateSys := outcomeStateSys{sys: gotSys, outcomeState: &sPriv}
|
||||
for _, op := range sPriv.Shim.Ops {
|
||||
if err := op.toSystem(&stateSys, tc.config); err != nil {
|
||||
t.Fatalf("toSystem: error = %#v", err)
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
@ -470,7 +479,7 @@ func TestApp(t *testing.T) {
|
||||
}()
|
||||
}
|
||||
|
||||
var gotParams *container.Params
|
||||
var gotParams container.Params
|
||||
{
|
||||
var sShim outcomeState
|
||||
|
||||
@ -482,13 +491,17 @@ func TestApp(t *testing.T) {
|
||||
t.Fatalf("populateLocal: error = %#v", err)
|
||||
}
|
||||
|
||||
stateParams := sShim.newParams()
|
||||
stateParams := outcomeStateParams{params: &gotParams, outcomeState: &sShim}
|
||||
if sShim.Container.Env == nil {
|
||||
stateParams.env = make(map[string]string, envAllocSize)
|
||||
} else {
|
||||
stateParams.env = maps.Clone(sShim.Container.Env)
|
||||
}
|
||||
for _, op := range sShim.Shim.Ops {
|
||||
if err := op.toContainer(stateParams); err != nil {
|
||||
if err := op.toContainer(&stateParams); err != nil {
|
||||
t.Fatalf("toContainer: error = %#v", err)
|
||||
}
|
||||
}
|
||||
gotParams = stateParams.params
|
||||
}
|
||||
|
||||
t.Run("sys", func(t *testing.T) {
|
||||
@ -498,8 +511,8 @@ func TestApp(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("params", func(t *testing.T) {
|
||||
if !reflect.DeepEqual(gotParams, tc.wantParams) {
|
||||
t.Errorf("toContainer: params =\n%s\n, want\n%s", mustMarshal(gotParams), mustMarshal(tc.wantParams))
|
||||
if !reflect.DeepEqual(&gotParams, tc.wantParams) {
|
||||
t.Errorf("toContainer: params =\n%s\n, want\n%s", mustMarshal(&gotParams), mustMarshal(tc.wantParams))
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -563,7 +576,6 @@ type stubNixOS struct {
|
||||
|
||||
func (k *stubNixOS) new(func(k syscallDispatcher)) { panic("not implemented") }
|
||||
|
||||
func (k *stubNixOS) getpid() int { return 0xdeadbeef }
|
||||
func (k *stubNixOS) getuid() int { return 1971 }
|
||||
func (k *stubNixOS) getgid() int { return 100 }
|
||||
|
||||
@ -583,8 +595,6 @@ func (k *stubNixOS) lookupEnv(key string) (string, bool) {
|
||||
return "/run/user/1971", true
|
||||
case "XDG_CONFIG_HOME":
|
||||
return "/home/ophestra/xdg/config", true
|
||||
case "DBUS_SYSTEM_BUS_ADDRESS":
|
||||
return "", false
|
||||
default:
|
||||
panic(fmt.Sprintf("attempted to access unexpected environment variable %q", key))
|
||||
}
|
||||
@ -659,7 +669,7 @@ func (k *stubNixOS) evalSymlinks(path string) (string, error) {
|
||||
return "/run/user/1971", nil
|
||||
case "/tmp/hakurei.0":
|
||||
return "/tmp/hakurei.0", nil
|
||||
case "/var/run/dbus":
|
||||
case "/run/dbus":
|
||||
return "/run/dbus", nil
|
||||
case "/dev/kvm":
|
||||
return "/dev/kvm", nil
|
||||
@ -734,8 +744,8 @@ func (k *stubNixOS) cmdOutput(cmd *exec.Cmd) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (k *stubNixOS) overflowUid(message.Msg) int { return 65534 }
|
||||
func (k *stubNixOS) overflowGid(message.Msg) int { return 65534 }
|
||||
func (k *stubNixOS) overflowUid(container.Msg) int { return 65534 }
|
||||
func (k *stubNixOS) overflowGid(container.Msg) int { return 65534 }
|
||||
|
||||
func (k *stubNixOS) mustHsuPath() *check.Absolute { return m("/proc/nonexistent/hsu") }
|
||||
|
||||
|
@ -12,7 +12,6 @@ import (
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
// osFile represents [os.File].
|
||||
@ -29,8 +28,6 @@ type syscallDispatcher interface {
|
||||
// just synchronising access is not enough, as this is for test instrumentation.
|
||||
new(f func(k syscallDispatcher))
|
||||
|
||||
// getpid provides [os.Getpid].
|
||||
getpid() int
|
||||
// getuid provides [os.Getuid].
|
||||
getuid() int
|
||||
// getgid provides [os.Getgid].
|
||||
@ -56,9 +53,9 @@ type syscallDispatcher interface {
|
||||
cmdOutput(cmd *exec.Cmd) ([]byte, error)
|
||||
|
||||
// overflowUid provides [container.OverflowUid].
|
||||
overflowUid(msg message.Msg) int
|
||||
overflowUid(msg container.Msg) int
|
||||
// overflowGid provides [container.OverflowGid].
|
||||
overflowGid(msg message.Msg) int
|
||||
overflowGid(msg container.Msg) int
|
||||
|
||||
// mustHsuPath provides [internal.MustHsuPath].
|
||||
mustHsuPath() *check.Absolute
|
||||
@ -72,7 +69,6 @@ type direct struct{}
|
||||
|
||||
func (k direct) new(f func(k syscallDispatcher)) { go f(k) }
|
||||
|
||||
func (direct) getpid() int { return os.Getpid() }
|
||||
func (direct) getuid() int { return os.Getuid() }
|
||||
func (direct) getgid() int { return os.Getgid() }
|
||||
func (direct) lookupEnv(key string) (string, bool) { return os.LookupEnv(key) }
|
||||
@ -94,8 +90,8 @@ func (direct) lookupGroupId(name string) (gid string, err error) {
|
||||
|
||||
func (direct) cmdOutput(cmd *exec.Cmd) ([]byte, error) { return cmd.Output() }
|
||||
|
||||
func (direct) overflowUid(msg message.Msg) int { return container.OverflowUid(msg) }
|
||||
func (direct) overflowGid(msg message.Msg) int { return container.OverflowGid(msg) }
|
||||
func (direct) overflowUid(msg container.Msg) int { return container.OverflowUid(msg) }
|
||||
func (direct) overflowGid(msg container.Msg) int { return container.OverflowGid(msg) }
|
||||
|
||||
func (direct) mustHsuPath() *check.Absolute { return internal.MustHsuPath() }
|
||||
|
||||
|
@ -1,312 +1,16 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"reflect"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/container/stub"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/message"
|
||||
"hakurei.app/system"
|
||||
)
|
||||
|
||||
// call initialises a [stub.Call].
|
||||
// This keeps composites analysis happy without making the test cases too bloated.
|
||||
func call(name string, args stub.ExpectArgs, ret any, err error) stub.Call {
|
||||
return stub.Call{Name: name, Args: args, Ret: ret, Err: err}
|
||||
}
|
||||
|
||||
// checkExpectUid is the uid value used by checkOpBehaviour to initialise [system.I].
|
||||
const checkExpectUid = 0xcafebabe
|
||||
|
||||
// wantAutoEtcPrefix is the autoetc prefix corresponding to checkExpectInstanceId.
|
||||
const wantAutoEtcPrefix = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
|
||||
// checkExpectInstanceId is the [state.ID] value used by checkOpBehaviour to initialise outcomeState.
|
||||
var checkExpectInstanceId = *(*state.ID)(bytes.Repeat([]byte{0xaa}, len(state.ID{})))
|
||||
|
||||
type opBehaviourTestCase struct {
|
||||
name string
|
||||
newOp func(isShim, clearUnexported bool) outcomeOp
|
||||
newConfig func() *hst.Config
|
||||
|
||||
pStateSys func(state *outcomeStateSys)
|
||||
toSystem []stub.Call
|
||||
wantSys *system.I
|
||||
extraCheckSys func(t *testing.T, state *outcomeStateSys)
|
||||
wantErrSystem error
|
||||
|
||||
pStateContainer func(state *outcomeStateParams)
|
||||
toContainer []stub.Call
|
||||
wantParams *container.Params
|
||||
extraCheckParams func(t *testing.T, state *outcomeStateParams)
|
||||
wantErrContainer error
|
||||
}
|
||||
|
||||
func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
|
||||
t.Helper()
|
||||
|
||||
wantNewState := []stub.Call{
|
||||
// newOutcomeState
|
||||
call("getpid", stub.ExpectArgs{}, 0xdead, nil),
|
||||
call("isVerbose", stub.ExpectArgs{}, true, nil),
|
||||
call("mustHsuPath", stub.ExpectArgs{}, m(container.Nonexistent), nil),
|
||||
call("cmdOutput", stub.ExpectArgs{container.Nonexistent, os.Stderr, []string{}, "/"}, []byte("0"), nil),
|
||||
call("tempdir", stub.ExpectArgs{}, container.Nonexistent+"/tmp", nil),
|
||||
call("lookupEnv", stub.ExpectArgs{"XDG_RUNTIME_DIR"}, container.Nonexistent+"/xdg_runtime_dir", nil),
|
||||
call("getuid", stub.ExpectArgs{}, 1000, nil),
|
||||
call("getgid", stub.ExpectArgs{}, 100, nil),
|
||||
|
||||
// populateLocal
|
||||
call("verbosef", stub.ExpectArgs{"process share directory at %q, runtime directory at %q", []any{
|
||||
m(container.Nonexistent + "/tmp/hakurei.0"),
|
||||
m(container.Nonexistent + "/xdg_runtime_dir/hakurei"),
|
||||
}}, nil, nil),
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Helper()
|
||||
t.Parallel()
|
||||
|
||||
wantCallsFull := slices.Concat(wantNewState, tc.toSystem, []stub.Call{{Name: stub.CallSeparator}})
|
||||
if tc.wantErrSystem == nil {
|
||||
wantCallsFull = append(wantCallsFull, slices.Concat(wantNewState, tc.toContainer)...)
|
||||
}
|
||||
|
||||
wantConfig := tc.newConfig()
|
||||
k := &kstub{panicDispatcher{}, stub.New(t,
|
||||
func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { return &kstub{panicDispatcher{}, s} },
|
||||
stub.Expect{Calls: wantCallsFull},
|
||||
)}
|
||||
defer stub.HandleExit(t)
|
||||
|
||||
{
|
||||
config := tc.newConfig()
|
||||
s := newOutcomeState(k, k, &checkExpectInstanceId, config, &Hsu{k: k})
|
||||
if err := s.populateLocal(k, k); err != nil {
|
||||
t.Fatalf("populateLocal: error = %v", err)
|
||||
}
|
||||
stateSys := s.newSys(config, newI())
|
||||
if tc.pStateSys != nil {
|
||||
tc.pStateSys(stateSys)
|
||||
}
|
||||
op := tc.newOp(false, true)
|
||||
|
||||
if err := op.toSystem(stateSys); !reflect.DeepEqual(err, tc.wantErrSystem) {
|
||||
t.Fatalf("toSystem: error = %#v, want %#v", err, tc.wantErrSystem)
|
||||
}
|
||||
k.Expects(stub.CallSeparator)
|
||||
if !reflect.DeepEqual(config, wantConfig) {
|
||||
t.Errorf("toSystem clobbered config: %#v, want %#v", config, wantConfig)
|
||||
}
|
||||
|
||||
if tc.wantErrSystem != nil {
|
||||
goto out
|
||||
}
|
||||
|
||||
if !stateSys.sys.Equal(tc.wantSys) {
|
||||
t.Errorf("toSystem: %#v, want %#v", stateSys.sys, tc.wantSys)
|
||||
}
|
||||
if tc.extraCheckSys != nil {
|
||||
tc.extraCheckSys(t, stateSys)
|
||||
}
|
||||
if wantOpSys := tc.newOp(true, false); !reflect.DeepEqual(op, wantOpSys) {
|
||||
t.Errorf("toSystem: op = %#v, want %#v", op, wantOpSys)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
config := tc.newConfig()
|
||||
s := newOutcomeState(k, k, &checkExpectInstanceId, config, &Hsu{k: k})
|
||||
stateParams := s.newParams()
|
||||
if err := s.populateLocal(k, k); err != nil {
|
||||
t.Fatalf("populateLocal: error = %v", err)
|
||||
}
|
||||
if tc.pStateContainer != nil {
|
||||
tc.pStateContainer(stateParams)
|
||||
}
|
||||
op := tc.newOp(true, true)
|
||||
|
||||
if err := op.toContainer(stateParams); !reflect.DeepEqual(err, tc.wantErrContainer) {
|
||||
t.Fatalf("toContainer: error = %#v, want %#v", err, tc.wantErrContainer)
|
||||
}
|
||||
|
||||
if tc.wantErrContainer != nil {
|
||||
goto out
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(stateParams.params, tc.wantParams) {
|
||||
t.Errorf("toContainer:\n%s\nwant\n%s", mustMarshal(stateParams.params), mustMarshal(tc.wantParams))
|
||||
}
|
||||
if tc.extraCheckParams != nil {
|
||||
tc.extraCheckParams(t, stateParams)
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
k.VisitIncomplete(func(s *stub.Stub[syscallDispatcher]) {
|
||||
count := k.Pos() - 1 // separator
|
||||
if count-len(wantNewState) < len(tc.toSystem) {
|
||||
t.Errorf("toSystem: %d calls, want %d", count-len(wantNewState), len(tc.toSystem))
|
||||
} else {
|
||||
t.Errorf("toContainer: %d calls, want %d", count-len(tc.toSystem)-2*len(wantNewState), len(tc.toContainer))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newI() *system.I { return system.New(panicMsgContext{}, panicMsgContext{}, checkExpectUid) }
|
||||
|
||||
type kstub struct {
|
||||
panicDispatcher
|
||||
*stub.Stub[syscallDispatcher]
|
||||
}
|
||||
|
||||
func (k *kstub) getpid() int { k.Helper(); return k.Expects("getpid").Ret.(int) }
|
||||
func (k *kstub) getuid() int { k.Helper(); return k.Expects("getuid").Ret.(int) }
|
||||
func (k *kstub) getgid() int { k.Helper(); return k.Expects("getgid").Ret.(int) }
|
||||
func (k *kstub) lookupEnv(key string) (string, bool) {
|
||||
k.Helper()
|
||||
expect := k.Expects("lookupEnv")
|
||||
if expect.Error(
|
||||
stub.CheckArg(k.Stub, "key", key, 0)) != nil {
|
||||
k.FailNow()
|
||||
}
|
||||
if expect.Ret == nil {
|
||||
return "\x00", false
|
||||
}
|
||||
return expect.Ret.(string), true
|
||||
}
|
||||
func (k *kstub) readdir(name string) ([]os.DirEntry, error) {
|
||||
k.Helper()
|
||||
expect := k.Expects("readdir")
|
||||
return expect.Ret.([]os.DirEntry), expect.Error(
|
||||
stub.CheckArg(k.Stub, "name", name, 0))
|
||||
}
|
||||
func (k *kstub) tempdir() string { k.Helper(); return k.Expects("tempdir").Ret.(string) }
|
||||
func (k *kstub) evalSymlinks(path string) (string, error) {
|
||||
k.Helper()
|
||||
expect := k.Expects("evalSymlinks")
|
||||
return expect.Ret.(string), expect.Error(
|
||||
stub.CheckArg(k.Stub, "path", path, 0))
|
||||
}
|
||||
|
||||
func (k *kstub) cmdOutput(cmd *exec.Cmd) ([]byte, error) {
|
||||
k.Helper()
|
||||
expect := k.Expects("cmdOutput")
|
||||
return expect.Ret.([]byte), expect.Error(
|
||||
stub.CheckArg(k.Stub, "cmd.Path", cmd.Path, 0),
|
||||
stub.CheckArgReflect(k.Stub, "cmd.Stderr", cmd.Stderr, 1),
|
||||
stub.CheckArgReflect(k.Stub, "cmd.Env", cmd.Env, 2),
|
||||
stub.CheckArg(k.Stub, "cmd.Dir", cmd.Dir, 3))
|
||||
}
|
||||
|
||||
func (k *kstub) mustHsuPath() *check.Absolute {
|
||||
k.Helper()
|
||||
return k.Expects("mustHsuPath").Ret.(*check.Absolute)
|
||||
}
|
||||
|
||||
func (k *kstub) GetLogger() *log.Logger { panic("unreachable") }
|
||||
|
||||
func (k *kstub) IsVerbose() bool { k.Helper(); return k.Expects("isVerbose").Ret.(bool) }
|
||||
func (k *kstub) SwapVerbose(verbose bool) bool {
|
||||
k.Helper()
|
||||
expect := k.Expects("swapVerbose")
|
||||
if expect.Error(
|
||||
stub.CheckArg(k.Stub, "verbose", verbose, 0)) != nil {
|
||||
k.FailNow()
|
||||
}
|
||||
return expect.Ret.(bool)
|
||||
}
|
||||
|
||||
// ignoreValue marks a value to be ignored by the test suite.
|
||||
type ignoreValue struct{}
|
||||
|
||||
func (k *kstub) Verbose(v ...any) {
|
||||
k.Helper()
|
||||
expect := k.Expects("verbose")
|
||||
|
||||
// translate ignores in v
|
||||
if want, ok := expect.Args[0].([]any); ok && len(v) == len(want) {
|
||||
for i, a := range want {
|
||||
if _, ok = a.(ignoreValue); ok {
|
||||
v[i] = ignoreValue{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if expect.Error(
|
||||
stub.CheckArgReflect(k.Stub, "v", v, 0)) != nil {
|
||||
k.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func (k *kstub) Verbosef(format string, v ...any) {
|
||||
k.Helper()
|
||||
if k.Expects("verbosef").Error(
|
||||
stub.CheckArg(k.Stub, "format", format, 0),
|
||||
stub.CheckArgReflect(k.Stub, "v", v, 1)) != nil {
|
||||
k.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func (k *kstub) Suspend() bool { k.Helper(); return k.Expects("suspend").Ret.(bool) }
|
||||
func (k *kstub) Resume() bool { k.Helper(); return k.Expects("resume").Ret.(bool) }
|
||||
func (k *kstub) BeforeExit() { k.Helper(); k.Expects("beforeExit") }
|
||||
|
||||
// stubDir returns a slice of [os.DirEntry] with only their Name method implemented.
|
||||
func stubDir(names ...string) []os.DirEntry {
|
||||
d := make([]os.DirEntry, len(names))
|
||||
for i, name := range names {
|
||||
d[i] = nameDentry(name)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// nameDentry implements the Name method on [os.DirEntry].
|
||||
type nameDentry string
|
||||
|
||||
func (e nameDentry) Name() string { return string(e) }
|
||||
func (nameDentry) IsDir() bool { panic("unreachable") }
|
||||
func (nameDentry) Type() fs.FileMode { panic("unreachable") }
|
||||
func (nameDentry) Info() (fs.FileInfo, error) { panic("unreachable") }
|
||||
|
||||
// panicMsgContext implements [message.Msg] and [context.Context] with methods wrapping panic.
|
||||
// This should be assigned to test cases to be checked against.
|
||||
type panicMsgContext struct{}
|
||||
|
||||
func (panicMsgContext) GetLogger() *log.Logger { panic("unreachable") }
|
||||
func (panicMsgContext) IsVerbose() bool { panic("unreachable") }
|
||||
func (panicMsgContext) SwapVerbose(bool) bool { panic("unreachable") }
|
||||
func (panicMsgContext) Verbose(...any) { panic("unreachable") }
|
||||
func (panicMsgContext) Verbosef(string, ...any) { panic("unreachable") }
|
||||
func (panicMsgContext) Suspend() bool { panic("unreachable") }
|
||||
func (panicMsgContext) Resume() bool { panic("unreachable") }
|
||||
func (panicMsgContext) BeforeExit() { panic("unreachable") }
|
||||
|
||||
func (panicMsgContext) Deadline() (time.Time, bool) { panic("unreachable") }
|
||||
func (panicMsgContext) Done() <-chan struct{} { panic("unreachable") }
|
||||
func (panicMsgContext) Err() error { panic("unreachable") }
|
||||
func (panicMsgContext) Value(any) any { panic("unreachable") }
|
||||
|
||||
// panicDispatcher implements syscallDispatcher with methods wrapping panic.
|
||||
// This type is meant to be embedded in partial syscallDispatcher implementations.
|
||||
type panicDispatcher struct{}
|
||||
|
||||
func (panicDispatcher) new(func(k syscallDispatcher)) { panic("unreachable") }
|
||||
func (panicDispatcher) getpid() int { panic("unreachable") }
|
||||
func (panicDispatcher) getuid() int { panic("unreachable") }
|
||||
func (panicDispatcher) getgid() int { panic("unreachable") }
|
||||
func (panicDispatcher) lookupEnv(string) (string, bool) { panic("unreachable") }
|
||||
@ -317,7 +21,7 @@ func (panicDispatcher) tempdir() string { panic("unreachab
|
||||
func (panicDispatcher) evalSymlinks(string) (string, error) { panic("unreachable") }
|
||||
func (panicDispatcher) lookupGroupId(string) (string, error) { panic("unreachable") }
|
||||
func (panicDispatcher) cmdOutput(*exec.Cmd) ([]byte, error) { panic("unreachable") }
|
||||
func (panicDispatcher) overflowUid(message.Msg) int { panic("unreachable") }
|
||||
func (panicDispatcher) overflowGid(message.Msg) int { panic("unreachable") }
|
||||
func (panicDispatcher) overflowUid(container.Msg) int { panic("unreachable") }
|
||||
func (panicDispatcher) overflowGid(container.Msg) int { panic("unreachable") }
|
||||
func (panicDispatcher) mustHsuPath() *check.Absolute { panic("unreachable") }
|
||||
func (panicDispatcher) fatalf(string, ...any) { panic("unreachable") }
|
||||
|
@ -13,8 +13,6 @@ import (
|
||||
)
|
||||
|
||||
func TestEnvPaths(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
env *EnvPaths
|
||||
@ -50,7 +48,6 @@ func TestEnvPaths(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if tc.wantPanic != "" {
|
||||
defer func() {
|
||||
if r := recover(); r != tc.wantPanic {
|
||||
@ -69,8 +66,6 @@ func TestEnvPaths(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCopyPaths(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
env map[string]string
|
||||
@ -89,7 +84,6 @@ func TestCopyPaths(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if tc.fatal != "" {
|
||||
defer stub.HandleExit(t)
|
||||
}
|
||||
|
@ -1,16 +1,19 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/user"
|
||||
"sync/atomic"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/message"
|
||||
"hakurei.app/system"
|
||||
)
|
||||
|
||||
@ -21,14 +24,16 @@ func newWithMessageError(msg string, err error) error {
|
||||
|
||||
// An outcome is the runnable state of a hakurei container via [hst.Config].
|
||||
type outcome struct {
|
||||
// initial [hst.Config] gob stream for state data;
|
||||
// this is prepared ahead of time as config is clobbered during seal creation
|
||||
ct io.WriterTo
|
||||
|
||||
// Supplementary group ids. Populated during finalise.
|
||||
supp []string
|
||||
// Resolved priv side operating system interactions. Populated during finalise.
|
||||
sys *system.I
|
||||
// Transmitted to shim. Populated during finalise.
|
||||
state *outcomeState
|
||||
// Kept for saving to [state].
|
||||
config *hst.Config
|
||||
|
||||
// Whether the current process is in outcome.main.
|
||||
active atomic.Bool
|
||||
@ -37,7 +42,7 @@ type outcome struct {
|
||||
syscallDispatcher
|
||||
}
|
||||
|
||||
func (k *outcome) finalise(ctx context.Context, msg message.Msg, id *state.ID, config *hst.Config) error {
|
||||
func (k *outcome) finalise(ctx context.Context, msg container.Msg, id *state.ID, config *hst.Config) error {
|
||||
if ctx == nil || id == nil {
|
||||
// unreachable
|
||||
panic("invalid call to finalise")
|
||||
@ -52,6 +57,16 @@ func (k *outcome) finalise(ctx context.Context, msg message.Msg, id *state.ID, c
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(ophestra): do not clobber during finalise
|
||||
{
|
||||
// encode initial configuration for state tracking
|
||||
ct := new(bytes.Buffer)
|
||||
if err := gob.NewEncoder(ct).Encode(config); err != nil {
|
||||
return &hst.AppError{Step: "encode initial config", Err: err}
|
||||
}
|
||||
k.ct = ct
|
||||
}
|
||||
|
||||
// hsu expects numerical group ids
|
||||
supp := make([]string, len(config.Groups))
|
||||
for i, name := range config.Groups {
|
||||
@ -68,19 +83,28 @@ func (k *outcome) finalise(ctx context.Context, msg message.Msg, id *state.ID, c
|
||||
}
|
||||
|
||||
// early validation complete at this point
|
||||
s := newOutcomeState(k.syscallDispatcher, msg, id, config, &Hsu{k: k})
|
||||
s := outcomeState{
|
||||
ID: id,
|
||||
Identity: config.Identity,
|
||||
UserID: (&Hsu{k: k}).MustIDMsg(msg),
|
||||
EnvPaths: copyPaths(k.syscallDispatcher),
|
||||
Container: config.Container,
|
||||
}
|
||||
s.populateEarly(k.syscallDispatcher, msg, config)
|
||||
if err := s.populateLocal(k.syscallDispatcher, msg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sys := system.New(k.ctx, msg, s.uid.unwrap())
|
||||
if err := s.newSys(config, sys).toSystem(); err != nil {
|
||||
return err
|
||||
stateSys := outcomeStateSys{sys: sys, outcomeState: &s}
|
||||
for _, op := range s.Shim.Ops {
|
||||
if err := op.toSystem(&stateSys, config); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
k.sys = sys
|
||||
k.supp = supp
|
||||
k.state = s
|
||||
k.config = config
|
||||
k.state = &s
|
||||
return nil
|
||||
}
|
||||
|
@ -9,9 +9,9 @@ import (
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/fhs"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
// Hsu caches responses from cmd/hsu.
|
||||
@ -74,7 +74,7 @@ func (h *Hsu) ID() (int, error) {
|
||||
func (h *Hsu) MustID() int { return h.MustIDMsg(nil) }
|
||||
|
||||
// MustIDMsg implements MustID with a custom [container.Msg].
|
||||
func (h *Hsu) MustIDMsg(msg message.Msg) int {
|
||||
func (h *Hsu) MustIDMsg(msg container.Msg) int {
|
||||
id, err := h.ID()
|
||||
if err == nil {
|
||||
return id
|
||||
@ -87,7 +87,7 @@ func (h *Hsu) MustIDMsg(msg message.Msg) int {
|
||||
}
|
||||
os.Exit(1)
|
||||
return -0xdeadbeef
|
||||
} else if m, ok := message.GetMessage(err); ok {
|
||||
} else if m, ok := container.GetErrorMessage(err); ok {
|
||||
log.Fatal(m)
|
||||
return -0xdeadbeef
|
||||
} else {
|
||||
|
@ -1,15 +1,13 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"maps"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/message"
|
||||
"hakurei.app/system"
|
||||
"hakurei.app/system/acl"
|
||||
)
|
||||
@ -57,10 +55,13 @@ type outcomeState struct {
|
||||
sc hst.Paths
|
||||
*EnvPaths
|
||||
|
||||
// Matched paths to cover. Populated by spFilesystemOp.
|
||||
HidePaths []*check.Absolute
|
||||
|
||||
// Copied via populateLocal.
|
||||
k syscallDispatcher
|
||||
// Copied via populateLocal.
|
||||
msg message.Msg
|
||||
msg container.Msg
|
||||
}
|
||||
|
||||
// valid checks outcomeState to be safe for use with outcomeOp.
|
||||
@ -72,21 +73,13 @@ func (s *outcomeState) valid() bool {
|
||||
s.EnvPaths != nil
|
||||
}
|
||||
|
||||
// newOutcomeState returns the address of a new outcomeState with its exported fields populated via syscallDispatcher.
|
||||
func newOutcomeState(k syscallDispatcher, msg message.Msg, id *state.ID, config *hst.Config, hsu *Hsu) *outcomeState {
|
||||
s := outcomeState{
|
||||
Shim: &shimParams{PrivPID: k.getpid(), Verbose: msg.IsVerbose()},
|
||||
ID: id,
|
||||
Identity: config.Identity,
|
||||
UserID: hsu.MustIDMsg(msg),
|
||||
EnvPaths: copyPaths(k),
|
||||
Container: config.Container,
|
||||
}
|
||||
// populateEarly populates exported fields via syscallDispatcher.
|
||||
// This must only be called from the priv side.
|
||||
func (s *outcomeState) populateEarly(k syscallDispatcher, msg container.Msg, config *hst.Config) {
|
||||
s.Shim = &shimParams{PrivPID: os.Getpid(), Verbose: msg.IsVerbose(), Ops: fromConfig(config)}
|
||||
|
||||
// enforce bounds and default early
|
||||
if s.Container.WaitDelay < 0 {
|
||||
s.Shim.WaitDelay = 0
|
||||
} else if s.Container.WaitDelay == 0 {
|
||||
if s.Container.WaitDelay <= 0 {
|
||||
s.Shim.WaitDelay = hst.WaitDelayDefault
|
||||
} else if s.Container.WaitDelay > hst.WaitDelayMax {
|
||||
s.Shim.WaitDelay = hst.WaitDelayMax
|
||||
@ -100,12 +93,12 @@ func newOutcomeState(k syscallDispatcher, msg message.Msg, id *state.ID, config
|
||||
s.Mapuid, s.Mapgid = k.overflowUid(msg), k.overflowGid(msg)
|
||||
}
|
||||
|
||||
return &s
|
||||
return
|
||||
}
|
||||
|
||||
// populateLocal populates unexported fields from transmitted exported fields.
|
||||
// These fields are cheaper to recompute per-process.
|
||||
func (s *outcomeState) populateLocal(k syscallDispatcher, msg message.Msg) error {
|
||||
func (s *outcomeState) populateLocal(k syscallDispatcher, msg container.Msg) error {
|
||||
if !s.valid() || k == nil || msg == nil {
|
||||
return newWithMessage("impossible outcome state reached")
|
||||
}
|
||||
@ -148,43 +141,10 @@ type outcomeStateSys struct {
|
||||
// Process-specific directory in XDG_RUNTIME_DIR, nil if unused.
|
||||
runtimeSharePath *check.Absolute
|
||||
|
||||
// Copied from [hst.Config]. Safe for read by outcomeOp.toSystem.
|
||||
appId string
|
||||
// Copied from [hst.Config]. Safe for read by outcomeOp.toSystem.
|
||||
et hst.Enablement
|
||||
|
||||
// Copied from [hst.Config]. Safe for read by spWaylandOp.toSystem only.
|
||||
directWayland bool
|
||||
// Copied header from [hst.Config]. Safe for read by spFinalOp.toSystem only.
|
||||
extraPerms []*hst.ExtraPermConfig
|
||||
// Copied address from [hst.Config]. Safe for read by spDBusOp.toSystem only.
|
||||
sessionBus, systemBus *hst.BusConfig
|
||||
|
||||
sys *system.I
|
||||
*outcomeState
|
||||
}
|
||||
|
||||
// newSys returns the address of a new outcomeStateSys embedding the current outcomeState.
|
||||
func (s *outcomeState) newSys(config *hst.Config, sys *system.I) *outcomeStateSys {
|
||||
return &outcomeStateSys{
|
||||
appId: config.ID, et: config.Enablements.Unwrap(),
|
||||
directWayland: config.DirectWayland, extraPerms: config.ExtraPerms,
|
||||
sessionBus: config.SessionBus, systemBus: config.SystemBus,
|
||||
sys: sys, outcomeState: s,
|
||||
}
|
||||
}
|
||||
|
||||
// newParams returns the address of a new outcomeStateParams embedding the current outcomeState.
|
||||
func (s *outcomeState) newParams() *outcomeStateParams {
|
||||
stateParams := outcomeStateParams{params: new(container.Params), outcomeState: s}
|
||||
if s.Container.Env == nil {
|
||||
stateParams.env = make(map[string]string, envAllocSize)
|
||||
} else {
|
||||
stateParams.env = maps.Clone(s.Container.Env)
|
||||
}
|
||||
return &stateParams
|
||||
}
|
||||
|
||||
// ensureRuntimeDir must be called if access to paths within XDG_RUNTIME_DIR is required.
|
||||
func (state *outcomeStateSys) ensureRuntimeDir() {
|
||||
if state.useRuntimeDir {
|
||||
@ -240,15 +200,12 @@ type outcomeStateParams struct {
|
||||
*outcomeState
|
||||
}
|
||||
|
||||
// errNotEnabled is returned by outcomeOp.toSystem and used internally to exclude an outcomeOp from transmission.
|
||||
var errNotEnabled = errors.New("op not enabled in the configuration")
|
||||
|
||||
// An outcomeOp inflicts an outcome on [system.I] and contains enough information to
|
||||
// inflict it on [container.Params] in a separate process.
|
||||
// An implementation of outcomeOp must store cross-process states in exported fields only.
|
||||
type outcomeOp interface {
|
||||
// toSystem inflicts the current outcome on [system.I] in the priv side process.
|
||||
toSystem(state *outcomeStateSys) error
|
||||
toSystem(state *outcomeStateSys, config *hst.Config) error
|
||||
|
||||
// toContainer inflicts the current outcome on [container.Params] in the shim process.
|
||||
// The implementation must not write to the Env field of [container.Params] as it will be overwritten
|
||||
@ -256,45 +213,36 @@ type outcomeOp interface {
|
||||
toContainer(state *outcomeStateParams) error
|
||||
}
|
||||
|
||||
// toSystem calls the outcomeOp.toSystem method on all outcomeOp implementations and populates shimParams.Ops.
|
||||
// fromConfig returns a corresponding slice of outcomeOp for [hst.Config].
|
||||
// This function assumes the caller has already called the Validate method on [hst.Config]
|
||||
// and checked that it returns nil.
|
||||
func (state *outcomeStateSys) toSystem() error {
|
||||
if state.Shim == nil || state.Shim.Ops != nil {
|
||||
return newWithMessage("invalid ops state reached")
|
||||
}
|
||||
|
||||
ops := [...]outcomeOp{
|
||||
func fromConfig(config *hst.Config) (ops []outcomeOp) {
|
||||
ops = []outcomeOp{
|
||||
// must run first
|
||||
&spParamsOp{},
|
||||
|
||||
// TODO(ophestra): move this late for #8 and #9
|
||||
&spFilesystemOp{},
|
||||
spFilesystemOp{},
|
||||
|
||||
spRuntimeOp{},
|
||||
spTmpdirOp{},
|
||||
spAccountOp{},
|
||||
|
||||
// optional via enablements
|
||||
&spWaylandOp{},
|
||||
&spX11Op{},
|
||||
&spPulseOp{},
|
||||
&spDBusOp{},
|
||||
|
||||
spFinalOp{},
|
||||
}
|
||||
|
||||
state.Shim.Ops = make([]outcomeOp, 0, len(ops))
|
||||
for _, op := range ops {
|
||||
if err := op.toSystem(state); err != nil {
|
||||
// this error is used internally to exclude this outcomeOp from transmission
|
||||
if errors.Is(err, errNotEnabled) {
|
||||
continue
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
state.Shim.Ops = append(state.Shim.Ops, op)
|
||||
et := config.Enablements.Unwrap()
|
||||
if et&hst.EWayland != 0 {
|
||||
ops = append(ops, &spWaylandOp{})
|
||||
}
|
||||
return nil
|
||||
if et&hst.EX11 != 0 {
|
||||
ops = append(ops, &spX11Op{})
|
||||
}
|
||||
if et&hst.EPulse != 0 {
|
||||
ops = append(ops, &spPulseOp{})
|
||||
}
|
||||
if et&hst.EDBus != 0 {
|
||||
ops = append(ops, &spDBusOp{})
|
||||
}
|
||||
|
||||
ops = append(ops, spFinal{})
|
||||
return
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/hst"
|
||||
@ -8,8 +9,6 @@ import (
|
||||
)
|
||||
|
||||
func TestOutcomeStateValid(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
s *outcomeState
|
||||
@ -25,10 +24,55 @@ func TestOutcomeStateValid(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := tc.s.valid(); got != tc.want {
|
||||
t.Errorf("valid: %v, want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromConfig(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
config *hst.Config
|
||||
want []outcomeOp
|
||||
}{
|
||||
{"ne", new(hst.Config), []outcomeOp{
|
||||
&spParamsOp{},
|
||||
spFilesystemOp{},
|
||||
spRuntimeOp{},
|
||||
spTmpdirOp{},
|
||||
spAccountOp{},
|
||||
spFinal{},
|
||||
}},
|
||||
{"wayland pulse", &hst.Config{Enablements: hst.NewEnablements(hst.EWayland | hst.EPulse)}, []outcomeOp{
|
||||
&spParamsOp{},
|
||||
spFilesystemOp{},
|
||||
spRuntimeOp{},
|
||||
spTmpdirOp{},
|
||||
spAccountOp{},
|
||||
&spWaylandOp{},
|
||||
&spPulseOp{},
|
||||
spFinal{},
|
||||
}},
|
||||
{"all", &hst.Config{Enablements: hst.NewEnablements(0xff)}, []outcomeOp{
|
||||
&spParamsOp{},
|
||||
spFilesystemOp{},
|
||||
spRuntimeOp{},
|
||||
spTmpdirOp{},
|
||||
spAccountOp{},
|
||||
&spWaylandOp{},
|
||||
&spX11Op{},
|
||||
&spPulseOp{},
|
||||
&spDBusOp{},
|
||||
spFinal{},
|
||||
}},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := fromConfig(tc.config); !reflect.DeepEqual(got, tc.want) {
|
||||
t.Errorf("fromConfig: %#v, want %#v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,6 @@ import (
|
||||
)
|
||||
|
||||
func TestDeepContainsH(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
basepath string
|
||||
@ -77,7 +75,6 @@ func TestDeepContainsH(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got, err := deepContainsH(tc.basepath, tc.targpath); (err != nil) != tc.wantErr {
|
||||
t.Errorf("deepContainsH() error = %v, wantErr %v", err, tc.wantErr)
|
||||
} else if got != tc.want {
|
||||
|
@ -17,7 +17,6 @@ import (
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/message"
|
||||
"hakurei.app/system"
|
||||
)
|
||||
|
||||
@ -41,7 +40,7 @@ type mainState struct {
|
||||
cmdWait chan error
|
||||
|
||||
k *outcome
|
||||
message.Msg
|
||||
container.Msg
|
||||
uintptr
|
||||
}
|
||||
|
||||
@ -208,7 +207,7 @@ func (ms mainState) fatal(fallback string, ferr error) {
|
||||
}
|
||||
|
||||
// main carries out outcome and terminates. main does not return.
|
||||
func (k *outcome) main(msg message.Msg) {
|
||||
func (k *outcome) main(msg container.Msg) {
|
||||
if !k.active.CompareAndSwap(false, true) {
|
||||
panic("outcome: attempted to run twice")
|
||||
}
|
||||
@ -290,11 +289,10 @@ func (k *outcome) main(msg message.Msg) {
|
||||
// shim accepted setup payload, create process state
|
||||
if ok, err := ms.store.Do(k.state.identity.unwrap(), func(c state.Cursor) {
|
||||
if err := c.Save(&state.State{
|
||||
ID: k.state.id.unwrap(),
|
||||
PID: ms.cmd.Process.Pid,
|
||||
Config: k.config,
|
||||
Time: *ms.Time,
|
||||
}); err != nil {
|
||||
ID: k.state.id.unwrap(),
|
||||
PID: ms.cmd.Process.Pid,
|
||||
Time: *ms.Time,
|
||||
}, k.ct); err != nil {
|
||||
ms.fatal("cannot save state entry:", err)
|
||||
}
|
||||
}); err != nil {
|
||||
@ -313,10 +311,10 @@ func (k *outcome) main(msg message.Msg) {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// printMessageError prints the error message according to [message.GetMessage],
|
||||
// printMessageError prints the error message according to [container.GetErrorMessage],
|
||||
// or fallback prepended to err if an error message is not available.
|
||||
func printMessageError(fallback string, err error) {
|
||||
m, ok := message.GetMessage(err)
|
||||
m, ok := container.GetErrorMessage(err)
|
||||
if !ok {
|
||||
log.Println(fallback, err)
|
||||
return
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"maps"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
@ -17,7 +18,6 @@ import (
|
||||
"hakurei.app/container/bits"
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
//#include "shim-signal.h"
|
||||
@ -47,13 +47,17 @@ type shimParams struct {
|
||||
}
|
||||
|
||||
// valid checks shimParams to be safe for use.
|
||||
func (p *shimParams) valid() bool { return p != nil && p.PrivPID > 0 }
|
||||
func (p *shimParams) valid() bool {
|
||||
return p != nil &&
|
||||
p.Ops != nil &&
|
||||
p.PrivPID > 0
|
||||
}
|
||||
|
||||
// ShimMain is the main function of the shim process and runs as the unconstrained target user.
|
||||
func ShimMain() {
|
||||
log.SetPrefix("shim: ")
|
||||
log.SetFlags(0)
|
||||
msg := message.NewMsg(log.Default())
|
||||
msg := container.NewMsg(log.Default())
|
||||
|
||||
if err := container.SetDumpable(container.SUID_DUMP_DISABLE); err != nil {
|
||||
log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
|
||||
@ -77,7 +81,7 @@ func ShimMain() {
|
||||
closeSetup = f
|
||||
|
||||
if err = state.populateLocal(direct{}, msg); err != nil {
|
||||
if m, ok := message.GetMessage(err); ok {
|
||||
if m, ok := container.GetErrorMessage(err); ok {
|
||||
log.Fatal(m)
|
||||
} else {
|
||||
log.Fatalf("cannot populate local state: %v", err)
|
||||
@ -101,10 +105,16 @@ func ShimMain() {
|
||||
log.Fatalf("cannot set parent-death signal: %v", errno)
|
||||
}
|
||||
|
||||
stateParams := state.newParams()
|
||||
var params container.Params
|
||||
stateParams := outcomeStateParams{params: ¶ms, outcomeState: &state}
|
||||
if state.Container.Env == nil {
|
||||
stateParams.env = make(map[string]string, envAllocSize)
|
||||
} else {
|
||||
stateParams.env = maps.Clone(state.Container.Env)
|
||||
}
|
||||
for _, op := range state.Shim.Ops {
|
||||
if err := op.toContainer(stateParams); err != nil {
|
||||
if m, ok := message.GetMessage(err); ok {
|
||||
if err := op.toContainer(&stateParams); err != nil {
|
||||
if m, ok := container.GetErrorMessage(err); ok {
|
||||
log.Fatal(m)
|
||||
} else {
|
||||
log.Fatalf("cannot create container state: %v", err)
|
||||
@ -123,7 +133,7 @@ func ShimMain() {
|
||||
|
||||
switch buf[0] {
|
||||
case 0: // got SIGCONT from monitor: shim exit requested
|
||||
if fp := cancelContainer.Load(); stateParams.params.ForwardCancel && fp != nil && *fp != nil {
|
||||
if fp := cancelContainer.Load(); params.ForwardCancel && fp != nil && *fp != nil {
|
||||
(*fp)()
|
||||
// shim now bound by ShimWaitDelay, implemented below
|
||||
continue
|
||||
@ -151,7 +161,7 @@ func ShimMain() {
|
||||
}
|
||||
}()
|
||||
|
||||
if stateParams.params.Ops == nil {
|
||||
if params.Ops == nil {
|
||||
log.Fatal("invalid container params")
|
||||
}
|
||||
|
||||
@ -164,7 +174,7 @@ func ShimMain() {
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
cancelContainer.Store(&stop)
|
||||
z := container.New(ctx, msg)
|
||||
z.Params = *stateParams.params
|
||||
z.Params = params
|
||||
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||
|
||||
// bounds and default enforced in finalise.go
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/container/fhs"
|
||||
"hakurei.app/hst"
|
||||
)
|
||||
|
||||
func init() { gob.Register(spAccountOp{}) }
|
||||
@ -13,36 +14,31 @@ func init() { gob.Register(spAccountOp{}) }
|
||||
// spAccountOp sets up user account emulation inside the container.
|
||||
type spAccountOp struct{}
|
||||
|
||||
func (s spAccountOp) toSystem(state *outcomeStateSys) error {
|
||||
func (s spAccountOp) toSystem(state *outcomeStateSys, _ *hst.Config) error {
|
||||
const fallbackUsername = "chronos"
|
||||
|
||||
// do checks here to fail before fork/exec
|
||||
if state.Container == nil || state.Container.Home == nil || state.Container.Shell == nil {
|
||||
// unreachable
|
||||
return syscall.ENOTRECOVERABLE
|
||||
}
|
||||
|
||||
// default is applied in toContainer
|
||||
if state.Container.Username != "" && !isValidUsername(state.Container.Username) {
|
||||
if state.Container.Username == "" {
|
||||
state.Container.Username = fallbackUsername
|
||||
} else if !isValidUsername(state.Container.Username) {
|
||||
return newWithMessage(fmt.Sprintf("invalid user name %q", state.Container.Username))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s spAccountOp) toContainer(state *outcomeStateParams) error {
|
||||
const fallbackUsername = "chronos"
|
||||
|
||||
username := state.Container.Username
|
||||
if username == "" {
|
||||
username = fallbackUsername
|
||||
}
|
||||
|
||||
state.params.Dir = state.Container.Home
|
||||
state.env["HOME"] = state.Container.Home.String()
|
||||
state.env["USER"] = username
|
||||
state.env["USER"] = state.Container.Username
|
||||
state.env["SHELL"] = state.Container.Shell.String()
|
||||
|
||||
state.params.
|
||||
Place(fhs.AbsEtc.Append("passwd"),
|
||||
[]byte(username+":x:"+
|
||||
[]byte(state.Container.Username+":x:"+
|
||||
state.mapuid.String()+":"+
|
||||
state.mapgid.String()+
|
||||
":Hakurei:"+
|
||||
|
@ -1,89 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"os"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/stub"
|
||||
"hakurei.app/hst"
|
||||
)
|
||||
|
||||
func TestSpAccountOp(t *testing.T) {
|
||||
t.Parallel()
|
||||
config := hst.Template()
|
||||
|
||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||
{"invalid state", func(bool, bool) outcomeOp { return spAccountOp{} }, func() *hst.Config {
|
||||
c := hst.Template()
|
||||
c.Container.Shell = nil
|
||||
return c
|
||||
}, nil, []stub.Call{
|
||||
// this op performs basic validation and does not make calls during toSystem
|
||||
}, nil, nil, syscall.ENOTRECOVERABLE, nil, nil, nil, nil, nil},
|
||||
|
||||
{"invalid user name", func(bool, bool) outcomeOp { return spAccountOp{} }, func() *hst.Config {
|
||||
c := hst.Template()
|
||||
c.Container.Username = "9"
|
||||
return c
|
||||
}, nil, []stub.Call{
|
||||
// this op performs basic validation and does not make calls during toSystem
|
||||
}, nil, nil, &hst.AppError{
|
||||
Step: "finalise",
|
||||
Err: os.ErrInvalid,
|
||||
Msg: `invalid user name "9"`,
|
||||
}, nil, nil, nil, nil, nil},
|
||||
|
||||
{"success fallback username", func(bool, bool) outcomeOp { return spAccountOp{} }, func() *hst.Config {
|
||||
c := hst.Template()
|
||||
c.Container.Username = ""
|
||||
return c
|
||||
}, nil, []stub.Call{
|
||||
// this op performs basic validation and does not make calls during toSystem
|
||||
}, newI(), nil, nil, func(state *outcomeStateParams) {
|
||||
state.params.Ops = new(container.Ops)
|
||||
}, []stub.Call{
|
||||
// this op configures the container state and does not make calls during toContainer
|
||||
}, &container.Params{
|
||||
Dir: config.Container.Home,
|
||||
Ops: new(container.Ops).
|
||||
Place(m("/etc/passwd"), []byte("chronos:x:1000:100:Hakurei:/data/data/org.chromium.Chromium:/run/current-system/sw/bin/zsh\n")).
|
||||
Place(m("/etc/group"), []byte("hakurei:x:100:\n")),
|
||||
}, func(t *testing.T, state *outcomeStateParams) {
|
||||
wantEnv := map[string]string{
|
||||
"HOME": config.Container.Home.String(),
|
||||
"USER": config.Container.Username,
|
||||
"SHELL": config.Container.Shell.String(),
|
||||
}
|
||||
maps.Copy(wantEnv, config.Container.Env)
|
||||
if !maps.Equal(state.env, wantEnv) {
|
||||
t.Errorf("toContainer: env = %#v, want %#v", state.env, wantEnv)
|
||||
}
|
||||
}, nil},
|
||||
|
||||
{"success", func(bool, bool) outcomeOp { return spAccountOp{} }, hst.Template, nil, []stub.Call{
|
||||
// this op performs basic validation and does not make calls during toSystem
|
||||
}, newI(), nil, nil, func(state *outcomeStateParams) {
|
||||
state.params.Ops = new(container.Ops)
|
||||
}, []stub.Call{
|
||||
// this op configures the container state and does not make calls during toContainer
|
||||
}, &container.Params{
|
||||
Dir: config.Container.Home,
|
||||
Ops: new(container.Ops).
|
||||
Place(m("/etc/passwd"), []byte("chronos:x:1000:100:Hakurei:/data/data/org.chromium.Chromium:/run/current-system/sw/bin/zsh\n")).
|
||||
Place(m("/etc/group"), []byte("hakurei:x:100:\n")),
|
||||
}, func(t *testing.T, state *outcomeStateParams) {
|
||||
wantEnv := map[string]string{
|
||||
"HOME": config.Container.Home.String(),
|
||||
"USER": config.Container.Username,
|
||||
"SHELL": config.Container.Shell.String(),
|
||||
}
|
||||
maps.Copy(wantEnv, config.Container.Env)
|
||||
if !maps.Equal(state.env, wantEnv) {
|
||||
t.Errorf("toContainer: env = %#v, want %#v", state.env, wantEnv)
|
||||
}
|
||||
}, nil},
|
||||
})
|
||||
}
|
@ -15,7 +15,6 @@ import (
|
||||
"hakurei.app/container/fhs"
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/message"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
@ -32,7 +31,7 @@ type spParamsOp struct {
|
||||
TermSet bool
|
||||
}
|
||||
|
||||
func (s *spParamsOp) toSystem(state *outcomeStateSys) error {
|
||||
func (s *spParamsOp) toSystem(state *outcomeStateSys, _ *hst.Config) error {
|
||||
s.Term, s.TermSet = state.k.lookupEnv("TERM")
|
||||
state.sys.Ensure(state.sc.SharePath, 0711)
|
||||
return nil
|
||||
@ -65,7 +64,7 @@ func (s *spParamsOp) toContainer(state *outcomeStateParams) error {
|
||||
|
||||
// the container is canceled when shim is requested to exit or receives an interrupt or termination signal;
|
||||
// this behaviour is implemented in the shim
|
||||
state.params.ForwardCancel = state.Shim.WaitDelay > 0
|
||||
state.params.ForwardCancel = state.Container.WaitDelay >= 0
|
||||
|
||||
if state.Container.Multiarch {
|
||||
state.params.SeccompFlags |= seccomp.AllowMultiarch
|
||||
@ -105,7 +104,7 @@ func (s *spParamsOp) toContainer(state *outcomeStateParams) error {
|
||||
// early mount points
|
||||
state.params.
|
||||
Proc(fhs.AbsProc).
|
||||
Tmpfs(hst.AbsPrivateTmp, 1<<12, 0755)
|
||||
Tmpfs(hst.AbsTmp, 1<<12, 0755)
|
||||
if !state.Container.Device {
|
||||
state.params.DevWritable(fhs.AbsDev, true)
|
||||
} else {
|
||||
@ -117,15 +116,12 @@ func (s *spParamsOp) toContainer(state *outcomeStateParams) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() { gob.Register(new(spFilesystemOp)) }
|
||||
func init() { gob.Register(spFilesystemOp{}) }
|
||||
|
||||
// spFilesystemOp applies configured filesystems to [container.Params], excluding the optional root filesystem.
|
||||
type spFilesystemOp struct {
|
||||
// Matched paths to cover. Stored during toSystem.
|
||||
HidePaths []*check.Absolute
|
||||
}
|
||||
type spFilesystemOp struct{}
|
||||
|
||||
func (s *spFilesystemOp) toSystem(state *outcomeStateSys) error {
|
||||
func (s spFilesystemOp) toSystem(state *outcomeStateSys, _ *hst.Config) error {
|
||||
/* retrieve paths and hide them if they're made available in the sandbox;
|
||||
|
||||
this feature tries to improve user experience of permissive defaults, and
|
||||
@ -139,12 +135,7 @@ func (s *spFilesystemOp) toSystem(state *outcomeStateSys) error {
|
||||
varRunNscd,
|
||||
}
|
||||
|
||||
// dbus.Address does not go through syscallDispatcher
|
||||
systemBusAddr := dbus.FallbackSystemBusAddress
|
||||
if addr, ok := state.k.lookupEnv(dbus.SystemBusAddress); ok {
|
||||
systemBusAddr = addr
|
||||
}
|
||||
|
||||
_, systemBusAddr := dbus.Address()
|
||||
if entries, err := dbus.Parse([]byte(systemBusAddr)); err != nil {
|
||||
return &hst.AppError{Step: "parse dbus address", Err: err}
|
||||
} else {
|
||||
@ -252,9 +243,16 @@ func (s *spFilesystemOp) toSystem(state *outcomeStateSys) error {
|
||||
for i, ok := range hidePathMatch {
|
||||
if ok {
|
||||
if a, err := check.NewAbs(hidePaths[i]); err != nil {
|
||||
return newWithMessage("invalid path hiding candidate " + strconv.Quote(hidePaths[i]))
|
||||
var absoluteError *check.AbsoluteError
|
||||
if !errors.As(err, &absoluteError) {
|
||||
return newWithMessageError(absoluteError.Error(), absoluteError)
|
||||
}
|
||||
if absoluteError == nil {
|
||||
return newWithMessage("impossible path checking state reached")
|
||||
}
|
||||
return newWithMessage("invalid path hiding candidate " + strconv.Quote(absoluteError.Pathname))
|
||||
} else {
|
||||
s.HidePaths = append(s.HidePaths, a)
|
||||
state.HidePaths = append(state.HidePaths, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -262,7 +260,7 @@ func (s *spFilesystemOp) toSystem(state *outcomeStateSys) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *spFilesystemOp) toContainer(state *outcomeStateParams) error {
|
||||
func (s spFilesystemOp) toContainer(state *outcomeStateParams) error {
|
||||
for i, c := range state.filesystem {
|
||||
if !c.Valid() {
|
||||
return newWithMessage("invalid filesystem at index " + strconv.Itoa(i))
|
||||
@ -270,7 +268,7 @@ func (s *spFilesystemOp) toContainer(state *outcomeStateParams) error {
|
||||
c.Apply(&state.as)
|
||||
}
|
||||
|
||||
for _, a := range s.HidePaths {
|
||||
for _, a := range state.HidePaths {
|
||||
state.params.Tmpfs(a, 1<<13, 0755)
|
||||
}
|
||||
|
||||
@ -301,7 +299,7 @@ func resolveRoot(c *hst.ContainerConfig) (rootfs hst.FilesystemConfig, filesyste
|
||||
}
|
||||
|
||||
// evalSymlinks calls syscallDispatcher.evalSymlinks but discards errors unwrapping to [fs.ErrNotExist].
|
||||
func evalSymlinks(msg message.Msg, k syscallDispatcher, v *string) error {
|
||||
func evalSymlinks(msg container.Msg, k syscallDispatcher, v *string) error {
|
||||
if p, err := k.evalSymlinks(*v); err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
return err
|
||||
|
@ -1,427 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"maps"
|
||||
"os"
|
||||
"reflect"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/bits"
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/container/fhs"
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/container/stub"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
func TestSpParamsOp(t *testing.T) {
|
||||
t.Parallel()
|
||||
config := hst.Template()
|
||||
|
||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||
{"invalid program path", func(isShim, _ bool) outcomeOp {
|
||||
if !isShim {
|
||||
return new(spParamsOp)
|
||||
}
|
||||
return &spParamsOp{Term: "xterm", TermSet: true}
|
||||
}, func() *hst.Config {
|
||||
c := hst.Template()
|
||||
c.Container.Path = nil
|
||||
return c
|
||||
}, nil, []stub.Call{
|
||||
call("lookupEnv", stub.ExpectArgs{"TERM"}, "xterm", nil),
|
||||
}, newI().
|
||||
Ensure(m(container.Nonexistent+"/tmp/hakurei.0"), 0711), nil, nil, nil, []stub.Call{
|
||||
// this op configures the container state and does not make calls during toContainer
|
||||
}, nil, nil, &hst.AppError{
|
||||
Step: "finalise",
|
||||
Err: os.ErrInvalid,
|
||||
Msg: "invalid program path",
|
||||
}},
|
||||
|
||||
{"success defaultargs secure", func(isShim, _ bool) outcomeOp {
|
||||
if !isShim {
|
||||
return new(spParamsOp)
|
||||
}
|
||||
return &spParamsOp{Term: "xterm", TermSet: true}
|
||||
}, func() *hst.Config {
|
||||
c := hst.Template()
|
||||
c.Container.Args = nil
|
||||
c.Container.Multiarch = false
|
||||
c.Container.SeccompCompat = false
|
||||
c.Container.Devel = false
|
||||
c.Container.Userns = false
|
||||
c.Container.Tty = false
|
||||
c.Container.Device = false
|
||||
return c
|
||||
}, nil, []stub.Call{
|
||||
call("lookupEnv", stub.ExpectArgs{"TERM"}, "xterm", nil),
|
||||
}, newI().
|
||||
Ensure(m(container.Nonexistent+"/tmp/hakurei.0"), 0711), nil, nil, nil, []stub.Call{
|
||||
// this op configures the container state and does not make calls during toContainer
|
||||
}, &container.Params{
|
||||
Hostname: config.Container.Hostname,
|
||||
HostNet: config.Container.HostNet,
|
||||
HostAbstract: config.Container.HostAbstract,
|
||||
Path: config.Container.Path,
|
||||
Args: []string{config.Container.Path.String()},
|
||||
SeccompPresets: bits.PresetExt | bits.PresetDenyDevel | bits.PresetDenyNS | bits.PresetDenyTTY,
|
||||
Uid: 1000,
|
||||
Gid: 100,
|
||||
Ops: new(container.Ops).
|
||||
Root(m("/var/lib/hakurei/base/org.debian"), bits.BindWritable).
|
||||
Proc(fhs.AbsProc).Tmpfs(hst.AbsPrivateTmp, 1<<12, 0755).
|
||||
DevWritable(fhs.AbsDev, true).
|
||||
Tmpfs(fhs.AbsDev.Append("shm"), 0, 01777),
|
||||
}, func(t *testing.T, state *outcomeStateParams) {
|
||||
wantEnv := map[string]string{
|
||||
"TERM": "xterm",
|
||||
}
|
||||
maps.Copy(wantEnv, config.Container.Env)
|
||||
if !maps.Equal(state.env, wantEnv) {
|
||||
t.Errorf("toContainer: env = %#v, want %#v", state.env, wantEnv)
|
||||
}
|
||||
|
||||
const wantAutoEtcPrefix = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
if state.as.AutoEtcPrefix != wantAutoEtcPrefix {
|
||||
t.Errorf("toContainer: as.AutoEtcPrefix = %q, want %q", state.as.AutoEtcPrefix, wantAutoEtcPrefix)
|
||||
}
|
||||
|
||||
wantFilesystems := config.Container.Filesystem[1:]
|
||||
if !reflect.DeepEqual(state.filesystem, wantFilesystems) {
|
||||
t.Errorf("toContainer: filesystem = %#v, want %#v", state.filesystem, wantFilesystems)
|
||||
}
|
||||
}, nil},
|
||||
|
||||
{"success", func(isShim, _ bool) outcomeOp {
|
||||
if !isShim {
|
||||
return new(spParamsOp)
|
||||
}
|
||||
return &spParamsOp{Term: "xterm", TermSet: true}
|
||||
}, hst.Template, nil, []stub.Call{
|
||||
call("lookupEnv", stub.ExpectArgs{"TERM"}, "xterm", nil),
|
||||
}, newI().
|
||||
Ensure(m(container.Nonexistent+"/tmp/hakurei.0"), 0711), nil, nil, nil, []stub.Call{
|
||||
// this op configures the container state and does not make calls during toContainer
|
||||
}, &container.Params{
|
||||
Hostname: config.Container.Hostname,
|
||||
RetainSession: config.Container.Tty,
|
||||
HostNet: config.Container.HostNet,
|
||||
HostAbstract: config.Container.HostAbstract,
|
||||
Path: config.Container.Path,
|
||||
Args: config.Container.Args,
|
||||
SeccompFlags: seccomp.AllowMultiarch,
|
||||
Uid: 1000,
|
||||
Gid: 100,
|
||||
Ops: new(container.Ops).
|
||||
Root(m("/var/lib/hakurei/base/org.debian"), bits.BindWritable).
|
||||
Proc(fhs.AbsProc).Tmpfs(hst.AbsPrivateTmp, 1<<12, 0755).
|
||||
Bind(fhs.AbsDev, fhs.AbsDev, bits.BindWritable|bits.BindDevice).
|
||||
Tmpfs(fhs.AbsDev.Append("shm"), 0, 01777),
|
||||
}, func(t *testing.T, state *outcomeStateParams) {
|
||||
wantEnv := map[string]string{
|
||||
"TERM": "xterm",
|
||||
}
|
||||
maps.Copy(wantEnv, config.Container.Env)
|
||||
if !maps.Equal(state.env, wantEnv) {
|
||||
t.Errorf("toContainer: env = %#v, want %#v", state.env, wantEnv)
|
||||
}
|
||||
|
||||
if state.as.AutoEtcPrefix != wantAutoEtcPrefix {
|
||||
t.Errorf("toContainer: as.AutoEtcPrefix = %q, want %q", state.as.AutoEtcPrefix, wantAutoEtcPrefix)
|
||||
}
|
||||
|
||||
wantFilesystems := config.Container.Filesystem[1:]
|
||||
if !reflect.DeepEqual(state.filesystem, wantFilesystems) {
|
||||
t.Errorf("toContainer: filesystem = %#v, want %#v", state.filesystem, wantFilesystems)
|
||||
}
|
||||
}, nil},
|
||||
})
|
||||
}
|
||||
|
||||
func TestSpFilesystemOp(t *testing.T) {
|
||||
const nePrefix = container.Nonexistent + "/eval"
|
||||
var stubDebianRoot = stubDir("bin", "dev", "etc", "home", "lib64", "lost+found",
|
||||
"mnt", "nix", "proc", "root", "run", "srv", "sys", "tmp", "usr", "var")
|
||||
config := hst.Template()
|
||||
|
||||
newConfigSmall := func() *hst.Config {
|
||||
c := hst.Template()
|
||||
c.Container.Filesystem = []hst.FilesystemConfigJSON{
|
||||
{FilesystemConfig: &hst.FSBind{Target: fhs.AbsEtc, Source: fhs.AbsEtc, Special: true}},
|
||||
{FilesystemConfig: &hst.FSOverlay{Target: m("/nix/store"), Lower: []*check.Absolute{
|
||||
fhs.AbsVarLib.Append("hakurei/base/org.nixos/.ro-store"),
|
||||
fhs.AbsVarLib.Append("hakurei/base/org.nixos/org.chromium.Chromium"),
|
||||
}}},
|
||||
{FilesystemConfig: &hst.FSEphemeral{Target: hst.AbsPrivateTmp}},
|
||||
}
|
||||
c.Container.Device = false
|
||||
return c
|
||||
}
|
||||
configSmall := newConfigSmall()
|
||||
|
||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||
{"readdir", func(bool, bool) outcomeOp {
|
||||
return new(spFilesystemOp)
|
||||
}, hst.Template, nil, []stub.Call{
|
||||
call("lookupEnv", stub.ExpectArgs{dbus.SystemBusAddress}, nil, nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{container.Nonexistent + "/xdg_runtime_dir"}, nePrefix+"/xdg_runtime_dir", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{container.Nonexistent + "/tmp/hakurei.0"}, nePrefix+"/tmp/hakurei.0", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/run/nscd"}, "", &os.PathError{Op: "lstat", Path: "/var/run/nscd", Err: os.ErrNotExist}),
|
||||
call("verbosef", stub.ExpectArgs{"path %q does not yet exist", []any{"/var/run/nscd"}}, nil, nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/run/dbus"}, nePrefix+"/run/dbus", nil),
|
||||
call("readdir", stub.ExpectArgs{"/var/lib/hakurei/base/org.debian"}, []os.DirEntry{}, stub.UniqueError(2)),
|
||||
}, nil, nil, &hst.AppError{
|
||||
Step: "access autoroot source",
|
||||
Err: stub.UniqueError(2),
|
||||
}, nil, nil, nil, nil, nil},
|
||||
|
||||
{"invalid dbus address", func(bool, bool) outcomeOp { return new(spFilesystemOp) }, func() *hst.Config {
|
||||
c := newConfigSmall()
|
||||
c.Container.Filesystem = append(c.Container.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: invalidFSHost(false)})
|
||||
return c
|
||||
}, nil, []stub.Call{
|
||||
call("lookupEnv", stub.ExpectArgs{dbus.SystemBusAddress}, "invalid", nil),
|
||||
}, nil, nil, &hst.AppError{
|
||||
Step: "parse dbus address",
|
||||
Err: &dbus.BadAddressError{
|
||||
Type: dbus.ErrNoColon,
|
||||
EntryVal: []byte("invalid"),
|
||||
PairPos: -1,
|
||||
},
|
||||
}, nil, nil, nil, nil, nil},
|
||||
|
||||
{"invalid fs early", func(bool, bool) outcomeOp { return new(spFilesystemOp) }, func() *hst.Config {
|
||||
c := newConfigSmall()
|
||||
c.Container.Filesystem = append(c.Container.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: invalidFSHost(false)})
|
||||
return c
|
||||
}, nil, []stub.Call{
|
||||
call("lookupEnv", stub.ExpectArgs{dbus.SystemBusAddress}, "invalid:meow=0;unix:path=/system_bus_socket;unix:path=system_bus_socket", nil),
|
||||
call("verbosef", stub.ExpectArgs{"dbus socket %q is in an unusual location", []any{"/system_bus_socket"}}, nil, nil),
|
||||
call("verbosef", stub.ExpectArgs{"dbus socket %q is not absolute", []any{"system_bus_socket"}}, nil, nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{container.Nonexistent + "/xdg_runtime_dir"}, nePrefix+"/xdg_runtime_dir", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{container.Nonexistent + "/tmp/hakurei.0"}, nePrefix+"/tmp/hakurei.0", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/run/nscd"}, "", &os.PathError{Op: "lstat", Path: "/var/run/nscd", Err: os.ErrNotExist}),
|
||||
call("verbosef", stub.ExpectArgs{"path %q does not yet exist", []any{"/var/run/nscd"}}, nil, nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/"}, nePrefix+"/etc/dbus", nil), // to match hidePaths
|
||||
}, nil, nil, &hst.AppError{
|
||||
Step: "finalise",
|
||||
Err: os.ErrInvalid,
|
||||
Msg: "invalid filesystem at index 3",
|
||||
}, nil, nil, nil, nil, nil},
|
||||
|
||||
{"evalSymlinks early", func(bool, bool) outcomeOp { return new(spFilesystemOp) }, newConfigSmall, nil, []stub.Call{
|
||||
call("lookupEnv", stub.ExpectArgs{dbus.SystemBusAddress}, "invalid:meow=0;unix:path=/system_bus_socket;unix:path=system_bus_socket", nil),
|
||||
call("verbosef", stub.ExpectArgs{"dbus socket %q is in an unusual location", []any{"/system_bus_socket"}}, nil, nil),
|
||||
call("verbosef", stub.ExpectArgs{"dbus socket %q is not absolute", []any{"system_bus_socket"}}, nil, nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{container.Nonexistent + "/xdg_runtime_dir"}, "", stub.UniqueError(0)),
|
||||
}, nil, nil, &hst.AppError{
|
||||
Step: "evaluate path hiding target",
|
||||
Err: stub.UniqueError(0),
|
||||
}, nil, nil, nil, nil, nil},
|
||||
|
||||
{"host nil abs", func(bool, bool) outcomeOp { return new(spFilesystemOp) }, func() *hst.Config {
|
||||
c := newConfigSmall()
|
||||
c.Container.Filesystem = append(c.Container.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: invalidFSHost(true)})
|
||||
return c
|
||||
}, nil, []stub.Call{
|
||||
call("lookupEnv", stub.ExpectArgs{dbus.SystemBusAddress}, "invalid:meow=0;unix:path=/system_bus_socket;unix:path=system_bus_socket", nil),
|
||||
call("verbosef", stub.ExpectArgs{"dbus socket %q is in an unusual location", []any{"/system_bus_socket"}}, nil, nil),
|
||||
call("verbosef", stub.ExpectArgs{"dbus socket %q is not absolute", []any{"system_bus_socket"}}, nil, nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{container.Nonexistent + "/xdg_runtime_dir"}, nePrefix+"/xdg_runtime_dir", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{container.Nonexistent + "/tmp/hakurei.0"}, nePrefix+"/tmp/hakurei.0", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/run/nscd"}, "", &os.PathError{Op: "lstat", Path: "/var/run/nscd", Err: os.ErrNotExist}),
|
||||
call("verbosef", stub.ExpectArgs{"path %q does not yet exist", []any{"/var/run/nscd"}}, nil, nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/"}, nePrefix+"/etc/dbus", nil), // to match hidePaths
|
||||
call("evalSymlinks", stub.ExpectArgs{"/etc/"}, nePrefix+"/etc", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.nixos/.ro-store"}, nePrefix+"/var/lib/hakurei/base/org.nixos/.ro-store", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.nixos/org.chromium.Chromium"}, "var/lib/hakurei/base/org.nixos/org.chromium.Chromium", nil),
|
||||
}, nil, nil, &hst.AppError{
|
||||
Step: "finalise",
|
||||
Err: os.ErrInvalid,
|
||||
Msg: "impossible path hiding state reached",
|
||||
}, nil, nil, nil, nil, nil},
|
||||
|
||||
{"evalSymlinks late", func(bool, bool) outcomeOp { return new(spFilesystemOp) }, newConfigSmall, nil, []stub.Call{
|
||||
call("lookupEnv", stub.ExpectArgs{dbus.SystemBusAddress}, "invalid:meow=0;unix:path=/system_bus_socket;unix:path=system_bus_socket", nil),
|
||||
call("verbosef", stub.ExpectArgs{"dbus socket %q is in an unusual location", []any{"/system_bus_socket"}}, nil, nil),
|
||||
call("verbosef", stub.ExpectArgs{"dbus socket %q is not absolute", []any{"system_bus_socket"}}, nil, nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{container.Nonexistent + "/xdg_runtime_dir"}, nePrefix+"/xdg_runtime_dir", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{container.Nonexistent + "/tmp/hakurei.0"}, nePrefix+"/tmp/hakurei.0", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/run/nscd"}, "", &os.PathError{Op: "lstat", Path: "/var/run/nscd", Err: os.ErrNotExist}),
|
||||
call("verbosef", stub.ExpectArgs{"path %q does not yet exist", []any{"/var/run/nscd"}}, nil, nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/"}, nePrefix+"/etc/dbus", nil), // to match hidePaths
|
||||
call("evalSymlinks", stub.ExpectArgs{"/etc/"}, nePrefix+"/etc", stub.UniqueError(1)),
|
||||
}, nil, nil, &hst.AppError{
|
||||
Step: "evaluate path hiding source",
|
||||
Err: stub.UniqueError(1),
|
||||
}, nil, nil, nil, nil, nil},
|
||||
|
||||
{"invalid contains", func(bool, bool) outcomeOp { return new(spFilesystemOp) }, newConfigSmall, nil, []stub.Call{
|
||||
call("lookupEnv", stub.ExpectArgs{dbus.SystemBusAddress}, "invalid:meow=0;unix:path=/system_bus_socket;unix:path=system_bus_socket", nil),
|
||||
call("verbosef", stub.ExpectArgs{"dbus socket %q is in an unusual location", []any{"/system_bus_socket"}}, nil, nil),
|
||||
call("verbosef", stub.ExpectArgs{"dbus socket %q is not absolute", []any{"system_bus_socket"}}, nil, nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{container.Nonexistent + "/xdg_runtime_dir"}, nePrefix+"/xdg_runtime_dir", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{container.Nonexistent + "/tmp/hakurei.0"}, nePrefix+"/tmp/hakurei.0", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/run/nscd"}, "", &os.PathError{Op: "lstat", Path: "/var/run/nscd", Err: os.ErrNotExist}),
|
||||
call("verbosef", stub.ExpectArgs{"path %q does not yet exist", []any{"/var/run/nscd"}}, nil, nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/"}, nePrefix+"/etc/dbus", nil), // to match hidePaths
|
||||
call("evalSymlinks", stub.ExpectArgs{"/etc/"}, nePrefix+"/etc", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.nixos/.ro-store"}, nePrefix+"/var/lib/hakurei/base/org.nixos/.ro-store", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.nixos/org.chromium.Chromium"}, "var/lib/hakurei/base/org.nixos/org.chromium.Chromium", nil),
|
||||
call("verbosef", stub.ExpectArgs{"hiding path %q from %q", []any{"/proc/nonexistent/eval/etc/dbus", "/etc/"}}, nil, nil),
|
||||
}, nil, nil, &hst.AppError{
|
||||
Step: "determine path hiding outcome",
|
||||
Err: errors.New("Rel: can't make /proc/nonexistent/eval/xdg_runtime_dir relative to var/lib/hakurei/base/org.nixos/org.chromium.Chromium"),
|
||||
}, nil, nil, nil, nil, nil},
|
||||
|
||||
{"invalid hide", func(bool, bool) outcomeOp { return new(spFilesystemOp) }, newConfigSmall, nil, []stub.Call{
|
||||
call("lookupEnv", stub.ExpectArgs{dbus.SystemBusAddress}, "invalid:meow=0;unix:path=/system_bus_socket;unix:path=system_bus_socket", nil),
|
||||
call("verbosef", stub.ExpectArgs{"dbus socket %q is in an unusual location", []any{"/system_bus_socket"}}, nil, nil),
|
||||
call("verbosef", stub.ExpectArgs{"dbus socket %q is not absolute", []any{"system_bus_socket"}}, nil, nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{container.Nonexistent + "/xdg_runtime_dir"}, "xdg_runtime_dir", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{container.Nonexistent + "/tmp/hakurei.0"}, "tmp/hakurei.0", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/run/nscd"}, "nscd", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "nonexistent/dbus", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/etc/"}, "nonexistent", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.nixos/.ro-store"}, ".ro-store", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.nixos/org.chromium.Chromium"}, "org.chromium.Chromium", nil),
|
||||
call("verbosef", stub.ExpectArgs{"hiding path %q from %q", []any{"nonexistent/dbus", "/etc/"}}, nil, nil),
|
||||
}, nil, nil, &hst.AppError{
|
||||
Step: "finalise",
|
||||
Err: os.ErrInvalid,
|
||||
Msg: `invalid path hiding candidate "nonexistent/dbus"`,
|
||||
}, nil, nil, nil, nil, nil},
|
||||
|
||||
{"invalid fs", func(isShim, clearUnexported bool) outcomeOp {
|
||||
if !isShim {
|
||||
return new(spFilesystemOp)
|
||||
}
|
||||
return &spFilesystemOp{HidePaths: []*check.Absolute{m("/proc/nonexistent/eval/etc/dbus")}}
|
||||
}, newConfigSmall, nil, []stub.Call{
|
||||
call("lookupEnv", stub.ExpectArgs{dbus.SystemBusAddress}, "invalid:meow=0;unix:path=/system_bus_socket;unix:path=system_bus_socket", nil),
|
||||
call("verbosef", stub.ExpectArgs{"dbus socket %q is in an unusual location", []any{"/system_bus_socket"}}, nil, nil),
|
||||
call("verbosef", stub.ExpectArgs{"dbus socket %q is not absolute", []any{"system_bus_socket"}}, nil, nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{container.Nonexistent + "/xdg_runtime_dir"}, nePrefix+"/xdg_runtime_dir", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{container.Nonexistent + "/tmp/hakurei.0"}, nePrefix+"/tmp/hakurei.0", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/run/nscd"}, "", &os.PathError{Op: "lstat", Path: "/var/run/nscd", Err: os.ErrNotExist}),
|
||||
call("verbosef", stub.ExpectArgs{"path %q does not yet exist", []any{"/var/run/nscd"}}, nil, nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/"}, nePrefix+"/etc/dbus", nil), // to match hidePaths
|
||||
call("evalSymlinks", stub.ExpectArgs{"/etc/"}, nePrefix+"/etc", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.nixos/.ro-store"}, nePrefix+"/var/lib/hakurei/base/org.nixos/.ro-store", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.nixos/org.chromium.Chromium"}, nePrefix+"/var/lib/hakurei/base/org.nixos/org.chromium.Chromium", nil),
|
||||
call("verbosef", stub.ExpectArgs{"hiding path %q from %q", []any{"/proc/nonexistent/eval/etc/dbus", "/etc/"}}, nil, nil),
|
||||
}, newI(), nil, nil, func(state *outcomeStateParams) {
|
||||
state.filesystem = configSmall.Container.Filesystem
|
||||
state.params.Ops = new(container.Ops)
|
||||
state.as = hst.ApplyState{AutoEtcPrefix: wantAutoEtcPrefix, Ops: opsAdapter{state.params.Ops}}
|
||||
state.filesystem = append(state.filesystem, hst.FilesystemConfigJSON{})
|
||||
}, []stub.Call{
|
||||
// this op configures the container state and does not make calls during toContainer
|
||||
}, nil, nil, &hst.AppError{
|
||||
Step: "finalise",
|
||||
Err: os.ErrInvalid,
|
||||
Msg: "invalid filesystem at index 3",
|
||||
}},
|
||||
|
||||
{"success noroot nodev envdbus strangedbus dbusnotabs hide", func(isShim, clearUnexported bool) outcomeOp {
|
||||
if !isShim {
|
||||
return new(spFilesystemOp)
|
||||
}
|
||||
return &spFilesystemOp{HidePaths: []*check.Absolute{m("/proc/nonexistent/eval/etc/dbus")}}
|
||||
}, newConfigSmall, nil, []stub.Call{
|
||||
call("lookupEnv", stub.ExpectArgs{dbus.SystemBusAddress}, "invalid:meow=0;unix:path=/system_bus_socket;unix:path=system_bus_socket", nil),
|
||||
call("verbosef", stub.ExpectArgs{"dbus socket %q is in an unusual location", []any{"/system_bus_socket"}}, nil, nil),
|
||||
call("verbosef", stub.ExpectArgs{"dbus socket %q is not absolute", []any{"system_bus_socket"}}, nil, nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{container.Nonexistent + "/xdg_runtime_dir"}, nePrefix+"/xdg_runtime_dir", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{container.Nonexistent + "/tmp/hakurei.0"}, nePrefix+"/tmp/hakurei.0", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/run/nscd"}, "", &os.PathError{Op: "lstat", Path: "/var/run/nscd", Err: os.ErrNotExist}),
|
||||
call("verbosef", stub.ExpectArgs{"path %q does not yet exist", []any{"/var/run/nscd"}}, nil, nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/"}, nePrefix+"/etc/dbus", nil), // to match hidePaths
|
||||
call("evalSymlinks", stub.ExpectArgs{"/etc/"}, nePrefix+"/etc", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.nixos/.ro-store"}, nePrefix+"/var/lib/hakurei/base/org.nixos/.ro-store", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.nixos/org.chromium.Chromium"}, nePrefix+"/var/lib/hakurei/base/org.nixos/org.chromium.Chromium", nil),
|
||||
call("verbosef", stub.ExpectArgs{"hiding path %q from %q", []any{"/proc/nonexistent/eval/etc/dbus", "/etc/"}}, nil, nil),
|
||||
}, newI(), nil, nil, func(state *outcomeStateParams) {
|
||||
state.filesystem = configSmall.Container.Filesystem
|
||||
state.params.Ops = new(container.Ops)
|
||||
state.as = hst.ApplyState{AutoEtcPrefix: wantAutoEtcPrefix, Ops: opsAdapter{state.params.Ops}}
|
||||
}, []stub.Call{
|
||||
// this op configures the container state and does not make calls during toContainer
|
||||
}, &container.Params{
|
||||
Ops: new(container.Ops).
|
||||
Etc(fhs.AbsEtc, wantAutoEtcPrefix).
|
||||
OverlayReadonly(
|
||||
check.MustAbs("/nix/store"),
|
||||
fhs.AbsVarLib.Append("hakurei/base/org.nixos/.ro-store"),
|
||||
fhs.AbsVarLib.Append("hakurei/base/org.nixos/org.chromium.Chromium")).
|
||||
Readonly(hst.AbsPrivateTmp, 0755).
|
||||
Tmpfs(m("/proc/nonexistent/eval/etc/dbus"), 1<<13, 0755).
|
||||
Remount(fhs.AbsDev, syscall.MS_RDONLY),
|
||||
}, nil, nil},
|
||||
|
||||
{"success", func(bool, bool) outcomeOp {
|
||||
return new(spFilesystemOp)
|
||||
}, hst.Template, nil, []stub.Call{
|
||||
call("lookupEnv", stub.ExpectArgs{dbus.SystemBusAddress}, nil, nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{container.Nonexistent + "/xdg_runtime_dir"}, nePrefix+"/xdg_runtime_dir", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{container.Nonexistent + "/tmp/hakurei.0"}, nePrefix+"/tmp/hakurei.0", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/run/nscd"}, "", &os.PathError{Op: "lstat", Path: "/var/run/nscd", Err: os.ErrNotExist}),
|
||||
call("verbosef", stub.ExpectArgs{"path %q does not yet exist", []any{"/var/run/nscd"}}, nil, nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/run/dbus"}, nePrefix+"/run/dbus", nil),
|
||||
call("readdir", stub.ExpectArgs{"/var/lib/hakurei/base/org.debian"}, stubDebianRoot, nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/etc/"}, nePrefix+"/etc", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/nix/u0/org.chromium.Chromium/rw-store/upper"}, nePrefix+"/var/lib/hakurei/nix/u0/org.chromium.Chromium/rw-store/upper", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/nix/u0/org.chromium.Chromium/rw-store/work"}, nePrefix+"/var/lib/hakurei/nix/u0/org.chromium.Chromium/rw-store/work", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.nixos/ro-store"}, nePrefix+"/var/lib/hakurei/base/org.nixos/ro-store", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/u0/org.chromium.Chromium"}, nePrefix+"/var/lib/hakurei/u0/org.chromium.Chromium", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/dev/dri"}, nePrefix+"/dev/dri", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.debian/bin"}, nePrefix+"/var/lib/hakurei/base/org.debian/bin", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.debian/home"}, nePrefix+"/var/lib/hakurei/base/org.debian/home", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.debian/lib64"}, nePrefix+"/var/lib/hakurei/base/org.debian/lib64", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.debian/lost+found"}, nePrefix+"/var/lib/hakurei/base/org.debian/lost+found", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.debian/nix"}, nePrefix+"/var/lib/hakurei/base/org.debian/nix", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.debian/root"}, nePrefix+"/var/lib/hakurei/base/org.debian/root", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.debian/run"}, nePrefix+"/var/lib/hakurei/base/org.debian/run", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.debian/srv"}, nePrefix+"/var/lib/hakurei/base/org.debian/srv", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.debian/sys"}, nePrefix+"/var/lib/hakurei/base/org.debian/sys", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.debian/usr"}, nePrefix+"/var/lib/hakurei/base/org.debian/usr", nil),
|
||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.debian/var"}, nePrefix+"/var/lib/hakurei/base/org.debian/var", nil),
|
||||
}, newI(), nil, nil, func(state *outcomeStateParams) {
|
||||
state.filesystem = config.Container.Filesystem[1:]
|
||||
state.params.Ops = new(container.Ops)
|
||||
state.as = hst.ApplyState{AutoEtcPrefix: wantAutoEtcPrefix, Ops: opsAdapter{state.params.Ops}}
|
||||
}, []stub.Call{
|
||||
// this op configures the container state and does not make calls during toContainer
|
||||
}, &container.Params{
|
||||
Ops: new(container.Ops).
|
||||
Etc(fhs.AbsEtc, wantAutoEtcPrefix).
|
||||
Tmpfs(fhs.AbsTmp, 0, 0755).
|
||||
Overlay(
|
||||
check.MustAbs("/nix/store"),
|
||||
fhs.AbsVarLib.Append("hakurei/nix/u0/org.chromium.Chromium/rw-store/upper"),
|
||||
fhs.AbsVarLib.Append("hakurei/nix/u0/org.chromium.Chromium/rw-store/work"),
|
||||
fhs.AbsVarLib.Append("hakurei/base/org.nixos/ro-store")).
|
||||
Link(fhs.AbsRun.Append("current-system"), "/run/current-system", true).
|
||||
Link(fhs.AbsRun.Append("opengl-driver"), "/run/opengl-driver", true).
|
||||
Bind(
|
||||
fhs.AbsVarLib.Append("hakurei/u0/org.chromium.Chromium"),
|
||||
check.MustAbs("/data/data/org.chromium.Chromium"),
|
||||
bits.BindWritable|bits.BindEnsure).
|
||||
Bind(fhs.AbsDev.Append("dri"), fhs.AbsDev.Append("dri"), bits.BindDevice|bits.BindWritable|bits.BindOptional),
|
||||
}, nil, nil},
|
||||
})
|
||||
}
|
||||
|
||||
// invalidFSHost implements the Host method of [hst.FilesystemConfig] with an invalid response.
|
||||
type invalidFSHost bool
|
||||
|
||||
func (f invalidFSHost) Valid() bool { return bool(f) }
|
||||
func (invalidFSHost) Path() *check.Absolute { panic("unreachable") }
|
||||
func (invalidFSHost) Host() []*check.Absolute { return []*check.Absolute{nil} }
|
||||
func (invalidFSHost) Apply(*hst.ApplyState) { panic("unreachable") }
|
||||
func (invalidFSHost) String() string { panic("unreachable") }
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user