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