Compare commits
35 Commits
v0.2.2
...
a40d182706
| Author | SHA1 | Date | |
|---|---|---|---|
|
a40d182706
|
|||
|
e5baaf416f
|
|||
|
ee6c471fe6
|
|||
|
16bf3178d3
|
|||
|
034c59a26a
|
|||
|
5bf28901a4
|
|||
|
9b507715d4
|
|||
|
12ab7ea3b4
|
|||
|
1f0226f7e0
|
|||
|
584ce3da68
|
|||
|
5d18af0007
|
|||
|
0e6c1a5026
|
|||
|
d23b4dc9e6
|
|||
|
3ce63e95d7
|
|||
|
2489766efe
|
|||
|
9e48d7f562
|
|||
|
f280994957
|
|||
|
ae7b343cde
|
|||
|
a63a372fe0
|
|||
|
16f9001f5f
|
|||
|
80ad2e4e23
|
|||
|
92b83bd599
|
|||
|
8ace214832
|
|||
|
eb5ee4fece
|
|||
|
9462af08f3
|
|||
|
a5f0aa3f30
|
|||
|
dd0bb0a391
|
|||
|
d16da6da8c
|
|||
|
e58181a930
|
|||
|
71e70b7b5f
|
|||
|
afa1a8043e
|
|||
|
1ba1cb8865
|
|||
|
44ba7a5f02
|
|||
|
dc467493d8
|
|||
|
46cd3a28c8
|
@@ -2,10 +2,13 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -13,36 +16,52 @@ import (
|
|||||||
|
|
||||||
"hakurei.app/command"
|
"hakurei.app/command"
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
"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/internal/hlog"
|
|
||||||
"hakurei.app/system"
|
|
||||||
"hakurei.app/system/dbus"
|
"hakurei.app/system/dbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func buildCommand(ctx context.Context, out io.Writer) command.Command {
|
func buildCommand(ctx context.Context, msg container.Msg, early *earlyHardeningErrs, out io.Writer) command.Command {
|
||||||
var (
|
var (
|
||||||
flagVerbose bool
|
flagVerbose bool
|
||||||
flagJSON bool
|
flagJSON bool
|
||||||
)
|
)
|
||||||
c := command.New(out, log.Printf, "hakurei", func([]string) error { internal.InstallOutput(flagVerbose); return nil }).
|
c := command.New(out, log.Printf, "hakurei", func([]string) error {
|
||||||
|
msg.SwapVerbose(flagVerbose)
|
||||||
|
|
||||||
|
if early.yamaLSM != nil {
|
||||||
|
msg.Verbosef("cannot enable ptrace protection via Yama LSM: %v", early.yamaLSM)
|
||||||
|
// not fatal
|
||||||
|
}
|
||||||
|
|
||||||
|
if early.dumpable != nil {
|
||||||
|
log.Printf("cannot set SUID_DUMP_DISABLE: %s", early.dumpable)
|
||||||
|
// not fatal
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}).
|
||||||
Flag(&flagVerbose, "v", command.BoolFlag(false), "Increase log verbosity").
|
Flag(&flagVerbose, "v", command.BoolFlag(false), "Increase log verbosity").
|
||||||
Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output in JSON when applicable")
|
Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output in JSON when applicable")
|
||||||
|
|
||||||
c.Command("shim", command.UsageInternal, func([]string) error { app.ShimMain(); return errSuccess })
|
c.Command("shim", command.UsageInternal, func([]string) error { app.ShimMain(); return errSuccess })
|
||||||
|
|
||||||
c.Command("app", "Load app from configuration file", func(args []string) error {
|
c.Command("app", "Load and start container from configuration file", func(args []string) error {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
log.Fatal("app requires at least 1 argument")
|
log.Fatal("app requires at least 1 argument")
|
||||||
}
|
}
|
||||||
|
|
||||||
// config extraArgs...
|
// config extraArgs...
|
||||||
config := tryPath(args[0])
|
config := tryPath(msg, args[0])
|
||||||
config.Args = append(config.Args, args[1:]...)
|
if config != nil && config.Container != nil {
|
||||||
|
config.Container.Args = append(config.Container.Args, args[1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
app.Main(ctx, config)
|
app.Main(ctx, msg, config)
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -62,14 +81,8 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
|
|||||||
flagWayland, flagX11, flagDBus, flagPulse bool
|
flagWayland, flagX11, flagDBus, flagPulse bool
|
||||||
)
|
)
|
||||||
|
|
||||||
c.NewCommand("run", "Configure and start a permissive default sandbox", func(args []string) error {
|
c.NewCommand("run", "Configure and start a permissive container", func(args []string) error {
|
||||||
// initialise config from flags
|
if flagIdentity < hst.IdentityMin || flagIdentity > hst.IdentityMax {
|
||||||
config := &hst.Config{
|
|
||||||
ID: flagID,
|
|
||||||
Args: args,
|
|
||||||
}
|
|
||||||
|
|
||||||
if flagIdentity < 0 || flagIdentity > 9999 {
|
|
||||||
log.Fatalf("identity %d out of range", flagIdentity)
|
log.Fatalf("identity %d out of range", flagIdentity)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,15 +91,15 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
|
|||||||
passwd *user.User
|
passwd *user.User
|
||||||
passwdOnce sync.Once
|
passwdOnce sync.Once
|
||||||
passwdFunc = func() {
|
passwdFunc = func() {
|
||||||
us := strconv.Itoa(app.HsuUid(new(app.Hsu).MustID(), flagIdentity))
|
us := strconv.Itoa(app.HsuUid(new(app.Hsu).MustIDMsg(msg), flagIdentity))
|
||||||
if u, err := user.LookupId(us); err != nil {
|
if u, err := user.LookupId(us); err != nil {
|
||||||
hlog.Verbosef("cannot look up uid %s", us)
|
msg.Verbosef("cannot look up uid %s", us)
|
||||||
passwd = &user.User{
|
passwd = &user.User{
|
||||||
Uid: us,
|
Uid: us,
|
||||||
Gid: us,
|
Gid: us,
|
||||||
Username: "chronos",
|
Username: "chronos",
|
||||||
Name: "Hakurei Permissive Default",
|
Name: "Hakurei Permissive Default",
|
||||||
HomeDir: container.FHSVarEmpty,
|
HomeDir: fhs.VarEmpty,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
passwd = u
|
passwd = u
|
||||||
@@ -94,60 +107,128 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if flagHomeDir == "os" {
|
// paths are identical, resolve inner shell and program path
|
||||||
passwdOnce.Do(passwdFunc)
|
shell := fhs.AbsRoot.Append("bin", "sh")
|
||||||
flagHomeDir = passwd.HomeDir
|
if a, err := check.NewAbs(os.Getenv("SHELL")); err == nil {
|
||||||
|
shell = a
|
||||||
|
}
|
||||||
|
progPath := shell
|
||||||
|
if len(args) > 0 {
|
||||||
|
if p, err := exec.LookPath(args[0]); err != nil {
|
||||||
|
log.Fatal(errors.Unwrap(err))
|
||||||
|
return err
|
||||||
|
} else if progPath, err = check.NewAbs(p); err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if flagUserName == "chronos" {
|
var et hst.Enablement
|
||||||
passwdOnce.Do(passwdFunc)
|
|
||||||
flagUserName = passwd.Username
|
|
||||||
}
|
|
||||||
|
|
||||||
config.Identity = flagIdentity
|
|
||||||
config.Groups = flagGroups
|
|
||||||
config.Username = flagUserName
|
|
||||||
|
|
||||||
if a, err := container.NewAbs(flagHomeDir); err != nil {
|
|
||||||
log.Fatal(err.Error())
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
config.Home = a
|
|
||||||
}
|
|
||||||
|
|
||||||
var e system.Enablement
|
|
||||||
if flagWayland {
|
if flagWayland {
|
||||||
e |= system.EWayland
|
et |= hst.EWayland
|
||||||
}
|
}
|
||||||
if flagX11 {
|
if flagX11 {
|
||||||
e |= system.EX11
|
et |= hst.EX11
|
||||||
}
|
}
|
||||||
if flagDBus {
|
if flagDBus {
|
||||||
e |= system.EDBus
|
et |= hst.EDBus
|
||||||
}
|
}
|
||||||
if flagPulse {
|
if flagPulse {
|
||||||
e |= system.EPulse
|
et |= hst.EPulse
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &hst.Config{
|
||||||
|
ID: flagID,
|
||||||
|
Identity: flagIdentity,
|
||||||
|
Groups: flagGroups,
|
||||||
|
Enablements: hst.NewEnablements(et),
|
||||||
|
|
||||||
|
Container: &hst.ContainerConfig{
|
||||||
|
Userns: true,
|
||||||
|
HostNet: true,
|
||||||
|
Tty: true,
|
||||||
|
HostAbstract: true,
|
||||||
|
|
||||||
|
Filesystem: []hst.FilesystemConfigJSON{
|
||||||
|
// autoroot, includes the home directory
|
||||||
|
{FilesystemConfig: &hst.FSBind{
|
||||||
|
Target: fhs.AbsRoot,
|
||||||
|
Source: fhs.AbsRoot,
|
||||||
|
Write: true,
|
||||||
|
Special: true,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
|
||||||
|
Username: flagUserName,
|
||||||
|
Shell: shell,
|
||||||
|
|
||||||
|
Path: progPath,
|
||||||
|
Args: args,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind GPU stuff
|
||||||
|
if et&(hst.EX11|hst.EWayland) != 0 {
|
||||||
|
config.Container.Filesystem = append(config.Container.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
|
||||||
|
Source: fhs.AbsDev.Append("dri"),
|
||||||
|
Device: true,
|
||||||
|
Optional: true,
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Container.Filesystem = append(config.Container.Filesystem,
|
||||||
|
// opportunistically bind kvm
|
||||||
|
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
|
||||||
|
Source: fhs.AbsDev.Append("kvm"),
|
||||||
|
Device: true,
|
||||||
|
Optional: true,
|
||||||
|
}},
|
||||||
|
|
||||||
|
// do autoetc last
|
||||||
|
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
|
||||||
|
Target: fhs.AbsEtc,
|
||||||
|
Source: fhs.AbsEtc,
|
||||||
|
Special: true,
|
||||||
|
}},
|
||||||
|
)
|
||||||
|
|
||||||
|
if config.Container.Username == "chronos" {
|
||||||
|
passwdOnce.Do(passwdFunc)
|
||||||
|
config.Container.Username = passwd.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
homeDir := flagHomeDir
|
||||||
|
if homeDir == "os" {
|
||||||
|
passwdOnce.Do(passwdFunc)
|
||||||
|
homeDir = passwd.HomeDir
|
||||||
|
}
|
||||||
|
if a, err := check.NewAbs(homeDir); err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
config.Container.Home = a
|
||||||
|
}
|
||||||
}
|
}
|
||||||
config.Enablements = hst.NewEnablements(e)
|
|
||||||
|
|
||||||
// parse D-Bus config file from flags if applicable
|
// parse D-Bus config file from flags if applicable
|
||||||
if flagDBus {
|
if flagDBus {
|
||||||
if flagDBusConfigSession == "builtin" {
|
if flagDBusConfigSession == "builtin" {
|
||||||
config.SessionBus = dbus.NewConfig(flagID, true, flagDBusMpris)
|
config.SessionBus = dbus.NewConfig(flagID, true, flagDBusMpris)
|
||||||
} else {
|
} else {
|
||||||
if conf, err := dbus.NewConfigFromFile(flagDBusConfigSession); err != nil {
|
if f, err := os.Open(flagDBusConfigSession); err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
} else if err = json.NewDecoder(f).Decode(&config.SessionBus); err != nil {
|
||||||
log.Fatalf("cannot load session bus proxy config from %q: %s", flagDBusConfigSession, err)
|
log.Fatalf("cannot load session bus proxy config from %q: %s", flagDBusConfigSession, err)
|
||||||
} else {
|
|
||||||
config.SessionBus = conf
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// system bus proxy is optional
|
// system bus proxy is optional
|
||||||
if flagDBusConfigSystem != "nil" {
|
if flagDBusConfigSystem != "nil" {
|
||||||
if conf, err := dbus.NewConfigFromFile(flagDBusConfigSystem); err != nil {
|
if f, err := os.Open(flagDBusConfigSystem); err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
} else if err = json.NewDecoder(f).Decode(&config.SystemBus); err != nil {
|
||||||
log.Fatalf("cannot load system bus proxy config from %q: %s", flagDBusConfigSystem, err)
|
log.Fatalf("cannot load system bus proxy config from %q: %s", flagDBusConfigSystem, err)
|
||||||
} else {
|
|
||||||
config.SystemBus = conf
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +243,7 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Main(ctx, config)
|
app.Main(ctx, msg, config)
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}).
|
}).
|
||||||
Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"),
|
Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"),
|
||||||
@@ -202,11 +283,13 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
|
|||||||
|
|
||||||
case 1: // instance
|
case 1: // instance
|
||||||
name := args[0]
|
name := args[0]
|
||||||
config, entry := tryShort(name)
|
config, entry := tryShort(msg, name)
|
||||||
if config == nil {
|
if config == nil {
|
||||||
config = tryPath(name)
|
config = tryPath(msg, name)
|
||||||
|
}
|
||||||
|
if !printShowInstance(os.Stdout, time.Now().UTC(), entry, config, flagShort, flagJSON) {
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
printShowInstance(os.Stdout, time.Now().UTC(), entry, config, flagShort, flagJSON)
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
log.Fatal("show requires 1 argument")
|
log.Fatal("show requires 1 argument")
|
||||||
@@ -219,31 +302,16 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
|
|||||||
var flagShort bool
|
var flagShort bool
|
||||||
c.NewCommand("ps", "List active instances", func(args []string) error {
|
c.NewCommand("ps", "List active instances", func(args []string) error {
|
||||||
var sc hst.Paths
|
var sc hst.Paths
|
||||||
app.CopyPaths(&sc, new(app.Hsu).MustID())
|
app.CopyPaths().Copy(&sc, new(app.Hsu).MustID())
|
||||||
printPs(os.Stdout, time.Now().UTC(), state.NewMulti(sc.RunDirPath.String()), flagShort, flagJSON)
|
printPs(os.Stdout, time.Now().UTC(), state.NewMulti(msg, sc.RunDirPath.String()), flagShort, flagJSON)
|
||||||
return errSuccess
|
return errSuccess
|
||||||
}).Flag(&flagShort, "short", command.BoolFlag(false), "Print instance id")
|
}).Flag(&flagShort, "short", command.BoolFlag(false), "Print instance id")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Command("version", "Display version information", func(args []string) error {
|
c.Command("version", "Display version information", func(args []string) error { fmt.Println(internal.Version()); return errSuccess })
|
||||||
fmt.Println(internal.Version())
|
c.Command("license", "Show full license text", func(args []string) error { fmt.Println(license); return errSuccess })
|
||||||
return errSuccess
|
c.Command("template", "Produce a config template", func(args []string) error { printJSON(os.Stdout, false, hst.Template()); return errSuccess })
|
||||||
})
|
c.Command("help", "Show this help message", func([]string) error { c.PrintHelp(); return errSuccess })
|
||||||
|
|
||||||
c.Command("license", "Show full license text", func(args []string) error {
|
|
||||||
fmt.Println(license)
|
|
||||||
return errSuccess
|
|
||||||
})
|
|
||||||
|
|
||||||
c.Command("template", "Produce a config template", func(args []string) error {
|
|
||||||
printJSON(os.Stdout, false, hst.Template())
|
|
||||||
return errSuccess
|
|
||||||
})
|
|
||||||
|
|
||||||
c.Command("help", "Show this help message", func([]string) error {
|
|
||||||
c.PrintHelp()
|
|
||||||
return errSuccess
|
|
||||||
})
|
|
||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"hakurei.app/command"
|
"hakurei.app/command"
|
||||||
|
"hakurei.app/container"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHelp(t *testing.T) {
|
func TestHelp(t *testing.T) {
|
||||||
@@ -20,8 +21,8 @@ func TestHelp(t *testing.T) {
|
|||||||
Usage: hakurei [-h | --help] [-v] [--json] COMMAND [OPTIONS]
|
Usage: hakurei [-h | --help] [-v] [--json] COMMAND [OPTIONS]
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
app Load app from configuration file
|
app Load and start container from configuration file
|
||||||
run Configure and start a permissive default sandbox
|
run Configure and start a permissive container
|
||||||
show Show live or local app configuration
|
show Show live or local app configuration
|
||||||
ps List active instances
|
ps List active instances
|
||||||
version Display version information
|
version Display version information
|
||||||
@@ -68,7 +69,7 @@ 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) {
|
||||||
out := new(bytes.Buffer)
|
out := new(bytes.Buffer)
|
||||||
c := buildCommand(t.Context(), out)
|
c := buildCommand(t.Context(), container.NewMsg(nil), new(earlyHardeningErrs), out)
|
||||||
if err := c.Parse(tc.args); !errors.Is(err, command.ErrHelp) && !errors.Is(err, flag.ErrHelp) {
|
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,8 +13,6 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/internal"
|
|
||||||
"hakurei.app/internal/hlog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -24,20 +22,20 @@ var (
|
|||||||
license string
|
license string
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { hlog.Prepare("hakurei") }
|
// earlyHardeningErrs are errors collected while setting up early hardening feature.
|
||||||
|
type earlyHardeningErrs struct{ yamaLSM, dumpable error }
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// early init path, skips root check and duplicate PR_SET_DUMPABLE
|
// early init path, skips root check and duplicate PR_SET_DUMPABLE
|
||||||
container.TryArgv0(hlog.Output{}, hlog.Prepare, internal.InstallOutput)
|
container.TryArgv0(nil)
|
||||||
|
|
||||||
if err := container.SetPtracer(0); err != nil {
|
log.SetPrefix("hakurei: ")
|
||||||
hlog.Verbosef("cannot enable ptrace protection via Yama LSM: %v", err)
|
log.SetFlags(0)
|
||||||
// not fatal: this program runs as the privileged user
|
msg := container.NewMsg(log.Default())
|
||||||
}
|
|
||||||
|
|
||||||
if err := container.SetDumpable(container.SUID_DUMP_DISABLE); err != nil {
|
early := earlyHardeningErrs{
|
||||||
log.Printf("cannot set SUID_DUMP_DISABLE: %s", err)
|
yamaLSM: container.SetPtracer(0),
|
||||||
// not fatal: this program runs as the privileged user
|
dumpable: container.SetDumpable(container.SUID_DUMP_DISABLE),
|
||||||
}
|
}
|
||||||
|
|
||||||
if os.Geteuid() == 0 {
|
if os.Geteuid() == 0 {
|
||||||
@@ -48,10 +46,10 @@ func main() {
|
|||||||
syscall.SIGINT, syscall.SIGTERM)
|
syscall.SIGINT, syscall.SIGTERM)
|
||||||
defer stop() // unreachable
|
defer stop() // unreachable
|
||||||
|
|
||||||
buildCommand(ctx, os.Stderr).MustParse(os.Args[1:], func(err error) {
|
buildCommand(ctx, msg, &early, os.Stderr).MustParse(os.Args[1:], func(err error) {
|
||||||
hlog.Verbosef("command returned %v", err)
|
msg.Verbosef("command returned %v", err)
|
||||||
if errors.Is(err, errSuccess) {
|
if errors.Is(err, errSuccess) {
|
||||||
hlog.BeforeExit()
|
msg.BeforeExit()
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
// this catches faulty command handlers that fail to return before this point
|
// this catches faulty command handlers that fail to return before this point
|
||||||
|
|||||||
@@ -10,20 +10,20 @@ 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/internal/hlog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func tryPath(name string) (config *hst.Config) {
|
func tryPath(msg container.Msg, name string) (config *hst.Config) {
|
||||||
var r io.Reader
|
var r io.Reader
|
||||||
config = new(hst.Config)
|
config = new(hst.Config)
|
||||||
|
|
||||||
if name != "-" {
|
if name != "-" {
|
||||||
r = tryFd(name)
|
r = tryFd(msg, name)
|
||||||
if r == nil {
|
if r == nil {
|
||||||
hlog.Verbose("load configuration from file")
|
msg.Verbose("load configuration from file")
|
||||||
|
|
||||||
if f, err := os.Open(name); err != nil {
|
if f, err := os.Open(name); err != nil {
|
||||||
log.Fatalf("cannot access configuration file %q: %s", name, err)
|
log.Fatalf("cannot access configuration file %q: %s", name, err)
|
||||||
@@ -49,14 +49,14 @@ func tryPath(name string) (config *hst.Config) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryFd(name string) io.ReadCloser {
|
func tryFd(msg container.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) {
|
||||||
hlog.Verbosef("name cannot be interpreted as int64: %v", err)
|
msg.Verbosef("name cannot be interpreted as int64: %v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
hlog.Verbosef("trying config stream from %d", v)
|
msg.Verbosef("trying config stream from %d", v)
|
||||||
fd := uintptr(v)
|
fd := uintptr(v)
|
||||||
if _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_GETFD, 0); errno != 0 {
|
if _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_GETFD, 0); errno != 0 {
|
||||||
if errors.Is(errno, syscall.EBADF) {
|
if errors.Is(errno, syscall.EBADF) {
|
||||||
@@ -68,7 +68,7 @@ func tryFd(name string) io.ReadCloser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryShort(name string) (config *hst.Config, entry *state.State) {
|
func tryShort(msg container.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
|
||||||
@@ -86,11 +86,11 @@ func tryShort(name string) (config *hst.Config, entry *state.State) {
|
|||||||
|
|
||||||
// try to match from state store
|
// try to match from state store
|
||||||
if likePrefix && len(name) >= 8 {
|
if likePrefix && len(name) >= 8 {
|
||||||
hlog.Verbose("argument looks like prefix")
|
msg.Verbose("argument looks like prefix")
|
||||||
|
|
||||||
var sc hst.Paths
|
var sc hst.Paths
|
||||||
app.CopyPaths(&sc, new(app.Hsu).MustID())
|
app.CopyPaths().Copy(&sc, new(app.Hsu).MustID())
|
||||||
s := state.NewMulti(sc.RunDirPath.String())
|
s := state.NewMulti(msg, sc.RunDirPath.String())
|
||||||
if entries, err := state.Join(s); err != nil {
|
if entries, err := state.Join(s); err != nil {
|
||||||
log.Printf("cannot join store: %v", err)
|
log.Printf("cannot join store: %v", err)
|
||||||
// drop to fetch from file
|
// drop to fetch from file
|
||||||
@@ -104,7 +104,7 @@ func tryShort(name string) (config *hst.Config, entry *state.State) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
hlog.Verbosef("instance %s skipped", v)
|
msg.Verbosef("instance %s skipped", v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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/system/dbus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func printShowSystem(output io.Writer, short, flagJSON bool) {
|
func printShowSystem(output io.Writer, short, flagJSON bool) {
|
||||||
@@ -22,7 +22,7 @@ func printShowSystem(output io.Writer, short, flagJSON bool) {
|
|||||||
defer t.MustFlush()
|
defer t.MustFlush()
|
||||||
|
|
||||||
info := &hst.Info{User: new(app.Hsu).MustID()}
|
info := &hst.Info{User: new(app.Hsu).MustID()}
|
||||||
app.CopyPaths(&info.Paths, info.User)
|
app.CopyPaths().Copy(&info.Paths, info.User)
|
||||||
|
|
||||||
if flagJSON {
|
if flagJSON {
|
||||||
printJSON(output, short, info)
|
printJSON(output, short, info)
|
||||||
@@ -39,7 +39,9 @@ func printShowSystem(output io.Writer, short, flagJSON bool) {
|
|||||||
func printShowInstance(
|
func printShowInstance(
|
||||||
output io.Writer, now time.Time,
|
output io.Writer, now time.Time,
|
||||||
instance *state.State, config *hst.Config,
|
instance *state.State, config *hst.Config,
|
||||||
short, flagJSON bool) {
|
short, flagJSON bool) (valid bool) {
|
||||||
|
valid = true
|
||||||
|
|
||||||
if flagJSON {
|
if flagJSON {
|
||||||
if instance != nil {
|
if instance != nil {
|
||||||
printJSON(output, short, instance)
|
printJSON(output, short, instance)
|
||||||
@@ -52,8 +54,11 @@ func printShowInstance(
|
|||||||
t := newPrinter(output)
|
t := newPrinter(output)
|
||||||
defer t.MustFlush()
|
defer t.MustFlush()
|
||||||
|
|
||||||
if config.Container == nil {
|
if err := config.Validate(); err != nil {
|
||||||
mustPrint(output, "Warning: this configuration uses permissive defaults!\n\n")
|
valid = false
|
||||||
|
if m, ok := container.GetErrorMessage(err); ok {
|
||||||
|
mustPrint(output, "Error: "+m+"!\n\n")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if instance != nil {
|
if instance != nil {
|
||||||
@@ -73,11 +78,11 @@ func printShowInstance(
|
|||||||
if len(config.Groups) > 0 {
|
if len(config.Groups) > 0 {
|
||||||
t.Printf(" Groups:\t%s\n", strings.Join(config.Groups, ", "))
|
t.Printf(" Groups:\t%s\n", strings.Join(config.Groups, ", "))
|
||||||
}
|
}
|
||||||
if config.Home != nil {
|
|
||||||
t.Printf(" Home:\t%s\n", config.Home)
|
|
||||||
}
|
|
||||||
if config.Container != nil {
|
if config.Container != nil {
|
||||||
params := config.Container
|
params := config.Container
|
||||||
|
if params.Home != nil {
|
||||||
|
t.Printf(" Home:\t%s\n", params.Home)
|
||||||
|
}
|
||||||
if params.Hostname != "" {
|
if params.Hostname != "" {
|
||||||
t.Printf(" Hostname:\t%s\n", params.Hostname)
|
t.Printf(" Hostname:\t%s\n", params.Hostname)
|
||||||
}
|
}
|
||||||
@@ -100,12 +105,12 @@ func printShowInstance(
|
|||||||
}
|
}
|
||||||
t.Printf(" Flags:\t%s\n", strings.Join(flags, " "))
|
t.Printf(" Flags:\t%s\n", strings.Join(flags, " "))
|
||||||
|
|
||||||
if config.Path != nil {
|
if params.Path != nil {
|
||||||
t.Printf(" Path:\t%s\n", config.Path)
|
t.Printf(" Path:\t%s\n", params.Path)
|
||||||
|
}
|
||||||
|
if len(params.Args) > 0 {
|
||||||
|
t.Printf(" Arguments:\t%s\n", strings.Join(params.Args, " "))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if len(config.Args) > 0 {
|
|
||||||
t.Printf(" Arguments:\t%s\n", strings.Join(config.Args, " "))
|
|
||||||
}
|
}
|
||||||
t.Printf("\n")
|
t.Printf("\n")
|
||||||
|
|
||||||
@@ -114,6 +119,7 @@ func printShowInstance(
|
|||||||
t.Printf("Filesystem\n")
|
t.Printf("Filesystem\n")
|
||||||
for _, f := range config.Container.Filesystem {
|
for _, f := range config.Container.Filesystem {
|
||||||
if !f.Valid() {
|
if !f.Valid() {
|
||||||
|
valid = false
|
||||||
t.Println(" <invalid>")
|
t.Println(" <invalid>")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -133,7 +139,7 @@ func printShowInstance(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
printDBus := func(c *dbus.Config) {
|
printDBus := func(c *hst.BusConfig) {
|
||||||
t.Printf(" Filter:\t%v\n", c.Filter)
|
t.Printf(" Filter:\t%v\n", c.Filter)
|
||||||
if len(c.See) > 0 {
|
if len(c.See) > 0 {
|
||||||
t.Printf(" See:\t%q\n", c.See)
|
t.Printf(" See:\t%q\n", c.See)
|
||||||
@@ -161,6 +167,8 @@ func printShowInstance(
|
|||||||
printDBus(config.SystemBus)
|
printDBus(config.SystemBus)
|
||||||
t.Printf("\n")
|
t.Printf("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func printPs(output io.Writer, now time.Time, s state.Store, short, flagJSON bool) {
|
func printPs(output io.Writer, now time.Time, s state.Store, short, flagJSON bool) {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
|
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/app/state"
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/system/dbus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -27,13 +26,14 @@ var (
|
|||||||
testAppTime = time.Unix(0, 9).UTC()
|
testAppTime = time.Unix(0, 9).UTC()
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_printShowInstance(t *testing.T) {
|
func TestPrintShowInstance(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
instance *state.State
|
instance *state.State
|
||||||
config *hst.Config
|
config *hst.Config
|
||||||
short, json bool
|
short, json bool
|
||||||
want string
|
want string
|
||||||
|
valid bool
|
||||||
}{
|
}{
|
||||||
{"config", nil, hst.Template(), false, false, `App
|
{"config", nil, hst.Template(), false, false, `App
|
||||||
Identity: 9 (org.chromium.Chromium)
|
Identity: 9 (org.chromium.Chromium)
|
||||||
@@ -71,21 +71,25 @@ System bus
|
|||||||
Filter: true
|
Filter: true
|
||||||
Talk: ["org.bluez" "org.freedesktop.Avahi" "org.freedesktop.UPower"]
|
Talk: ["org.bluez" "org.freedesktop.Avahi" "org.freedesktop.UPower"]
|
||||||
|
|
||||||
`},
|
`, true},
|
||||||
{"config pd", nil, new(hst.Config), false, false, `Warning: this configuration uses permissive defaults!
|
{"config pd", nil, new(hst.Config), false, false, `Error: configuration missing container state!
|
||||||
|
|
||||||
App
|
App
|
||||||
Identity: 0
|
Identity: 0
|
||||||
Enablements: (no enablements)
|
Enablements: (no enablements)
|
||||||
|
|
||||||
`},
|
`, false},
|
||||||
{"config flag none", nil, &hst.Config{Container: new(hst.ContainerConfig)}, false, false, `App
|
{"config flag none", nil, &hst.Config{Container: new(hst.ContainerConfig)}, false, false, `Error: container configuration missing path to home directory!
|
||||||
|
|
||||||
|
App
|
||||||
Identity: 0
|
Identity: 0
|
||||||
Enablements: (no enablements)
|
Enablements: (no enablements)
|
||||||
Flags: none
|
Flags: none
|
||||||
|
|
||||||
`},
|
`, false},
|
||||||
{"config nil entries", nil, &hst.Config{Container: &hst.ContainerConfig{Filesystem: make([]hst.FilesystemConfigJSON, 1)}, ExtraPerms: make([]*hst.ExtraPermConfig, 1)}, false, false, `App
|
{"config nil entries", nil, &hst.Config{Container: &hst.ContainerConfig{Filesystem: make([]hst.FilesystemConfigJSON, 1)}, ExtraPerms: make([]*hst.ExtraPermConfig, 1)}, false, false, `Error: container configuration missing path to home directory!
|
||||||
|
|
||||||
|
App
|
||||||
Identity: 0
|
Identity: 0
|
||||||
Enablements: (no enablements)
|
Enablements: (no enablements)
|
||||||
Flags: none
|
Flags: none
|
||||||
@@ -95,8 +99,8 @@ Filesystem
|
|||||||
|
|
||||||
Extra ACL
|
Extra ACL
|
||||||
|
|
||||||
`},
|
`, false},
|
||||||
{"config pd dbus see", nil, &hst.Config{SessionBus: &dbus.Config{See: []string{"org.example.test"}}}, false, false, `Warning: this configuration uses permissive defaults!
|
{"config pd dbus see", nil, &hst.Config{SessionBus: &hst.BusConfig{See: []string{"org.example.test"}}}, false, false, `Error: configuration missing container state!
|
||||||
|
|
||||||
App
|
App
|
||||||
Identity: 0
|
Identity: 0
|
||||||
@@ -106,7 +110,7 @@ Session bus
|
|||||||
Filter: false
|
Filter: false
|
||||||
See: ["org.example.test"]
|
See: ["org.example.test"]
|
||||||
|
|
||||||
`},
|
`, false},
|
||||||
|
|
||||||
{"instance", testState, hst.Template(), false, false, `State
|
{"instance", testState, hst.Template(), false, false, `State
|
||||||
Instance: 8e2c76b066dabe574cf073bdb46eb5c1 (3735928559)
|
Instance: 8e2c76b066dabe574cf073bdb46eb5c1 (3735928559)
|
||||||
@@ -148,8 +152,8 @@ System bus
|
|||||||
Filter: true
|
Filter: true
|
||||||
Talk: ["org.bluez" "org.freedesktop.Avahi" "org.freedesktop.UPower"]
|
Talk: ["org.bluez" "org.freedesktop.Avahi" "org.freedesktop.UPower"]
|
||||||
|
|
||||||
`},
|
`, true},
|
||||||
{"instance pd", testState, new(hst.Config), false, false, `Warning: this configuration uses permissive defaults!
|
{"instance pd", testState, new(hst.Config), false, false, `Error: configuration missing container state!
|
||||||
|
|
||||||
State
|
State
|
||||||
Instance: 8e2c76b066dabe574cf073bdb46eb5c1 (3735928559)
|
Instance: 8e2c76b066dabe574cf073bdb46eb5c1 (3735928559)
|
||||||
@@ -159,10 +163,10 @@ App
|
|||||||
Identity: 0
|
Identity: 0
|
||||||
Enablements: (no enablements)
|
Enablements: (no enablements)
|
||||||
|
|
||||||
`},
|
`, false},
|
||||||
|
|
||||||
{"json nil", nil, nil, false, true, `null
|
{"json nil", nil, nil, false, true, `null
|
||||||
`},
|
`, true},
|
||||||
{"json instance", testState, nil, false, true, `{
|
{"json instance", testState, nil, false, true, `{
|
||||||
"instance": [
|
"instance": [
|
||||||
142,
|
142,
|
||||||
@@ -185,14 +189,6 @@ App
|
|||||||
"pid": 3735928559,
|
"pid": 3735928559,
|
||||||
"config": {
|
"config": {
|
||||||
"id": "org.chromium.Chromium",
|
"id": "org.chromium.Chromium",
|
||||||
"path": "/run/current-system/sw/bin/chromium",
|
|
||||||
"args": [
|
|
||||||
"chromium",
|
|
||||||
"--ignore-gpu-blocklist",
|
|
||||||
"--disable-smooth-scrolling",
|
|
||||||
"--enable-features=UseOzonePlatform",
|
|
||||||
"--ozone-platform=wayland"
|
|
||||||
],
|
|
||||||
"enablements": {
|
"enablements": {
|
||||||
"wayland": true,
|
"wayland": true,
|
||||||
"dbus": true,
|
"dbus": true,
|
||||||
@@ -234,9 +230,6 @@ App
|
|||||||
"broadcast": null,
|
"broadcast": null,
|
||||||
"filter": true
|
"filter": true
|
||||||
},
|
},
|
||||||
"username": "chronos",
|
|
||||||
"shell": "/run/current-system/sw/bin/zsh",
|
|
||||||
"home": "/data/data/org.chromium.Chromium",
|
|
||||||
"extra_perms": [
|
"extra_perms": [
|
||||||
{
|
{
|
||||||
"ensure": true,
|
"ensure": true,
|
||||||
@@ -259,8 +252,6 @@ App
|
|||||||
"container": {
|
"container": {
|
||||||
"hostname": "localhost",
|
"hostname": "localhost",
|
||||||
"wait_delay": -1,
|
"wait_delay": -1,
|
||||||
"seccomp_flags": 1,
|
|
||||||
"seccomp_presets": 1,
|
|
||||||
"seccomp_compat": true,
|
"seccomp_compat": true,
|
||||||
"devel": true,
|
"devel": true,
|
||||||
"userns": true,
|
"userns": true,
|
||||||
@@ -333,22 +324,25 @@ App
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"username": "chronos",
|
||||||
|
"shell": "/run/current-system/sw/bin/zsh",
|
||||||
|
"home": "/data/data/org.chromium.Chromium",
|
||||||
|
"path": "/run/current-system/sw/bin/chromium",
|
||||||
|
"args": [
|
||||||
|
"chromium",
|
||||||
|
"--ignore-gpu-blocklist",
|
||||||
|
"--disable-smooth-scrolling",
|
||||||
|
"--enable-features=UseOzonePlatform",
|
||||||
|
"--ozone-platform=wayland"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"time": "1970-01-01T00:00:00.000000009Z"
|
"time": "1970-01-01T00:00:00.000000009Z"
|
||||||
}
|
}
|
||||||
`},
|
`, true},
|
||||||
{"json config", nil, hst.Template(), false, true, `{
|
{"json config", nil, hst.Template(), false, true, `{
|
||||||
"id": "org.chromium.Chromium",
|
"id": "org.chromium.Chromium",
|
||||||
"path": "/run/current-system/sw/bin/chromium",
|
|
||||||
"args": [
|
|
||||||
"chromium",
|
|
||||||
"--ignore-gpu-blocklist",
|
|
||||||
"--disable-smooth-scrolling",
|
|
||||||
"--enable-features=UseOzonePlatform",
|
|
||||||
"--ozone-platform=wayland"
|
|
||||||
],
|
|
||||||
"enablements": {
|
"enablements": {
|
||||||
"wayland": true,
|
"wayland": true,
|
||||||
"dbus": true,
|
"dbus": true,
|
||||||
@@ -390,9 +384,6 @@ App
|
|||||||
"broadcast": null,
|
"broadcast": null,
|
||||||
"filter": true
|
"filter": true
|
||||||
},
|
},
|
||||||
"username": "chronos",
|
|
||||||
"shell": "/run/current-system/sw/bin/zsh",
|
|
||||||
"home": "/data/data/org.chromium.Chromium",
|
|
||||||
"extra_perms": [
|
"extra_perms": [
|
||||||
{
|
{
|
||||||
"ensure": true,
|
"ensure": true,
|
||||||
@@ -415,8 +406,6 @@ App
|
|||||||
"container": {
|
"container": {
|
||||||
"hostname": "localhost",
|
"hostname": "localhost",
|
||||||
"wait_delay": -1,
|
"wait_delay": -1,
|
||||||
"seccomp_flags": 1,
|
|
||||||
"seccomp_presets": 1,
|
|
||||||
"seccomp_compat": true,
|
"seccomp_compat": true,
|
||||||
"devel": true,
|
"devel": true,
|
||||||
"userns": true,
|
"userns": true,
|
||||||
@@ -489,26 +478,39 @@ App
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"username": "chronos",
|
||||||
|
"shell": "/run/current-system/sw/bin/zsh",
|
||||||
|
"home": "/data/data/org.chromium.Chromium",
|
||||||
|
"path": "/run/current-system/sw/bin/chromium",
|
||||||
|
"args": [
|
||||||
|
"chromium",
|
||||||
|
"--ignore-gpu-blocklist",
|
||||||
|
"--disable-smooth-scrolling",
|
||||||
|
"--enable-features=UseOzonePlatform",
|
||||||
|
"--ozone-platform=wayland"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`},
|
`, true},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
output := new(strings.Builder)
|
output := new(strings.Builder)
|
||||||
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 {
|
||||||
t.Errorf("printShowInstance: got\n%s\nwant\n%s",
|
t.Errorf("printShowInstance: \n%s\nwant\n%s", got, tc.want)
|
||||||
got, tc.want)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if gotValid != tc.valid {
|
||||||
|
t.Errorf("printShowInstance: valid = %v, want %v", gotValid, tc.valid)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_printPs(t *testing.T) {
|
func TestPrintPs(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
entries state.Entries
|
entries state.Entries
|
||||||
@@ -551,14 +553,6 @@ func Test_printPs(t *testing.T) {
|
|||||||
"pid": 3735928559,
|
"pid": 3735928559,
|
||||||
"config": {
|
"config": {
|
||||||
"id": "org.chromium.Chromium",
|
"id": "org.chromium.Chromium",
|
||||||
"path": "/run/current-system/sw/bin/chromium",
|
|
||||||
"args": [
|
|
||||||
"chromium",
|
|
||||||
"--ignore-gpu-blocklist",
|
|
||||||
"--disable-smooth-scrolling",
|
|
||||||
"--enable-features=UseOzonePlatform",
|
|
||||||
"--ozone-platform=wayland"
|
|
||||||
],
|
|
||||||
"enablements": {
|
"enablements": {
|
||||||
"wayland": true,
|
"wayland": true,
|
||||||
"dbus": true,
|
"dbus": true,
|
||||||
@@ -600,9 +594,6 @@ func Test_printPs(t *testing.T) {
|
|||||||
"broadcast": null,
|
"broadcast": null,
|
||||||
"filter": true
|
"filter": true
|
||||||
},
|
},
|
||||||
"username": "chronos",
|
|
||||||
"shell": "/run/current-system/sw/bin/zsh",
|
|
||||||
"home": "/data/data/org.chromium.Chromium",
|
|
||||||
"extra_perms": [
|
"extra_perms": [
|
||||||
{
|
{
|
||||||
"ensure": true,
|
"ensure": true,
|
||||||
@@ -625,8 +616,6 @@ func Test_printPs(t *testing.T) {
|
|||||||
"container": {
|
"container": {
|
||||||
"hostname": "localhost",
|
"hostname": "localhost",
|
||||||
"wait_delay": -1,
|
"wait_delay": -1,
|
||||||
"seccomp_flags": 1,
|
|
||||||
"seccomp_presets": 1,
|
|
||||||
"seccomp_compat": true,
|
"seccomp_compat": true,
|
||||||
"devel": true,
|
"devel": true,
|
||||||
"userns": true,
|
"userns": true,
|
||||||
@@ -699,6 +688,17 @@ func Test_printPs(t *testing.T) {
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"username": "chronos",
|
||||||
|
"shell": "/run/current-system/sw/bin/zsh",
|
||||||
|
"home": "/data/data/org.chromium.Chromium",
|
||||||
|
"path": "/run/current-system/sw/bin/chromium",
|
||||||
|
"args": [
|
||||||
|
"chromium",
|
||||||
|
"--ignore-gpu-blocklist",
|
||||||
|
"--disable-smooth-scrolling",
|
||||||
|
"--enable-features=UseOzonePlatform",
|
||||||
|
"--ozone-platform=wayland"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,10 +5,9 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/fhs"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/system/dbus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type appInfo struct {
|
type appInfo struct {
|
||||||
@@ -38,9 +37,9 @@ type appInfo struct {
|
|||||||
// passed through to [hst.Config]
|
// passed through to [hst.Config]
|
||||||
DirectWayland bool `json:"direct_wayland,omitempty"`
|
DirectWayland bool `json:"direct_wayland,omitempty"`
|
||||||
// passed through to [hst.Config]
|
// passed through to [hst.Config]
|
||||||
SystemBus *dbus.Config `json:"system_bus,omitempty"`
|
SystemBus *hst.BusConfig `json:"system_bus,omitempty"`
|
||||||
// passed through to [hst.Config]
|
// passed through to [hst.Config]
|
||||||
SessionBus *dbus.Config `json:"session_bus,omitempty"`
|
SessionBus *hst.BusConfig `json:"session_bus,omitempty"`
|
||||||
// passed through to [hst.Config]
|
// passed through to [hst.Config]
|
||||||
Enablements *hst.Enablements `json:"enablements,omitempty"`
|
Enablements *hst.Enablements `json:"enablements,omitempty"`
|
||||||
|
|
||||||
@@ -56,30 +55,23 @@ type appInfo struct {
|
|||||||
// store path to nixGL source
|
// store path to nixGL source
|
||||||
NixGL string `json:"nix_gl,omitempty"`
|
NixGL string `json:"nix_gl,omitempty"`
|
||||||
// store path to activate-and-exec script
|
// store path to activate-and-exec script
|
||||||
Launcher *container.Absolute `json:"launcher"`
|
Launcher *check.Absolute `json:"launcher"`
|
||||||
// store path to /run/current-system
|
// store path to /run/current-system
|
||||||
CurrentSystem *container.Absolute `json:"current_system"`
|
CurrentSystem *check.Absolute `json:"current_system"`
|
||||||
// store path to home-manager activation package
|
// store path to home-manager activation package
|
||||||
ActivationPackage string `json:"activation_package"`
|
ActivationPackage string `json:"activation_package"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *appInfo) toHst(pathSet *appPathSet, pathname *container.Absolute, argv []string, flagDropShell bool) *hst.Config {
|
func (app *appInfo) toHst(pathSet *appPathSet, pathname *check.Absolute, argv []string, flagDropShell bool) *hst.Config {
|
||||||
config := &hst.Config{
|
config := &hst.Config{
|
||||||
ID: app.ID,
|
ID: app.ID,
|
||||||
|
|
||||||
Path: pathname,
|
|
||||||
Args: argv,
|
|
||||||
|
|
||||||
Enablements: app.Enablements,
|
Enablements: app.Enablements,
|
||||||
|
|
||||||
SystemBus: app.SystemBus,
|
SystemBus: app.SystemBus,
|
||||||
SessionBus: app.SessionBus,
|
SessionBus: app.SessionBus,
|
||||||
DirectWayland: app.DirectWayland,
|
DirectWayland: app.DirectWayland,
|
||||||
|
|
||||||
Username: "hakurei",
|
|
||||||
Shell: pathShell,
|
|
||||||
Home: pathDataData.Append(app.ID),
|
|
||||||
|
|
||||||
Identity: app.Identity,
|
Identity: app.Identity,
|
||||||
Groups: app.Groups,
|
Groups: app.Groups,
|
||||||
|
|
||||||
@@ -92,33 +84,35 @@ func (app *appInfo) toHst(pathSet *appPathSet, pathname *container.Absolute, arg
|
|||||||
Device: app.Device,
|
Device: app.Device,
|
||||||
Tty: app.Tty || flagDropShell,
|
Tty: app.Tty || flagDropShell,
|
||||||
MapRealUID: app.MapRealUID,
|
MapRealUID: app.MapRealUID,
|
||||||
|
Multiarch: app.Multiarch,
|
||||||
Filesystem: []hst.FilesystemConfigJSON{
|
Filesystem: []hst.FilesystemConfigJSON{
|
||||||
{FilesystemConfig: &hst.FSBind{Target: container.AbsFHSEtc, Source: pathSet.cacheDir.Append("etc"), Special: true}},
|
{FilesystemConfig: &hst.FSBind{Target: fhs.AbsEtc, Source: pathSet.cacheDir.Append("etc"), Special: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: pathSet.nixPath.Append("store"), Target: pathNixStore}},
|
{FilesystemConfig: &hst.FSBind{Source: pathSet.nixPath.Append("store"), Target: pathNixStore}},
|
||||||
{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: container.AbsFHSUsrBin, 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.AbsTmp.Append("app")}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSEtc.Append("resolv.conf"), Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsEtc.Append("resolv.conf"), Optional: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSSys.Append("block"), Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("block"), Optional: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSSys.Append("bus"), Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("bus"), Optional: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSSys.Append("class"), Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("class"), Optional: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSSys.Append("dev"), Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("dev"), Optional: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSSys.Append("devices"), Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("devices"), Optional: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Target: pathDataData.Append(app.ID), Source: pathSet.homeDir, Write: true, Ensure: true}},
|
{FilesystemConfig: &hst.FSBind{Target: pathDataData.Append(app.ID), Source: pathSet.homeDir, Write: true, Ensure: true}},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Username: "hakurei",
|
||||||
|
Shell: pathShell,
|
||||||
|
Home: pathDataData.Append(app.ID),
|
||||||
|
|
||||||
|
Path: pathname,
|
||||||
|
Args: argv,
|
||||||
},
|
},
|
||||||
ExtraPerms: []*hst.ExtraPermConfig{
|
ExtraPerms: []*hst.ExtraPermConfig{
|
||||||
{Path: dataHome, Execute: true},
|
{Path: dataHome, Execute: true},
|
||||||
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
|
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if app.Multiarch {
|
|
||||||
config.Container.SeccompFlags |= seccomp.AllowMultiarch
|
|
||||||
}
|
|
||||||
if app.Bluetooth {
|
|
||||||
config.Container.SeccompFlags |= seccomp.AllowBluetooth
|
|
||||||
}
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,23 +12,24 @@ import (
|
|||||||
|
|
||||||
"hakurei.app/command"
|
"hakurei.app/command"
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
"hakurei.app/container/fhs"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal"
|
|
||||||
"hakurei.app/internal/hlog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errSuccess = errors.New("success")
|
errSuccess = errors.New("success")
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func main() {
|
||||||
hlog.Prepare("hpkg")
|
log.SetPrefix("hpkg: ")
|
||||||
|
log.SetFlags(0)
|
||||||
|
msg := container.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)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if os.Geteuid() == 0 {
|
if os.Geteuid() == 0 {
|
||||||
log.Fatal("this program must not run as root")
|
log.Fatal("this program must not run as root")
|
||||||
}
|
}
|
||||||
@@ -41,7 +42,7 @@ func main() {
|
|||||||
flagVerbose bool
|
flagVerbose bool
|
||||||
flagDropShell bool
|
flagDropShell bool
|
||||||
)
|
)
|
||||||
c := command.New(os.Stderr, log.Printf, "hpkg", func([]string) error { internal.InstallOutput(flagVerbose); return nil }).
|
c := command.New(os.Stderr, log.Printf, "hpkg", func([]string) error { msg.SwapVerbose(flagVerbose); return nil }).
|
||||||
Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console").
|
Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console").
|
||||||
Flag(&flagDropShell, "s", command.BoolFlag(false), "Drop to a shell in place of next hakurei action")
|
Flag(&flagDropShell, "s", command.BoolFlag(false), "Drop to a shell in place of next hakurei action")
|
||||||
|
|
||||||
@@ -80,22 +81,22 @@ func main() {
|
|||||||
Extract package and set up for cleanup.
|
Extract package and set up for cleanup.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var workDir *container.Absolute
|
var workDir *check.Absolute
|
||||||
if p, err := os.MkdirTemp("", "hpkg.*"); err != nil {
|
if p, err := os.MkdirTemp("", "hpkg.*"); err != nil {
|
||||||
log.Printf("cannot create temporary directory: %v", err)
|
log.Printf("cannot create temporary directory: %v", err)
|
||||||
return err
|
return err
|
||||||
} else if workDir, err = container.NewAbs(p); err != nil {
|
} else if workDir, err = check.NewAbs(p); err != nil {
|
||||||
log.Printf("invalid temporary directory: %v", err)
|
log.Printf("invalid temporary directory: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
cleanup := func() {
|
cleanup := func() {
|
||||||
// should be faster than a native implementation
|
// should be faster than a native implementation
|
||||||
mustRun(chmod, "-R", "+w", workDir.String())
|
mustRun(msg, chmod, "-R", "+w", workDir.String())
|
||||||
mustRun(rm, "-rf", workDir.String())
|
mustRun(msg, rm, "-rf", workDir.String())
|
||||||
}
|
}
|
||||||
beforeRunFail.Store(&cleanup)
|
beforeRunFail.Store(&cleanup)
|
||||||
|
|
||||||
mustRun(tar, "-C", workDir.String(), "-xf", pkgPath)
|
mustRun(msg, tar, "-C", workDir.String(), "-xf", pkgPath)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Parse bundle and app metadata, do pre-install checks.
|
Parse bundle and app metadata, do pre-install checks.
|
||||||
@@ -148,10 +149,10 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// sec: should compare version string
|
// sec: should compare version string
|
||||||
hlog.Verbosef("installing application %q version %q over local %q",
|
msg.Verbosef("installing application %q version %q over local %q",
|
||||||
bundle.ID, bundle.Version, a.Version)
|
bundle.ID, bundle.Version, a.Version)
|
||||||
} else {
|
} else {
|
||||||
hlog.Verbosef("application %q clean installation", bundle.ID)
|
msg.Verbosef("application %q clean installation", bundle.ID)
|
||||||
// sec: should install credentials
|
// sec: should install credentials
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +160,7 @@ func main() {
|
|||||||
Setup steps for files owned by the target user.
|
Setup steps for files owned by the target user.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
withCacheDir(ctx, "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.Tmp + "/bundle",
|
||||||
// replace inner /etc
|
// replace inner /etc
|
||||||
@@ -181,7 +182,7 @@ func main() {
|
|||||||
}, workDir, bundle, pathSet, flagDropShell, cleanup)
|
}, workDir, bundle, pathSet, flagDropShell, cleanup)
|
||||||
|
|
||||||
if bundle.GPU {
|
if bundle.GPU {
|
||||||
withCacheDir(ctx, "mesa-wrappers", []string{
|
withCacheDir(ctx, msg, "mesa-wrappers", []string{
|
||||||
// link nixGL mesa wrappers
|
// link nixGL mesa wrappers
|
||||||
"mkdir -p nix/.nixGL",
|
"mkdir -p nix/.nixGL",
|
||||||
"ln -s " + bundle.Mesa + "/bin/nixGLIntel nix/.nixGL/nixGL",
|
"ln -s " + bundle.Mesa + "/bin/nixGLIntel nix/.nixGL/nixGL",
|
||||||
@@ -193,7 +194,7 @@ func main() {
|
|||||||
Activate home-manager generation.
|
Activate home-manager generation.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
withNixDaemon(ctx, "activate", []string{
|
withNixDaemon(ctx, msg, "activate", []string{
|
||||||
// clean up broken links
|
// clean up broken links
|
||||||
"mkdir -p .local/state/{nix,home-manager}",
|
"mkdir -p .local/state/{nix,home-manager}",
|
||||||
"chmod -R +w .local/state/{nix,home-manager}",
|
"chmod -R +w .local/state/{nix,home-manager}",
|
||||||
@@ -261,7 +262,7 @@ func main() {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
if a.GPU && flagAutoDrivers {
|
if a.GPU && flagAutoDrivers {
|
||||||
withNixDaemon(ctx, "nix-gl", []string{
|
withNixDaemon(ctx, msg, "nix-gl", []string{
|
||||||
"mkdir -p /nix/.nixGL/auto",
|
"mkdir -p /nix/.nixGL/auto",
|
||||||
"rm -rf /nix/.nixGL/auto",
|
"rm -rf /nix/.nixGL/auto",
|
||||||
"export NIXPKGS_ALLOW_UNFREE=1",
|
"export NIXPKGS_ALLOW_UNFREE=1",
|
||||||
@@ -275,12 +276,12 @@ func main() {
|
|||||||
"path:" + a.NixGL + "#nixVulkanNvidia",
|
"path:" + a.NixGL + "#nixVulkanNvidia",
|
||||||
}, true, func(config *hst.Config) *hst.Config {
|
}, true, func(config *hst.Config) *hst.Config {
|
||||||
config.Container.Filesystem = append(config.Container.Filesystem, []hst.FilesystemConfigJSON{
|
config.Container.Filesystem = append(config.Container.Filesystem, []hst.FilesystemConfigJSON{
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSEtc.Append("resolv.conf"), Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsEtc.Append("resolv.conf"), Optional: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSSys.Append("block"), Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("block"), Optional: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSSys.Append("bus"), Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("bus"), Optional: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSSys.Append("class"), Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("class"), Optional: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSSys.Append("dev"), Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("dev"), Optional: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSSys.Append("devices"), Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("devices"), Optional: true}},
|
||||||
}...)
|
}...)
|
||||||
appendGPUFilesystem(config)
|
appendGPUFilesystem(config)
|
||||||
return config
|
return config
|
||||||
@@ -316,7 +317,7 @@ func main() {
|
|||||||
Spawn app.
|
Spawn app.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
mustRunApp(ctx, config, func() {})
|
mustRunApp(ctx, msg, config, func() {})
|
||||||
return errSuccess
|
return errSuccess
|
||||||
}).
|
}).
|
||||||
Flag(&flagDropShellNixGL, "s", command.BoolFlag(false), "Drop to a shell on nixGL build").
|
Flag(&flagDropShellNixGL, "s", command.BoolFlag(false), "Drop to a shell on nixGL build").
|
||||||
@@ -324,9 +325,9 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.MustParse(os.Args[1:], func(err error) {
|
c.MustParse(os.Args[1:], func(err error) {
|
||||||
hlog.Verbosef("command returned %v", err)
|
msg.Verbosef("command returned %v", err)
|
||||||
if errors.Is(err, errSuccess) {
|
if errors.Is(err, errSuccess) {
|
||||||
hlog.BeforeExit()
|
msg.BeforeExit()
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,35 +8,36 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
"hakurei.app/container/fhs"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/hlog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const bash = "bash"
|
const bash = "bash"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dataHome *container.Absolute
|
dataHome *check.Absolute
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// dataHome
|
// dataHome
|
||||||
if a, err := container.NewAbs(os.Getenv("HAKUREI_DATA_HOME")); err == nil {
|
if a, err := check.NewAbs(os.Getenv("HAKUREI_DATA_HOME")); err == nil {
|
||||||
dataHome = a
|
dataHome = a
|
||||||
} else {
|
} else {
|
||||||
dataHome = container.AbsFHSVarLib.Append("hakurei/" + strconv.Itoa(os.Getuid()))
|
dataHome = fhs.AbsVarLib.Append("hakurei/" + strconv.Itoa(os.Getuid()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
pathBin = container.AbsFHSRoot.Append("bin")
|
pathBin = fhs.AbsRoot.Append("bin")
|
||||||
|
|
||||||
pathNix = container.MustAbs("/nix/")
|
pathNix = check.MustAbs("/nix/")
|
||||||
pathNixStore = pathNix.Append("store/")
|
pathNixStore = pathNix.Append("store/")
|
||||||
pathCurrentSystem = container.AbsFHSRun.Append("current-system")
|
pathCurrentSystem = fhs.AbsRun.Append("current-system")
|
||||||
pathSwBin = pathCurrentSystem.Append("sw/bin/")
|
pathSwBin = pathCurrentSystem.Append("sw/bin/")
|
||||||
pathShell = pathSwBin.Append(bash)
|
pathShell = pathSwBin.Append(bash)
|
||||||
|
|
||||||
pathData = container.MustAbs("/data")
|
pathData = check.MustAbs("/data")
|
||||||
pathDataData = pathData.Append("data")
|
pathDataData = pathData.Append("data")
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -51,8 +52,8 @@ func lookPath(file string) string {
|
|||||||
|
|
||||||
var beforeRunFail = new(atomic.Pointer[func()])
|
var beforeRunFail = new(atomic.Pointer[func()])
|
||||||
|
|
||||||
func mustRun(name string, arg ...string) {
|
func mustRun(msg container.Msg, name string, arg ...string) {
|
||||||
hlog.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
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
@@ -65,15 +66,15 @@ func mustRun(name string, arg ...string) {
|
|||||||
|
|
||||||
type appPathSet struct {
|
type appPathSet struct {
|
||||||
// ${dataHome}/${id}
|
// ${dataHome}/${id}
|
||||||
baseDir *container.Absolute
|
baseDir *check.Absolute
|
||||||
// ${baseDir}/app
|
// ${baseDir}/app
|
||||||
metaPath *container.Absolute
|
metaPath *check.Absolute
|
||||||
// ${baseDir}/files
|
// ${baseDir}/files
|
||||||
homeDir *container.Absolute
|
homeDir *check.Absolute
|
||||||
// ${baseDir}/cache
|
// ${baseDir}/cache
|
||||||
cacheDir *container.Absolute
|
cacheDir *check.Absolute
|
||||||
// ${baseDir}/cache/nix
|
// ${baseDir}/cache/nix
|
||||||
nixPath *container.Absolute
|
nixPath *check.Absolute
|
||||||
}
|
}
|
||||||
|
|
||||||
func pathSetByApp(id string) *appPathSet {
|
func pathSetByApp(id string) *appPathSet {
|
||||||
@@ -89,28 +90,28 @@ func pathSetByApp(id string) *appPathSet {
|
|||||||
func appendGPUFilesystem(config *hst.Config) {
|
func appendGPUFilesystem(config *hst.Config) {
|
||||||
config.Container.Filesystem = append(config.Container.Filesystem, []hst.FilesystemConfigJSON{
|
config.Container.Filesystem = append(config.Container.Filesystem, []hst.FilesystemConfigJSON{
|
||||||
// flatpak commit 763a686d874dd668f0236f911de00b80766ffe79
|
// flatpak commit 763a686d874dd668f0236f911de00b80766ffe79
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("dri"), Device: true, Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("dri"), Device: true, Optional: true}},
|
||||||
// mali
|
// mali
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("mali"), Device: true, Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("mali"), Device: true, Optional: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("mali0"), Device: true, Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("mali0"), Device: true, Optional: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("umplock"), Device: true, Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("umplock"), Device: true, Optional: true}},
|
||||||
// nvidia
|
// nvidia
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("nvidiactl"), Device: true, Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidiactl"), Device: true, Optional: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("nvidia-modeset"), Device: true, Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia-modeset"), Device: true, Optional: true}},
|
||||||
// nvidia OpenCL/CUDA
|
// nvidia OpenCL/CUDA
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("nvidia-uvm"), Device: true, Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia-uvm"), Device: true, Optional: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("nvidia-uvm-tools"), Device: true, Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia-uvm-tools"), Device: true, Optional: true}},
|
||||||
|
|
||||||
// flatpak commit d2dff2875bb3b7e2cd92d8204088d743fd07f3ff
|
// flatpak commit d2dff2875bb3b7e2cd92d8204088d743fd07f3ff
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("nvidia0"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("nvidia1"), Device: true, Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia0"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia1"), Device: true, Optional: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("nvidia2"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("nvidia3"), Device: true, Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia2"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia3"), Device: true, Optional: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("nvidia4"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("nvidia5"), Device: true, Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia4"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia5"), Device: true, Optional: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("nvidia6"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("nvidia7"), Device: true, Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia6"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia7"), Device: true, Optional: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("nvidia8"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("nvidia9"), Device: true, Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia8"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia9"), Device: true, Optional: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("nvidia10"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("nvidia11"), Device: true, Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia10"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia11"), Device: true, Optional: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("nvidia12"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("nvidia13"), Device: true, Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia12"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia13"), Device: true, Optional: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("nvidia14"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("nvidia15"), Device: true, Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia14"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia15"), Device: true, Optional: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("nvidia16"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("nvidia17"), Device: true, Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia16"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia17"), Device: true, Optional: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("nvidia18"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("nvidia19"), Device: true, Optional: true}},
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia18"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia19"), Device: true, Optional: true}},
|
||||||
}...)
|
}...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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/internal/hlog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var hakureiPath = internal.MustHakureiPath()
|
var hakureiPath = internal.MustHakureiPath()
|
||||||
|
|
||||||
func mustRunApp(ctx context.Context, config *hst.Config, beforeFail func()) {
|
func mustRunApp(ctx context.Context, msg container.Msg, config *hst.Config, beforeFail func()) {
|
||||||
var (
|
var (
|
||||||
cmd *exec.Cmd
|
cmd *exec.Cmd
|
||||||
st io.WriteCloser
|
st io.WriteCloser
|
||||||
@@ -26,10 +26,10 @@ func mustRunApp(ctx context.Context, config *hst.Config, beforeFail func()) {
|
|||||||
beforeFail()
|
beforeFail()
|
||||||
log.Fatalf("cannot pipe: %v", err)
|
log.Fatalf("cannot pipe: %v", err)
|
||||||
} else {
|
} else {
|
||||||
if hlog.Load() {
|
if msg.IsVerbose() {
|
||||||
cmd = exec.CommandContext(ctx, hakureiPath, "-v", "app", "3")
|
cmd = exec.CommandContext(ctx, hakureiPath.String(), "-v", "app", "3")
|
||||||
} else {
|
} else {
|
||||||
cmd = exec.CommandContext(ctx, hakureiPath, "app", "3")
|
cmd = exec.CommandContext(ctx, hakureiPath.String(), "app", "3")
|
||||||
}
|
}
|
||||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||||
cmd.ExtraFiles = []*os.File{r}
|
cmd.ExtraFiles = []*os.File{r}
|
||||||
@@ -51,7 +51,8 @@ func mustRunApp(ctx context.Context, config *hst.Config, beforeFail func()) {
|
|||||||
var exitError *exec.ExitError
|
var exitError *exec.ExitError
|
||||||
if errors.As(err, &exitError) {
|
if errors.As(err, &exitError) {
|
||||||
beforeFail()
|
beforeFail()
|
||||||
internal.Exit(exitError.ExitCode())
|
msg.BeforeExit()
|
||||||
|
os.Exit(exitError.ExitCode())
|
||||||
} else {
|
} else {
|
||||||
beforeFail()
|
beforeFail()
|
||||||
log.Fatalf("cannot wait: %v", err)
|
log.Fatalf("cannot wait: %v", err)
|
||||||
|
|||||||
@@ -62,11 +62,11 @@ def check_state(name, enablements):
|
|||||||
|
|
||||||
config = instance['config']
|
config = instance['config']
|
||||||
|
|
||||||
if len(config['args']) != 1 or not (config['args'][0].startswith("/nix/store/")) or f"hakurei-{name}-" not in (config['args'][0]):
|
if len(config['container']['args']) != 1 or not (config['container']['args'][0].startswith("/nix/store/")) or f"hakurei-{name}-" not in (config['container']['args'][0]):
|
||||||
raise Exception(f"unexpected args {instance['config']['args']}")
|
raise Exception(f"unexpected args {config['container']['args']}")
|
||||||
|
|
||||||
if config['enablements'] != enablements:
|
if config['enablements'] != enablements:
|
||||||
raise Exception(f"unexpected enablements {instance['config']['enablements']}")
|
raise Exception(f"unexpected enablements {config['enablements']}")
|
||||||
|
|
||||||
|
|
||||||
start_all()
|
start_all()
|
||||||
|
|||||||
@@ -2,38 +2,24 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/check"
|
||||||
|
"hakurei.app/container/fhs"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func withNixDaemon(
|
func withNixDaemon(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
msg container.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(),
|
||||||
) {
|
) {
|
||||||
mustRunAppDropShell(ctx, updateConfig(&hst.Config{
|
mustRunAppDropShell(ctx, msg, updateConfig(&hst.Config{
|
||||||
ID: app.ID,
|
ID: app.ID,
|
||||||
|
|
||||||
Path: pathShell,
|
|
||||||
Args: []string{bash, "-lc", "rm -f /nix/var/nix/daemon-socket/socket && " +
|
|
||||||
// start nix-daemon
|
|
||||||
"nix-daemon --store / & " +
|
|
||||||
// wait for socket to appear
|
|
||||||
"(while [ ! -S /nix/var/nix/daemon-socket/socket ]; do sleep 0.01; done) && " +
|
|
||||||
// create directory so nix stops complaining
|
|
||||||
"mkdir -p /nix/var/nix/profiles/per-user/root/channels && " +
|
|
||||||
strings.Join(command, " && ") +
|
|
||||||
// terminate nix-daemon
|
|
||||||
" && pkill nix-daemon",
|
|
||||||
},
|
|
||||||
|
|
||||||
Username: "hakurei",
|
|
||||||
Shell: pathShell,
|
|
||||||
Home: pathDataData.Append(app.ID),
|
|
||||||
ExtraPerms: []*hst.ExtraPermConfig{
|
ExtraPerms: []*hst.ExtraPermConfig{
|
||||||
{Path: dataHome, Execute: true},
|
{Path: dataHome, Execute: true},
|
||||||
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
|
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
|
||||||
@@ -42,36 +28,48 @@ func withNixDaemon(
|
|||||||
Identity: app.Identity,
|
Identity: app.Identity,
|
||||||
|
|
||||||
Container: &hst.ContainerConfig{
|
Container: &hst.ContainerConfig{
|
||||||
Hostname: formatHostname(app.Name) + "-" + action,
|
Hostname: formatHostname(app.Name) + "-" + action,
|
||||||
Userns: true, // nix sandbox requires userns
|
Userns: true, // nix sandbox requires userns
|
||||||
HostNet: net,
|
HostNet: net,
|
||||||
SeccompFlags: seccomp.AllowMultiarch,
|
Multiarch: true,
|
||||||
Tty: dropShell,
|
Tty: dropShell,
|
||||||
Filesystem: []hst.FilesystemConfigJSON{
|
Filesystem: []hst.FilesystemConfigJSON{
|
||||||
{FilesystemConfig: &hst.FSBind{Target: container.AbsFHSEtc, Source: pathSet.cacheDir.Append("etc"), Special: true}},
|
{FilesystemConfig: &hst.FSBind{Target: fhs.AbsEtc, Source: pathSet.cacheDir.Append("etc"), Special: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: pathSet.nixPath, Target: pathNix, Write: true}},
|
{FilesystemConfig: &hst.FSBind{Source: pathSet.nixPath, Target: pathNix, Write: true}},
|
||||||
{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: container.AbsFHSUsrBin, Linkname: pathSwBin.String()}},
|
{FilesystemConfig: &hst.FSLink{Target: fhs.AbsUsrBin, Linkname: pathSwBin.String()}},
|
||||||
{FilesystemConfig: &hst.FSBind{Target: pathDataData.Append(app.ID), Source: pathSet.homeDir, Write: true, Ensure: true}},
|
{FilesystemConfig: &hst.FSBind{Target: pathDataData.Append(app.ID), Source: pathSet.homeDir, Write: true, Ensure: true}},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Username: "hakurei",
|
||||||
|
Shell: pathShell,
|
||||||
|
Home: pathDataData.Append(app.ID),
|
||||||
|
|
||||||
|
Path: pathShell,
|
||||||
|
Args: []string{bash, "-lc", "rm -f /nix/var/nix/daemon-socket/socket && " +
|
||||||
|
// start nix-daemon
|
||||||
|
"nix-daemon --store / & " +
|
||||||
|
// wait for socket to appear
|
||||||
|
"(while [ ! -S /nix/var/nix/daemon-socket/socket ]; do sleep 0.01; done) && " +
|
||||||
|
// create directory so nix stops complaining
|
||||||
|
"mkdir -p /nix/var/nix/profiles/per-user/root/channels && " +
|
||||||
|
strings.Join(command, " && ") +
|
||||||
|
// terminate nix-daemon
|
||||||
|
" && pkill nix-daemon",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}), dropShell, beforeFail)
|
}), dropShell, beforeFail)
|
||||||
}
|
}
|
||||||
|
|
||||||
func withCacheDir(
|
func withCacheDir(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
action string, command []string, workDir *container.Absolute,
|
msg container.Msg,
|
||||||
|
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, &hst.Config{
|
mustRunAppDropShell(ctx, msg, &hst.Config{
|
||||||
ID: app.ID,
|
ID: app.ID,
|
||||||
|
|
||||||
Path: pathShell,
|
|
||||||
Args: []string{bash, "-lc", strings.Join(command, " && ")},
|
|
||||||
|
|
||||||
Username: "nixos",
|
|
||||||
Shell: pathShell,
|
|
||||||
Home: pathDataData.Append(app.ID, "cache"),
|
|
||||||
ExtraPerms: []*hst.ExtraPermConfig{
|
ExtraPerms: []*hst.ExtraPermConfig{
|
||||||
{Path: dataHome, Execute: true},
|
{Path: dataHome, Execute: true},
|
||||||
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
|
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
|
||||||
@@ -81,28 +79,38 @@ func withCacheDir(
|
|||||||
Identity: app.Identity,
|
Identity: app.Identity,
|
||||||
|
|
||||||
Container: &hst.ContainerConfig{
|
Container: &hst.ContainerConfig{
|
||||||
Hostname: formatHostname(app.Name) + "-" + action,
|
Hostname: formatHostname(app.Name) + "-" + action,
|
||||||
SeccompFlags: seccomp.AllowMultiarch,
|
Multiarch: true,
|
||||||
Tty: dropShell,
|
Tty: dropShell,
|
||||||
Filesystem: []hst.FilesystemConfigJSON{
|
Filesystem: []hst.FilesystemConfigJSON{
|
||||||
{FilesystemConfig: &hst.FSBind{Target: container.AbsFHSEtc, Source: workDir.Append(container.FHSEtc), Special: true}},
|
{FilesystemConfig: &hst.FSBind{Target: fhs.AbsEtc, Source: workDir.Append(fhs.Etc), Special: true}},
|
||||||
{FilesystemConfig: &hst.FSBind{Source: workDir.Append("nix"), Target: pathNix}},
|
{FilesystemConfig: &hst.FSBind{Source: workDir.Append("nix"), Target: pathNix}},
|
||||||
{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: container.AbsFHSUsrBin, 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.AbsTmp.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}},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Username: "nixos",
|
||||||
|
Shell: pathShell,
|
||||||
|
Home: pathDataData.Append(app.ID, "cache"),
|
||||||
|
|
||||||
|
Path: pathShell,
|
||||||
|
Args: []string{bash, "-lc", strings.Join(command, " && ")},
|
||||||
},
|
},
|
||||||
}, dropShell, beforeFail)
|
}, dropShell, beforeFail)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustRunAppDropShell(ctx context.Context, config *hst.Config, dropShell bool, beforeFail func()) {
|
func mustRunAppDropShell(ctx context.Context, msg container.Msg, config *hst.Config, dropShell bool, beforeFail func()) {
|
||||||
if dropShell {
|
if dropShell {
|
||||||
config.Args = []string{bash, "-l"}
|
if config.Container != nil {
|
||||||
mustRunApp(ctx, config, beforeFail)
|
config.Container.Args = []string{bash, "-l"}
|
||||||
|
}
|
||||||
|
mustRunApp(ctx, msg, config, beforeFail)
|
||||||
beforeFail()
|
beforeFail()
|
||||||
internal.Exit(0)
|
msg.BeforeExit()
|
||||||
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
mustRunApp(ctx, config, beforeFail)
|
mustRunApp(ctx, msg, config, beforeFail)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
// minimise imports to avoid inadvertently calling init or global variable functions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -19,6 +21,9 @@ const (
|
|||||||
envGroups = "HAKUREI_GROUPS"
|
envGroups = "HAKUREI_GROUPS"
|
||||||
|
|
||||||
PR_SET_NO_NEW_PRIVS = 0x26
|
PR_SET_NO_NEW_PRIVS = 0x26
|
||||||
|
|
||||||
|
identityMin = 0
|
||||||
|
identityMax = 9999
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -29,6 +34,9 @@ func main() {
|
|||||||
if os.Geteuid() != 0 {
|
if os.Geteuid() != 0 {
|
||||||
log.Fatal("this program must be owned by uid 0 and have the setuid bit set")
|
log.Fatal("this program must be owned by uid 0 and have the setuid bit set")
|
||||||
}
|
}
|
||||||
|
if os.Getegid() != os.Getgid() {
|
||||||
|
log.Fatal("this program must not have the setgid bit set")
|
||||||
|
}
|
||||||
|
|
||||||
puid := os.Getuid()
|
puid := os.Getuid()
|
||||||
if puid == 0 {
|
if puid == 0 {
|
||||||
@@ -91,7 +99,7 @@ func main() {
|
|||||||
// allowed identity range 0 to 9999
|
// allowed identity range 0 to 9999
|
||||||
if as, ok := os.LookupEnv(envIdentity); !ok {
|
if as, ok := os.LookupEnv(envIdentity); !ok {
|
||||||
log.Fatal("HAKUREI_IDENTITY not set")
|
log.Fatal("HAKUREI_IDENTITY not set")
|
||||||
} else if identity, err := parseUint32Fast(as); err != nil || identity < 0 || identity > 9999 {
|
} else if identity, err := parseUint32Fast(as); err != nil || identity < identityMin || identity > identityMax {
|
||||||
log.Fatal("invalid identity")
|
log.Fatal("invalid identity")
|
||||||
} else {
|
} else {
|
||||||
uid += identity
|
uid += identity
|
||||||
|
|||||||
@@ -3,15 +3,18 @@ package container
|
|||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
"hakurei.app/container/fhs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { gob.Register(new(AutoEtcOp)) }
|
func init() { gob.Register(new(AutoEtcOp)) }
|
||||||
|
|
||||||
// Etc appends an [Op] that expands host /etc into a toplevel symlink mirror with /etc semantics.
|
// Etc appends an [Op] that expands host /etc into a toplevel symlink mirror with /etc semantics.
|
||||||
// This is not a generic setup op. It is implemented here to reduce ipc overhead.
|
// This is not a generic setup op. It is implemented here to reduce ipc overhead.
|
||||||
func (f *Ops) Etc(host *Absolute, prefix string) *Ops {
|
func (f *Ops) Etc(host *check.Absolute, prefix string) *Ops {
|
||||||
e := &AutoEtcOp{prefix}
|
e := &AutoEtcOp{prefix}
|
||||||
f.Mkdir(AbsFHSEtc, 0755)
|
f.Mkdir(fhs.AbsEtc, 0755)
|
||||||
f.Bind(host, e.hostPath(), 0)
|
f.Bind(host, e.hostPath(), 0)
|
||||||
*f = append(*f, e)
|
*f = append(*f, e)
|
||||||
return f
|
return f
|
||||||
@@ -27,7 +30,7 @@ func (e *AutoEtcOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
}
|
}
|
||||||
state.nonrepeatable |= nrAutoEtc
|
state.nonrepeatable |= nrAutoEtc
|
||||||
|
|
||||||
const target = sysrootPath + FHSEtc
|
const target = sysrootPath + fhs.Etc
|
||||||
rel := e.hostRel() + "/"
|
rel := e.hostRel() + "/"
|
||||||
|
|
||||||
if err := k.mkdirAll(target, 0755); err != nil {
|
if err := k.mkdirAll(target, 0755); err != nil {
|
||||||
@@ -42,7 +45,7 @@ func (e *AutoEtcOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
case ".host", "passwd", "group":
|
case ".host", "passwd", "group":
|
||||||
|
|
||||||
case "mtab":
|
case "mtab":
|
||||||
if err = k.symlink(FHSProc+"mounts", target+n); err != nil {
|
if err = k.symlink(fhs.Proc+"mounts", target+n); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,8 +60,8 @@ func (e *AutoEtcOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *AutoEtcOp) hostPath() *Absolute { return AbsFHSEtc.Append(e.hostRel()) }
|
func (e *AutoEtcOp) hostPath() *check.Absolute { return fhs.AbsEtc.Append(e.hostRel()) }
|
||||||
func (e *AutoEtcOp) hostRel() string { return ".host/" + e.Prefix }
|
func (e *AutoEtcOp) hostRel() string { return ".host/" + e.Prefix }
|
||||||
|
|
||||||
func (e *AutoEtcOp) Is(op Op) bool {
|
func (e *AutoEtcOp) Is(op Op) bool {
|
||||||
ve, ok := op.(*AutoEtcOp)
|
ve, ok := op.(*AutoEtcOp)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/container/stub"
|
"hakurei.app/container/stub"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -256,11 +257,11 @@ func TestAutoEtcOp(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
checkOpsBuilder(t, []opsBuilderTestCase{
|
checkOpsBuilder(t, []opsBuilderTestCase{
|
||||||
{"pd", new(Ops).Etc(MustAbs("/etc/"), "048090b6ed8f9ebb10e275ff5d8c0659"), Ops{
|
{"pd", new(Ops).Etc(check.MustAbs("/etc/"), "048090b6ed8f9ebb10e275ff5d8c0659"), Ops{
|
||||||
&MkdirOp{Path: MustAbs("/etc/"), Perm: 0755},
|
&MkdirOp{Path: check.MustAbs("/etc/"), Perm: 0755},
|
||||||
&BindMountOp{
|
&BindMountOp{
|
||||||
Source: MustAbs("/etc/"),
|
Source: check.MustAbs("/etc/"),
|
||||||
Target: MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
Target: check.MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
||||||
},
|
},
|
||||||
&AutoEtcOp{Prefix: "048090b6ed8f9ebb10e275ff5d8c0659"},
|
&AutoEtcOp{Prefix: "048090b6ed8f9ebb10e275ff5d8c0659"},
|
||||||
}},
|
}},
|
||||||
|
|||||||
@@ -3,19 +3,22 @@ package container
|
|||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
"hakurei.app/container/fhs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { gob.Register(new(AutoRootOp)) }
|
func init() { gob.Register(new(AutoRootOp)) }
|
||||||
|
|
||||||
// Root appends an [Op] that expands a directory into a toplevel bind mount mirror on container root.
|
// Root appends an [Op] that expands a directory into a toplevel bind mount mirror on container root.
|
||||||
// This is not a generic setup op. It is implemented here to reduce ipc overhead.
|
// This is not a generic setup op. It is implemented here to reduce ipc overhead.
|
||||||
func (f *Ops) Root(host *Absolute, flags int) *Ops {
|
func (f *Ops) Root(host *check.Absolute, flags int) *Ops {
|
||||||
*f = append(*f, &AutoRootOp{host, flags, nil})
|
*f = append(*f, &AutoRootOp{host, flags, nil})
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
type AutoRootOp struct {
|
type AutoRootOp struct {
|
||||||
Host *Absolute
|
Host *check.Absolute
|
||||||
// passed through to bindMount
|
// passed through to bindMount
|
||||||
Flags int
|
Flags int
|
||||||
|
|
||||||
@@ -34,11 +37,11 @@ func (r *AutoRootOp) early(state *setupState, k syscallDispatcher) error {
|
|||||||
r.resolved = make([]*BindMountOp, 0, len(d))
|
r.resolved = make([]*BindMountOp, 0, len(d))
|
||||||
for _, ent := range d {
|
for _, ent := range d {
|
||||||
name := ent.Name()
|
name := ent.Name()
|
||||||
if IsAutoRootBindable(name) {
|
if IsAutoRootBindable(state, name) {
|
||||||
// careful: the Valid method is skipped, make sure this is always valid
|
// careful: the Valid method is skipped, make sure this is always valid
|
||||||
op := &BindMountOp{
|
op := &BindMountOp{
|
||||||
Source: r.Host.Append(name),
|
Source: r.Host.Append(name),
|
||||||
Target: AbsFHSRoot.Append(name),
|
Target: fhs.AbsRoot.Append(name),
|
||||||
Flags: r.Flags,
|
Flags: r.Flags,
|
||||||
}
|
}
|
||||||
if err = op.early(state, k); err != nil {
|
if err = op.early(state, k); err != nil {
|
||||||
@@ -78,7 +81,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(name string) bool {
|
func IsAutoRootBindable(msg Msg, name string) bool {
|
||||||
switch name {
|
switch name {
|
||||||
case "proc", "dev", "tmp", "mnt", "etc":
|
case "proc", "dev", "tmp", "mnt", "etc":
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/bits"
|
||||||
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/container/stub"
|
"hakurei.app/container/stub"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,15 +20,15 @@ func TestAutoRootOp(t *testing.T) {
|
|||||||
|
|
||||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||||
{"readdir", &Params{ParentPerm: 0750}, &AutoRootOp{
|
{"readdir", &Params{ParentPerm: 0750}, &AutoRootOp{
|
||||||
Host: MustAbs("/"),
|
Host: check.MustAbs("/"),
|
||||||
Flags: BindWritable,
|
Flags: bits.BindWritable,
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("readdir", stub.ExpectArgs{"/"}, stubDir(), stub.UniqueError(2)),
|
call("readdir", stub.ExpectArgs{"/"}, stubDir(), stub.UniqueError(2)),
|
||||||
}, stub.UniqueError(2), nil, nil},
|
}, stub.UniqueError(2), nil, nil},
|
||||||
|
|
||||||
{"early", &Params{ParentPerm: 0750}, &AutoRootOp{
|
{"early", &Params{ParentPerm: 0750}, &AutoRootOp{
|
||||||
Host: MustAbs("/"),
|
Host: check.MustAbs("/"),
|
||||||
Flags: BindWritable,
|
Flags: bits.BindWritable,
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("readdir", stub.ExpectArgs{"/"}, stubDir("bin", "dev", "etc", "home", "lib64",
|
call("readdir", stub.ExpectArgs{"/"}, stubDir("bin", "dev", "etc", "home", "lib64",
|
||||||
"lost+found", "mnt", "nix", "proc", "root", "run", "srv", "sys", "tmp", "usr", "var"), nil),
|
"lost+found", "mnt", "nix", "proc", "root", "run", "srv", "sys", "tmp", "usr", "var"), nil),
|
||||||
@@ -34,8 +36,8 @@ func TestAutoRootOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(1), nil, nil},
|
}, stub.UniqueError(1), nil, nil},
|
||||||
|
|
||||||
{"apply", &Params{ParentPerm: 0750}, &AutoRootOp{
|
{"apply", &Params{ParentPerm: 0750}, &AutoRootOp{
|
||||||
Host: MustAbs("/"),
|
Host: check.MustAbs("/"),
|
||||||
Flags: BindWritable,
|
Flags: bits.BindWritable,
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("readdir", stub.ExpectArgs{"/"}, stubDir("bin", "dev", "etc", "home", "lib64",
|
call("readdir", stub.ExpectArgs{"/"}, stubDir("bin", "dev", "etc", "home", "lib64",
|
||||||
"lost+found", "mnt", "nix", "proc", "root", "run", "srv", "sys", "tmp", "usr", "var"), nil),
|
"lost+found", "mnt", "nix", "proc", "root", "run", "srv", "sys", "tmp", "usr", "var"), nil),
|
||||||
@@ -55,8 +57,8 @@ func TestAutoRootOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(0)},
|
}, stub.UniqueError(0)},
|
||||||
|
|
||||||
{"success pd", &Params{ParentPerm: 0750}, &AutoRootOp{
|
{"success pd", &Params{ParentPerm: 0750}, &AutoRootOp{
|
||||||
Host: MustAbs("/"),
|
Host: check.MustAbs("/"),
|
||||||
Flags: BindWritable,
|
Flags: bits.BindWritable,
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("readdir", stub.ExpectArgs{"/"}, stubDir("bin", "dev", "etc", "home", "lib64",
|
call("readdir", stub.ExpectArgs{"/"}, stubDir("bin", "dev", "etc", "home", "lib64",
|
||||||
"lost+found", "mnt", "nix", "proc", "root", "run", "srv", "sys", "tmp", "usr", "var"), nil),
|
"lost+found", "mnt", "nix", "proc", "root", "run", "srv", "sys", "tmp", "usr", "var"), nil),
|
||||||
@@ -86,7 +88,7 @@ func TestAutoRootOp(t *testing.T) {
|
|||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"success", &Params{ParentPerm: 0750}, &AutoRootOp{
|
{"success", &Params{ParentPerm: 0750}, &AutoRootOp{
|
||||||
Host: MustAbs("/var/lib/planterette/base/debian:f92c9052"),
|
Host: check.MustAbs("/var/lib/planterette/base/debian:f92c9052"),
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("readdir", stub.ExpectArgs{"/var/lib/planterette/base/debian:f92c9052"}, stubDir("bin", "dev", "etc", "home", "lib64",
|
call("readdir", stub.ExpectArgs{"/var/lib/planterette/base/debian:f92c9052"}, stubDir("bin", "dev", "etc", "home", "lib64",
|
||||||
"lost+found", "mnt", "nix", "proc", "root", "run", "srv", "sys", "tmp", "usr", "var"), nil),
|
"lost+found", "mnt", "nix", "proc", "root", "run", "srv", "sys", "tmp", "usr", "var"), nil),
|
||||||
@@ -119,14 +121,14 @@ func TestAutoRootOp(t *testing.T) {
|
|||||||
checkOpsValid(t, []opValidTestCase{
|
checkOpsValid(t, []opValidTestCase{
|
||||||
{"nil", (*AutoRootOp)(nil), false},
|
{"nil", (*AutoRootOp)(nil), false},
|
||||||
{"zero", new(AutoRootOp), false},
|
{"zero", new(AutoRootOp), false},
|
||||||
{"valid", &AutoRootOp{Host: MustAbs("/")}, true},
|
{"valid", &AutoRootOp{Host: check.MustAbs("/")}, true},
|
||||||
})
|
})
|
||||||
|
|
||||||
checkOpsBuilder(t, []opsBuilderTestCase{
|
checkOpsBuilder(t, []opsBuilderTestCase{
|
||||||
{"pd", new(Ops).Root(MustAbs("/"), BindWritable), Ops{
|
{"pd", new(Ops).Root(check.MustAbs("/"), bits.BindWritable), Ops{
|
||||||
&AutoRootOp{
|
&AutoRootOp{
|
||||||
Host: MustAbs("/"),
|
Host: check.MustAbs("/"),
|
||||||
Flags: BindWritable,
|
Flags: bits.BindWritable,
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
})
|
})
|
||||||
@@ -135,43 +137,43 @@ func TestAutoRootOp(t *testing.T) {
|
|||||||
{"zero", new(AutoRootOp), new(AutoRootOp), false},
|
{"zero", new(AutoRootOp), new(AutoRootOp), false},
|
||||||
|
|
||||||
{"internal ne", &AutoRootOp{
|
{"internal ne", &AutoRootOp{
|
||||||
Host: MustAbs("/"),
|
Host: check.MustAbs("/"),
|
||||||
Flags: BindWritable,
|
Flags: bits.BindWritable,
|
||||||
}, &AutoRootOp{
|
}, &AutoRootOp{
|
||||||
Host: MustAbs("/"),
|
Host: check.MustAbs("/"),
|
||||||
Flags: BindWritable,
|
Flags: bits.BindWritable,
|
||||||
resolved: []*BindMountOp{new(BindMountOp)},
|
resolved: []*BindMountOp{new(BindMountOp)},
|
||||||
}, true},
|
}, true},
|
||||||
|
|
||||||
{"flags differs", &AutoRootOp{
|
{"flags differs", &AutoRootOp{
|
||||||
Host: MustAbs("/"),
|
Host: check.MustAbs("/"),
|
||||||
Flags: BindWritable | BindDevice,
|
Flags: bits.BindWritable | bits.BindDevice,
|
||||||
}, &AutoRootOp{
|
}, &AutoRootOp{
|
||||||
Host: MustAbs("/"),
|
Host: check.MustAbs("/"),
|
||||||
Flags: BindWritable,
|
Flags: bits.BindWritable,
|
||||||
}, false},
|
}, false},
|
||||||
|
|
||||||
{"host differs", &AutoRootOp{
|
{"host differs", &AutoRootOp{
|
||||||
Host: MustAbs("/tmp/"),
|
Host: check.MustAbs("/tmp/"),
|
||||||
Flags: BindWritable,
|
Flags: bits.BindWritable,
|
||||||
}, &AutoRootOp{
|
}, &AutoRootOp{
|
||||||
Host: MustAbs("/"),
|
Host: check.MustAbs("/"),
|
||||||
Flags: BindWritable,
|
Flags: bits.BindWritable,
|
||||||
}, false},
|
}, false},
|
||||||
|
|
||||||
{"equals", &AutoRootOp{
|
{"equals", &AutoRootOp{
|
||||||
Host: MustAbs("/"),
|
Host: check.MustAbs("/"),
|
||||||
Flags: BindWritable,
|
Flags: bits.BindWritable,
|
||||||
}, &AutoRootOp{
|
}, &AutoRootOp{
|
||||||
Host: MustAbs("/"),
|
Host: check.MustAbs("/"),
|
||||||
Flags: BindWritable,
|
Flags: bits.BindWritable,
|
||||||
}, true},
|
}, true},
|
||||||
})
|
})
|
||||||
|
|
||||||
checkOpMeta(t, []opMetaTestCase{
|
checkOpMeta(t, []opMetaTestCase{
|
||||||
{"root", &AutoRootOp{
|
{"root", &AutoRootOp{
|
||||||
Host: MustAbs("/"),
|
Host: check.MustAbs("/"),
|
||||||
Flags: BindWritable,
|
Flags: bits.BindWritable,
|
||||||
}, "setting up", `auto root "/" flags 0x2`},
|
}, "setting up", `auto root "/" flags 0x2`},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -180,19 +182,26 @@ func TestIsAutoRootBindable(t *testing.T) {
|
|||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
want bool
|
want bool
|
||||||
|
log bool
|
||||||
}{
|
}{
|
||||||
{"proc", false},
|
{"proc", false, false},
|
||||||
{"dev", false},
|
{"dev", false, false},
|
||||||
{"tmp", false},
|
{"tmp", false, false},
|
||||||
{"mnt", false},
|
{"mnt", false, false},
|
||||||
{"etc", false},
|
{"etc", false, false},
|
||||||
{"", false},
|
{"", false, true},
|
||||||
|
|
||||||
{"var", true},
|
{"var", true, false},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
if got := IsAutoRootBindable(tc.name); got != tc.want {
|
var msg Msg
|
||||||
|
if tc.log {
|
||||||
|
msg = &kstub{nil, stub.New(t, func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { panic("unreachable") }, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{"got unexpected root entry"}}, nil, nil),
|
||||||
|
}})}
|
||||||
|
}
|
||||||
|
if got := IsAutoRootBindable(msg, tc.name); got != tc.want {
|
||||||
t.Errorf("IsAutoRootBindable: %v, want %v", got, tc.want)
|
t.Errorf("IsAutoRootBindable: %v, want %v", got, tc.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
13
container/bits/bits.go
Normal file
13
container/bits/bits.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// Package bits contains constants for configuring the container.
|
||||||
|
package bits
|
||||||
|
|
||||||
|
const (
|
||||||
|
// BindOptional skips nonexistent host paths.
|
||||||
|
BindOptional = 1 << iota
|
||||||
|
// BindWritable mounts filesystem read-write.
|
||||||
|
BindWritable
|
||||||
|
// BindDevice allows access to devices (special files) on this filesystem.
|
||||||
|
BindDevice
|
||||||
|
// BindEnsure attempts to create the host path if it does not exist.
|
||||||
|
BindEnsure
|
||||||
|
)
|
||||||
20
container/bits/seccomp.go
Normal file
20
container/bits/seccomp.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package bits
|
||||||
|
|
||||||
|
// FilterPreset specifies parts of the syscall filter preset to enable.
|
||||||
|
type FilterPreset int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PresetExt are project-specific extensions.
|
||||||
|
PresetExt FilterPreset = 1 << iota
|
||||||
|
// PresetDenyNS denies namespace setup syscalls.
|
||||||
|
PresetDenyNS
|
||||||
|
// PresetDenyTTY denies faking input.
|
||||||
|
PresetDenyTTY
|
||||||
|
// PresetDenyDevel denies development-related syscalls.
|
||||||
|
PresetDenyDevel
|
||||||
|
// PresetLinux32 sets PER_LINUX32.
|
||||||
|
PresetLinux32
|
||||||
|
|
||||||
|
// PresetStrict is a strict preset useful as a default value.
|
||||||
|
PresetStrict = PresetExt | PresetDenyNS | PresetDenyTTY | PresetDenyDevel
|
||||||
|
)
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
package container
|
// Package check provides types yielding values checked to meet a condition.
|
||||||
|
package check
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -11,9 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// AbsoluteError is returned by [NewAbs] and holds the invalid pathname.
|
// AbsoluteError is returned by [NewAbs] and holds the invalid pathname.
|
||||||
type AbsoluteError struct {
|
type AbsoluteError struct{ Pathname string }
|
||||||
Pathname string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *AbsoluteError) Error() string { return fmt.Sprintf("path %q is not absolute", e.Pathname) }
|
func (e *AbsoluteError) Error() string { return fmt.Sprintf("path %q is not absolute", e.Pathname) }
|
||||||
func (e *AbsoluteError) Is(target error) bool {
|
func (e *AbsoluteError) Is(target error) bool {
|
||||||
@@ -25,15 +24,13 @@ func (e *AbsoluteError) Is(target error) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Absolute holds a pathname checked to be absolute.
|
// Absolute holds a pathname checked to be absolute.
|
||||||
type Absolute struct {
|
type Absolute struct{ pathname string }
|
||||||
pathname string
|
|
||||||
}
|
|
||||||
|
|
||||||
// isAbs wraps [path.IsAbs] in case additional checks are added in the future.
|
// unsafeAbs returns [check.Absolute] on any string value.
|
||||||
func isAbs(pathname string) bool { return path.IsAbs(pathname) }
|
func unsafeAbs(pathname string) *Absolute { return &Absolute{pathname} }
|
||||||
|
|
||||||
func (a *Absolute) String() string {
|
func (a *Absolute) String() string {
|
||||||
if a.pathname == zeroString {
|
if a.pathname == "" {
|
||||||
panic("attempted use of zero Absolute")
|
panic("attempted use of zero Absolute")
|
||||||
}
|
}
|
||||||
return a.pathname
|
return a.pathname
|
||||||
@@ -44,16 +41,16 @@ func (a *Absolute) Is(v *Absolute) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return a != nil && v != nil &&
|
return a != nil && v != nil &&
|
||||||
a.pathname != zeroString && v.pathname != zeroString &&
|
a.pathname != "" && v.pathname != "" &&
|
||||||
a.pathname == v.pathname
|
a.pathname == v.pathname
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAbs checks pathname and returns a new [Absolute] if pathname is absolute.
|
// NewAbs checks pathname and returns a new [Absolute] if pathname is absolute.
|
||||||
func NewAbs(pathname string) (*Absolute, error) {
|
func NewAbs(pathname string) (*Absolute, error) {
|
||||||
if !isAbs(pathname) {
|
if !path.IsAbs(pathname) {
|
||||||
return nil, &AbsoluteError{pathname}
|
return nil, &AbsoluteError{pathname}
|
||||||
}
|
}
|
||||||
return &Absolute{pathname}, nil
|
return unsafeAbs(pathname), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustAbs calls [NewAbs] and panics on error.
|
// MustAbs calls [NewAbs] and panics on error.
|
||||||
@@ -67,16 +64,16 @@ func MustAbs(pathname string) *Absolute {
|
|||||||
|
|
||||||
// Append calls [path.Join] with [Absolute] as the first element.
|
// Append calls [path.Join] with [Absolute] as the first element.
|
||||||
func (a *Absolute) Append(elem ...string) *Absolute {
|
func (a *Absolute) Append(elem ...string) *Absolute {
|
||||||
return &Absolute{path.Join(append([]string{a.String()}, elem...)...)}
|
return unsafeAbs(path.Join(append([]string{a.String()}, elem...)...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dir calls [path.Dir] with [Absolute] as its argument.
|
// Dir calls [path.Dir] with [Absolute] as its argument.
|
||||||
func (a *Absolute) Dir() *Absolute { return &Absolute{path.Dir(a.String())} }
|
func (a *Absolute) Dir() *Absolute { return unsafeAbs(path.Dir(a.String())) }
|
||||||
|
|
||||||
func (a *Absolute) GobEncode() ([]byte, error) { return []byte(a.String()), nil }
|
func (a *Absolute) GobEncode() ([]byte, error) { return []byte(a.String()), nil }
|
||||||
func (a *Absolute) GobDecode(data []byte) error {
|
func (a *Absolute) GobDecode(data []byte) error {
|
||||||
pathname := string(data)
|
pathname := string(data)
|
||||||
if !isAbs(pathname) {
|
if !path.IsAbs(pathname) {
|
||||||
return &AbsoluteError{pathname}
|
return &AbsoluteError{pathname}
|
||||||
}
|
}
|
||||||
a.pathname = pathname
|
a.pathname = pathname
|
||||||
@@ -89,7 +86,7 @@ func (a *Absolute) UnmarshalJSON(data []byte) error {
|
|||||||
if err := json.Unmarshal(data, &pathname); err != nil {
|
if err := json.Unmarshal(data, &pathname); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !isAbs(pathname) {
|
if !path.IsAbs(pathname) {
|
||||||
return &AbsoluteError{pathname}
|
return &AbsoluteError{pathname}
|
||||||
}
|
}
|
||||||
a.pathname = pathname
|
a.pathname = pathname
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package container
|
package check_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -9,8 +9,14 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
_ "unsafe"
|
||||||
|
|
||||||
|
. "hakurei.app/container/check"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:linkname unsafeAbs hakurei.app/container/check.unsafeAbs
|
||||||
|
func unsafeAbs(_ string) *Absolute
|
||||||
|
|
||||||
func TestAbsoluteError(t *testing.T) {
|
func TestAbsoluteError(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -80,7 +86,7 @@ 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) {
|
||||||
pathname := "/etc"
|
pathname := "/etc"
|
||||||
if got := (&Absolute{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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
29
container/check/overlay.go
Normal file
29
container/check/overlay.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package check
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SpecialOverlayEscape is the escape string for overlay mount options.
|
||||||
|
SpecialOverlayEscape = `\`
|
||||||
|
// SpecialOverlayOption is the separator string between overlay mount options.
|
||||||
|
SpecialOverlayOption = ","
|
||||||
|
// SpecialOverlayPath is the separator string between overlay paths.
|
||||||
|
SpecialOverlayPath = ":"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EscapeOverlayDataSegment escapes a string for formatting into the data argument of an overlay mount call.
|
||||||
|
func EscapeOverlayDataSegment(s string) string {
|
||||||
|
if s == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if f := strings.SplitN(s, "\x00", 2); len(f) > 0 {
|
||||||
|
s = f[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.NewReplacer(
|
||||||
|
SpecialOverlayEscape, SpecialOverlayEscape+SpecialOverlayEscape,
|
||||||
|
SpecialOverlayOption, SpecialOverlayEscape+SpecialOverlayOption,
|
||||||
|
SpecialOverlayPath, SpecialOverlayEscape+SpecialOverlayPath,
|
||||||
|
).Replace(s)
|
||||||
|
}
|
||||||
27
container/check/overlay_test.go
Normal file
27
container/check/overlay_test.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package check_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEscapeOverlayDataSegment(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
s string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"zero", "", ""},
|
||||||
|
{"multi", `\\\:,:,\\\`, `\\\\\\\:\,\:\,\\\\\\`},
|
||||||
|
{"bwrap", `/path :,\`, `/path \:\,\\`},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if got := check.EscapeOverlayDataSegment(tc.s); got != tc.want {
|
||||||
|
t.Errorf("escapeOverlayDataSegment: %s, want %s", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,9 @@ import (
|
|||||||
. "syscall"
|
. "syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"hakurei.app/container/bits"
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
"hakurei.app/container/fhs"
|
||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/seccomp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,17 +52,18 @@ type (
|
|||||||
|
|
||||||
cmd *exec.Cmd
|
cmd *exec.Cmd
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
msg Msg
|
||||||
Params
|
Params
|
||||||
}
|
}
|
||||||
|
|
||||||
// Params holds container configuration and is safe to serialise.
|
// Params holds container configuration and is safe to serialise.
|
||||||
Params struct {
|
Params struct {
|
||||||
// Working directory in the container.
|
// Working directory in the container.
|
||||||
Dir *Absolute
|
Dir *check.Absolute
|
||||||
// Initial process environment.
|
// Initial process environment.
|
||||||
Env []string
|
Env []string
|
||||||
// Pathname of initial process in the container.
|
// Pathname of initial process in the container.
|
||||||
Path *Absolute
|
Path *check.Absolute
|
||||||
// Initial process argv.
|
// Initial process argv.
|
||||||
Args []string
|
Args []string
|
||||||
// Deliver SIGINT to the initial process on context cancellation.
|
// Deliver SIGINT to the initial process on context cancellation.
|
||||||
@@ -81,7 +85,7 @@ type (
|
|||||||
// Extra seccomp flags.
|
// Extra seccomp flags.
|
||||||
SeccompFlags seccomp.ExportFlag
|
SeccompFlags seccomp.ExportFlag
|
||||||
// Seccomp presets. Has no effect unless SeccompRules is zero-length.
|
// Seccomp presets. Has no effect unless SeccompRules is zero-length.
|
||||||
SeccompPresets seccomp.FilterPreset
|
SeccompPresets bits.FilterPreset
|
||||||
// Do not load seccomp program.
|
// Do not load seccomp program.
|
||||||
SeccompDisable bool
|
SeccompDisable bool
|
||||||
|
|
||||||
@@ -162,14 +166,14 @@ func (p *Container) Start() error {
|
|||||||
|
|
||||||
// map to overflow id to work around ownership checks
|
// map to overflow id to work around ownership checks
|
||||||
if p.Uid < 1 {
|
if p.Uid < 1 {
|
||||||
p.Uid = OverflowUid()
|
p.Uid = OverflowUid(p.msg)
|
||||||
}
|
}
|
||||||
if p.Gid < 1 {
|
if p.Gid < 1 {
|
||||||
p.Gid = OverflowGid()
|
p.Gid = OverflowGid(p.msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !p.RetainSession {
|
if !p.RetainSession {
|
||||||
p.SeccompPresets |= seccomp.PresetDenyTTY
|
p.SeccompPresets |= bits.PresetDenyTTY
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.AdoptWaitDelay == 0 {
|
if p.AdoptWaitDelay == 0 {
|
||||||
@@ -197,7 +201,7 @@ func (p *Container) Start() error {
|
|||||||
} else {
|
} else {
|
||||||
p.cmd.Cancel = func() error { return p.cmd.Process.Signal(CancelSignal) }
|
p.cmd.Cancel = func() error { return p.cmd.Process.Signal(CancelSignal) }
|
||||||
}
|
}
|
||||||
p.cmd.Dir = FHSRoot
|
p.cmd.Dir = fhs.Root
|
||||||
p.cmd.SysProcAttr = &SysProcAttr{
|
p.cmd.SysProcAttr = &SysProcAttr{
|
||||||
Setsid: !p.RetainSession,
|
Setsid: !p.RetainSession,
|
||||||
Pdeathsig: SIGKILL,
|
Pdeathsig: SIGKILL,
|
||||||
@@ -263,19 +267,19 @@ func (p *Container) Start() error {
|
|||||||
}
|
}
|
||||||
return &StartError{false, "kernel version too old for LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET", ENOSYS, true, false}
|
return &StartError{false, "kernel version too old for LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET", ENOSYS, true, false}
|
||||||
} else {
|
} else {
|
||||||
msg.Verbosef("landlock abi version %d", abi)
|
p.msg.Verbosef("landlock abi version %d", abi)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rulesetFd, err := rulesetAttr.Create(0); err != nil {
|
if rulesetFd, err := rulesetAttr.Create(0); err != nil {
|
||||||
return &StartError{true, "create landlock ruleset", err, false, false}
|
return &StartError{true, "create landlock ruleset", err, false, false}
|
||||||
} else {
|
} else {
|
||||||
msg.Verbosef("enforcing landlock ruleset %s", rulesetAttr)
|
p.msg.Verbosef("enforcing landlock ruleset %s", rulesetAttr)
|
||||||
if err = LandlockRestrictSelf(rulesetFd, 0); err != nil {
|
if err = LandlockRestrictSelf(rulesetFd, 0); err != nil {
|
||||||
_ = Close(rulesetFd)
|
_ = Close(rulesetFd)
|
||||||
return &StartError{true, "enforce landlock ruleset", err, false, false}
|
return &StartError{true, "enforce landlock ruleset", err, false, false}
|
||||||
}
|
}
|
||||||
if err = Close(rulesetFd); err != nil {
|
if err = Close(rulesetFd); err != nil {
|
||||||
msg.Verbosef("cannot close landlock ruleset: %v", err)
|
p.msg.Verbosef("cannot close landlock ruleset: %v", err)
|
||||||
// not fatal
|
// not fatal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,7 +287,7 @@ func (p *Container) Start() error {
|
|||||||
landlockOut:
|
landlockOut:
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.Verbose("starting container init")
|
p.msg.Verbose("starting container init")
|
||||||
if err := p.cmd.Start(); err != nil {
|
if err := p.cmd.Start(); err != nil {
|
||||||
return &StartError{false, "start container init", err, false, true}
|
return &StartError{false, "start container init", err, false, true}
|
||||||
}
|
}
|
||||||
@@ -313,7 +317,7 @@ func (p *Container) Serve() error {
|
|||||||
|
|
||||||
// do not transmit nil
|
// do not transmit nil
|
||||||
if p.Dir == nil {
|
if p.Dir == nil {
|
||||||
p.Dir = AbsFHSRoot
|
p.Dir = fhs.AbsRoot
|
||||||
}
|
}
|
||||||
if p.SeccompRules == nil {
|
if p.SeccompRules == nil {
|
||||||
p.SeccompRules = make([]seccomp.NativeRule, 0)
|
p.SeccompRules = make([]seccomp.NativeRule, 0)
|
||||||
@@ -325,7 +329,7 @@ func (p *Container) Serve() error {
|
|||||||
Getuid(),
|
Getuid(),
|
||||||
Getgid(),
|
Getgid(),
|
||||||
len(p.ExtraFiles),
|
len(p.ExtraFiles),
|
||||||
msg.IsVerbose(),
|
p.msg.IsVerbose(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -392,17 +396,21 @@ 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) *Container {
|
func New(ctx context.Context, msg Msg) *Container {
|
||||||
p := &Container{ctx: ctx, Params: Params{Ops: new(Ops)}}
|
if msg == nil {
|
||||||
|
msg = NewMsg(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &Container{ctx: ctx, msg: msg, Params: Params{Ops: new(Ops)}}
|
||||||
c, cancel := context.WithCancel(ctx)
|
c, cancel := context.WithCancel(ctx)
|
||||||
p.cancel = cancel
|
p.cancel = cancel
|
||||||
p.cmd = exec.CommandContext(c, MustExecutable())
|
p.cmd = exec.CommandContext(c, MustExecutable(msg))
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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, pathname *Absolute, name string, args ...string) *Container {
|
func NewCommand(ctx context.Context, msg Msg, pathname *check.Absolute, name string, args ...string) *Container {
|
||||||
z := New(ctx)
|
z := New(ctx, msg)
|
||||||
z.Path = pathname
|
z.Path = pathname
|
||||||
z.Args = append([]string{name}, args...)
|
z.Args = append([]string{name}, args...)
|
||||||
return z
|
return z
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ import (
|
|||||||
|
|
||||||
"hakurei.app/command"
|
"hakurei.app/command"
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/container/bits"
|
||||||
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/seccomp"
|
||||||
"hakurei.app/container/vfs"
|
"hakurei.app/container/vfs"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal"
|
|
||||||
"hakurei.app/internal/hlog"
|
|
||||||
"hakurei.app/ldd"
|
"hakurei.app/ldd"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -201,20 +201,20 @@ var containerTestCases = []struct {
|
|||||||
|
|
||||||
rules []seccomp.NativeRule
|
rules []seccomp.NativeRule
|
||||||
flags seccomp.ExportFlag
|
flags seccomp.ExportFlag
|
||||||
presets seccomp.FilterPreset
|
presets bits.FilterPreset
|
||||||
}{
|
}{
|
||||||
{"minimal", true, false, false, true,
|
{"minimal", true, false, false, true,
|
||||||
emptyOps, emptyMnt,
|
emptyOps, emptyMnt,
|
||||||
1000, 100, nil, 0, seccomp.PresetStrict},
|
1000, 100, nil, 0, bits.PresetStrict},
|
||||||
{"allow", true, true, true, false,
|
{"allow", true, true, true, false,
|
||||||
emptyOps, emptyMnt,
|
emptyOps, emptyMnt,
|
||||||
1000, 100, nil, 0, seccomp.PresetExt | seccomp.PresetDenyDevel},
|
1000, 100, nil, 0, bits.PresetExt | bits.PresetDenyDevel},
|
||||||
{"no filter", false, true, true, true,
|
{"no filter", false, true, true, true,
|
||||||
emptyOps, emptyMnt,
|
emptyOps, emptyMnt,
|
||||||
1000, 100, nil, 0, seccomp.PresetExt},
|
1000, 100, nil, 0, bits.PresetExt},
|
||||||
{"custom rules", true, true, true, false,
|
{"custom rules", true, true, true, false,
|
||||||
emptyOps, emptyMnt,
|
emptyOps, emptyMnt,
|
||||||
1, 31, []seccomp.NativeRule{{Syscall: seccomp.ScmpSyscall(syscall.SYS_SETUID), Errno: seccomp.ScmpErrno(syscall.EPERM)}}, 0, seccomp.PresetExt},
|
1, 31, []seccomp.NativeRule{{Syscall: seccomp.ScmpSyscall(syscall.SYS_SETUID), Errno: seccomp.ScmpErrno(syscall.EPERM)}}, 0, bits.PresetExt},
|
||||||
|
|
||||||
{"tmpfs", true, false, false, true,
|
{"tmpfs", true, false, false, true,
|
||||||
earlyOps(new(container.Ops).
|
earlyOps(new(container.Ops).
|
||||||
@@ -223,11 +223,11 @@ var containerTestCases = []struct {
|
|||||||
earlyMnt(
|
earlyMnt(
|
||||||
ent("/", hst.Tmp, "rw,nosuid,nodev,relatime", "tmpfs", "ephemeral", ignore),
|
ent("/", hst.Tmp, "rw,nosuid,nodev,relatime", "tmpfs", "ephemeral", ignore),
|
||||||
),
|
),
|
||||||
9, 9, nil, 0, seccomp.PresetStrict},
|
9, 9, nil, 0, bits.PresetStrict},
|
||||||
|
|
||||||
{"dev", true, true /* go test output is not a tty */, false, false,
|
{"dev", true, true /* go test output is not a tty */, false, false,
|
||||||
earlyOps(new(container.Ops).
|
earlyOps(new(container.Ops).
|
||||||
Dev(container.MustAbs("/dev"), true),
|
Dev(check.MustAbs("/dev"), true),
|
||||||
),
|
),
|
||||||
earlyMnt(
|
earlyMnt(
|
||||||
ent("/", "/dev", "ro,nosuid,nodev,relatime", "tmpfs", "devtmpfs", ignore),
|
ent("/", "/dev", "ro,nosuid,nodev,relatime", "tmpfs", "devtmpfs", ignore),
|
||||||
@@ -241,11 +241,11 @@ var containerTestCases = []struct {
|
|||||||
ent("/", "/dev/mqueue", "rw,nosuid,nodev,noexec,relatime", "mqueue", "mqueue", "rw"),
|
ent("/", "/dev/mqueue", "rw,nosuid,nodev,noexec,relatime", "mqueue", "mqueue", "rw"),
|
||||||
ent("/", "/dev/shm", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore),
|
ent("/", "/dev/shm", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore),
|
||||||
),
|
),
|
||||||
1971, 100, nil, 0, seccomp.PresetStrict},
|
1971, 100, nil, 0, bits.PresetStrict},
|
||||||
|
|
||||||
{"dev no mqueue", true, true /* go test output is not a tty */, false, false,
|
{"dev no mqueue", true, true /* go test output is not a tty */, false, false,
|
||||||
earlyOps(new(container.Ops).
|
earlyOps(new(container.Ops).
|
||||||
Dev(container.MustAbs("/dev"), false),
|
Dev(check.MustAbs("/dev"), false),
|
||||||
),
|
),
|
||||||
earlyMnt(
|
earlyMnt(
|
||||||
ent("/", "/dev", "ro,nosuid,nodev,relatime", "tmpfs", "devtmpfs", ignore),
|
ent("/", "/dev", "ro,nosuid,nodev,relatime", "tmpfs", "devtmpfs", ignore),
|
||||||
@@ -258,17 +258,17 @@ var containerTestCases = []struct {
|
|||||||
ent("/", "/dev/pts", "rw,nosuid,noexec,relatime", "devpts", "devpts", "rw,mode=620,ptmxmode=666"),
|
ent("/", "/dev/pts", "rw,nosuid,noexec,relatime", "devpts", "devpts", "rw,mode=620,ptmxmode=666"),
|
||||||
ent("/", "/dev/shm", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore),
|
ent("/", "/dev/shm", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore),
|
||||||
),
|
),
|
||||||
1971, 100, nil, 0, seccomp.PresetStrict},
|
1971, 100, nil, 0, bits.PresetStrict},
|
||||||
|
|
||||||
{"overlay", true, false, false, true,
|
{"overlay", true, false, false, true,
|
||||||
func(t *testing.T) (*container.Ops, context.Context) {
|
func(t *testing.T) (*container.Ops, context.Context) {
|
||||||
tempDir := container.MustAbs(t.TempDir())
|
tempDir := check.MustAbs(t.TempDir())
|
||||||
lower0, lower1, upper, work :=
|
lower0, lower1, upper, work :=
|
||||||
tempDir.Append("lower0"),
|
tempDir.Append("lower0"),
|
||||||
tempDir.Append("lower1"),
|
tempDir.Append("lower1"),
|
||||||
tempDir.Append("upper"),
|
tempDir.Append("upper"),
|
||||||
tempDir.Append("work")
|
tempDir.Append("work")
|
||||||
for _, a := range []*container.Absolute{lower0, lower1, upper, work} {
|
for _, a := range []*check.Absolute{lower0, lower1, upper, work} {
|
||||||
if err := os.Mkdir(a.String(), 0755); err != nil {
|
if err := os.Mkdir(a.String(), 0755); err != nil {
|
||||||
t.Fatalf("Mkdir: error = %v", err)
|
t.Fatalf("Mkdir: error = %v", err)
|
||||||
}
|
}
|
||||||
@@ -286,24 +286,24 @@ var containerTestCases = []struct {
|
|||||||
return []*vfs.MountInfoEntry{
|
return []*vfs.MountInfoEntry{
|
||||||
ent("/", hst.Tmp, "rw", "overlay", "overlay",
|
ent("/", hst.Tmp, "rw", "overlay", "overlay",
|
||||||
"rw,lowerdir="+
|
"rw,lowerdir="+
|
||||||
container.InternalToHostOvlEscape(ctx.Value(testVal("lower0")).(*container.Absolute).String())+":"+
|
container.InternalToHostOvlEscape(ctx.Value(testVal("lower0")).(*check.Absolute).String())+":"+
|
||||||
container.InternalToHostOvlEscape(ctx.Value(testVal("lower1")).(*container.Absolute).String())+
|
container.InternalToHostOvlEscape(ctx.Value(testVal("lower1")).(*check.Absolute).String())+
|
||||||
",upperdir="+
|
",upperdir="+
|
||||||
container.InternalToHostOvlEscape(ctx.Value(testVal("upper")).(*container.Absolute).String())+
|
container.InternalToHostOvlEscape(ctx.Value(testVal("upper")).(*check.Absolute).String())+
|
||||||
",workdir="+
|
",workdir="+
|
||||||
container.InternalToHostOvlEscape(ctx.Value(testVal("work")).(*container.Absolute).String())+
|
container.InternalToHostOvlEscape(ctx.Value(testVal("work")).(*check.Absolute).String())+
|
||||||
",redirect_dir=nofollow,uuid=on,userxattr"),
|
",redirect_dir=nofollow,uuid=on,userxattr"),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
1 << 3, 1 << 14, nil, 0, seccomp.PresetStrict},
|
1 << 3, 1 << 14, nil, 0, bits.PresetStrict},
|
||||||
|
|
||||||
{"overlay ephemeral", true, false, false, true,
|
{"overlay ephemeral", true, false, false, true,
|
||||||
func(t *testing.T) (*container.Ops, context.Context) {
|
func(t *testing.T) (*container.Ops, context.Context) {
|
||||||
tempDir := container.MustAbs(t.TempDir())
|
tempDir := check.MustAbs(t.TempDir())
|
||||||
lower0, lower1 :=
|
lower0, lower1 :=
|
||||||
tempDir.Append("lower0"),
|
tempDir.Append("lower0"),
|
||||||
tempDir.Append("lower1")
|
tempDir.Append("lower1")
|
||||||
for _, a := range []*container.Absolute{lower0, lower1} {
|
for _, a := range []*check.Absolute{lower0, lower1} {
|
||||||
if err := os.Mkdir(a.String(), 0755); err != nil {
|
if err := os.Mkdir(a.String(), 0755); err != nil {
|
||||||
t.Fatalf("Mkdir: error = %v", err)
|
t.Fatalf("Mkdir: error = %v", err)
|
||||||
}
|
}
|
||||||
@@ -319,15 +319,15 @@ var containerTestCases = []struct {
|
|||||||
ent("/", hst.Tmp, "rw", "overlay", "overlay", ignore),
|
ent("/", hst.Tmp, "rw", "overlay", "overlay", ignore),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
1 << 3, 1 << 14, nil, 0, seccomp.PresetStrict},
|
1 << 3, 1 << 14, nil, 0, bits.PresetStrict},
|
||||||
|
|
||||||
{"overlay readonly", true, false, false, true,
|
{"overlay readonly", true, false, false, true,
|
||||||
func(t *testing.T) (*container.Ops, context.Context) {
|
func(t *testing.T) (*container.Ops, context.Context) {
|
||||||
tempDir := container.MustAbs(t.TempDir())
|
tempDir := check.MustAbs(t.TempDir())
|
||||||
lower0, lower1 :=
|
lower0, lower1 :=
|
||||||
tempDir.Append("lower0"),
|
tempDir.Append("lower0"),
|
||||||
tempDir.Append("lower1")
|
tempDir.Append("lower1")
|
||||||
for _, a := range []*container.Absolute{lower0, lower1} {
|
for _, a := range []*check.Absolute{lower0, lower1} {
|
||||||
if err := os.Mkdir(a.String(), 0755); err != nil {
|
if err := os.Mkdir(a.String(), 0755); err != nil {
|
||||||
t.Fatalf("Mkdir: error = %v", err)
|
t.Fatalf("Mkdir: error = %v", err)
|
||||||
}
|
}
|
||||||
@@ -342,17 +342,15 @@ var containerTestCases = []struct {
|
|||||||
return []*vfs.MountInfoEntry{
|
return []*vfs.MountInfoEntry{
|
||||||
ent("/", hst.Tmp, "rw", "overlay", "overlay",
|
ent("/", hst.Tmp, "rw", "overlay", "overlay",
|
||||||
"ro,lowerdir="+
|
"ro,lowerdir="+
|
||||||
container.InternalToHostOvlEscape(ctx.Value(testVal("lower0")).(*container.Absolute).String())+":"+
|
container.InternalToHostOvlEscape(ctx.Value(testVal("lower0")).(*check.Absolute).String())+":"+
|
||||||
container.InternalToHostOvlEscape(ctx.Value(testVal("lower1")).(*container.Absolute).String())+
|
container.InternalToHostOvlEscape(ctx.Value(testVal("lower1")).(*check.Absolute).String())+
|
||||||
",redirect_dir=nofollow,userxattr"),
|
",redirect_dir=nofollow,userxattr"),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
1 << 3, 1 << 14, nil, 0, seccomp.PresetStrict},
|
1 << 3, 1 << 14, nil, 0, bits.PresetStrict},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContainer(t *testing.T) {
|
func TestContainer(t *testing.T) {
|
||||||
replaceOutput(t)
|
|
||||||
|
|
||||||
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
|
||||||
@@ -392,7 +390,7 @@ func TestContainer(t *testing.T) {
|
|||||||
ctx, cancel := context.WithTimeout(t.Context(), helperDefaultTimeout)
|
ctx, cancel := context.WithTimeout(t.Context(), helperDefaultTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
var libPaths []*container.Absolute
|
var libPaths []*check.Absolute
|
||||||
c := helperNewContainerLibPaths(ctx, &libPaths, "container", strconv.Itoa(i))
|
c := helperNewContainerLibPaths(ctx, &libPaths, "container", strconv.Itoa(i))
|
||||||
c.Uid = tc.uid
|
c.Uid = tc.uid
|
||||||
c.Gid = tc.gid
|
c.Gid = tc.gid
|
||||||
@@ -413,11 +411,11 @@ func TestContainer(t *testing.T) {
|
|||||||
c.HostNet = tc.net
|
c.HostNet = tc.net
|
||||||
|
|
||||||
c.
|
c.
|
||||||
Readonly(container.MustAbs(pathReadonly), 0755).
|
Readonly(check.MustAbs(pathReadonly), 0755).
|
||||||
Tmpfs(container.MustAbs("/tmp"), 0, 0755).
|
Tmpfs(check.MustAbs("/tmp"), 0, 0755).
|
||||||
Place(container.MustAbs("/etc/hostname"), []byte(c.Hostname))
|
Place(check.MustAbs("/etc/hostname"), []byte(c.Hostname))
|
||||||
// needs /proc to check mountinfo
|
// needs /proc to check mountinfo
|
||||||
c.Proc(container.MustAbs("/proc"))
|
c.Proc(check.MustAbs("/proc"))
|
||||||
|
|
||||||
// mountinfo cannot be resolved directly by helper due to libPaths nondeterminism
|
// mountinfo cannot be resolved directly by helper due to libPaths nondeterminism
|
||||||
mnt := make([]*vfs.MountInfoEntry, 0, 3+len(libPaths))
|
mnt := make([]*vfs.MountInfoEntry, 0, 3+len(libPaths))
|
||||||
@@ -448,10 +446,10 @@ func TestContainer(t *testing.T) {
|
|||||||
_, _ = output.WriteTo(os.Stdout)
|
_, _ = output.WriteTo(os.Stdout)
|
||||||
t.Fatalf("cannot serialise expected mount points: %v", err)
|
t.Fatalf("cannot serialise expected mount points: %v", err)
|
||||||
}
|
}
|
||||||
c.Place(container.MustAbs(pathWantMnt), want.Bytes())
|
c.Place(check.MustAbs(pathWantMnt), want.Bytes())
|
||||||
|
|
||||||
if tc.ro {
|
if tc.ro {
|
||||||
c.Remount(container.MustAbs("/"), syscall.MS_RDONLY)
|
c.Remount(check.MustAbs("/"), syscall.MS_RDONLY)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.Start(); err != nil {
|
if err := c.Start(); err != nil {
|
||||||
@@ -547,12 +545,13 @@ func testContainerCancel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestContainerString(t *testing.T) {
|
func TestContainerString(t *testing.T) {
|
||||||
c := container.NewCommand(t.Context(), container.MustAbs("/run/current-system/sw/bin/ldd"), "ldd", "/usr/bin/env")
|
msg := container.NewMsg(nil)
|
||||||
|
c := container.NewCommand(t.Context(), msg, check.MustAbs("/run/current-system/sw/bin/ldd"), "ldd", "/usr/bin/env")
|
||||||
c.SeccompFlags |= seccomp.AllowMultiarch
|
c.SeccompFlags |= seccomp.AllowMultiarch
|
||||||
c.SeccompRules = seccomp.Preset(
|
c.SeccompRules = seccomp.Preset(
|
||||||
seccomp.PresetExt|seccomp.PresetDenyNS|seccomp.PresetDenyTTY,
|
bits.PresetExt|bits.PresetDenyNS|bits.PresetDenyTTY,
|
||||||
c.SeccompFlags)
|
c.SeccompFlags)
|
||||||
c.SeccompPresets = seccomp.PresetStrict
|
c.SeccompPresets = bits.PresetStrict
|
||||||
want := `argv: ["ldd" "/usr/bin/env"], filter: true, rules: 65, flags: 0x1, presets: 0xf`
|
want := `argv: ["ldd" "/usr/bin/env"], filter: true, rules: 65, flags: 0x1, presets: 0xf`
|
||||||
if got := c.String(); got != want {
|
if got := c.String(); got != want {
|
||||||
t.Errorf("String: %s, want %s", got, want)
|
t.Errorf("String: %s, want %s", got, want)
|
||||||
@@ -683,13 +682,13 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
absHelperInnerPath = container.MustAbs(helperInnerPath)
|
absHelperInnerPath = check.MustAbs(helperInnerPath)
|
||||||
)
|
)
|
||||||
|
|
||||||
var helperCommands []func(c command.Command)
|
var helperCommands []func(c command.Command)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
container.TryArgv0(hlog.Output{}, hlog.Prepare, internal.InstallOutput)
|
container.TryArgv0(nil)
|
||||||
|
|
||||||
if os.Getenv(envDoCheck) == "1" {
|
if os.Getenv(envDoCheck) == "1" {
|
||||||
c := command.New(os.Stderr, log.Printf, "helper", func(args []string) error {
|
c := command.New(os.Stderr, log.Printf, "helper", func(args []string) error {
|
||||||
@@ -711,13 +710,14 @@ func TestMain(m *testing.M) {
|
|||||||
os.Exit(m.Run())
|
os.Exit(m.Run())
|
||||||
}
|
}
|
||||||
|
|
||||||
func helperNewContainerLibPaths(ctx context.Context, libPaths *[]*container.Absolute, args ...string) (c *container.Container) {
|
func helperNewContainerLibPaths(ctx context.Context, libPaths *[]*check.Absolute, args ...string) (c *container.Container) {
|
||||||
c = container.NewCommand(ctx, absHelperInnerPath, "helper", args...)
|
msg := container.NewMsg(nil)
|
||||||
|
c = container.NewCommand(ctx, msg, absHelperInnerPath, "helper", args...)
|
||||||
c.Env = append(c.Env, envDoCheck+"=1")
|
c.Env = append(c.Env, envDoCheck+"=1")
|
||||||
c.Bind(container.MustAbs(os.Args[0]), absHelperInnerPath, 0)
|
c.Bind(check.MustAbs(os.Args[0]), absHelperInnerPath, 0)
|
||||||
|
|
||||||
// in case test has cgo enabled
|
// in case test has cgo enabled
|
||||||
if entries, err := ldd.Exec(ctx, os.Args[0]); err != nil {
|
if entries, err := ldd.Exec(ctx, msg, os.Args[0]); err != nil {
|
||||||
log.Fatalf("ldd: %v", err)
|
log.Fatalf("ldd: %v", err)
|
||||||
} else {
|
} else {
|
||||||
*libPaths = ldd.Path(entries)
|
*libPaths = ldd.Path(entries)
|
||||||
@@ -730,5 +730,5 @@ func helperNewContainerLibPaths(ctx context.Context, libPaths *[]*container.Abso
|
|||||||
}
|
}
|
||||||
|
|
||||||
func helperNewContainer(ctx context.Context, args ...string) (c *container.Container) {
|
func helperNewContainer(ctx context.Context, args ...string) (c *container.Container) {
|
||||||
return helperNewContainerLibPaths(ctx, new([]*container.Absolute), args...)
|
return helperNewContainerLibPaths(ctx, new([]*check.Absolute), args...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package container
|
|||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@@ -38,7 +37,7 @@ type syscallDispatcher interface {
|
|||||||
setNoNewPrivs() error
|
setNoNewPrivs() error
|
||||||
|
|
||||||
// lastcap provides [LastCap].
|
// lastcap provides [LastCap].
|
||||||
lastcap() uintptr
|
lastcap(msg 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.
|
||||||
@@ -53,9 +52,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(source, target string, flags uintptr) error
|
bindMount(msg Msg, source, target string, flags uintptr) error
|
||||||
// remount provides procPaths.remount.
|
// remount provides procPaths.remount.
|
||||||
remount(target string, flags uintptr) error
|
remount(msg 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,22 +121,12 @@ type syscallDispatcher interface {
|
|||||||
// wait4 provides syscall.Wait4
|
// wait4 provides syscall.Wait4
|
||||||
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 [log.Printf].
|
// printf provides the Printf method of [log.Logger].
|
||||||
printf(format string, v ...any)
|
printf(msg Msg, format string, v ...any)
|
||||||
// fatal provides [log.Fatal]
|
// fatal provides the Fatal method of [log.Logger]
|
||||||
fatal(v ...any)
|
fatal(msg Msg, v ...any)
|
||||||
// fatalf provides [log.Fatalf]
|
// fatalf provides the Fatalf method of [log.Logger]
|
||||||
fatalf(format string, v ...any)
|
fatalf(msg Msg, format string, v ...any)
|
||||||
// verbose provides [Msg.Verbose].
|
|
||||||
verbose(v ...any)
|
|
||||||
// verbosef provides [Msg.Verbosef].
|
|
||||||
verbosef(format string, v ...any)
|
|
||||||
// suspend provides [Msg.Suspend].
|
|
||||||
suspend()
|
|
||||||
// resume provides [Msg.Resume].
|
|
||||||
resume() bool
|
|
||||||
// beforeExit provides [Msg.BeforeExit].
|
|
||||||
beforeExit()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// direct implements syscallDispatcher on the current kernel.
|
// direct implements syscallDispatcher on the current kernel.
|
||||||
@@ -151,7 +140,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() uintptr { return LastCap() }
|
func (direct) lastcap(msg 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() }
|
||||||
@@ -161,11 +150,11 @@ 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(source, target string, flags uintptr) error {
|
func (direct) bindMount(msg Msg, source, target string, flags uintptr) error {
|
||||||
return hostProc.bindMount(source, target, flags)
|
return hostProc.bindMount(msg, source, target, flags)
|
||||||
}
|
}
|
||||||
func (direct) remount(target string, flags uintptr) error {
|
func (direct) remount(msg Msg, target string, flags uintptr) error {
|
||||||
return hostProc.remount(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 {
|
||||||
return mountTmpfs(k, fsname, target, flags, size, perm)
|
return mountTmpfs(k, fsname, target, flags, size, perm)
|
||||||
@@ -232,11 +221,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(format string, v ...any) { log.Printf(format, v...) }
|
func (direct) printf(msg Msg, format string, v ...any) { msg.GetLogger().Printf(format, v...) }
|
||||||
func (direct) fatal(v ...any) { log.Fatal(v...) }
|
func (direct) fatal(msg Msg, v ...any) { msg.GetLogger().Fatal(v...) }
|
||||||
func (direct) fatalf(format string, v ...any) { log.Fatalf(format, v...) }
|
func (direct) fatalf(msg Msg, format string, v ...any) { msg.GetLogger().Fatalf(format, v...) }
|
||||||
func (direct) verbose(v ...any) { msg.Verbose(v...) }
|
|
||||||
func (direct) verbosef(format string, v ...any) { msg.Verbosef(format, v...) }
|
|
||||||
func (direct) suspend() { msg.Suspend() }
|
|
||||||
func (direct) resume() bool { return msg.Resume() }
|
|
||||||
func (direct) beforeExit() { msg.BeforeExit() }
|
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package container
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -136,7 +138,7 @@ func call(name string, args stub.ExpectArgs, ret any, err error) stub.Call {
|
|||||||
|
|
||||||
type simpleTestCase struct {
|
type simpleTestCase struct {
|
||||||
name string
|
name string
|
||||||
f func(k syscallDispatcher) error
|
f func(k *kstub) error
|
||||||
want stub.Expect
|
want stub.Expect
|
||||||
wantErr error
|
wantErr error
|
||||||
}
|
}
|
||||||
@@ -185,11 +187,11 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
|
|||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
state := &setupState{Params: tc.params}
|
|
||||||
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} },
|
||||||
stub.Expect{Calls: slices.Concat(tc.early, []stub.Call{{Name: stub.CallSeparator}}, tc.apply)},
|
stub.Expect{Calls: slices.Concat(tc.early, []stub.Call{{Name: stub.CallSeparator}}, tc.apply)},
|
||||||
)}
|
)}
|
||||||
|
state := &setupState{Params: tc.params, Msg: k}
|
||||||
defer stub.HandleExit(t)
|
defer stub.HandleExit(t)
|
||||||
errEarly := tc.op.early(state, k)
|
errEarly := tc.op.early(state, k)
|
||||||
k.Expects(stub.CallSeparator)
|
k.Expects(stub.CallSeparator)
|
||||||
@@ -327,7 +329,11 @@ 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() uintptr { k.Helper(); return k.Expects("lastcap").Ret.(uintptr) }
|
func (k *kstub) lastcap(msg Msg) uintptr {
|
||||||
|
k.Helper()
|
||||||
|
k.checkMsg(msg)
|
||||||
|
return k.Expects("lastcap").Ret.(uintptr)
|
||||||
|
}
|
||||||
|
|
||||||
func (k *kstub) capset(hdrp *capHeader, datap *[2]capData) error {
|
func (k *kstub) capset(hdrp *capHeader, datap *[2]capData) error {
|
||||||
k.Helper()
|
k.Helper()
|
||||||
@@ -403,16 +409,18 @@ func (k *kstub) receive(key string, e any, fdp *uintptr) (closeFunc func() error
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) bindMount(source, target string, flags uintptr) error {
|
func (k *kstub) bindMount(msg Msg, source, target string, flags uintptr) error {
|
||||||
k.Helper()
|
k.Helper()
|
||||||
|
k.checkMsg(msg)
|
||||||
return k.Expects("bindMount").Error(
|
return k.Expects("bindMount").Error(
|
||||||
stub.CheckArg(k.Stub, "source", source, 0),
|
stub.CheckArg(k.Stub, "source", source, 0),
|
||||||
stub.CheckArg(k.Stub, "target", target, 1),
|
stub.CheckArg(k.Stub, "target", target, 1),
|
||||||
stub.CheckArg(k.Stub, "flags", flags, 2))
|
stub.CheckArg(k.Stub, "flags", flags, 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) remount(target string, flags uintptr) error {
|
func (k *kstub) remount(msg Msg, target string, flags uintptr) error {
|
||||||
k.Helper()
|
k.Helper()
|
||||||
|
k.checkMsg(msg)
|
||||||
return k.Expects("remount").Error(
|
return k.Expects("remount").Error(
|
||||||
stub.CheckArg(k.Stub, "target", target, 0),
|
stub.CheckArg(k.Stub, "target", target, 0),
|
||||||
stub.CheckArg(k.Stub, "flags", flags, 1))
|
stub.CheckArg(k.Stub, "flags", flags, 1))
|
||||||
@@ -694,7 +702,7 @@ func (k *kstub) wait4(pid int, wstatus *syscall.WaitStatus, options int, rusage
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) printf(format string, v ...any) {
|
func (k *kstub) printf(_ 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),
|
||||||
@@ -703,7 +711,7 @@ func (k *kstub) printf(format string, v ...any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) fatal(v ...any) {
|
func (k *kstub) fatal(_ 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 {
|
||||||
@@ -712,7 +720,7 @@ func (k *kstub) fatal(v ...any) {
|
|||||||
panic(stub.PanicExit)
|
panic(stub.PanicExit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) fatalf(format string, v ...any) {
|
func (k *kstub) fatalf(_ 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),
|
||||||
@@ -722,7 +730,35 @@ func (k *kstub) fatalf(format string, v ...any) {
|
|||||||
panic(stub.PanicExit)
|
panic(stub.PanicExit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) verbose(v ...any) {
|
func (k *kstub) checkMsg(msg Msg) {
|
||||||
|
k.Helper()
|
||||||
|
var target *kstub
|
||||||
|
|
||||||
|
if state, ok := msg.(*setupState); ok {
|
||||||
|
target = state.Msg.(*kstub)
|
||||||
|
} else {
|
||||||
|
target = msg.(*kstub)
|
||||||
|
}
|
||||||
|
|
||||||
|
if k != target {
|
||||||
|
panic(fmt.Sprintf("unexpected Msg: %#v", msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *kstub) GetLogger() *log.Logger { panic("unreachable") }
|
||||||
|
func (k *kstub) IsVerbose() bool { panic("unreachable") }
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *kstub) Verbose(v ...any) {
|
||||||
k.Helper()
|
k.Helper()
|
||||||
if k.Expects("verbose").Error(
|
if k.Expects("verbose").Error(
|
||||||
stub.CheckArgReflect(k.Stub, "v", v, 0)) != nil {
|
stub.CheckArgReflect(k.Stub, "v", v, 0)) != nil {
|
||||||
@@ -730,7 +766,7 @@ func (k *kstub) verbose(v ...any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) verbosef(format string, v ...any) {
|
func (k *kstub) Verbosef(format string, v ...any) {
|
||||||
k.Helper()
|
k.Helper()
|
||||||
if k.Expects("verbosef").Error(
|
if k.Expects("verbosef").Error(
|
||||||
stub.CheckArg(k.Stub, "format", format, 0),
|
stub.CheckArg(k.Stub, "format", format, 0),
|
||||||
@@ -739,6 +775,6 @@ func (k *kstub) verbosef(format string, v ...any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) suspend() { k.Helper(); k.Expects("suspend") }
|
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) Resume() bool { k.Helper(); return k.Expects("resume").Ret.(bool) }
|
||||||
func (k *kstub) beforeExit() { k.Helper(); k.Expects("beforeExit") }
|
func (k *kstub) BeforeExit() { k.Helper(); k.Expects("beforeExit") }
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/container/vfs"
|
"hakurei.app/container/vfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ func messageFromError(err error) (string, bool) {
|
|||||||
if m, ok := messagePrefixP[os.PathError]("cannot ", err); ok {
|
if m, ok := messagePrefixP[os.PathError]("cannot ", err); ok {
|
||||||
return m, ok
|
return m, ok
|
||||||
}
|
}
|
||||||
if m, ok := messagePrefixP[AbsoluteError]("", err); ok {
|
if m, ok := messagePrefixP[check.AbsoluteError]("", err); ok {
|
||||||
return m, ok
|
return m, ok
|
||||||
}
|
}
|
||||||
if m, ok := messagePrefix[OpRepeatError]("", err); ok {
|
if m, ok := messagePrefix[OpRepeatError]("", err); ok {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/container/stub"
|
"hakurei.app/container/stub"
|
||||||
"hakurei.app/container/vfs"
|
"hakurei.app/container/vfs"
|
||||||
)
|
)
|
||||||
@@ -34,7 +35,7 @@ func TestMessageFromError(t *testing.T) {
|
|||||||
Err: stub.UniqueError(0xdeadbeef),
|
Err: stub.UniqueError(0xdeadbeef),
|
||||||
}, "cannot mount /sysroot: unique error 3735928559 injected by the test suite", true},
|
}, "cannot mount /sysroot: unique error 3735928559 injected by the test suite", true},
|
||||||
|
|
||||||
{"absolute", &AbsoluteError{"etc/mtab"},
|
{"absolute", &check.AbsoluteError{Pathname: "etc/mtab"},
|
||||||
`path "etc/mtab" is not absolute`, true},
|
`path "etc/mtab" is not absolute`, true},
|
||||||
|
|
||||||
{"repeat", OpRepeatError("autoetc"),
|
{"repeat", OpRepeatError("autoetc"),
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
@@ -11,16 +10,16 @@ var (
|
|||||||
executableOnce sync.Once
|
executableOnce sync.Once
|
||||||
)
|
)
|
||||||
|
|
||||||
func copyExecutable() {
|
func copyExecutable(msg Msg) {
|
||||||
if name, err := os.Executable(); err != nil {
|
if name, err := os.Executable(); err != nil {
|
||||||
msg.BeforeExit()
|
msg.BeforeExit()
|
||||||
log.Fatalf("cannot read executable path: %v", err)
|
msg.GetLogger().Fatalf("cannot read executable path: %v", err)
|
||||||
} else {
|
} else {
|
||||||
executable = name
|
executable = name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func MustExecutable() string {
|
func MustExecutable(msg Msg) string {
|
||||||
executableOnce.Do(copyExecutable)
|
executableOnce.Do(func() { copyExecutable(msg) })
|
||||||
return executable
|
return executable
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
func TestExecutable(t *testing.T) {
|
func TestExecutable(t *testing.T) {
|
||||||
for i := 0; i < 16; i++ {
|
for i := 0; i < 16; i++ {
|
||||||
if got := container.MustExecutable(); got != os.Args[0] {
|
if got := container.MustExecutable(container.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])
|
||||||
}
|
}
|
||||||
|
|||||||
41
container/fhs/abs.go
Normal file
41
container/fhs/abs.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package fhs
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "unsafe"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
)
|
||||||
|
|
||||||
|
/* constants in this file bypass abs check, be extremely careful when changing them! */
|
||||||
|
|
||||||
|
//go:linkname unsafeAbs hakurei.app/container/check.unsafeAbs
|
||||||
|
func unsafeAbs(_ string) *check.Absolute
|
||||||
|
|
||||||
|
var (
|
||||||
|
// AbsRoot is [Root] as [check.Absolute].
|
||||||
|
AbsRoot = unsafeAbs(Root)
|
||||||
|
// AbsEtc is [Etc] as [check.Absolute].
|
||||||
|
AbsEtc = unsafeAbs(Etc)
|
||||||
|
// AbsTmp is [Tmp] as [check.Absolute].
|
||||||
|
AbsTmp = unsafeAbs(Tmp)
|
||||||
|
|
||||||
|
// AbsRun is [Run] as [check.Absolute].
|
||||||
|
AbsRun = unsafeAbs(Run)
|
||||||
|
// AbsRunUser is [RunUser] as [check.Absolute].
|
||||||
|
AbsRunUser = unsafeAbs(RunUser)
|
||||||
|
|
||||||
|
// AbsUsrBin is [UsrBin] as [check.Absolute].
|
||||||
|
AbsUsrBin = unsafeAbs(UsrBin)
|
||||||
|
|
||||||
|
// AbsVar is [Var] as [check.Absolute].
|
||||||
|
AbsVar = unsafeAbs(Var)
|
||||||
|
// AbsVarLib is [VarLib] as [check.Absolute].
|
||||||
|
AbsVarLib = unsafeAbs(VarLib)
|
||||||
|
|
||||||
|
// AbsDev is [Dev] as [check.Absolute].
|
||||||
|
AbsDev = unsafeAbs(Dev)
|
||||||
|
// AbsProc is [Proc] as [check.Absolute].
|
||||||
|
AbsProc = unsafeAbs(Proc)
|
||||||
|
// AbsSys is [Sys] as [check.Absolute].
|
||||||
|
AbsSys = unsafeAbs(Sys)
|
||||||
|
)
|
||||||
38
container/fhs/fhs.go
Normal file
38
container/fhs/fhs.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// Package fhs provides constant and checked pathname values for common FHS paths.
|
||||||
|
package fhs
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Root points to the file system root.
|
||||||
|
Root = "/"
|
||||||
|
// Etc points to the directory for system-specific configuration.
|
||||||
|
Etc = "/etc/"
|
||||||
|
// Tmp points to the place for small temporary files.
|
||||||
|
Tmp = "/tmp/"
|
||||||
|
|
||||||
|
// Run points to a "tmpfs" file system for system packages to place runtime data, socket files, and similar.
|
||||||
|
Run = "/run/"
|
||||||
|
// RunUser points to a directory containing per-user runtime directories,
|
||||||
|
// each usually individually mounted "tmpfs" instances.
|
||||||
|
RunUser = Run + "user/"
|
||||||
|
|
||||||
|
// Usr points to vendor-supplied operating system resources.
|
||||||
|
Usr = "/usr/"
|
||||||
|
// UsrBin points to binaries and executables for user commands that shall appear in the $PATH search path.
|
||||||
|
UsrBin = Usr + "bin/"
|
||||||
|
|
||||||
|
// Var points to persistent, variable system data. Writable during normal system operation.
|
||||||
|
Var = "/var/"
|
||||||
|
// VarLib points to persistent system data.
|
||||||
|
VarLib = Var + "lib/"
|
||||||
|
// VarEmpty points to a nonstandard directory that is usually empty.
|
||||||
|
VarEmpty = Var + "empty/"
|
||||||
|
|
||||||
|
// Dev points to the root directory for device nodes.
|
||||||
|
Dev = "/dev/"
|
||||||
|
// Proc points to a virtual kernel file system exposing the process list and other functionality.
|
||||||
|
Proc = "/proc/"
|
||||||
|
// ProcSys points to a hierarchy below /proc/ that exposes a number of kernel tunables.
|
||||||
|
ProcSys = Proc + "sys/"
|
||||||
|
// Sys points to a virtual kernel file system exposing discovered devices and other functionality.
|
||||||
|
Sys = "/sys/"
|
||||||
|
)
|
||||||
@@ -3,6 +3,7 @@ package container
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
@@ -11,6 +12,7 @@ import (
|
|||||||
. "syscall"
|
. "syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"hakurei.app/container/fhs"
|
||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/seccomp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,7 +31,7 @@ const (
|
|||||||
|
|
||||||
it should be noted that none of this should become relevant at any point since the resulting
|
it should be noted that none of this should become relevant at any point since the resulting
|
||||||
intermediate root tmpfs should be effectively anonymous */
|
intermediate root tmpfs should be effectively anonymous */
|
||||||
intermediateHostPath = FHSProc + "self/fd"
|
intermediateHostPath = fhs.Proc + "self/fd"
|
||||||
|
|
||||||
// setup params file descriptor
|
// setup params file descriptor
|
||||||
setupEnv = "HAKUREI_SETUP"
|
setupEnv = "HAKUREI_SETUP"
|
||||||
@@ -59,6 +61,7 @@ type (
|
|||||||
setupState struct {
|
setupState struct {
|
||||||
nonrepeatable uintptr
|
nonrepeatable uintptr
|
||||||
*Params
|
*Params
|
||||||
|
Msg
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -91,20 +94,23 @@ type initParams struct {
|
|||||||
Verbose bool
|
Verbose bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func Init(prepareLogger func(prefix string), setVerbose func(verbose bool)) {
|
// Init is called by [TryArgv0] if the current process is the container init.
|
||||||
initEntrypoint(direct{}, prepareLogger, setVerbose)
|
func Init(msg Msg) {
|
||||||
|
if msg == nil {
|
||||||
|
panic("attempting to call initEntrypoint with nil msg")
|
||||||
|
}
|
||||||
|
initEntrypoint(direct{}, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setVerbose func(verbose bool)) {
|
func initEntrypoint(k syscallDispatcher, msg Msg) {
|
||||||
k.lockOSThread()
|
k.lockOSThread()
|
||||||
prepareLogger("init")
|
|
||||||
|
|
||||||
if k.getpid() != 1 {
|
if k.getpid() != 1 {
|
||||||
k.fatal("this process must run as pid 1")
|
k.fatal(msg, "this process must run as pid 1")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := k.setPtracer(0); err != nil {
|
if err := k.setPtracer(0); err != nil {
|
||||||
k.verbosef("cannot enable ptrace protection via Yama LSM: %v", err)
|
msg.Verbosef("cannot enable ptrace protection via Yama LSM: %v", err)
|
||||||
// not fatal: this program has no additional privileges at initial program start
|
// not fatal: this program has no additional privileges at initial program start
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,65 +122,65 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
|||||||
)
|
)
|
||||||
if f, err := k.receive(setupEnv, ¶ms, &setupFd); err != nil {
|
if f, err := k.receive(setupEnv, ¶ms, &setupFd); err != nil {
|
||||||
if errors.Is(err, EBADF) {
|
if errors.Is(err, EBADF) {
|
||||||
k.fatal("invalid setup descriptor")
|
k.fatal(msg, "invalid setup descriptor")
|
||||||
}
|
}
|
||||||
if errors.Is(err, ErrReceiveEnv) {
|
if errors.Is(err, ErrReceiveEnv) {
|
||||||
k.fatal("HAKUREI_SETUP not set")
|
k.fatal(msg, "HAKUREI_SETUP not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
k.fatalf("cannot decode init setup payload: %v", err)
|
k.fatalf(msg, "cannot decode init setup payload: %v", err)
|
||||||
} else {
|
} else {
|
||||||
if params.Ops == nil {
|
if params.Ops == nil {
|
||||||
k.fatal("invalid setup parameters")
|
k.fatal(msg, "invalid setup parameters")
|
||||||
}
|
}
|
||||||
if params.ParentPerm == 0 {
|
if params.ParentPerm == 0 {
|
||||||
params.ParentPerm = 0755
|
params.ParentPerm = 0755
|
||||||
}
|
}
|
||||||
|
|
||||||
setVerbose(params.Verbose)
|
msg.SwapVerbose(params.Verbose)
|
||||||
k.verbose("received setup parameters")
|
msg.Verbose("received setup parameters")
|
||||||
closeSetup = f
|
closeSetup = f
|
||||||
offsetSetup = int(setupFd + 1)
|
offsetSetup = int(setupFd + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// write uid/gid map here so parent does not need to set dumpable
|
// write uid/gid map here so parent does not need to set dumpable
|
||||||
if err := k.setDumpable(SUID_DUMP_USER); err != nil {
|
if err := k.setDumpable(SUID_DUMP_USER); err != nil {
|
||||||
k.fatalf("cannot set SUID_DUMP_USER: %v", err)
|
k.fatalf(msg, "cannot set SUID_DUMP_USER: %v", err)
|
||||||
}
|
}
|
||||||
if err := k.writeFile(FHSProc+"self/uid_map",
|
if err := k.writeFile(fhs.Proc+"self/uid_map",
|
||||||
append([]byte{}, strconv.Itoa(params.Uid)+" "+strconv.Itoa(params.HostUid)+" 1\n"...),
|
append([]byte{}, strconv.Itoa(params.Uid)+" "+strconv.Itoa(params.HostUid)+" 1\n"...),
|
||||||
0); err != nil {
|
0); err != nil {
|
||||||
k.fatalf("%v", err)
|
k.fatalf(msg, "%v", err)
|
||||||
}
|
}
|
||||||
if err := k.writeFile(FHSProc+"self/setgroups",
|
if err := k.writeFile(fhs.Proc+"self/setgroups",
|
||||||
[]byte("deny\n"),
|
[]byte("deny\n"),
|
||||||
0); err != nil && !os.IsNotExist(err) {
|
0); err != nil && !os.IsNotExist(err) {
|
||||||
k.fatalf("%v", err)
|
k.fatalf(msg, "%v", err)
|
||||||
}
|
}
|
||||||
if err := k.writeFile(FHSProc+"self/gid_map",
|
if err := k.writeFile(fhs.Proc+"self/gid_map",
|
||||||
append([]byte{}, strconv.Itoa(params.Gid)+" "+strconv.Itoa(params.HostGid)+" 1\n"...),
|
append([]byte{}, strconv.Itoa(params.Gid)+" "+strconv.Itoa(params.HostGid)+" 1\n"...),
|
||||||
0); err != nil {
|
0); err != nil {
|
||||||
k.fatalf("%v", err)
|
k.fatalf(msg, "%v", err)
|
||||||
}
|
}
|
||||||
if err := k.setDumpable(SUID_DUMP_DISABLE); err != nil {
|
if err := k.setDumpable(SUID_DUMP_DISABLE); err != nil {
|
||||||
k.fatalf("cannot set SUID_DUMP_DISABLE: %v", err)
|
k.fatalf(msg, "cannot set SUID_DUMP_DISABLE: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
oldmask := k.umask(0)
|
oldmask := k.umask(0)
|
||||||
if params.Hostname != "" {
|
if params.Hostname != "" {
|
||||||
if err := k.sethostname([]byte(params.Hostname)); err != nil {
|
if err := k.sethostname([]byte(params.Hostname)); err != nil {
|
||||||
k.fatalf("cannot set hostname: %v", err)
|
k.fatalf(msg, "cannot set hostname: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// cache sysctl before pivot_root
|
// cache sysctl before pivot_root
|
||||||
lastcap := k.lastcap()
|
lastcap := k.lastcap(msg)
|
||||||
|
|
||||||
if err := k.mount(zeroString, FHSRoot, 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("cannot make / rslave: %v", err)
|
k.fatalf(msg, "cannot make / rslave: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
state := &setupState{Params: ¶ms.Params}
|
state := &setupState{Params: ¶ms.Params, Msg: msg}
|
||||||
|
|
||||||
/* early is called right before pivot_root into intermediate root;
|
/* early is called right before pivot_root into intermediate root;
|
||||||
this step is mostly for gathering information that would otherwise be difficult to obtain
|
this step is mostly for gathering information that would otherwise be difficult to obtain
|
||||||
@@ -182,41 +188,41 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
|||||||
the state of the mount namespace */
|
the state of the mount namespace */
|
||||||
for i, op := range *params.Ops {
|
for i, op := range *params.Ops {
|
||||||
if op == nil || !op.Valid() {
|
if op == nil || !op.Valid() {
|
||||||
k.fatalf("invalid op at index %d", i)
|
k.fatalf(msg, "invalid op at index %d", i)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := op.early(state, k); err != nil {
|
if err := op.early(state, k); err != nil {
|
||||||
if m, ok := messageFromError(err); ok {
|
if m, ok := messageFromError(err); ok {
|
||||||
k.fatal(m)
|
k.fatal(msg, m)
|
||||||
} else {
|
} else {
|
||||||
k.fatalf("cannot prepare op at index %d: %v", i, err)
|
k.fatalf(msg, "cannot prepare op at index %d: %v", i, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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("cannot mount intermediate root: %v", err)
|
k.fatalf(msg, "cannot mount intermediate root: %v", err)
|
||||||
}
|
}
|
||||||
if err := k.chdir(intermediateHostPath); err != nil {
|
if err := k.chdir(intermediateHostPath); err != nil {
|
||||||
k.fatalf("cannot enter intermediate host path: %v", err)
|
k.fatalf(msg, "cannot enter intermediate host path: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := k.mkdir(sysrootDir, 0755); err != nil {
|
if err := k.mkdir(sysrootDir, 0755); err != nil {
|
||||||
k.fatalf("%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("cannot bind sysroot: %v", err)
|
k.fatalf(msg, "cannot bind sysroot: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := k.mkdir(hostDir, 0755); err != nil {
|
if err := k.mkdir(hostDir, 0755); err != nil {
|
||||||
k.fatalf("%v", err)
|
k.fatalf(msg, "%v", err)
|
||||||
}
|
}
|
||||||
// pivot_root uncovers intermediateHostPath in hostDir
|
// pivot_root uncovers intermediateHostPath in hostDir
|
||||||
if err := k.pivotRoot(intermediateHostPath, hostDir); err != nil {
|
if err := k.pivotRoot(intermediateHostPath, hostDir); err != nil {
|
||||||
k.fatalf("cannot pivot into intermediate root: %v", err)
|
k.fatalf(msg, "cannot pivot into intermediate root: %v", err)
|
||||||
}
|
}
|
||||||
if err := k.chdir(FHSRoot); err != nil {
|
if err := k.chdir(fhs.Root); err != nil {
|
||||||
k.fatalf("cannot enter intermediate root: %v", err)
|
k.fatalf(msg, "cannot enter intermediate root: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* apply is called right after pivot_root and entering the new root;
|
/* apply is called right after pivot_root and entering the new root;
|
||||||
@@ -226,64 +232,64 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
|||||||
for i, op := range *params.Ops {
|
for i, op := range *params.Ops {
|
||||||
// ops already checked during early setup
|
// ops already checked during early setup
|
||||||
if prefix, ok := op.prefix(); ok {
|
if prefix, ok := op.prefix(); ok {
|
||||||
k.verbosef("%s %s", prefix, op)
|
msg.Verbosef("%s %s", prefix, op)
|
||||||
}
|
}
|
||||||
if err := op.apply(state, k); err != nil {
|
if err := op.apply(state, k); err != nil {
|
||||||
if m, ok := messageFromError(err); ok {
|
if m, ok := messageFromError(err); ok {
|
||||||
k.fatal(m)
|
k.fatal(msg, m)
|
||||||
} else {
|
} else {
|
||||||
k.fatalf("cannot apply op at index %d: %v", i, err)
|
k.fatalf(msg, "cannot apply op at index %d: %v", i, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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("cannot make host root rprivate: %v", err)
|
k.fatalf(msg, "cannot make host root rprivate: %v", err)
|
||||||
}
|
}
|
||||||
if err := k.unmount(hostDir, MNT_DETACH); err != nil {
|
if err := k.unmount(hostDir, MNT_DETACH); err != nil {
|
||||||
k.fatalf("cannot unmount host root: %v", err)
|
k.fatalf(msg, "cannot unmount host root: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
var fd int
|
var fd int
|
||||||
if err := IgnoringEINTR(func() (err error) {
|
if err := IgnoringEINTR(func() (err error) {
|
||||||
fd, err = k.open(FHSRoot, O_DIRECTORY|O_RDONLY, 0)
|
fd, err = k.open(fhs.Root, O_DIRECTORY|O_RDONLY, 0)
|
||||||
return
|
return
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
k.fatalf("cannot open intermediate root: %v", err)
|
k.fatalf(msg, "cannot open intermediate root: %v", err)
|
||||||
}
|
}
|
||||||
if err := k.chdir(sysrootPath); err != nil {
|
if err := k.chdir(sysrootPath); err != nil {
|
||||||
k.fatalf("cannot enter sysroot: %v", err)
|
k.fatalf(msg, "cannot enter sysroot: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := k.pivotRoot(".", "."); err != nil {
|
if err := k.pivotRoot(".", "."); err != nil {
|
||||||
k.fatalf("cannot pivot into sysroot: %v", err)
|
k.fatalf(msg, "cannot pivot into sysroot: %v", err)
|
||||||
}
|
}
|
||||||
if err := k.fchdir(fd); err != nil {
|
if err := k.fchdir(fd); err != nil {
|
||||||
k.fatalf("cannot re-enter intermediate root: %v", err)
|
k.fatalf(msg, "cannot re-enter intermediate root: %v", err)
|
||||||
}
|
}
|
||||||
if err := k.unmount(".", MNT_DETACH); err != nil {
|
if err := k.unmount(".", MNT_DETACH); err != nil {
|
||||||
k.fatalf("cannot unmount intermediate root: %v", err)
|
k.fatalf(msg, "cannot unmount intermediate root: %v", err)
|
||||||
}
|
}
|
||||||
if err := k.chdir(FHSRoot); err != nil {
|
if err := k.chdir(fhs.Root); err != nil {
|
||||||
k.fatalf("cannot enter root: %v", err)
|
k.fatalf(msg, "cannot enter root: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := k.close(fd); err != nil {
|
if err := k.close(fd); err != nil {
|
||||||
k.fatalf("cannot close intermediate root: %v", err)
|
k.fatalf(msg, "cannot close intermediate root: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := k.capAmbientClearAll(); err != nil {
|
if err := k.capAmbientClearAll(); err != nil {
|
||||||
k.fatalf("cannot clear the ambient capability set: %v", err)
|
k.fatalf(msg, "cannot clear the ambient capability set: %v", err)
|
||||||
}
|
}
|
||||||
for i := uintptr(0); i <= lastcap; i++ {
|
for i := uintptr(0); i <= lastcap; i++ {
|
||||||
if params.Privileged && i == CAP_SYS_ADMIN {
|
if params.Privileged && i == CAP_SYS_ADMIN {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := k.capBoundingSetDrop(i); err != nil {
|
if err := k.capBoundingSetDrop(i); err != nil {
|
||||||
k.fatalf("cannot drop capability from bounding set: %v", err)
|
k.fatalf(msg, "cannot drop capability from bounding set: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,29 +298,29 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
|||||||
keep[capToIndex(CAP_SYS_ADMIN)] |= capToMask(CAP_SYS_ADMIN)
|
keep[capToIndex(CAP_SYS_ADMIN)] |= capToMask(CAP_SYS_ADMIN)
|
||||||
|
|
||||||
if err := k.capAmbientRaise(CAP_SYS_ADMIN); err != nil {
|
if err := k.capAmbientRaise(CAP_SYS_ADMIN); err != nil {
|
||||||
k.fatalf("cannot raise CAP_SYS_ADMIN: %v", err)
|
k.fatalf(msg, "cannot raise CAP_SYS_ADMIN: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := k.capset(
|
if err := k.capset(
|
||||||
&capHeader{_LINUX_CAPABILITY_VERSION_3, 0},
|
&capHeader{_LINUX_CAPABILITY_VERSION_3, 0},
|
||||||
&[2]capData{{0, keep[0], keep[0]}, {0, keep[1], keep[1]}},
|
&[2]capData{{0, keep[0], keep[0]}, {0, keep[1], keep[1]}},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
k.fatalf("cannot capset: %v", err)
|
k.fatalf(msg, "cannot capset: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !params.SeccompDisable {
|
if !params.SeccompDisable {
|
||||||
rules := params.SeccompRules
|
rules := params.SeccompRules
|
||||||
if len(rules) == 0 { // non-empty rules slice always overrides presets
|
if len(rules) == 0 { // non-empty rules slice always overrides presets
|
||||||
k.verbosef("resolving presets %#x", params.SeccompPresets)
|
msg.Verbosef("resolving presets %#x", params.SeccompPresets)
|
||||||
rules = seccomp.Preset(params.SeccompPresets, params.SeccompFlags)
|
rules = seccomp.Preset(params.SeccompPresets, params.SeccompFlags)
|
||||||
}
|
}
|
||||||
if err := k.seccompLoad(rules, params.SeccompFlags); err != nil {
|
if err := k.seccompLoad(rules, params.SeccompFlags); err != nil {
|
||||||
// this also indirectly asserts PR_SET_NO_NEW_PRIVS
|
// this also indirectly asserts PR_SET_NO_NEW_PRIVS
|
||||||
k.fatalf("cannot load syscall filter: %v", err)
|
k.fatalf(msg, "cannot load syscall filter: %v", err)
|
||||||
}
|
}
|
||||||
k.verbosef("%d filter rules loaded", len(rules))
|
msg.Verbosef("%d filter rules loaded", len(rules))
|
||||||
} else {
|
} else {
|
||||||
k.verbose("syscall filter not configured")
|
msg.Verbose("syscall filter not configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
extraFiles := make([]*os.File, params.Count)
|
extraFiles := make([]*os.File, params.Count)
|
||||||
@@ -331,14 +337,14 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
|||||||
cmd.ExtraFiles = extraFiles
|
cmd.ExtraFiles = extraFiles
|
||||||
cmd.Dir = params.Dir.String()
|
cmd.Dir = params.Dir.String()
|
||||||
|
|
||||||
k.verbosef("starting initial program %s", params.Path)
|
msg.Verbosef("starting initial program %s", params.Path)
|
||||||
if err := k.start(cmd); err != nil {
|
if err := k.start(cmd); err != nil {
|
||||||
k.fatalf("%v", err)
|
k.fatalf(msg, "%v", err)
|
||||||
}
|
}
|
||||||
k.suspend()
|
msg.Suspend()
|
||||||
|
|
||||||
if err := closeSetup(); err != nil {
|
if err := closeSetup(); err != nil {
|
||||||
k.printf("cannot close setup pipe: %v", err)
|
k.printf(msg, "cannot close setup pipe: %v", err)
|
||||||
// not fatal
|
// not fatal
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,7 +378,7 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !errors.Is(err, ECHILD) {
|
if !errors.Is(err, ECHILD) {
|
||||||
k.printf("unexpected wait4 response: %v", err)
|
k.printf(msg, "unexpected wait4 response: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
close(done)
|
close(done)
|
||||||
@@ -389,50 +395,50 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case s := <-sig:
|
case s := <-sig:
|
||||||
if k.resume() {
|
if msg.Resume() {
|
||||||
k.verbosef("%s after process start", s.String())
|
msg.Verbosef("%s after process start", s.String())
|
||||||
} else {
|
} else {
|
||||||
k.verbosef("got %s", s.String())
|
msg.Verbosef("got %s", s.String())
|
||||||
}
|
}
|
||||||
if s == CancelSignal && params.ForwardCancel && cmd.Process != nil {
|
if s == CancelSignal && params.ForwardCancel && cmd.Process != nil {
|
||||||
k.verbose("forwarding context cancellation")
|
msg.Verbose("forwarding context cancellation")
|
||||||
if err := k.signal(cmd, os.Interrupt); err != nil {
|
if err := k.signal(cmd, os.Interrupt); err != nil {
|
||||||
k.printf("cannot forward cancellation: %v", err)
|
k.printf(msg, "cannot forward cancellation: %v", err)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
k.beforeExit()
|
msg.BeforeExit()
|
||||||
k.exit(0)
|
k.exit(0)
|
||||||
|
|
||||||
case w := <-info:
|
case w := <-info:
|
||||||
if w.wpid == cmd.Process.Pid {
|
if w.wpid == cmd.Process.Pid {
|
||||||
// initial process exited, output is most likely available again
|
// initial process exited, output is most likely available again
|
||||||
k.resume()
|
msg.Resume()
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case w.wstatus.Exited():
|
case w.wstatus.Exited():
|
||||||
r = w.wstatus.ExitStatus()
|
r = w.wstatus.ExitStatus()
|
||||||
k.verbosef("initial process exited with code %d", w.wstatus.ExitStatus())
|
msg.Verbosef("initial process exited with code %d", w.wstatus.ExitStatus())
|
||||||
|
|
||||||
case w.wstatus.Signaled():
|
case w.wstatus.Signaled():
|
||||||
r = 128 + int(w.wstatus.Signal())
|
r = 128 + int(w.wstatus.Signal())
|
||||||
k.verbosef("initial process exited with signal %s", w.wstatus.Signal())
|
msg.Verbosef("initial process exited with signal %s", w.wstatus.Signal())
|
||||||
|
|
||||||
default:
|
default:
|
||||||
r = 255
|
r = 255
|
||||||
k.verbosef("initial process exited with status %#x", w.wstatus)
|
msg.Verbosef("initial process exited with status %#x", w.wstatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() { time.Sleep(params.AdoptWaitDelay); close(timeout) }()
|
go func() { time.Sleep(params.AdoptWaitDelay); close(timeout) }()
|
||||||
}
|
}
|
||||||
|
|
||||||
case <-done:
|
case <-done:
|
||||||
k.beforeExit()
|
msg.BeforeExit()
|
||||||
k.exit(r)
|
k.exit(r)
|
||||||
|
|
||||||
case <-timeout:
|
case <-timeout:
|
||||||
k.printf("timeout exceeded waiting for lingering processes")
|
k.printf(msg, "timeout exceeded waiting for lingering processes")
|
||||||
k.beforeExit()
|
msg.BeforeExit()
|
||||||
k.exit(r)
|
k.exit(r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -441,10 +447,16 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
|||||||
const initName = "init"
|
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".
|
||||||
func TryArgv0(v Msg, prepare func(prefix string), setVerbose func(verbose bool)) {
|
// If a nil msg is passed, the system logger is used instead.
|
||||||
|
func TryArgv0(msg Msg) {
|
||||||
|
if msg == nil {
|
||||||
|
log.SetPrefix(initName + ": ")
|
||||||
|
log.SetFlags(0)
|
||||||
|
msg = 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 {
|
||||||
msg = v
|
Init(msg)
|
||||||
Init(prepare, setVerbose)
|
|
||||||
msg.BeforeExit()
|
msg.BeforeExit()
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,12 +5,15 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"hakurei.app/container/bits"
|
||||||
|
"hakurei.app/container/check"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { gob.Register(new(BindMountOp)) }
|
func init() { gob.Register(new(BindMountOp)) }
|
||||||
|
|
||||||
// Bind appends an [Op] that bind mounts host path [BindMountOp.Source] on container path [BindMountOp.Target].
|
// Bind appends an [Op] that bind mounts host path [BindMountOp.Source] on container path [BindMountOp.Target].
|
||||||
func (f *Ops) Bind(source, target *Absolute, flags int) *Ops {
|
func (f *Ops) Bind(source, target *check.Absolute, flags int) *Ops {
|
||||||
*f = append(*f, &BindMountOp{nil, source, target, flags})
|
*f = append(*f, &BindMountOp{nil, source, target, flags})
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
@@ -18,50 +21,39 @@ func (f *Ops) Bind(source, target *Absolute, flags int) *Ops {
|
|||||||
// BindMountOp bind mounts host path Source on container path Target.
|
// BindMountOp bind mounts host path Source on container path Target.
|
||||||
// Note that Flags uses bits declared in this package and should not be set with constants in [syscall].
|
// Note that Flags uses bits declared in this package and should not be set with constants in [syscall].
|
||||||
type BindMountOp struct {
|
type BindMountOp struct {
|
||||||
sourceFinal, Source, Target *Absolute
|
sourceFinal, Source, Target *check.Absolute
|
||||||
|
|
||||||
Flags int
|
Flags int
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
// BindOptional skips nonexistent host paths.
|
|
||||||
BindOptional = 1 << iota
|
|
||||||
// BindWritable mounts filesystem read-write.
|
|
||||||
BindWritable
|
|
||||||
// BindDevice allows access to devices (special files) on this filesystem.
|
|
||||||
BindDevice
|
|
||||||
// BindEnsure attempts to create the host path if it does not exist.
|
|
||||||
BindEnsure
|
|
||||||
)
|
|
||||||
|
|
||||||
func (b *BindMountOp) Valid() bool {
|
func (b *BindMountOp) Valid() bool {
|
||||||
return b != nil &&
|
return b != nil &&
|
||||||
b.Source != nil && b.Target != nil &&
|
b.Source != nil && b.Target != nil &&
|
||||||
b.Flags&(BindOptional|BindEnsure) != (BindOptional|BindEnsure)
|
b.Flags&(bits.BindOptional|bits.BindEnsure) != (bits.BindOptional|bits.BindEnsure)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BindMountOp) early(_ *setupState, k syscallDispatcher) error {
|
func (b *BindMountOp) early(_ *setupState, k syscallDispatcher) error {
|
||||||
if b.Flags&BindEnsure != 0 {
|
if b.Flags&bits.BindEnsure != 0 {
|
||||||
if err := k.mkdirAll(b.Source.String(), 0700); err != nil {
|
if err := k.mkdirAll(b.Source.String(), 0700); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if pathname, err := k.evalSymlinks(b.Source.String()); err != nil {
|
if pathname, err := k.evalSymlinks(b.Source.String()); err != nil {
|
||||||
if os.IsNotExist(err) && b.Flags&BindOptional != 0 {
|
if os.IsNotExist(err) && b.Flags&bits.BindOptional != 0 {
|
||||||
// leave sourceFinal as nil
|
// leave sourceFinal as nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
b.sourceFinal, err = NewAbs(pathname)
|
b.sourceFinal, err = check.NewAbs(pathname)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BindMountOp) apply(_ *setupState, k syscallDispatcher) error {
|
func (b *BindMountOp) apply(state *setupState, k syscallDispatcher) error {
|
||||||
if b.sourceFinal == nil {
|
if b.sourceFinal == nil {
|
||||||
if b.Flags&BindOptional == 0 {
|
if b.Flags&bits.BindOptional == 0 {
|
||||||
// unreachable
|
// unreachable
|
||||||
return OpStateError("bind")
|
return OpStateError("bind")
|
||||||
}
|
}
|
||||||
@@ -84,19 +76,19 @@ func (b *BindMountOp) apply(_ *setupState, k syscallDispatcher) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var flags uintptr = syscall.MS_REC
|
var flags uintptr = syscall.MS_REC
|
||||||
if b.Flags&BindWritable == 0 {
|
if b.Flags&bits.BindWritable == 0 {
|
||||||
flags |= syscall.MS_RDONLY
|
flags |= syscall.MS_RDONLY
|
||||||
}
|
}
|
||||||
if b.Flags&BindDevice == 0 {
|
if b.Flags&bits.BindDevice == 0 {
|
||||||
flags |= syscall.MS_NODEV
|
flags |= syscall.MS_NODEV
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.sourceFinal.String() == b.Target.String() {
|
if b.sourceFinal.String() == b.Target.String() {
|
||||||
k.verbosef("mounting %q flags %#x", target, flags)
|
state.Verbosef("mounting %q flags %#x", target, flags)
|
||||||
} else {
|
} else {
|
||||||
k.verbosef("mounting %q on %q flags %#x", source, target, flags)
|
state.Verbosef("mounting %q on %q flags %#x", source, target, flags)
|
||||||
}
|
}
|
||||||
return k.bindMount(source, target, flags)
|
return k.bindMount(state, source, target, flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BindMountOp) Is(op Op) bool {
|
func (b *BindMountOp) Is(op Op) bool {
|
||||||
|
|||||||
@@ -6,30 +6,32 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/bits"
|
||||||
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/container/stub"
|
"hakurei.app/container/stub"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBindMountOp(t *testing.T) {
|
func TestBindMountOp(t *testing.T) {
|
||||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||||
{"ENOENT not optional", new(Params), &BindMountOp{
|
{"ENOENT not optional", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/bin/"),
|
Source: check.MustAbs("/bin/"),
|
||||||
Target: MustAbs("/bin/"),
|
Target: check.MustAbs("/bin/"),
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "", syscall.ENOENT),
|
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "", syscall.ENOENT),
|
||||||
}, syscall.ENOENT, nil, nil},
|
}, syscall.ENOENT, nil, nil},
|
||||||
|
|
||||||
{"skip optional", new(Params), &BindMountOp{
|
{"skip optional", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/bin/"),
|
Source: check.MustAbs("/bin/"),
|
||||||
Target: MustAbs("/bin/"),
|
Target: check.MustAbs("/bin/"),
|
||||||
Flags: BindOptional,
|
Flags: bits.BindOptional,
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "", syscall.ENOENT),
|
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "", syscall.ENOENT),
|
||||||
}, nil, nil, nil},
|
}, nil, nil, nil},
|
||||||
|
|
||||||
{"success optional", new(Params), &BindMountOp{
|
{"success optional", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/bin/"),
|
Source: check.MustAbs("/bin/"),
|
||||||
Target: MustAbs("/bin/"),
|
Target: check.MustAbs("/bin/"),
|
||||||
Flags: BindOptional,
|
Flags: bits.BindOptional,
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", nil),
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
@@ -40,9 +42,9 @@ func TestBindMountOp(t *testing.T) {
|
|||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"ensureFile device", new(Params), &BindMountOp{
|
{"ensureFile device", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/dev/null"),
|
Source: check.MustAbs("/dev/null"),
|
||||||
Target: MustAbs("/dev/null"),
|
Target: check.MustAbs("/dev/null"),
|
||||||
Flags: BindWritable | BindDevice,
|
Flags: bits.BindWritable | bits.BindDevice,
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/dev/null"}, "/dev/null", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/dev/null"}, "/dev/null", nil),
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
@@ -51,17 +53,17 @@ func TestBindMountOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(5)},
|
}, stub.UniqueError(5)},
|
||||||
|
|
||||||
{"mkdirAll ensure", new(Params), &BindMountOp{
|
{"mkdirAll ensure", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/bin/"),
|
Source: check.MustAbs("/bin/"),
|
||||||
Target: MustAbs("/bin/"),
|
Target: check.MustAbs("/bin/"),
|
||||||
Flags: BindEnsure,
|
Flags: bits.BindEnsure,
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("mkdirAll", stub.ExpectArgs{"/bin/", os.FileMode(0700)}, nil, stub.UniqueError(4)),
|
call("mkdirAll", stub.ExpectArgs{"/bin/", os.FileMode(0700)}, nil, stub.UniqueError(4)),
|
||||||
}, stub.UniqueError(4), nil, nil},
|
}, stub.UniqueError(4), nil, nil},
|
||||||
|
|
||||||
{"success ensure", new(Params), &BindMountOp{
|
{"success ensure", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/bin/"),
|
Source: check.MustAbs("/bin/"),
|
||||||
Target: MustAbs("/usr/bin/"),
|
Target: check.MustAbs("/usr/bin/"),
|
||||||
Flags: BindEnsure,
|
Flags: bits.BindEnsure,
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("mkdirAll", stub.ExpectArgs{"/bin/", os.FileMode(0700)}, nil, nil),
|
call("mkdirAll", stub.ExpectArgs{"/bin/", os.FileMode(0700)}, nil, nil),
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", nil),
|
||||||
@@ -73,9 +75,9 @@ func TestBindMountOp(t *testing.T) {
|
|||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"success device ro", new(Params), &BindMountOp{
|
{"success device ro", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/dev/null"),
|
Source: check.MustAbs("/dev/null"),
|
||||||
Target: MustAbs("/dev/null"),
|
Target: check.MustAbs("/dev/null"),
|
||||||
Flags: BindDevice,
|
Flags: bits.BindDevice,
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/dev/null"}, "/dev/null", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/dev/null"}, "/dev/null", nil),
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
@@ -86,9 +88,9 @@ func TestBindMountOp(t *testing.T) {
|
|||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"success device", new(Params), &BindMountOp{
|
{"success device", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/dev/null"),
|
Source: check.MustAbs("/dev/null"),
|
||||||
Target: MustAbs("/dev/null"),
|
Target: check.MustAbs("/dev/null"),
|
||||||
Flags: BindWritable | BindDevice,
|
Flags: bits.BindWritable | bits.BindDevice,
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/dev/null"}, "/dev/null", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/dev/null"}, "/dev/null", nil),
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
@@ -99,15 +101,15 @@ func TestBindMountOp(t *testing.T) {
|
|||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"evalSymlinks", new(Params), &BindMountOp{
|
{"evalSymlinks", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/bin/"),
|
Source: check.MustAbs("/bin/"),
|
||||||
Target: MustAbs("/bin/"),
|
Target: check.MustAbs("/bin/"),
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", stub.UniqueError(3)),
|
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", stub.UniqueError(3)),
|
||||||
}, stub.UniqueError(3), nil, nil},
|
}, stub.UniqueError(3), nil, nil},
|
||||||
|
|
||||||
{"stat", new(Params), &BindMountOp{
|
{"stat", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/bin/"),
|
Source: check.MustAbs("/bin/"),
|
||||||
Target: MustAbs("/bin/"),
|
Target: check.MustAbs("/bin/"),
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", nil),
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
@@ -115,8 +117,8 @@ func TestBindMountOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(2)},
|
}, stub.UniqueError(2)},
|
||||||
|
|
||||||
{"mkdirAll", new(Params), &BindMountOp{
|
{"mkdirAll", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/bin/"),
|
Source: check.MustAbs("/bin/"),
|
||||||
Target: MustAbs("/bin/"),
|
Target: check.MustAbs("/bin/"),
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", nil),
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
@@ -125,8 +127,8 @@ func TestBindMountOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(1)},
|
}, stub.UniqueError(1)},
|
||||||
|
|
||||||
{"bindMount", new(Params), &BindMountOp{
|
{"bindMount", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/bin/"),
|
Source: check.MustAbs("/bin/"),
|
||||||
Target: MustAbs("/bin/"),
|
Target: check.MustAbs("/bin/"),
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", nil),
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
@@ -137,8 +139,8 @@ func TestBindMountOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(0)},
|
}, stub.UniqueError(0)},
|
||||||
|
|
||||||
{"success eval equals", new(Params), &BindMountOp{
|
{"success eval equals", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/bin/"),
|
Source: check.MustAbs("/bin/"),
|
||||||
Target: MustAbs("/bin/"),
|
Target: check.MustAbs("/bin/"),
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/bin", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/bin", nil),
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
@@ -149,8 +151,8 @@ func TestBindMountOp(t *testing.T) {
|
|||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"success", new(Params), &BindMountOp{
|
{"success", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/bin/"),
|
Source: check.MustAbs("/bin/"),
|
||||||
Target: MustAbs("/bin/"),
|
Target: check.MustAbs("/bin/"),
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", nil),
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
@@ -173,21 +175,21 @@ func TestBindMountOp(t *testing.T) {
|
|||||||
checkOpsValid(t, []opValidTestCase{
|
checkOpsValid(t, []opValidTestCase{
|
||||||
{"nil", (*BindMountOp)(nil), false},
|
{"nil", (*BindMountOp)(nil), false},
|
||||||
{"zero", new(BindMountOp), false},
|
{"zero", new(BindMountOp), false},
|
||||||
{"nil source", &BindMountOp{Target: MustAbs("/")}, false},
|
{"nil source", &BindMountOp{Target: check.MustAbs("/")}, false},
|
||||||
{"nil target", &BindMountOp{Source: MustAbs("/")}, false},
|
{"nil target", &BindMountOp{Source: check.MustAbs("/")}, false},
|
||||||
{"flag optional ensure", &BindMountOp{Source: MustAbs("/"), Target: MustAbs("/"), Flags: BindOptional | BindEnsure}, false},
|
{"flag optional ensure", &BindMountOp{Source: check.MustAbs("/"), Target: check.MustAbs("/"), Flags: bits.BindOptional | bits.BindEnsure}, false},
|
||||||
{"valid", &BindMountOp{Source: MustAbs("/"), Target: MustAbs("/")}, true},
|
{"valid", &BindMountOp{Source: check.MustAbs("/"), Target: check.MustAbs("/")}, true},
|
||||||
})
|
})
|
||||||
|
|
||||||
checkOpsBuilder(t, []opsBuilderTestCase{
|
checkOpsBuilder(t, []opsBuilderTestCase{
|
||||||
{"autoetc", new(Ops).Bind(
|
{"autoetc", new(Ops).Bind(
|
||||||
MustAbs("/etc/"),
|
check.MustAbs("/etc/"),
|
||||||
MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
check.MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
||||||
0,
|
0,
|
||||||
), Ops{
|
), Ops{
|
||||||
&BindMountOp{
|
&BindMountOp{
|
||||||
Source: MustAbs("/etc/"),
|
Source: check.MustAbs("/etc/"),
|
||||||
Target: MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
Target: check.MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
})
|
})
|
||||||
@@ -196,45 +198,45 @@ func TestBindMountOp(t *testing.T) {
|
|||||||
{"zero", new(BindMountOp), new(BindMountOp), false},
|
{"zero", new(BindMountOp), new(BindMountOp), false},
|
||||||
|
|
||||||
{"internal ne", &BindMountOp{
|
{"internal ne", &BindMountOp{
|
||||||
Source: MustAbs("/etc/"),
|
Source: check.MustAbs("/etc/"),
|
||||||
Target: MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
Target: check.MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
||||||
}, &BindMountOp{
|
}, &BindMountOp{
|
||||||
Source: MustAbs("/etc/"),
|
Source: check.MustAbs("/etc/"),
|
||||||
Target: MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
Target: check.MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
||||||
sourceFinal: MustAbs("/etc/"),
|
sourceFinal: check.MustAbs("/etc/"),
|
||||||
}, true},
|
}, true},
|
||||||
|
|
||||||
{"flags differs", &BindMountOp{
|
{"flags differs", &BindMountOp{
|
||||||
Source: MustAbs("/etc/"),
|
Source: check.MustAbs("/etc/"),
|
||||||
Target: MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
Target: check.MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
||||||
}, &BindMountOp{
|
}, &BindMountOp{
|
||||||
Source: MustAbs("/etc/"),
|
Source: check.MustAbs("/etc/"),
|
||||||
Target: MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
Target: check.MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
||||||
Flags: BindOptional,
|
Flags: bits.BindOptional,
|
||||||
}, false},
|
}, false},
|
||||||
|
|
||||||
{"source differs", &BindMountOp{
|
{"source differs", &BindMountOp{
|
||||||
Source: MustAbs("/.hakurei/etc/"),
|
Source: check.MustAbs("/.hakurei/etc/"),
|
||||||
Target: MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
Target: check.MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
||||||
}, &BindMountOp{
|
}, &BindMountOp{
|
||||||
Source: MustAbs("/etc/"),
|
Source: check.MustAbs("/etc/"),
|
||||||
Target: MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
Target: check.MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
||||||
}, false},
|
}, false},
|
||||||
|
|
||||||
{"target differs", &BindMountOp{
|
{"target differs", &BindMountOp{
|
||||||
Source: MustAbs("/etc/"),
|
Source: check.MustAbs("/etc/"),
|
||||||
Target: MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
Target: check.MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
||||||
}, &BindMountOp{
|
}, &BindMountOp{
|
||||||
Source: MustAbs("/etc/"),
|
Source: check.MustAbs("/etc/"),
|
||||||
Target: MustAbs("/etc/"),
|
Target: check.MustAbs("/etc/"),
|
||||||
}, false},
|
}, false},
|
||||||
|
|
||||||
{"equals", &BindMountOp{
|
{"equals", &BindMountOp{
|
||||||
Source: MustAbs("/etc/"),
|
Source: check.MustAbs("/etc/"),
|
||||||
Target: MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
Target: check.MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
||||||
}, &BindMountOp{
|
}, &BindMountOp{
|
||||||
Source: MustAbs("/etc/"),
|
Source: check.MustAbs("/etc/"),
|
||||||
Target: MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
Target: check.MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
||||||
}, true},
|
}, true},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -242,14 +244,14 @@ func TestBindMountOp(t *testing.T) {
|
|||||||
{"invalid", new(BindMountOp), "mounting", "<invalid>"},
|
{"invalid", new(BindMountOp), "mounting", "<invalid>"},
|
||||||
|
|
||||||
{"autoetc", &BindMountOp{
|
{"autoetc", &BindMountOp{
|
||||||
Source: MustAbs("/etc/"),
|
Source: check.MustAbs("/etc/"),
|
||||||
Target: MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
Target: check.MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
|
||||||
}, "mounting", `"/etc/" on "/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659" flags 0x0`},
|
}, "mounting", `"/etc/" on "/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659" flags 0x0`},
|
||||||
|
|
||||||
{"hostdev", &BindMountOp{
|
{"hostdev", &BindMountOp{
|
||||||
Source: MustAbs("/dev/"),
|
Source: check.MustAbs("/dev/"),
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Flags: BindWritable | BindDevice,
|
Flags: bits.BindWritable | bits.BindDevice,
|
||||||
}, "mounting", `"/dev/" flags 0x6`},
|
}, "mounting", `"/dev/" flags 0x6`},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,19 +5,22 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
. "syscall"
|
. "syscall"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
"hakurei.app/container/fhs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { gob.Register(new(MountDevOp)) }
|
func init() { gob.Register(new(MountDevOp)) }
|
||||||
|
|
||||||
// Dev appends an [Op] that mounts a subset of host /dev.
|
// Dev appends an [Op] that mounts a subset of host /dev.
|
||||||
func (f *Ops) Dev(target *Absolute, mqueue bool) *Ops {
|
func (f *Ops) Dev(target *check.Absolute, mqueue bool) *Ops {
|
||||||
*f = append(*f, &MountDevOp{target, mqueue, false})
|
*f = append(*f, &MountDevOp{target, mqueue, false})
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
// DevWritable appends an [Op] that mounts a writable subset of host /dev.
|
// DevWritable appends an [Op] that mounts a writable subset of host /dev.
|
||||||
// There is usually no good reason to write to /dev, so this should always be followed by a [RemountOp].
|
// There is usually no good reason to write to /dev, so this should always be followed by a [RemountOp].
|
||||||
func (f *Ops) DevWritable(target *Absolute, mqueue bool) *Ops {
|
func (f *Ops) DevWritable(target *check.Absolute, mqueue bool) *Ops {
|
||||||
*f = append(*f, &MountDevOp{target, mqueue, true})
|
*f = append(*f, &MountDevOp{target, mqueue, true})
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
@@ -26,7 +29,7 @@ func (f *Ops) DevWritable(target *Absolute, mqueue bool) *Ops {
|
|||||||
// If Mqueue is true, a private instance of [FstypeMqueue] is mounted.
|
// If Mqueue is true, a private instance of [FstypeMqueue] is mounted.
|
||||||
// If Write is true, the resulting mount point is left writable.
|
// If Write is true, the resulting mount point is left writable.
|
||||||
type MountDevOp struct {
|
type MountDevOp struct {
|
||||||
Target *Absolute
|
Target *check.Absolute
|
||||||
Mqueue bool
|
Mqueue bool
|
||||||
Write bool
|
Write bool
|
||||||
}
|
}
|
||||||
@@ -46,7 +49,8 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := k.bindMount(
|
if err := k.bindMount(
|
||||||
toHost(FHSDev+name),
|
state,
|
||||||
|
toHost(fhs.Dev+name),
|
||||||
targetPath,
|
targetPath,
|
||||||
0,
|
0,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
@@ -55,15 +59,15 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
}
|
}
|
||||||
for i, name := range []string{"stdin", "stdout", "stderr"} {
|
for i, name := range []string{"stdin", "stdout", "stderr"} {
|
||||||
if err := k.symlink(
|
if err := k.symlink(
|
||||||
FHSProc+"self/fd/"+string(rune(i+'0')),
|
fhs.Proc+"self/fd/"+string(rune(i+'0')),
|
||||||
path.Join(target, name),
|
path.Join(target, name),
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, pair := range [][2]string{
|
for _, pair := range [][2]string{
|
||||||
{FHSProc + "self/fd", "fd"},
|
{fhs.Proc + "self/fd", "fd"},
|
||||||
{FHSProc + "kcore", "core"},
|
{fhs.Proc + "kcore", "core"},
|
||||||
{"pts/ptmx", "ptmx"},
|
{"pts/ptmx", "ptmx"},
|
||||||
} {
|
} {
|
||||||
if err := k.symlink(pair[0], path.Join(target, pair[1])); err != nil {
|
if err := k.symlink(pair[0], path.Join(target, pair[1])); err != nil {
|
||||||
@@ -93,6 +97,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
if name, err := k.readlink(hostProc.stdout()); err != nil {
|
if name, err := k.readlink(hostProc.stdout()); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if err = k.bindMount(
|
} else if err = k.bindMount(
|
||||||
|
state,
|
||||||
toHost(name),
|
toHost(name),
|
||||||
consolePath,
|
consolePath,
|
||||||
0,
|
0,
|
||||||
@@ -116,7 +121,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := k.remount(target, MS_RDONLY); err != nil {
|
if err := k.remount(state, target, MS_RDONLY); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return k.mountTmpfs(SourceTmpfs, devShmPath, MS_NOSUID|MS_NODEV, 0, 01777)
|
return k.mountTmpfs(SourceTmpfs, devShmPath, MS_NOSUID|MS_NODEV, 0, 01777)
|
||||||
|
|||||||
@@ -4,20 +4,21 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/container/stub"
|
"hakurei.app/container/stub"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMountDevOp(t *testing.T) {
|
func TestMountDevOp(t *testing.T) {
|
||||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||||
{"mountTmpfs", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"mountTmpfs", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, stub.UniqueError(27)),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, stub.UniqueError(27)),
|
||||||
}, stub.UniqueError(27)},
|
}, stub.UniqueError(27)},
|
||||||
|
|
||||||
{"ensureFile null", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"ensureFile null", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -25,7 +26,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(26)},
|
}, stub.UniqueError(26)},
|
||||||
|
|
||||||
{"bindMount null", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"bindMount null", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -34,7 +35,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(25)},
|
}, stub.UniqueError(25)},
|
||||||
|
|
||||||
{"ensureFile zero", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"ensureFile zero", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -44,7 +45,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(24)},
|
}, stub.UniqueError(24)},
|
||||||
|
|
||||||
{"bindMount zero", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"bindMount zero", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -55,7 +56,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(23)},
|
}, stub.UniqueError(23)},
|
||||||
|
|
||||||
{"ensureFile full", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"ensureFile full", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -67,7 +68,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(22)},
|
}, stub.UniqueError(22)},
|
||||||
|
|
||||||
{"bindMount full", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"bindMount full", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -80,7 +81,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(21)},
|
}, stub.UniqueError(21)},
|
||||||
|
|
||||||
{"ensureFile random", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"ensureFile random", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -94,7 +95,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(20)},
|
}, stub.UniqueError(20)},
|
||||||
|
|
||||||
{"bindMount random", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"bindMount random", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -109,7 +110,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(19)},
|
}, stub.UniqueError(19)},
|
||||||
|
|
||||||
{"ensureFile urandom", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"ensureFile urandom", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -125,7 +126,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(18)},
|
}, stub.UniqueError(18)},
|
||||||
|
|
||||||
{"bindMount urandom", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"bindMount urandom", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -142,7 +143,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(17)},
|
}, stub.UniqueError(17)},
|
||||||
|
|
||||||
{"ensureFile tty", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"ensureFile tty", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -160,7 +161,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(16)},
|
}, stub.UniqueError(16)},
|
||||||
|
|
||||||
{"bindMount tty", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"bindMount tty", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -179,7 +180,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(15)},
|
}, stub.UniqueError(15)},
|
||||||
|
|
||||||
{"symlink stdin", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"symlink stdin", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -199,7 +200,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(14)},
|
}, stub.UniqueError(14)},
|
||||||
|
|
||||||
{"symlink stdout", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"symlink stdout", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -220,7 +221,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(13)},
|
}, stub.UniqueError(13)},
|
||||||
|
|
||||||
{"symlink stderr", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"symlink stderr", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -242,7 +243,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(12)},
|
}, stub.UniqueError(12)},
|
||||||
|
|
||||||
{"symlink fd", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"symlink fd", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -265,7 +266,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(11)},
|
}, stub.UniqueError(11)},
|
||||||
|
|
||||||
{"symlink kcore", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"symlink kcore", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -289,7 +290,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(10)},
|
}, stub.UniqueError(10)},
|
||||||
|
|
||||||
{"symlink ptmx", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"symlink ptmx", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -314,7 +315,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(9)},
|
}, stub.UniqueError(9)},
|
||||||
|
|
||||||
{"mkdir shm", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"mkdir shm", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -340,7 +341,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(8)},
|
}, stub.UniqueError(8)},
|
||||||
|
|
||||||
{"mkdir devpts", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"mkdir devpts", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -367,7 +368,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(7)},
|
}, stub.UniqueError(7)},
|
||||||
|
|
||||||
{"mount devpts", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"mount devpts", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -395,7 +396,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(6)},
|
}, stub.UniqueError(6)},
|
||||||
|
|
||||||
{"ensureFile stdout", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"ensureFile stdout", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -425,7 +426,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(5)},
|
}, stub.UniqueError(5)},
|
||||||
|
|
||||||
{"readlink stdout", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"readlink stdout", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -456,7 +457,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(4)},
|
}, stub.UniqueError(4)},
|
||||||
|
|
||||||
{"bindMount stdout", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"bindMount stdout", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -488,7 +489,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(3)},
|
}, stub.UniqueError(3)},
|
||||||
|
|
||||||
{"mkdir mqueue", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"mkdir mqueue", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -521,7 +522,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(2)},
|
}, stub.UniqueError(2)},
|
||||||
|
|
||||||
{"mount mqueue", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"mount mqueue", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -555,7 +556,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(1)},
|
}, stub.UniqueError(1)},
|
||||||
|
|
||||||
{"success no session", &Params{ParentPerm: 0755}, &MountDevOp{
|
{"success no session", &Params{ParentPerm: 0755}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
Write: true,
|
Write: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
@@ -586,7 +587,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"success no tty", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"success no tty", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
Write: true,
|
Write: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
@@ -618,7 +619,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"remount", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"remount", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
call("ensureFile", stub.ExpectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil),
|
call("ensureFile", stub.ExpectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil),
|
||||||
@@ -650,7 +651,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(0)},
|
}, stub.UniqueError(0)},
|
||||||
|
|
||||||
{"success no mqueue", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"success no mqueue", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
call("ensureFile", stub.ExpectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil),
|
call("ensureFile", stub.ExpectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil),
|
||||||
@@ -683,7 +684,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"success rw", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"success rw", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
Write: true,
|
Write: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
@@ -718,7 +719,7 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"success", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
{"success", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
call("mountTmpfs", stub.ExpectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil),
|
||||||
@@ -757,20 +758,20 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
checkOpsValid(t, []opValidTestCase{
|
checkOpsValid(t, []opValidTestCase{
|
||||||
{"nil", (*MountDevOp)(nil), false},
|
{"nil", (*MountDevOp)(nil), false},
|
||||||
{"zero", new(MountDevOp), false},
|
{"zero", new(MountDevOp), false},
|
||||||
{"valid", &MountDevOp{Target: MustAbs("/dev/")}, true},
|
{"valid", &MountDevOp{Target: check.MustAbs("/dev/")}, true},
|
||||||
})
|
})
|
||||||
|
|
||||||
checkOpsBuilder(t, []opsBuilderTestCase{
|
checkOpsBuilder(t, []opsBuilderTestCase{
|
||||||
{"dev", new(Ops).Dev(MustAbs("/dev/"), true), Ops{
|
{"dev", new(Ops).Dev(check.MustAbs("/dev/"), true), Ops{
|
||||||
&MountDevOp{
|
&MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
|
|
||||||
{"dev writable", new(Ops).DevWritable(MustAbs("/.hakurei/dev/"), false), Ops{
|
{"dev writable", new(Ops).DevWritable(check.MustAbs("/.hakurei/dev/"), false), Ops{
|
||||||
&MountDevOp{
|
&MountDevOp{
|
||||||
Target: MustAbs("/.hakurei/dev/"),
|
Target: check.MustAbs("/.hakurei/dev/"),
|
||||||
Write: true,
|
Write: true,
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
@@ -780,46 +781,46 @@ func TestMountDevOp(t *testing.T) {
|
|||||||
{"zero", new(MountDevOp), new(MountDevOp), false},
|
{"zero", new(MountDevOp), new(MountDevOp), false},
|
||||||
|
|
||||||
{"write differs", &MountDevOp{
|
{"write differs", &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, &MountDevOp{
|
}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
Write: true,
|
Write: true,
|
||||||
}, false},
|
}, false},
|
||||||
|
|
||||||
{"mqueue differs", &MountDevOp{
|
{"mqueue differs", &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
}, &MountDevOp{
|
}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, false},
|
}, false},
|
||||||
|
|
||||||
{"target differs", &MountDevOp{
|
{"target differs", &MountDevOp{
|
||||||
Target: MustAbs("/"),
|
Target: check.MustAbs("/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, &MountDevOp{
|
}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, false},
|
}, false},
|
||||||
|
|
||||||
{"equals", &MountDevOp{
|
{"equals", &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, &MountDevOp{
|
}, &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, true},
|
}, true},
|
||||||
})
|
})
|
||||||
|
|
||||||
checkOpMeta(t, []opMetaTestCase{
|
checkOpMeta(t, []opMetaTestCase{
|
||||||
{"mqueue", &MountDevOp{
|
{"mqueue", &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Mqueue: true,
|
Mqueue: true,
|
||||||
}, "mounting", `dev on "/dev/" with mqueue`},
|
}, "mounting", `dev on "/dev/" with mqueue`},
|
||||||
|
|
||||||
{"dev", &MountDevOp{
|
{"dev", &MountDevOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
}, "mounting", `dev on "/dev/"`},
|
}, "mounting", `dev on "/dev/"`},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,19 +4,21 @@ import (
|
|||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { gob.Register(new(MkdirOp)) }
|
func init() { gob.Register(new(MkdirOp)) }
|
||||||
|
|
||||||
// Mkdir appends an [Op] that creates a directory in the container filesystem.
|
// Mkdir appends an [Op] that creates a directory in the container filesystem.
|
||||||
func (f *Ops) Mkdir(name *Absolute, perm os.FileMode) *Ops {
|
func (f *Ops) Mkdir(name *check.Absolute, perm os.FileMode) *Ops {
|
||||||
*f = append(*f, &MkdirOp{name, perm})
|
*f = append(*f, &MkdirOp{name, perm})
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
// MkdirOp creates a directory at container Path with permission bits set to Perm.
|
// MkdirOp creates a directory at container Path with permission bits set to Perm.
|
||||||
type MkdirOp struct {
|
type MkdirOp struct {
|
||||||
Path *Absolute
|
Path *check.Absolute
|
||||||
Perm os.FileMode
|
Perm os.FileMode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/container/stub"
|
"hakurei.app/container/stub"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMkdirOp(t *testing.T) {
|
func TestMkdirOp(t *testing.T) {
|
||||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||||
{"success", new(Params), &MkdirOp{
|
{"success", new(Params), &MkdirOp{
|
||||||
Path: MustAbs("/.hakurei"),
|
Path: check.MustAbs("/.hakurei"),
|
||||||
Perm: 0500,
|
Perm: 0500,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mkdirAll", stub.ExpectArgs{"/sysroot/.hakurei", os.FileMode(0500)}, nil, nil),
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/.hakurei", os.FileMode(0500)}, nil, nil),
|
||||||
@@ -20,25 +21,25 @@ func TestMkdirOp(t *testing.T) {
|
|||||||
checkOpsValid(t, []opValidTestCase{
|
checkOpsValid(t, []opValidTestCase{
|
||||||
{"nil", (*MkdirOp)(nil), false},
|
{"nil", (*MkdirOp)(nil), false},
|
||||||
{"zero", new(MkdirOp), false},
|
{"zero", new(MkdirOp), false},
|
||||||
{"valid", &MkdirOp{Path: MustAbs("/.hakurei")}, true},
|
{"valid", &MkdirOp{Path: check.MustAbs("/.hakurei")}, true},
|
||||||
})
|
})
|
||||||
|
|
||||||
checkOpsBuilder(t, []opsBuilderTestCase{
|
checkOpsBuilder(t, []opsBuilderTestCase{
|
||||||
{"etc", new(Ops).Mkdir(MustAbs("/etc/"), 0), Ops{
|
{"etc", new(Ops).Mkdir(check.MustAbs("/etc/"), 0), Ops{
|
||||||
&MkdirOp{Path: MustAbs("/etc/")},
|
&MkdirOp{Path: check.MustAbs("/etc/")},
|
||||||
}},
|
}},
|
||||||
})
|
})
|
||||||
|
|
||||||
checkOpIs(t, []opIsTestCase{
|
checkOpIs(t, []opIsTestCase{
|
||||||
{"zero", new(MkdirOp), new(MkdirOp), false},
|
{"zero", new(MkdirOp), new(MkdirOp), false},
|
||||||
{"path differs", &MkdirOp{Path: MustAbs("/"), Perm: 0755}, &MkdirOp{Path: MustAbs("/etc/"), Perm: 0755}, false},
|
{"path differs", &MkdirOp{Path: check.MustAbs("/"), Perm: 0755}, &MkdirOp{Path: check.MustAbs("/etc/"), Perm: 0755}, false},
|
||||||
{"perm differs", &MkdirOp{Path: MustAbs("/")}, &MkdirOp{Path: MustAbs("/"), Perm: 0755}, false},
|
{"perm differs", &MkdirOp{Path: check.MustAbs("/")}, &MkdirOp{Path: check.MustAbs("/"), Perm: 0755}, false},
|
||||||
{"equals", &MkdirOp{Path: MustAbs("/")}, &MkdirOp{Path: MustAbs("/")}, true},
|
{"equals", &MkdirOp{Path: check.MustAbs("/")}, &MkdirOp{Path: check.MustAbs("/")}, true},
|
||||||
})
|
})
|
||||||
|
|
||||||
checkOpMeta(t, []opMetaTestCase{
|
checkOpMeta(t, []opMetaTestCase{
|
||||||
{"etc", &MkdirOp{
|
{"etc", &MkdirOp{
|
||||||
Path: MustAbs("/etc/"),
|
Path: check.MustAbs("/etc/"),
|
||||||
}, "creating", `directory "/etc/" perm ----------`},
|
}, "creating", `directory "/etc/" perm ----------`},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
"hakurei.app/container/fhs"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -52,7 +55,7 @@ func (e *OverlayArgumentError) Error() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Overlay appends an [Op] that mounts the overlay pseudo filesystem on [MountOverlayOp.Target].
|
// Overlay appends an [Op] that mounts the overlay pseudo filesystem on [MountOverlayOp.Target].
|
||||||
func (f *Ops) Overlay(target, state, work *Absolute, layers ...*Absolute) *Ops {
|
func (f *Ops) Overlay(target, state, work *check.Absolute, layers ...*check.Absolute) *Ops {
|
||||||
*f = append(*f, &MountOverlayOp{
|
*f = append(*f, &MountOverlayOp{
|
||||||
Target: target,
|
Target: target,
|
||||||
Lower: layers,
|
Lower: layers,
|
||||||
@@ -64,34 +67,34 @@ func (f *Ops) Overlay(target, state, work *Absolute, layers ...*Absolute) *Ops {
|
|||||||
|
|
||||||
// OverlayEphemeral appends an [Op] that mounts the overlay pseudo filesystem on [MountOverlayOp.Target]
|
// OverlayEphemeral appends an [Op] that mounts the overlay pseudo filesystem on [MountOverlayOp.Target]
|
||||||
// with an ephemeral upperdir and workdir.
|
// with an ephemeral upperdir and workdir.
|
||||||
func (f *Ops) OverlayEphemeral(target *Absolute, layers ...*Absolute) *Ops {
|
func (f *Ops) OverlayEphemeral(target *check.Absolute, layers ...*check.Absolute) *Ops {
|
||||||
return f.Overlay(target, AbsFHSRoot, nil, layers...)
|
return f.Overlay(target, fhs.AbsRoot, nil, layers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OverlayReadonly appends an [Op] that mounts the overlay pseudo filesystem readonly on [MountOverlayOp.Target]
|
// OverlayReadonly appends an [Op] that mounts the overlay pseudo filesystem readonly on [MountOverlayOp.Target]
|
||||||
func (f *Ops) OverlayReadonly(target *Absolute, layers ...*Absolute) *Ops {
|
func (f *Ops) OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) *Ops {
|
||||||
return f.Overlay(target, nil, nil, layers...)
|
return f.Overlay(target, nil, nil, layers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MountOverlayOp mounts [FstypeOverlay] on container path Target.
|
// MountOverlayOp mounts [FstypeOverlay] on container path Target.
|
||||||
type MountOverlayOp struct {
|
type MountOverlayOp struct {
|
||||||
Target *Absolute
|
Target *check.Absolute
|
||||||
|
|
||||||
// Any filesystem, does not need to be on a writable filesystem.
|
// Any filesystem, does not need to be on a writable filesystem.
|
||||||
Lower []*Absolute
|
Lower []*check.Absolute
|
||||||
// formatted for [OptionOverlayLowerdir], resolved, prefixed and escaped during early
|
// formatted for [OptionOverlayLowerdir], resolved, prefixed and escaped during early
|
||||||
lower []string
|
lower []string
|
||||||
// The upperdir is normally on a writable filesystem.
|
// The upperdir is normally on a writable filesystem.
|
||||||
//
|
//
|
||||||
// If Work is nil and Upper holds the special value [AbsFHSRoot],
|
// If Work is nil and Upper holds the special value [fhs.AbsRoot],
|
||||||
// an ephemeral upperdir and workdir will be set up.
|
// an ephemeral upperdir and workdir will be set up.
|
||||||
//
|
//
|
||||||
// If both Work and Upper are nil, upperdir and workdir is omitted and the overlay is mounted readonly.
|
// If both Work and Upper are nil, upperdir and workdir is omitted and the overlay is mounted readonly.
|
||||||
Upper *Absolute
|
Upper *check.Absolute
|
||||||
// formatted for [OptionOverlayUpperdir], resolved, prefixed and escaped during early
|
// formatted for [OptionOverlayUpperdir], resolved, prefixed and escaped during early
|
||||||
upper string
|
upper string
|
||||||
// The workdir needs to be an empty directory on the same filesystem as upperdir.
|
// The workdir needs to be an empty directory on the same filesystem as upperdir.
|
||||||
Work *Absolute
|
Work *check.Absolute
|
||||||
// formatted for [OptionOverlayWorkdir], resolved, prefixed and escaped during early
|
// formatted for [OptionOverlayWorkdir], resolved, prefixed and escaped during early
|
||||||
work string
|
work string
|
||||||
|
|
||||||
@@ -117,7 +120,7 @@ func (o *MountOverlayOp) Valid() bool {
|
|||||||
func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
|
func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
|
||||||
if o.Work == nil && o.Upper != nil {
|
if o.Work == nil && o.Upper != nil {
|
||||||
switch o.Upper.String() {
|
switch o.Upper.String() {
|
||||||
case FHSRoot: // ephemeral
|
case fhs.Root: // ephemeral
|
||||||
o.ephemeral = true // intermediate root not yet available
|
o.ephemeral = true // intermediate root not yet available
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -136,7 +139,7 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
|
|||||||
if v, err := k.evalSymlinks(o.Upper.String()); err != nil {
|
if v, err := k.evalSymlinks(o.Upper.String()); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
o.upper = EscapeOverlayDataSegment(toHost(v))
|
o.upper = check.EscapeOverlayDataSegment(toHost(v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +147,7 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
|
|||||||
if v, err := k.evalSymlinks(o.Work.String()); err != nil {
|
if v, err := k.evalSymlinks(o.Work.String()); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
o.work = EscapeOverlayDataSegment(toHost(v))
|
o.work = check.EscapeOverlayDataSegment(toHost(v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,7 +157,7 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
|
|||||||
if v, err := k.evalSymlinks(a.String()); err != nil {
|
if v, err := k.evalSymlinks(a.String()); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
o.lower[i] = EscapeOverlayDataSegment(toHost(v))
|
o.lower[i] = check.EscapeOverlayDataSegment(toHost(v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -172,10 +175,10 @@ func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
if o.ephemeral {
|
if o.ephemeral {
|
||||||
var err error
|
var err error
|
||||||
// these directories are created internally, therefore early (absolute, symlink, prefix, escape) is bypassed
|
// these directories are created internally, therefore early (absolute, symlink, prefix, escape) is bypassed
|
||||||
if o.upper, err = k.mkdirTemp(FHSRoot, intermediatePatternOverlayUpper); err != nil {
|
if o.upper, err = k.mkdirTemp(fhs.Root, intermediatePatternOverlayUpper); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if o.work, err = k.mkdirTemp(FHSRoot, intermediatePatternOverlayWork); err != nil {
|
if o.work, err = k.mkdirTemp(fhs.Root, intermediatePatternOverlayWork); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,17 +199,17 @@ func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
OptionOverlayWorkdir+"="+o.work)
|
OptionOverlayWorkdir+"="+o.work)
|
||||||
}
|
}
|
||||||
options = append(options,
|
options = append(options,
|
||||||
OptionOverlayLowerdir+"="+strings.Join(o.lower, SpecialOverlayPath),
|
OptionOverlayLowerdir+"="+strings.Join(o.lower, check.SpecialOverlayPath),
|
||||||
OptionOverlayUserxattr)
|
OptionOverlayUserxattr)
|
||||||
|
|
||||||
return k.mount(SourceOverlay, target, FstypeOverlay, 0, strings.Join(options, SpecialOverlayOption))
|
return k.mount(SourceOverlay, target, FstypeOverlay, 0, strings.Join(options, check.SpecialOverlayOption))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *MountOverlayOp) Is(op Op) bool {
|
func (o *MountOverlayOp) Is(op Op) bool {
|
||||||
vo, ok := op.(*MountOverlayOp)
|
vo, ok := op.(*MountOverlayOp)
|
||||||
return ok && o.Valid() && vo.Valid() &&
|
return ok && o.Valid() && vo.Valid() &&
|
||||||
o.Target.Is(vo.Target) &&
|
o.Target.Is(vo.Target) &&
|
||||||
slices.EqualFunc(o.Lower, vo.Lower, func(a *Absolute, v *Absolute) bool { return a.Is(v) }) &&
|
slices.EqualFunc(o.Lower, vo.Lower, func(a, v *check.Absolute) bool { return a.Is(v) }) &&
|
||||||
o.Upper.Is(vo.Upper) && o.Work.Is(vo.Work)
|
o.Upper.Is(vo.Upper) && o.Work.Is(vo.Work)
|
||||||
}
|
}
|
||||||
func (*MountOverlayOp) prefix() (string, bool) { return "mounting", true }
|
func (*MountOverlayOp) prefix() (string, bool) { return "mounting", true }
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/container/stub"
|
"hakurei.app/container/stub"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,21 +39,21 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
|
|
||||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||||
{"mkdirTemp invalid ephemeral", &Params{ParentPerm: 0705}, &MountOverlayOp{
|
{"mkdirTemp invalid ephemeral", &Params{ParentPerm: 0705}, &MountOverlayOp{
|
||||||
Target: MustAbs("/"),
|
Target: check.MustAbs("/"),
|
||||||
Lower: []*Absolute{
|
Lower: []*check.Absolute{
|
||||||
MustAbs("/var/lib/planterette/base/debian:f92c9052"),
|
check.MustAbs("/var/lib/planterette/base/debian:f92c9052"),
|
||||||
MustAbs("/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"),
|
check.MustAbs("/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"),
|
||||||
},
|
},
|
||||||
Upper: MustAbs("/proc/"),
|
Upper: check.MustAbs("/proc/"),
|
||||||
}, nil, &OverlayArgumentError{OverlayEphemeralUnexpectedUpper, "/proc/"}, nil, nil},
|
}, nil, &OverlayArgumentError{OverlayEphemeralUnexpectedUpper, "/proc/"}, nil, nil},
|
||||||
|
|
||||||
{"mkdirTemp upper ephemeral", &Params{ParentPerm: 0705}, &MountOverlayOp{
|
{"mkdirTemp upper ephemeral", &Params{ParentPerm: 0705}, &MountOverlayOp{
|
||||||
Target: MustAbs("/"),
|
Target: check.MustAbs("/"),
|
||||||
Lower: []*Absolute{
|
Lower: []*check.Absolute{
|
||||||
MustAbs("/var/lib/planterette/base/debian:f92c9052"),
|
check.MustAbs("/var/lib/planterette/base/debian:f92c9052"),
|
||||||
MustAbs("/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"),
|
check.MustAbs("/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"),
|
||||||
},
|
},
|
||||||
Upper: MustAbs("/"),
|
Upper: check.MustAbs("/"),
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/base/debian:f92c9052"}, "/var/lib/planterette/base/debian:f92c9052", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/base/debian:f92c9052"}, "/var/lib/planterette/base/debian:f92c9052", nil),
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"}, "/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"}, "/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052", nil),
|
||||||
@@ -62,12 +63,12 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(6)},
|
}, stub.UniqueError(6)},
|
||||||
|
|
||||||
{"mkdirTemp work ephemeral", &Params{ParentPerm: 0705}, &MountOverlayOp{
|
{"mkdirTemp work ephemeral", &Params{ParentPerm: 0705}, &MountOverlayOp{
|
||||||
Target: MustAbs("/"),
|
Target: check.MustAbs("/"),
|
||||||
Lower: []*Absolute{
|
Lower: []*check.Absolute{
|
||||||
MustAbs("/var/lib/planterette/base/debian:f92c9052"),
|
check.MustAbs("/var/lib/planterette/base/debian:f92c9052"),
|
||||||
MustAbs("/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"),
|
check.MustAbs("/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"),
|
||||||
},
|
},
|
||||||
Upper: MustAbs("/"),
|
Upper: check.MustAbs("/"),
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/base/debian:f92c9052"}, "/var/lib/planterette/base/debian:f92c9052", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/base/debian:f92c9052"}, "/var/lib/planterette/base/debian:f92c9052", nil),
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"}, "/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"}, "/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052", nil),
|
||||||
@@ -78,12 +79,12 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(5)},
|
}, stub.UniqueError(5)},
|
||||||
|
|
||||||
{"success ephemeral", &Params{ParentPerm: 0705}, &MountOverlayOp{
|
{"success ephemeral", &Params{ParentPerm: 0705}, &MountOverlayOp{
|
||||||
Target: MustAbs("/"),
|
Target: check.MustAbs("/"),
|
||||||
Lower: []*Absolute{
|
Lower: []*check.Absolute{
|
||||||
MustAbs("/var/lib/planterette/base/debian:f92c9052"),
|
check.MustAbs("/var/lib/planterette/base/debian:f92c9052"),
|
||||||
MustAbs("/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"),
|
check.MustAbs("/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"),
|
||||||
},
|
},
|
||||||
Upper: MustAbs("/"),
|
Upper: check.MustAbs("/"),
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/base/debian:f92c9052"}, "/var/lib/planterette/base/debian:f92c9052", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/base/debian:f92c9052"}, "/var/lib/planterette/base/debian:f92c9052", nil),
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"}, "/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"}, "/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052", nil),
|
||||||
@@ -101,9 +102,9 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"short lower ro", &Params{ParentPerm: 0755}, &MountOverlayOp{
|
{"short lower ro", &Params{ParentPerm: 0755}, &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{
|
Lower: []*check.Absolute{
|
||||||
MustAbs("/mnt-root/nix/.ro-store"),
|
check.MustAbs("/mnt-root/nix/.ro-store"),
|
||||||
},
|
},
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/.ro-store", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/.ro-store", nil),
|
||||||
@@ -112,10 +113,10 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
}, &OverlayArgumentError{OverlayReadonlyLower, zeroString}},
|
}, &OverlayArgumentError{OverlayReadonlyLower, zeroString}},
|
||||||
|
|
||||||
{"success ro noPrefix", &Params{ParentPerm: 0755}, &MountOverlayOp{
|
{"success ro noPrefix", &Params{ParentPerm: 0755}, &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{
|
Lower: []*check.Absolute{
|
||||||
MustAbs("/mnt-root/nix/.ro-store"),
|
check.MustAbs("/mnt-root/nix/.ro-store"),
|
||||||
MustAbs("/mnt-root/nix/.ro-store0"),
|
check.MustAbs("/mnt-root/nix/.ro-store0"),
|
||||||
},
|
},
|
||||||
noPrefix: true,
|
noPrefix: true,
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
@@ -131,10 +132,10 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"success ro", &Params{ParentPerm: 0755}, &MountOverlayOp{
|
{"success ro", &Params{ParentPerm: 0755}, &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{
|
Lower: []*check.Absolute{
|
||||||
MustAbs("/mnt-root/nix/.ro-store"),
|
check.MustAbs("/mnt-root/nix/.ro-store"),
|
||||||
MustAbs("/mnt-root/nix/.ro-store0"),
|
check.MustAbs("/mnt-root/nix/.ro-store0"),
|
||||||
},
|
},
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/.ro-store", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/.ro-store", nil),
|
||||||
@@ -149,9 +150,9 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"nil lower", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
{"nil lower", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: check.MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
Work: check.MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil),
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", nil),
|
||||||
@@ -160,29 +161,29 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
}, &OverlayArgumentError{OverlayEmptyLower, zeroString}},
|
}, &OverlayArgumentError{OverlayEmptyLower, zeroString}},
|
||||||
|
|
||||||
{"evalSymlinks upper", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
{"evalSymlinks upper", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: check.MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
Work: check.MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", stub.UniqueError(4)),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", stub.UniqueError(4)),
|
||||||
}, stub.UniqueError(4), nil, nil},
|
}, stub.UniqueError(4), nil, nil},
|
||||||
|
|
||||||
{"evalSymlinks work", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
{"evalSymlinks work", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: check.MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
Work: check.MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil),
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", stub.UniqueError(3)),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", stub.UniqueError(3)),
|
||||||
}, stub.UniqueError(3), nil, nil},
|
}, stub.UniqueError(3), nil, nil},
|
||||||
|
|
||||||
{"evalSymlinks lower", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
{"evalSymlinks lower", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: check.MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
Work: check.MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil),
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", nil),
|
||||||
@@ -190,10 +191,10 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(2), nil, nil},
|
}, stub.UniqueError(2), nil, nil},
|
||||||
|
|
||||||
{"mkdirAll", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
{"mkdirAll", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: check.MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
Work: check.MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil),
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", nil),
|
||||||
@@ -203,10 +204,10 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(1)},
|
}, stub.UniqueError(1)},
|
||||||
|
|
||||||
{"mount", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
{"mount", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: check.MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
Work: check.MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil),
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", nil),
|
||||||
@@ -217,10 +218,10 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(0)},
|
}, stub.UniqueError(0)},
|
||||||
|
|
||||||
{"success single layer", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
{"success single layer", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: check.MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
Work: check.MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil),
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", nil),
|
||||||
@@ -235,16 +236,16 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"success", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
{"success", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{
|
Lower: []*check.Absolute{
|
||||||
MustAbs("/mnt-root/nix/.ro-store"),
|
check.MustAbs("/mnt-root/nix/.ro-store"),
|
||||||
MustAbs("/mnt-root/nix/.ro-store0"),
|
check.MustAbs("/mnt-root/nix/.ro-store0"),
|
||||||
MustAbs("/mnt-root/nix/.ro-store1"),
|
check.MustAbs("/mnt-root/nix/.ro-store1"),
|
||||||
MustAbs("/mnt-root/nix/.ro-store2"),
|
check.MustAbs("/mnt-root/nix/.ro-store2"),
|
||||||
MustAbs("/mnt-root/nix/.ro-store3"),
|
check.MustAbs("/mnt-root/nix/.ro-store3"),
|
||||||
},
|
},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: check.MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
Work: check.MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil),
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", nil),
|
||||||
@@ -272,7 +273,7 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
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) {
|
||||||
wantErr := OpStateError("overlay")
|
wantErr := OpStateError("overlay")
|
||||||
if err := (&MountOverlayOp{
|
if err := (&MountOverlayOp{
|
||||||
Work: MustAbs("/"),
|
Work: check.MustAbs("/"),
|
||||||
}).early(nil, nil); !errors.Is(err, wantErr) {
|
}).early(nil, nil); !errors.Is(err, wantErr) {
|
||||||
t.Errorf("apply: error = %v, want %v", err, wantErr)
|
t.Errorf("apply: error = %v, want %v", err, wantErr)
|
||||||
}
|
}
|
||||||
@@ -282,39 +283,39 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
checkOpsValid(t, []opValidTestCase{
|
checkOpsValid(t, []opValidTestCase{
|
||||||
{"nil", (*MountOverlayOp)(nil), false},
|
{"nil", (*MountOverlayOp)(nil), false},
|
||||||
{"zero", new(MountOverlayOp), false},
|
{"zero", new(MountOverlayOp), false},
|
||||||
{"nil lower", &MountOverlayOp{Target: MustAbs("/"), Lower: []*Absolute{nil}}, false},
|
{"nil lower", &MountOverlayOp{Target: check.MustAbs("/"), Lower: []*check.Absolute{nil}}, false},
|
||||||
{"ro", &MountOverlayOp{Target: MustAbs("/"), Lower: []*Absolute{MustAbs("/")}}, true},
|
{"ro", &MountOverlayOp{Target: check.MustAbs("/"), Lower: []*check.Absolute{check.MustAbs("/")}}, true},
|
||||||
{"ro work", &MountOverlayOp{Target: MustAbs("/"), Work: MustAbs("/tmp/")}, false},
|
{"ro work", &MountOverlayOp{Target: check.MustAbs("/"), Work: check.MustAbs("/tmp/")}, false},
|
||||||
{"rw", &MountOverlayOp{Target: MustAbs("/"), Lower: []*Absolute{MustAbs("/")}, Upper: MustAbs("/"), Work: MustAbs("/")}, true},
|
{"rw", &MountOverlayOp{Target: check.MustAbs("/"), Lower: []*check.Absolute{check.MustAbs("/")}, Upper: check.MustAbs("/"), Work: check.MustAbs("/")}, true},
|
||||||
})
|
})
|
||||||
|
|
||||||
checkOpsBuilder(t, []opsBuilderTestCase{
|
checkOpsBuilder(t, []opsBuilderTestCase{
|
||||||
{"full", new(Ops).Overlay(
|
{"full", new(Ops).Overlay(
|
||||||
MustAbs("/nix/store"),
|
check.MustAbs("/nix/store"),
|
||||||
MustAbs("/mnt-root/nix/.rw-store/upper"),
|
check.MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
MustAbs("/mnt-root/nix/.rw-store/work"),
|
check.MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||||
MustAbs("/mnt-root/nix/.ro-store"),
|
check.MustAbs("/mnt-root/nix/.ro-store"),
|
||||||
), Ops{
|
), Ops{
|
||||||
&MountOverlayOp{
|
&MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: check.MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
Work: check.MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
|
|
||||||
{"ephemeral", new(Ops).OverlayEphemeral(MustAbs("/nix/store"), MustAbs("/mnt-root/nix/.ro-store")), Ops{
|
{"ephemeral", new(Ops).OverlayEphemeral(check.MustAbs("/nix/store"), check.MustAbs("/mnt-root/nix/.ro-store")), Ops{
|
||||||
&MountOverlayOp{
|
&MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
Upper: MustAbs("/"),
|
Upper: check.MustAbs("/"),
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
|
|
||||||
{"readonly", new(Ops).OverlayReadonly(MustAbs("/nix/store"), MustAbs("/mnt-root/nix/.ro-store")), Ops{
|
{"readonly", new(Ops).OverlayReadonly(check.MustAbs("/nix/store"), check.MustAbs("/mnt-root/nix/.ro-store")), Ops{
|
||||||
&MountOverlayOp{
|
&MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
})
|
})
|
||||||
@@ -323,74 +324,74 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
{"zero", new(MountOverlayOp), new(MountOverlayOp), false},
|
{"zero", new(MountOverlayOp), new(MountOverlayOp), false},
|
||||||
|
|
||||||
{"differs target", &MountOverlayOp{
|
{"differs target", &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store/differs"),
|
Target: check.MustAbs("/nix/store/differs"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: check.MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
Work: check.MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||||
}, &MountOverlayOp{
|
}, &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: check.MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work")}, false},
|
Work: check.MustAbs("/mnt-root/nix/.rw-store/work")}, false},
|
||||||
|
|
||||||
{"differs lower", &MountOverlayOp{
|
{"differs lower", &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store/differs")},
|
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store/differs")},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: check.MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
Work: check.MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||||
}, &MountOverlayOp{
|
}, &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: check.MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work")}, false},
|
Work: check.MustAbs("/mnt-root/nix/.rw-store/work")}, false},
|
||||||
|
|
||||||
{"differs upper", &MountOverlayOp{
|
{"differs upper", &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper/differs"),
|
Upper: check.MustAbs("/mnt-root/nix/.rw-store/upper/differs"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
Work: check.MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||||
}, &MountOverlayOp{
|
}, &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: check.MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work")}, false},
|
Work: check.MustAbs("/mnt-root/nix/.rw-store/work")}, false},
|
||||||
|
|
||||||
{"differs work", &MountOverlayOp{
|
{"differs work", &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: check.MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work/differs"),
|
Work: check.MustAbs("/mnt-root/nix/.rw-store/work/differs"),
|
||||||
}, &MountOverlayOp{
|
}, &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: check.MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work")}, false},
|
Work: check.MustAbs("/mnt-root/nix/.rw-store/work")}, false},
|
||||||
|
|
||||||
{"equals ro", &MountOverlayOp{
|
{"equals ro", &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
}, &MountOverlayOp{
|
}, &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")}}, true},
|
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")}}, true},
|
||||||
|
|
||||||
{"equals", &MountOverlayOp{
|
{"equals", &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: check.MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
Work: check.MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||||
}, &MountOverlayOp{
|
}, &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: check.MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work")}, true},
|
Work: check.MustAbs("/mnt-root/nix/.rw-store/work")}, true},
|
||||||
})
|
})
|
||||||
|
|
||||||
checkOpMeta(t, []opMetaTestCase{
|
checkOpMeta(t, []opMetaTestCase{
|
||||||
{"nix", &MountOverlayOp{
|
{"nix", &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: check.MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
Work: check.MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||||
}, "mounting", `overlay on "/nix/store" with 1 layers`},
|
}, "mounting", `overlay on "/nix/store" with 1 layers`},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import (
|
|||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
"hakurei.app/container/fhs"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -14,13 +17,13 @@ const (
|
|||||||
func init() { gob.Register(new(TmpfileOp)) }
|
func init() { gob.Register(new(TmpfileOp)) }
|
||||||
|
|
||||||
// Place appends an [Op] that places a file in container path [TmpfileOp.Path] containing [TmpfileOp.Data].
|
// Place appends an [Op] that places a file in container path [TmpfileOp.Path] containing [TmpfileOp.Data].
|
||||||
func (f *Ops) Place(name *Absolute, data []byte) *Ops {
|
func (f *Ops) Place(name *check.Absolute, data []byte) *Ops {
|
||||||
*f = append(*f, &TmpfileOp{name, data})
|
*f = append(*f, &TmpfileOp{name, data})
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlaceP is like Place but writes the address of [TmpfileOp.Data] to the pointer dataP points to.
|
// PlaceP is like Place but writes the address of [TmpfileOp.Data] to the pointer dataP points to.
|
||||||
func (f *Ops) PlaceP(name *Absolute, dataP **[]byte) *Ops {
|
func (f *Ops) PlaceP(name *check.Absolute, dataP **[]byte) *Ops {
|
||||||
t := &TmpfileOp{Path: name}
|
t := &TmpfileOp{Path: name}
|
||||||
*dataP = &t.Data
|
*dataP = &t.Data
|
||||||
|
|
||||||
@@ -30,7 +33,7 @@ func (f *Ops) PlaceP(name *Absolute, dataP **[]byte) *Ops {
|
|||||||
|
|
||||||
// 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 *Absolute
|
Path *check.Absolute
|
||||||
Data []byte
|
Data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +41,7 @@ func (t *TmpfileOp) Valid() bool { return t != ni
|
|||||||
func (t *TmpfileOp) early(*setupState, syscallDispatcher) error { return nil }
|
func (t *TmpfileOp) early(*setupState, syscallDispatcher) error { return nil }
|
||||||
func (t *TmpfileOp) apply(state *setupState, k syscallDispatcher) error {
|
func (t *TmpfileOp) apply(state *setupState, k syscallDispatcher) error {
|
||||||
var tmpPath string
|
var tmpPath string
|
||||||
if f, err := k.createTemp(FHSRoot, intermediatePatternTmpfile); err != nil {
|
if f, err := k.createTemp(fhs.Root, intermediatePatternTmpfile); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if _, err = f.Write(t.Data); err != nil {
|
} else if _, err = f.Write(t.Data); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -52,6 +55,7 @@ func (t *TmpfileOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
if err := k.ensureFile(target, 0444, state.ParentPerm); err != nil {
|
if err := k.ensureFile(target, 0444, state.ParentPerm); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if err = k.bindMount(
|
} else if err = k.bindMount(
|
||||||
|
state,
|
||||||
tmpPath,
|
tmpPath,
|
||||||
target,
|
target,
|
||||||
syscall.MS_RDONLY|syscall.MS_NODEV,
|
syscall.MS_RDONLY|syscall.MS_NODEV,
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/container/stub"
|
"hakurei.app/container/stub"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTmpfileOp(t *testing.T) {
|
func TestTmpfileOp(t *testing.T) {
|
||||||
const sampleDataString = `chronos:x:65534:65534:Hakurei:/var/empty:/bin/zsh`
|
const sampleDataString = `chronos:x:65534:65534:Hakurei:/var/empty:/bin/zsh`
|
||||||
var (
|
var (
|
||||||
samplePath = MustAbs("/etc/passwd")
|
samplePath = check.MustAbs("/etc/passwd")
|
||||||
sampleData = []byte(sampleDataString)
|
sampleData = []byte(sampleDataString)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -100,7 +101,7 @@ func TestTmpfileOp(t *testing.T) {
|
|||||||
{"zero", new(TmpfileOp), new(TmpfileOp), false},
|
{"zero", new(TmpfileOp), new(TmpfileOp), false},
|
||||||
|
|
||||||
{"differs path", &TmpfileOp{
|
{"differs path", &TmpfileOp{
|
||||||
Path: MustAbs("/etc/group"),
|
Path: check.MustAbs("/etc/group"),
|
||||||
Data: sampleData,
|
Data: sampleData,
|
||||||
}, &TmpfileOp{
|
}, &TmpfileOp{
|
||||||
Path: samplePath,
|
Path: samplePath,
|
||||||
|
|||||||
@@ -4,18 +4,20 @@ import (
|
|||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
. "syscall"
|
. "syscall"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { gob.Register(new(MountProcOp)) }
|
func init() { gob.Register(new(MountProcOp)) }
|
||||||
|
|
||||||
// Proc appends an [Op] that mounts a private instance of proc.
|
// Proc appends an [Op] that mounts a private instance of proc.
|
||||||
func (f *Ops) Proc(target *Absolute) *Ops {
|
func (f *Ops) Proc(target *check.Absolute) *Ops {
|
||||||
*f = append(*f, &MountProcOp{target})
|
*f = append(*f, &MountProcOp{target})
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
// MountProcOp mounts a new instance of [FstypeProc] on container path Target.
|
// MountProcOp mounts a new instance of [FstypeProc] on container path Target.
|
||||||
type MountProcOp struct{ Target *Absolute }
|
type MountProcOp struct{ Target *check.Absolute }
|
||||||
|
|
||||||
func (p *MountProcOp) Valid() bool { return p != nil && p.Target != nil }
|
func (p *MountProcOp) Valid() bool { return p != nil && p.Target != nil }
|
||||||
func (p *MountProcOp) early(*setupState, syscallDispatcher) error { return nil }
|
func (p *MountProcOp) early(*setupState, syscallDispatcher) error { return nil }
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/container/stub"
|
"hakurei.app/container/stub"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -11,14 +12,14 @@ func TestMountProcOp(t *testing.T) {
|
|||||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||||
{"mkdir", &Params{ParentPerm: 0755},
|
{"mkdir", &Params{ParentPerm: 0755},
|
||||||
&MountProcOp{
|
&MountProcOp{
|
||||||
Target: MustAbs("/proc/"),
|
Target: check.MustAbs("/proc/"),
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, stub.UniqueError(0)),
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, stub.UniqueError(0)),
|
||||||
}, stub.UniqueError(0)},
|
}, stub.UniqueError(0)},
|
||||||
|
|
||||||
{"success", &Params{ParentPerm: 0700},
|
{"success", &Params{ParentPerm: 0700},
|
||||||
&MountProcOp{
|
&MountProcOp{
|
||||||
Target: MustAbs("/proc/"),
|
Target: check.MustAbs("/proc/"),
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0700)}, nil, nil),
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0700)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"proc", "/sysroot/proc", "proc", uintptr(0xe), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"proc", "/sysroot/proc", "proc", uintptr(0xe), ""}, nil, nil),
|
||||||
@@ -28,12 +29,12 @@ func TestMountProcOp(t *testing.T) {
|
|||||||
checkOpsValid(t, []opValidTestCase{
|
checkOpsValid(t, []opValidTestCase{
|
||||||
{"nil", (*MountProcOp)(nil), false},
|
{"nil", (*MountProcOp)(nil), false},
|
||||||
{"zero", new(MountProcOp), false},
|
{"zero", new(MountProcOp), false},
|
||||||
{"valid", &MountProcOp{Target: MustAbs("/proc/")}, true},
|
{"valid", &MountProcOp{Target: check.MustAbs("/proc/")}, true},
|
||||||
})
|
})
|
||||||
|
|
||||||
checkOpsBuilder(t, []opsBuilderTestCase{
|
checkOpsBuilder(t, []opsBuilderTestCase{
|
||||||
{"proc", new(Ops).Proc(MustAbs("/proc/")), Ops{
|
{"proc", new(Ops).Proc(check.MustAbs("/proc/")), Ops{
|
||||||
&MountProcOp{Target: MustAbs("/proc/")},
|
&MountProcOp{Target: check.MustAbs("/proc/")},
|
||||||
}},
|
}},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -41,20 +42,20 @@ func TestMountProcOp(t *testing.T) {
|
|||||||
{"zero", new(MountProcOp), new(MountProcOp), false},
|
{"zero", new(MountProcOp), new(MountProcOp), false},
|
||||||
|
|
||||||
{"target differs", &MountProcOp{
|
{"target differs", &MountProcOp{
|
||||||
Target: MustAbs("/proc/nonexistent"),
|
Target: check.MustAbs("/proc/nonexistent"),
|
||||||
}, &MountProcOp{
|
}, &MountProcOp{
|
||||||
Target: MustAbs("/proc/"),
|
Target: check.MustAbs("/proc/"),
|
||||||
}, false},
|
}, false},
|
||||||
|
|
||||||
{"equals", &MountProcOp{
|
{"equals", &MountProcOp{
|
||||||
Target: MustAbs("/proc/"),
|
Target: check.MustAbs("/proc/"),
|
||||||
}, &MountProcOp{
|
}, &MountProcOp{
|
||||||
Target: MustAbs("/proc/"),
|
Target: check.MustAbs("/proc/"),
|
||||||
}, true},
|
}, true},
|
||||||
})
|
})
|
||||||
|
|
||||||
checkOpMeta(t, []opMetaTestCase{
|
checkOpMeta(t, []opMetaTestCase{
|
||||||
{"proc", &MountProcOp{Target: MustAbs("/proc/")},
|
{"proc", &MountProcOp{Target: check.MustAbs("/proc/")},
|
||||||
"mounting", `proc on "/proc/"`},
|
"mounting", `proc on "/proc/"`},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,26 +3,28 @@ package container
|
|||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { gob.Register(new(RemountOp)) }
|
func init() { gob.Register(new(RemountOp)) }
|
||||||
|
|
||||||
// Remount appends an [Op] that applies [RemountOp.Flags] on container path [RemountOp.Target].
|
// Remount appends an [Op] that applies [RemountOp.Flags] on container path [RemountOp.Target].
|
||||||
func (f *Ops) Remount(target *Absolute, flags uintptr) *Ops {
|
func (f *Ops) Remount(target *check.Absolute, flags uintptr) *Ops {
|
||||||
*f = append(*f, &RemountOp{target, flags})
|
*f = append(*f, &RemountOp{target, flags})
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemountOp remounts Target with Flags.
|
// RemountOp remounts Target with Flags.
|
||||||
type RemountOp struct {
|
type RemountOp struct {
|
||||||
Target *Absolute
|
Target *check.Absolute
|
||||||
Flags uintptr
|
Flags uintptr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RemountOp) Valid() bool { return r != nil && r.Target != nil }
|
func (r *RemountOp) Valid() bool { return r != nil && r.Target != nil }
|
||||||
func (*RemountOp) early(*setupState, syscallDispatcher) error { return nil }
|
func (*RemountOp) early(*setupState, syscallDispatcher) error { return nil }
|
||||||
func (r *RemountOp) apply(_ *setupState, k syscallDispatcher) error {
|
func (r *RemountOp) apply(state *setupState, k syscallDispatcher) error {
|
||||||
return k.remount(toSysroot(r.Target.String()), r.Flags)
|
return k.remount(state, toSysroot(r.Target.String()), r.Flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RemountOp) Is(op Op) bool {
|
func (r *RemountOp) Is(op Op) bool {
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/container/stub"
|
"hakurei.app/container/stub"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRemountOp(t *testing.T) {
|
func TestRemountOp(t *testing.T) {
|
||||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||||
{"success", new(Params), &RemountOp{
|
{"success", new(Params), &RemountOp{
|
||||||
Target: MustAbs("/"),
|
Target: check.MustAbs("/"),
|
||||||
Flags: syscall.MS_RDONLY,
|
Flags: syscall.MS_RDONLY,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("remount", stub.ExpectArgs{"/sysroot", uintptr(1)}, nil, nil),
|
call("remount", stub.ExpectArgs{"/sysroot", uintptr(1)}, nil, nil),
|
||||||
@@ -20,13 +21,13 @@ func TestRemountOp(t *testing.T) {
|
|||||||
checkOpsValid(t, []opValidTestCase{
|
checkOpsValid(t, []opValidTestCase{
|
||||||
{"nil", (*RemountOp)(nil), false},
|
{"nil", (*RemountOp)(nil), false},
|
||||||
{"zero", new(RemountOp), false},
|
{"zero", new(RemountOp), false},
|
||||||
{"valid", &RemountOp{Target: MustAbs("/"), Flags: syscall.MS_RDONLY}, true},
|
{"valid", &RemountOp{Target: check.MustAbs("/"), Flags: syscall.MS_RDONLY}, true},
|
||||||
})
|
})
|
||||||
|
|
||||||
checkOpsBuilder(t, []opsBuilderTestCase{
|
checkOpsBuilder(t, []opsBuilderTestCase{
|
||||||
{"root", new(Ops).Remount(MustAbs("/"), syscall.MS_RDONLY), Ops{
|
{"root", new(Ops).Remount(check.MustAbs("/"), syscall.MS_RDONLY), Ops{
|
||||||
&RemountOp{
|
&RemountOp{
|
||||||
Target: MustAbs("/"),
|
Target: check.MustAbs("/"),
|
||||||
Flags: syscall.MS_RDONLY,
|
Flags: syscall.MS_RDONLY,
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
@@ -36,33 +37,33 @@ func TestRemountOp(t *testing.T) {
|
|||||||
{"zero", new(RemountOp), new(RemountOp), false},
|
{"zero", new(RemountOp), new(RemountOp), false},
|
||||||
|
|
||||||
{"target differs", &RemountOp{
|
{"target differs", &RemountOp{
|
||||||
Target: MustAbs("/dev/"),
|
Target: check.MustAbs("/dev/"),
|
||||||
Flags: syscall.MS_RDONLY,
|
Flags: syscall.MS_RDONLY,
|
||||||
}, &RemountOp{
|
}, &RemountOp{
|
||||||
Target: MustAbs("/"),
|
Target: check.MustAbs("/"),
|
||||||
Flags: syscall.MS_RDONLY,
|
Flags: syscall.MS_RDONLY,
|
||||||
}, false},
|
}, false},
|
||||||
|
|
||||||
{"flags differs", &RemountOp{
|
{"flags differs", &RemountOp{
|
||||||
Target: MustAbs("/"),
|
Target: check.MustAbs("/"),
|
||||||
Flags: syscall.MS_RDONLY | syscall.MS_NODEV,
|
Flags: syscall.MS_RDONLY | syscall.MS_NODEV,
|
||||||
}, &RemountOp{
|
}, &RemountOp{
|
||||||
Target: MustAbs("/"),
|
Target: check.MustAbs("/"),
|
||||||
Flags: syscall.MS_RDONLY,
|
Flags: syscall.MS_RDONLY,
|
||||||
}, false},
|
}, false},
|
||||||
|
|
||||||
{"equals", &RemountOp{
|
{"equals", &RemountOp{
|
||||||
Target: MustAbs("/"),
|
Target: check.MustAbs("/"),
|
||||||
Flags: syscall.MS_RDONLY,
|
Flags: syscall.MS_RDONLY,
|
||||||
}, &RemountOp{
|
}, &RemountOp{
|
||||||
Target: MustAbs("/"),
|
Target: check.MustAbs("/"),
|
||||||
Flags: syscall.MS_RDONLY,
|
Flags: syscall.MS_RDONLY,
|
||||||
}, true},
|
}, true},
|
||||||
})
|
})
|
||||||
|
|
||||||
checkOpMeta(t, []opMetaTestCase{
|
checkOpMeta(t, []opMetaTestCase{
|
||||||
{"root", &RemountOp{
|
{"root", &RemountOp{
|
||||||
Target: MustAbs("/"),
|
Target: check.MustAbs("/"),
|
||||||
Flags: syscall.MS_RDONLY,
|
Flags: syscall.MS_RDONLY,
|
||||||
}, "remounting", `"/" flags 0x1`},
|
}, "remounting", `"/" flags 0x1`},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,19 +4,21 @@ import (
|
|||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { gob.Register(new(SymlinkOp)) }
|
func init() { gob.Register(new(SymlinkOp)) }
|
||||||
|
|
||||||
// Link appends an [Op] that creates a symlink in the container filesystem.
|
// Link appends an [Op] that creates a symlink in the container filesystem.
|
||||||
func (f *Ops) Link(target *Absolute, linkName string, dereference bool) *Ops {
|
func (f *Ops) Link(target *check.Absolute, linkName string, dereference bool) *Ops {
|
||||||
*f = append(*f, &SymlinkOp{target, linkName, dereference})
|
*f = append(*f, &SymlinkOp{target, linkName, dereference})
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
// SymlinkOp optionally dereferences LinkName and creates a symlink at container path Target.
|
// SymlinkOp optionally dereferences LinkName and creates a symlink at container path Target.
|
||||||
type SymlinkOp struct {
|
type SymlinkOp struct {
|
||||||
Target *Absolute
|
Target *check.Absolute
|
||||||
// LinkName is an arbitrary uninterpreted pathname.
|
// LinkName is an arbitrary uninterpreted pathname.
|
||||||
LinkName string
|
LinkName string
|
||||||
|
|
||||||
@@ -28,8 +30,8 @@ func (l *SymlinkOp) Valid() bool { return l != nil && l.Target != nil && l.LinkN
|
|||||||
|
|
||||||
func (l *SymlinkOp) early(_ *setupState, k syscallDispatcher) error {
|
func (l *SymlinkOp) early(_ *setupState, k syscallDispatcher) error {
|
||||||
if l.Dereference {
|
if l.Dereference {
|
||||||
if !isAbs(l.LinkName) {
|
if !path.IsAbs(l.LinkName) {
|
||||||
return &AbsoluteError{l.LinkName}
|
return &check.AbsoluteError{Pathname: l.LinkName}
|
||||||
}
|
}
|
||||||
if name, err := k.readlink(l.LinkName); err != nil {
|
if name, err := k.readlink(l.LinkName); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -4,26 +4,27 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/container/stub"
|
"hakurei.app/container/stub"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSymlinkOp(t *testing.T) {
|
func TestSymlinkOp(t *testing.T) {
|
||||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||||
{"mkdir", &Params{ParentPerm: 0700}, &SymlinkOp{
|
{"mkdir", &Params{ParentPerm: 0700}, &SymlinkOp{
|
||||||
Target: MustAbs("/etc/nixos"),
|
Target: check.MustAbs("/etc/nixos"),
|
||||||
LinkName: "/etc/static/nixos",
|
LinkName: "/etc/static/nixos",
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mkdirAll", stub.ExpectArgs{"/sysroot/etc", os.FileMode(0700)}, nil, stub.UniqueError(1)),
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/etc", os.FileMode(0700)}, nil, stub.UniqueError(1)),
|
||||||
}, stub.UniqueError(1)},
|
}, stub.UniqueError(1)},
|
||||||
|
|
||||||
{"abs", &Params{ParentPerm: 0755}, &SymlinkOp{
|
{"abs", &Params{ParentPerm: 0755}, &SymlinkOp{
|
||||||
Target: MustAbs("/etc/mtab"),
|
Target: check.MustAbs("/etc/mtab"),
|
||||||
LinkName: "etc/mtab",
|
LinkName: "etc/mtab",
|
||||||
Dereference: true,
|
Dereference: true,
|
||||||
}, nil, &AbsoluteError{"etc/mtab"}, nil, nil},
|
}, nil, &check.AbsoluteError{Pathname: "etc/mtab"}, nil, nil},
|
||||||
|
|
||||||
{"readlink", &Params{ParentPerm: 0755}, &SymlinkOp{
|
{"readlink", &Params{ParentPerm: 0755}, &SymlinkOp{
|
||||||
Target: MustAbs("/etc/mtab"),
|
Target: check.MustAbs("/etc/mtab"),
|
||||||
LinkName: "/etc/mtab",
|
LinkName: "/etc/mtab",
|
||||||
Dereference: true,
|
Dereference: true,
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
@@ -31,7 +32,7 @@ func TestSymlinkOp(t *testing.T) {
|
|||||||
}, stub.UniqueError(0), nil, nil},
|
}, stub.UniqueError(0), nil, nil},
|
||||||
|
|
||||||
{"success noderef", &Params{ParentPerm: 0700}, &SymlinkOp{
|
{"success noderef", &Params{ParentPerm: 0700}, &SymlinkOp{
|
||||||
Target: MustAbs("/etc/nixos"),
|
Target: check.MustAbs("/etc/nixos"),
|
||||||
LinkName: "/etc/static/nixos",
|
LinkName: "/etc/static/nixos",
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("mkdirAll", stub.ExpectArgs{"/sysroot/etc", os.FileMode(0700)}, nil, nil),
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/etc", os.FileMode(0700)}, nil, nil),
|
||||||
@@ -39,7 +40,7 @@ func TestSymlinkOp(t *testing.T) {
|
|||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"success", &Params{ParentPerm: 0755}, &SymlinkOp{
|
{"success", &Params{ParentPerm: 0755}, &SymlinkOp{
|
||||||
Target: MustAbs("/etc/mtab"),
|
Target: check.MustAbs("/etc/mtab"),
|
||||||
LinkName: "/etc/mtab",
|
LinkName: "/etc/mtab",
|
||||||
Dereference: true,
|
Dereference: true,
|
||||||
}, []stub.Call{
|
}, []stub.Call{
|
||||||
@@ -54,18 +55,18 @@ func TestSymlinkOp(t *testing.T) {
|
|||||||
{"nil", (*SymlinkOp)(nil), false},
|
{"nil", (*SymlinkOp)(nil), false},
|
||||||
{"zero", new(SymlinkOp), false},
|
{"zero", new(SymlinkOp), false},
|
||||||
{"nil target", &SymlinkOp{LinkName: "/run/current-system"}, false},
|
{"nil target", &SymlinkOp{LinkName: "/run/current-system"}, false},
|
||||||
{"zero linkname", &SymlinkOp{Target: MustAbs("/run/current-system")}, false},
|
{"zero linkname", &SymlinkOp{Target: check.MustAbs("/run/current-system")}, false},
|
||||||
{"valid", &SymlinkOp{Target: MustAbs("/run/current-system"), LinkName: "/run/current-system", Dereference: true}, true},
|
{"valid", &SymlinkOp{Target: check.MustAbs("/run/current-system"), LinkName: "/run/current-system", Dereference: true}, true},
|
||||||
})
|
})
|
||||||
|
|
||||||
checkOpsBuilder(t, []opsBuilderTestCase{
|
checkOpsBuilder(t, []opsBuilderTestCase{
|
||||||
{"current-system", new(Ops).Link(
|
{"current-system", new(Ops).Link(
|
||||||
MustAbs("/run/current-system"),
|
check.MustAbs("/run/current-system"),
|
||||||
"/run/current-system",
|
"/run/current-system",
|
||||||
true,
|
true,
|
||||||
), Ops{
|
), Ops{
|
||||||
&SymlinkOp{
|
&SymlinkOp{
|
||||||
Target: MustAbs("/run/current-system"),
|
Target: check.MustAbs("/run/current-system"),
|
||||||
LinkName: "/run/current-system",
|
LinkName: "/run/current-system",
|
||||||
Dereference: true,
|
Dereference: true,
|
||||||
},
|
},
|
||||||
@@ -76,40 +77,40 @@ func TestSymlinkOp(t *testing.T) {
|
|||||||
{"zero", new(SymlinkOp), new(SymlinkOp), false},
|
{"zero", new(SymlinkOp), new(SymlinkOp), false},
|
||||||
|
|
||||||
{"target differs", &SymlinkOp{
|
{"target differs", &SymlinkOp{
|
||||||
Target: MustAbs("/run/current-system/differs"),
|
Target: check.MustAbs("/run/current-system/differs"),
|
||||||
LinkName: "/run/current-system",
|
LinkName: "/run/current-system",
|
||||||
Dereference: true,
|
Dereference: true,
|
||||||
}, &SymlinkOp{
|
}, &SymlinkOp{
|
||||||
Target: MustAbs("/run/current-system"),
|
Target: check.MustAbs("/run/current-system"),
|
||||||
LinkName: "/run/current-system",
|
LinkName: "/run/current-system",
|
||||||
Dereference: true,
|
Dereference: true,
|
||||||
}, false},
|
}, false},
|
||||||
|
|
||||||
{"linkname differs", &SymlinkOp{
|
{"linkname differs", &SymlinkOp{
|
||||||
Target: MustAbs("/run/current-system"),
|
Target: check.MustAbs("/run/current-system"),
|
||||||
LinkName: "/run/current-system/differs",
|
LinkName: "/run/current-system/differs",
|
||||||
Dereference: true,
|
Dereference: true,
|
||||||
}, &SymlinkOp{
|
}, &SymlinkOp{
|
||||||
Target: MustAbs("/run/current-system"),
|
Target: check.MustAbs("/run/current-system"),
|
||||||
LinkName: "/run/current-system",
|
LinkName: "/run/current-system",
|
||||||
Dereference: true,
|
Dereference: true,
|
||||||
}, false},
|
}, false},
|
||||||
|
|
||||||
{"dereference differs", &SymlinkOp{
|
{"dereference differs", &SymlinkOp{
|
||||||
Target: MustAbs("/run/current-system"),
|
Target: check.MustAbs("/run/current-system"),
|
||||||
LinkName: "/run/current-system",
|
LinkName: "/run/current-system",
|
||||||
}, &SymlinkOp{
|
}, &SymlinkOp{
|
||||||
Target: MustAbs("/run/current-system"),
|
Target: check.MustAbs("/run/current-system"),
|
||||||
LinkName: "/run/current-system",
|
LinkName: "/run/current-system",
|
||||||
Dereference: true,
|
Dereference: true,
|
||||||
}, false},
|
}, false},
|
||||||
|
|
||||||
{"equals", &SymlinkOp{
|
{"equals", &SymlinkOp{
|
||||||
Target: MustAbs("/run/current-system"),
|
Target: check.MustAbs("/run/current-system"),
|
||||||
LinkName: "/run/current-system",
|
LinkName: "/run/current-system",
|
||||||
Dereference: true,
|
Dereference: true,
|
||||||
}, &SymlinkOp{
|
}, &SymlinkOp{
|
||||||
Target: MustAbs("/run/current-system"),
|
Target: check.MustAbs("/run/current-system"),
|
||||||
LinkName: "/run/current-system",
|
LinkName: "/run/current-system",
|
||||||
Dereference: true,
|
Dereference: true,
|
||||||
}, true},
|
}, true},
|
||||||
@@ -117,7 +118,7 @@ func TestSymlinkOp(t *testing.T) {
|
|||||||
|
|
||||||
checkOpMeta(t, []opMetaTestCase{
|
checkOpMeta(t, []opMetaTestCase{
|
||||||
{"current-system", &SymlinkOp{
|
{"current-system", &SymlinkOp{
|
||||||
Target: MustAbs("/run/current-system"),
|
Target: check.MustAbs("/run/current-system"),
|
||||||
LinkName: "/run/current-system",
|
LinkName: "/run/current-system",
|
||||||
Dereference: true,
|
Dereference: true,
|
||||||
}, "creating", `symlink on "/run/current-system" linkname "/run/current-system"`},
|
}, "creating", `symlink on "/run/current-system" linkname "/run/current-system"`},
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
. "syscall"
|
. "syscall"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { gob.Register(new(MountTmpfsOp)) }
|
func init() { gob.Register(new(MountTmpfsOp)) }
|
||||||
@@ -18,13 +20,13 @@ func (e TmpfsSizeError) Error() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tmpfs appends an [Op] that mounts tmpfs on container path [MountTmpfsOp.Path].
|
// Tmpfs appends an [Op] that mounts tmpfs on container path [MountTmpfsOp.Path].
|
||||||
func (f *Ops) Tmpfs(target *Absolute, size int, perm os.FileMode) *Ops {
|
func (f *Ops) Tmpfs(target *check.Absolute, size int, perm os.FileMode) *Ops {
|
||||||
*f = append(*f, &MountTmpfsOp{SourceTmpfsEphemeral, target, MS_NOSUID | MS_NODEV, size, perm})
|
*f = append(*f, &MountTmpfsOp{SourceTmpfsEphemeral, target, MS_NOSUID | MS_NODEV, size, perm})
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
// Readonly appends an [Op] that mounts read-only tmpfs on container path [MountTmpfsOp.Path].
|
// Readonly appends an [Op] that mounts read-only tmpfs on container path [MountTmpfsOp.Path].
|
||||||
func (f *Ops) Readonly(target *Absolute, perm os.FileMode) *Ops {
|
func (f *Ops) Readonly(target *check.Absolute, perm os.FileMode) *Ops {
|
||||||
*f = append(*f, &MountTmpfsOp{SourceTmpfsReadonly, target, MS_RDONLY | MS_NOSUID | MS_NODEV, 0, perm})
|
*f = append(*f, &MountTmpfsOp{SourceTmpfsReadonly, target, MS_RDONLY | MS_NOSUID | MS_NODEV, 0, perm})
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
@@ -32,7 +34,7 @@ func (f *Ops) Readonly(target *Absolute, perm os.FileMode) *Ops {
|
|||||||
// MountTmpfsOp mounts [FstypeTmpfs] on container Path.
|
// MountTmpfsOp mounts [FstypeTmpfs] on container Path.
|
||||||
type MountTmpfsOp struct {
|
type MountTmpfsOp struct {
|
||||||
FSName string
|
FSName string
|
||||||
Path *Absolute
|
Path *check.Absolute
|
||||||
Flags uintptr
|
Flags uintptr
|
||||||
Size int
|
Size int
|
||||||
Perm os.FileMode
|
Perm os.FileMode
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/container/stub"
|
"hakurei.app/container/stub"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ func TestMountTmpfsOp(t *testing.T) {
|
|||||||
|
|
||||||
{"success", new(Params), &MountTmpfsOp{
|
{"success", new(Params), &MountTmpfsOp{
|
||||||
FSName: "ephemeral",
|
FSName: "ephemeral",
|
||||||
Path: MustAbs("/run/user/1000/"),
|
Path: check.MustAbs("/run/user/1000/"),
|
||||||
Size: 1 << 10,
|
Size: 1 << 10,
|
||||||
Perm: 0700,
|
Perm: 0700,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
@@ -42,19 +43,19 @@ func TestMountTmpfsOp(t *testing.T) {
|
|||||||
{"nil", (*MountTmpfsOp)(nil), false},
|
{"nil", (*MountTmpfsOp)(nil), false},
|
||||||
{"zero", new(MountTmpfsOp), false},
|
{"zero", new(MountTmpfsOp), false},
|
||||||
{"nil path", &MountTmpfsOp{FSName: "tmpfs"}, false},
|
{"nil path", &MountTmpfsOp{FSName: "tmpfs"}, false},
|
||||||
{"zero fsname", &MountTmpfsOp{Path: MustAbs("/tmp/")}, false},
|
{"zero fsname", &MountTmpfsOp{Path: check.MustAbs("/tmp/")}, false},
|
||||||
{"valid", &MountTmpfsOp{FSName: "tmpfs", Path: MustAbs("/tmp/")}, true},
|
{"valid", &MountTmpfsOp{FSName: "tmpfs", Path: check.MustAbs("/tmp/")}, true},
|
||||||
})
|
})
|
||||||
|
|
||||||
checkOpsBuilder(t, []opsBuilderTestCase{
|
checkOpsBuilder(t, []opsBuilderTestCase{
|
||||||
{"runtime", new(Ops).Tmpfs(
|
{"runtime", new(Ops).Tmpfs(
|
||||||
MustAbs("/run/user"),
|
check.MustAbs("/run/user"),
|
||||||
1<<10,
|
1<<10,
|
||||||
0755,
|
0755,
|
||||||
), Ops{
|
), Ops{
|
||||||
&MountTmpfsOp{
|
&MountTmpfsOp{
|
||||||
FSName: "ephemeral",
|
FSName: "ephemeral",
|
||||||
Path: MustAbs("/run/user"),
|
Path: check.MustAbs("/run/user"),
|
||||||
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
||||||
Size: 1 << 10,
|
Size: 1 << 10,
|
||||||
Perm: 0755,
|
Perm: 0755,
|
||||||
@@ -62,12 +63,12 @@ func TestMountTmpfsOp(t *testing.T) {
|
|||||||
}},
|
}},
|
||||||
|
|
||||||
{"nscd", new(Ops).Readonly(
|
{"nscd", new(Ops).Readonly(
|
||||||
MustAbs("/var/run/nscd"),
|
check.MustAbs("/var/run/nscd"),
|
||||||
0755,
|
0755,
|
||||||
), Ops{
|
), Ops{
|
||||||
&MountTmpfsOp{
|
&MountTmpfsOp{
|
||||||
FSName: "readonly",
|
FSName: "readonly",
|
||||||
Path: MustAbs("/var/run/nscd"),
|
Path: check.MustAbs("/var/run/nscd"),
|
||||||
Flags: syscall.MS_NOSUID | syscall.MS_NODEV | syscall.MS_RDONLY,
|
Flags: syscall.MS_NOSUID | syscall.MS_NODEV | syscall.MS_RDONLY,
|
||||||
Perm: 0755,
|
Perm: 0755,
|
||||||
},
|
},
|
||||||
@@ -79,13 +80,13 @@ func TestMountTmpfsOp(t *testing.T) {
|
|||||||
|
|
||||||
{"fsname differs", &MountTmpfsOp{
|
{"fsname differs", &MountTmpfsOp{
|
||||||
FSName: "readonly",
|
FSName: "readonly",
|
||||||
Path: MustAbs("/run/user"),
|
Path: check.MustAbs("/run/user"),
|
||||||
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
||||||
Size: 1 << 10,
|
Size: 1 << 10,
|
||||||
Perm: 0755,
|
Perm: 0755,
|
||||||
}, &MountTmpfsOp{
|
}, &MountTmpfsOp{
|
||||||
FSName: "ephemeral",
|
FSName: "ephemeral",
|
||||||
Path: MustAbs("/run/user"),
|
Path: check.MustAbs("/run/user"),
|
||||||
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
||||||
Size: 1 << 10,
|
Size: 1 << 10,
|
||||||
Perm: 0755,
|
Perm: 0755,
|
||||||
@@ -93,13 +94,13 @@ func TestMountTmpfsOp(t *testing.T) {
|
|||||||
|
|
||||||
{"path differs", &MountTmpfsOp{
|
{"path differs", &MountTmpfsOp{
|
||||||
FSName: "ephemeral",
|
FSName: "ephemeral",
|
||||||
Path: MustAbs("/run/user/differs"),
|
Path: check.MustAbs("/run/user/differs"),
|
||||||
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
||||||
Size: 1 << 10,
|
Size: 1 << 10,
|
||||||
Perm: 0755,
|
Perm: 0755,
|
||||||
}, &MountTmpfsOp{
|
}, &MountTmpfsOp{
|
||||||
FSName: "ephemeral",
|
FSName: "ephemeral",
|
||||||
Path: MustAbs("/run/user"),
|
Path: check.MustAbs("/run/user"),
|
||||||
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
||||||
Size: 1 << 10,
|
Size: 1 << 10,
|
||||||
Perm: 0755,
|
Perm: 0755,
|
||||||
@@ -107,13 +108,13 @@ func TestMountTmpfsOp(t *testing.T) {
|
|||||||
|
|
||||||
{"flags differs", &MountTmpfsOp{
|
{"flags differs", &MountTmpfsOp{
|
||||||
FSName: "ephemeral",
|
FSName: "ephemeral",
|
||||||
Path: MustAbs("/run/user"),
|
Path: check.MustAbs("/run/user"),
|
||||||
Flags: syscall.MS_NOSUID | syscall.MS_NODEV | syscall.MS_RDONLY,
|
Flags: syscall.MS_NOSUID | syscall.MS_NODEV | syscall.MS_RDONLY,
|
||||||
Size: 1 << 10,
|
Size: 1 << 10,
|
||||||
Perm: 0755,
|
Perm: 0755,
|
||||||
}, &MountTmpfsOp{
|
}, &MountTmpfsOp{
|
||||||
FSName: "ephemeral",
|
FSName: "ephemeral",
|
||||||
Path: MustAbs("/run/user"),
|
Path: check.MustAbs("/run/user"),
|
||||||
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
||||||
Size: 1 << 10,
|
Size: 1 << 10,
|
||||||
Perm: 0755,
|
Perm: 0755,
|
||||||
@@ -121,13 +122,13 @@ func TestMountTmpfsOp(t *testing.T) {
|
|||||||
|
|
||||||
{"size differs", &MountTmpfsOp{
|
{"size differs", &MountTmpfsOp{
|
||||||
FSName: "ephemeral",
|
FSName: "ephemeral",
|
||||||
Path: MustAbs("/run/user"),
|
Path: check.MustAbs("/run/user"),
|
||||||
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
||||||
Size: 1,
|
Size: 1,
|
||||||
Perm: 0755,
|
Perm: 0755,
|
||||||
}, &MountTmpfsOp{
|
}, &MountTmpfsOp{
|
||||||
FSName: "ephemeral",
|
FSName: "ephemeral",
|
||||||
Path: MustAbs("/run/user"),
|
Path: check.MustAbs("/run/user"),
|
||||||
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
||||||
Size: 1 << 10,
|
Size: 1 << 10,
|
||||||
Perm: 0755,
|
Perm: 0755,
|
||||||
@@ -135,13 +136,13 @@ func TestMountTmpfsOp(t *testing.T) {
|
|||||||
|
|
||||||
{"perm differs", &MountTmpfsOp{
|
{"perm differs", &MountTmpfsOp{
|
||||||
FSName: "ephemeral",
|
FSName: "ephemeral",
|
||||||
Path: MustAbs("/run/user"),
|
Path: check.MustAbs("/run/user"),
|
||||||
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
||||||
Size: 1 << 10,
|
Size: 1 << 10,
|
||||||
Perm: 0700,
|
Perm: 0700,
|
||||||
}, &MountTmpfsOp{
|
}, &MountTmpfsOp{
|
||||||
FSName: "ephemeral",
|
FSName: "ephemeral",
|
||||||
Path: MustAbs("/run/user"),
|
Path: check.MustAbs("/run/user"),
|
||||||
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
||||||
Size: 1 << 10,
|
Size: 1 << 10,
|
||||||
Perm: 0755,
|
Perm: 0755,
|
||||||
@@ -149,13 +150,13 @@ func TestMountTmpfsOp(t *testing.T) {
|
|||||||
|
|
||||||
{"equals", &MountTmpfsOp{
|
{"equals", &MountTmpfsOp{
|
||||||
FSName: "ephemeral",
|
FSName: "ephemeral",
|
||||||
Path: MustAbs("/run/user"),
|
Path: check.MustAbs("/run/user"),
|
||||||
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
||||||
Size: 1 << 10,
|
Size: 1 << 10,
|
||||||
Perm: 0755,
|
Perm: 0755,
|
||||||
}, &MountTmpfsOp{
|
}, &MountTmpfsOp{
|
||||||
FSName: "ephemeral",
|
FSName: "ephemeral",
|
||||||
Path: MustAbs("/run/user"),
|
Path: check.MustAbs("/run/user"),
|
||||||
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
||||||
Size: 1 << 10,
|
Size: 1 << 10,
|
||||||
Perm: 0755,
|
Perm: 0755,
|
||||||
@@ -165,7 +166,7 @@ func TestMountTmpfsOp(t *testing.T) {
|
|||||||
checkOpMeta(t, []opMetaTestCase{
|
checkOpMeta(t, []opMetaTestCase{
|
||||||
{"runtime", &MountTmpfsOp{
|
{"runtime", &MountTmpfsOp{
|
||||||
FSName: "ephemeral",
|
FSName: "ephemeral",
|
||||||
Path: MustAbs("/run/user"),
|
Path: check.MustAbs("/run/user"),
|
||||||
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
||||||
Size: 1 << 10,
|
Size: 1 << 10,
|
||||||
Perm: 0755,
|
Perm: 0755,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
. "syscall"
|
. "syscall"
|
||||||
|
|
||||||
"hakurei.app/container/vfs"
|
"hakurei.app/container/vfs"
|
||||||
@@ -59,7 +58,6 @@ const (
|
|||||||
FstypeNULL = zeroString
|
FstypeNULL = zeroString
|
||||||
// FstypeProc represents the proc pseudo-filesystem.
|
// FstypeProc represents the proc pseudo-filesystem.
|
||||||
// A fully visible instance of proc must be available in the mount namespace for proc to be mounted.
|
// A fully visible instance of proc must be available in the mount namespace for proc to be mounted.
|
||||||
// This filesystem type is usually mounted on [FHSProc].
|
|
||||||
FstypeProc = "proc"
|
FstypeProc = "proc"
|
||||||
// FstypeDevpts represents the devpts pseudo-filesystem.
|
// FstypeDevpts represents the devpts pseudo-filesystem.
|
||||||
// This type of filesystem is usually mounted on /dev/pts.
|
// This type of filesystem is usually mounted on /dev/pts.
|
||||||
@@ -86,28 +84,20 @@ const (
|
|||||||
// OptionOverlayUserxattr represents the userxattr option of the overlay pseudo-filesystem.
|
// OptionOverlayUserxattr represents the userxattr option of the overlay pseudo-filesystem.
|
||||||
// Use the "user.overlay." xattr namespace instead of "trusted.overlay.".
|
// Use the "user.overlay." xattr namespace instead of "trusted.overlay.".
|
||||||
OptionOverlayUserxattr = "userxattr"
|
OptionOverlayUserxattr = "userxattr"
|
||||||
|
|
||||||
// SpecialOverlayEscape is the escape string for overlay mount options.
|
|
||||||
SpecialOverlayEscape = `\`
|
|
||||||
// SpecialOverlayOption is the separator string between overlay mount options.
|
|
||||||
SpecialOverlayOption = ","
|
|
||||||
// SpecialOverlayPath is the separator string between overlay paths.
|
|
||||||
SpecialOverlayPath = ":"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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(source, target string, flags uintptr) error {
|
func (p *procPaths) bindMount(msg Msg, source, target string, flags uintptr) error {
|
||||||
// syscallDispatcher.bindMount and procPaths.remount must not be called from this function
|
// 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 {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
return p.k.remount(msg, target, flags)
|
||||||
return p.k.remount(target, flags)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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(target string, flags uintptr) error {
|
func (p *procPaths) remount(msg Msg, target string, flags uintptr) error {
|
||||||
// syscallDispatcher methods bindMount, remount must not be called from this function
|
// syscallDispatcher methods bindMount, remount must not be called from this function
|
||||||
|
|
||||||
var targetFinal string
|
var targetFinal string
|
||||||
@@ -116,7 +106,7 @@ func (p *procPaths) remount(target string, flags uintptr) error {
|
|||||||
} else {
|
} else {
|
||||||
targetFinal = v
|
targetFinal = v
|
||||||
if targetFinal != target {
|
if targetFinal != target {
|
||||||
p.k.verbosef("target resolves to %q", targetFinal)
|
msg.Verbosef("target resolves to %q", targetFinal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +136,7 @@ func (p *procPaths) remount(target string, flags uintptr) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = remountWithFlags(p.k, n, mf); err != nil {
|
if err = remountWithFlags(p.k, msg, n, mf); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if flags&MS_REC == 0 {
|
if flags&MS_REC == 0 {
|
||||||
@@ -159,7 +149,7 @@ func (p *procPaths) remount(target string, flags uintptr) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = remountWithFlags(p.k, cur, mf); err != nil && !errors.Is(err, EACCES) {
|
if err = remountWithFlags(p.k, msg, cur, mf); err != nil && !errors.Is(err, EACCES) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,12 +159,12 @@ func (p *procPaths) remount(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, n *vfs.MountInfoNode, mf uintptr) error {
|
func remountWithFlags(k syscallDispatcher, msg Msg, n *vfs.MountInfoNode, mf uintptr) error {
|
||||||
// syscallDispatcher methods bindMount, remount must not be called from this function
|
// syscallDispatcher methods bindMount, remount must not be called from this function
|
||||||
|
|
||||||
kf, unmatched := n.Flags()
|
kf, unmatched := n.Flags()
|
||||||
if len(unmatched) != 0 {
|
if len(unmatched) != 0 {
|
||||||
k.verbosef("unmatched vfs options: %q", unmatched)
|
msg.Verbosef("unmatched vfs options: %q", unmatched)
|
||||||
}
|
}
|
||||||
|
|
||||||
if kf&mf != mf {
|
if kf&mf != mf {
|
||||||
@@ -208,20 +198,3 @@ func parentPerm(perm os.FileMode) os.FileMode {
|
|||||||
}
|
}
|
||||||
return os.FileMode(pperm)
|
return os.FileMode(pperm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EscapeOverlayDataSegment escapes a string for formatting into the data argument of an overlay mount call.
|
|
||||||
func EscapeOverlayDataSegment(s string) string {
|
|
||||||
if s == zeroString {
|
|
||||||
return zeroString
|
|
||||||
}
|
|
||||||
|
|
||||||
if f := strings.SplitN(s, "\x00", 2); len(f) > 0 {
|
|
||||||
s = f[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.NewReplacer(
|
|
||||||
SpecialOverlayEscape, SpecialOverlayEscape+SpecialOverlayEscape,
|
|
||||||
SpecialOverlayOption, SpecialOverlayEscape+SpecialOverlayOption,
|
|
||||||
SpecialOverlayPath, SpecialOverlayEscape+SpecialOverlayPath,
|
|
||||||
).Replace(s)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,21 +11,21 @@ import (
|
|||||||
|
|
||||||
func TestBindMount(t *testing.T) {
|
func TestBindMount(t *testing.T) {
|
||||||
checkSimple(t, "bindMount", []simpleTestCase{
|
checkSimple(t, "bindMount", []simpleTestCase{
|
||||||
{"mount", func(k syscallDispatcher) error {
|
{"mount", func(k *kstub) error {
|
||||||
return newProcPaths(k, hostPath).bindMount("/host/nix", "/sysroot/nix", syscall.MS_RDONLY)
|
return newProcPaths(k, hostPath).bindMount(nil, "/host/nix", "/sysroot/nix", syscall.MS_RDONLY)
|
||||||
}, stub.Expect{Calls: []stub.Call{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/nix", "", uintptr(0x9000), ""}, nil, stub.UniqueError(0xbad)),
|
call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/nix", "", uintptr(0x9000), ""}, nil, stub.UniqueError(0xbad)),
|
||||||
}}, stub.UniqueError(0xbad)},
|
}}, stub.UniqueError(0xbad)},
|
||||||
|
|
||||||
{"success ne", func(k syscallDispatcher) error {
|
{"success ne", func(k *kstub) error {
|
||||||
return newProcPaths(k, hostPath).bindMount("/host/nix", "/sysroot/.host-nix", syscall.MS_RDONLY)
|
return newProcPaths(k, hostPath).bindMount(k, "/host/nix", "/sysroot/.host-nix", syscall.MS_RDONLY)
|
||||||
}, stub.Expect{Calls: []stub.Call{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/.host-nix", "", uintptr(0x9000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/.host-nix", "", uintptr(0x9000), ""}, nil, nil),
|
||||||
call("remount", stub.ExpectArgs{"/sysroot/.host-nix", uintptr(1)}, nil, nil),
|
call("remount", stub.ExpectArgs{"/sysroot/.host-nix", uintptr(1)}, nil, nil),
|
||||||
}}, nil},
|
}}, nil},
|
||||||
|
|
||||||
{"success", func(k syscallDispatcher) error {
|
{"success", func(k *kstub) error {
|
||||||
return newProcPaths(k, hostPath).bindMount("/host/nix", "/sysroot/nix", syscall.MS_RDONLY)
|
return newProcPaths(k, hostPath).bindMount(k, "/host/nix", "/sysroot/nix", syscall.MS_RDONLY)
|
||||||
}, stub.Expect{Calls: []stub.Call{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/nix", "", uintptr(0x9000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/nix", "", uintptr(0x9000), ""}, nil, nil),
|
||||||
call("remount", stub.ExpectArgs{"/sysroot/nix", uintptr(1)}, nil, nil),
|
call("remount", stub.ExpectArgs{"/sysroot/nix", uintptr(1)}, nil, nil),
|
||||||
@@ -77,29 +77,29 @@ func TestRemount(t *testing.T) {
|
|||||||
416 415 0:30 / /sysroot/nix/store ro,relatime master:5 - overlay overlay rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work`
|
416 415 0:30 / /sysroot/nix/store ro,relatime master:5 - overlay overlay rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work`
|
||||||
|
|
||||||
checkSimple(t, "remount", []simpleTestCase{
|
checkSimple(t, "remount", []simpleTestCase{
|
||||||
{"evalSymlinks", func(k syscallDispatcher) error {
|
{"evalSymlinks", func(k *kstub) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, stub.Expect{Calls: []stub.Call{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", stub.UniqueError(6)),
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", stub.UniqueError(6)),
|
||||||
}}, stub.UniqueError(6)},
|
}}, stub.UniqueError(6)},
|
||||||
|
|
||||||
{"open", func(k syscallDispatcher) error {
|
{"open", func(k *kstub) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, stub.Expect{Calls: []stub.Call{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||||
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, stub.UniqueError(5)),
|
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, stub.UniqueError(5)),
|
||||||
}}, &os.PathError{Op: "open", Path: "/sysroot/nix", Err: stub.UniqueError(5)}},
|
}}, &os.PathError{Op: "open", Path: "/sysroot/nix", Err: stub.UniqueError(5)}},
|
||||||
|
|
||||||
{"readlink", func(k syscallDispatcher) error {
|
{"readlink", func(k *kstub) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, stub.Expect{Calls: []stub.Call{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||||
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||||
call("readlink", stub.ExpectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/nix", stub.UniqueError(4)),
|
call("readlink", stub.ExpectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/nix", stub.UniqueError(4)),
|
||||||
}}, stub.UniqueError(4)},
|
}}, stub.UniqueError(4)},
|
||||||
|
|
||||||
{"close", func(k syscallDispatcher) error {
|
{"close", func(k *kstub) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, stub.Expect{Calls: []stub.Call{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||||
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||||
@@ -107,8 +107,8 @@ func TestRemount(t *testing.T) {
|
|||||||
call("close", stub.ExpectArgs{0xdeadbeef}, nil, stub.UniqueError(3)),
|
call("close", stub.ExpectArgs{0xdeadbeef}, nil, stub.UniqueError(3)),
|
||||||
}}, &os.PathError{Op: "close", Path: "/sysroot/nix", Err: stub.UniqueError(3)}},
|
}}, &os.PathError{Op: "close", Path: "/sysroot/nix", Err: stub.UniqueError(3)}},
|
||||||
|
|
||||||
{"mountinfo no match", func(k syscallDispatcher) error {
|
{"mountinfo no match", func(k *kstub) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount(k, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, stub.Expect{Calls: []stub.Call{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/.hakurei", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/.hakurei", nil),
|
||||||
call("verbosef", stub.ExpectArgs{"target resolves to %q", []any{"/sysroot/.hakurei"}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"target resolves to %q", []any{"/sysroot/.hakurei"}}, nil, nil),
|
||||||
@@ -118,8 +118,8 @@ func TestRemount(t *testing.T) {
|
|||||||
call("openNew", stub.ExpectArgs{"/host/proc/self/mountinfo"}, newConstFile(sampleMountinfoNix), nil),
|
call("openNew", stub.ExpectArgs{"/host/proc/self/mountinfo"}, newConstFile(sampleMountinfoNix), nil),
|
||||||
}}, &vfs.DecoderError{Op: "unfold", Line: -1, Err: vfs.UnfoldTargetError("/sysroot/.hakurei")}},
|
}}, &vfs.DecoderError{Op: "unfold", Line: -1, Err: vfs.UnfoldTargetError("/sysroot/.hakurei")}},
|
||||||
|
|
||||||
{"mountinfo", func(k syscallDispatcher) error {
|
{"mountinfo", func(k *kstub) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, stub.Expect{Calls: []stub.Call{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||||
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||||
@@ -128,8 +128,8 @@ func TestRemount(t *testing.T) {
|
|||||||
call("openNew", stub.ExpectArgs{"/host/proc/self/mountinfo"}, newConstFile("\x00"), nil),
|
call("openNew", stub.ExpectArgs{"/host/proc/self/mountinfo"}, newConstFile("\x00"), nil),
|
||||||
}}, &vfs.DecoderError{Op: "parse", Line: 0, Err: vfs.ErrMountInfoFields}},
|
}}, &vfs.DecoderError{Op: "parse", Line: 0, Err: vfs.ErrMountInfoFields}},
|
||||||
|
|
||||||
{"mount", func(k syscallDispatcher) error {
|
{"mount", func(k *kstub) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, stub.Expect{Calls: []stub.Call{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||||
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||||
@@ -139,8 +139,8 @@ func TestRemount(t *testing.T) {
|
|||||||
call("mount", stub.ExpectArgs{"none", "/sysroot/nix", "", uintptr(0x209027), ""}, nil, stub.UniqueError(2)),
|
call("mount", stub.ExpectArgs{"none", "/sysroot/nix", "", uintptr(0x209027), ""}, nil, stub.UniqueError(2)),
|
||||||
}}, stub.UniqueError(2)},
|
}}, stub.UniqueError(2)},
|
||||||
|
|
||||||
{"mount propagate", func(k syscallDispatcher) error {
|
{"mount propagate", func(k *kstub) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, stub.Expect{Calls: []stub.Call{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||||
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||||
@@ -151,8 +151,8 @@ func TestRemount(t *testing.T) {
|
|||||||
call("mount", stub.ExpectArgs{"none", "/sysroot/nix/.ro-store", "", uintptr(0x209027), ""}, nil, stub.UniqueError(1)),
|
call("mount", stub.ExpectArgs{"none", "/sysroot/nix/.ro-store", "", uintptr(0x209027), ""}, nil, stub.UniqueError(1)),
|
||||||
}}, stub.UniqueError(1)},
|
}}, stub.UniqueError(1)},
|
||||||
|
|
||||||
{"success toplevel", func(k syscallDispatcher) error {
|
{"success toplevel", func(k *kstub) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/bin", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount(nil, "/sysroot/bin", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, stub.Expect{Calls: []stub.Call{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/bin"}, "/sysroot/bin", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/bin"}, "/sysroot/bin", nil),
|
||||||
call("open", stub.ExpectArgs{"/sysroot/bin", 0x280000, uint32(0)}, 0xbabe, nil),
|
call("open", stub.ExpectArgs{"/sysroot/bin", 0x280000, uint32(0)}, 0xbabe, nil),
|
||||||
@@ -162,8 +162,8 @@ func TestRemount(t *testing.T) {
|
|||||||
call("mount", stub.ExpectArgs{"none", "/sysroot/bin", "", uintptr(0x209027), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"none", "/sysroot/bin", "", uintptr(0x209027), ""}, nil, nil),
|
||||||
}}, nil},
|
}}, nil},
|
||||||
|
|
||||||
{"success EACCES", func(k syscallDispatcher) error {
|
{"success EACCES", func(k *kstub) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, stub.Expect{Calls: []stub.Call{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||||
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||||
@@ -175,8 +175,8 @@ func TestRemount(t *testing.T) {
|
|||||||
call("mount", stub.ExpectArgs{"none", "/sysroot/nix/store", "", uintptr(0x209027), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"none", "/sysroot/nix/store", "", uintptr(0x209027), ""}, nil, nil),
|
||||||
}}, nil},
|
}}, nil},
|
||||||
|
|
||||||
{"success no propagate", func(k syscallDispatcher) error {
|
{"success no propagate", func(k *kstub) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, stub.Expect{Calls: []stub.Call{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||||
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||||
@@ -186,8 +186,8 @@ func TestRemount(t *testing.T) {
|
|||||||
call("mount", stub.ExpectArgs{"none", "/sysroot/nix", "", uintptr(0x209027), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"none", "/sysroot/nix", "", uintptr(0x209027), ""}, nil, nil),
|
||||||
}}, nil},
|
}}, nil},
|
||||||
|
|
||||||
{"success case sensitive", func(k syscallDispatcher) error {
|
{"success case sensitive", func(k *kstub) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, stub.Expect{Calls: []stub.Call{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||||
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||||
@@ -199,8 +199,8 @@ func TestRemount(t *testing.T) {
|
|||||||
call("mount", stub.ExpectArgs{"none", "/sysroot/nix/store", "", uintptr(0x209027), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"none", "/sysroot/nix/store", "", uintptr(0x209027), ""}, nil, nil),
|
||||||
}}, nil},
|
}}, nil},
|
||||||
|
|
||||||
{"success", func(k syscallDispatcher) error {
|
{"success", func(k *kstub) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/.nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount(k, "/sysroot/.nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, stub.Expect{Calls: []stub.Call{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/.nix"}, "/sysroot/NIX", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/.nix"}, "/sysroot/NIX", nil),
|
||||||
call("verbosef", stub.ExpectArgs{"target resolves to %q", []any{"/sysroot/NIX"}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"target resolves to %q", []any{"/sysroot/NIX"}}, nil, nil),
|
||||||
@@ -217,18 +217,18 @@ func TestRemount(t *testing.T) {
|
|||||||
|
|
||||||
func TestRemountWithFlags(t *testing.T) {
|
func TestRemountWithFlags(t *testing.T) {
|
||||||
checkSimple(t, "remountWithFlags", []simpleTestCase{
|
checkSimple(t, "remountWithFlags", []simpleTestCase{
|
||||||
{"noop unmatched", func(k syscallDispatcher) error {
|
{"noop unmatched", func(k *kstub) error {
|
||||||
return remountWithFlags(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)
|
||||||
}, stub.Expect{Calls: []stub.Call{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
call("verbosef", stub.ExpectArgs{"unmatched vfs options: %q", []any{[]string{"cat"}}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"unmatched vfs options: %q", []any{[]string{"cat"}}}, nil, nil),
|
||||||
}}, nil},
|
}}, nil},
|
||||||
|
|
||||||
{"noop", func(k syscallDispatcher) error {
|
{"noop", func(k *kstub) error {
|
||||||
return remountWithFlags(k, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime"}}, 0)
|
return remountWithFlags(k, nil, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime"}}, 0)
|
||||||
}, stub.Expect{}, nil},
|
}, stub.Expect{}, nil},
|
||||||
|
|
||||||
{"success", func(k syscallDispatcher) error {
|
{"success", func(k *kstub) error {
|
||||||
return remountWithFlags(k, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime"}}, syscall.MS_RDONLY)
|
return remountWithFlags(k, nil, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime"}}, syscall.MS_RDONLY)
|
||||||
}, stub.Expect{Calls: []stub.Call{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
call("mount", stub.ExpectArgs{"none", "", "", uintptr(0x209021), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"none", "", "", uintptr(0x209021), ""}, nil, nil),
|
||||||
}}, nil},
|
}}, nil},
|
||||||
@@ -237,20 +237,20 @@ func TestRemountWithFlags(t *testing.T) {
|
|||||||
|
|
||||||
func TestMountTmpfs(t *testing.T) {
|
func TestMountTmpfs(t *testing.T) {
|
||||||
checkSimple(t, "mountTmpfs", []simpleTestCase{
|
checkSimple(t, "mountTmpfs", []simpleTestCase{
|
||||||
{"mkdirAll", func(k syscallDispatcher) 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)
|
||||||
}, stub.Expect{Calls: []stub.Call{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
call("mkdirAll", stub.ExpectArgs{"/sysroot/run/user/1000", os.FileMode(0700)}, nil, stub.UniqueError(0)),
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/run/user/1000", os.FileMode(0700)}, nil, stub.UniqueError(0)),
|
||||||
}}, stub.UniqueError(0)},
|
}}, stub.UniqueError(0)},
|
||||||
|
|
||||||
{"success no size", func(k syscallDispatcher) error {
|
{"success no size", func(k *kstub) error {
|
||||||
return mountTmpfs(k, "ephemeral", "/sysroot/run/user/1000", 0, 0, 0710)
|
return mountTmpfs(k, "ephemeral", "/sysroot/run/user/1000", 0, 0, 0710)
|
||||||
}, stub.Expect{Calls: []stub.Call{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
call("mkdirAll", stub.ExpectArgs{"/sysroot/run/user/1000", os.FileMode(0750)}, nil, nil),
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/run/user/1000", os.FileMode(0750)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"ephemeral", "/sysroot/run/user/1000", "tmpfs", uintptr(0), "mode=0710"}, nil, nil),
|
call("mount", stub.ExpectArgs{"ephemeral", "/sysroot/run/user/1000", "tmpfs", uintptr(0), "mode=0710"}, nil, nil),
|
||||||
}}, nil},
|
}}, nil},
|
||||||
|
|
||||||
{"success", func(k syscallDispatcher) error {
|
{"success", 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)
|
||||||
}, stub.Expect{Calls: []stub.Call{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
call("mkdirAll", stub.ExpectArgs{"/sysroot/run/user/1000", os.FileMode(0700)}, nil, nil),
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/run/user/1000", os.FileMode(0700)}, nil, nil),
|
||||||
@@ -281,23 +281,3 @@ func TestParentPerm(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEscapeOverlayDataSegment(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
s string
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{"zero", zeroString, zeroString},
|
|
||||||
{"multi", `\\\:,:,\\\`, `\\\\\\\:\,\:\,\\\\\\`},
|
|
||||||
{"bwrap", `/path :,\`, `/path \:\,\\`},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
if got := EscapeOverlayDataSegment(tc.s); got != tc.want {
|
|
||||||
t.Errorf("escapeOverlayDataSegment: %s, want %s", got, tc.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -23,45 +23,88 @@ func GetErrorMessage(err error) (string, bool) {
|
|||||||
return e.Message(), true
|
return e.Message(), true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Msg is used for package-wide verbose logging.
|
||||||
type Msg interface {
|
type Msg interface {
|
||||||
|
// GetLogger returns the address of the underlying [log.Logger].
|
||||||
|
GetLogger() *log.Logger
|
||||||
|
|
||||||
|
// IsVerbose atomically loads and returns whether [Msg] has verbose logging enabled.
|
||||||
IsVerbose() bool
|
IsVerbose() bool
|
||||||
|
// SwapVerbose atomically stores a new verbose state and returns the previous value held by [Msg].
|
||||||
|
SwapVerbose(verbose bool) bool
|
||||||
|
// Verbose passes its argument to the Println method of the underlying [log.Logger] if IsVerbose returns true.
|
||||||
Verbose(v ...any)
|
Verbose(v ...any)
|
||||||
|
// Verbosef passes its argument to the Printf method of the underlying [log.Logger] if IsVerbose returns true.
|
||||||
Verbosef(format string, v ...any)
|
Verbosef(format string, v ...any)
|
||||||
|
|
||||||
Suspend()
|
// Suspend causes the embedded [Suspendable] to withhold writes to its downstream [io.Writer].
|
||||||
|
// Suspend returns false and is a noop if called between calls to Suspend and Resume.
|
||||||
|
Suspend() bool
|
||||||
|
|
||||||
|
// Resume dumps the entire buffer held by the embedded [Suspendable] and stops withholding future writes.
|
||||||
|
// Resume returns false and is a noop if a call to Suspend does not precede it.
|
||||||
Resume() bool
|
Resume() bool
|
||||||
|
|
||||||
|
// BeforeExit runs implementation-specific cleanup code, and optionally prints warnings.
|
||||||
|
// BeforeExit is called before [os.Exit].
|
||||||
BeforeExit()
|
BeforeExit()
|
||||||
}
|
}
|
||||||
|
|
||||||
type DefaultMsg struct{ inactive atomic.Bool }
|
// defaultMsg is the default implementation of the [Msg] interface.
|
||||||
|
// The zero value is not safe for use. Callers should use the [NewMsg] function instead.
|
||||||
|
type defaultMsg struct {
|
||||||
|
verbose atomic.Bool
|
||||||
|
|
||||||
func (msg *DefaultMsg) IsVerbose() bool { return true }
|
logger *log.Logger
|
||||||
func (msg *DefaultMsg) Verbose(v ...any) {
|
Suspendable
|
||||||
if !msg.inactive.Load() {
|
}
|
||||||
log.Println(v...)
|
|
||||||
|
// NewMsg initialises a downstream [log.Logger] for a new [Msg].
|
||||||
|
// The [log.Logger] should no longer be configured after NewMsg returns.
|
||||||
|
// If downstream is nil, a new logger is initialised in its place.
|
||||||
|
func NewMsg(downstream *log.Logger) Msg {
|
||||||
|
if downstream == nil {
|
||||||
|
downstream = log.New(log.Writer(), "container: ", 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := defaultMsg{logger: downstream}
|
||||||
|
m.Suspendable.Downstream = downstream.Writer()
|
||||||
|
downstream.SetOutput(&m.Suspendable)
|
||||||
|
return &m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *defaultMsg) GetLogger() *log.Logger { return msg.logger }
|
||||||
|
|
||||||
|
func (msg *defaultMsg) IsVerbose() bool { return msg.verbose.Load() }
|
||||||
|
func (msg *defaultMsg) SwapVerbose(verbose bool) bool { return msg.verbose.Swap(verbose) }
|
||||||
|
func (msg *defaultMsg) Verbose(v ...any) {
|
||||||
|
if msg.verbose.Load() {
|
||||||
|
msg.logger.Println(v...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (msg *DefaultMsg) Verbosef(format string, v ...any) {
|
func (msg *defaultMsg) Verbosef(format string, v ...any) {
|
||||||
if !msg.inactive.Load() {
|
if msg.verbose.Load() {
|
||||||
log.Printf(format, v...)
|
msg.logger.Printf(format, v...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *DefaultMsg) Suspend() { msg.inactive.Store(true) }
|
// Resume calls [Suspendable.Resume] and prints a message if buffer was filled
|
||||||
func (msg *DefaultMsg) Resume() bool { return msg.inactive.CompareAndSwap(true, false) }
|
// between calls to [Suspendable.Suspend] and Resume.
|
||||||
func (msg *DefaultMsg) BeforeExit() {}
|
func (msg *defaultMsg) Resume() bool {
|
||||||
|
resumed, dropped, _, err := msg.Suspendable.Resume()
|
||||||
|
if err != nil {
|
||||||
|
// probably going to result in an error as well, so this message is as good as unreachable
|
||||||
|
msg.logger.Printf("cannot dump buffer on resume: %v", err)
|
||||||
|
}
|
||||||
|
if resumed && dropped > 0 {
|
||||||
|
msg.logger.Printf("dropped %d bytes while output is suspended", dropped)
|
||||||
|
}
|
||||||
|
return resumed
|
||||||
|
}
|
||||||
|
|
||||||
// msg is the [Msg] implemented used by all exported [container] functions.
|
// BeforeExit prints a message if called between calls to [Suspendable.Suspend] and Resume.
|
||||||
var msg Msg = new(DefaultMsg)
|
func (msg *defaultMsg) BeforeExit() {
|
||||||
|
if msg.Resume() {
|
||||||
// GetOutput returns the current active [Msg] implementation.
|
msg.logger.Printf("beforeExit reached on suspended output")
|
||||||
func GetOutput() Msg { return msg }
|
|
||||||
|
|
||||||
// SetOutput replaces the current active [Msg] implementation.
|
|
||||||
func SetOutput(v Msg) {
|
|
||||||
if v == nil {
|
|
||||||
msg = new(DefaultMsg)
|
|
||||||
} else {
|
|
||||||
msg = v
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
package container_test
|
package container_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/container/stub"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMessageError(t *testing.T) {
|
func TestMessageError(t *testing.T) {
|
||||||
@@ -39,146 +41,137 @@ func TestMessageError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultMsg(t *testing.T) {
|
func TestDefaultMsg(t *testing.T) {
|
||||||
{
|
// copied from output.go
|
||||||
w := log.Writer()
|
const suspendBufMax = 1 << 24
|
||||||
f := log.Flags()
|
|
||||||
t.Cleanup(func() { log.SetOutput(w); log.SetFlags(f) })
|
|
||||||
}
|
|
||||||
msg := new(container.DefaultMsg)
|
|
||||||
|
|
||||||
t.Run("is verbose", func(t *testing.T) {
|
t.Run("logger", func(t *testing.T) {
|
||||||
if !msg.IsVerbose() {
|
t.Run("nil", func(t *testing.T) {
|
||||||
t.Error("IsVerbose unexpected outcome")
|
got := container.NewMsg(nil).GetLogger()
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("verbose", func(t *testing.T) {
|
if out := got.Writer().(*container.Suspendable).Downstream; out != log.Writer() {
|
||||||
log.SetOutput(panicWriter{})
|
t.Errorf("GetLogger: Downstream = %#v", out)
|
||||||
msg.Suspend()
|
|
||||||
msg.Verbose()
|
|
||||||
msg.Verbosef("\x00")
|
|
||||||
msg.Resume()
|
|
||||||
|
|
||||||
buf := new(strings.Builder)
|
|
||||||
log.SetOutput(buf)
|
|
||||||
log.SetFlags(0)
|
|
||||||
msg.Verbose()
|
|
||||||
msg.Verbosef("\x00")
|
|
||||||
|
|
||||||
want := "\n\x00\n"
|
|
||||||
if buf.String() != want {
|
|
||||||
t.Errorf("Verbose: %q, want %q", buf.String(), want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("inactive", func(t *testing.T) {
|
|
||||||
{
|
|
||||||
inactive := msg.Resume()
|
|
||||||
if inactive {
|
|
||||||
t.Cleanup(func() { msg.Suspend() })
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if msg.Resume() {
|
if prefix := got.Prefix(); prefix != "container: " {
|
||||||
t.Error("Resume unexpected outcome")
|
t.Errorf("GetLogger: prefix = %q", prefix)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
msg.Suspend()
|
t.Run("takeover", func(t *testing.T) {
|
||||||
if !msg.Resume() {
|
l := log.New(io.Discard, "\x00", 0xdeadbeef)
|
||||||
t.Error("Resume unexpected outcome")
|
got := container.NewMsg(l)
|
||||||
}
|
|
||||||
|
if logger := got.GetLogger(); logger != l {
|
||||||
|
t.Errorf("GetLogger: %#v, want %#v", logger, l)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ds := l.Writer().(*container.Suspendable).Downstream; ds != io.Discard {
|
||||||
|
t.Errorf("GetLogger: Downstream = %#v", ds)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// the function is a noop
|
dw := expectWriter{t: t}
|
||||||
t.Run("beforeExit", func(t *testing.T) { msg.BeforeExit() })
|
|
||||||
}
|
|
||||||
|
|
||||||
type panicWriter struct{}
|
steps := []struct {
|
||||||
|
name string
|
||||||
|
pt, next []byte
|
||||||
|
err error
|
||||||
|
|
||||||
func (panicWriter) Write([]byte) (int, error) { panic("unreachable") }
|
f func(t *testing.T, msg container.Msg)
|
||||||
|
}{
|
||||||
|
{"zero verbose", nil, nil, nil, func(t *testing.T, msg container.Msg) {
|
||||||
|
if msg.IsVerbose() {
|
||||||
|
t.Error("IsVerbose unexpected true")
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
|
||||||
func saveRestoreOutput(t *testing.T) {
|
{"swap false", nil, nil, nil, func(t *testing.T, msg container.Msg) {
|
||||||
out := container.GetOutput()
|
if msg.SwapVerbose(false) {
|
||||||
t.Cleanup(func() { container.SetOutput(out) })
|
t.Error("SwapVerbose unexpected true")
|
||||||
}
|
}
|
||||||
|
}},
|
||||||
|
{"write discard", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||||
|
msg.Verbose("\x00")
|
||||||
|
msg.Verbosef("\x00")
|
||||||
|
}},
|
||||||
|
{"verbose false", nil, nil, nil, func(t *testing.T, msg container.Msg) {
|
||||||
|
if msg.IsVerbose() {
|
||||||
|
t.Error("IsVerbose unexpected true")
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
|
||||||
func replaceOutput(t *testing.T) {
|
{"swap true", nil, nil, nil, func(t *testing.T, msg container.Msg) {
|
||||||
saveRestoreOutput(t)
|
if msg.SwapVerbose(true) {
|
||||||
container.SetOutput(&testOutput{t: t})
|
t.Error("SwapVerbose unexpected true")
|
||||||
}
|
}
|
||||||
|
}},
|
||||||
|
{"write verbose", []byte("test: \x00\n"), nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||||
|
msg.Verbose("\x00")
|
||||||
|
}},
|
||||||
|
{"write verbosef", []byte(`test: "\x00"` + "\n"), nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||||
|
msg.Verbosef("%q", "\x00")
|
||||||
|
}},
|
||||||
|
{"verbose true", nil, nil, nil, func(t *testing.T, msg container.Msg) {
|
||||||
|
if !msg.IsVerbose() {
|
||||||
|
t.Error("IsVerbose unexpected false")
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
|
||||||
type testOutput struct {
|
{"resume noop", nil, nil, nil, func(t *testing.T, msg container.Msg) {
|
||||||
t *testing.T
|
if msg.Resume() {
|
||||||
suspended atomic.Bool
|
t.Error("Resume unexpected success")
|
||||||
}
|
}
|
||||||
|
}},
|
||||||
|
{"beforeExit noop", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||||
|
msg.BeforeExit()
|
||||||
|
}},
|
||||||
|
|
||||||
func (out *testOutput) IsVerbose() bool { return testing.Verbose() }
|
{"beforeExit suspend", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||||
|
msg.Suspend()
|
||||||
|
}},
|
||||||
|
{"beforeExit message", []byte("test: beforeExit reached on suspended output\n"), nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||||
|
msg.BeforeExit()
|
||||||
|
}},
|
||||||
|
{"post beforeExit resume noop", nil, nil, nil, func(t *testing.T, msg container.Msg) {
|
||||||
|
if msg.Resume() {
|
||||||
|
t.Error("Resume unexpected success")
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
|
||||||
func (out *testOutput) Verbose(v ...any) {
|
{"suspend", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||||
if !out.IsVerbose() {
|
msg.Suspend()
|
||||||
return
|
}},
|
||||||
}
|
{"suspend write", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||||
out.t.Log(v...)
|
msg.GetLogger().Print("\x00")
|
||||||
}
|
}},
|
||||||
|
{"resume error", []byte("test: \x00\n"), []byte("test: cannot dump buffer on resume: unique error 0 injected by the test suite\n"), stub.UniqueError(0), func(t *testing.T, msg container.Msg) {
|
||||||
|
if !msg.Resume() {
|
||||||
|
t.Error("Resume unexpected failure")
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
|
||||||
func (out *testOutput) Verbosef(format string, v ...any) {
|
{"suspend drop", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||||
if !out.IsVerbose() {
|
msg.Suspend()
|
||||||
return
|
}},
|
||||||
}
|
{"suspend write fill", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||||
out.t.Logf(format, v...)
|
msg.GetLogger().Print(strings.Repeat("\x00", suspendBufMax))
|
||||||
}
|
}},
|
||||||
|
{"resume dropped", append([]byte("test: "), bytes.Repeat([]byte{0}, suspendBufMax-6)...), []byte("test: dropped 7 bytes while output is suspended\n"), nil, func(t *testing.T, msg container.Msg) {
|
||||||
func (out *testOutput) Suspend() {
|
if !msg.Resume() {
|
||||||
if out.suspended.CompareAndSwap(false, true) {
|
t.Error("Resume unexpected failure")
|
||||||
out.Verbose("suspend called")
|
}
|
||||||
return
|
}},
|
||||||
}
|
|
||||||
out.Verbose("suspend called on suspended output")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (out *testOutput) Resume() bool {
|
|
||||||
if out.suspended.CompareAndSwap(true, false) {
|
|
||||||
out.Verbose("resume called")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
out.Verbose("resume called on unsuspended output")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (out *testOutput) BeforeExit() { out.Verbose("beforeExit called") }
|
|
||||||
|
|
||||||
func TestGetSetOutput(t *testing.T) {
|
|
||||||
{
|
|
||||||
out := container.GetOutput()
|
|
||||||
t.Cleanup(func() { container.SetOutput(out) })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("default", func(t *testing.T) {
|
msg := container.NewMsg(log.New(&dw, "test: ", 0))
|
||||||
container.SetOutput(new(stubOutput))
|
for _, step := range steps {
|
||||||
if v, ok := container.GetOutput().(*container.DefaultMsg); ok {
|
// these share the same writer, so cannot be subtests
|
||||||
t.Fatalf("SetOutput: got unexpected output %#v", v)
|
t.Logf("running step %q", step.name)
|
||||||
|
dw.expect, dw.next, dw.err = step.pt, step.next, step.err
|
||||||
|
step.f(t, msg)
|
||||||
|
if dw.expect != nil {
|
||||||
|
t.Errorf("expect: %q", string(dw.expect))
|
||||||
}
|
}
|
||||||
container.SetOutput(nil)
|
}
|
||||||
if _, ok := container.GetOutput().(*container.DefaultMsg); !ok {
|
|
||||||
t.Fatalf("SetOutput: got unexpected output %#v", container.GetOutput())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("stub", func(t *testing.T) {
|
|
||||||
container.SetOutput(new(stubOutput))
|
|
||||||
if _, ok := container.GetOutput().(*stubOutput); !ok {
|
|
||||||
t.Fatalf("SetOutput: got unexpected output %#v", container.GetOutput())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type stubOutput struct {
|
|
||||||
wrapF func(error, ...any) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*stubOutput) IsVerbose() bool { panic("unreachable") }
|
|
||||||
func (*stubOutput) Verbose(...any) { panic("unreachable") }
|
|
||||||
func (*stubOutput) Verbosef(string, ...any) { panic("unreachable") }
|
|
||||||
func (*stubOutput) Suspend() { panic("unreachable") }
|
|
||||||
func (*stubOutput) Resume() bool { panic("unreachable") }
|
|
||||||
func (*stubOutput) BeforeExit() { panic("unreachable") }
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func TestSuspendable(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// shares the same writer
|
// shares the same writer
|
||||||
testCases := []struct {
|
steps := []struct {
|
||||||
name string
|
name string
|
||||||
w, pt []byte
|
w, pt []byte
|
||||||
err error
|
err error
|
||||||
@@ -75,25 +75,25 @@ func TestSuspendable(t *testing.T) {
|
|||||||
var dw expectWriter
|
var dw expectWriter
|
||||||
|
|
||||||
w := container.Suspendable{Downstream: &dw}
|
w := container.Suspendable{Downstream: &dw}
|
||||||
for _, tc := range testCases {
|
for _, step := range steps {
|
||||||
// these share the same writer, so cannot be subtests
|
// these share the same writer, so cannot be subtests
|
||||||
t.Logf("writing step %q", tc.name)
|
t.Logf("writing step %q", step.name)
|
||||||
dw.expect, dw.err = tc.pt, tc.err
|
dw.expect, dw.err = step.pt, step.err
|
||||||
|
|
||||||
var (
|
var (
|
||||||
gotN int
|
gotN int
|
||||||
gotErr error
|
gotErr error
|
||||||
)
|
)
|
||||||
|
|
||||||
wantN := tc.n
|
wantN := step.n
|
||||||
switch wantN {
|
switch wantN {
|
||||||
case nSpecialPtEquiv:
|
case nSpecialPtEquiv:
|
||||||
wantN = len(tc.pt)
|
wantN = len(step.pt)
|
||||||
gotN, gotErr = w.Write(tc.w)
|
gotN, gotErr = w.Write(step.w)
|
||||||
|
|
||||||
case nSpecialWEquiv:
|
case nSpecialWEquiv:
|
||||||
wantN = len(tc.w)
|
wantN = len(step.w)
|
||||||
gotN, gotErr = w.Write(tc.w)
|
gotN, gotErr = w.Write(step.w)
|
||||||
|
|
||||||
case nSpecialSuspend:
|
case nSpecialSuspend:
|
||||||
s := w.IsSuspended()
|
s := w.IsSuspended()
|
||||||
@@ -101,8 +101,8 @@ func TestSuspendable(t *testing.T) {
|
|||||||
t.Fatal("Suspend: unexpected success")
|
t.Fatal("Suspend: unexpected success")
|
||||||
}
|
}
|
||||||
|
|
||||||
wantN = len(tc.w)
|
wantN = len(step.w)
|
||||||
gotN, gotErr = w.Write(tc.w)
|
gotN, gotErr = w.Write(step.w)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if wantN <= nSpecialDump {
|
if wantN <= nSpecialDump {
|
||||||
@@ -118,10 +118,10 @@ func TestSuspendable(t *testing.T) {
|
|||||||
t.Errorf("Resume: dropped = %d, want %d", dropped, wantDropped)
|
t.Errorf("Resume: dropped = %d, want %d", dropped, wantDropped)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantN = len(tc.pt)
|
wantN = len(step.pt)
|
||||||
gotN, gotErr = int(n), err
|
gotN, gotErr = int(n), err
|
||||||
} else {
|
} else {
|
||||||
gotN, gotErr = w.Write(tc.w)
|
gotN, gotErr = w.Write(step.w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,9 +129,13 @@ func TestSuspendable(t *testing.T) {
|
|||||||
t.Errorf("Write: n = %d, want %d", gotN, wantN)
|
t.Errorf("Write: n = %d, want %d", gotN, wantN)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(gotErr, tc.wantErr) {
|
if !reflect.DeepEqual(gotErr, step.wantErr) {
|
||||||
t.Errorf("Write: %v", gotErr)
|
t.Errorf("Write: %v", gotErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dw.expect != nil {
|
||||||
|
t.Errorf("expect: %q", string(dw.expect))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,17 +143,31 @@ func TestSuspendable(t *testing.T) {
|
|||||||
type expectWriter struct {
|
type expectWriter struct {
|
||||||
expect []byte
|
expect []byte
|
||||||
err error
|
err error
|
||||||
|
|
||||||
|
// optional consecutive write
|
||||||
|
next []byte
|
||||||
|
|
||||||
|
// optional, calls Error on failure if not nil
|
||||||
|
t *testing.T
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *expectWriter) Write(p []byte) (n int, err error) {
|
func (w *expectWriter) Write(p []byte) (n int, err error) {
|
||||||
defer func() { w.expect = nil }()
|
defer func() { w.expect = w.next; w.next = nil }()
|
||||||
|
|
||||||
n, err = len(p), w.err
|
n, err = len(p), w.err
|
||||||
if w.expect == nil {
|
if w.expect == nil {
|
||||||
return 0, errors.New("unexpected call to Write: " + strconv.Quote(string(p)))
|
n, err = 0, errors.New("unexpected call to Write: "+strconv.Quote(string(p)))
|
||||||
|
if w.t != nil {
|
||||||
|
w.t.Error(err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if string(p) != string(w.expect) {
|
if string(p) != string(w.expect) {
|
||||||
return 0, errors.New("p = " + strconv.Quote(string(p)) + ", want " + strconv.Quote(string(w.expect)))
|
n, err = 0, errors.New("p = "+strconv.Quote(string(p))+", want "+strconv.Quote(string(w.expect)))
|
||||||
|
if w.t != nil {
|
||||||
|
w.t.Error(err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,84 +9,18 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"hakurei.app/container/fhs"
|
||||||
"hakurei.app/container/vfs"
|
"hakurei.app/container/vfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
/* constants in this file bypass abs check, be extremely careful when changing them! */
|
|
||||||
|
|
||||||
const (
|
|
||||||
// FHSRoot points to the file system root.
|
|
||||||
FHSRoot = "/"
|
|
||||||
// FHSEtc points to the directory for system-specific configuration.
|
|
||||||
FHSEtc = "/etc/"
|
|
||||||
// FHSTmp points to the place for small temporary files.
|
|
||||||
FHSTmp = "/tmp/"
|
|
||||||
|
|
||||||
// FHSRun points to a "tmpfs" file system for system packages to place runtime data, socket files, and similar.
|
|
||||||
FHSRun = "/run/"
|
|
||||||
// FHSRunUser points to a directory containing per-user runtime directories,
|
|
||||||
// each usually individually mounted "tmpfs" instances.
|
|
||||||
FHSRunUser = FHSRun + "user/"
|
|
||||||
|
|
||||||
// FHSUsr points to vendor-supplied operating system resources.
|
|
||||||
FHSUsr = "/usr/"
|
|
||||||
// FHSUsrBin points to binaries and executables for user commands that shall appear in the $PATH search path.
|
|
||||||
FHSUsrBin = FHSUsr + "bin/"
|
|
||||||
|
|
||||||
// FHSVar points to persistent, variable system data. Writable during normal system operation.
|
|
||||||
FHSVar = "/var/"
|
|
||||||
// FHSVarLib points to persistent system data.
|
|
||||||
FHSVarLib = FHSVar + "lib/"
|
|
||||||
// FHSVarEmpty points to a nonstandard directory that is usually empty.
|
|
||||||
FHSVarEmpty = FHSVar + "empty/"
|
|
||||||
|
|
||||||
// FHSDev points to the root directory for device nodes.
|
|
||||||
FHSDev = "/dev/"
|
|
||||||
// FHSProc points to a virtual kernel file system exposing the process list and other functionality.
|
|
||||||
FHSProc = "/proc/"
|
|
||||||
// FHSProcSys points to a hierarchy below /proc/ that exposes a number of kernel tunables.
|
|
||||||
FHSProcSys = FHSProc + "sys/"
|
|
||||||
// FHSSys points to a virtual kernel file system exposing discovered devices and other functionality.
|
|
||||||
FHSSys = "/sys/"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// AbsFHSRoot is [FHSRoot] as [Absolute].
|
|
||||||
AbsFHSRoot = &Absolute{FHSRoot}
|
|
||||||
// AbsFHSEtc is [FHSEtc] as [Absolute].
|
|
||||||
AbsFHSEtc = &Absolute{FHSEtc}
|
|
||||||
// AbsFHSTmp is [FHSTmp] as [Absolute].
|
|
||||||
AbsFHSTmp = &Absolute{FHSTmp}
|
|
||||||
|
|
||||||
// AbsFHSRun is [FHSRun] as [Absolute].
|
|
||||||
AbsFHSRun = &Absolute{FHSRun}
|
|
||||||
// AbsFHSRunUser is [FHSRunUser] as [Absolute].
|
|
||||||
AbsFHSRunUser = &Absolute{FHSRunUser}
|
|
||||||
|
|
||||||
// AbsFHSUsrBin is [FHSUsrBin] as [Absolute].
|
|
||||||
AbsFHSUsrBin = &Absolute{FHSUsrBin}
|
|
||||||
|
|
||||||
// AbsFHSVar is [FHSVar] as [Absolute].
|
|
||||||
AbsFHSVar = &Absolute{FHSVar}
|
|
||||||
// AbsFHSVarLib is [FHSVarLib] as [Absolute].
|
|
||||||
AbsFHSVarLib = &Absolute{FHSVarLib}
|
|
||||||
|
|
||||||
// AbsFHSDev is [FHSDev] as [Absolute].
|
|
||||||
AbsFHSDev = &Absolute{FHSDev}
|
|
||||||
// AbsFHSProc is [FHSProc] as [Absolute].
|
|
||||||
AbsFHSProc = &Absolute{FHSProc}
|
|
||||||
// AbsFHSSys is [FHSSys] as [Absolute].
|
|
||||||
AbsFHSSys = &Absolute{FHSSys}
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Nonexistent is a path that cannot exist.
|
// Nonexistent is a path that cannot exist.
|
||||||
// /proc is chosen because a system with covered /proc is unsupported by this package.
|
// /proc is chosen because a system with covered /proc is unsupported by this package.
|
||||||
Nonexistent = FHSProc + "nonexistent"
|
Nonexistent = fhs.Proc + "nonexistent"
|
||||||
|
|
||||||
hostPath = FHSRoot + hostDir
|
hostPath = fhs.Root + hostDir
|
||||||
hostDir = "host"
|
hostDir = "host"
|
||||||
sysrootPath = FHSRoot + sysrootDir
|
sysrootPath = fhs.Root + sysrootDir
|
||||||
sysrootDir = "sysroot"
|
sysrootDir = "sysroot"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/container/vfs"
|
"hakurei.app/container/vfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,8 +50,8 @@ func TestToHost(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// InternalToHostOvlEscape exports toHost passed to EscapeOverlayDataSegment.
|
// InternalToHostOvlEscape exports toHost passed to [check.EscapeOverlayDataSegment].
|
||||||
func InternalToHostOvlEscape(s string) string { return EscapeOverlayDataSegment(toHost(s)) }
|
func InternalToHostOvlEscape(s string) string { return check.EscapeOverlayDataSegment(toHost(s)) }
|
||||||
|
|
||||||
func TestCreateFile(t *testing.T) {
|
func TestCreateFile(t *testing.T) {
|
||||||
t.Run("nonexistent", func(t *testing.T) {
|
t.Run("nonexistent", func(t *testing.T) {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package seccomp_test
|
package seccomp_test
|
||||||
|
|
||||||
import . "hakurei.app/container/seccomp"
|
import (
|
||||||
|
. "hakurei.app/container/bits"
|
||||||
|
. "hakurei.app/container/seccomp"
|
||||||
|
)
|
||||||
|
|
||||||
var bpfExpected = bpfLookup{
|
var bpfExpected = bpfLookup{
|
||||||
{AllowMultiarch | AllowCAN |
|
{AllowMultiarch | AllowCAN |
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package seccomp_test
|
package seccomp_test
|
||||||
|
|
||||||
import . "hakurei.app/container/seccomp"
|
import (
|
||||||
|
. "hakurei.app/container/bits"
|
||||||
|
. "hakurei.app/container/seccomp"
|
||||||
|
)
|
||||||
|
|
||||||
var bpfExpected = bpfLookup{
|
var bpfExpected = bpfLookup{
|
||||||
{AllowMultiarch | AllowCAN |
|
{AllowMultiarch | AllowCAN |
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ package seccomp_test
|
|||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
|
||||||
|
"hakurei.app/container/bits"
|
||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/seccomp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
bpfPreset = struct {
|
bpfPreset = struct {
|
||||||
seccomp.ExportFlag
|
seccomp.ExportFlag
|
||||||
seccomp.FilterPreset
|
bits.FilterPreset
|
||||||
}
|
}
|
||||||
bpfLookup map[bpfPreset][]byte
|
bpfLookup map[bpfPreset][]byte
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
. "hakurei.app/container/bits"
|
||||||
. "hakurei.app/container/seccomp"
|
. "hakurei.app/container/seccomp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -4,46 +4,33 @@ package seccomp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
. "syscall"
|
. "syscall"
|
||||||
|
|
||||||
|
"hakurei.app/container/bits"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FilterPreset int
|
func Preset(presets bits.FilterPreset, flags ExportFlag) (rules []NativeRule) {
|
||||||
|
|
||||||
const (
|
|
||||||
// PresetExt are project-specific extensions.
|
|
||||||
PresetExt FilterPreset = 1 << iota
|
|
||||||
// PresetDenyNS denies namespace setup syscalls.
|
|
||||||
PresetDenyNS
|
|
||||||
// PresetDenyTTY denies faking input.
|
|
||||||
PresetDenyTTY
|
|
||||||
// PresetDenyDevel denies development-related syscalls.
|
|
||||||
PresetDenyDevel
|
|
||||||
// PresetLinux32 sets PER_LINUX32.
|
|
||||||
PresetLinux32
|
|
||||||
)
|
|
||||||
|
|
||||||
func Preset(presets FilterPreset, flags ExportFlag) (rules []NativeRule) {
|
|
||||||
allowedPersonality := PER_LINUX
|
allowedPersonality := PER_LINUX
|
||||||
if presets&PresetLinux32 != 0 {
|
if presets&bits.PresetLinux32 != 0 {
|
||||||
allowedPersonality = PER_LINUX32
|
allowedPersonality = PER_LINUX32
|
||||||
}
|
}
|
||||||
presetDevelFinal := presetDevel(ScmpDatum(allowedPersonality))
|
presetDevelFinal := presetDevel(ScmpDatum(allowedPersonality))
|
||||||
|
|
||||||
l := len(presetCommon)
|
l := len(presetCommon)
|
||||||
if presets&PresetDenyNS != 0 {
|
if presets&bits.PresetDenyNS != 0 {
|
||||||
l += len(presetNamespace)
|
l += len(presetNamespace)
|
||||||
}
|
}
|
||||||
if presets&PresetDenyTTY != 0 {
|
if presets&bits.PresetDenyTTY != 0 {
|
||||||
l += len(presetTTY)
|
l += len(presetTTY)
|
||||||
}
|
}
|
||||||
if presets&PresetDenyDevel != 0 {
|
if presets&bits.PresetDenyDevel != 0 {
|
||||||
l += len(presetDevelFinal)
|
l += len(presetDevelFinal)
|
||||||
}
|
}
|
||||||
if flags&AllowMultiarch == 0 {
|
if flags&AllowMultiarch == 0 {
|
||||||
l += len(presetEmu)
|
l += len(presetEmu)
|
||||||
}
|
}
|
||||||
if presets&PresetExt != 0 {
|
if presets&bits.PresetExt != 0 {
|
||||||
l += len(presetCommonExt)
|
l += len(presetCommonExt)
|
||||||
if presets&PresetDenyNS != 0 {
|
if presets&bits.PresetDenyNS != 0 {
|
||||||
l += len(presetNamespaceExt)
|
l += len(presetNamespaceExt)
|
||||||
}
|
}
|
||||||
if flags&AllowMultiarch == 0 {
|
if flags&AllowMultiarch == 0 {
|
||||||
@@ -53,21 +40,21 @@ func Preset(presets FilterPreset, flags ExportFlag) (rules []NativeRule) {
|
|||||||
|
|
||||||
rules = make([]NativeRule, 0, l)
|
rules = make([]NativeRule, 0, l)
|
||||||
rules = append(rules, presetCommon...)
|
rules = append(rules, presetCommon...)
|
||||||
if presets&PresetDenyNS != 0 {
|
if presets&bits.PresetDenyNS != 0 {
|
||||||
rules = append(rules, presetNamespace...)
|
rules = append(rules, presetNamespace...)
|
||||||
}
|
}
|
||||||
if presets&PresetDenyTTY != 0 {
|
if presets&bits.PresetDenyTTY != 0 {
|
||||||
rules = append(rules, presetTTY...)
|
rules = append(rules, presetTTY...)
|
||||||
}
|
}
|
||||||
if presets&PresetDenyDevel != 0 {
|
if presets&bits.PresetDenyDevel != 0 {
|
||||||
rules = append(rules, presetDevelFinal...)
|
rules = append(rules, presetDevelFinal...)
|
||||||
}
|
}
|
||||||
if flags&AllowMultiarch == 0 {
|
if flags&AllowMultiarch == 0 {
|
||||||
rules = append(rules, presetEmu...)
|
rules = append(rules, presetEmu...)
|
||||||
}
|
}
|
||||||
if presets&PresetExt != 0 {
|
if presets&bits.PresetExt != 0 {
|
||||||
rules = append(rules, presetCommonExt...)
|
rules = append(rules, presetCommonExt...)
|
||||||
if presets&PresetDenyNS != 0 {
|
if presets&bits.PresetDenyNS != 0 {
|
||||||
rules = append(rules, presetNamespaceExt...)
|
rules = append(rules, presetNamespaceExt...)
|
||||||
}
|
}
|
||||||
if flags&AllowMultiarch == 0 {
|
if flags&AllowMultiarch == 0 {
|
||||||
|
|||||||
@@ -8,10 +8,6 @@ import (
|
|||||||
"hakurei.app/helper/proc"
|
"hakurei.app/helper/proc"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
PresetStrict = PresetExt | PresetDenyNS | PresetDenyTTY | PresetDenyDevel
|
|
||||||
)
|
|
||||||
|
|
||||||
// New returns an inactive Encoder instance.
|
// New returns an inactive Encoder instance.
|
||||||
func New(rules []NativeRule, flags ExportFlag) *Encoder { return &Encoder{newExporter(rules, flags)} }
|
func New(rules []NativeRule, flags ExportFlag) *Encoder { return &Encoder{newExporter(rules, flags)} }
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ package container
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"hakurei.app/container/fhs"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -17,31 +18,33 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
kernelOverflowuidPath = FHSProcSys + "kernel/overflowuid"
|
kernelOverflowuidPath = fhs.ProcSys + "kernel/overflowuid"
|
||||||
kernelOverflowgidPath = FHSProcSys + "kernel/overflowgid"
|
kernelOverflowgidPath = fhs.ProcSys + "kernel/overflowgid"
|
||||||
kernelCapLastCapPath = FHSProcSys + "kernel/cap_last_cap"
|
kernelCapLastCapPath = fhs.ProcSys + "kernel/cap_last_cap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mustReadSysctl() {
|
func mustReadSysctl(msg Msg) {
|
||||||
if v, err := os.ReadFile(kernelOverflowuidPath); err != nil {
|
sysctlOnce.Do(func() {
|
||||||
log.Fatalf("cannot read %q: %v", kernelOverflowuidPath, err)
|
if v, err := os.ReadFile(kernelOverflowuidPath); err != nil {
|
||||||
} else if kernelOverflowuid, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil {
|
msg.GetLogger().Fatalf("cannot read %q: %v", kernelOverflowuidPath, err)
|
||||||
log.Fatalf("cannot interpret %q: %v", kernelOverflowuidPath, err)
|
} else if kernelOverflowuid, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil {
|
||||||
}
|
msg.GetLogger().Fatalf("cannot interpret %q: %v", kernelOverflowuidPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
if v, err := os.ReadFile(kernelOverflowgidPath); err != nil {
|
if v, err := os.ReadFile(kernelOverflowgidPath); err != nil {
|
||||||
log.Fatalf("cannot read %q: %v", kernelOverflowgidPath, err)
|
msg.GetLogger().Fatalf("cannot read %q: %v", kernelOverflowgidPath, err)
|
||||||
} else if kernelOverflowgid, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil {
|
} else if kernelOverflowgid, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil {
|
||||||
log.Fatalf("cannot interpret %q: %v", kernelOverflowgidPath, err)
|
msg.GetLogger().Fatalf("cannot interpret %q: %v", kernelOverflowgidPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if v, err := os.ReadFile(kernelCapLastCapPath); err != nil {
|
if v, err := os.ReadFile(kernelCapLastCapPath); err != nil {
|
||||||
log.Fatalf("cannot read %q: %v", kernelCapLastCapPath, err)
|
msg.GetLogger().Fatalf("cannot read %q: %v", kernelCapLastCapPath, err)
|
||||||
} else if kernelCapLastCap, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil {
|
} else if kernelCapLastCap, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil {
|
||||||
log.Fatalf("cannot interpret %q: %v", kernelCapLastCapPath, err)
|
msg.GetLogger().Fatalf("cannot interpret %q: %v", kernelCapLastCapPath, err)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func OverflowUid() int { sysctlOnce.Do(mustReadSysctl); return kernelOverflowuid }
|
func OverflowUid(msg Msg) int { mustReadSysctl(msg); return kernelOverflowuid }
|
||||||
func OverflowGid() int { sysctlOnce.Do(mustReadSysctl); return kernelOverflowgid }
|
func OverflowGid(msg Msg) int { mustReadSysctl(msg); return kernelOverflowgid }
|
||||||
func LastCap() uintptr { sysctlOnce.Do(mustReadSysctl); return uintptr(kernelCapLastCap) }
|
func LastCap(msg Msg) uintptr { mustReadSysctl(msg); return uintptr(kernelCapLastCap) }
|
||||||
|
|||||||
4
dist/comp/_hakurei
vendored
4
dist/comp/_hakurei
vendored
@@ -54,8 +54,8 @@ __hakurei_instances() {
|
|||||||
{
|
{
|
||||||
local -a _hakurei_cmds
|
local -a _hakurei_cmds
|
||||||
_hakurei_cmds=(
|
_hakurei_cmds=(
|
||||||
"app:Load app from configuration file"
|
"app:Load and start container from configuration file"
|
||||||
"run:Configure and start a permissive default sandbox"
|
"run:Configure and start a permissive container"
|
||||||
"show:Show live or local app configuration"
|
"show:Show live or local app configuration"
|
||||||
"ps:List active instances"
|
"ps:List active instances"
|
||||||
"version:Display version information"
|
"version:Display version information"
|
||||||
|
|||||||
2
dist/install.sh
vendored
2
dist/install.sh
vendored
@@ -4,7 +4,7 @@ cd "$(dirname -- "$0")" || exit 1
|
|||||||
install -vDm0755 "bin/hakurei" "${HAKUREI_INSTALL_PREFIX}/usr/bin/hakurei"
|
install -vDm0755 "bin/hakurei" "${HAKUREI_INSTALL_PREFIX}/usr/bin/hakurei"
|
||||||
install -vDm0755 "bin/hpkg" "${HAKUREI_INSTALL_PREFIX}/usr/bin/hpkg"
|
install -vDm0755 "bin/hpkg" "${HAKUREI_INSTALL_PREFIX}/usr/bin/hpkg"
|
||||||
|
|
||||||
install -vDm6511 "bin/hsu" "${HAKUREI_INSTALL_PREFIX}/usr/bin/hsu"
|
install -vDm4511 "bin/hsu" "${HAKUREI_INSTALL_PREFIX}/usr/bin/hsu"
|
||||||
if [ ! -f "${HAKUREI_INSTALL_PREFIX}/etc/hsurc" ]; then
|
if [ ! -f "${HAKUREI_INSTALL_PREFIX}/etc/hsurc" ]; then
|
||||||
install -vDm0400 "hsurc.default" "${HAKUREI_INSTALL_PREFIX}/etc/hsurc"
|
install -vDm0400 "hsurc.default" "${HAKUREI_INSTALL_PREFIX}/etc/hsurc"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -10,13 +10,15 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/helper/proc"
|
"hakurei.app/helper/proc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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,
|
||||||
pathname *container.Absolute, name string,
|
msg container.Msg,
|
||||||
|
pathname *check.Absolute, name string,
|
||||||
wt io.WriterTo,
|
wt io.WriterTo,
|
||||||
stat bool,
|
stat bool,
|
||||||
argF func(argsFd, statFd int) []string,
|
argF func(argsFd, statFd int) []string,
|
||||||
@@ -26,7 +28,7 @@ func New(
|
|||||||
var args []string
|
var args []string
|
||||||
h := new(helperContainer)
|
h := new(helperContainer)
|
||||||
h.helperFiles, args = newHelperFiles(ctx, wt, stat, argF, extraFiles)
|
h.helperFiles, args = newHelperFiles(ctx, wt, stat, argF, extraFiles)
|
||||||
h.Container = container.NewCommand(ctx, pathname, name, args...)
|
h.Container = container.NewCommand(ctx, msg, pathname, name, args...)
|
||||||
h.WaitDelay = WaitDelay
|
h.WaitDelay = WaitDelay
|
||||||
if cmdF != nil {
|
if cmdF != nil {
|
||||||
cmdF(h.Container)
|
cmdF(h.Container)
|
||||||
|
|||||||
@@ -7,12 +7,14 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
"hakurei.app/container/fhs"
|
||||||
"hakurei.app/helper"
|
"hakurei.app/helper"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestContainer(t *testing.T) {
|
func TestContainer(t *testing.T) {
|
||||||
t.Run("start invalid container", func(t *testing.T) {
|
t.Run("start invalid container", func(t *testing.T) {
|
||||||
h := helper.New(t.Context(), container.MustAbs(container.Nonexistent), "hakurei", argsWt, false, argF, nil, nil)
|
h := helper.New(t.Context(), nil, check.MustAbs(container.Nonexistent), "hakurei", argsWt, false, argF, nil, nil)
|
||||||
|
|
||||||
wantErr := "container: starting an invalid container"
|
wantErr := "container: starting an invalid container"
|
||||||
if err := h.Start(); err == nil || err.Error() != wantErr {
|
if err := h.Start(); err == nil || err.Error() != wantErr {
|
||||||
@@ -22,7 +24,7 @@ func TestContainer(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("valid new helper nil check", func(t *testing.T) {
|
t.Run("valid new helper nil check", func(t *testing.T) {
|
||||||
if got := helper.New(t.Context(), container.MustAbs(container.Nonexistent), "hakurei", argsWt, false, argF, nil, nil); got == nil {
|
if got := helper.New(t.Context(), nil, check.MustAbs(container.Nonexistent), "hakurei", argsWt, false, argF, nil, nil); got == nil {
|
||||||
t.Errorf("New(%q, %q) got nil",
|
t.Errorf("New(%q, %q) got nil",
|
||||||
argsWt, "hakurei")
|
argsWt, "hakurei")
|
||||||
return
|
return
|
||||||
@@ -31,12 +33,12 @@ func TestContainer(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("implementation compliance", func(t *testing.T) {
|
t.Run("implementation compliance", func(t *testing.T) {
|
||||||
testHelper(t, func(ctx context.Context, setOutput func(stdoutP, stderrP *io.Writer), stat bool) helper.Helper {
|
testHelper(t, func(ctx context.Context, setOutput func(stdoutP, stderrP *io.Writer), stat bool) helper.Helper {
|
||||||
return helper.New(ctx, container.MustAbs(os.Args[0]), "helper", argsWt, stat, argF, func(z *container.Container) {
|
return helper.New(ctx, nil, check.MustAbs(os.Args[0]), "helper", argsWt, stat, argF, func(z *container.Container) {
|
||||||
setOutput(&z.Stdout, &z.Stderr)
|
setOutput(&z.Stdout, &z.Stderr)
|
||||||
z.
|
z.
|
||||||
Bind(container.AbsFHSRoot, container.AbsFHSRoot, 0).
|
Bind(fhs.AbsRoot, fhs.AbsRoot, 0).
|
||||||
Proc(container.AbsFHSProc).
|
Proc(fhs.AbsProc).
|
||||||
Dev(container.AbsFHSDev, true)
|
Dev(fhs.AbsDev, true)
|
||||||
}, nil)
|
}, nil)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,11 +6,18 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var FulfillmentTimeout = 2 * time.Second
|
var FulfillmentTimeout = 2 * time.Second
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if testing.Testing() {
|
||||||
|
FulfillmentTimeout *= 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// A File is an extra file with deferred initialisation.
|
// A File is an extra file with deferred initialisation.
|
||||||
type File interface {
|
type File interface {
|
||||||
// Init initialises File state. Init must not be called more than once.
|
// Init initialises File state. Init must not be called more than once.
|
||||||
|
|||||||
@@ -6,12 +6,10 @@ import (
|
|||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/helper"
|
"hakurei.app/helper"
|
||||||
"hakurei.app/internal"
|
|
||||||
"hakurei.app/internal/hlog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
container.TryArgv0(hlog.Output{}, hlog.Prepare, internal.InstallOutput)
|
container.TryArgv0(nil)
|
||||||
helper.InternalHelperStub()
|
helper.InternalHelperStub()
|
||||||
os.Exit(m.Run())
|
os.Exit(m.Run())
|
||||||
}
|
}
|
||||||
|
|||||||
186
hst/config.go
186
hst/config.go
@@ -1,112 +1,178 @@
|
|||||||
package hst
|
package hst
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/container/seccomp"
|
|
||||||
"hakurei.app/system/dbus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const Tmp = "/.hakurei"
|
const Tmp = "/.hakurei"
|
||||||
|
|
||||||
var AbsTmp = container.MustAbs(Tmp)
|
var AbsTmp = check.MustAbs(Tmp)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// WaitDelayDefault is used when WaitDelay has its zero value.
|
||||||
|
WaitDelayDefault = 5 * time.Second
|
||||||
|
// WaitDelayMax is used if WaitDelay exceeds its value.
|
||||||
|
WaitDelayMax = 30 * time.Second
|
||||||
|
|
||||||
|
// IdentityMin is the minimum value of [Config.Identity]. This is enforced by cmd/hsu.
|
||||||
|
IdentityMin = 0
|
||||||
|
// IdentityMax is the maximum value of [Config.Identity]. This is enforced by cmd/hsu.
|
||||||
|
IdentityMax = 9999
|
||||||
|
|
||||||
|
// ShimExitRequest is returned when the priv side process requests shim exit.
|
||||||
|
ShimExitRequest = 254
|
||||||
|
// ShimExitOrphan is returned when the shim is orphaned before priv side delivers a signal.
|
||||||
|
ShimExitOrphan = 3
|
||||||
|
)
|
||||||
|
|
||||||
// Config is used to seal an app implementation.
|
|
||||||
type (
|
type (
|
||||||
|
// Config configures an application container, implemented in internal/app.
|
||||||
Config struct {
|
Config struct {
|
||||||
// reverse-DNS style arbitrary identifier string from config;
|
// Reverse-DNS style configured arbitrary identifier string.
|
||||||
// passed to wayland security-context-v1 as application ID
|
// Passed to wayland security-context-v1 and used as part of defaults in dbus session proxy.
|
||||||
// and used as part of defaults in dbus session proxy
|
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
|
||||||
// absolute path to executable file
|
// System services to make available in the container.
|
||||||
Path *container.Absolute `json:"path,omitempty"`
|
|
||||||
// final args passed to container init
|
|
||||||
Args []string `json:"args"`
|
|
||||||
|
|
||||||
// system services to make available in the container
|
|
||||||
Enablements *Enablements `json:"enablements,omitempty"`
|
Enablements *Enablements `json:"enablements,omitempty"`
|
||||||
|
|
||||||
// session D-Bus proxy configuration;
|
// Session D-Bus proxy configuration.
|
||||||
// nil makes session bus proxy assume built-in defaults
|
// If set to nil, session bus proxy assume built-in defaults.
|
||||||
SessionBus *dbus.Config `json:"session_bus,omitempty"`
|
SessionBus *BusConfig `json:"session_bus,omitempty"`
|
||||||
// system D-Bus proxy configuration;
|
// System D-Bus proxy configuration.
|
||||||
// nil disables system bus proxy
|
// If set to nil, system bus proxy is disabled.
|
||||||
SystemBus *dbus.Config `json:"system_bus,omitempty"`
|
SystemBus *BusConfig `json:"system_bus,omitempty"`
|
||||||
// direct access to wayland socket; when this gets set no attempt is made to attach security-context-v1
|
// Direct access to wayland socket, no attempt is made to attach security-context-v1
|
||||||
// and the bare socket is mounted to the sandbox
|
// and the bare socket is made available to the container.
|
||||||
DirectWayland bool `json:"direct_wayland,omitempty"`
|
DirectWayland bool `json:"direct_wayland,omitempty"`
|
||||||
|
|
||||||
// passwd username in container, defaults to passwd name of target uid or chronos
|
// Extra acl update ops to perform before setuid.
|
||||||
Username string `json:"username,omitempty"`
|
|
||||||
// absolute path to shell
|
|
||||||
Shell *container.Absolute `json:"shell"`
|
|
||||||
// directory to enter and use as home in the container mount namespace
|
|
||||||
Home *container.Absolute `json:"home"`
|
|
||||||
|
|
||||||
// extra acl ops to perform before setuid
|
|
||||||
ExtraPerms []*ExtraPermConfig `json:"extra_perms,omitempty"`
|
ExtraPerms []*ExtraPermConfig `json:"extra_perms,omitempty"`
|
||||||
|
|
||||||
// numerical application id, used for init user namespace credentials
|
// Numerical application id, passed to hsu, used to derive init user namespace credentials.
|
||||||
Identity int `json:"identity"`
|
Identity int `json:"identity"`
|
||||||
// list of supplementary groups inherited by container processes
|
// Init user namespace supplementary groups inherited by all container processes.
|
||||||
Groups []string `json:"groups"`
|
Groups []string `json:"groups"`
|
||||||
|
|
||||||
// abstract container configuration baseline
|
// High level configuration applied to the underlying [container.Params].
|
||||||
Container *ContainerConfig `json:"container"`
|
Container *ContainerConfig `json:"container"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerConfig describes the container configuration baseline to which the app implementation adds upon.
|
// ContainerConfig describes the container configuration to be applied to an underlying [container.Params].
|
||||||
ContainerConfig struct {
|
ContainerConfig struct {
|
||||||
// container hostname
|
// Container UTS namespace hostname.
|
||||||
Hostname string `json:"hostname,omitempty"`
|
Hostname string `json:"hostname,omitempty"`
|
||||||
|
|
||||||
// duration to wait for after interrupting a container's initial process in nanoseconds;
|
// Duration in nanoseconds to wait for after interrupting the initial process.
|
||||||
// a negative value causes the container to be terminated immediately on cancellation
|
// Defaults to [WaitDelayDefault] if less than or equals to zero,
|
||||||
|
// or [WaitDelayMax] if greater than [WaitDelayMax].
|
||||||
WaitDelay time.Duration `json:"wait_delay,omitempty"`
|
WaitDelay time.Duration `json:"wait_delay,omitempty"`
|
||||||
|
|
||||||
// extra seccomp flags
|
// Emit Flatpak-compatible seccomp filter programs.
|
||||||
SeccompFlags seccomp.ExportFlag `json:"seccomp_flags"`
|
|
||||||
// extra seccomp presets
|
|
||||||
SeccompPresets seccomp.FilterPreset `json:"seccomp_presets"`
|
|
||||||
// disable project-specific filter extensions
|
|
||||||
SeccompCompat bool `json:"seccomp_compat,omitempty"`
|
SeccompCompat bool `json:"seccomp_compat,omitempty"`
|
||||||
// allow ptrace and friends
|
// Allow ptrace and friends.
|
||||||
Devel bool `json:"devel,omitempty"`
|
Devel bool `json:"devel,omitempty"`
|
||||||
// allow userns creation in container
|
// Allow userns creation and container setup syscalls.
|
||||||
Userns bool `json:"userns,omitempty"`
|
Userns bool `json:"userns,omitempty"`
|
||||||
// share host net namespace
|
// Share host net namespace.
|
||||||
HostNet bool `json:"host_net,omitempty"`
|
HostNet bool `json:"host_net,omitempty"`
|
||||||
// share abstract unix socket scope
|
// Share abstract unix socket scope.
|
||||||
HostAbstract bool `json:"host_abstract,omitempty"`
|
HostAbstract bool `json:"host_abstract,omitempty"`
|
||||||
// allow dangerous terminal I/O
|
// Allow dangerous terminal I/O (faking input).
|
||||||
Tty bool `json:"tty,omitempty"`
|
Tty bool `json:"tty,omitempty"`
|
||||||
// allow multiarch
|
// Allow multiarch.
|
||||||
Multiarch bool `json:"multiarch,omitempty"`
|
Multiarch bool `json:"multiarch,omitempty"`
|
||||||
|
|
||||||
// initial process environment variables
|
// Initial process environment variables.
|
||||||
Env map[string]string `json:"env"`
|
Env map[string]string `json:"env"`
|
||||||
// map target user uid to privileged user uid in the user namespace;
|
|
||||||
// some programs fail to connect to dbus session running as a different uid,
|
/* Map target user uid to privileged user uid in the container user namespace.
|
||||||
// this option works around it by mapping priv-side caller uid in container
|
|
||||||
|
Some programs fail to connect to dbus session running as a different uid,
|
||||||
|
this option works around it by mapping priv-side caller uid in container. */
|
||||||
MapRealUID bool `json:"map_real_uid"`
|
MapRealUID bool `json:"map_real_uid"`
|
||||||
|
|
||||||
// pass through all devices
|
// Mount /dev/ from the init mount namespace as-is in the container mount namespace.
|
||||||
Device bool `json:"device,omitempty"`
|
Device bool `json:"device,omitempty"`
|
||||||
// container mount points;
|
|
||||||
// if the first element targets /, it is inserted early and excluded from path hiding
|
/* Container mount points.
|
||||||
|
|
||||||
|
If the first element targets /, it is inserted early and excluded from path hiding. */
|
||||||
Filesystem []FilesystemConfigJSON `json:"filesystem"`
|
Filesystem []FilesystemConfigJSON `json:"filesystem"`
|
||||||
|
|
||||||
|
// String used as the username of the emulated user, validated against the default NAME_REGEX from adduser.
|
||||||
|
// Defaults to passwd name of target uid or chronos.
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
// Pathname of shell in the container filesystem to use for the emulated user.
|
||||||
|
Shell *check.Absolute `json:"shell"`
|
||||||
|
// Directory in the container filesystem to enter and use as the home directory of the emulated user.
|
||||||
|
Home *check.Absolute `json:"home"`
|
||||||
|
|
||||||
|
// Pathname to executable file in the container filesystem.
|
||||||
|
Path *check.Absolute `json:"path,omitempty"`
|
||||||
|
// Final args passed to the initial program.
|
||||||
|
Args []string `json:"args"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrConfigNull is returned by [Config.Validate] for an invalid configuration that contains a null value for any
|
||||||
|
// field that must not be null.
|
||||||
|
ErrConfigNull = errors.New("unexpected null in config")
|
||||||
|
|
||||||
|
// ErrIdentityBounds is returned by [Config.Validate] for an out of bounds [Config.Identity] value.
|
||||||
|
ErrIdentityBounds = errors.New("identity out of bounds")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Validate checks [Config] and returns [AppError] if an invalid value is encountered.
|
||||||
|
func (config *Config) Validate() error {
|
||||||
|
if config == nil {
|
||||||
|
return &AppError{Step: "validate configuration", Err: ErrConfigNull,
|
||||||
|
Msg: "invalid configuration"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is checked again in hsu
|
||||||
|
if config.Identity < IdentityMin || config.Identity > IdentityMax {
|
||||||
|
return &AppError{Step: "validate configuration", Err: ErrIdentityBounds,
|
||||||
|
Msg: "identity " + strconv.Itoa(config.Identity) + " out of range"}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := config.SessionBus.CheckInterfaces("session"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := config.SystemBus.CheckInterfaces("system"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Container == nil {
|
||||||
|
return &AppError{Step: "validate configuration", Err: ErrConfigNull,
|
||||||
|
Msg: "configuration missing container state"}
|
||||||
|
}
|
||||||
|
if config.Container.Home == nil {
|
||||||
|
return &AppError{Step: "validate configuration", Err: ErrConfigNull,
|
||||||
|
Msg: "container configuration missing path to home directory"}
|
||||||
|
}
|
||||||
|
if config.Container.Shell == nil {
|
||||||
|
return &AppError{Step: "validate configuration", Err: ErrConfigNull,
|
||||||
|
Msg: "container configuration missing path to shell"}
|
||||||
|
}
|
||||||
|
if config.Container.Path == nil {
|
||||||
|
return &AppError{Step: "validate configuration", Err: ErrConfigNull,
|
||||||
|
Msg: "container configuration missing path to initial program"}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ExtraPermConfig describes an acl update op.
|
// ExtraPermConfig describes an acl update op.
|
||||||
type ExtraPermConfig struct {
|
type ExtraPermConfig struct {
|
||||||
Ensure bool `json:"ensure,omitempty"`
|
Ensure bool `json:"ensure,omitempty"`
|
||||||
Path *container.Absolute `json:"path"`
|
Path *check.Absolute `json:"path"`
|
||||||
Read bool `json:"r,omitempty"`
|
Read bool `json:"r,omitempty"`
|
||||||
Write bool `json:"w,omitempty"`
|
Write bool `json:"w,omitempty"`
|
||||||
Execute bool `json:"x,omitempty"`
|
Execute bool `json:"x,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ExtraPermConfig) String() string {
|
func (e *ExtraPermConfig) String() string {
|
||||||
|
|||||||
@@ -1,12 +1,57 @@
|
|||||||
package hst_test
|
package hst_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container/fhs"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestConfigValidate(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
config *hst.Config
|
||||||
|
wantErr error
|
||||||
|
}{
|
||||||
|
{"nil", nil, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||||
|
Msg: "invalid configuration"}},
|
||||||
|
{"identity lower", &hst.Config{Identity: -1}, &hst.AppError{Step: "validate configuration", Err: hst.ErrIdentityBounds,
|
||||||
|
Msg: "identity -1 out of range"}},
|
||||||
|
{"identity upper", &hst.Config{Identity: 10000}, &hst.AppError{Step: "validate configuration", Err: hst.ErrIdentityBounds,
|
||||||
|
Msg: "identity 10000 out of range"}},
|
||||||
|
{"dbus session", &hst.Config{SessionBus: &hst.BusConfig{See: []string{""}}},
|
||||||
|
&hst.BadInterfaceError{Interface: "", Segment: "session"}},
|
||||||
|
{"dbus system", &hst.Config{SystemBus: &hst.BusConfig{See: []string{""}}},
|
||||||
|
&hst.BadInterfaceError{Interface: "", Segment: "system"}},
|
||||||
|
{"container", &hst.Config{}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||||
|
Msg: "configuration missing container state"}},
|
||||||
|
{"home", &hst.Config{Container: &hst.ContainerConfig{}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||||
|
Msg: "container configuration missing path to home directory"}},
|
||||||
|
{"shell", &hst.Config{Container: &hst.ContainerConfig{
|
||||||
|
Home: fhs.AbsTmp,
|
||||||
|
}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||||
|
Msg: "container configuration missing path to shell"}},
|
||||||
|
{"path", &hst.Config{Container: &hst.ContainerConfig{
|
||||||
|
Home: fhs.AbsTmp,
|
||||||
|
Shell: fhs.AbsTmp,
|
||||||
|
}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||||
|
Msg: "container configuration missing path to initial program"}},
|
||||||
|
{"valid", &hst.Config{Container: &hst.ContainerConfig{
|
||||||
|
Home: fhs.AbsTmp,
|
||||||
|
Shell: fhs.AbsTmp,
|
||||||
|
Path: fhs.AbsTmp,
|
||||||
|
}}, nil},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if err := tc.config.Validate(); !reflect.DeepEqual(err, tc.wantErr) {
|
||||||
|
t.Errorf("Validate: error = %#v, want %#v", err, tc.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestExtraPermConfig(t *testing.T) {
|
func TestExtraPermConfig(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -15,14 +60,14 @@ func TestExtraPermConfig(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{"nil", nil, "<invalid>"},
|
{"nil", nil, "<invalid>"},
|
||||||
{"nil path", &hst.ExtraPermConfig{Path: nil}, "<invalid>"},
|
{"nil path", &hst.ExtraPermConfig{Path: nil}, "<invalid>"},
|
||||||
{"r", &hst.ExtraPermConfig{Path: container.AbsFHSRoot, Read: true}, "r--:/"},
|
{"r", &hst.ExtraPermConfig{Path: fhs.AbsRoot, Read: true}, "r--:/"},
|
||||||
{"r+", &hst.ExtraPermConfig{Ensure: true, Path: container.AbsFHSRoot, 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.AbsTmp, Write: true}, "-w-:/.hakurei"},
|
||||||
{"w+", &hst.ExtraPermConfig{Ensure: true, Path: hst.AbsTmp, Write: true}, "-w-+:/.hakurei"},
|
{"w+", &hst.ExtraPermConfig{Ensure: true, Path: hst.AbsTmp, Write: true}, "-w-+:/.hakurei"},
|
||||||
{"x", &hst.ExtraPermConfig{Path: container.AbsFHSRunUser, Execute: true}, "--x:/run/user/"},
|
{"x", &hst.ExtraPermConfig{Path: fhs.AbsRunUser, Execute: true}, "--x:/run/user/"},
|
||||||
{"x+", &hst.ExtraPermConfig{Ensure: true, Path: container.AbsFHSRunUser, Execute: true}, "--x+:/run/user/"},
|
{"x+", &hst.ExtraPermConfig{Ensure: true, Path: fhs.AbsRunUser, Execute: true}, "--x+:/run/user/"},
|
||||||
{"rwx", &hst.ExtraPermConfig{Path: container.AbsFHSTmp, Read: true, Write: true, Execute: true}, "rwx:/tmp/"},
|
{"rwx", &hst.ExtraPermConfig{Path: fhs.AbsTmp, Read: true, Write: true, Execute: true}, "rwx:/tmp/"},
|
||||||
{"rwx+", &hst.ExtraPermConfig{Ensure: true, Path: container.AbsFHSTmp, Read: true, Write: true, Execute: true}, "rwx+:/tmp/"},
|
{"rwx+", &hst.ExtraPermConfig{Ensure: true, Path: fhs.AbsTmp, Read: true, Write: true, Execute: true}, "rwx+:/tmp/"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
|||||||
109
hst/dbus.go
Normal file
109
hst/dbus.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package hst
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BadInterfaceError is returned when Interface fails an undocumented check in xdg-dbus-proxy,
|
||||||
|
// which would have cause a silent failure.
|
||||||
|
type BadInterfaceError struct {
|
||||||
|
// Interface is the offending interface string.
|
||||||
|
Interface string
|
||||||
|
// Segment is passed through from the [BusConfig.CheckInterfaces] argument.
|
||||||
|
Segment string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *BadInterfaceError) Message() string { return e.Error() }
|
||||||
|
func (e *BadInterfaceError) Error() string {
|
||||||
|
if e == nil {
|
||||||
|
return "<nil>"
|
||||||
|
}
|
||||||
|
return "bad interface string " + strconv.Quote(e.Interface) + " in " + e.Segment + " bus configuration"
|
||||||
|
}
|
||||||
|
|
||||||
|
// BusConfig configures the xdg-dbus-proxy process.
|
||||||
|
type BusConfig struct {
|
||||||
|
// See set 'see' policy for NAME (--see=NAME)
|
||||||
|
See []string `json:"see"`
|
||||||
|
// Talk set 'talk' policy for NAME (--talk=NAME)
|
||||||
|
Talk []string `json:"talk"`
|
||||||
|
// Own set 'own' policy for NAME (--own=NAME)
|
||||||
|
Own []string `json:"own"`
|
||||||
|
|
||||||
|
// Call set RULE for calls on NAME (--call=NAME=RULE)
|
||||||
|
Call map[string]string `json:"call"`
|
||||||
|
// Broadcast set RULE for broadcasts from NAME (--broadcast=NAME=RULE)
|
||||||
|
Broadcast map[string]string `json:"broadcast"`
|
||||||
|
|
||||||
|
// Log turn on logging (--log)
|
||||||
|
Log bool `json:"log,omitempty"`
|
||||||
|
// Filter enable filtering (--filter)
|
||||||
|
Filter bool `json:"filter"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interfaces iterates over all interface strings specified in [BusConfig].
|
||||||
|
func (c *BusConfig) Interfaces(yield func(string) bool) {
|
||||||
|
if c == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, iface := range c.See {
|
||||||
|
if !yield(iface) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, iface := range c.Talk {
|
||||||
|
if !yield(iface) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, iface := range c.Own {
|
||||||
|
if !yield(iface) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for iface := range c.Call {
|
||||||
|
if !yield(iface) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for iface := range c.Broadcast {
|
||||||
|
if !yield(iface) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckInterfaces checks for invalid interface strings based on an undocumented check in xdg-dbus-error,
|
||||||
|
// returning [BadInterfaceError] if one is encountered.
|
||||||
|
func (c *BusConfig) CheckInterfaces(segment string) error {
|
||||||
|
if c == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for iface := range c.Interfaces {
|
||||||
|
/*
|
||||||
|
xdg-dbus-proxy fails without output when this condition is not met:
|
||||||
|
char *dot = strrchr (filter->interface, '.');
|
||||||
|
if (dot != NULL)
|
||||||
|
{
|
||||||
|
*dot = 0;
|
||||||
|
if (strcmp (dot + 1, "*") != 0)
|
||||||
|
filter->member = g_strdup (dot + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
trim ".*" since they are removed before searching for '.':
|
||||||
|
if (g_str_has_suffix (name, ".*"))
|
||||||
|
{
|
||||||
|
name[strlen (name) - 2] = 0;
|
||||||
|
wildcard = TRUE;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
if strings.IndexByte(strings.TrimSuffix(iface, ".*"), '.') == -1 {
|
||||||
|
return &BadInterfaceError{iface, segment}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
109
hst/dbus_test.go
Normal file
109
hst/dbus_test.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package hst_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/hst"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBadInterfaceError(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"nil", (*hst.BadInterfaceError)(nil), "<nil>"},
|
||||||
|
{"session", &hst.BadInterfaceError{Interface: "\x00", Segment: "session"},
|
||||||
|
`bad interface string "\x00" in session bus configuration`},
|
||||||
|
{"system", &hst.BadInterfaceError{Interface: "\x01", Segment: "system"},
|
||||||
|
`bad interface string "\x01" in system bus configuration`},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if gotError := tc.err.Error(); gotError != tc.want {
|
||||||
|
t.Errorf("Error: %s, want %s", gotError, tc.want)
|
||||||
|
}
|
||||||
|
if gotMessage, ok := container.GetErrorMessage(tc.err); !ok {
|
||||||
|
t.Error("GetErrorMessage: ok = false")
|
||||||
|
} else if gotMessage != tc.want {
|
||||||
|
t.Errorf("GetErrorMessage: %s, want %s", gotMessage, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBusConfigInterfaces(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
c *hst.BusConfig
|
||||||
|
cutoff int
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{"nil", nil, 0, nil},
|
||||||
|
{"all", &hst.BusConfig{
|
||||||
|
See: []string{"see"}, Talk: []string{"talk"}, Own: []string{"own"},
|
||||||
|
Call: map[string]string{"call": "unreachable"},
|
||||||
|
Broadcast: map[string]string{"broadcast": "unreachable"},
|
||||||
|
}, 0, []string{"see", "talk", "own", "call", "broadcast"}},
|
||||||
|
|
||||||
|
{"all cutoff", &hst.BusConfig{
|
||||||
|
See: []string{"see"}, Talk: []string{"talk"}, Own: []string{"own"},
|
||||||
|
Call: map[string]string{"call": "unreachable"},
|
||||||
|
Broadcast: map[string]string{"broadcast": "unreachable"},
|
||||||
|
}, 3, []string{"see", "talk", "own"}},
|
||||||
|
|
||||||
|
{"cutoff see", &hst.BusConfig{See: []string{"see"}}, 1, []string{"see"}},
|
||||||
|
{"cutoff talk", &hst.BusConfig{Talk: []string{"talk"}}, 1, []string{"talk"}},
|
||||||
|
{"cutoff own", &hst.BusConfig{Own: []string{"own"}}, 1, []string{"own"}},
|
||||||
|
{"cutoff call", &hst.BusConfig{Call: map[string]string{"call": "unreachable"}}, 1, []string{"call"}},
|
||||||
|
{"cutoff broadcast", &hst.BusConfig{Broadcast: map[string]string{"broadcast": "unreachable"}}, 1, []string{"broadcast"}},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
var got []string
|
||||||
|
if tc.cutoff > 0 {
|
||||||
|
var i int
|
||||||
|
got = make([]string, 0, tc.cutoff)
|
||||||
|
for v := range tc.c.Interfaces {
|
||||||
|
i++
|
||||||
|
got = append(got, v)
|
||||||
|
if i == tc.cutoff {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
got = slices.Collect(tc.c.Interfaces)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !slices.Equal(got, tc.want) {
|
||||||
|
t.Errorf("Interfaces: %q, want %q", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBusConfigCheckInterfaces(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
c *hst.BusConfig
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{"nil", nil, nil},
|
||||||
|
{"zero", &hst.BusConfig{See: []string{""}},
|
||||||
|
&hst.BadInterfaceError{Interface: "", Segment: "zero"}},
|
||||||
|
{"suffix", &hst.BusConfig{See: []string{".*"}},
|
||||||
|
&hst.BadInterfaceError{Interface: ".*", Segment: "suffix"}},
|
||||||
|
{"valid suffix", &hst.BusConfig{See: []string{"..*"}}, nil},
|
||||||
|
{"valid", &hst.BusConfig{See: []string{"."}}, nil},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if err := tc.c.CheckInterfaces(tc.name); !reflect.DeepEqual(err, tc.err) {
|
||||||
|
t.Errorf("CheckInterfaces: error = %#v, want %#v", err, tc.err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,15 +2,56 @@ package hst
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"hakurei.app/system"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewEnablements returns the address of [system.Enablement] as [Enablements].
|
// Enablement represents an optional host service to export to the target user.
|
||||||
func NewEnablements(e system.Enablement) *Enablements { return (*Enablements)(&e) }
|
type Enablement byte
|
||||||
|
|
||||||
// enablementsJSON is the [json] representation of the [system.Enablement] bit field.
|
const (
|
||||||
|
EWayland Enablement = 1 << iota
|
||||||
|
EX11
|
||||||
|
EDBus
|
||||||
|
EPulse
|
||||||
|
|
||||||
|
EM
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e Enablement) String() string {
|
||||||
|
switch e {
|
||||||
|
case 0:
|
||||||
|
return "(no enablements)"
|
||||||
|
case EWayland:
|
||||||
|
return "wayland"
|
||||||
|
case EX11:
|
||||||
|
return "x11"
|
||||||
|
case EDBus:
|
||||||
|
return "dbus"
|
||||||
|
case EPulse:
|
||||||
|
return "pulseaudio"
|
||||||
|
default:
|
||||||
|
buf := new(strings.Builder)
|
||||||
|
buf.Grow(32)
|
||||||
|
|
||||||
|
for i := Enablement(1); i < EM; i <<= 1 {
|
||||||
|
if e&i != 0 {
|
||||||
|
buf.WriteString(", " + i.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf.Len() == 0 {
|
||||||
|
return fmt.Sprintf("e%x", byte(e))
|
||||||
|
}
|
||||||
|
return strings.TrimPrefix(buf.String(), ", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEnablements returns the address of [Enablement] as [Enablements].
|
||||||
|
func NewEnablements(e Enablement) *Enablements { return (*Enablements)(&e) }
|
||||||
|
|
||||||
|
// enablementsJSON is the [json] representation of the [Enablement] bit field.
|
||||||
type enablementsJSON struct {
|
type enablementsJSON struct {
|
||||||
Wayland bool `json:"wayland,omitempty"`
|
Wayland bool `json:"wayland,omitempty"`
|
||||||
X11 bool `json:"x11,omitempty"`
|
X11 bool `json:"x11,omitempty"`
|
||||||
@@ -18,15 +59,15 @@ type enablementsJSON struct {
|
|||||||
Pulse bool `json:"pulse,omitempty"`
|
Pulse bool `json:"pulse,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enablements is the [json] adapter for [system.Enablement].
|
// Enablements is the [json] adapter for [Enablement].
|
||||||
type Enablements system.Enablement
|
type Enablements Enablement
|
||||||
|
|
||||||
// Unwrap returns the underlying [system.Enablement].
|
// Unwrap returns the underlying [Enablement].
|
||||||
func (e *Enablements) Unwrap() system.Enablement {
|
func (e *Enablements) Unwrap() Enablement {
|
||||||
if e == nil {
|
if e == nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return system.Enablement(*e)
|
return Enablement(*e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Enablements) MarshalJSON() ([]byte, error) {
|
func (e *Enablements) MarshalJSON() ([]byte, error) {
|
||||||
@@ -34,10 +75,10 @@ func (e *Enablements) MarshalJSON() ([]byte, error) {
|
|||||||
return nil, syscall.EINVAL
|
return nil, syscall.EINVAL
|
||||||
}
|
}
|
||||||
return json.Marshal(&enablementsJSON{
|
return json.Marshal(&enablementsJSON{
|
||||||
Wayland: system.Enablement(*e)&system.EWayland != 0,
|
Wayland: Enablement(*e)&EWayland != 0,
|
||||||
X11: system.Enablement(*e)&system.EX11 != 0,
|
X11: Enablement(*e)&EX11 != 0,
|
||||||
DBus: system.Enablement(*e)&system.EDBus != 0,
|
DBus: Enablement(*e)&EDBus != 0,
|
||||||
Pulse: system.Enablement(*e)&system.EPulse != 0,
|
Pulse: Enablement(*e)&EPulse != 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,18 +92,18 @@ func (e *Enablements) UnmarshalJSON(data []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var ve system.Enablement
|
var ve Enablement
|
||||||
if v.Wayland {
|
if v.Wayland {
|
||||||
ve |= system.EWayland
|
ve |= EWayland
|
||||||
}
|
}
|
||||||
if v.X11 {
|
if v.X11 {
|
||||||
ve |= system.EX11
|
ve |= EX11
|
||||||
}
|
}
|
||||||
if v.DBus {
|
if v.DBus {
|
||||||
ve |= system.EDBus
|
ve |= EDBus
|
||||||
}
|
}
|
||||||
if v.Pulse {
|
if v.Pulse {
|
||||||
ve |= system.EPulse
|
ve |= EPulse
|
||||||
}
|
}
|
||||||
*e = Enablements(ve)
|
*e = Enablements(ve)
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -7,9 +7,44 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/system"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestEnablementString(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
flags hst.Enablement
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{0, "(no enablements)"},
|
||||||
|
{hst.EWayland, "wayland"},
|
||||||
|
{hst.EX11, "x11"},
|
||||||
|
{hst.EDBus, "dbus"},
|
||||||
|
{hst.EPulse, "pulseaudio"},
|
||||||
|
{hst.EWayland | hst.EX11, "wayland, x11"},
|
||||||
|
{hst.EWayland | hst.EDBus, "wayland, dbus"},
|
||||||
|
{hst.EWayland | hst.EPulse, "wayland, pulseaudio"},
|
||||||
|
{hst.EX11 | hst.EDBus, "x11, dbus"},
|
||||||
|
{hst.EX11 | hst.EPulse, "x11, pulseaudio"},
|
||||||
|
{hst.EDBus | hst.EPulse, "dbus, pulseaudio"},
|
||||||
|
{hst.EWayland | hst.EX11 | hst.EDBus, "wayland, x11, dbus"},
|
||||||
|
{hst.EWayland | hst.EX11 | hst.EPulse, "wayland, x11, pulseaudio"},
|
||||||
|
{hst.EWayland | hst.EDBus | hst.EPulse, "wayland, dbus, pulseaudio"},
|
||||||
|
{hst.EX11 | hst.EDBus | hst.EPulse, "x11, dbus, pulseaudio"},
|
||||||
|
{hst.EWayland | hst.EX11 | hst.EDBus | hst.EPulse, "wayland, x11, dbus, pulseaudio"},
|
||||||
|
|
||||||
|
{1 << 5, "e20"},
|
||||||
|
{1 << 6, "e40"},
|
||||||
|
{1 << 7, "e80"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.want, func(t *testing.T) {
|
||||||
|
if got := tc.flags.String(); got != tc.want {
|
||||||
|
t.Errorf("String: %q, want %q", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestEnablements(t *testing.T) {
|
func TestEnablements(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -19,11 +54,11 @@ func TestEnablements(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{"nil", nil, "null", `{"value":null,"magic":3236757504}`},
|
{"nil", nil, "null", `{"value":null,"magic":3236757504}`},
|
||||||
{"zero", hst.NewEnablements(0), `{}`, `{"value":{},"magic":3236757504}`},
|
{"zero", hst.NewEnablements(0), `{}`, `{"value":{},"magic":3236757504}`},
|
||||||
{"wayland", hst.NewEnablements(system.EWayland), `{"wayland":true}`, `{"value":{"wayland":true},"magic":3236757504}`},
|
{"wayland", hst.NewEnablements(hst.EWayland), `{"wayland":true}`, `{"value":{"wayland":true},"magic":3236757504}`},
|
||||||
{"x11", hst.NewEnablements(system.EX11), `{"x11":true}`, `{"value":{"x11":true},"magic":3236757504}`},
|
{"x11", hst.NewEnablements(hst.EX11), `{"x11":true}`, `{"value":{"x11":true},"magic":3236757504}`},
|
||||||
{"dbus", hst.NewEnablements(system.EDBus), `{"dbus":true}`, `{"value":{"dbus":true},"magic":3236757504}`},
|
{"dbus", hst.NewEnablements(hst.EDBus), `{"dbus":true}`, `{"value":{"dbus":true},"magic":3236757504}`},
|
||||||
{"pulse", hst.NewEnablements(system.EPulse), `{"pulse":true}`, `{"value":{"pulse":true},"magic":3236757504}`},
|
{"pulse", hst.NewEnablements(hst.EPulse), `{"pulse":true}`, `{"value":{"pulse":true},"magic":3236757504}`},
|
||||||
{"all", hst.NewEnablements(system.EWayland | system.EX11 | system.EDBus | system.EPulse), `{"wayland":true,"x11":true,"dbus":true,"pulse":true}`, `{"value":{"wayland":true,"x11":true,"dbus":true,"pulse":true},"magic":3236757504}`},
|
{"all", hst.NewEnablements(hst.EWayland | hst.EX11 | hst.EDBus | hst.EPulse), `{"wayland":true,"x11":true,"dbus":true,"pulse":true}`, `{"value":{"wayland":true,"x11":true,"dbus":true,"pulse":true},"magic":3236757504}`},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
@@ -88,7 +123,7 @@ func TestEnablements(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("val", func(t *testing.T) {
|
t.Run("val", func(t *testing.T) {
|
||||||
if got := hst.NewEnablements(system.EWayland | system.EPulse).Unwrap(); got != system.EWayland|system.EPulse {
|
if got := hst.NewEnablements(hst.EWayland | hst.EPulse).Unwrap(); got != hst.EWayland|hst.EPulse {
|
||||||
t.Errorf("Unwrap: %v", got)
|
t.Errorf("Unwrap: %v", got)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
32
hst/fs.go
32
hst/fs.go
@@ -4,9 +4,10 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container/check"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FilesystemConfig is an abstract representation of a mount point.
|
// FilesystemConfig is an abstract representation of a mount point.
|
||||||
@@ -14,21 +15,44 @@ type FilesystemConfig interface {
|
|||||||
// Valid returns whether the configuration is valid.
|
// Valid returns whether the configuration is valid.
|
||||||
Valid() bool
|
Valid() bool
|
||||||
// Path returns the target path in the container.
|
// Path returns the target path in the container.
|
||||||
Path() *container.Absolute
|
Path() *check.Absolute
|
||||||
// Host returns a slice of all host paths used by this operation.
|
// Host returns a slice of all host paths used by this operation.
|
||||||
Host() []*container.Absolute
|
Host() []*check.Absolute
|
||||||
// Apply appends the [container.Op] implementing this operation.
|
// Apply appends the [container.Op] implementing this operation.
|
||||||
Apply(z *ApplyState)
|
Apply(z *ApplyState)
|
||||||
|
|
||||||
fmt.Stringer
|
fmt.Stringer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The Ops interface enables [FilesystemConfig] to queue container ops without depending on the container package.
|
||||||
|
type Ops interface {
|
||||||
|
// Tmpfs appends an op that mounts tmpfs on a container path.
|
||||||
|
Tmpfs(target *check.Absolute, size int, perm os.FileMode) Ops
|
||||||
|
// Readonly appends an op that mounts read-only tmpfs on a container path.
|
||||||
|
Readonly(target *check.Absolute, perm os.FileMode) Ops
|
||||||
|
|
||||||
|
// Bind appends an op that bind mounts a host path on a container path.
|
||||||
|
Bind(source, target *check.Absolute, flags int) Ops
|
||||||
|
// Overlay appends an op that mounts the overlay pseudo filesystem.
|
||||||
|
Overlay(target, state, work *check.Absolute, layers ...*check.Absolute) Ops
|
||||||
|
// OverlayReadonly appends an op that mounts the overlay pseudo filesystem readonly.
|
||||||
|
OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) Ops
|
||||||
|
|
||||||
|
// Link appends an op that creates a symlink in the container filesystem.
|
||||||
|
Link(target *check.Absolute, linkName string, dereference bool) Ops
|
||||||
|
|
||||||
|
// Root appends an op that expands a directory into a toplevel bind mount mirror on container root.
|
||||||
|
Root(host *check.Absolute, flags int) Ops
|
||||||
|
// Etc appends an op that expands host /etc into a toplevel symlink mirror with /etc semantics.
|
||||||
|
Etc(host *check.Absolute, prefix string) Ops
|
||||||
|
}
|
||||||
|
|
||||||
// ApplyState holds the address of [container.Ops] and any relevant application state.
|
// ApplyState holds the address of [container.Ops] and any relevant application state.
|
||||||
type ApplyState struct {
|
type ApplyState struct {
|
||||||
// AutoEtcPrefix is the prefix for [container.AutoEtcOp].
|
// AutoEtcPrefix is the prefix for [container.AutoEtcOp].
|
||||||
AutoEtcPrefix string
|
AutoEtcPrefix string
|
||||||
|
|
||||||
*container.Ops
|
Ops
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ package hst_test
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -216,11 +218,11 @@ type stubFS struct {
|
|||||||
typeName string
|
typeName string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s stubFS) Valid() bool { return false }
|
func (s stubFS) Valid() bool { return false }
|
||||||
func (s stubFS) Path() *container.Absolute { panic("unreachable") }
|
func (s stubFS) Path() *check.Absolute { panic("unreachable") }
|
||||||
func (s stubFS) Host() []*container.Absolute { panic("unreachable") }
|
func (s stubFS) Host() []*check.Absolute { panic("unreachable") }
|
||||||
func (s stubFS) Apply(*hst.ApplyState) { panic("unreachable") }
|
func (s stubFS) Apply(*hst.ApplyState) { panic("unreachable") }
|
||||||
func (s stubFS) String() string { return "<invalid " + s.typeName + ">" }
|
func (s stubFS) String() string { return "<invalid " + s.typeName + ">" }
|
||||||
|
|
||||||
type sCheck struct {
|
type sCheck struct {
|
||||||
FS hst.FilesystemConfigJSON `json:"fs"`
|
FS hst.FilesystemConfigJSON `json:"fs"`
|
||||||
@@ -232,8 +234,8 @@ type fsTestCase struct {
|
|||||||
fs hst.FilesystemConfig
|
fs hst.FilesystemConfig
|
||||||
valid bool
|
valid bool
|
||||||
ops container.Ops
|
ops container.Ops
|
||||||
path *container.Absolute
|
path *check.Absolute
|
||||||
host []*container.Absolute
|
host []*check.Absolute
|
||||||
str string
|
str string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,7 +250,7 @@ func checkFs(t *testing.T, testCases []fsTestCase) {
|
|||||||
|
|
||||||
t.Run("ops", func(t *testing.T) {
|
t.Run("ops", func(t *testing.T) {
|
||||||
ops := new(container.Ops)
|
ops := new(container.Ops)
|
||||||
tc.fs.Apply(&hst.ApplyState{AutoEtcPrefix: ":3", Ops: ops})
|
tc.fs.Apply(&hst.ApplyState{AutoEtcPrefix: ":3", Ops: opsAdapter{ops}})
|
||||||
if !reflect.DeepEqual(ops, &tc.ops) {
|
if !reflect.DeepEqual(ops, &tc.ops) {
|
||||||
gotString := new(strings.Builder)
|
gotString := new(strings.Builder)
|
||||||
for _, op := range *ops {
|
for _, op := range *ops {
|
||||||
@@ -287,11 +289,45 @@ func checkFs(t *testing.T, testCases []fsTestCase) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func m(pathname string) *container.Absolute { return container.MustAbs(pathname) }
|
type opsAdapter struct{ *container.Ops }
|
||||||
func ms(pathnames ...string) []*container.Absolute {
|
|
||||||
as := make([]*container.Absolute, len(pathnames))
|
func (p opsAdapter) Tmpfs(target *check.Absolute, size int, perm os.FileMode) hst.Ops {
|
||||||
|
return opsAdapter{p.Ops.Tmpfs(target, size, perm)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p opsAdapter) Readonly(target *check.Absolute, perm os.FileMode) hst.Ops {
|
||||||
|
return opsAdapter{p.Ops.Readonly(target, perm)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p opsAdapter) Bind(source, target *check.Absolute, flags int) hst.Ops {
|
||||||
|
return opsAdapter{p.Ops.Bind(source, target, flags)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p opsAdapter) Overlay(target, state, work *check.Absolute, layers ...*check.Absolute) hst.Ops {
|
||||||
|
return opsAdapter{p.Ops.Overlay(target, state, work, layers...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p opsAdapter) OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) hst.Ops {
|
||||||
|
return opsAdapter{p.Ops.OverlayReadonly(target, layers...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p opsAdapter) Link(target *check.Absolute, linkName string, dereference bool) hst.Ops {
|
||||||
|
return opsAdapter{p.Ops.Link(target, linkName, dereference)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p opsAdapter) Root(host *check.Absolute, flags int) hst.Ops {
|
||||||
|
return opsAdapter{p.Ops.Root(host, flags)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p opsAdapter) Etc(host *check.Absolute, prefix string) hst.Ops {
|
||||||
|
return opsAdapter{p.Ops.Etc(host, prefix)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func m(pathname string) *check.Absolute { return check.MustAbs(pathname) }
|
||||||
|
func ms(pathnames ...string) []*check.Absolute {
|
||||||
|
as := make([]*check.Absolute, len(pathnames))
|
||||||
for i, pathname := range pathnames {
|
for i, pathname := range pathnames {
|
||||||
as[i] = container.MustAbs(pathname)
|
as[i] = check.MustAbs(pathname)
|
||||||
}
|
}
|
||||||
return as
|
return as
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import (
|
|||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container/bits"
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
"hakurei.app/container/fhs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { gob.Register(new(FSBind)) }
|
func init() { gob.Register(new(FSBind)) }
|
||||||
@@ -15,9 +17,9 @@ 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
|
// mount point in container, same as Source if empty
|
||||||
Target *container.Absolute `json:"dst,omitempty"`
|
Target *check.Absolute `json:"dst,omitempty"`
|
||||||
// host filesystem path to make available to the container
|
// host filesystem path to make available to the container
|
||||||
Source *container.Absolute `json:"src"`
|
Source *check.Absolute `json:"src"`
|
||||||
// do not mount Target read-only
|
// do not mount Target read-only
|
||||||
Write bool `json:"write,omitempty"`
|
Write bool `json:"write,omitempty"`
|
||||||
// do not disable device files on Target, implies Write
|
// do not disable device files on Target, implies Write
|
||||||
@@ -28,19 +30,19 @@ type FSBind struct {
|
|||||||
Optional bool `json:"optional,omitempty"`
|
Optional bool `json:"optional,omitempty"`
|
||||||
|
|
||||||
// enable special behaviour:
|
// enable special behaviour:
|
||||||
// for autoroot, Target must be set to [container.AbsFHSRoot];
|
// for autoroot, Target must be set to [fhs.AbsRoot];
|
||||||
// for autoetc, Target must be set to [container.AbsFHSEtc]
|
// for autoetc, Target must be set to [fhs.AbsEtc]
|
||||||
Special bool `json:"special,omitempty"`
|
Special bool `json:"special,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAutoRoot returns whether this FSBind has autoroot behaviour enabled.
|
// IsAutoRoot returns whether this FSBind has autoroot behaviour enabled.
|
||||||
func (b *FSBind) IsAutoRoot() bool {
|
func (b *FSBind) IsAutoRoot() bool {
|
||||||
return b.Valid() && b.Special && b.Target.String() == container.FHSRoot
|
return b.Valid() && b.Special && b.Target.String() == fhs.Root
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAutoEtc returns whether this FSBind has autoetc behaviour enabled.
|
// IsAutoEtc returns whether this FSBind has autoetc behaviour enabled.
|
||||||
func (b *FSBind) IsAutoEtc() bool {
|
func (b *FSBind) IsAutoEtc() bool {
|
||||||
return b.Valid() && b.Special && b.Target.String() == container.FHSEtc
|
return b.Valid() && b.Special && b.Target.String() == fhs.Etc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *FSBind) Valid() bool {
|
func (b *FSBind) Valid() bool {
|
||||||
@@ -55,7 +57,7 @@ func (b *FSBind) Valid() bool {
|
|||||||
return false
|
return false
|
||||||
} else {
|
} else {
|
||||||
switch b.Target.String() {
|
switch b.Target.String() {
|
||||||
case container.FHSRoot, container.FHSEtc:
|
case fhs.Root, fhs.Etc:
|
||||||
break
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -66,7 +68,7 @@ func (b *FSBind) Valid() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *FSBind) Path() *container.Absolute {
|
func (b *FSBind) Path() *check.Absolute {
|
||||||
if !b.Valid() {
|
if !b.Valid() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -76,11 +78,11 @@ func (b *FSBind) Path() *container.Absolute {
|
|||||||
return b.Target
|
return b.Target
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *FSBind) Host() []*container.Absolute {
|
func (b *FSBind) Host() []*check.Absolute {
|
||||||
if !b.Valid() {
|
if !b.Valid() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return []*container.Absolute{b.Source}
|
return []*check.Absolute{b.Source}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *FSBind) Apply(z *ApplyState) {
|
func (b *FSBind) Apply(z *ApplyState) {
|
||||||
@@ -94,16 +96,16 @@ func (b *FSBind) Apply(z *ApplyState) {
|
|||||||
}
|
}
|
||||||
var flags int
|
var flags int
|
||||||
if b.Write {
|
if b.Write {
|
||||||
flags |= container.BindWritable
|
flags |= bits.BindWritable
|
||||||
}
|
}
|
||||||
if b.Device {
|
if b.Device {
|
||||||
flags |= container.BindDevice | container.BindWritable
|
flags |= bits.BindDevice | bits.BindWritable
|
||||||
}
|
}
|
||||||
if b.Ensure {
|
if b.Ensure {
|
||||||
flags |= container.BindEnsure
|
flags |= bits.BindEnsure
|
||||||
}
|
}
|
||||||
if b.Optional {
|
if b.Optional {
|
||||||
flags |= container.BindOptional
|
flags |= bits.BindOptional
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
@@ -137,7 +139,7 @@ func (b *FSBind) String() string {
|
|||||||
if flagSym != "" {
|
if flagSym != "" {
|
||||||
prefix += ":" + flagSym
|
prefix += ":" + flagSym
|
||||||
}
|
}
|
||||||
if b.Source.String() != container.FHSRoot {
|
if b.Source.String() != fhs.Root {
|
||||||
return prefix + ":" + b.Source.String()
|
return prefix + ":" + b.Source.String()
|
||||||
}
|
}
|
||||||
return prefix
|
return prefix
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/container/bits"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ func TestFSBind(t *testing.T) {
|
|||||||
}, true, container.Ops{&container.BindMountOp{
|
}, true, container.Ops{&container.BindMountOp{
|
||||||
Source: m("/mnt/dev"),
|
Source: m("/mnt/dev"),
|
||||||
Target: m("/dev"),
|
Target: m("/dev"),
|
||||||
Flags: container.BindWritable | container.BindDevice | container.BindOptional,
|
Flags: bits.BindWritable | bits.BindDevice | bits.BindOptional,
|
||||||
}}, m("/dev"), ms("/mnt/dev"),
|
}}, m("/dev"), ms("/mnt/dev"),
|
||||||
"d+/mnt/dev:/dev"},
|
"d+/mnt/dev:/dev"},
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@ func TestFSBind(t *testing.T) {
|
|||||||
}, true, container.Ops{&container.BindMountOp{
|
}, true, container.Ops{&container.BindMountOp{
|
||||||
Source: m("/mnt/dev"),
|
Source: m("/mnt/dev"),
|
||||||
Target: m("/dev"),
|
Target: m("/dev"),
|
||||||
Flags: container.BindWritable | container.BindDevice | container.BindEnsure,
|
Flags: bits.BindWritable | bits.BindDevice | bits.BindEnsure,
|
||||||
}}, m("/dev"), ms("/mnt/dev"),
|
}}, m("/dev"), ms("/mnt/dev"),
|
||||||
"d-/mnt/dev:/dev"},
|
"d-/mnt/dev:/dev"},
|
||||||
|
|
||||||
@@ -45,7 +46,7 @@ func TestFSBind(t *testing.T) {
|
|||||||
}, true, container.Ops{&container.BindMountOp{
|
}, true, container.Ops{&container.BindMountOp{
|
||||||
Source: m("/mnt/dev"),
|
Source: m("/mnt/dev"),
|
||||||
Target: m("/dev"),
|
Target: m("/dev"),
|
||||||
Flags: container.BindWritable | container.BindDevice,
|
Flags: bits.BindWritable | bits.BindDevice,
|
||||||
}}, m("/dev"), ms("/mnt/dev"),
|
}}, m("/dev"), ms("/mnt/dev"),
|
||||||
"d*/mnt/dev:/dev"},
|
"d*/mnt/dev:/dev"},
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@ func TestFSBind(t *testing.T) {
|
|||||||
}, true, container.Ops{&container.BindMountOp{
|
}, true, container.Ops{&container.BindMountOp{
|
||||||
Source: m("/mnt/tmp"),
|
Source: m("/mnt/tmp"),
|
||||||
Target: m("/tmp"),
|
Target: m("/tmp"),
|
||||||
Flags: container.BindWritable,
|
Flags: bits.BindWritable,
|
||||||
}}, m("/tmp"), ms("/mnt/tmp"),
|
}}, m("/tmp"), ms("/mnt/tmp"),
|
||||||
"w*/mnt/tmp:/tmp"},
|
"w*/mnt/tmp:/tmp"},
|
||||||
|
|
||||||
@@ -95,7 +96,7 @@ func TestFSBind(t *testing.T) {
|
|||||||
Special: true,
|
Special: true,
|
||||||
}, true, container.Ops{&container.AutoRootOp{
|
}, true, container.Ops{&container.AutoRootOp{
|
||||||
Host: m("/"),
|
Host: m("/"),
|
||||||
Flags: container.BindWritable,
|
Flags: bits.BindWritable,
|
||||||
}}, m("/"), ms("/"), "autoroot:w"},
|
}}, m("/"), ms("/"), "autoroot:w"},
|
||||||
|
|
||||||
{"autoroot silly", &hst.FSBind{
|
{"autoroot silly", &hst.FSBind{
|
||||||
@@ -105,7 +106,7 @@ func TestFSBind(t *testing.T) {
|
|||||||
Special: true,
|
Special: true,
|
||||||
}, true, container.Ops{&container.AutoRootOp{
|
}, true, container.Ops{&container.AutoRootOp{
|
||||||
Host: m("/etc"),
|
Host: m("/etc"),
|
||||||
Flags: container.BindWritable,
|
Flags: bits.BindWritable,
|
||||||
}}, m("/"), ms("/etc"), "autoroot:w:/etc"},
|
}}, m("/"), ms("/etc"), "autoroot:w:/etc"},
|
||||||
|
|
||||||
{"autoetc", &hst.FSBind{
|
{"autoetc", &hst.FSBind{
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container/check"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { gob.Register(new(FSEphemeral)) }
|
func init() { gob.Register(new(FSEphemeral)) }
|
||||||
@@ -16,7 +16,7 @@ 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
|
// mount point in container
|
||||||
Target *container.Absolute `json:"dst,omitempty"`
|
Target *check.Absolute `json:"dst,omitempty"`
|
||||||
// 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
|
||||||
@@ -27,14 +27,14 @@ type FSEphemeral struct {
|
|||||||
|
|
||||||
func (e *FSEphemeral) Valid() bool { return e != nil && e.Target != nil }
|
func (e *FSEphemeral) Valid() bool { return e != nil && e.Target != nil }
|
||||||
|
|
||||||
func (e *FSEphemeral) Path() *container.Absolute {
|
func (e *FSEphemeral) Path() *check.Absolute {
|
||||||
if !e.Valid() {
|
if !e.Valid() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return e.Target
|
return e.Target
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *FSEphemeral) Host() []*container.Absolute { return nil }
|
func (e *FSEphemeral) Host() []*check.Absolute { return nil }
|
||||||
|
|
||||||
const fsEphemeralDefaultPerm = os.FileMode(0755)
|
const fsEphemeralDefaultPerm = os.FileMode(0755)
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container/check"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { gob.Register(new(FSLink)) }
|
func init() { gob.Register(new(FSLink)) }
|
||||||
@@ -15,7 +15,7 @@ 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
|
// link path in container
|
||||||
Target *container.Absolute `json:"dst"`
|
Target *check.Absolute `json:"dst"`
|
||||||
// linkname the symlink points to
|
// linkname the symlink points to
|
||||||
Linkname string `json:"linkname"`
|
Linkname string `json:"linkname"`
|
||||||
// whether to dereference linkname before creating the link
|
// whether to dereference linkname before creating the link
|
||||||
@@ -29,14 +29,14 @@ func (l *FSLink) Valid() bool {
|
|||||||
return !l.Dereference || path.IsAbs(l.Linkname)
|
return !l.Dereference || path.IsAbs(l.Linkname)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *FSLink) Path() *container.Absolute {
|
func (l *FSLink) Path() *check.Absolute {
|
||||||
if !l.Valid() {
|
if !l.Valid() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return l.Target
|
return l.Target
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *FSLink) Host() []*container.Absolute { return nil }
|
func (l *FSLink) Host() []*check.Absolute { return nil }
|
||||||
|
|
||||||
func (l *FSLink) Apply(z *ApplyState) {
|
func (l *FSLink) Apply(z *ApplyState) {
|
||||||
if !l.Valid() {
|
if !l.Valid() {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container/check"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { gob.Register(new(FSOverlay)) }
|
func init() { gob.Register(new(FSOverlay)) }
|
||||||
@@ -15,14 +15,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
|
// mount point in container
|
||||||
Target *container.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 []*container.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 *container.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 *container.Absolute `json:"work,omitempty"`
|
Work *check.Absolute `json:"work,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *FSOverlay) Valid() bool {
|
func (o *FSOverlay) Valid() bool {
|
||||||
@@ -43,18 +43,18 @@ func (o *FSOverlay) Valid() bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *FSOverlay) Path() *container.Absolute {
|
func (o *FSOverlay) Path() *check.Absolute {
|
||||||
if !o.Valid() {
|
if !o.Valid() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return o.Target
|
return o.Target
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *FSOverlay) Host() []*container.Absolute {
|
func (o *FSOverlay) Host() []*check.Absolute {
|
||||||
if !o.Valid() {
|
if !o.Valid() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
p := make([]*container.Absolute, 0, 2+len(o.Lower))
|
p := make([]*check.Absolute, 0, 2+len(o.Lower))
|
||||||
if o.Upper != nil && o.Work != nil {
|
if o.Upper != nil && o.Work != nil {
|
||||||
p = append(p, o.Upper, o.Work)
|
p = append(p, o.Upper, o.Work)
|
||||||
}
|
}
|
||||||
@@ -81,18 +81,18 @@ func (o *FSOverlay) String() string {
|
|||||||
|
|
||||||
lower := make([]string, len(o.Lower))
|
lower := make([]string, len(o.Lower))
|
||||||
for i, a := range o.Lower {
|
for i, a := range o.Lower {
|
||||||
lower[i] = container.EscapeOverlayDataSegment(a.String())
|
lower[i] = check.EscapeOverlayDataSegment(a.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.Upper != nil && o.Work != nil {
|
if o.Upper != nil && o.Work != nil {
|
||||||
return "w*" + strings.Join(append([]string{
|
return "w*" + strings.Join(append([]string{
|
||||||
container.EscapeOverlayDataSegment(o.Target.String()),
|
check.EscapeOverlayDataSegment(o.Target.String()),
|
||||||
container.EscapeOverlayDataSegment(o.Upper.String()),
|
check.EscapeOverlayDataSegment(o.Upper.String()),
|
||||||
container.EscapeOverlayDataSegment(o.Work.String())},
|
check.EscapeOverlayDataSegment(o.Work.String())},
|
||||||
lower...), container.SpecialOverlayPath)
|
lower...), check.SpecialOverlayPath)
|
||||||
} else {
|
} else {
|
||||||
return "*" + strings.Join(append([]string{
|
return "*" + strings.Join(append([]string{
|
||||||
container.EscapeOverlayDataSegment(o.Target.String())},
|
check.EscapeOverlayDataSegment(o.Target.String())},
|
||||||
lower...), container.SpecialOverlayPath)
|
lower...), check.SpecialOverlayPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFSOverlay(t *testing.T) {
|
func TestFSOverlay(t *testing.T) {
|
||||||
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: []*container.Absolute{nil}}, false, nil, nil, nil, "<invalid>"},
|
{"nil lower", &hst.FSOverlay{Target: m("/etc"), Lower: []*check.Absolute{nil}}, false, nil, nil, nil, "<invalid>"},
|
||||||
{"zero lower", &hst.FSOverlay{Target: m("/etc"), Upper: m("/"), Work: m("/")}, false, nil, nil, nil, "<invalid>"},
|
{"zero lower", &hst.FSOverlay{Target: m("/etc"), Upper: m("/"), Work: m("/")}, false, nil, nil, nil, "<invalid>"},
|
||||||
{"zero lower ro", &hst.FSOverlay{Target: m("/etc")}, false, nil, nil, nil, "<invalid>"},
|
{"zero lower ro", &hst.FSOverlay{Target: m("/etc")}, false, nil, nil, nil, "<invalid>"},
|
||||||
{"short lower", &hst.FSOverlay{Target: m("/etc"), Lower: ms("/etc")}, false, nil, nil, nil, "<invalid>"},
|
{"short lower", &hst.FSOverlay{Target: m("/etc"), Lower: ms("/etc")}, false, nil, nil, nil, "<invalid>"},
|
||||||
|
|||||||
99
hst/hst.go
99
hst/hst.go
@@ -6,10 +6,8 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/fhs"
|
||||||
"hakurei.app/system"
|
|
||||||
"hakurei.app/system/dbus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// An AppError is returned while starting an app according to [hst.Config].
|
// An AppError is returned while starting an app according to [hst.Config].
|
||||||
@@ -41,13 +39,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 `/tmp`)
|
||||||
TempDir *container.Absolute `json:"temp_dir"`
|
TempDir *check.Absolute `json:"temp_dir"`
|
||||||
// path to shared directory (usually `/tmp/hakurei.%d`, [Info.User])
|
// path to shared directory (usually `/tmp/hakurei.%d`, [Info.User])
|
||||||
SharePath *container.Absolute `json:"share_path"`
|
SharePath *check.Absolute `json:"share_path"`
|
||||||
// XDG_RUNTIME_DIR value (usually `/run/user/%d`, uid)
|
// XDG_RUNTIME_DIR value (usually `/run/user/%d`, uid)
|
||||||
RuntimePath *container.Absolute `json:"runtime_path"`
|
RuntimePath *check.Absolute `json:"runtime_path"`
|
||||||
// application runtime directory (usually `/run/user/%d/hakurei`)
|
// application runtime directory (usually `/run/user/%d/hakurei`)
|
||||||
RunDirPath *container.Absolute `json:"run_dir_path"`
|
RunDirPath *check.Absolute `json:"run_dir_path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Info struct {
|
type Info struct {
|
||||||
@@ -62,18 +60,9 @@ func Template() *Config {
|
|||||||
return &Config{
|
return &Config{
|
||||||
ID: "org.chromium.Chromium",
|
ID: "org.chromium.Chromium",
|
||||||
|
|
||||||
Path: container.AbsFHSRun.Append("current-system/sw/bin/chromium"),
|
Enablements: NewEnablements(EWayland | EDBus | EPulse),
|
||||||
Args: []string{
|
|
||||||
"chromium",
|
|
||||||
"--ignore-gpu-blocklist",
|
|
||||||
"--disable-smooth-scrolling",
|
|
||||||
"--enable-features=UseOzonePlatform",
|
|
||||||
"--ozone-platform=wayland",
|
|
||||||
},
|
|
||||||
|
|
||||||
Enablements: NewEnablements(system.EWayland | system.EDBus | system.EPulse),
|
SessionBus: &BusConfig{
|
||||||
|
|
||||||
SessionBus: &dbus.Config{
|
|
||||||
See: nil,
|
See: nil,
|
||||||
Talk: []string{"org.freedesktop.Notifications", "org.freedesktop.FileManager1", "org.freedesktop.ScreenSaver",
|
Talk: []string{"org.freedesktop.Notifications", "org.freedesktop.FileManager1", "org.freedesktop.ScreenSaver",
|
||||||
"org.freedesktop.secrets", "org.kde.kwalletd5", "org.kde.kwalletd6", "org.gnome.SessionManager"},
|
"org.freedesktop.secrets", "org.kde.kwalletd5", "org.kde.kwalletd6", "org.gnome.SessionManager"},
|
||||||
@@ -84,7 +73,7 @@ func Template() *Config {
|
|||||||
Log: false,
|
Log: false,
|
||||||
Filter: true,
|
Filter: true,
|
||||||
},
|
},
|
||||||
SystemBus: &dbus.Config{
|
SystemBus: &BusConfig{
|
||||||
See: nil,
|
See: nil,
|
||||||
Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"},
|
Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"},
|
||||||
Own: nil,
|
Own: nil,
|
||||||
@@ -95,31 +84,26 @@ func Template() *Config {
|
|||||||
},
|
},
|
||||||
DirectWayland: false,
|
DirectWayland: false,
|
||||||
|
|
||||||
Username: "chronos",
|
|
||||||
Shell: container.AbsFHSRun.Append("current-system/sw/bin/zsh"),
|
|
||||||
Home: container.MustAbs("/data/data/org.chromium.Chromium"),
|
|
||||||
ExtraPerms: []*ExtraPermConfig{
|
ExtraPerms: []*ExtraPermConfig{
|
||||||
{Path: container.AbsFHSVarLib.Append("hakurei/u0"), Ensure: true, Execute: true},
|
{Path: fhs.AbsVarLib.Append("hakurei/u0"), Ensure: true, Execute: true},
|
||||||
{Path: container.AbsFHSVarLib.Append("hakurei/u0/org.chromium.Chromium"), Read: true, Write: true, Execute: true},
|
{Path: fhs.AbsVarLib.Append("hakurei/u0/org.chromium.Chromium"), Read: true, Write: true, Execute: true},
|
||||||
},
|
},
|
||||||
|
|
||||||
Identity: 9,
|
Identity: 9,
|
||||||
Groups: []string{"video", "dialout", "plugdev"},
|
Groups: []string{"video", "dialout", "plugdev"},
|
||||||
|
|
||||||
Container: &ContainerConfig{
|
Container: &ContainerConfig{
|
||||||
Hostname: "localhost",
|
Hostname: "localhost",
|
||||||
Devel: true,
|
Devel: true,
|
||||||
Userns: true,
|
Userns: true,
|
||||||
HostNet: true,
|
HostNet: true,
|
||||||
HostAbstract: true,
|
HostAbstract: true,
|
||||||
Device: true,
|
Device: true,
|
||||||
WaitDelay: -1,
|
WaitDelay: -1,
|
||||||
SeccompFlags: seccomp.AllowMultiarch,
|
SeccompCompat: true,
|
||||||
SeccompPresets: seccomp.PresetExt,
|
Tty: true,
|
||||||
SeccompCompat: true,
|
Multiarch: true,
|
||||||
Tty: true,
|
MapRealUID: true,
|
||||||
Multiarch: true,
|
|
||||||
MapRealUID: true,
|
|
||||||
// example API credentials pulled from Google Chrome
|
// example API credentials pulled from Google Chrome
|
||||||
// DO NOT USE THESE IN A REAL BROWSER
|
// DO NOT USE THESE IN A REAL BROWSER
|
||||||
Env: map[string]string{
|
Env: map[string]string{
|
||||||
@@ -128,21 +112,34 @@ func Template() *Config {
|
|||||||
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT",
|
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT",
|
||||||
},
|
},
|
||||||
Filesystem: []FilesystemConfigJSON{
|
Filesystem: []FilesystemConfigJSON{
|
||||||
{&FSBind{Target: container.AbsFHSRoot, Source: container.AbsFHSVarLib.Append("hakurei/base/org.debian"), Write: true, Special: true}},
|
{&FSBind{Target: fhs.AbsRoot, Source: fhs.AbsVarLib.Append("hakurei/base/org.debian"), Write: true, Special: true}},
|
||||||
{&FSBind{Target: container.AbsFHSEtc, Source: container.AbsFHSEtc, Special: true}},
|
{&FSBind{Target: fhs.AbsEtc, Source: fhs.AbsEtc, Special: true}},
|
||||||
{&FSEphemeral{Target: container.AbsFHSTmp, Write: true, Perm: 0755}},
|
{&FSEphemeral{Target: fhs.AbsTmp, Write: true, Perm: 0755}},
|
||||||
{&FSOverlay{
|
{&FSOverlay{
|
||||||
Target: container.MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*container.Absolute{container.MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
Upper: container.MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: check.MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: container.MustAbs("/mnt-root/nix/.rw-store/work"),
|
Work: check.MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||||
}},
|
}},
|
||||||
{&FSBind{Source: container.MustAbs("/nix/store")}},
|
{&FSBind{Source: check.MustAbs("/nix/store")}},
|
||||||
{&FSLink{Target: container.AbsFHSRun.Append("current-system"), Linkname: "/run/current-system", Dereference: true}},
|
{&FSLink{Target: fhs.AbsRun.Append("current-system"), Linkname: "/run/current-system", Dereference: true}},
|
||||||
{&FSLink{Target: container.AbsFHSRun.Append("opengl-driver"), Linkname: "/run/opengl-driver", Dereference: true}},
|
{&FSLink{Target: fhs.AbsRun.Append("opengl-driver"), Linkname: "/run/opengl-driver", Dereference: true}},
|
||||||
{&FSBind{Source: container.AbsFHSVarLib.Append("hakurei/u0/org.chromium.Chromium"),
|
{&FSBind{Source: fhs.AbsVarLib.Append("hakurei/u0/org.chromium.Chromium"),
|
||||||
Target: container.MustAbs("/data/data/org.chromium.Chromium"), Write: true, Ensure: true}},
|
Target: check.MustAbs("/data/data/org.chromium.Chromium"), Write: true, Ensure: true}},
|
||||||
{&FSBind{Source: container.AbsFHSDev.Append("dri"), Device: true, Optional: true}},
|
{&FSBind{Source: fhs.AbsDev.Append("dri"), Device: true, Optional: true}},
|
||||||
|
},
|
||||||
|
|
||||||
|
Username: "chronos",
|
||||||
|
Shell: fhs.AbsRun.Append("current-system/sw/bin/zsh"),
|
||||||
|
Home: check.MustAbs("/data/data/org.chromium.Chromium"),
|
||||||
|
|
||||||
|
Path: fhs.AbsRun.Append("current-system/sw/bin/chromium"),
|
||||||
|
Args: []string{
|
||||||
|
"chromium",
|
||||||
|
"--ignore-gpu-blocklist",
|
||||||
|
"--disable-smooth-scrolling",
|
||||||
|
"--enable-features=UseOzonePlatform",
|
||||||
|
"--ozone-platform=wayland",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,14 +92,6 @@ func TestAppError(t *testing.T) {
|
|||||||
func TestTemplate(t *testing.T) {
|
func TestTemplate(t *testing.T) {
|
||||||
const want = `{
|
const want = `{
|
||||||
"id": "org.chromium.Chromium",
|
"id": "org.chromium.Chromium",
|
||||||
"path": "/run/current-system/sw/bin/chromium",
|
|
||||||
"args": [
|
|
||||||
"chromium",
|
|
||||||
"--ignore-gpu-blocklist",
|
|
||||||
"--disable-smooth-scrolling",
|
|
||||||
"--enable-features=UseOzonePlatform",
|
|
||||||
"--ozone-platform=wayland"
|
|
||||||
],
|
|
||||||
"enablements": {
|
"enablements": {
|
||||||
"wayland": true,
|
"wayland": true,
|
||||||
"dbus": true,
|
"dbus": true,
|
||||||
@@ -141,9 +133,6 @@ func TestTemplate(t *testing.T) {
|
|||||||
"broadcast": null,
|
"broadcast": null,
|
||||||
"filter": true
|
"filter": true
|
||||||
},
|
},
|
||||||
"username": "chronos",
|
|
||||||
"shell": "/run/current-system/sw/bin/zsh",
|
|
||||||
"home": "/data/data/org.chromium.Chromium",
|
|
||||||
"extra_perms": [
|
"extra_perms": [
|
||||||
{
|
{
|
||||||
"ensure": true,
|
"ensure": true,
|
||||||
@@ -166,8 +155,6 @@ func TestTemplate(t *testing.T) {
|
|||||||
"container": {
|
"container": {
|
||||||
"hostname": "localhost",
|
"hostname": "localhost",
|
||||||
"wait_delay": -1,
|
"wait_delay": -1,
|
||||||
"seccomp_flags": 1,
|
|
||||||
"seccomp_presets": 1,
|
|
||||||
"seccomp_compat": true,
|
"seccomp_compat": true,
|
||||||
"devel": true,
|
"devel": true,
|
||||||
"userns": true,
|
"userns": true,
|
||||||
@@ -240,6 +227,17 @@ func TestTemplate(t *testing.T) {
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"username": "chronos",
|
||||||
|
"shell": "/run/current-system/sw/bin/zsh",
|
||||||
|
"home": "/data/data/org.chromium.Chromium",
|
||||||
|
"path": "/run/current-system/sw/bin/chromium",
|
||||||
|
"args": [
|
||||||
|
"chromium",
|
||||||
|
"--ignore-gpu-blocklist",
|
||||||
|
"--disable-smooth-scrolling",
|
||||||
|
"--enable-features=UseOzonePlatform",
|
||||||
|
"--ozone-platform=wayland"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|||||||
@@ -6,23 +6,24 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/app/state"
|
"hakurei.app/internal/app/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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, config *hst.Config) {
|
func Main(ctx context.Context, msg container.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)
|
||||||
}
|
}
|
||||||
|
|
||||||
seal := outcome{id: &stringPair[state.ID]{id, id.String()}, syscallDispatcher: direct{}}
|
seal := outcome{syscallDispatcher: direct{}}
|
||||||
if err := seal.finalise(ctx, config); err != nil {
|
if err := seal.finalise(ctx, msg, &id, config); err != nil {
|
||||||
printMessageError("cannot seal app:", err)
|
printMessageError("cannot seal app:", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
seal.main()
|
seal.main(msg)
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,199 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/fs"
|
|
||||||
"log"
|
|
||||||
"os/exec"
|
|
||||||
"os/user"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stubNixOS struct {
|
|
||||||
lookPathErr map[string]error
|
|
||||||
usernameErr map[string]error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *stubNixOS) new(func(k syscallDispatcher)) { panic("not implemented") }
|
|
||||||
|
|
||||||
func (k *stubNixOS) getuid() int { return 1971 }
|
|
||||||
func (k *stubNixOS) getgid() int { return 100 }
|
|
||||||
|
|
||||||
func (k *stubNixOS) lookupEnv(key string) (string, bool) {
|
|
||||||
switch key {
|
|
||||||
case "SHELL":
|
|
||||||
return "/run/current-system/sw/bin/zsh", true
|
|
||||||
case "TERM":
|
|
||||||
return "xterm-256color", true
|
|
||||||
case "WAYLAND_DISPLAY":
|
|
||||||
return "wayland-0", true
|
|
||||||
case "PULSE_COOKIE":
|
|
||||||
return "", false
|
|
||||||
case "HOME":
|
|
||||||
return "/home/ophestra", true
|
|
||||||
case "XDG_RUNTIME_DIR":
|
|
||||||
return "/run/user/1971", true
|
|
||||||
case "XDG_CONFIG_HOME":
|
|
||||||
return "/home/ophestra/xdg/config", true
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("attempted to access unexpected environment variable %q", key))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *stubNixOS) stat(name string) (fs.FileInfo, error) {
|
|
||||||
switch name {
|
|
||||||
case "/var/run/nscd":
|
|
||||||
return nil, nil
|
|
||||||
case "/run/user/1971/pulse":
|
|
||||||
return nil, nil
|
|
||||||
case "/run/user/1971/pulse/native":
|
|
||||||
return stubFileInfoMode(0666), nil
|
|
||||||
case "/home/ophestra/.pulse-cookie":
|
|
||||||
return stubFileInfoIsDir(true), nil
|
|
||||||
case "/home/ophestra/xdg/config/pulse/cookie":
|
|
||||||
return stubFileInfoIsDir(false), nil
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("attempted to stat unexpected path %q", name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *stubNixOS) readdir(name string) ([]fs.DirEntry, error) {
|
|
||||||
switch name {
|
|
||||||
case "/":
|
|
||||||
return stubDirEntries("bin", "boot", "dev", "etc", "home", "lib",
|
|
||||||
"lib64", "nix", "proc", "root", "run", "srv", "sys", "tmp", "usr", "var")
|
|
||||||
|
|
||||||
case "/run":
|
|
||||||
return stubDirEntries("agetty.reload", "binfmt", "booted-system",
|
|
||||||
"credentials", "cryptsetup", "current-system", "dbus", "host", "keys",
|
|
||||||
"libvirt", "libvirtd.pid", "lock", "log", "lvm", "mount", "NetworkManager",
|
|
||||||
"nginx", "nixos", "nscd", "opengl-driver", "pppd", "resolvconf", "sddm",
|
|
||||||
"store", "syncoid", "system", "systemd", "tmpfiles.d", "udev", "udisks2",
|
|
||||||
"user", "utmp", "virtlogd.pid", "wrappers", "zed.pid", "zed.state")
|
|
||||||
|
|
||||||
case "/etc":
|
|
||||||
return stubDirEntries("alsa", "bashrc", "binfmt.d", "dbus-1", "default",
|
|
||||||
"ethertypes", "fonts", "fstab", "fuse.conf", "group", "host.conf", "hostid",
|
|
||||||
"hostname", "hostname.CHECKSUM", "hosts", "inputrc", "ipsec.d", "issue", "kbd",
|
|
||||||
"libblockdev", "locale.conf", "localtime", "login.defs", "lsb-release", "lvm",
|
|
||||||
"machine-id", "man_db.conf", "modprobe.d", "modules-load.d", "mtab", "nanorc",
|
|
||||||
"netgroup", "NetworkManager", "nix", "nixos", "NIXOS", "nscd.conf", "nsswitch.conf",
|
|
||||||
"opensnitchd", "os-release", "pam", "pam.d", "passwd", "pipewire", "pki", "polkit-1",
|
|
||||||
"profile", "protocols", "qemu", "resolv.conf", "resolvconf.conf", "rpc", "samba",
|
|
||||||
"sddm.conf", "secureboot", "services", "set-environment", "shadow", "shells", "ssh",
|
|
||||||
"ssl", "static", "subgid", "subuid", "sudoers", "sysctl.d", "systemd", "terminfo",
|
|
||||||
"tmpfiles.d", "udev", "udisks2", "UPower", "vconsole.conf", "X11", "zfs", "zinputrc",
|
|
||||||
"zoneinfo", "zprofile", "zshenv", "zshrc")
|
|
||||||
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("attempted to read unexpected directory %q", name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *stubNixOS) tempdir() string { return "/tmp/" }
|
|
||||||
|
|
||||||
func (k *stubNixOS) evalSymlinks(path string) (string, error) {
|
|
||||||
switch path {
|
|
||||||
case "/run/user/1971":
|
|
||||||
return "/run/user/1971", nil
|
|
||||||
case "/tmp/hakurei.0":
|
|
||||||
return "/tmp/hakurei.0", nil
|
|
||||||
case "/run/dbus":
|
|
||||||
return "/run/dbus", nil
|
|
||||||
case "/dev/kvm":
|
|
||||||
return "/dev/kvm", nil
|
|
||||||
case "/etc/":
|
|
||||||
return "/etc/", nil
|
|
||||||
case "/bin":
|
|
||||||
return "/bin", nil
|
|
||||||
case "/boot":
|
|
||||||
return "/boot", nil
|
|
||||||
case "/home":
|
|
||||||
return "/home", nil
|
|
||||||
case "/lib":
|
|
||||||
return "/lib", nil
|
|
||||||
case "/lib64":
|
|
||||||
return "/lib64", nil
|
|
||||||
case "/nix":
|
|
||||||
return "/nix", nil
|
|
||||||
case "/root":
|
|
||||||
return "/root", nil
|
|
||||||
case "/run":
|
|
||||||
return "/run", nil
|
|
||||||
case "/srv":
|
|
||||||
return "/srv", nil
|
|
||||||
case "/sys":
|
|
||||||
return "/sys", nil
|
|
||||||
case "/usr":
|
|
||||||
return "/usr", nil
|
|
||||||
case "/var":
|
|
||||||
return "/var", nil
|
|
||||||
case "/dev/dri":
|
|
||||||
return "/dev/dri", nil
|
|
||||||
case "/usr/bin/":
|
|
||||||
return "/usr/bin/", nil
|
|
||||||
case "/nix/store":
|
|
||||||
return "/nix/store", nil
|
|
||||||
case "/run/current-system":
|
|
||||||
return "/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-nixos-system-satori-25.05.99999999.aaaaaaa", nil
|
|
||||||
case "/sys/block":
|
|
||||||
return "/sys/block", nil
|
|
||||||
case "/sys/bus":
|
|
||||||
return "/sys/bus", nil
|
|
||||||
case "/sys/class":
|
|
||||||
return "/sys/class", nil
|
|
||||||
case "/sys/dev":
|
|
||||||
return "/sys/dev", nil
|
|
||||||
case "/sys/devices":
|
|
||||||
return "/sys/devices", nil
|
|
||||||
case "/run/opengl-driver":
|
|
||||||
return "/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-graphics-drivers", nil
|
|
||||||
case "/var/lib/persist/module/hakurei/0/1":
|
|
||||||
return "/var/lib/persist/module/hakurei/0/1", nil
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("attempted to evaluate unexpected path %q", path))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *stubNixOS) lookPath(file string) (string, error) {
|
|
||||||
if k.lookPathErr != nil {
|
|
||||||
if err, ok := k.lookPathErr[file]; ok {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch file {
|
|
||||||
case "zsh":
|
|
||||||
return "/run/current-system/sw/bin/zsh", nil
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("attempted to look up unexpected executable %q", file))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *stubNixOS) lookupGroupId(name string) (string, error) {
|
|
||||||
switch name {
|
|
||||||
case "video":
|
|
||||||
return "26", nil
|
|
||||||
default:
|
|
||||||
return "", user.UnknownGroupError(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *stubNixOS) cmdOutput(cmd *exec.Cmd) ([]byte, error) {
|
|
||||||
switch cmd.Path {
|
|
||||||
case "/proc/nonexistent/hsu":
|
|
||||||
return []byte{'0'}, nil
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("unexpected cmd %#v", cmd))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *stubNixOS) overflowUid() int { return 65534 }
|
|
||||||
func (k *stubNixOS) overflowGid() int { return 65534 }
|
|
||||||
|
|
||||||
func (k *stubNixOS) mustHsuPath() string { return "/proc/nonexistent/hsu" }
|
|
||||||
|
|
||||||
func (k *stubNixOS) fatalf(format string, v ...any) { panic(fmt.Sprintf(format, v...)) }
|
|
||||||
|
|
||||||
func (k *stubNixOS) isVerbose() bool { return true }
|
|
||||||
func (k *stubNixOS) verbose(v ...any) { log.Print(v...) }
|
|
||||||
func (k *stubNixOS) verbosef(format string, v ...any) { log.Printf(format, v...) }
|
|
||||||
@@ -1,25 +1,36 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"bytes"
|
||||||
|
"encoding/gob"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"log"
|
||||||
|
"maps"
|
||||||
|
"os/exec"
|
||||||
|
"os/user"
|
||||||
"reflect"
|
"reflect"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/bits"
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
"hakurei.app/container/fhs"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/app/state"
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/system"
|
"hakurei.app/system"
|
||||||
"hakurei.app/system/acl"
|
"hakurei.app/system/acl"
|
||||||
"hakurei.app/system/dbus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestApp(t *testing.T) {
|
func TestApp(t *testing.T) {
|
||||||
|
msg := container.NewMsg(nil)
|
||||||
|
msg.SwapVerbose(testing.Verbose())
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
k syscallDispatcher
|
k syscallDispatcher
|
||||||
@@ -30,19 +41,47 @@ func TestApp(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"nixos permissive defaults no enablements", new(stubNixOS),
|
"nixos permissive defaults no enablements", new(stubNixOS),
|
||||||
&hst.Config{Username: "chronos", Home: m("/home/chronos")},
|
&hst.Config{Container: &hst.ContainerConfig{
|
||||||
|
Userns: true, HostNet: true, HostAbstract: true, Tty: true,
|
||||||
|
|
||||||
|
Filesystem: []hst.FilesystemConfigJSON{
|
||||||
|
{FilesystemConfig: &hst.FSBind{
|
||||||
|
Target: fhs.AbsRoot,
|
||||||
|
Source: fhs.AbsRoot,
|
||||||
|
Write: true,
|
||||||
|
Special: true,
|
||||||
|
}},
|
||||||
|
{FilesystemConfig: &hst.FSBind{
|
||||||
|
Source: fhs.AbsDev.Append("kvm"),
|
||||||
|
Device: true,
|
||||||
|
Optional: true,
|
||||||
|
}},
|
||||||
|
{FilesystemConfig: &hst.FSBind{
|
||||||
|
Target: fhs.AbsEtc,
|
||||||
|
Source: fhs.AbsEtc,
|
||||||
|
Special: true,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
|
||||||
|
Username: "chronos",
|
||||||
|
Shell: m("/run/current-system/sw/bin/zsh"),
|
||||||
|
Home: m("/home/chronos"),
|
||||||
|
|
||||||
|
Path: m("/run/current-system/sw/bin/zsh"),
|
||||||
|
Args: []string{"/run/current-system/sw/bin/zsh"},
|
||||||
|
}},
|
||||||
state.ID{
|
state.ID{
|
||||||
0x4a, 0x45, 0x0b, 0x65,
|
0x4a, 0x45, 0x0b, 0x65,
|
||||||
0x96, 0xd7, 0xbc, 0x15,
|
0x96, 0xd7, 0xbc, 0x15,
|
||||||
0xbd, 0x01, 0x78, 0x0e,
|
0xbd, 0x01, 0x78, 0x0e,
|
||||||
0xb9, 0xa6, 0x07, 0xac,
|
0xb9, 0xa6, 0x07, 0xac,
|
||||||
},
|
},
|
||||||
system.New(context.TODO(), 1000000).
|
system.New(t.Context(), msg, 1000000).
|
||||||
Ensure("/tmp/hakurei.0", 0711).
|
Ensure(m("/tmp/hakurei.0"), 0711).
|
||||||
Ensure("/tmp/hakurei.0/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime", acl.Execute).
|
Ensure(m("/tmp/hakurei.0/runtime"), 0700).UpdatePermType(system.User, m("/tmp/hakurei.0/runtime"), acl.Execute).
|
||||||
Ensure("/tmp/hakurei.0/runtime/0", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime/0", acl.Read, acl.Write, acl.Execute).
|
Ensure(m("/tmp/hakurei.0/runtime/0"), 0700).UpdatePermType(system.User, m("/tmp/hakurei.0/runtime/0"), acl.Read, acl.Write, acl.Execute).
|
||||||
Ensure("/tmp/hakurei.0/tmpdir", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir", acl.Execute).
|
Ensure(m("/tmp/hakurei.0/tmpdir"), 0700).UpdatePermType(system.User, m("/tmp/hakurei.0/tmpdir"), acl.Execute).
|
||||||
Ensure("/tmp/hakurei.0/tmpdir/0", 01700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir/0", acl.Read, acl.Write, acl.Execute),
|
Ensure(m("/tmp/hakurei.0/tmpdir/0"), 01700).UpdatePermType(system.User, m("/tmp/hakurei.0/tmpdir/0"), acl.Read, acl.Write, acl.Execute),
|
||||||
&container.Params{
|
&container.Params{
|
||||||
Dir: m("/home/chronos"),
|
Dir: m("/home/chronos"),
|
||||||
Path: m("/run/current-system/sw/bin/zsh"),
|
Path: m("/run/current-system/sw/bin/zsh"),
|
||||||
@@ -57,24 +96,24 @@ func TestApp(t *testing.T) {
|
|||||||
"XDG_SESSION_TYPE=tty",
|
"XDG_SESSION_TYPE=tty",
|
||||||
},
|
},
|
||||||
Ops: new(container.Ops).
|
Ops: new(container.Ops).
|
||||||
Root(m("/"), container.BindWritable).
|
Root(m("/"), bits.BindWritable).
|
||||||
Proc(m("/proc/")).
|
Proc(m("/proc/")).
|
||||||
Tmpfs(hst.AbsTmp, 4096, 0755).
|
Tmpfs(hst.AbsTmp, 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"), container.BindWritable|container.BindDevice|container.BindOptional).
|
Bind(m("/dev/kvm"), m("/dev/kvm"), bits.BindWritable|bits.BindDevice|bits.BindOptional).
|
||||||
Readonly(m("/var/run/nscd"), 0755).
|
|
||||||
Etc(m("/etc/"), "4a450b6596d7bc15bd01780eb9a607ac").
|
Etc(m("/etc/"), "4a450b6596d7bc15bd01780eb9a607ac").
|
||||||
Tmpfs(m("/run/user/1971"), 8192, 0755).
|
Tmpfs(m("/run/user/1971"), 8192, 0755).
|
||||||
|
Tmpfs(m("/run/nscd"), 8192, 0755).
|
||||||
Tmpfs(m("/run/dbus"), 8192, 0755).
|
Tmpfs(m("/run/dbus"), 8192, 0755).
|
||||||
Remount(m("/dev/"), syscall.MS_RDONLY).
|
Remount(m("/dev/"), syscall.MS_RDONLY).
|
||||||
Tmpfs(m("/run/user/"), 4096, 0755).
|
Tmpfs(m("/run/user/"), 4096, 0755).
|
||||||
Bind(m("/tmp/hakurei.0/runtime/0"), m("/run/user/65534"), container.BindWritable).
|
Bind(m("/tmp/hakurei.0/runtime/0"), m("/run/user/65534"), bits.BindWritable).
|
||||||
Bind(m("/tmp/hakurei.0/tmpdir/0"), m("/tmp/"), container.BindWritable).
|
Bind(m("/tmp/hakurei.0/tmpdir/0"), m("/tmp/"), bits.BindWritable).
|
||||||
Place(m("/etc/passwd"), []byte("chronos:x:65534:65534:Hakurei:/home/chronos:/run/current-system/sw/bin/zsh\n")).
|
Place(m("/etc/passwd"), []byte("chronos:x:65534:65534:Hakurei:/home/chronos:/run/current-system/sw/bin/zsh\n")).
|
||||||
Place(m("/etc/group"), []byte("hakurei:x:65534:\n")).
|
Place(m("/etc/group"), []byte("hakurei:x:65534:\n")).
|
||||||
Remount(m("/"), syscall.MS_RDONLY),
|
Remount(m("/"), syscall.MS_RDONLY),
|
||||||
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyDevel,
|
SeccompPresets: bits.PresetExt | bits.PresetDenyDevel,
|
||||||
HostNet: true,
|
HostNet: true,
|
||||||
HostAbstract: true,
|
HostAbstract: true,
|
||||||
RetainSession: true,
|
RetainSession: true,
|
||||||
@@ -85,12 +124,9 @@ func TestApp(t *testing.T) {
|
|||||||
"nixos permissive defaults chromium", new(stubNixOS),
|
"nixos permissive defaults chromium", new(stubNixOS),
|
||||||
&hst.Config{
|
&hst.Config{
|
||||||
ID: "org.chromium.Chromium",
|
ID: "org.chromium.Chromium",
|
||||||
Args: []string{"zsh", "-c", "exec chromium "},
|
|
||||||
Identity: 9,
|
Identity: 9,
|
||||||
Groups: []string{"video"},
|
Groups: []string{"video"},
|
||||||
Username: "chronos",
|
SessionBus: &hst.BusConfig{
|
||||||
Home: m("/home/chronos"),
|
|
||||||
SessionBus: &dbus.Config{
|
|
||||||
Talk: []string{
|
Talk: []string{
|
||||||
"org.freedesktop.Notifications",
|
"org.freedesktop.Notifications",
|
||||||
"org.freedesktop.FileManager1",
|
"org.freedesktop.FileManager1",
|
||||||
@@ -113,7 +149,7 @@ func TestApp(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Filter: true,
|
Filter: true,
|
||||||
},
|
},
|
||||||
SystemBus: &dbus.Config{
|
SystemBus: &hst.BusConfig{
|
||||||
Talk: []string{
|
Talk: []string{
|
||||||
"org.bluez",
|
"org.bluez",
|
||||||
"org.freedesktop.Avahi",
|
"org.freedesktop.Avahi",
|
||||||
@@ -121,7 +157,42 @@ func TestApp(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Filter: true,
|
Filter: true,
|
||||||
},
|
},
|
||||||
Enablements: hst.NewEnablements(system.EWayland | system.EDBus | system.EPulse),
|
Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPulse),
|
||||||
|
|
||||||
|
Container: &hst.ContainerConfig{
|
||||||
|
Userns: true, HostNet: true, HostAbstract: true, Tty: true,
|
||||||
|
|
||||||
|
Filesystem: []hst.FilesystemConfigJSON{
|
||||||
|
{FilesystemConfig: &hst.FSBind{
|
||||||
|
Target: fhs.AbsRoot,
|
||||||
|
Source: fhs.AbsRoot,
|
||||||
|
Write: true,
|
||||||
|
Special: true,
|
||||||
|
}},
|
||||||
|
{FilesystemConfig: &hst.FSBind{
|
||||||
|
Source: fhs.AbsDev.Append("dri"),
|
||||||
|
Device: true,
|
||||||
|
Optional: true,
|
||||||
|
}},
|
||||||
|
{FilesystemConfig: &hst.FSBind{
|
||||||
|
Source: fhs.AbsDev.Append("kvm"),
|
||||||
|
Device: true,
|
||||||
|
Optional: true,
|
||||||
|
}},
|
||||||
|
{FilesystemConfig: &hst.FSBind{
|
||||||
|
Target: fhs.AbsEtc,
|
||||||
|
Source: fhs.AbsEtc,
|
||||||
|
Special: true,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
|
||||||
|
Username: "chronos",
|
||||||
|
Shell: m("/run/current-system/sw/bin/zsh"),
|
||||||
|
Home: m("/home/chronos"),
|
||||||
|
|
||||||
|
Path: m("/run/current-system/sw/bin/zsh"),
|
||||||
|
Args: []string{"zsh", "-c", "exec chromium "},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
state.ID{
|
state.ID{
|
||||||
0xeb, 0xf0, 0x83, 0xd1,
|
0xeb, 0xf0, 0x83, 0xd1,
|
||||||
@@ -129,20 +200,19 @@ func TestApp(t *testing.T) {
|
|||||||
0x82, 0xd4, 0x13, 0x36,
|
0x82, 0xd4, 0x13, 0x36,
|
||||||
0x9b, 0x64, 0xce, 0x7c,
|
0x9b, 0x64, 0xce, 0x7c,
|
||||||
},
|
},
|
||||||
system.New(context.TODO(), 1000009).
|
system.New(t.Context(), msg, 1000009).
|
||||||
Ensure("/tmp/hakurei.0", 0711).
|
Ensure(m("/tmp/hakurei.0"), 0711).
|
||||||
Ensure("/tmp/hakurei.0/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime", acl.Execute).
|
Ensure(m("/tmp/hakurei.0/runtime"), 0700).UpdatePermType(system.User, m("/tmp/hakurei.0/runtime"), acl.Execute).
|
||||||
Ensure("/tmp/hakurei.0/runtime/9", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime/9", acl.Read, acl.Write, acl.Execute).
|
Ensure(m("/tmp/hakurei.0/runtime/9"), 0700).UpdatePermType(system.User, m("/tmp/hakurei.0/runtime/9"), acl.Read, acl.Write, acl.Execute).
|
||||||
Ensure("/tmp/hakurei.0/tmpdir", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir", acl.Execute).
|
Ensure(m("/tmp/hakurei.0/tmpdir"), 0700).UpdatePermType(system.User, m("/tmp/hakurei.0/tmpdir"), acl.Execute).
|
||||||
Ensure("/tmp/hakurei.0/tmpdir/9", 01700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir/9", acl.Read, acl.Write, acl.Execute).
|
Ensure(m("/tmp/hakurei.0/tmpdir/9"), 01700).UpdatePermType(system.User, m("/tmp/hakurei.0/tmpdir/9"), acl.Read, acl.Write, acl.Execute).
|
||||||
Ephemeral(system.Process, "/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c", 0711).
|
Ephemeral(system.Process, m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c"), 0711).
|
||||||
Wayland(new(*os.File), "/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/wayland", "/run/user/1971/wayland-0", "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c").
|
Wayland(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/wayland"), m("/run/user/1971/wayland-0"), "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c").
|
||||||
Ensure("/run/user/1971/hakurei", 0700).UpdatePermType(system.User, "/run/user/1971/hakurei", acl.Execute).
|
Ensure(m("/run/user/1971/hakurei"), 0700).UpdatePermType(system.User, m("/run/user/1971/hakurei"), acl.Execute).
|
||||||
Ensure("/run/user/1971", 0700).UpdatePermType(system.User, "/run/user/1971", acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
|
Ensure(m("/run/user/1971"), 0700).UpdatePermType(system.User, m("/run/user/1971"), acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
|
||||||
Ephemeral(system.Process, "/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c", 0700).UpdatePermType(system.Process, "/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c", acl.Execute).
|
Ephemeral(system.Process, m("/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c"), 0700).UpdatePermType(system.Process, m("/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c"), acl.Execute).
|
||||||
Link("/run/user/1971/pulse/native", "/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c/pulse").
|
Link(m("/run/user/1971/pulse/native"), m("/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c/pulse")).
|
||||||
CopyFile(new([]byte), "/home/ophestra/xdg/config/pulse/cookie", 256, 256).
|
MustProxyDBus(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/bus"), &hst.BusConfig{
|
||||||
MustProxyDBus("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/bus", &dbus.Config{
|
|
||||||
Talk: []string{
|
Talk: []string{
|
||||||
"org.freedesktop.Notifications",
|
"org.freedesktop.Notifications",
|
||||||
"org.freedesktop.FileManager1",
|
"org.freedesktop.FileManager1",
|
||||||
@@ -164,7 +234,7 @@ func TestApp(t *testing.T) {
|
|||||||
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*",
|
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*",
|
||||||
},
|
},
|
||||||
Filter: true,
|
Filter: true,
|
||||||
}, "/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/system_bus_socket", &dbus.Config{
|
}, m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/system_bus_socket"), &hst.BusConfig{
|
||||||
Talk: []string{
|
Talk: []string{
|
||||||
"org.bluez",
|
"org.bluez",
|
||||||
"org.freedesktop.Avahi",
|
"org.freedesktop.Avahi",
|
||||||
@@ -172,8 +242,8 @@ func TestApp(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Filter: true,
|
Filter: true,
|
||||||
}).
|
}).
|
||||||
UpdatePerm("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/bus", acl.Read, acl.Write).
|
UpdatePerm(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/bus"), acl.Read, acl.Write).
|
||||||
UpdatePerm("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/system_bus_socket", acl.Read, acl.Write),
|
UpdatePerm(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/system_bus_socket"), acl.Read, acl.Write),
|
||||||
&container.Params{
|
&container.Params{
|
||||||
Dir: m("/home/chronos"),
|
Dir: m("/home/chronos"),
|
||||||
Path: m("/run/current-system/sw/bin/zsh"),
|
Path: m("/run/current-system/sw/bin/zsh"),
|
||||||
@@ -193,30 +263,30 @@ func TestApp(t *testing.T) {
|
|||||||
"XDG_SESSION_TYPE=tty",
|
"XDG_SESSION_TYPE=tty",
|
||||||
},
|
},
|
||||||
Ops: new(container.Ops).
|
Ops: new(container.Ops).
|
||||||
Root(m("/"), container.BindWritable).
|
Root(m("/"), bits.BindWritable).
|
||||||
Proc(m("/proc/")).
|
Proc(m("/proc/")).
|
||||||
Tmpfs(hst.AbsTmp, 4096, 0755).
|
Tmpfs(hst.AbsTmp, 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"), container.BindWritable|container.BindDevice|container.BindOptional).
|
Bind(m("/dev/dri"), m("/dev/dri"), bits.BindWritable|bits.BindDevice|bits.BindOptional).
|
||||||
Bind(m("/dev/kvm"), m("/dev/kvm"), container.BindWritable|container.BindDevice|container.BindOptional).
|
Bind(m("/dev/kvm"), m("/dev/kvm"), bits.BindWritable|bits.BindDevice|bits.BindOptional).
|
||||||
Readonly(m("/var/run/nscd"), 0755).
|
|
||||||
Etc(m("/etc/"), "ebf083d1b175911782d413369b64ce7c").
|
Etc(m("/etc/"), "ebf083d1b175911782d413369b64ce7c").
|
||||||
Tmpfs(m("/run/user/1971"), 8192, 0755).
|
Tmpfs(m("/run/user/1971"), 8192, 0755).
|
||||||
|
Tmpfs(m("/run/nscd"), 8192, 0755).
|
||||||
Tmpfs(m("/run/dbus"), 8192, 0755).
|
Tmpfs(m("/run/dbus"), 8192, 0755).
|
||||||
Remount(m("/dev/"), syscall.MS_RDONLY).
|
Remount(m("/dev/"), syscall.MS_RDONLY).
|
||||||
Tmpfs(m("/run/user/"), 4096, 0755).
|
Tmpfs(m("/run/user/"), 4096, 0755).
|
||||||
Bind(m("/tmp/hakurei.0/runtime/9"), m("/run/user/65534"), container.BindWritable).
|
Bind(m("/tmp/hakurei.0/runtime/9"), m("/run/user/65534"), bits.BindWritable).
|
||||||
Bind(m("/tmp/hakurei.0/tmpdir/9"), m("/tmp/"), container.BindWritable).
|
Bind(m("/tmp/hakurei.0/tmpdir/9"), m("/tmp/"), bits.BindWritable).
|
||||||
Place(m("/etc/passwd"), []byte("chronos:x:65534:65534:Hakurei:/home/chronos:/run/current-system/sw/bin/zsh\n")).
|
Place(m("/etc/passwd"), []byte("chronos:x:65534:65534:Hakurei:/home/chronos:/run/current-system/sw/bin/zsh\n")).
|
||||||
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"), nil).
|
Place(m(hst.Tmp+"/pulse-cookie"), bytes.Repeat([]byte{0}, pulseCookieSizeMax)).
|
||||||
Bind(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/bus"), m("/run/user/65534/bus"), 0).
|
Bind(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/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("/run/dbus/system_bus_socket"), 0).
|
||||||
Remount(m("/"), syscall.MS_RDONLY),
|
Remount(m("/"), syscall.MS_RDONLY),
|
||||||
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyDevel,
|
SeccompPresets: bits.PresetExt | bits.PresetDenyDevel,
|
||||||
HostNet: true,
|
HostNet: true,
|
||||||
HostAbstract: true,
|
HostAbstract: true,
|
||||||
RetainSession: true,
|
RetainSession: true,
|
||||||
@@ -228,10 +298,7 @@ func TestApp(t *testing.T) {
|
|||||||
"nixos chromium direct wayland", new(stubNixOS),
|
"nixos chromium direct wayland", new(stubNixOS),
|
||||||
&hst.Config{
|
&hst.Config{
|
||||||
ID: "org.chromium.Chromium",
|
ID: "org.chromium.Chromium",
|
||||||
Path: m("/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"),
|
Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPulse),
|
||||||
Enablements: hst.NewEnablements(system.EWayland | system.EDBus | system.EPulse),
|
|
||||||
Shell: m("/run/current-system/sw/bin/zsh"),
|
|
||||||
|
|
||||||
Container: &hst.ContainerConfig{
|
Container: &hst.ContainerConfig{
|
||||||
Userns: true, HostNet: true, MapRealUID: true, Env: nil,
|
Userns: true, HostNet: true, MapRealUID: true, Env: nil,
|
||||||
Filesystem: []hst.FilesystemConfigJSON{
|
Filesystem: []hst.FilesystemConfigJSON{
|
||||||
@@ -249,12 +316,18 @@ func TestApp(t *testing.T) {
|
|||||||
f(&hst.FSBind{Source: m("/etc/"), Target: m("/etc/"), Special: true}),
|
f(&hst.FSBind{Source: m("/etc/"), Target: m("/etc/"), Special: true}),
|
||||||
f(&hst.FSBind{Source: m("/var/lib/persist/module/hakurei/0/1"), Write: true, Ensure: true}),
|
f(&hst.FSBind{Source: m("/var/lib/persist/module/hakurei/0/1"), Write: true, Ensure: true}),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Username: "u0_a1",
|
||||||
|
Shell: m("/run/current-system/sw/bin/zsh"),
|
||||||
|
Home: m("/var/lib/persist/module/hakurei/0/1"),
|
||||||
|
|
||||||
|
Path: m("/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"),
|
||||||
},
|
},
|
||||||
SystemBus: &dbus.Config{
|
SystemBus: &hst.BusConfig{
|
||||||
Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"},
|
Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"},
|
||||||
Filter: true,
|
Filter: true,
|
||||||
},
|
},
|
||||||
SessionBus: &dbus.Config{
|
SessionBus: &hst.BusConfig{
|
||||||
Talk: []string{
|
Talk: []string{
|
||||||
"org.freedesktop.FileManager1", "org.freedesktop.Notifications",
|
"org.freedesktop.FileManager1", "org.freedesktop.Notifications",
|
||||||
"org.freedesktop.ScreenSaver", "org.freedesktop.secrets",
|
"org.freedesktop.ScreenSaver", "org.freedesktop.secrets",
|
||||||
@@ -270,8 +343,6 @@ func TestApp(t *testing.T) {
|
|||||||
},
|
},
|
||||||
DirectWayland: true,
|
DirectWayland: true,
|
||||||
|
|
||||||
Username: "u0_a1",
|
|
||||||
Home: m("/var/lib/persist/module/hakurei/0/1"),
|
|
||||||
Identity: 1, Groups: []string{},
|
Identity: 1, Groups: []string{},
|
||||||
},
|
},
|
||||||
state.ID{
|
state.ID{
|
||||||
@@ -280,20 +351,19 @@ func TestApp(t *testing.T) {
|
|||||||
0x4c, 0xf0, 0x73, 0xbd,
|
0x4c, 0xf0, 0x73, 0xbd,
|
||||||
0xb4, 0x6e, 0xb5, 0xc1,
|
0xb4, 0x6e, 0xb5, 0xc1,
|
||||||
},
|
},
|
||||||
system.New(context.TODO(), 1000001).
|
system.New(t.Context(), msg, 1000001).
|
||||||
Ensure("/tmp/hakurei.0", 0711).
|
Ensure(m("/tmp/hakurei.0"), 0711).
|
||||||
Ensure("/tmp/hakurei.0/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime", acl.Execute).
|
Ensure(m("/tmp/hakurei.0/runtime"), 0700).UpdatePermType(system.User, m("/tmp/hakurei.0/runtime"), acl.Execute).
|
||||||
Ensure("/tmp/hakurei.0/runtime/1", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime/1", acl.Read, acl.Write, acl.Execute).
|
Ensure(m("/tmp/hakurei.0/runtime/1"), 0700).UpdatePermType(system.User, m("/tmp/hakurei.0/runtime/1"), acl.Read, acl.Write, acl.Execute).
|
||||||
Ensure("/tmp/hakurei.0/tmpdir", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir", acl.Execute).
|
Ensure(m("/tmp/hakurei.0/tmpdir"), 0700).UpdatePermType(system.User, m("/tmp/hakurei.0/tmpdir"), acl.Execute).
|
||||||
Ensure("/tmp/hakurei.0/tmpdir/1", 01700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir/1", acl.Read, acl.Write, acl.Execute).
|
Ensure(m("/tmp/hakurei.0/tmpdir/1"), 01700).UpdatePermType(system.User, m("/tmp/hakurei.0/tmpdir/1"), acl.Read, acl.Write, acl.Execute).
|
||||||
Ensure("/run/user/1971/hakurei", 0700).UpdatePermType(system.User, "/run/user/1971/hakurei", acl.Execute).
|
Ensure(m("/run/user/1971/hakurei"), 0700).UpdatePermType(system.User, m("/run/user/1971/hakurei"), acl.Execute).
|
||||||
Ensure("/run/user/1971", 0700).UpdatePermType(system.User, "/run/user/1971", acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
|
Ensure(m("/run/user/1971"), 0700).UpdatePermType(system.User, m("/run/user/1971"), acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
|
||||||
UpdatePermType(system.EWayland, "/run/user/1971/wayland-0", acl.Read, acl.Write, acl.Execute).
|
UpdatePermType(hst.EWayland, m("/run/user/1971/wayland-0"), acl.Read, acl.Write, acl.Execute).
|
||||||
Ephemeral(system.Process, "/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1", 0700).UpdatePermType(system.Process, "/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1", acl.Execute).
|
Ephemeral(system.Process, m("/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1"), 0700).UpdatePermType(system.Process, m("/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1"), acl.Execute).
|
||||||
Link("/run/user/1971/pulse/native", "/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1/pulse").
|
Link(m("/run/user/1971/pulse/native"), m("/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1/pulse")).
|
||||||
CopyFile(nil, "/home/ophestra/xdg/config/pulse/cookie", 256, 256).
|
Ephemeral(system.Process, m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1"), 0711).
|
||||||
Ephemeral(system.Process, "/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1", 0711).
|
MustProxyDBus(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus"), &hst.BusConfig{
|
||||||
MustProxyDBus("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus", &dbus.Config{
|
|
||||||
Talk: []string{
|
Talk: []string{
|
||||||
"org.freedesktop.FileManager1", "org.freedesktop.Notifications",
|
"org.freedesktop.FileManager1", "org.freedesktop.Notifications",
|
||||||
"org.freedesktop.ScreenSaver", "org.freedesktop.secrets",
|
"org.freedesktop.ScreenSaver", "org.freedesktop.secrets",
|
||||||
@@ -306,7 +376,7 @@ func TestApp(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Call: map[string]string{}, Broadcast: map[string]string{},
|
Call: map[string]string{}, Broadcast: map[string]string{},
|
||||||
Filter: true,
|
Filter: true,
|
||||||
}, "/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", &dbus.Config{
|
}, m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket"), &hst.BusConfig{
|
||||||
Talk: []string{
|
Talk: []string{
|
||||||
"org.bluez",
|
"org.bluez",
|
||||||
"org.freedesktop.Avahi",
|
"org.freedesktop.Avahi",
|
||||||
@@ -314,8 +384,8 @@ func TestApp(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Filter: true,
|
Filter: true,
|
||||||
}).
|
}).
|
||||||
UpdatePerm("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus", acl.Read, acl.Write).
|
UpdatePerm(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus"), acl.Read, acl.Write).
|
||||||
UpdatePerm("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", acl.Read, acl.Write),
|
UpdatePerm(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket"), acl.Read, acl.Write),
|
||||||
&container.Params{
|
&container.Params{
|
||||||
Uid: 1971,
|
Uid: 1971,
|
||||||
Gid: 100,
|
Gid: 100,
|
||||||
@@ -345,28 +415,28 @@ func TestApp(t *testing.T) {
|
|||||||
Bind(m("/usr/bin/"), m("/usr/bin/"), 0).
|
Bind(m("/usr/bin/"), m("/usr/bin/"), 0).
|
||||||
Bind(m("/nix/store"), m("/nix/store"), 0).
|
Bind(m("/nix/store"), m("/nix/store"), 0).
|
||||||
Bind(m("/run/current-system"), m("/run/current-system"), 0).
|
Bind(m("/run/current-system"), m("/run/current-system"), 0).
|
||||||
Bind(m("/sys/block"), m("/sys/block"), container.BindOptional).
|
Bind(m("/sys/block"), m("/sys/block"), bits.BindOptional).
|
||||||
Bind(m("/sys/bus"), m("/sys/bus"), container.BindOptional).
|
Bind(m("/sys/bus"), m("/sys/bus"), bits.BindOptional).
|
||||||
Bind(m("/sys/class"), m("/sys/class"), container.BindOptional).
|
Bind(m("/sys/class"), m("/sys/class"), bits.BindOptional).
|
||||||
Bind(m("/sys/dev"), m("/sys/dev"), container.BindOptional).
|
Bind(m("/sys/dev"), m("/sys/dev"), bits.BindOptional).
|
||||||
Bind(m("/sys/devices"), m("/sys/devices"), container.BindOptional).
|
Bind(m("/sys/devices"), m("/sys/devices"), bits.BindOptional).
|
||||||
Bind(m("/run/opengl-driver"), m("/run/opengl-driver"), 0).
|
Bind(m("/run/opengl-driver"), m("/run/opengl-driver"), 0).
|
||||||
Bind(m("/dev/dri"), m("/dev/dri"), container.BindDevice|container.BindWritable|container.BindOptional).
|
Bind(m("/dev/dri"), m("/dev/dri"), bits.BindDevice|bits.BindWritable|bits.BindOptional).
|
||||||
Etc(m("/etc/"), "8e2c76b066dabe574cf073bdb46eb5c1").
|
Etc(m("/etc/"), "8e2c76b066dabe574cf073bdb46eb5c1").
|
||||||
Bind(m("/var/lib/persist/module/hakurei/0/1"), m("/var/lib/persist/module/hakurei/0/1"), container.BindWritable|container.BindEnsure).
|
Bind(m("/var/lib/persist/module/hakurei/0/1"), m("/var/lib/persist/module/hakurei/0/1"), bits.BindWritable|bits.BindEnsure).
|
||||||
Remount(m("/dev/"), syscall.MS_RDONLY).
|
Remount(m("/dev/"), syscall.MS_RDONLY).
|
||||||
Tmpfs(m("/run/user/"), 4096, 0755).
|
Tmpfs(m("/run/user/"), 4096, 0755).
|
||||||
Bind(m("/tmp/hakurei.0/runtime/1"), m("/run/user/1971"), container.BindWritable).
|
Bind(m("/tmp/hakurei.0/runtime/1"), m("/run/user/1971"), bits.BindWritable).
|
||||||
Bind(m("/tmp/hakurei.0/tmpdir/1"), m("/tmp/"), container.BindWritable).
|
Bind(m("/tmp/hakurei.0/tmpdir/1"), m("/tmp/"), bits.BindWritable).
|
||||||
Place(m("/etc/passwd"), []byte("u0_a1:x:1971:100:Hakurei:/var/lib/persist/module/hakurei/0/1:/run/current-system/sw/bin/zsh\n")).
|
Place(m("/etc/passwd"), []byte("u0_a1:x:1971:100:Hakurei:/var/lib/persist/module/hakurei/0/1:/run/current-system/sw/bin/zsh\n")).
|
||||||
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"), nil).
|
Place(m(hst.Tmp+"/pulse-cookie"), bytes.Repeat([]byte{0}, pulseCookieSizeMax)).
|
||||||
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus"), m("/run/user/1971/bus"), 0).
|
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/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("/run/dbus/system_bus_socket"), 0).
|
||||||
Remount(m("/"), syscall.MS_RDONLY),
|
Remount(m("/"), syscall.MS_RDONLY),
|
||||||
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyTTY | seccomp.PresetDenyDevel,
|
SeccompPresets: bits.PresetExt | bits.PresetDenyTTY | bits.PresetDenyDevel,
|
||||||
HostNet: true,
|
HostNet: true,
|
||||||
ForwardCancel: true,
|
ForwardCancel: true,
|
||||||
},
|
},
|
||||||
@@ -375,28 +445,75 @@ 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.Run("finalise", func(t *testing.T) {
|
gr, gw := io.Pipe()
|
||||||
seal := outcome{syscallDispatcher: tc.k, id: &stringPair[state.ID]{tc.id, tc.id.String()}}
|
|
||||||
err := seal.finalise(t.Context(), tc.config)
|
var gotSys *system.I
|
||||||
if err != nil {
|
{
|
||||||
if s, ok := container.GetErrorMessage(err); !ok {
|
sPriv := outcomeState{
|
||||||
t.Fatalf("Seal: error = %v", err)
|
ID: &tc.id,
|
||||||
} else {
|
Identity: tc.config.Identity,
|
||||||
t.Fatalf("Seal: %s", s)
|
UserID: (&Hsu{k: tc.k}).MustIDMsg(msg),
|
||||||
|
EnvPaths: copyPaths(tc.k),
|
||||||
|
Container: tc.config.Container,
|
||||||
|
}
|
||||||
|
|
||||||
|
sPriv.populateEarly(tc.k, msg, tc.config)
|
||||||
|
if err := sPriv.populateLocal(tc.k, msg); err != nil {
|
||||||
|
t.Fatalf("populateLocal: error = %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
gotSys = system.New(t.Context(), msg, sPriv.uid.unwrap())
|
||||||
|
stateSys := outcomeStateSys{sys: gotSys, outcomeState: &sPriv}
|
||||||
|
for _, op := range sPriv.Shim.Ops {
|
||||||
|
if err := op.toSystem(&stateSys, tc.config); err != nil {
|
||||||
|
t.Fatalf("toSystem: error = %#v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("sys", func(t *testing.T) {
|
go func() {
|
||||||
if !seal.sys.Equal(tc.wantSys) {
|
e := gob.NewEncoder(gw)
|
||||||
t.Errorf("Seal: sys = %#v, want %#v", seal.sys, tc.wantSys)
|
if err := errors.Join(e.Encode(&sPriv)); err != nil {
|
||||||
|
t.Errorf("Encode: error = %v", err)
|
||||||
|
panic("unexpected encode fault")
|
||||||
}
|
}
|
||||||
})
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
t.Run("params", func(t *testing.T) {
|
var gotParams container.Params
|
||||||
if !reflect.DeepEqual(seal.container, tc.wantParams) {
|
{
|
||||||
t.Errorf("seal: container =\n%s\n, want\n%s", mustMarshal(seal.container), mustMarshal(tc.wantParams))
|
var sShim outcomeState
|
||||||
|
|
||||||
|
d := gob.NewDecoder(gr)
|
||||||
|
if err := errors.Join(d.Decode(&sShim)); err != nil {
|
||||||
|
t.Fatalf("Decode: error = %v", err)
|
||||||
|
}
|
||||||
|
if err := sShim.populateLocal(tc.k, msg); err != nil {
|
||||||
|
t.Fatalf("populateLocal: error = %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stateParams := outcomeStateParams{params: &gotParams, outcomeState: &sShim}
|
||||||
|
if sShim.Container.Env == nil {
|
||||||
|
stateParams.env = make(map[string]string, envAllocSize)
|
||||||
|
} else {
|
||||||
|
stateParams.env = maps.Clone(sShim.Container.Env)
|
||||||
|
}
|
||||||
|
for _, op := range sShim.Shim.Ops {
|
||||||
|
if err := op.toContainer(&stateParams); err != nil {
|
||||||
|
t.Fatalf("toContainer: error = %#v", err)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("sys", func(t *testing.T) {
|
||||||
|
if !gotSys.Equal(tc.wantSys) {
|
||||||
|
t.Errorf("toSystem: sys = %#v, want %#v", gotSys, tc.wantSys)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("params", func(t *testing.T) {
|
||||||
|
if !reflect.DeepEqual(&gotParams, tc.wantParams) {
|
||||||
|
t.Errorf("toContainer: params =\n%s\n, want\n%s", mustMarshal(&gotParams), mustMarshal(tc.wantParams))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -443,8 +560,203 @@ func (s stubFileInfoIsDir) ModTime() time.Time { panic("attempted to call ModTim
|
|||||||
func (s stubFileInfoIsDir) IsDir() bool { return bool(s) }
|
func (s stubFileInfoIsDir) IsDir() bool { return bool(s) }
|
||||||
func (s stubFileInfoIsDir) Sys() any { panic("attempted to call Sys") }
|
func (s stubFileInfoIsDir) Sys() any { panic("attempted to call Sys") }
|
||||||
|
|
||||||
func m(pathname string) *container.Absolute {
|
type stubFileInfoPulseCookie struct{ stubFileInfoIsDir }
|
||||||
return container.MustAbs(pathname)
|
|
||||||
|
func (s stubFileInfoPulseCookie) Size() int64 { return pulseCookieSizeMax }
|
||||||
|
|
||||||
|
type stubOsFileReadCloser struct{ io.ReadCloser }
|
||||||
|
|
||||||
|
func (s stubOsFileReadCloser) Name() string { panic("attempting to call Name") }
|
||||||
|
func (s stubOsFileReadCloser) Write([]byte) (int, error) { panic("attempting to call Write") }
|
||||||
|
func (s stubOsFileReadCloser) Stat() (fs.FileInfo, error) { panic("attempting to call Stat") }
|
||||||
|
|
||||||
|
type stubNixOS struct {
|
||||||
|
usernameErr map[string]error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *stubNixOS) new(func(k syscallDispatcher)) { panic("not implemented") }
|
||||||
|
|
||||||
|
func (k *stubNixOS) getuid() int { return 1971 }
|
||||||
|
func (k *stubNixOS) getgid() int { return 100 }
|
||||||
|
|
||||||
|
func (k *stubNixOS) lookupEnv(key string) (string, bool) {
|
||||||
|
switch key {
|
||||||
|
case "SHELL":
|
||||||
|
return "/run/current-system/sw/bin/zsh", true
|
||||||
|
case "TERM":
|
||||||
|
return "xterm-256color", true
|
||||||
|
case "WAYLAND_DISPLAY":
|
||||||
|
return "wayland-0", true
|
||||||
|
case "PULSE_COOKIE":
|
||||||
|
return "", false
|
||||||
|
case "HOME":
|
||||||
|
return "/home/ophestra", true
|
||||||
|
case "XDG_RUNTIME_DIR":
|
||||||
|
return "/run/user/1971", true
|
||||||
|
case "XDG_CONFIG_HOME":
|
||||||
|
return "/home/ophestra/xdg/config", true
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("attempted to access unexpected environment variable %q", key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *stubNixOS) stat(name string) (fs.FileInfo, error) {
|
||||||
|
switch name {
|
||||||
|
case "/var/run/nscd":
|
||||||
|
return nil, nil
|
||||||
|
case "/run/user/1971/pulse":
|
||||||
|
return nil, nil
|
||||||
|
case "/run/user/1971/pulse/native":
|
||||||
|
return stubFileInfoMode(0666), nil
|
||||||
|
case "/home/ophestra/.pulse-cookie":
|
||||||
|
return stubFileInfoIsDir(true), nil
|
||||||
|
case "/home/ophestra/xdg/config/pulse/cookie":
|
||||||
|
return stubFileInfoPulseCookie{false}, nil
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("attempted to stat unexpected path %q", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *stubNixOS) open(name string) (osFile, error) {
|
||||||
|
switch name {
|
||||||
|
case "/home/ophestra/xdg/config/pulse/cookie":
|
||||||
|
return stubOsFileReadCloser{io.NopCloser(bytes.NewReader(bytes.Repeat([]byte{0}, pulseCookieSizeMax)))}, nil
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("attempted to open unexpected path %q", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *stubNixOS) readdir(name string) ([]fs.DirEntry, error) {
|
||||||
|
switch name {
|
||||||
|
case "/":
|
||||||
|
return stubDirEntries("bin", "boot", "dev", "etc", "home", "lib",
|
||||||
|
"lib64", "nix", "proc", "root", "run", "srv", "sys", "tmp", "usr", "var")
|
||||||
|
|
||||||
|
case "/run":
|
||||||
|
return stubDirEntries("agetty.reload", "binfmt", "booted-system",
|
||||||
|
"credentials", "cryptsetup", "current-system", "dbus", "host", "keys",
|
||||||
|
"libvirt", "libvirtd.pid", "lock", "log", "lvm", "mount", "NetworkManager",
|
||||||
|
"nginx", "nixos", "nscd", "opengl-driver", "pppd", "resolvconf", "sddm",
|
||||||
|
"store", "syncoid", "system", "systemd", "tmpfiles.d", "udev", "udisks2",
|
||||||
|
"user", "utmp", "virtlogd.pid", "wrappers", "zed.pid", "zed.state")
|
||||||
|
|
||||||
|
case "/etc":
|
||||||
|
return stubDirEntries("alsa", "bashrc", "binfmt.d", "dbus-1", "default",
|
||||||
|
"ethertypes", "fonts", "fstab", "fuse.conf", "group", "host.conf", "hostid",
|
||||||
|
"hostname", "hostname.CHECKSUM", "hosts", "inputrc", "ipsec.d", "issue", "kbd",
|
||||||
|
"libblockdev", "locale.conf", "localtime", "login.defs", "lsb-release", "lvm",
|
||||||
|
"machine-id", "man_db.conf", "modprobe.d", "modules-load.d", "mtab", "nanorc",
|
||||||
|
"netgroup", "NetworkManager", "nix", "nixos", "NIXOS", "nscd.conf", "nsswitch.conf",
|
||||||
|
"opensnitchd", "os-release", "pam", "pam.d", "passwd", "pipewire", "pki", "polkit-1",
|
||||||
|
"profile", "protocols", "qemu", "resolv.conf", "resolvconf.conf", "rpc", "samba",
|
||||||
|
"sddm.conf", "secureboot", "services", "set-environment", "shadow", "shells", "ssh",
|
||||||
|
"ssl", "static", "subgid", "subuid", "sudoers", "sysctl.d", "systemd", "terminfo",
|
||||||
|
"tmpfiles.d", "udev", "udisks2", "UPower", "vconsole.conf", "X11", "zfs", "zinputrc",
|
||||||
|
"zoneinfo", "zprofile", "zshenv", "zshrc")
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("attempted to read unexpected directory %q", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *stubNixOS) tempdir() string { return "/tmp/" }
|
||||||
|
|
||||||
|
func (k *stubNixOS) evalSymlinks(path string) (string, error) {
|
||||||
|
switch path {
|
||||||
|
case "/var/run/nscd":
|
||||||
|
return "/run/nscd", nil
|
||||||
|
case "/run/user/1971":
|
||||||
|
return "/run/user/1971", nil
|
||||||
|
case "/tmp/hakurei.0":
|
||||||
|
return "/tmp/hakurei.0", nil
|
||||||
|
case "/run/dbus":
|
||||||
|
return "/run/dbus", nil
|
||||||
|
case "/dev/kvm":
|
||||||
|
return "/dev/kvm", nil
|
||||||
|
case "/etc/":
|
||||||
|
return "/etc/", nil
|
||||||
|
case "/bin":
|
||||||
|
return "/bin", nil
|
||||||
|
case "/boot":
|
||||||
|
return "/boot", nil
|
||||||
|
case "/home":
|
||||||
|
return "/home", nil
|
||||||
|
case "/lib":
|
||||||
|
return "/lib", nil
|
||||||
|
case "/lib64":
|
||||||
|
return "/lib64", nil
|
||||||
|
case "/nix":
|
||||||
|
return "/nix", nil
|
||||||
|
case "/root":
|
||||||
|
return "/root", nil
|
||||||
|
case "/run":
|
||||||
|
return "/run", nil
|
||||||
|
case "/srv":
|
||||||
|
return "/srv", nil
|
||||||
|
case "/sys":
|
||||||
|
return "/sys", nil
|
||||||
|
case "/usr":
|
||||||
|
return "/usr", nil
|
||||||
|
case "/var":
|
||||||
|
return "/var", nil
|
||||||
|
case "/dev/dri":
|
||||||
|
return "/dev/dri", nil
|
||||||
|
case "/usr/bin/":
|
||||||
|
return "/usr/bin/", nil
|
||||||
|
case "/nix/store":
|
||||||
|
return "/nix/store", nil
|
||||||
|
case "/run/current-system":
|
||||||
|
return "/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-nixos-system-satori-25.05.99999999.aaaaaaa", nil
|
||||||
|
case "/sys/block":
|
||||||
|
return "/sys/block", nil
|
||||||
|
case "/sys/bus":
|
||||||
|
return "/sys/bus", nil
|
||||||
|
case "/sys/class":
|
||||||
|
return "/sys/class", nil
|
||||||
|
case "/sys/dev":
|
||||||
|
return "/sys/dev", nil
|
||||||
|
case "/sys/devices":
|
||||||
|
return "/sys/devices", nil
|
||||||
|
case "/run/opengl-driver":
|
||||||
|
return "/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-graphics-drivers", nil
|
||||||
|
case "/var/lib/persist/module/hakurei/0/1":
|
||||||
|
return "/var/lib/persist/module/hakurei/0/1", nil
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("attempted to evaluate unexpected path %q", path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *stubNixOS) lookupGroupId(name string) (string, error) {
|
||||||
|
switch name {
|
||||||
|
case "video":
|
||||||
|
return "26", nil
|
||||||
|
default:
|
||||||
|
return "", user.UnknownGroupError(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *stubNixOS) cmdOutput(cmd *exec.Cmd) ([]byte, error) {
|
||||||
|
switch cmd.Path {
|
||||||
|
case "/proc/nonexistent/hsu":
|
||||||
|
return []byte{'0'}, nil
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unexpected cmd %#v", cmd))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *stubNixOS) overflowUid(container.Msg) int { return 65534 }
|
||||||
|
func (k *stubNixOS) overflowGid(container.Msg) int { return 65534 }
|
||||||
|
|
||||||
|
func (k *stubNixOS) mustHsuPath() *check.Absolute { return m("/proc/nonexistent/hsu") }
|
||||||
|
|
||||||
|
func (k *stubNixOS) fatalf(format string, v ...any) { panic(fmt.Sprintf(format, v...)) }
|
||||||
|
|
||||||
|
func (k *stubNixOS) isVerbose() bool { return true }
|
||||||
|
func (k *stubNixOS) verbose(v ...any) { log.Print(v...) }
|
||||||
|
func (k *stubNixOS) verbosef(format string, v ...any) { log.Printf(format, v...) }
|
||||||
|
|
||||||
|
func m(pathname string) *check.Absolute {
|
||||||
|
return check.MustAbs(pathname)
|
||||||
}
|
}
|
||||||
|
|
||||||
func f(c hst.FilesystemConfig) hst.FilesystemConfigJSON {
|
func f(c hst.FilesystemConfig) hst.FilesystemConfigJSON {
|
||||||
|
|||||||
@@ -1,254 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/fs"
|
|
||||||
"maps"
|
|
||||||
"path"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"hakurei.app/container"
|
|
||||||
"hakurei.app/container/seccomp"
|
|
||||||
"hakurei.app/hst"
|
|
||||||
"hakurei.app/system/dbus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// in practice there should be less than 30 system mount points
|
|
||||||
const preallocateOpsCount = 1 << 5
|
|
||||||
|
|
||||||
// newContainer initialises [container.Params] via [hst.ContainerConfig].
|
|
||||||
// Note that remaining container setup must be queued by the caller.
|
|
||||||
func newContainer(
|
|
||||||
k syscallDispatcher,
|
|
||||||
s *hst.ContainerConfig,
|
|
||||||
prefix string,
|
|
||||||
sc *hst.Paths,
|
|
||||||
uid, gid *int,
|
|
||||||
) (*container.Params, map[string]string, error) {
|
|
||||||
if s == nil {
|
|
||||||
return nil, nil, newWithMessage("invalid container configuration")
|
|
||||||
}
|
|
||||||
|
|
||||||
params := &container.Params{
|
|
||||||
Hostname: s.Hostname,
|
|
||||||
SeccompFlags: s.SeccompFlags,
|
|
||||||
SeccompPresets: s.SeccompPresets,
|
|
||||||
RetainSession: s.Tty,
|
|
||||||
HostNet: s.HostNet,
|
|
||||||
HostAbstract: s.HostAbstract,
|
|
||||||
|
|
||||||
// the container is canceled when shim is requested to exit or receives an interrupt or termination signal;
|
|
||||||
// this behaviour is implemented in the shim
|
|
||||||
ForwardCancel: s.WaitDelay >= 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
as := &hst.ApplyState{AutoEtcPrefix: prefix}
|
|
||||||
{
|
|
||||||
ops := make(container.Ops, 0, preallocateOpsCount+len(s.Filesystem))
|
|
||||||
params.Ops = &ops
|
|
||||||
as.Ops = &ops
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Multiarch {
|
|
||||||
params.SeccompFlags |= seccomp.AllowMultiarch
|
|
||||||
}
|
|
||||||
|
|
||||||
if !s.SeccompCompat {
|
|
||||||
params.SeccompPresets |= seccomp.PresetExt
|
|
||||||
}
|
|
||||||
if !s.Devel {
|
|
||||||
params.SeccompPresets |= seccomp.PresetDenyDevel
|
|
||||||
}
|
|
||||||
if !s.Userns {
|
|
||||||
params.SeccompPresets |= seccomp.PresetDenyNS
|
|
||||||
}
|
|
||||||
if !s.Tty {
|
|
||||||
params.SeccompPresets |= seccomp.PresetDenyTTY
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.MapRealUID {
|
|
||||||
params.Uid = k.getuid()
|
|
||||||
*uid = params.Uid
|
|
||||||
params.Gid = k.getgid()
|
|
||||||
*gid = params.Gid
|
|
||||||
} else {
|
|
||||||
*uid = k.overflowUid()
|
|
||||||
*gid = k.overflowGid()
|
|
||||||
}
|
|
||||||
|
|
||||||
filesystem := s.Filesystem
|
|
||||||
var autoroot *hst.FSBind
|
|
||||||
// valid happens late, so root mount gets it here
|
|
||||||
if len(filesystem) > 0 && filesystem[0].Valid() && filesystem[0].Path().String() == container.FHSRoot {
|
|
||||||
// if the first element targets /, it is inserted early and excluded from path hiding
|
|
||||||
rootfs := filesystem[0].FilesystemConfig
|
|
||||||
filesystem = filesystem[1:]
|
|
||||||
rootfs.Apply(as)
|
|
||||||
|
|
||||||
// autoroot requires special handling during path hiding
|
|
||||||
if b, ok := rootfs.(*hst.FSBind); ok && b.IsAutoRoot() {
|
|
||||||
autoroot = b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
params.
|
|
||||||
Proc(container.AbsFHSProc).
|
|
||||||
Tmpfs(hst.AbsTmp, 1<<12, 0755)
|
|
||||||
|
|
||||||
if !s.Device {
|
|
||||||
params.DevWritable(container.AbsFHSDev, true)
|
|
||||||
} else {
|
|
||||||
params.Bind(container.AbsFHSDev, container.AbsFHSDev, container.BindWritable|container.BindDevice)
|
|
||||||
}
|
|
||||||
// /dev is mounted readonly later on, this prevents /dev/shm from going readonly with it
|
|
||||||
params.Tmpfs(container.AbsFHSDev.Append("shm"), 0, 01777)
|
|
||||||
|
|
||||||
/* retrieve paths and hide them if they're made available in the sandbox;
|
|
||||||
|
|
||||||
this feature tries to improve user experience of permissive defaults, and
|
|
||||||
to warn about issues in custom configuration; it is NOT a security feature
|
|
||||||
and should not be treated as such, ALWAYS be careful with what you bind */
|
|
||||||
var hidePaths []string
|
|
||||||
hidePaths = append(hidePaths, sc.RuntimePath.String(), sc.SharePath.String())
|
|
||||||
_, systemBusAddr := dbus.Address()
|
|
||||||
if entries, err := dbus.Parse([]byte(systemBusAddr)); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
} else {
|
|
||||||
// there is usually only one, do not preallocate
|
|
||||||
for _, entry := range entries {
|
|
||||||
if entry.Method != "unix" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, pair := range entry.Values {
|
|
||||||
if pair[0] == "path" {
|
|
||||||
if path.IsAbs(pair[1]) {
|
|
||||||
// get parent dir of socket
|
|
||||||
dir := path.Dir(pair[1])
|
|
||||||
if dir == "." || dir == container.FHSRoot {
|
|
||||||
k.verbosef("dbus socket %q is in an unusual location", pair[1])
|
|
||||||
}
|
|
||||||
hidePaths = append(hidePaths, dir)
|
|
||||||
} else {
|
|
||||||
k.verbosef("dbus socket %q is not absolute", pair[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hidePathMatch := make([]bool, len(hidePaths))
|
|
||||||
for i := range hidePaths {
|
|
||||||
if err := evalSymlinks(k, &hidePaths[i]); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var hidePathSourceCount int
|
|
||||||
for i, c := range filesystem {
|
|
||||||
if !c.Valid() {
|
|
||||||
return nil, nil, fmt.Errorf("invalid filesystem at index %d", i)
|
|
||||||
}
|
|
||||||
c.Apply(as)
|
|
||||||
|
|
||||||
// fs counter
|
|
||||||
hidePathSourceCount += len(c.Host())
|
|
||||||
}
|
|
||||||
|
|
||||||
// AutoRootOp is a collection of many BindMountOp internally
|
|
||||||
var autoRootEntries []fs.DirEntry
|
|
||||||
if autoroot != nil {
|
|
||||||
if d, err := k.readdir(autoroot.Source.String()); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
} else {
|
|
||||||
// autoroot counter
|
|
||||||
hidePathSourceCount += len(d)
|
|
||||||
autoRootEntries = d
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hidePathSource := make([]*container.Absolute, 0, hidePathSourceCount)
|
|
||||||
|
|
||||||
// fs append
|
|
||||||
for _, c := range filesystem {
|
|
||||||
// all entries already checked above
|
|
||||||
hidePathSource = append(hidePathSource, c.Host()...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// autoroot append
|
|
||||||
if autoroot != nil {
|
|
||||||
for _, ent := range autoRootEntries {
|
|
||||||
name := ent.Name()
|
|
||||||
if container.IsAutoRootBindable(name) {
|
|
||||||
hidePathSource = append(hidePathSource, autoroot.Source.Append(name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// evaluated path, input path
|
|
||||||
hidePathSourceEval := make([][2]string, len(hidePathSource))
|
|
||||||
for i, a := range hidePathSource {
|
|
||||||
if a == nil {
|
|
||||||
// unreachable
|
|
||||||
return nil, nil, syscall.ENOTRECOVERABLE
|
|
||||||
}
|
|
||||||
|
|
||||||
hidePathSourceEval[i] = [2]string{a.String(), a.String()}
|
|
||||||
if err := evalSymlinks(k, &hidePathSourceEval[i][0]); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range hidePathSourceEval {
|
|
||||||
for i := range hidePaths {
|
|
||||||
// skip matched entries
|
|
||||||
if hidePathMatch[i] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok, err := deepContainsH(p[0], hidePaths[i]); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
} else if ok {
|
|
||||||
hidePathMatch[i] = true
|
|
||||||
k.verbosef("hiding path %q from %q", hidePaths[i], p[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// cover matched paths
|
|
||||||
for i, ok := range hidePathMatch {
|
|
||||||
if ok {
|
|
||||||
if a, err := container.NewAbs(hidePaths[i]); err != nil {
|
|
||||||
var absoluteError *container.AbsoluteError
|
|
||||||
if !errors.As(err, &absoluteError) {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if absoluteError == nil {
|
|
||||||
return nil, nil, syscall.ENOTRECOVERABLE
|
|
||||||
}
|
|
||||||
return nil, nil, fmt.Errorf("invalid path hiding candidate %q", absoluteError.Pathname)
|
|
||||||
} else {
|
|
||||||
params.Tmpfs(a, 1<<13, 0755)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// no more ContainerConfig paths beyond this point
|
|
||||||
if !s.Device {
|
|
||||||
params.Remount(container.AbsFHSDev, syscall.MS_RDONLY)
|
|
||||||
}
|
|
||||||
|
|
||||||
return params, maps.Clone(s.Env), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// evalSymlinks calls syscallDispatcher.evalSymlinks but discards errors unwrapping to [fs.ErrNotExist].
|
|
||||||
func evalSymlinks(k syscallDispatcher, v *string) error {
|
|
||||||
if p, err := k.evalSymlinks(*v); err != nil {
|
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
k.verbosef("path %q does not yet exist", *v)
|
|
||||||
} else {
|
|
||||||
*v = p
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -8,10 +10,17 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/internal"
|
"hakurei.app/internal"
|
||||||
"hakurei.app/internal/hlog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// osFile represents [os.File].
|
||||||
|
type osFile interface {
|
||||||
|
Name() string
|
||||||
|
io.Writer
|
||||||
|
fs.File
|
||||||
|
}
|
||||||
|
|
||||||
// syscallDispatcher provides methods that make state-dependent system calls as part of their behaviour.
|
// syscallDispatcher provides methods that make state-dependent system calls as part of their behaviour.
|
||||||
type syscallDispatcher interface {
|
type syscallDispatcher interface {
|
||||||
// new starts a goroutine with a new instance of syscallDispatcher.
|
// new starts a goroutine with a new instance of syscallDispatcher.
|
||||||
@@ -27,6 +36,8 @@ type syscallDispatcher interface {
|
|||||||
lookupEnv(key string) (string, bool)
|
lookupEnv(key string) (string, bool)
|
||||||
// stat provides [os.Stat].
|
// stat provides [os.Stat].
|
||||||
stat(name string) (os.FileInfo, error)
|
stat(name string) (os.FileInfo, error)
|
||||||
|
// open provides [os.Open].
|
||||||
|
open(name string) (osFile, error)
|
||||||
// readdir provides [os.ReadDir].
|
// readdir provides [os.ReadDir].
|
||||||
readdir(name string) ([]os.DirEntry, error)
|
readdir(name string) ([]os.DirEntry, error)
|
||||||
// tempdir provides [os.TempDir].
|
// tempdir provides [os.TempDir].
|
||||||
@@ -35,9 +46,6 @@ type syscallDispatcher interface {
|
|||||||
// evalSymlinks provides [filepath.EvalSymlinks].
|
// evalSymlinks provides [filepath.EvalSymlinks].
|
||||||
evalSymlinks(path string) (string, error)
|
evalSymlinks(path string) (string, error)
|
||||||
|
|
||||||
// lookPath provides exec.LookPath.
|
|
||||||
lookPath(file string) (string, error)
|
|
||||||
|
|
||||||
// lookupGroupId calls [user.LookupGroup] and returns the Gid field of the resulting [user.Group] struct.
|
// lookupGroupId calls [user.LookupGroup] and returns the Gid field of the resulting [user.Group] struct.
|
||||||
lookupGroupId(name string) (string, error)
|
lookupGroupId(name string) (string, error)
|
||||||
|
|
||||||
@@ -45,19 +53,15 @@ type syscallDispatcher interface {
|
|||||||
cmdOutput(cmd *exec.Cmd) ([]byte, error)
|
cmdOutput(cmd *exec.Cmd) ([]byte, error)
|
||||||
|
|
||||||
// overflowUid provides [container.OverflowUid].
|
// overflowUid provides [container.OverflowUid].
|
||||||
overflowUid() int
|
overflowUid(msg container.Msg) int
|
||||||
// overflowGid provides [container.OverflowGid].
|
// overflowGid provides [container.OverflowGid].
|
||||||
overflowGid() int
|
overflowGid(msg container.Msg) int
|
||||||
|
|
||||||
// mustHsuPath provides [internal.MustHsuPath].
|
// mustHsuPath provides [internal.MustHsuPath].
|
||||||
mustHsuPath() string
|
mustHsuPath() *check.Absolute
|
||||||
|
|
||||||
// fatalf provides [log.Fatalf].
|
// fatalf provides [log.Fatalf].
|
||||||
fatalf(format string, v ...any)
|
fatalf(format string, v ...any)
|
||||||
|
|
||||||
isVerbose() bool
|
|
||||||
verbose(v ...any)
|
|
||||||
verbosef(format string, v ...any)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// direct implements syscallDispatcher on the current kernel.
|
// direct implements syscallDispatcher on the current kernel.
|
||||||
@@ -69,13 +73,12 @@ 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) }
|
||||||
func (direct) stat(name string) (os.FileInfo, error) { return os.Stat(name) }
|
func (direct) stat(name string) (os.FileInfo, error) { return os.Stat(name) }
|
||||||
|
func (direct) open(name string) (osFile, error) { return os.Open(name) }
|
||||||
func (direct) readdir(name string) ([]os.DirEntry, error) { return os.ReadDir(name) }
|
func (direct) readdir(name string) ([]os.DirEntry, error) { return os.ReadDir(name) }
|
||||||
func (direct) tempdir() string { return os.TempDir() }
|
func (direct) tempdir() string { return os.TempDir() }
|
||||||
|
|
||||||
func (direct) evalSymlinks(path string) (string, error) { return filepath.EvalSymlinks(path) }
|
func (direct) evalSymlinks(path string) (string, error) { return filepath.EvalSymlinks(path) }
|
||||||
|
|
||||||
func (direct) lookPath(file string) (string, error) { return exec.LookPath(file) }
|
|
||||||
|
|
||||||
func (direct) lookupGroupId(name string) (gid string, err error) {
|
func (direct) lookupGroupId(name string) (gid string, err error) {
|
||||||
var group *user.Group
|
var group *user.Group
|
||||||
group, err = user.LookupGroup(name)
|
group, err = user.LookupGroup(name)
|
||||||
@@ -87,13 +90,9 @@ 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() int { return container.OverflowUid() }
|
func (direct) overflowUid(msg container.Msg) int { return container.OverflowUid(msg) }
|
||||||
func (direct) overflowGid() int { return container.OverflowGid() }
|
func (direct) overflowGid(msg container.Msg) int { return container.OverflowGid(msg) }
|
||||||
|
|
||||||
func (direct) mustHsuPath() string { return internal.MustHsuPath() }
|
func (direct) mustHsuPath() *check.Absolute { return internal.MustHsuPath() }
|
||||||
|
|
||||||
func (direct) fatalf(format string, v ...any) { log.Fatalf(format, v...) }
|
func (direct) fatalf(format string, v ...any) { log.Fatalf(format, v...) }
|
||||||
|
|
||||||
func (k direct) isVerbose() bool { return hlog.Load() }
|
|
||||||
func (direct) verbose(v ...any) { hlog.Verbose(v...) }
|
|
||||||
func (direct) verbosef(format string, v ...any) { hlog.Verbosef(format, v...) }
|
|
||||||
|
|||||||
27
internal/app/dispatcher_test.go
Normal file
27
internal/app/dispatcher_test.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
)
|
||||||
|
|
||||||
|
type panicDispatcher struct{}
|
||||||
|
|
||||||
|
func (panicDispatcher) new(func(k syscallDispatcher)) { panic("unreachable") }
|
||||||
|
func (panicDispatcher) getuid() int { panic("unreachable") }
|
||||||
|
func (panicDispatcher) getgid() int { panic("unreachable") }
|
||||||
|
func (panicDispatcher) lookupEnv(string) (string, bool) { panic("unreachable") }
|
||||||
|
func (panicDispatcher) stat(string) (os.FileInfo, error) { panic("unreachable") }
|
||||||
|
func (panicDispatcher) open(string) (osFile, error) { panic("unreachable") }
|
||||||
|
func (panicDispatcher) readdir(string) ([]os.DirEntry, error) { panic("unreachable") }
|
||||||
|
func (panicDispatcher) tempdir() string { panic("unreachable") }
|
||||||
|
func (panicDispatcher) evalSymlinks(string) (string, error) { panic("unreachable") }
|
||||||
|
func (panicDispatcher) lookupGroupId(string) (string, error) { panic("unreachable") }
|
||||||
|
func (panicDispatcher) cmdOutput(*exec.Cmd) ([]byte, error) { panic("unreachable") }
|
||||||
|
func (panicDispatcher) overflowUid(container.Msg) int { panic("unreachable") }
|
||||||
|
func (panicDispatcher) overflowGid(container.Msg) int { panic("unreachable") }
|
||||||
|
func (panicDispatcher) mustHsuPath() *check.Absolute { panic("unreachable") }
|
||||||
|
func (panicDispatcher) fatalf(string, ...any) { panic("unreachable") }
|
||||||
59
internal/app/env.go
Normal file
59
internal/app/env.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
"hakurei.app/hst"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EnvPaths holds paths copied from the environment and is used to create [hst.Paths].
|
||||||
|
type EnvPaths struct {
|
||||||
|
// TempDir is returned by [os.TempDir].
|
||||||
|
TempDir *check.Absolute
|
||||||
|
// RuntimePath is copied from $XDG_RUNTIME_DIR.
|
||||||
|
RuntimePath *check.Absolute
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy expands [EnvPaths] into [hst.Paths].
|
||||||
|
func (env *EnvPaths) Copy(v *hst.Paths, userid int) {
|
||||||
|
if env == nil || env.TempDir == nil || v == nil {
|
||||||
|
panic("attempting to use an invalid EnvPaths")
|
||||||
|
}
|
||||||
|
|
||||||
|
v.TempDir = env.TempDir
|
||||||
|
v.SharePath = env.TempDir.Append("hakurei." + strconv.Itoa(userid))
|
||||||
|
|
||||||
|
if env.RuntimePath == nil {
|
||||||
|
// fall back to path in share since hakurei has no hard XDG dependency
|
||||||
|
v.RunDirPath = v.SharePath.Append("run")
|
||||||
|
v.RuntimePath = v.RunDirPath.Append("compat")
|
||||||
|
} else {
|
||||||
|
v.RuntimePath = env.RuntimePath
|
||||||
|
v.RunDirPath = env.RuntimePath.Append("hakurei")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyPaths returns a populated [EnvPaths].
|
||||||
|
func CopyPaths() *EnvPaths { return copyPaths(direct{}) }
|
||||||
|
|
||||||
|
// copyPaths returns a populated [EnvPaths].
|
||||||
|
func copyPaths(k syscallDispatcher) *EnvPaths {
|
||||||
|
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
||||||
|
|
||||||
|
var env EnvPaths
|
||||||
|
|
||||||
|
if tempDir, err := check.NewAbs(k.tempdir()); err != nil {
|
||||||
|
k.fatalf("invalid TMPDIR: %v", err)
|
||||||
|
panic("unreachable")
|
||||||
|
} else {
|
||||||
|
env.TempDir = tempDir
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _ := k.lookupEnv(xdgRuntimeDir)
|
||||||
|
if a, err := check.NewAbs(r); err == nil {
|
||||||
|
env.RuntimePath = a
|
||||||
|
}
|
||||||
|
|
||||||
|
return &env
|
||||||
|
}
|
||||||
131
internal/app/env_test.go
Normal file
131
internal/app/env_test.go
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
"hakurei.app/container/fhs"
|
||||||
|
"hakurei.app/container/stub"
|
||||||
|
"hakurei.app/hst"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEnvPaths(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
env *EnvPaths
|
||||||
|
want hst.Paths
|
||||||
|
|
||||||
|
wantPanic string
|
||||||
|
}{
|
||||||
|
{"nil", nil, hst.Paths{}, "attempting to use an invalid EnvPaths"},
|
||||||
|
{"zero", new(EnvPaths), hst.Paths{}, "attempting to use an invalid EnvPaths"},
|
||||||
|
|
||||||
|
{"nil tempdir", &EnvPaths{
|
||||||
|
RuntimePath: fhs.AbsTmp,
|
||||||
|
}, hst.Paths{}, "attempting to use an invalid EnvPaths"},
|
||||||
|
|
||||||
|
{"nil runtime", &EnvPaths{
|
||||||
|
TempDir: fhs.AbsTmp,
|
||||||
|
}, hst.Paths{
|
||||||
|
TempDir: fhs.AbsTmp,
|
||||||
|
SharePath: fhs.AbsTmp.Append("hakurei.3735928559"),
|
||||||
|
RuntimePath: fhs.AbsTmp.Append("hakurei.3735928559/run/compat"),
|
||||||
|
RunDirPath: fhs.AbsTmp.Append("hakurei.3735928559/run"),
|
||||||
|
}, ""},
|
||||||
|
|
||||||
|
{"full", &EnvPaths{
|
||||||
|
TempDir: fhs.AbsTmp,
|
||||||
|
RuntimePath: fhs.AbsRunUser.Append("1000"),
|
||||||
|
}, hst.Paths{
|
||||||
|
TempDir: fhs.AbsTmp,
|
||||||
|
SharePath: fhs.AbsTmp.Append("hakurei.3735928559"),
|
||||||
|
RuntimePath: fhs.AbsRunUser.Append("1000"),
|
||||||
|
RunDirPath: fhs.AbsRunUser.Append("1000/hakurei"),
|
||||||
|
}, ""},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if tc.wantPanic != "" {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != tc.wantPanic {
|
||||||
|
t.Errorf("Copy: panic = %#v, want %q", r, tc.wantPanic)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
var sc hst.Paths
|
||||||
|
tc.env.Copy(&sc, 0xdeadbeef)
|
||||||
|
if !reflect.DeepEqual(&sc, &tc.want) {
|
||||||
|
t.Errorf("Copy: %#v, want %#v", sc, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopyPaths(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
env map[string]string
|
||||||
|
tmp string
|
||||||
|
fatal string
|
||||||
|
want EnvPaths
|
||||||
|
}{
|
||||||
|
{"invalid tempdir", nil, "\x00",
|
||||||
|
"invalid TMPDIR: path \"\\x00\" is not absolute", EnvPaths{}},
|
||||||
|
{"empty environment", make(map[string]string), container.Nonexistent,
|
||||||
|
"", EnvPaths{TempDir: check.MustAbs(container.Nonexistent)}},
|
||||||
|
{"invalid XDG_RUNTIME_DIR", map[string]string{"XDG_RUNTIME_DIR": "\x00"}, container.Nonexistent,
|
||||||
|
"", EnvPaths{TempDir: check.MustAbs(container.Nonexistent)}},
|
||||||
|
{"full", map[string]string{"XDG_RUNTIME_DIR": "/\x00"}, container.Nonexistent,
|
||||||
|
"", EnvPaths{TempDir: check.MustAbs(container.Nonexistent), RuntimePath: check.MustAbs("/\x00")}},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if tc.fatal != "" {
|
||||||
|
defer stub.HandleExit(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
k := copyPathsDispatcher{t: t, env: tc.env, tmp: tc.tmp, expectsFatal: tc.fatal}
|
||||||
|
got := copyPaths(k)
|
||||||
|
|
||||||
|
if tc.fatal != "" {
|
||||||
|
t.Fatalf("copyPaths: expected fatal %q", tc.fatal)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, &tc.want) {
|
||||||
|
t.Errorf("copyPaths: %#v, want %#v", got, &tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyPathsDispatcher implements enough of syscallDispatcher for all copyPaths code paths.
|
||||||
|
type copyPathsDispatcher struct {
|
||||||
|
env map[string]string
|
||||||
|
tmp string
|
||||||
|
|
||||||
|
// must be checked at the conclusion of the test
|
||||||
|
expectsFatal string
|
||||||
|
|
||||||
|
t *testing.T
|
||||||
|
panicDispatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k copyPathsDispatcher) tempdir() string { return k.tmp }
|
||||||
|
func (k copyPathsDispatcher) lookupEnv(key string) (value string, ok bool) {
|
||||||
|
value, ok = k.env[key]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (k copyPathsDispatcher) fatalf(format string, v ...any) {
|
||||||
|
if k.expectsFatal == "" {
|
||||||
|
k.t.Fatalf("unexpected call to fatalf: format = %q, v = %#v", format, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := fmt.Sprintf(format, v...); got != k.expectsFatal {
|
||||||
|
k.t.Fatalf("fatalf: %q, want %q", got, k.expectsFatal)
|
||||||
|
}
|
||||||
|
panic(stub.PanicExit)
|
||||||
|
}
|
||||||
@@ -7,24 +7,14 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"slices"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/app/state"
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/internal/hlog"
|
|
||||||
"hakurei.app/system"
|
"hakurei.app/system"
|
||||||
"hakurei.app/system/acl"
|
|
||||||
"hakurei.app/system/dbus"
|
|
||||||
"hakurei.app/system/wayland"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func newWithMessage(msg string) error { return newWithMessageError(msg, os.ErrInvalid) }
|
func newWithMessage(msg string) error { return newWithMessageError(msg, os.ErrInvalid) }
|
||||||
@@ -34,129 +24,40 @@ 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 {
|
||||||
// copied from initialising [app]
|
|
||||||
id *stringPair[state.ID]
|
|
||||||
// copied from [sys.State]
|
|
||||||
runDirPath *container.Absolute
|
|
||||||
|
|
||||||
// initial [hst.Config] gob stream for state data;
|
// initial [hst.Config] gob stream for state data;
|
||||||
// this is prepared ahead of time as config is clobbered during seal creation
|
// this is prepared ahead of time as config is clobbered during seal creation
|
||||||
ct io.WriterTo
|
ct io.WriterTo
|
||||||
// dump dbus proxy message buffer
|
|
||||||
dbusMsg func()
|
|
||||||
|
|
||||||
user hsuUser
|
// Supplementary group ids. Populated during finalise.
|
||||||
sys *system.I
|
supp []string
|
||||||
ctx context.Context
|
// Resolved priv side operating system interactions. Populated during finalise.
|
||||||
|
sys *system.I
|
||||||
|
// Transmitted to shim. Populated during finalise.
|
||||||
|
state *outcomeState
|
||||||
|
|
||||||
waitDelay time.Duration
|
// Whether the current process is in outcome.main.
|
||||||
container *container.Params
|
active atomic.Bool
|
||||||
env map[string]string
|
|
||||||
sync *os.File
|
|
||||||
active atomic.Bool
|
|
||||||
|
|
||||||
|
ctx context.Context
|
||||||
syscallDispatcher
|
syscallDispatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
// shareHost holds optional share directory state that must not be accessed directly
|
func (k *outcome) finalise(ctx context.Context, msg container.Msg, id *state.ID, config *hst.Config) error {
|
||||||
type shareHost struct {
|
if ctx == nil || id == nil {
|
||||||
// whether XDG_RUNTIME_DIR is used post hsu
|
|
||||||
useRuntimeDir bool
|
|
||||||
// process-specific directory in tmpdir, empty if unused
|
|
||||||
sharePath *container.Absolute
|
|
||||||
// process-specific directory in XDG_RUNTIME_DIR, empty if unused
|
|
||||||
runtimeSharePath *container.Absolute
|
|
||||||
|
|
||||||
seal *outcome
|
|
||||||
sc hst.Paths
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensureRuntimeDir must be called if direct access to paths within XDG_RUNTIME_DIR is required
|
|
||||||
func (share *shareHost) ensureRuntimeDir() {
|
|
||||||
if share.useRuntimeDir {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
share.useRuntimeDir = true
|
|
||||||
share.seal.sys.Ensure(share.sc.RunDirPath.String(), 0700)
|
|
||||||
share.seal.sys.UpdatePermType(system.User, share.sc.RunDirPath.String(), acl.Execute)
|
|
||||||
share.seal.sys.Ensure(share.sc.RuntimePath.String(), 0700) // ensure this dir in case XDG_RUNTIME_DIR is unset
|
|
||||||
share.seal.sys.UpdatePermType(system.User, share.sc.RuntimePath.String(), acl.Execute)
|
|
||||||
}
|
|
||||||
|
|
||||||
// instance returns a process-specific share path within tmpdir
|
|
||||||
func (share *shareHost) instance() *container.Absolute {
|
|
||||||
if share.sharePath != nil {
|
|
||||||
return share.sharePath
|
|
||||||
}
|
|
||||||
share.sharePath = share.sc.SharePath.Append(share.seal.id.String())
|
|
||||||
share.seal.sys.Ephemeral(system.Process, share.sharePath.String(), 0711)
|
|
||||||
return share.sharePath
|
|
||||||
}
|
|
||||||
|
|
||||||
// runtime returns a process-specific share path within XDG_RUNTIME_DIR
|
|
||||||
func (share *shareHost) runtime() *container.Absolute {
|
|
||||||
if share.runtimeSharePath != nil {
|
|
||||||
return share.runtimeSharePath
|
|
||||||
}
|
|
||||||
share.ensureRuntimeDir()
|
|
||||||
share.runtimeSharePath = share.sc.RunDirPath.Append(share.seal.id.String())
|
|
||||||
share.seal.sys.Ephemeral(system.Process, share.runtimeSharePath.String(), 0700)
|
|
||||||
share.seal.sys.UpdatePerm(share.runtimeSharePath.String(), acl.Execute)
|
|
||||||
return share.runtimeSharePath
|
|
||||||
}
|
|
||||||
|
|
||||||
// hsuUser stores post-hsu credentials and metadata
|
|
||||||
type hsuUser struct {
|
|
||||||
identity *stringPair[int]
|
|
||||||
// target uid resolved by hid:aid
|
|
||||||
uid *stringPair[int]
|
|
||||||
|
|
||||||
// supplementary group ids
|
|
||||||
supp []string
|
|
||||||
|
|
||||||
// app user home directory
|
|
||||||
home *container.Absolute
|
|
||||||
// passwd database username
|
|
||||||
username string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
|
|
||||||
const (
|
|
||||||
home = "HOME"
|
|
||||||
shell = "SHELL"
|
|
||||||
|
|
||||||
xdgConfigHome = "XDG_CONFIG_HOME"
|
|
||||||
xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
|
||||||
xdgSessionClass = "XDG_SESSION_CLASS"
|
|
||||||
xdgSessionType = "XDG_SESSION_TYPE"
|
|
||||||
|
|
||||||
term = "TERM"
|
|
||||||
display = "DISPLAY"
|
|
||||||
|
|
||||||
pulseServer = "PULSE_SERVER"
|
|
||||||
pulseCookie = "PULSE_COOKIE"
|
|
||||||
|
|
||||||
dbusSessionBusAddress = "DBUS_SESSION_BUS_ADDRESS"
|
|
||||||
dbusSystemBusAddress = "DBUS_SYSTEM_BUS_ADDRESS"
|
|
||||||
)
|
|
||||||
|
|
||||||
if ctx == nil {
|
|
||||||
// unreachable
|
// unreachable
|
||||||
panic("invalid call to finalise")
|
panic("invalid call to finalise")
|
||||||
}
|
}
|
||||||
if k.ctx != nil {
|
if k.ctx != nil || k.sys != nil || k.state != nil {
|
||||||
// unreachable
|
// unreachable
|
||||||
panic("attempting to finalise twice")
|
panic("attempting to finalise twice")
|
||||||
}
|
}
|
||||||
k.ctx = ctx
|
k.ctx = ctx
|
||||||
|
|
||||||
if config == nil {
|
if err := config.Validate(); err != nil {
|
||||||
return newWithMessage("invalid configuration")
|
return err
|
||||||
}
|
|
||||||
if config.Home == nil {
|
|
||||||
return newWithMessage("invalid path to home directory")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(ophestra): do not clobber during finalise
|
||||||
{
|
{
|
||||||
// encode initial configuration for state tracking
|
// encode initial configuration for state tracking
|
||||||
ct := new(bytes.Buffer)
|
ct := new(bytes.Buffer)
|
||||||
@@ -166,26 +67,8 @@ func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
|
|||||||
k.ct = ct
|
k.ct = ct
|
||||||
}
|
}
|
||||||
|
|
||||||
// allowed identity range 0 to 9999, this is checked again in hsu
|
// hsu expects numerical group ids
|
||||||
if config.Identity < 0 || config.Identity > 9999 {
|
supp := make([]string, len(config.Groups))
|
||||||
return newWithMessage(fmt.Sprintf("identity %d out of range", config.Identity))
|
|
||||||
}
|
|
||||||
|
|
||||||
k.user = hsuUser{
|
|
||||||
identity: newInt(config.Identity),
|
|
||||||
home: config.Home,
|
|
||||||
username: config.Username,
|
|
||||||
}
|
|
||||||
|
|
||||||
hsu := Hsu{k: k}
|
|
||||||
if k.user.username == "" {
|
|
||||||
k.user.username = "chronos"
|
|
||||||
} else if !isValidUsername(k.user.username) {
|
|
||||||
return newWithMessage(fmt.Sprintf("invalid user name %q", k.user.username))
|
|
||||||
}
|
|
||||||
k.user.uid = newInt(HsuUid(hsu.MustID(), k.user.identity.unwrap()))
|
|
||||||
|
|
||||||
k.user.supp = make([]string, len(config.Groups))
|
|
||||||
for i, name := range config.Groups {
|
for i, name := range config.Groups {
|
||||||
if gid, err := k.lookupGroupId(name); err != nil {
|
if gid, err := k.lookupGroupId(name); err != nil {
|
||||||
var unknownGroupError user.UnknownGroupError
|
var unknownGroupError user.UnknownGroupError
|
||||||
@@ -195,411 +78,33 @@ func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
|
|||||||
return &hst.AppError{Step: "look up group by name", Err: err}
|
return &hst.AppError{Step: "look up group by name", Err: err}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
k.user.supp[i] = gid
|
supp[i] = gid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// permissive defaults
|
// early validation complete at this point
|
||||||
if config.Container == nil {
|
s := outcomeState{
|
||||||
hlog.Verbose("container configuration not supplied, PROCEED WITH CAUTION")
|
ID: id,
|
||||||
|
Identity: config.Identity,
|
||||||
if config.Shell == nil {
|
UserID: (&Hsu{k: k}).MustIDMsg(msg),
|
||||||
config.Shell = container.AbsFHSRoot.Append("bin", "sh")
|
EnvPaths: copyPaths(k.syscallDispatcher),
|
||||||
s, _ := k.lookupEnv(shell)
|
Container: config.Container,
|
||||||
if a, err := container.NewAbs(s); err == nil {
|
}
|
||||||
config.Shell = a
|
s.populateEarly(k.syscallDispatcher, msg, config)
|
||||||
}
|
if err := s.populateLocal(k.syscallDispatcher, msg); err != nil {
|
||||||
}
|
return err
|
||||||
|
|
||||||
// hsu clears the environment so resolve paths early
|
|
||||||
if config.Path == nil {
|
|
||||||
if len(config.Args) > 0 {
|
|
||||||
if p, err := k.lookPath(config.Args[0]); err != nil {
|
|
||||||
return &hst.AppError{Step: "look up executable file", Err: err}
|
|
||||||
} else if config.Path, err = container.NewAbs(p); err != nil {
|
|
||||||
return newWithMessageError(err.Error(), err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
config.Path = config.Shell
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
conf := &hst.ContainerConfig{
|
|
||||||
Userns: true,
|
|
||||||
HostNet: true,
|
|
||||||
HostAbstract: true,
|
|
||||||
Tty: true,
|
|
||||||
|
|
||||||
Filesystem: []hst.FilesystemConfigJSON{
|
|
||||||
// autoroot, includes the home directory
|
|
||||||
{FilesystemConfig: &hst.FSBind{
|
|
||||||
Target: container.AbsFHSRoot,
|
|
||||||
Source: container.AbsFHSRoot,
|
|
||||||
Write: true,
|
|
||||||
Special: true,
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// bind GPU stuff
|
|
||||||
if config.Enablements.Unwrap()&(system.EX11|system.EWayland) != 0 {
|
|
||||||
conf.Filesystem = append(conf.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("dri"), Device: true, Optional: true}})
|
|
||||||
}
|
|
||||||
// opportunistically bind kvm
|
|
||||||
conf.Filesystem = append(conf.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("kvm"), Device: true, Optional: true}})
|
|
||||||
|
|
||||||
// hide nscd from container if present
|
|
||||||
nscd := container.AbsFHSVar.Append("run/nscd")
|
|
||||||
if _, err := k.stat(nscd.String()); !errors.Is(err, fs.ErrNotExist) {
|
|
||||||
conf.Filesystem = append(conf.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSEphemeral{Target: nscd}})
|
|
||||||
}
|
|
||||||
|
|
||||||
// do autoetc last
|
|
||||||
conf.Filesystem = append(conf.Filesystem,
|
|
||||||
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
|
|
||||||
Target: container.AbsFHSEtc,
|
|
||||||
Source: container.AbsFHSEtc,
|
|
||||||
Special: true,
|
|
||||||
}},
|
|
||||||
)
|
|
||||||
|
|
||||||
config.Container = conf
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// late nil checks for pd behaviour
|
sys := system.New(k.ctx, msg, s.uid.unwrap())
|
||||||
if config.Shell == nil {
|
stateSys := outcomeStateSys{sys: sys, outcomeState: &s}
|
||||||
return newWithMessage("invalid shell path")
|
for _, op := range s.Shim.Ops {
|
||||||
}
|
if err := op.toSystem(&stateSys, config); err != nil {
|
||||||
if config.Path == nil {
|
|
||||||
return newWithMessage("invalid program path")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(ophestra): revert this after params to shim
|
|
||||||
share := &shareHost{seal: k}
|
|
||||||
copyPaths(k.syscallDispatcher, &share.sc, hsu.MustID())
|
|
||||||
|
|
||||||
var mapuid, mapgid *stringPair[int]
|
|
||||||
{
|
|
||||||
var uid, gid int
|
|
||||||
var err error
|
|
||||||
k.container, k.env, err = newContainer(k, config.Container, k.id.String(), &share.sc, &uid, &gid)
|
|
||||||
k.waitDelay = config.Container.WaitDelay
|
|
||||||
if err != nil {
|
|
||||||
return &hst.AppError{Step: "initialise container configuration", Err: err}
|
|
||||||
}
|
|
||||||
if len(config.Args) == 0 {
|
|
||||||
config.Args = []string{config.Path.String()}
|
|
||||||
}
|
|
||||||
k.container.Path = config.Path
|
|
||||||
k.container.Args = config.Args
|
|
||||||
|
|
||||||
mapuid = newInt(uid)
|
|
||||||
mapgid = newInt(gid)
|
|
||||||
if k.env == nil {
|
|
||||||
k.env = make(map[string]string, 1<<6)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// inner XDG_RUNTIME_DIR default formatting of `/run/user/%d` as mapped uid
|
|
||||||
innerRuntimeDir := container.AbsFHSRunUser.Append(mapuid.String())
|
|
||||||
k.env[xdgRuntimeDir] = innerRuntimeDir.String()
|
|
||||||
k.env[xdgSessionClass] = "user"
|
|
||||||
k.env[xdgSessionType] = "tty"
|
|
||||||
|
|
||||||
k.runDirPath = share.sc.RunDirPath
|
|
||||||
k.sys = system.New(k.ctx, k.user.uid.unwrap())
|
|
||||||
k.sys.Ensure(share.sc.SharePath.String(), 0711)
|
|
||||||
|
|
||||||
{
|
|
||||||
runtimeDir := share.sc.SharePath.Append("runtime")
|
|
||||||
k.sys.Ensure(runtimeDir.String(), 0700)
|
|
||||||
k.sys.UpdatePermType(system.User, runtimeDir.String(), acl.Execute)
|
|
||||||
runtimeDirInst := runtimeDir.Append(k.user.identity.String())
|
|
||||||
k.sys.Ensure(runtimeDirInst.String(), 0700)
|
|
||||||
k.sys.UpdatePermType(system.User, runtimeDirInst.String(), acl.Read, acl.Write, acl.Execute)
|
|
||||||
k.container.Tmpfs(container.AbsFHSRunUser, 1<<12, 0755)
|
|
||||||
k.container.Bind(runtimeDirInst, innerRuntimeDir, container.BindWritable)
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
tmpdir := share.sc.SharePath.Append("tmpdir")
|
|
||||||
k.sys.Ensure(tmpdir.String(), 0700)
|
|
||||||
k.sys.UpdatePermType(system.User, tmpdir.String(), acl.Execute)
|
|
||||||
tmpdirInst := tmpdir.Append(k.user.identity.String())
|
|
||||||
k.sys.Ensure(tmpdirInst.String(), 01700)
|
|
||||||
k.sys.UpdatePermType(system.User, tmpdirInst.String(), acl.Read, acl.Write, acl.Execute)
|
|
||||||
// mount inner /tmp from share so it shares persistence and storage behaviour of host /tmp
|
|
||||||
k.container.Bind(tmpdirInst, container.AbsFHSTmp, container.BindWritable)
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
username := "chronos"
|
|
||||||
if k.user.username != "" {
|
|
||||||
username = k.user.username
|
|
||||||
}
|
|
||||||
k.container.Dir = k.user.home
|
|
||||||
k.env["HOME"] = k.user.home.String()
|
|
||||||
k.env["USER"] = username
|
|
||||||
k.env[shell] = config.Shell.String()
|
|
||||||
|
|
||||||
k.container.Place(container.AbsFHSEtc.Append("passwd"),
|
|
||||||
[]byte(username+":x:"+mapuid.String()+":"+mapgid.String()+":Hakurei:"+k.user.home.String()+":"+config.Shell.String()+"\n"))
|
|
||||||
k.container.Place(container.AbsFHSEtc.Append("group"),
|
|
||||||
[]byte("hakurei:x:"+mapgid.String()+":\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// pass TERM for proper terminal I/O in initial process
|
|
||||||
if t, ok := k.lookupEnv(term); ok {
|
|
||||||
k.env[term] = t
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Enablements.Unwrap()&system.EWayland != 0 {
|
|
||||||
// outer wayland socket (usually `/run/user/%d/wayland-%d`)
|
|
||||||
var socketPath *container.Absolute
|
|
||||||
if name, ok := k.lookupEnv(wayland.WaylandDisplay); !ok {
|
|
||||||
hlog.Verbose(wayland.WaylandDisplay + " is not set, assuming " + wayland.FallbackName)
|
|
||||||
socketPath = share.sc.RuntimePath.Append(wayland.FallbackName)
|
|
||||||
} else if a, err := container.NewAbs(name); err != nil {
|
|
||||||
socketPath = share.sc.RuntimePath.Append(name)
|
|
||||||
} else {
|
|
||||||
socketPath = a
|
|
||||||
}
|
|
||||||
|
|
||||||
innerPath := innerRuntimeDir.Append(wayland.FallbackName)
|
|
||||||
k.env[wayland.WaylandDisplay] = wayland.FallbackName
|
|
||||||
|
|
||||||
if !config.DirectWayland { // set up security-context-v1
|
|
||||||
appID := config.ID
|
|
||||||
if appID == "" {
|
|
||||||
// use instance ID in case app id is not set
|
|
||||||
appID = "app.hakurei." + k.id.String()
|
|
||||||
}
|
|
||||||
// downstream socket paths
|
|
||||||
outerPath := share.instance().Append("wayland")
|
|
||||||
k.sys.Wayland(&k.sync, outerPath.String(), socketPath.String(), appID, k.id.String())
|
|
||||||
k.container.Bind(outerPath, innerPath, 0)
|
|
||||||
} else { // bind mount wayland socket (insecure)
|
|
||||||
hlog.Verbose("direct wayland access, PROCEED WITH CAUTION")
|
|
||||||
share.ensureRuntimeDir()
|
|
||||||
k.container.Bind(socketPath, innerPath, 0)
|
|
||||||
k.sys.UpdatePermType(system.EWayland, socketPath.String(), acl.Read, acl.Write, acl.Execute)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Enablements.Unwrap()&system.EX11 != 0 {
|
|
||||||
if d, ok := k.lookupEnv(display); !ok {
|
|
||||||
return newWithMessage("DISPLAY is not set")
|
|
||||||
} else {
|
|
||||||
socketDir := container.AbsFHSTmp.Append(".X11-unix")
|
|
||||||
|
|
||||||
// the socket file at `/tmp/.X11-unix/X%d` is typically owned by the priv user
|
|
||||||
// and not accessible by the target user
|
|
||||||
var socketPath *container.Absolute
|
|
||||||
if len(d) > 1 && d[0] == ':' { // `:%d`
|
|
||||||
if n, err := strconv.Atoi(d[1:]); err == nil && n >= 0 {
|
|
||||||
socketPath = socketDir.Append("X" + strconv.Itoa(n))
|
|
||||||
}
|
|
||||||
} else if len(d) > 5 && strings.HasPrefix(d, "unix:") { // `unix:%s`
|
|
||||||
if a, err := container.NewAbs(d[5:]); err == nil {
|
|
||||||
socketPath = a
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if socketPath != nil {
|
|
||||||
if _, err := k.stat(socketPath.String()); err != nil {
|
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
|
||||||
return &hst.AppError{Step: fmt.Sprintf("access X11 socket %q", socketPath), Err: err}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
k.sys.UpdatePermType(system.EX11, socketPath.String(), acl.Read, acl.Write, acl.Execute)
|
|
||||||
if !config.Container.HostAbstract {
|
|
||||||
d = "unix:" + socketPath.String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
k.sys.ChangeHosts("#" + k.user.uid.String())
|
|
||||||
k.env[display] = d
|
|
||||||
k.container.Bind(socketDir, socketDir, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Enablements.Unwrap()&system.EPulse != 0 {
|
|
||||||
// PulseAudio runtime directory (usually `/run/user/%d/pulse`)
|
|
||||||
pulseRuntimeDir := share.sc.RuntimePath.Append("pulse")
|
|
||||||
// PulseAudio socket (usually `/run/user/%d/pulse/native`)
|
|
||||||
pulseSocket := pulseRuntimeDir.Append("native")
|
|
||||||
|
|
||||||
if _, err := k.stat(pulseRuntimeDir.String()); err != nil {
|
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
|
||||||
return &hst.AppError{Step: fmt.Sprintf("access PulseAudio directory %q", pulseRuntimeDir), Err: err}
|
|
||||||
}
|
|
||||||
return newWithMessage(fmt.Sprintf("PulseAudio directory %q not found", pulseRuntimeDir))
|
|
||||||
}
|
|
||||||
|
|
||||||
if s, err := k.stat(pulseSocket.String()); err != nil {
|
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
|
||||||
return &hst.AppError{Step: fmt.Sprintf("access PulseAudio socket %q", pulseSocket), Err: err}
|
|
||||||
}
|
|
||||||
return newWithMessage(fmt.Sprintf("PulseAudio directory %q found but socket does not exist", pulseRuntimeDir))
|
|
||||||
} else {
|
|
||||||
if m := s.Mode(); m&0o006 != 0o006 {
|
|
||||||
return newWithMessage(fmt.Sprintf("unexpected permissions on %q: %s", pulseSocket, m))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// hard link pulse socket into target-executable share
|
|
||||||
innerPulseRuntimeDir := share.runtime().Append("pulse")
|
|
||||||
innerPulseSocket := innerRuntimeDir.Append("pulse", "native")
|
|
||||||
k.sys.Link(pulseSocket.String(), innerPulseRuntimeDir.String())
|
|
||||||
k.container.Bind(innerPulseRuntimeDir, innerPulseSocket, 0)
|
|
||||||
k.env[pulseServer] = "unix:" + innerPulseSocket.String()
|
|
||||||
|
|
||||||
// publish current user's pulse cookie for target user
|
|
||||||
var paCookiePath *container.Absolute
|
|
||||||
{
|
|
||||||
const paLocateStep = "locate PulseAudio cookie"
|
|
||||||
|
|
||||||
// from environment
|
|
||||||
if p, ok := k.lookupEnv(pulseCookie); ok {
|
|
||||||
if a, err := container.NewAbs(p); err != nil {
|
|
||||||
return &hst.AppError{Step: paLocateStep, Err: err}
|
|
||||||
} else {
|
|
||||||
// this takes precedence, do not verify whether the file is accessible
|
|
||||||
paCookiePath = a
|
|
||||||
goto out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// $HOME/.pulse-cookie
|
|
||||||
if p, ok := k.lookupEnv(home); ok {
|
|
||||||
if a, err := container.NewAbs(p); err != nil {
|
|
||||||
return &hst.AppError{Step: paLocateStep, Err: err}
|
|
||||||
} else {
|
|
||||||
paCookiePath = a.Append(".pulse-cookie")
|
|
||||||
}
|
|
||||||
|
|
||||||
if s, err := k.stat(paCookiePath.String()); err != nil {
|
|
||||||
paCookiePath = nil
|
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
|
||||||
return &hst.AppError{Step: "access PulseAudio cookie", Err: err}
|
|
||||||
}
|
|
||||||
// fallthrough
|
|
||||||
} else if s.IsDir() {
|
|
||||||
paCookiePath = nil
|
|
||||||
} else {
|
|
||||||
goto out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// $XDG_CONFIG_HOME/pulse/cookie
|
|
||||||
if p, ok := k.lookupEnv(xdgConfigHome); ok {
|
|
||||||
if a, err := container.NewAbs(p); err != nil {
|
|
||||||
return &hst.AppError{Step: paLocateStep, Err: err}
|
|
||||||
} else {
|
|
||||||
paCookiePath = a.Append("pulse", "cookie")
|
|
||||||
}
|
|
||||||
if s, err := k.stat(paCookiePath.String()); err != nil {
|
|
||||||
paCookiePath = nil
|
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
|
||||||
return &hst.AppError{Step: "access PulseAudio cookie", Err: err}
|
|
||||||
}
|
|
||||||
// fallthrough
|
|
||||||
} else if s.IsDir() {
|
|
||||||
paCookiePath = nil
|
|
||||||
} else {
|
|
||||||
goto out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out:
|
|
||||||
}
|
|
||||||
|
|
||||||
if paCookiePath != nil {
|
|
||||||
innerDst := hst.AbsTmp.Append("/pulse-cookie")
|
|
||||||
k.env[pulseCookie] = innerDst.String()
|
|
||||||
var payload *[]byte
|
|
||||||
k.container.PlaceP(innerDst, &payload)
|
|
||||||
k.sys.CopyFile(payload, paCookiePath.String(), 256, 256)
|
|
||||||
} else {
|
|
||||||
hlog.Verbose("cannot locate PulseAudio cookie (tried " +
|
|
||||||
"$PULSE_COOKIE, " +
|
|
||||||
"$XDG_CONFIG_HOME/pulse/cookie, " +
|
|
||||||
"$HOME/.pulse-cookie)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Enablements.Unwrap()&system.EDBus != 0 {
|
|
||||||
// ensure dbus session bus defaults
|
|
||||||
if config.SessionBus == nil {
|
|
||||||
config.SessionBus = dbus.NewConfig(config.ID, true, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// downstream socket paths
|
|
||||||
sessionPath, systemPath := share.instance().Append("bus"), share.instance().Append("system_bus_socket")
|
|
||||||
|
|
||||||
// configure dbus proxy
|
|
||||||
if f, err := k.sys.ProxyDBus(
|
|
||||||
config.SessionBus, config.SystemBus,
|
|
||||||
sessionPath.String(), systemPath.String(),
|
|
||||||
); err != nil {
|
|
||||||
return err
|
return err
|
||||||
} else {
|
|
||||||
k.dbusMsg = f
|
|
||||||
}
|
|
||||||
|
|
||||||
// share proxy sockets
|
|
||||||
sessionInner := innerRuntimeDir.Append("bus")
|
|
||||||
k.env[dbusSessionBusAddress] = "unix:path=" + sessionInner.String()
|
|
||||||
k.container.Bind(sessionPath, sessionInner, 0)
|
|
||||||
k.sys.UpdatePerm(sessionPath.String(), acl.Read, acl.Write)
|
|
||||||
if config.SystemBus != nil {
|
|
||||||
systemInner := container.AbsFHSRun.Append("dbus/system_bus_socket")
|
|
||||||
k.env[dbusSystemBusAddress] = "unix:path=" + systemInner.String()
|
|
||||||
k.container.Bind(systemPath, systemInner, 0)
|
|
||||||
k.sys.UpdatePerm(systemPath.String(), acl.Read, acl.Write)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// mount root read-only as the final setup Op
|
k.sys = sys
|
||||||
k.container.Remount(container.AbsFHSRoot, syscall.MS_RDONLY)
|
k.supp = supp
|
||||||
|
k.state = &s
|
||||||
// append ExtraPerms last
|
|
||||||
for _, p := range config.ExtraPerms {
|
|
||||||
if p == nil || p.Path == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.Ensure {
|
|
||||||
k.sys.Ensure(p.Path.String(), 0700)
|
|
||||||
}
|
|
||||||
|
|
||||||
perms := make(acl.Perms, 0, 3)
|
|
||||||
if p.Read {
|
|
||||||
perms = append(perms, acl.Read)
|
|
||||||
}
|
|
||||||
if p.Write {
|
|
||||||
perms = append(perms, acl.Write)
|
|
||||||
}
|
|
||||||
if p.Execute {
|
|
||||||
perms = append(perms, acl.Execute)
|
|
||||||
}
|
|
||||||
k.sys.UpdatePermType(system.User, p.Path.String(), perms...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// flatten and sort env for deterministic behaviour
|
|
||||||
k.container.Env = make([]string, 0, len(k.env))
|
|
||||||
for key, value := range k.env {
|
|
||||||
if strings.IndexByte(key, '=') != -1 {
|
|
||||||
return &hst.AppError{Step: "flatten environment", Err: syscall.EINVAL,
|
|
||||||
Msg: fmt.Sprintf("invalid environment variable %s", key)}
|
|
||||||
}
|
|
||||||
k.container.Env = append(k.container.Env, key+"="+value)
|
|
||||||
}
|
|
||||||
slices.Sort(k.container.Env)
|
|
||||||
|
|
||||||
if hlog.Load() {
|
|
||||||
hlog.Verbosef("created application seal for uid %s (%s) groups: %v, argv: %s, ops: %d",
|
|
||||||
k.user.uid, k.user.username, config.Groups, k.container.Args, len(*k.container.Ops))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/container/fhs"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/hlog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Hsu caches responses from cmd/hsu.
|
// Hsu caches responses from cmd/hsu.
|
||||||
@@ -40,13 +40,13 @@ func (h *Hsu) ID() (int, error) {
|
|||||||
h.ensureDispatcher()
|
h.ensureDispatcher()
|
||||||
h.idOnce.Do(func() {
|
h.idOnce.Do(func() {
|
||||||
h.id = -1
|
h.id = -1
|
||||||
hsuPath := h.k.mustHsuPath()
|
hsuPath := h.k.mustHsuPath().String()
|
||||||
|
|
||||||
cmd := exec.Command(hsuPath)
|
cmd := exec.Command(hsuPath)
|
||||||
cmd.Path = hsuPath
|
cmd.Path = hsuPath
|
||||||
cmd.Stderr = os.Stderr // pass through fatal messages
|
cmd.Stderr = os.Stderr // pass through fatal messages
|
||||||
cmd.Env = make([]string, 0)
|
cmd.Env = make([]string, 0)
|
||||||
cmd.Dir = container.FHSRoot
|
cmd.Dir = fhs.Root
|
||||||
var (
|
var (
|
||||||
p []byte
|
p []byte
|
||||||
exitError *exec.ExitError
|
exitError *exec.ExitError
|
||||||
@@ -71,7 +71,10 @@ func (h *Hsu) ID() (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MustID calls [Hsu.ID] and terminates on error.
|
// MustID calls [Hsu.ID] and terminates on error.
|
||||||
func (h *Hsu) MustID() int {
|
func (h *Hsu) MustID() int { return h.MustIDMsg(nil) }
|
||||||
|
|
||||||
|
// MustIDMsg implements MustID with a custom [container.Msg].
|
||||||
|
func (h *Hsu) MustIDMsg(msg container.Msg) int {
|
||||||
id, err := h.ID()
|
id, err := h.ID()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return id
|
return id
|
||||||
@@ -79,7 +82,9 @@ func (h *Hsu) MustID() int {
|
|||||||
|
|
||||||
const fallback = "cannot retrieve user id from setuid wrapper:"
|
const fallback = "cannot retrieve user id from setuid wrapper:"
|
||||||
if errors.Is(err, ErrHsuAccess) {
|
if errors.Is(err, ErrHsuAccess) {
|
||||||
hlog.Verbose("*"+fallback, err)
|
if msg != nil {
|
||||||
|
msg.Verbose("*"+fallback, err)
|
||||||
|
}
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
return -0xdeadbeef
|
return -0xdeadbeef
|
||||||
} else if m, ok := container.GetErrorMessage(err); ok {
|
} else if m, ok := container.GetErrorMessage(err); ok {
|
||||||
|
|||||||
248
internal/app/outcome.go
Normal file
248
internal/app/outcome.go
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/internal/app/state"
|
||||||
|
"hakurei.app/system"
|
||||||
|
"hakurei.app/system/acl"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newInt(v int) *stringPair[int] { return &stringPair[int]{v, strconv.Itoa(v)} }
|
||||||
|
|
||||||
|
// stringPair stores a value and its string representation.
|
||||||
|
type stringPair[T comparable] struct {
|
||||||
|
v T
|
||||||
|
s string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringPair[T]) unwrap() T { return s.v }
|
||||||
|
func (s *stringPair[T]) String() string { return s.s }
|
||||||
|
|
||||||
|
// outcomeState is copied to the shim process and available while applying outcomeOp.
|
||||||
|
// This is transmitted from the priv side to the shim, so exported fields should be kept to a minimum.
|
||||||
|
type outcomeState struct {
|
||||||
|
// Params only used by the shim process. Populated by populateEarly.
|
||||||
|
Shim *shimParams
|
||||||
|
|
||||||
|
// Generated and accounted for by the caller.
|
||||||
|
ID *state.ID
|
||||||
|
// Copied from ID.
|
||||||
|
id *stringPair[state.ID]
|
||||||
|
|
||||||
|
// Copied from the [hst.Config] field of the same name.
|
||||||
|
Identity int
|
||||||
|
// Copied from Identity.
|
||||||
|
identity *stringPair[int]
|
||||||
|
// Returned by [Hsu.MustIDMsg].
|
||||||
|
UserID int
|
||||||
|
// Target init namespace uid resolved from UserID and identity.
|
||||||
|
uid *stringPair[int]
|
||||||
|
|
||||||
|
// Included as part of [hst.Config], transmitted as-is unless permissive defaults.
|
||||||
|
Container *hst.ContainerConfig
|
||||||
|
|
||||||
|
// Mapped credentials within container user namespace.
|
||||||
|
Mapuid, Mapgid int
|
||||||
|
// Copied from their respective exported values.
|
||||||
|
mapuid, mapgid *stringPair[int]
|
||||||
|
|
||||||
|
// Copied from [EnvPaths] per-process.
|
||||||
|
sc hst.Paths
|
||||||
|
*EnvPaths
|
||||||
|
|
||||||
|
// Matched paths to cover. Populated by spFilesystemOp.
|
||||||
|
HidePaths []*check.Absolute
|
||||||
|
|
||||||
|
// Copied via populateLocal.
|
||||||
|
k syscallDispatcher
|
||||||
|
// Copied via populateLocal.
|
||||||
|
msg container.Msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// valid checks outcomeState to be safe for use with outcomeOp.
|
||||||
|
func (s *outcomeState) valid() bool {
|
||||||
|
return s != nil &&
|
||||||
|
s.Shim.valid() &&
|
||||||
|
s.ID != nil &&
|
||||||
|
s.Container != nil &&
|
||||||
|
s.EnvPaths != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// populateEarly populates exported fields via syscallDispatcher.
|
||||||
|
// This must only be called from the priv side.
|
||||||
|
func (s *outcomeState) populateEarly(k syscallDispatcher, msg container.Msg, config *hst.Config) {
|
||||||
|
s.Shim = &shimParams{PrivPID: os.Getpid(), Verbose: msg.IsVerbose(), Ops: fromConfig(config)}
|
||||||
|
|
||||||
|
// enforce bounds and default early
|
||||||
|
if s.Container.WaitDelay <= 0 {
|
||||||
|
s.Shim.WaitDelay = hst.WaitDelayDefault
|
||||||
|
} else if s.Container.WaitDelay > hst.WaitDelayMax {
|
||||||
|
s.Shim.WaitDelay = hst.WaitDelayMax
|
||||||
|
} else {
|
||||||
|
s.Shim.WaitDelay = s.Container.WaitDelay
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Container.MapRealUID {
|
||||||
|
s.Mapuid, s.Mapgid = k.getuid(), k.getgid()
|
||||||
|
} else {
|
||||||
|
s.Mapuid, s.Mapgid = k.overflowUid(msg), k.overflowGid(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// populateLocal populates unexported fields from transmitted exported fields.
|
||||||
|
// These fields are cheaper to recompute per-process.
|
||||||
|
func (s *outcomeState) populateLocal(k syscallDispatcher, msg container.Msg) error {
|
||||||
|
if !s.valid() || k == nil || msg == nil {
|
||||||
|
return newWithMessage("impossible outcome state reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.k != nil || s.msg != nil {
|
||||||
|
panic("attempting to call populateLocal twice")
|
||||||
|
}
|
||||||
|
s.k = k
|
||||||
|
s.msg = msg
|
||||||
|
|
||||||
|
s.id = &stringPair[state.ID]{*s.ID, s.ID.String()}
|
||||||
|
|
||||||
|
s.Copy(&s.sc, s.UserID)
|
||||||
|
msg.Verbosef("process share directory at %q, runtime directory at %q", s.sc.SharePath, s.sc.RunDirPath)
|
||||||
|
|
||||||
|
s.identity = newInt(s.Identity)
|
||||||
|
s.mapuid, s.mapgid = newInt(s.Mapuid), newInt(s.Mapgid)
|
||||||
|
s.uid = newInt(HsuUid(s.UserID, s.identity.unwrap()))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// instancePath returns a path formatted for outcomeStateSys.instance.
|
||||||
|
// This method must only be called from outcomeOp.toContainer if
|
||||||
|
// outcomeOp.toSystem has already called outcomeStateSys.instance.
|
||||||
|
func (s *outcomeState) instancePath() *check.Absolute { return s.sc.SharePath.Append(s.id.String()) }
|
||||||
|
|
||||||
|
// runtimePath returns a path formatted for outcomeStateSys.runtime.
|
||||||
|
// This method must only be called from outcomeOp.toContainer if
|
||||||
|
// outcomeOp.toSystem has already called outcomeStateSys.runtime.
|
||||||
|
func (s *outcomeState) runtimePath() *check.Absolute { return s.sc.RunDirPath.Append(s.id.String()) }
|
||||||
|
|
||||||
|
// outcomeStateSys wraps outcomeState and [system.I]. Used on the priv side only.
|
||||||
|
// Implementations of outcomeOp must not access fields other than sys unless explicitly stated.
|
||||||
|
type outcomeStateSys struct {
|
||||||
|
// Whether XDG_RUNTIME_DIR is used post hsu.
|
||||||
|
useRuntimeDir bool
|
||||||
|
// Process-specific directory in TMPDIR, nil if unused.
|
||||||
|
sharePath *check.Absolute
|
||||||
|
// Process-specific directory in XDG_RUNTIME_DIR, nil if unused.
|
||||||
|
runtimeSharePath *check.Absolute
|
||||||
|
|
||||||
|
sys *system.I
|
||||||
|
*outcomeState
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensureRuntimeDir must be called if access to paths within XDG_RUNTIME_DIR is required.
|
||||||
|
func (state *outcomeStateSys) ensureRuntimeDir() {
|
||||||
|
if state.useRuntimeDir {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
state.useRuntimeDir = true
|
||||||
|
state.sys.Ensure(state.sc.RunDirPath, 0700)
|
||||||
|
state.sys.UpdatePermType(system.User, state.sc.RunDirPath, acl.Execute)
|
||||||
|
state.sys.Ensure(state.sc.RuntimePath, 0700) // ensure this dir in case XDG_RUNTIME_DIR is unset
|
||||||
|
state.sys.UpdatePermType(system.User, state.sc.RuntimePath, acl.Execute)
|
||||||
|
}
|
||||||
|
|
||||||
|
// instance returns the pathname to a process-specific directory within TMPDIR.
|
||||||
|
// This directory must only hold entries bound to [system.Process].
|
||||||
|
func (state *outcomeStateSys) instance() *check.Absolute {
|
||||||
|
if state.sharePath != nil {
|
||||||
|
return state.sharePath
|
||||||
|
}
|
||||||
|
state.sharePath = state.instancePath()
|
||||||
|
state.sys.Ephemeral(system.Process, state.sharePath, 0711)
|
||||||
|
return state.sharePath
|
||||||
|
}
|
||||||
|
|
||||||
|
// runtime returns the pathname to a process-specific directory within XDG_RUNTIME_DIR.
|
||||||
|
// This directory must only hold entries bound to [system.Process].
|
||||||
|
func (state *outcomeStateSys) runtime() *check.Absolute {
|
||||||
|
if state.runtimeSharePath != nil {
|
||||||
|
return state.runtimeSharePath
|
||||||
|
}
|
||||||
|
state.ensureRuntimeDir()
|
||||||
|
state.runtimeSharePath = state.runtimePath()
|
||||||
|
state.sys.Ephemeral(system.Process, state.runtimeSharePath, 0700)
|
||||||
|
state.sys.UpdatePerm(state.runtimeSharePath, acl.Execute)
|
||||||
|
return state.runtimeSharePath
|
||||||
|
}
|
||||||
|
|
||||||
|
// outcomeStateParams wraps outcomeState and [container.Params]. Used on the shim side only.
|
||||||
|
type outcomeStateParams struct {
|
||||||
|
// Overrides the embedded [container.Params] in [container.Container]. The Env field must not be used.
|
||||||
|
params *container.Params
|
||||||
|
// Collapsed into the Env slice in [container.Params] by the final outcomeOp.
|
||||||
|
env map[string]string
|
||||||
|
|
||||||
|
// Filesystems with the optional root sliced off if present. Populated by spParamsOp.
|
||||||
|
// Safe for use by spFilesystemOp.
|
||||||
|
filesystem []hst.FilesystemConfigJSON
|
||||||
|
|
||||||
|
// Inner XDG_RUNTIME_DIR default formatting of `/run/user/%d` via mapped uid.
|
||||||
|
// Populated by spRuntimeOp.
|
||||||
|
runtimeDir *check.Absolute
|
||||||
|
|
||||||
|
as hst.ApplyState
|
||||||
|
*outcomeState
|
||||||
|
}
|
||||||
|
|
||||||
|
// An outcomeOp inflicts an outcome on [system.I] and contains enough information to
|
||||||
|
// inflict it on [container.Params] in a separate process.
|
||||||
|
// An implementation of outcomeOp must store cross-process states in exported fields only.
|
||||||
|
type outcomeOp interface {
|
||||||
|
// toSystem inflicts the current outcome on [system.I] in the priv side process.
|
||||||
|
toSystem(state *outcomeStateSys, config *hst.Config) error
|
||||||
|
|
||||||
|
// toContainer inflicts the current outcome on [container.Params] in the shim process.
|
||||||
|
// The implementation must not write to the Env field of [container.Params] as it will be overwritten
|
||||||
|
// by flattened env map.
|
||||||
|
toContainer(state *outcomeStateParams) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// fromConfig returns a corresponding slice of outcomeOp for [hst.Config].
|
||||||
|
// This function assumes the caller has already called the Validate method on [hst.Config]
|
||||||
|
// and checked that it returns nil.
|
||||||
|
func fromConfig(config *hst.Config) (ops []outcomeOp) {
|
||||||
|
ops = []outcomeOp{
|
||||||
|
// must run first
|
||||||
|
&spParamsOp{},
|
||||||
|
|
||||||
|
// TODO(ophestra): move this late for #8 and #9
|
||||||
|
spFilesystemOp{},
|
||||||
|
|
||||||
|
spRuntimeOp{},
|
||||||
|
spTmpdirOp{},
|
||||||
|
spAccountOp{},
|
||||||
|
}
|
||||||
|
|
||||||
|
et := config.Enablements.Unwrap()
|
||||||
|
if et&hst.EWayland != 0 {
|
||||||
|
ops = append(ops, &spWaylandOp{})
|
||||||
|
}
|
||||||
|
if et&hst.EX11 != 0 {
|
||||||
|
ops = append(ops, &spX11Op{})
|
||||||
|
}
|
||||||
|
if et&hst.EPulse != 0 {
|
||||||
|
ops = append(ops, &spPulseOp{})
|
||||||
|
}
|
||||||
|
if et&hst.EDBus != 0 {
|
||||||
|
ops = append(ops, &spDBusOp{})
|
||||||
|
}
|
||||||
|
|
||||||
|
ops = append(ops, spFinal{})
|
||||||
|
return
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user