Compare commits
69 Commits
v0.2.0
...
409ed172c8
| Author | SHA1 | Date | |
|---|---|---|---|
|
409ed172c8
|
|||
|
1c4f593566
|
|||
|
b99c63337d
|
|||
|
f09133a224
|
|||
|
16409b37a2
|
|||
|
a2a291791c
|
|||
|
8690419c2d
|
|||
|
1cdc6b4246
|
|||
|
56aad8dc11
|
|||
|
83c4f8b767
|
|||
|
d0ddd71934
|
|||
|
70e02090f7
|
|||
|
ca247b8037
|
|||
|
3f25c3f0af
|
|||
|
e271fa77aa
|
|||
|
f876043844
|
|||
|
6265aea73a
|
|||
|
c8a0effe90
|
|||
|
8df01b71d4
|
|||
|
985c4dd2fc
|
|||
|
da2b9c01ce
|
|||
|
323d132c40
|
|||
|
6cc2b406a4
|
|||
|
fcd0f2ede7
|
|||
|
e68db7fbfc
|
|||
|
ac81cfbedc
|
|||
|
05db06c87b
|
|||
|
e603b688ca
|
|||
|
a9def08533
|
|||
|
ecaf43358d
|
|||
|
197fa65b8f
|
|||
|
e81a45e849
|
|||
|
3920acf8c2
|
|||
|
19630a9593
|
|||
|
4051577d6b
|
|||
|
ddfb865e2d
|
|||
|
024d2ff782
|
|||
|
6f719bc3c1
|
|||
|
1b5d20a39b
|
|||
|
49600a6f46
|
|||
|
b489a3bba1
|
|||
|
780e3e5465
|
|||
|
712cfc06d7
|
|||
|
f5abce9df5
|
|||
|
ddb003e39b
|
|||
|
b12c290f12
|
|||
|
0122593312
|
|||
|
6aa431d57a
|
|||
|
08eeafe817
|
|||
|
d7c7c69a13
|
|||
|
50972096cd
|
|||
|
905b9f9785
|
|||
|
1c7e634f09
|
|||
|
8d472ebf2b
|
|||
|
4da6463135
|
|||
|
eb3385d490
|
|||
|
b8669338da
|
|||
|
f24dd4ab8c
|
|||
|
a462341a0a
|
|||
|
84ad9791e2
|
|||
|
b14690aa77
|
|||
|
d0b6852cd7
|
|||
|
da0459aca1
|
|||
|
1be8de6f5c
|
|||
|
0f41d96671
|
|||
|
92f510a647
|
|||
|
acb6931f3e
|
|||
|
9d932d1039
|
|||
|
9bc8532d56
|
@@ -6,11 +6,9 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
|
||||||
"os/user"
|
"os/user"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"hakurei.app/command"
|
"hakurei.app/command"
|
||||||
@@ -20,11 +18,12 @@ import (
|
|||||||
"hakurei.app/internal/app"
|
"hakurei.app/internal/app"
|
||||||
"hakurei.app/internal/app/state"
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/internal/hlog"
|
"hakurei.app/internal/hlog"
|
||||||
|
"hakurei.app/internal/sys"
|
||||||
"hakurei.app/system"
|
"hakurei.app/system"
|
||||||
"hakurei.app/system/dbus"
|
"hakurei.app/system/dbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func buildCommand(out io.Writer) command.Command {
|
func buildCommand(ctx context.Context, out io.Writer) command.Command {
|
||||||
var (
|
var (
|
||||||
flagVerbose bool
|
flagVerbose bool
|
||||||
flagJSON bool
|
flagJSON bool
|
||||||
@@ -44,35 +43,35 @@ func buildCommand(out io.Writer) command.Command {
|
|||||||
config := tryPath(args[0])
|
config := tryPath(args[0])
|
||||||
config.Args = append(config.Args, args[1:]...)
|
config.Args = append(config.Args, args[1:]...)
|
||||||
|
|
||||||
runApp(config)
|
app.Main(ctx, std, config)
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
})
|
})
|
||||||
|
|
||||||
{
|
{
|
||||||
var (
|
var (
|
||||||
dbusConfigSession string
|
flagDBusConfigSession string
|
||||||
dbusConfigSystem string
|
flagDBusConfigSystem string
|
||||||
mpris bool
|
flagDBusMpris bool
|
||||||
dbusVerbose bool
|
flagDBusVerbose bool
|
||||||
|
|
||||||
fid string
|
flagID string
|
||||||
aid int
|
flagIdentity int
|
||||||
groups command.RepeatableFlag
|
flagGroups command.RepeatableFlag
|
||||||
homeDir string
|
flagHomeDir string
|
||||||
userName string
|
flagUserName string
|
||||||
|
|
||||||
wayland, x11, dBus, pulse 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 default sandbox", func(args []string) error {
|
||||||
// initialise config from flags
|
// initialise config from flags
|
||||||
config := &hst.Config{
|
config := &hst.Config{
|
||||||
ID: fid,
|
ID: flagID,
|
||||||
Args: args,
|
Args: args,
|
||||||
}
|
}
|
||||||
|
|
||||||
if aid < 0 || aid > 9999 {
|
if flagIdentity < 0 || flagIdentity > 9999 {
|
||||||
log.Fatalf("aid %d out of range", aid)
|
log.Fatalf("identity %d out of range", flagIdentity)
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolve home/username from os when flag is unset
|
// resolve home/username from os when flag is unset
|
||||||
@@ -80,14 +79,7 @@ func buildCommand(out io.Writer) command.Command {
|
|||||||
passwd *user.User
|
passwd *user.User
|
||||||
passwdOnce sync.Once
|
passwdOnce sync.Once
|
||||||
passwdFunc = func() {
|
passwdFunc = func() {
|
||||||
var us string
|
us := strconv.Itoa(sys.MustUid(std, flagIdentity))
|
||||||
if uid, err := std.Uid(aid); err != nil {
|
|
||||||
hlog.PrintBaseError(err, "cannot obtain uid from setuid wrapper:")
|
|
||||||
os.Exit(1)
|
|
||||||
} else {
|
|
||||||
us = strconv.Itoa(uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
if u, err := user.LookupId(us); err != nil {
|
if u, err := user.LookupId(us); err != nil {
|
||||||
hlog.Verbosef("cannot look up uid %s", us)
|
hlog.Verbosef("cannot look up uid %s", us)
|
||||||
passwd = &user.User{
|
passwd = &user.User{
|
||||||
@@ -103,21 +95,21 @@ func buildCommand(out io.Writer) command.Command {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if homeDir == "os" {
|
if flagHomeDir == "os" {
|
||||||
passwdOnce.Do(passwdFunc)
|
passwdOnce.Do(passwdFunc)
|
||||||
homeDir = passwd.HomeDir
|
flagHomeDir = passwd.HomeDir
|
||||||
}
|
}
|
||||||
|
|
||||||
if userName == "chronos" {
|
if flagUserName == "chronos" {
|
||||||
passwdOnce.Do(passwdFunc)
|
passwdOnce.Do(passwdFunc)
|
||||||
userName = passwd.Username
|
flagUserName = passwd.Username
|
||||||
}
|
}
|
||||||
|
|
||||||
config.Identity = aid
|
config.Identity = flagIdentity
|
||||||
config.Groups = groups
|
config.Groups = flagGroups
|
||||||
config.Username = userName
|
config.Username = flagUserName
|
||||||
|
|
||||||
if a, err := container.NewAbs(homeDir); err != nil {
|
if a, err := container.NewAbs(flagHomeDir); err != nil {
|
||||||
log.Fatal(err.Error())
|
log.Fatal(err.Error())
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
@@ -125,105 +117,112 @@ func buildCommand(out io.Writer) command.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var e system.Enablement
|
var e system.Enablement
|
||||||
if wayland {
|
if flagWayland {
|
||||||
e |= system.EWayland
|
e |= system.EWayland
|
||||||
}
|
}
|
||||||
if x11 {
|
if flagX11 {
|
||||||
e |= system.EX11
|
e |= system.EX11
|
||||||
}
|
}
|
||||||
if dBus {
|
if flagDBus {
|
||||||
e |= system.EDBus
|
e |= system.EDBus
|
||||||
}
|
}
|
||||||
if pulse {
|
if flagPulse {
|
||||||
e |= system.EPulse
|
e |= system.EPulse
|
||||||
}
|
}
|
||||||
config.Enablements = hst.NewEnablements(e)
|
config.Enablements = hst.NewEnablements(e)
|
||||||
|
|
||||||
// parse D-Bus config file from flags if applicable
|
// parse D-Bus config file from flags if applicable
|
||||||
if dBus {
|
if flagDBus {
|
||||||
if dbusConfigSession == "builtin" {
|
if flagDBusConfigSession == "builtin" {
|
||||||
config.SessionBus = dbus.NewConfig(fid, true, mpris)
|
config.SessionBus = dbus.NewConfig(flagID, true, flagDBusMpris)
|
||||||
} else {
|
} else {
|
||||||
if conf, err := dbus.NewConfigFromFile(dbusConfigSession); err != nil {
|
if conf, err := dbus.NewConfigFromFile(flagDBusConfigSession); err != nil {
|
||||||
log.Fatalf("cannot load session bus proxy config from %q: %s", dbusConfigSession, err)
|
log.Fatalf("cannot load session bus proxy config from %q: %s", flagDBusConfigSession, err)
|
||||||
} else {
|
} else {
|
||||||
config.SessionBus = conf
|
config.SessionBus = conf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// system bus proxy is optional
|
// system bus proxy is optional
|
||||||
if dbusConfigSystem != "nil" {
|
if flagDBusConfigSystem != "nil" {
|
||||||
if conf, err := dbus.NewConfigFromFile(dbusConfigSystem); err != nil {
|
if conf, err := dbus.NewConfigFromFile(flagDBusConfigSystem); err != nil {
|
||||||
log.Fatalf("cannot load system bus proxy config from %q: %s", dbusConfigSystem, err)
|
log.Fatalf("cannot load system bus proxy config from %q: %s", flagDBusConfigSystem, err)
|
||||||
} else {
|
} else {
|
||||||
config.SystemBus = conf
|
config.SystemBus = conf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// override log from configuration
|
// override log from configuration
|
||||||
if dbusVerbose {
|
if flagDBusVerbose {
|
||||||
config.SessionBus.Log = true
|
if config.SessionBus != nil {
|
||||||
config.SystemBus.Log = true
|
config.SessionBus.Log = true
|
||||||
|
}
|
||||||
|
if config.SystemBus != nil {
|
||||||
|
config.SystemBus.Log = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// invoke app
|
app.Main(ctx, std, config)
|
||||||
runApp(config)
|
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}).
|
}).
|
||||||
Flag(&dbusConfigSession, "dbus-config", command.StringFlag("builtin"),
|
Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"),
|
||||||
"Path to session bus proxy config file, or \"builtin\" for defaults").
|
"Path to session bus proxy config file, or \"builtin\" for defaults").
|
||||||
Flag(&dbusConfigSystem, "dbus-system", command.StringFlag("nil"),
|
Flag(&flagDBusConfigSystem, "dbus-system", command.StringFlag("nil"),
|
||||||
"Path to system bus proxy config file, or \"nil\" to disable").
|
"Path to system bus proxy config file, or \"nil\" to disable").
|
||||||
Flag(&mpris, "mpris", command.BoolFlag(false),
|
Flag(&flagDBusMpris, "mpris", command.BoolFlag(false),
|
||||||
"Allow owning MPRIS D-Bus path, has no effect if custom config is available").
|
"Allow owning MPRIS D-Bus path, has no effect if custom config is available").
|
||||||
Flag(&dbusVerbose, "dbus-log", command.BoolFlag(false),
|
Flag(&flagDBusVerbose, "dbus-log", command.BoolFlag(false),
|
||||||
"Force buffered logging in the D-Bus proxy").
|
"Force buffered logging in the D-Bus proxy").
|
||||||
Flag(&fid, "id", command.StringFlag(""),
|
Flag(&flagID, "id", command.StringFlag(""),
|
||||||
"Reverse-DNS style Application identifier, leave empty to inherit instance identifier").
|
"Reverse-DNS style Application identifier, leave empty to inherit instance identifier").
|
||||||
Flag(&aid, "a", command.IntFlag(0),
|
Flag(&flagIdentity, "a", command.IntFlag(0),
|
||||||
"Application identity").
|
"Application identity").
|
||||||
Flag(nil, "g", &groups,
|
Flag(nil, "g", &flagGroups,
|
||||||
"Groups inherited by all container processes").
|
"Groups inherited by all container processes").
|
||||||
Flag(&homeDir, "d", command.StringFlag("os"),
|
Flag(&flagHomeDir, "d", command.StringFlag("os"),
|
||||||
"Container home directory").
|
"Container home directory").
|
||||||
Flag(&userName, "u", command.StringFlag("chronos"),
|
Flag(&flagUserName, "u", command.StringFlag("chronos"),
|
||||||
"Passwd user name within sandbox").
|
"Passwd user name within sandbox").
|
||||||
Flag(&wayland, "wayland", command.BoolFlag(false),
|
Flag(&flagWayland, "wayland", command.BoolFlag(false),
|
||||||
"Enable connection to Wayland via security-context-v1").
|
"Enable connection to Wayland via security-context-v1").
|
||||||
Flag(&x11, "X", command.BoolFlag(false),
|
Flag(&flagX11, "X", command.BoolFlag(false),
|
||||||
"Enable direct connection to X11").
|
"Enable direct connection to X11").
|
||||||
Flag(&dBus, "dbus", command.BoolFlag(false),
|
Flag(&flagDBus, "dbus", command.BoolFlag(false),
|
||||||
"Enable proxied connection to D-Bus").
|
"Enable proxied connection to D-Bus").
|
||||||
Flag(&pulse, "pulse", command.BoolFlag(false),
|
Flag(&flagPulse, "pulse", command.BoolFlag(false),
|
||||||
"Enable direct connection to PulseAudio")
|
"Enable direct connection to PulseAudio")
|
||||||
}
|
}
|
||||||
|
|
||||||
var showFlagShort bool
|
{
|
||||||
c.NewCommand("show", "Show live or local app configuration", func(args []string) error {
|
var flagShort bool
|
||||||
switch len(args) {
|
c.NewCommand("show", "Show live or local app configuration", func(args []string) error {
|
||||||
case 0: // system
|
switch len(args) {
|
||||||
printShowSystem(os.Stdout, showFlagShort, flagJSON)
|
case 0: // system
|
||||||
|
printShowSystem(os.Stdout, flagShort, flagJSON)
|
||||||
|
|
||||||
case 1: // instance
|
case 1: // instance
|
||||||
name := args[0]
|
name := args[0]
|
||||||
config, entry := tryShort(name)
|
config, entry := tryShort(name)
|
||||||
if config == nil {
|
if config == nil {
|
||||||
config = tryPath(name)
|
config = tryPath(name)
|
||||||
|
}
|
||||||
|
printShowInstance(os.Stdout, time.Now().UTC(), entry, config, flagShort, flagJSON)
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Fatal("show requires 1 argument")
|
||||||
}
|
}
|
||||||
printShowInstance(os.Stdout, time.Now().UTC(), entry, config, showFlagShort, flagJSON)
|
return errSuccess
|
||||||
|
}).Flag(&flagShort, "short", command.BoolFlag(false), "Omit filesystem information")
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
{
|
||||||
log.Fatal("show requires 1 argument")
|
var flagShort bool
|
||||||
}
|
c.NewCommand("ps", "List active instances", func(args []string) error {
|
||||||
return errSuccess
|
printPs(os.Stdout, time.Now().UTC(), state.NewMulti(std.Paths().RunDirPath.String()), flagShort, flagJSON)
|
||||||
}).Flag(&showFlagShort, "short", command.BoolFlag(false), "Omit filesystem information")
|
return errSuccess
|
||||||
|
}).Flag(&flagShort, "short", command.BoolFlag(false), "Print instance id")
|
||||||
var psFlagShort bool
|
}
|
||||||
c.NewCommand("ps", "List active instances", func(args []string) error {
|
|
||||||
printPs(os.Stdout, time.Now().UTC(), state.NewMulti(std.Paths().RunDirPath.String()), psFlagShort, flagJSON)
|
|
||||||
return errSuccess
|
|
||||||
}).Flag(&psFlagShort, "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())
|
fmt.Println(internal.Version())
|
||||||
@@ -247,20 +246,3 @@ func buildCommand(out io.Writer) command.Command {
|
|||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func runApp(config *hst.Config) {
|
|
||||||
ctx, stop := signal.NotifyContext(context.Background(),
|
|
||||||
syscall.SIGINT, syscall.SIGTERM)
|
|
||||||
defer stop() // unreachable
|
|
||||||
a := app.MustNew(ctx, std)
|
|
||||||
|
|
||||||
rs := new(app.RunState)
|
|
||||||
if sa, err := a.Seal(config); err != nil {
|
|
||||||
hlog.PrintBaseError(err, "cannot seal app:")
|
|
||||||
internal.Exit(1)
|
|
||||||
} else {
|
|
||||||
internal.Exit(app.PrintRunStateErr(rs, sa.Run(rs)))
|
|
||||||
}
|
|
||||||
|
|
||||||
*(*int)(nil) = 0 // not reached
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -68,7 +68,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(out)
|
c := buildCommand(t.Context(), 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)
|
||||||
|
|||||||
@@ -4,10 +4,13 @@ package main
|
|||||||
//go:generate cp ../../LICENSE .
|
//go:generate cp ../../LICENSE .
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/internal"
|
"hakurei.app/internal"
|
||||||
@@ -44,7 +47,11 @@ func main() {
|
|||||||
log.Fatal("this program must not run as root")
|
log.Fatal("this program must not run as root")
|
||||||
}
|
}
|
||||||
|
|
||||||
buildCommand(os.Stderr).MustParse(os.Args[1:], func(err error) {
|
ctx, stop := signal.NotifyContext(context.Background(),
|
||||||
|
syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
defer stop() // unreachable
|
||||||
|
|
||||||
|
buildCommand(ctx, os.Stderr).MustParse(os.Args[1:], func(err error) {
|
||||||
hlog.Verbosef("command returned %v", err)
|
hlog.Verbosef("command returned %v", err)
|
||||||
if errors.Is(err, errSuccess) {
|
if errors.Is(err, errSuccess) {
|
||||||
hlog.BeforeExit()
|
hlog.BeforeExit()
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -14,7 +13,7 @@ import (
|
|||||||
|
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/app/state"
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/internal/hlog"
|
"hakurei.app/internal/sys"
|
||||||
"hakurei.app/system/dbus"
|
"hakurei.app/system/dbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,14 +21,9 @@ func printShowSystem(output io.Writer, short, flagJSON bool) {
|
|||||||
t := newPrinter(output)
|
t := newPrinter(output)
|
||||||
defer t.MustFlush()
|
defer t.MustFlush()
|
||||||
|
|
||||||
info := &hst.Info{Paths: std.Paths()}
|
info := &hst.Info{
|
||||||
|
Paths: std.Paths(),
|
||||||
// get hid by querying uid of identity 0
|
User: sys.MustGetUserID(std),
|
||||||
if uid, err := std.Uid(0); err != nil {
|
|
||||||
hlog.PrintBaseError(err, "cannot obtain uid from setuid wrapper:")
|
|
||||||
os.Exit(1)
|
|
||||||
} else {
|
|
||||||
info.User = (uid / 10000) - 100
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if flagJSON {
|
if flagJSON {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package container
|
|||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { gob.Register(new(AutoEtcOp)) }
|
func init() { gob.Register(new(AutoEtcOp)) }
|
||||||
@@ -24,7 +23,7 @@ func (e *AutoEtcOp) Valid() bool { return e != ni
|
|||||||
func (e *AutoEtcOp) early(*setupState, syscallDispatcher) error { return nil }
|
func (e *AutoEtcOp) early(*setupState, syscallDispatcher) error { return nil }
|
||||||
func (e *AutoEtcOp) apply(state *setupState, k syscallDispatcher) error {
|
func (e *AutoEtcOp) apply(state *setupState, k syscallDispatcher) error {
|
||||||
if state.nonrepeatable&nrAutoEtc != 0 {
|
if state.nonrepeatable&nrAutoEtc != 0 {
|
||||||
return msg.WrapErr(fs.ErrInvalid, "autoetc is not repeatable")
|
return OpRepeatError("autoetc")
|
||||||
}
|
}
|
||||||
state.nonrepeatable |= nrAutoEtc
|
state.nonrepeatable |= nrAutoEtc
|
||||||
|
|
||||||
@@ -32,10 +31,10 @@ func (e *AutoEtcOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
rel := e.hostRel() + "/"
|
rel := e.hostRel() + "/"
|
||||||
|
|
||||||
if err := k.mkdirAll(target, 0755); err != nil {
|
if err := k.mkdirAll(target, 0755); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
}
|
}
|
||||||
if d, err := k.readdir(toSysroot(e.hostPath().String())); err != nil {
|
if d, err := k.readdir(toSysroot(e.hostPath().String())); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
} else {
|
} else {
|
||||||
for _, ent := range d {
|
for _, ent := range d {
|
||||||
n := ent.Name()
|
n := ent.Name()
|
||||||
@@ -44,12 +43,12 @@ func (e *AutoEtcOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
|
|
||||||
case "mtab":
|
case "mtab":
|
||||||
if err = k.symlink(FHSProc+"mounts", target+n); err != nil {
|
if err = k.symlink(FHSProc+"mounts", target+n); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if err = k.symlink(rel+n, target+n); err != nil {
|
if err = k.symlink(rel+n, target+n); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,15 @@ package container
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"io/fs"
|
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/stub"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAutoEtcOp(t *testing.T) {
|
func TestAutoEtcOp(t *testing.T) {
|
||||||
t.Run("nonrepeatable", func(t *testing.T) {
|
t.Run("nonrepeatable", func(t *testing.T) {
|
||||||
wantErr := msg.WrapErr(fs.ErrInvalid, "autoetc is not repeatable")
|
wantErr := OpRepeatError("autoetc")
|
||||||
if err := (&AutoEtcOp{Prefix: "81ceabb30d37bbdb3868004629cb84e9"}).apply(&setupState{nonrepeatable: nrAutoEtc}, nil); !errors.Is(err, wantErr) {
|
if err := (&AutoEtcOp{Prefix: "81ceabb30d37bbdb3868004629cb84e9"}).apply(&setupState{nonrepeatable: nrAutoEtc}, nil); !errors.Is(err, wantErr) {
|
||||||
t.Errorf("apply: error = %v, want %v", err, wantErr)
|
t.Errorf("apply: error = %v, want %v", err, wantErr)
|
||||||
}
|
}
|
||||||
@@ -18,22 +19,22 @@ func TestAutoEtcOp(t *testing.T) {
|
|||||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||||
{"mkdirAll", new(Params), &AutoEtcOp{
|
{"mkdirAll", new(Params), &AutoEtcOp{
|
||||||
Prefix: "81ceabb30d37bbdb3868004629cb84e9",
|
Prefix: "81ceabb30d37bbdb3868004629cb84e9",
|
||||||
}, nil, nil, []kexpect{
|
}, nil, nil, []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/sysroot/etc/", os.FileMode(0755)}, nil, errUnique},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/etc/", os.FileMode(0755)}, nil, stub.UniqueError(3)),
|
||||||
}, wrapErrSelf(errUnique)},
|
}, stub.UniqueError(3)},
|
||||||
|
|
||||||
{"readdir", new(Params), &AutoEtcOp{
|
{"readdir", new(Params), &AutoEtcOp{
|
||||||
Prefix: "81ceabb30d37bbdb3868004629cb84e9",
|
Prefix: "81ceabb30d37bbdb3868004629cb84e9",
|
||||||
}, nil, nil, []kexpect{
|
}, nil, nil, []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/sysroot/etc/", os.FileMode(0755)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/etc/", os.FileMode(0755)}, nil, nil),
|
||||||
{"readdir", expectArgs{"/sysroot/etc/.host/81ceabb30d37bbdb3868004629cb84e9"}, stubDir(), errUnique},
|
call("readdir", stub.ExpectArgs{"/sysroot/etc/.host/81ceabb30d37bbdb3868004629cb84e9"}, stubDir(), stub.UniqueError(2)),
|
||||||
}, wrapErrSelf(errUnique)},
|
}, stub.UniqueError(2)},
|
||||||
|
|
||||||
{"symlink", new(Params), &AutoEtcOp{
|
{"symlink", new(Params), &AutoEtcOp{
|
||||||
Prefix: "81ceabb30d37bbdb3868004629cb84e9",
|
Prefix: "81ceabb30d37bbdb3868004629cb84e9",
|
||||||
}, nil, nil, []kexpect{
|
}, nil, nil, []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/sysroot/etc/", os.FileMode(0755)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/etc/", os.FileMode(0755)}, nil, nil),
|
||||||
{"readdir", expectArgs{"/sysroot/etc/.host/81ceabb30d37bbdb3868004629cb84e9"}, stubDir(".host",
|
call("readdir", stub.ExpectArgs{"/sysroot/etc/.host/81ceabb30d37bbdb3868004629cb84e9"}, stubDir(".host",
|
||||||
"alsa", "bash_logout", "bashrc", "binfmt.d", "dbus-1", "default", "dhcpcd.exit-hook", "fonts",
|
"alsa", "bash_logout", "bashrc", "binfmt.d", "dbus-1", "default", "dhcpcd.exit-hook", "fonts",
|
||||||
"fstab", "fuse.conf", "group", "host.conf", "hostname", "hosts", "hsurc", "inputrc", "issue", "kbd",
|
"fstab", "fuse.conf", "group", "host.conf", "hostname", "hosts", "hsurc", "inputrc", "issue", "kbd",
|
||||||
"locale.conf", "login.defs", "lsb-release", "lvm", "machine-id", "man_db.conf", "mdadm.conf",
|
"locale.conf", "login.defs", "lsb-release", "lvm", "machine-id", "man_db.conf", "mdadm.conf",
|
||||||
@@ -41,15 +42,15 @@ func TestAutoEtcOp(t *testing.T) {
|
|||||||
"nsswitch.conf", "os-release", "pam", "pam.d", "passwd", "pipewire", "pki", "polkit-1", "profile",
|
"nsswitch.conf", "os-release", "pam", "pam.d", "passwd", "pipewire", "pki", "polkit-1", "profile",
|
||||||
"protocols", "resolv.conf", "resolvconf.conf", "rpc", "services", "set-environment", "shadow", "shells",
|
"protocols", "resolv.conf", "resolvconf.conf", "rpc", "services", "set-environment", "shadow", "shells",
|
||||||
"ssh", "ssl", "static", "subgid", "subuid", "sudoers", "sway", "sysctl.d", "systemd", "terminfo",
|
"ssh", "ssl", "static", "subgid", "subuid", "sudoers", "sway", "sysctl.d", "systemd", "terminfo",
|
||||||
"tmpfiles.d", "udev", "vconsole.conf", "X11", "xdg", "zoneinfo"), nil},
|
"tmpfiles.d", "udev", "vconsole.conf", "X11", "xdg", "zoneinfo"), nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/alsa", "/sysroot/etc/alsa"}, nil, errUnique},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/alsa", "/sysroot/etc/alsa"}, nil, stub.UniqueError(1)),
|
||||||
}, wrapErrSelf(errUnique)},
|
}, stub.UniqueError(1)},
|
||||||
|
|
||||||
{"symlink mtab", new(Params), &AutoEtcOp{
|
{"symlink mtab", new(Params), &AutoEtcOp{
|
||||||
Prefix: "81ceabb30d37bbdb3868004629cb84e9",
|
Prefix: "81ceabb30d37bbdb3868004629cb84e9",
|
||||||
}, nil, nil, []kexpect{
|
}, nil, nil, []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/sysroot/etc/", os.FileMode(0755)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/etc/", os.FileMode(0755)}, nil, nil),
|
||||||
{"readdir", expectArgs{"/sysroot/etc/.host/81ceabb30d37bbdb3868004629cb84e9"}, stubDir(".host",
|
call("readdir", stub.ExpectArgs{"/sysroot/etc/.host/81ceabb30d37bbdb3868004629cb84e9"}, stubDir(".host",
|
||||||
"alsa", "bash_logout", "bashrc", "binfmt.d", "dbus-1", "default", "dhcpcd.exit-hook", "fonts",
|
"alsa", "bash_logout", "bashrc", "binfmt.d", "dbus-1", "default", "dhcpcd.exit-hook", "fonts",
|
||||||
"fstab", "fuse.conf", "group", "host.conf", "hostname", "hosts", "hsurc", "inputrc", "issue", "kbd",
|
"fstab", "fuse.conf", "group", "host.conf", "hostname", "hosts", "hsurc", "inputrc", "issue", "kbd",
|
||||||
"locale.conf", "login.defs", "lsb-release", "lvm", "machine-id", "man_db.conf", "mdadm.conf",
|
"locale.conf", "login.defs", "lsb-release", "lvm", "machine-id", "man_db.conf", "mdadm.conf",
|
||||||
@@ -57,41 +58,41 @@ func TestAutoEtcOp(t *testing.T) {
|
|||||||
"nsswitch.conf", "os-release", "pam", "pam.d", "passwd", "pipewire", "pki", "polkit-1", "profile",
|
"nsswitch.conf", "os-release", "pam", "pam.d", "passwd", "pipewire", "pki", "polkit-1", "profile",
|
||||||
"protocols", "resolv.conf", "resolvconf.conf", "rpc", "services", "set-environment", "shadow", "shells",
|
"protocols", "resolv.conf", "resolvconf.conf", "rpc", "services", "set-environment", "shadow", "shells",
|
||||||
"ssh", "ssl", "static", "subgid", "subuid", "sudoers", "sway", "sysctl.d", "systemd", "terminfo",
|
"ssh", "ssl", "static", "subgid", "subuid", "sudoers", "sway", "sysctl.d", "systemd", "terminfo",
|
||||||
"tmpfiles.d", "udev", "vconsole.conf", "X11", "xdg", "zoneinfo"), nil},
|
"tmpfiles.d", "udev", "vconsole.conf", "X11", "xdg", "zoneinfo"), nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/alsa", "/sysroot/etc/alsa"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/alsa", "/sysroot/etc/alsa"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/bash_logout", "/sysroot/etc/bash_logout"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/bash_logout", "/sysroot/etc/bash_logout"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/bashrc", "/sysroot/etc/bashrc"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/bashrc", "/sysroot/etc/bashrc"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/binfmt.d", "/sysroot/etc/binfmt.d"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/binfmt.d", "/sysroot/etc/binfmt.d"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/dbus-1", "/sysroot/etc/dbus-1"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/dbus-1", "/sysroot/etc/dbus-1"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/default", "/sysroot/etc/default"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/default", "/sysroot/etc/default"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/dhcpcd.exit-hook", "/sysroot/etc/dhcpcd.exit-hook"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/dhcpcd.exit-hook", "/sysroot/etc/dhcpcd.exit-hook"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fonts", "/sysroot/etc/fonts"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fonts", "/sysroot/etc/fonts"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fstab", "/sysroot/etc/fstab"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fstab", "/sysroot/etc/fstab"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fuse.conf", "/sysroot/etc/fuse.conf"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fuse.conf", "/sysroot/etc/fuse.conf"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/host.conf", "/sysroot/etc/host.conf"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/host.conf", "/sysroot/etc/host.conf"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hostname", "/sysroot/etc/hostname"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hostname", "/sysroot/etc/hostname"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hosts", "/sysroot/etc/hosts"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hosts", "/sysroot/etc/hosts"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hsurc", "/sysroot/etc/hsurc"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hsurc", "/sysroot/etc/hsurc"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/inputrc", "/sysroot/etc/inputrc"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/inputrc", "/sysroot/etc/inputrc"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/issue", "/sysroot/etc/issue"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/issue", "/sysroot/etc/issue"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/kbd", "/sysroot/etc/kbd"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/kbd", "/sysroot/etc/kbd"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/locale.conf", "/sysroot/etc/locale.conf"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/locale.conf", "/sysroot/etc/locale.conf"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/login.defs", "/sysroot/etc/login.defs"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/login.defs", "/sysroot/etc/login.defs"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/lsb-release", "/sysroot/etc/lsb-release"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/lsb-release", "/sysroot/etc/lsb-release"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/lvm", "/sysroot/etc/lvm"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/lvm", "/sysroot/etc/lvm"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/machine-id", "/sysroot/etc/machine-id"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/machine-id", "/sysroot/etc/machine-id"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/man_db.conf", "/sysroot/etc/man_db.conf"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/man_db.conf", "/sysroot/etc/man_db.conf"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/mdadm.conf", "/sysroot/etc/mdadm.conf"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/mdadm.conf", "/sysroot/etc/mdadm.conf"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/modprobe.d", "/sysroot/etc/modprobe.d"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/modprobe.d", "/sysroot/etc/modprobe.d"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/modules-load.d", "/sysroot/etc/modules-load.d"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/modules-load.d", "/sysroot/etc/modules-load.d"}, nil, nil),
|
||||||
{"symlink", expectArgs{"/proc/mounts", "/sysroot/etc/mtab"}, nil, errUnique},
|
call("symlink", stub.ExpectArgs{"/proc/mounts", "/sysroot/etc/mtab"}, nil, stub.UniqueError(0)),
|
||||||
}, wrapErrSelf(errUnique)},
|
}, stub.UniqueError(0)},
|
||||||
|
|
||||||
{"success nested", new(Params), &AutoEtcOp{
|
{"success nested", new(Params), &AutoEtcOp{
|
||||||
Prefix: "81ceabb30d37bbdb3868004629cb84e9",
|
Prefix: "81ceabb30d37bbdb3868004629cb84e9",
|
||||||
}, nil, nil, []kexpect{
|
}, nil, nil, []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/sysroot/etc/", os.FileMode(0755)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/etc/", os.FileMode(0755)}, nil, nil),
|
||||||
{"readdir", expectArgs{"/sysroot/etc/.host/81ceabb30d37bbdb3868004629cb84e9"}, stubDir(".host",
|
call("readdir", stub.ExpectArgs{"/sysroot/etc/.host/81ceabb30d37bbdb3868004629cb84e9"}, stubDir(".host",
|
||||||
"alsa", "bash_logout", "bashrc", "binfmt.d", "dbus-1", "default", "dhcpcd.exit-hook", "fonts",
|
"alsa", "bash_logout", "bashrc", "binfmt.d", "dbus-1", "default", "dhcpcd.exit-hook", "fonts",
|
||||||
"fstab", "fuse.conf", "group", "host.conf", "hostname", "hosts", "hsurc", "inputrc", "issue", "kbd",
|
"fstab", "fuse.conf", "group", "host.conf", "hostname", "hosts", "hsurc", "inputrc", "issue", "kbd",
|
||||||
"locale.conf", "login.defs", "lsb-release", "lvm", "machine-id", "man_db.conf", "mdadm.conf",
|
"locale.conf", "login.defs", "lsb-release", "lvm", "machine-id", "man_db.conf", "mdadm.conf",
|
||||||
@@ -99,79 +100,79 @@ func TestAutoEtcOp(t *testing.T) {
|
|||||||
"nsswitch.conf", "os-release", "pam", "pam.d", "passwd", "pipewire", "pki", "polkit-1", "profile",
|
"nsswitch.conf", "os-release", "pam", "pam.d", "passwd", "pipewire", "pki", "polkit-1", "profile",
|
||||||
"protocols", "resolv.conf", "resolvconf.conf", "rpc", "services", "set-environment", "shadow", "shells",
|
"protocols", "resolv.conf", "resolvconf.conf", "rpc", "services", "set-environment", "shadow", "shells",
|
||||||
"ssh", "ssl", "static", "subgid", "subuid", "sudoers", "sway", "sysctl.d", "systemd", "terminfo",
|
"ssh", "ssl", "static", "subgid", "subuid", "sudoers", "sway", "sysctl.d", "systemd", "terminfo",
|
||||||
"tmpfiles.d", "udev", "vconsole.conf", "X11", "xdg", "zoneinfo"), nil},
|
"tmpfiles.d", "udev", "vconsole.conf", "X11", "xdg", "zoneinfo"), nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/alsa", "/sysroot/etc/alsa"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/alsa", "/sysroot/etc/alsa"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/bash_logout", "/sysroot/etc/bash_logout"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/bash_logout", "/sysroot/etc/bash_logout"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/bashrc", "/sysroot/etc/bashrc"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/bashrc", "/sysroot/etc/bashrc"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/binfmt.d", "/sysroot/etc/binfmt.d"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/binfmt.d", "/sysroot/etc/binfmt.d"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/dbus-1", "/sysroot/etc/dbus-1"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/dbus-1", "/sysroot/etc/dbus-1"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/default", "/sysroot/etc/default"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/default", "/sysroot/etc/default"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/dhcpcd.exit-hook", "/sysroot/etc/dhcpcd.exit-hook"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/dhcpcd.exit-hook", "/sysroot/etc/dhcpcd.exit-hook"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fonts", "/sysroot/etc/fonts"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fonts", "/sysroot/etc/fonts"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fstab", "/sysroot/etc/fstab"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fstab", "/sysroot/etc/fstab"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fuse.conf", "/sysroot/etc/fuse.conf"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fuse.conf", "/sysroot/etc/fuse.conf"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/host.conf", "/sysroot/etc/host.conf"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/host.conf", "/sysroot/etc/host.conf"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hostname", "/sysroot/etc/hostname"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hostname", "/sysroot/etc/hostname"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hosts", "/sysroot/etc/hosts"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hosts", "/sysroot/etc/hosts"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hsurc", "/sysroot/etc/hsurc"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hsurc", "/sysroot/etc/hsurc"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/inputrc", "/sysroot/etc/inputrc"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/inputrc", "/sysroot/etc/inputrc"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/issue", "/sysroot/etc/issue"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/issue", "/sysroot/etc/issue"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/kbd", "/sysroot/etc/kbd"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/kbd", "/sysroot/etc/kbd"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/locale.conf", "/sysroot/etc/locale.conf"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/locale.conf", "/sysroot/etc/locale.conf"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/login.defs", "/sysroot/etc/login.defs"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/login.defs", "/sysroot/etc/login.defs"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/lsb-release", "/sysroot/etc/lsb-release"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/lsb-release", "/sysroot/etc/lsb-release"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/lvm", "/sysroot/etc/lvm"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/lvm", "/sysroot/etc/lvm"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/machine-id", "/sysroot/etc/machine-id"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/machine-id", "/sysroot/etc/machine-id"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/man_db.conf", "/sysroot/etc/man_db.conf"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/man_db.conf", "/sysroot/etc/man_db.conf"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/mdadm.conf", "/sysroot/etc/mdadm.conf"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/mdadm.conf", "/sysroot/etc/mdadm.conf"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/modprobe.d", "/sysroot/etc/modprobe.d"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/modprobe.d", "/sysroot/etc/modprobe.d"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/modules-load.d", "/sysroot/etc/modules-load.d"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/modules-load.d", "/sysroot/etc/modules-load.d"}, nil, nil),
|
||||||
{"symlink", expectArgs{"/proc/mounts", "/sysroot/etc/mtab"}, nil, nil},
|
call("symlink", stub.ExpectArgs{"/proc/mounts", "/sysroot/etc/mtab"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nanorc", "/sysroot/etc/nanorc"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nanorc", "/sysroot/etc/nanorc"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/netgroup", "/sysroot/etc/netgroup"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/netgroup", "/sysroot/etc/netgroup"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nix", "/sysroot/etc/nix"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nix", "/sysroot/etc/nix"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nixos", "/sysroot/etc/nixos"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nixos", "/sysroot/etc/nixos"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/NIXOS", "/sysroot/etc/NIXOS"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/NIXOS", "/sysroot/etc/NIXOS"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nscd.conf", "/sysroot/etc/nscd.conf"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nscd.conf", "/sysroot/etc/nscd.conf"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nsswitch.conf", "/sysroot/etc/nsswitch.conf"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nsswitch.conf", "/sysroot/etc/nsswitch.conf"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/os-release", "/sysroot/etc/os-release"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/os-release", "/sysroot/etc/os-release"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/pam", "/sysroot/etc/pam"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/pam", "/sysroot/etc/pam"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/pam.d", "/sysroot/etc/pam.d"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/pam.d", "/sysroot/etc/pam.d"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/pipewire", "/sysroot/etc/pipewire"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/pipewire", "/sysroot/etc/pipewire"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/pki", "/sysroot/etc/pki"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/pki", "/sysroot/etc/pki"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/polkit-1", "/sysroot/etc/polkit-1"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/polkit-1", "/sysroot/etc/polkit-1"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/profile", "/sysroot/etc/profile"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/profile", "/sysroot/etc/profile"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/protocols", "/sysroot/etc/protocols"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/protocols", "/sysroot/etc/protocols"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/resolv.conf", "/sysroot/etc/resolv.conf"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/resolv.conf", "/sysroot/etc/resolv.conf"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/resolvconf.conf", "/sysroot/etc/resolvconf.conf"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/resolvconf.conf", "/sysroot/etc/resolvconf.conf"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/rpc", "/sysroot/etc/rpc"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/rpc", "/sysroot/etc/rpc"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/services", "/sysroot/etc/services"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/services", "/sysroot/etc/services"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/set-environment", "/sysroot/etc/set-environment"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/set-environment", "/sysroot/etc/set-environment"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/shadow", "/sysroot/etc/shadow"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/shadow", "/sysroot/etc/shadow"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/shells", "/sysroot/etc/shells"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/shells", "/sysroot/etc/shells"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/ssh", "/sysroot/etc/ssh"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/ssh", "/sysroot/etc/ssh"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/ssl", "/sysroot/etc/ssl"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/ssl", "/sysroot/etc/ssl"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/static", "/sysroot/etc/static"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/static", "/sysroot/etc/static"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/subgid", "/sysroot/etc/subgid"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/subgid", "/sysroot/etc/subgid"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/subuid", "/sysroot/etc/subuid"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/subuid", "/sysroot/etc/subuid"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/sudoers", "/sysroot/etc/sudoers"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/sudoers", "/sysroot/etc/sudoers"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/sway", "/sysroot/etc/sway"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/sway", "/sysroot/etc/sway"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/sysctl.d", "/sysroot/etc/sysctl.d"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/sysctl.d", "/sysroot/etc/sysctl.d"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/systemd", "/sysroot/etc/systemd"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/systemd", "/sysroot/etc/systemd"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/terminfo", "/sysroot/etc/terminfo"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/terminfo", "/sysroot/etc/terminfo"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/tmpfiles.d", "/sysroot/etc/tmpfiles.d"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/tmpfiles.d", "/sysroot/etc/tmpfiles.d"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/udev", "/sysroot/etc/udev"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/udev", "/sysroot/etc/udev"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/vconsole.conf", "/sysroot/etc/vconsole.conf"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/vconsole.conf", "/sysroot/etc/vconsole.conf"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/X11", "/sysroot/etc/X11"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/X11", "/sysroot/etc/X11"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/xdg", "/sysroot/etc/xdg"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/xdg", "/sysroot/etc/xdg"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/zoneinfo", "/sysroot/etc/zoneinfo"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/zoneinfo", "/sysroot/etc/zoneinfo"}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"success", new(Params), &AutoEtcOp{
|
{"success", new(Params), &AutoEtcOp{
|
||||||
Prefix: "81ceabb30d37bbdb3868004629cb84e9",
|
Prefix: "81ceabb30d37bbdb3868004629cb84e9",
|
||||||
}, nil, nil, []kexpect{
|
}, nil, nil, []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/sysroot/etc/", os.FileMode(0755)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/etc/", os.FileMode(0755)}, nil, nil),
|
||||||
{"readdir", expectArgs{"/sysroot/etc/.host/81ceabb30d37bbdb3868004629cb84e9"}, stubDir(
|
call("readdir", stub.ExpectArgs{"/sysroot/etc/.host/81ceabb30d37bbdb3868004629cb84e9"}, stubDir(
|
||||||
"alsa", "bash_logout", "bashrc", "binfmt.d", "dbus-1", "default", "dhcpcd.exit-hook", "fonts",
|
"alsa", "bash_logout", "bashrc", "binfmt.d", "dbus-1", "default", "dhcpcd.exit-hook", "fonts",
|
||||||
"fstab", "fuse.conf", "group", "host.conf", "hostname", "hosts", "hsurc", "inputrc", "issue", "kbd",
|
"fstab", "fuse.conf", "group", "host.conf", "hostname", "hosts", "hsurc", "inputrc", "issue", "kbd",
|
||||||
"locale.conf", "login.defs", "lsb-release", "lvm", "machine-id", "man_db.conf", "mdadm.conf",
|
"locale.conf", "login.defs", "lsb-release", "lvm", "machine-id", "man_db.conf", "mdadm.conf",
|
||||||
@@ -179,72 +180,72 @@ func TestAutoEtcOp(t *testing.T) {
|
|||||||
"nsswitch.conf", "os-release", "pam", "pam.d", "passwd", "pipewire", "pki", "polkit-1", "profile",
|
"nsswitch.conf", "os-release", "pam", "pam.d", "passwd", "pipewire", "pki", "polkit-1", "profile",
|
||||||
"protocols", "resolv.conf", "resolvconf.conf", "rpc", "services", "set-environment", "shadow", "shells",
|
"protocols", "resolv.conf", "resolvconf.conf", "rpc", "services", "set-environment", "shadow", "shells",
|
||||||
"ssh", "ssl", "static", "subgid", "subuid", "sudoers", "sway", "sysctl.d", "systemd", "terminfo",
|
"ssh", "ssl", "static", "subgid", "subuid", "sudoers", "sway", "sysctl.d", "systemd", "terminfo",
|
||||||
"tmpfiles.d", "udev", "vconsole.conf", "X11", "xdg", "zoneinfo"), nil},
|
"tmpfiles.d", "udev", "vconsole.conf", "X11", "xdg", "zoneinfo"), nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/alsa", "/sysroot/etc/alsa"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/alsa", "/sysroot/etc/alsa"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/bash_logout", "/sysroot/etc/bash_logout"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/bash_logout", "/sysroot/etc/bash_logout"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/bashrc", "/sysroot/etc/bashrc"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/bashrc", "/sysroot/etc/bashrc"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/binfmt.d", "/sysroot/etc/binfmt.d"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/binfmt.d", "/sysroot/etc/binfmt.d"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/dbus-1", "/sysroot/etc/dbus-1"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/dbus-1", "/sysroot/etc/dbus-1"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/default", "/sysroot/etc/default"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/default", "/sysroot/etc/default"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/dhcpcd.exit-hook", "/sysroot/etc/dhcpcd.exit-hook"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/dhcpcd.exit-hook", "/sysroot/etc/dhcpcd.exit-hook"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fonts", "/sysroot/etc/fonts"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fonts", "/sysroot/etc/fonts"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fstab", "/sysroot/etc/fstab"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fstab", "/sysroot/etc/fstab"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fuse.conf", "/sysroot/etc/fuse.conf"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fuse.conf", "/sysroot/etc/fuse.conf"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/host.conf", "/sysroot/etc/host.conf"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/host.conf", "/sysroot/etc/host.conf"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hostname", "/sysroot/etc/hostname"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hostname", "/sysroot/etc/hostname"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hosts", "/sysroot/etc/hosts"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hosts", "/sysroot/etc/hosts"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hsurc", "/sysroot/etc/hsurc"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hsurc", "/sysroot/etc/hsurc"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/inputrc", "/sysroot/etc/inputrc"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/inputrc", "/sysroot/etc/inputrc"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/issue", "/sysroot/etc/issue"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/issue", "/sysroot/etc/issue"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/kbd", "/sysroot/etc/kbd"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/kbd", "/sysroot/etc/kbd"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/locale.conf", "/sysroot/etc/locale.conf"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/locale.conf", "/sysroot/etc/locale.conf"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/login.defs", "/sysroot/etc/login.defs"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/login.defs", "/sysroot/etc/login.defs"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/lsb-release", "/sysroot/etc/lsb-release"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/lsb-release", "/sysroot/etc/lsb-release"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/lvm", "/sysroot/etc/lvm"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/lvm", "/sysroot/etc/lvm"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/machine-id", "/sysroot/etc/machine-id"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/machine-id", "/sysroot/etc/machine-id"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/man_db.conf", "/sysroot/etc/man_db.conf"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/man_db.conf", "/sysroot/etc/man_db.conf"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/mdadm.conf", "/sysroot/etc/mdadm.conf"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/mdadm.conf", "/sysroot/etc/mdadm.conf"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/modprobe.d", "/sysroot/etc/modprobe.d"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/modprobe.d", "/sysroot/etc/modprobe.d"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/modules-load.d", "/sysroot/etc/modules-load.d"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/modules-load.d", "/sysroot/etc/modules-load.d"}, nil, nil),
|
||||||
{"symlink", expectArgs{"/proc/mounts", "/sysroot/etc/mtab"}, nil, nil},
|
call("symlink", stub.ExpectArgs{"/proc/mounts", "/sysroot/etc/mtab"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nanorc", "/sysroot/etc/nanorc"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nanorc", "/sysroot/etc/nanorc"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/netgroup", "/sysroot/etc/netgroup"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/netgroup", "/sysroot/etc/netgroup"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nix", "/sysroot/etc/nix"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nix", "/sysroot/etc/nix"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nixos", "/sysroot/etc/nixos"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nixos", "/sysroot/etc/nixos"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/NIXOS", "/sysroot/etc/NIXOS"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/NIXOS", "/sysroot/etc/NIXOS"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nscd.conf", "/sysroot/etc/nscd.conf"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nscd.conf", "/sysroot/etc/nscd.conf"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nsswitch.conf", "/sysroot/etc/nsswitch.conf"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nsswitch.conf", "/sysroot/etc/nsswitch.conf"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/os-release", "/sysroot/etc/os-release"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/os-release", "/sysroot/etc/os-release"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/pam", "/sysroot/etc/pam"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/pam", "/sysroot/etc/pam"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/pam.d", "/sysroot/etc/pam.d"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/pam.d", "/sysroot/etc/pam.d"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/pipewire", "/sysroot/etc/pipewire"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/pipewire", "/sysroot/etc/pipewire"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/pki", "/sysroot/etc/pki"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/pki", "/sysroot/etc/pki"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/polkit-1", "/sysroot/etc/polkit-1"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/polkit-1", "/sysroot/etc/polkit-1"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/profile", "/sysroot/etc/profile"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/profile", "/sysroot/etc/profile"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/protocols", "/sysroot/etc/protocols"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/protocols", "/sysroot/etc/protocols"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/resolv.conf", "/sysroot/etc/resolv.conf"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/resolv.conf", "/sysroot/etc/resolv.conf"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/resolvconf.conf", "/sysroot/etc/resolvconf.conf"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/resolvconf.conf", "/sysroot/etc/resolvconf.conf"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/rpc", "/sysroot/etc/rpc"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/rpc", "/sysroot/etc/rpc"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/services", "/sysroot/etc/services"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/services", "/sysroot/etc/services"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/set-environment", "/sysroot/etc/set-environment"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/set-environment", "/sysroot/etc/set-environment"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/shadow", "/sysroot/etc/shadow"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/shadow", "/sysroot/etc/shadow"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/shells", "/sysroot/etc/shells"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/shells", "/sysroot/etc/shells"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/ssh", "/sysroot/etc/ssh"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/ssh", "/sysroot/etc/ssh"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/ssl", "/sysroot/etc/ssl"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/ssl", "/sysroot/etc/ssl"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/static", "/sysroot/etc/static"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/static", "/sysroot/etc/static"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/subgid", "/sysroot/etc/subgid"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/subgid", "/sysroot/etc/subgid"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/subuid", "/sysroot/etc/subuid"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/subuid", "/sysroot/etc/subuid"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/sudoers", "/sysroot/etc/sudoers"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/sudoers", "/sysroot/etc/sudoers"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/sway", "/sysroot/etc/sway"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/sway", "/sysroot/etc/sway"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/sysctl.d", "/sysroot/etc/sysctl.d"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/sysctl.d", "/sysroot/etc/sysctl.d"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/systemd", "/sysroot/etc/systemd"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/systemd", "/sysroot/etc/systemd"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/terminfo", "/sysroot/etc/terminfo"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/terminfo", "/sysroot/etc/terminfo"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/tmpfiles.d", "/sysroot/etc/tmpfiles.d"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/tmpfiles.d", "/sysroot/etc/tmpfiles.d"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/udev", "/sysroot/etc/udev"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/udev", "/sysroot/etc/udev"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/vconsole.conf", "/sysroot/etc/vconsole.conf"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/vconsole.conf", "/sysroot/etc/vconsole.conf"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/X11", "/sysroot/etc/X11"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/X11", "/sysroot/etc/X11"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/xdg", "/sysroot/etc/xdg"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/xdg", "/sysroot/etc/xdg"}, nil, nil),
|
||||||
{"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/zoneinfo", "/sysroot/etc/zoneinfo"}, nil, nil},
|
call("symlink", stub.ExpectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/zoneinfo", "/sysroot/etc/zoneinfo"}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package container
|
|||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { gob.Register(new(AutoRootOp)) }
|
func init() { gob.Register(new(AutoRootOp)) }
|
||||||
@@ -30,7 +29,7 @@ func (r *AutoRootOp) Valid() bool { return r != nil && r.Host != nil }
|
|||||||
|
|
||||||
func (r *AutoRootOp) early(state *setupState, k syscallDispatcher) error {
|
func (r *AutoRootOp) early(state *setupState, k syscallDispatcher) error {
|
||||||
if d, err := k.readdir(r.Host.String()); err != nil {
|
if d, err := k.readdir(r.Host.String()); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
} else {
|
} else {
|
||||||
r.resolved = make([]Op, 0, len(d))
|
r.resolved = make([]Op, 0, len(d))
|
||||||
for _, ent := range d {
|
for _, ent := range d {
|
||||||
@@ -53,7 +52,7 @@ func (r *AutoRootOp) early(state *setupState, k syscallDispatcher) error {
|
|||||||
|
|
||||||
func (r *AutoRootOp) apply(state *setupState, k syscallDispatcher) error {
|
func (r *AutoRootOp) apply(state *setupState, k syscallDispatcher) error {
|
||||||
if state.nonrepeatable&nrAutoRoot != 0 {
|
if state.nonrepeatable&nrAutoRoot != 0 {
|
||||||
return msg.WrapErr(fs.ErrInvalid, "autoroot is not repeatable")
|
return OpRepeatError("autoroot")
|
||||||
}
|
}
|
||||||
state.nonrepeatable |= nrAutoRoot
|
state.nonrepeatable |= nrAutoRoot
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,15 @@ package container
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"io/fs"
|
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/stub"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAutoRootOp(t *testing.T) {
|
func TestAutoRootOp(t *testing.T) {
|
||||||
t.Run("nonrepeatable", func(t *testing.T) {
|
t.Run("nonrepeatable", func(t *testing.T) {
|
||||||
wantErr := msg.WrapErr(fs.ErrInvalid, "autoroot is not repeatable")
|
wantErr := OpRepeatError("autoroot")
|
||||||
if err := new(AutoRootOp).apply(&setupState{nonrepeatable: nrAutoRoot}, nil); !errors.Is(err, wantErr) {
|
if err := new(AutoRootOp).apply(&setupState{nonrepeatable: nrAutoRoot}, nil); !errors.Is(err, wantErr) {
|
||||||
t.Errorf("apply: error = %v, want %v", err, wantErr)
|
t.Errorf("apply: error = %v, want %v", err, wantErr)
|
||||||
}
|
}
|
||||||
@@ -19,100 +20,100 @@ func TestAutoRootOp(t *testing.T) {
|
|||||||
{"readdir", &Params{ParentPerm: 0750}, &AutoRootOp{
|
{"readdir", &Params{ParentPerm: 0750}, &AutoRootOp{
|
||||||
Host: MustAbs("/"),
|
Host: MustAbs("/"),
|
||||||
Flags: BindWritable,
|
Flags: BindWritable,
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"readdir", expectArgs{"/"}, stubDir(), errUnique},
|
call("readdir", stub.ExpectArgs{"/"}, stubDir(), stub.UniqueError(2)),
|
||||||
}, wrapErrSelf(errUnique), nil, nil},
|
}, stub.UniqueError(2), nil, nil},
|
||||||
|
|
||||||
{"early", &Params{ParentPerm: 0750}, &AutoRootOp{
|
{"early", &Params{ParentPerm: 0750}, &AutoRootOp{
|
||||||
Host: MustAbs("/"),
|
Host: MustAbs("/"),
|
||||||
Flags: BindWritable,
|
Flags: BindWritable,
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"readdir", 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),
|
||||||
{"evalSymlinks", expectArgs{"/bin"}, "", errUnique},
|
call("evalSymlinks", stub.ExpectArgs{"/bin"}, "", stub.UniqueError(1)),
|
||||||
}, wrapErrSelf(errUnique), nil, nil},
|
}, stub.UniqueError(1), nil, nil},
|
||||||
|
|
||||||
{"apply", &Params{ParentPerm: 0750}, &AutoRootOp{
|
{"apply", &Params{ParentPerm: 0750}, &AutoRootOp{
|
||||||
Host: MustAbs("/"),
|
Host: MustAbs("/"),
|
||||||
Flags: BindWritable,
|
Flags: BindWritable,
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"readdir", 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),
|
||||||
{"evalSymlinks", expectArgs{"/bin"}, "/usr/bin", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/bin"}, "/usr/bin", nil),
|
||||||
{"evalSymlinks", expectArgs{"/home"}, "/home", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/home"}, "/home", nil),
|
||||||
{"evalSymlinks", expectArgs{"/lib64"}, "/lib64", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/lib64"}, "/lib64", nil),
|
||||||
{"evalSymlinks", expectArgs{"/lost+found"}, "/lost+found", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/lost+found"}, "/lost+found", nil),
|
||||||
{"evalSymlinks", expectArgs{"/nix"}, "/nix", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/nix"}, "/nix", nil),
|
||||||
{"evalSymlinks", expectArgs{"/root"}, "/root", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/root"}, "/root", nil),
|
||||||
{"evalSymlinks", expectArgs{"/run"}, "/run", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/run"}, "/run", nil),
|
||||||
{"evalSymlinks", expectArgs{"/srv"}, "/srv", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/srv"}, "/srv", nil),
|
||||||
{"evalSymlinks", expectArgs{"/sys"}, "/sys", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/sys"}, "/sys", nil),
|
||||||
{"evalSymlinks", expectArgs{"/usr"}, "/usr", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/usr"}, "/usr", nil),
|
||||||
{"evalSymlinks", expectArgs{"/var"}, "/var", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/var"}, "/var", nil),
|
||||||
}, nil, []kexpect{
|
}, nil, []stub.Call{
|
||||||
{"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/usr/bin"), MustAbs("/bin"), MustAbs("/bin"), BindWritable}}}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/usr/bin"), MustAbs("/bin"), MustAbs("/bin"), BindWritable}}}, nil, nil),
|
||||||
{"stat", expectArgs{"/host/usr/bin"}, isDirFi(false), errUnique},
|
call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(false), stub.UniqueError(0)),
|
||||||
}, wrapErrSelf(errUnique)},
|
}, stub.UniqueError(0)},
|
||||||
|
|
||||||
{"success pd", &Params{ParentPerm: 0750}, &AutoRootOp{
|
{"success pd", &Params{ParentPerm: 0750}, &AutoRootOp{
|
||||||
Host: MustAbs("/"),
|
Host: MustAbs("/"),
|
||||||
Flags: BindWritable,
|
Flags: BindWritable,
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"readdir", 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),
|
||||||
{"evalSymlinks", expectArgs{"/bin"}, "/usr/bin", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/bin"}, "/usr/bin", nil),
|
||||||
{"evalSymlinks", expectArgs{"/home"}, "/home", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/home"}, "/home", nil),
|
||||||
{"evalSymlinks", expectArgs{"/lib64"}, "/lib64", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/lib64"}, "/lib64", nil),
|
||||||
{"evalSymlinks", expectArgs{"/lost+found"}, "/lost+found", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/lost+found"}, "/lost+found", nil),
|
||||||
{"evalSymlinks", expectArgs{"/nix"}, "/nix", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/nix"}, "/nix", nil),
|
||||||
{"evalSymlinks", expectArgs{"/root"}, "/root", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/root"}, "/root", nil),
|
||||||
{"evalSymlinks", expectArgs{"/run"}, "/run", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/run"}, "/run", nil),
|
||||||
{"evalSymlinks", expectArgs{"/srv"}, "/srv", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/srv"}, "/srv", nil),
|
||||||
{"evalSymlinks", expectArgs{"/sys"}, "/sys", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/sys"}, "/sys", nil),
|
||||||
{"evalSymlinks", expectArgs{"/usr"}, "/usr", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/usr"}, "/usr", nil),
|
||||||
{"evalSymlinks", expectArgs{"/var"}, "/var", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/var"}, "/var", nil),
|
||||||
}, nil, []kexpect{
|
}, nil, []stub.Call{
|
||||||
{"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/usr/bin"), MustAbs("/bin"), MustAbs("/bin"), BindWritable}}}, nil, nil}, {"stat", expectArgs{"/host/usr/bin"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/usr/bin", "/sysroot/bin", uintptr(0x4004), false}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/usr/bin"), MustAbs("/bin"), MustAbs("/bin"), BindWritable}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/usr/bin", "/sysroot/bin", uintptr(0x4004), false}, nil, nil),
|
||||||
{"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/home"), MustAbs("/home"), MustAbs("/home"), BindWritable}}}, nil, nil}, {"stat", expectArgs{"/host/home"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/home", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/home", "/sysroot/home", uintptr(0x4004), false}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/home"), MustAbs("/home"), MustAbs("/home"), BindWritable}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/home"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/home", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/home", "/sysroot/home", uintptr(0x4004), false}, nil, nil),
|
||||||
{"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/lib64"), MustAbs("/lib64"), MustAbs("/lib64"), BindWritable}}}, nil, nil}, {"stat", expectArgs{"/host/lib64"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/lib64", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/lib64", "/sysroot/lib64", uintptr(0x4004), false}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/lib64"), MustAbs("/lib64"), MustAbs("/lib64"), BindWritable}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/lib64"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/lib64", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/lib64", "/sysroot/lib64", uintptr(0x4004), false}, nil, nil),
|
||||||
{"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/lost+found"), MustAbs("/lost+found"), MustAbs("/lost+found"), BindWritable}}}, nil, nil}, {"stat", expectArgs{"/host/lost+found"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/lost+found", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/lost+found", "/sysroot/lost+found", uintptr(0x4004), false}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/lost+found"), MustAbs("/lost+found"), MustAbs("/lost+found"), BindWritable}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/lost+found"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/lost+found", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/lost+found", "/sysroot/lost+found", uintptr(0x4004), false}, nil, nil),
|
||||||
{"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/nix"), MustAbs("/nix"), MustAbs("/nix"), BindWritable}}}, nil, nil}, {"stat", expectArgs{"/host/nix"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/nix", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/nix", "/sysroot/nix", uintptr(0x4004), false}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/nix"), MustAbs("/nix"), MustAbs("/nix"), BindWritable}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/nix"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/nix", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/nix", "/sysroot/nix", uintptr(0x4004), false}, nil, nil),
|
||||||
{"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/root"), MustAbs("/root"), MustAbs("/root"), BindWritable}}}, nil, nil}, {"stat", expectArgs{"/host/root"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/root", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/root", "/sysroot/root", uintptr(0x4004), false}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/root"), MustAbs("/root"), MustAbs("/root"), BindWritable}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/root"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/root", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/root", "/sysroot/root", uintptr(0x4004), false}, nil, nil),
|
||||||
{"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/run"), MustAbs("/run"), MustAbs("/run"), BindWritable}}}, nil, nil}, {"stat", expectArgs{"/host/run"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/run", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/run", "/sysroot/run", uintptr(0x4004), false}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/run"), MustAbs("/run"), MustAbs("/run"), BindWritable}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/run"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/run", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/run", "/sysroot/run", uintptr(0x4004), false}, nil, nil),
|
||||||
{"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/srv"), MustAbs("/srv"), MustAbs("/srv"), BindWritable}}}, nil, nil}, {"stat", expectArgs{"/host/srv"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/srv", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/srv", "/sysroot/srv", uintptr(0x4004), false}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/srv"), MustAbs("/srv"), MustAbs("/srv"), BindWritable}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/srv"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/srv", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/srv", "/sysroot/srv", uintptr(0x4004), false}, nil, nil),
|
||||||
{"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/sys"), MustAbs("/sys"), MustAbs("/sys"), BindWritable}}}, nil, nil}, {"stat", expectArgs{"/host/sys"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/sys", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/sys", "/sysroot/sys", uintptr(0x4004), false}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/sys"), MustAbs("/sys"), MustAbs("/sys"), BindWritable}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/sys"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/sys", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/sys", "/sysroot/sys", uintptr(0x4004), false}, nil, nil),
|
||||||
{"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/usr"), MustAbs("/usr"), MustAbs("/usr"), BindWritable}}}, nil, nil}, {"stat", expectArgs{"/host/usr"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/usr", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/usr", "/sysroot/usr", uintptr(0x4004), false}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/usr"), MustAbs("/usr"), MustAbs("/usr"), BindWritable}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/usr"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/usr", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/usr", "/sysroot/usr", uintptr(0x4004), false}, nil, nil),
|
||||||
{"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var"), MustAbs("/var"), MustAbs("/var"), BindWritable}}}, nil, nil}, {"stat", expectArgs{"/host/var"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/var", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/var", "/sysroot/var", uintptr(0x4004), false}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var"), MustAbs("/var"), MustAbs("/var"), BindWritable}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/var"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/var", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var", "/sysroot/var", uintptr(0x4004), false}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"success", &Params{ParentPerm: 0750}, &AutoRootOp{
|
{"success", &Params{ParentPerm: 0750}, &AutoRootOp{
|
||||||
Host: MustAbs("/var/lib/planterette/base/debian:f92c9052"),
|
Host: MustAbs("/var/lib/planterette/base/debian:f92c9052"),
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"readdir", 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),
|
||||||
{"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052/bin"}, "/var/lib/planterette/base/debian:f92c9052/usr/bin", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/base/debian:f92c9052/bin"}, "/var/lib/planterette/base/debian:f92c9052/usr/bin", nil),
|
||||||
{"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052/home"}, "/var/lib/planterette/base/debian:f92c9052/home", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/base/debian:f92c9052/home"}, "/var/lib/planterette/base/debian:f92c9052/home", nil),
|
||||||
{"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052/lib64"}, "/var/lib/planterette/base/debian:f92c9052/lib64", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/base/debian:f92c9052/lib64"}, "/var/lib/planterette/base/debian:f92c9052/lib64", nil),
|
||||||
{"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052/lost+found"}, "/var/lib/planterette/base/debian:f92c9052/lost+found", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/base/debian:f92c9052/lost+found"}, "/var/lib/planterette/base/debian:f92c9052/lost+found", nil),
|
||||||
{"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052/nix"}, "/var/lib/planterette/base/debian:f92c9052/nix", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/base/debian:f92c9052/nix"}, "/var/lib/planterette/base/debian:f92c9052/nix", nil),
|
||||||
{"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052/root"}, "/var/lib/planterette/base/debian:f92c9052/root", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/base/debian:f92c9052/root"}, "/var/lib/planterette/base/debian:f92c9052/root", nil),
|
||||||
{"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052/run"}, "/var/lib/planterette/base/debian:f92c9052/run", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/base/debian:f92c9052/run"}, "/var/lib/planterette/base/debian:f92c9052/run", nil),
|
||||||
{"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052/srv"}, "/var/lib/planterette/base/debian:f92c9052/srv", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/base/debian:f92c9052/srv"}, "/var/lib/planterette/base/debian:f92c9052/srv", nil),
|
||||||
{"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052/sys"}, "/var/lib/planterette/base/debian:f92c9052/sys", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/base/debian:f92c9052/sys"}, "/var/lib/planterette/base/debian:f92c9052/sys", nil),
|
||||||
{"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052/usr"}, "/var/lib/planterette/base/debian:f92c9052/usr", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/base/debian:f92c9052/usr"}, "/var/lib/planterette/base/debian:f92c9052/usr", nil),
|
||||||
{"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052/var"}, "/var/lib/planterette/base/debian:f92c9052/var", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/base/debian:f92c9052/var"}, "/var/lib/planterette/base/debian:f92c9052/var", nil),
|
||||||
}, nil, []kexpect{
|
}, nil, []stub.Call{
|
||||||
{"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/usr/bin"), MustAbs("/var/lib/planterette/base/debian:f92c9052/bin"), MustAbs("/bin"), 0}}}, nil, nil}, {"stat", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/usr/bin"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/usr/bin", "/sysroot/bin", uintptr(0x4005), false}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/usr/bin"), MustAbs("/var/lib/planterette/base/debian:f92c9052/bin"), MustAbs("/bin"), 0}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/usr/bin"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/usr/bin", "/sysroot/bin", uintptr(0x4005), false}, nil, nil),
|
||||||
{"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/home"), MustAbs("/var/lib/planterette/base/debian:f92c9052/home"), MustAbs("/home"), 0}}}, nil, nil}, {"stat", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/home"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/home", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/home", "/sysroot/home", uintptr(0x4005), false}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/home"), MustAbs("/var/lib/planterette/base/debian:f92c9052/home"), MustAbs("/home"), 0}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/home"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/home", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/home", "/sysroot/home", uintptr(0x4005), false}, nil, nil),
|
||||||
{"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/lib64"), MustAbs("/var/lib/planterette/base/debian:f92c9052/lib64"), MustAbs("/lib64"), 0}}}, nil, nil}, {"stat", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/lib64"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/lib64", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/lib64", "/sysroot/lib64", uintptr(0x4005), false}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/lib64"), MustAbs("/var/lib/planterette/base/debian:f92c9052/lib64"), MustAbs("/lib64"), 0}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/lib64"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/lib64", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/lib64", "/sysroot/lib64", uintptr(0x4005), false}, nil, nil),
|
||||||
{"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/lost+found"), MustAbs("/var/lib/planterette/base/debian:f92c9052/lost+found"), MustAbs("/lost+found"), 0}}}, nil, nil}, {"stat", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/lost+found"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/lost+found", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/lost+found", "/sysroot/lost+found", uintptr(0x4005), false}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/lost+found"), MustAbs("/var/lib/planterette/base/debian:f92c9052/lost+found"), MustAbs("/lost+found"), 0}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/lost+found"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/lost+found", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/lost+found", "/sysroot/lost+found", uintptr(0x4005), false}, nil, nil),
|
||||||
{"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/nix"), MustAbs("/var/lib/planterette/base/debian:f92c9052/nix"), MustAbs("/nix"), 0}}}, nil, nil}, {"stat", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/nix"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/nix", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/nix", "/sysroot/nix", uintptr(0x4005), false}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/nix"), MustAbs("/var/lib/planterette/base/debian:f92c9052/nix"), MustAbs("/nix"), 0}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/nix"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/nix", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/nix", "/sysroot/nix", uintptr(0x4005), false}, nil, nil),
|
||||||
{"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/root"), MustAbs("/var/lib/planterette/base/debian:f92c9052/root"), MustAbs("/root"), 0}}}, nil, nil}, {"stat", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/root"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/root", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/root", "/sysroot/root", uintptr(0x4005), false}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/root"), MustAbs("/var/lib/planterette/base/debian:f92c9052/root"), MustAbs("/root"), 0}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/root"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/root", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/root", "/sysroot/root", uintptr(0x4005), false}, nil, nil),
|
||||||
{"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/run"), MustAbs("/var/lib/planterette/base/debian:f92c9052/run"), MustAbs("/run"), 0}}}, nil, nil}, {"stat", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/run"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/run", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/run", "/sysroot/run", uintptr(0x4005), false}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/run"), MustAbs("/var/lib/planterette/base/debian:f92c9052/run"), MustAbs("/run"), 0}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/run"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/run", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/run", "/sysroot/run", uintptr(0x4005), false}, nil, nil),
|
||||||
{"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/srv"), MustAbs("/var/lib/planterette/base/debian:f92c9052/srv"), MustAbs("/srv"), 0}}}, nil, nil}, {"stat", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/srv"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/srv", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/srv", "/sysroot/srv", uintptr(0x4005), false}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/srv"), MustAbs("/var/lib/planterette/base/debian:f92c9052/srv"), MustAbs("/srv"), 0}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/srv"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/srv", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/srv", "/sysroot/srv", uintptr(0x4005), false}, nil, nil),
|
||||||
{"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/sys"), MustAbs("/var/lib/planterette/base/debian:f92c9052/sys"), MustAbs("/sys"), 0}}}, nil, nil}, {"stat", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/sys"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/sys", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/sys", "/sysroot/sys", uintptr(0x4005), false}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/sys"), MustAbs("/var/lib/planterette/base/debian:f92c9052/sys"), MustAbs("/sys"), 0}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/sys"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/sys", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/sys", "/sysroot/sys", uintptr(0x4005), false}, nil, nil),
|
||||||
{"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/usr"), MustAbs("/var/lib/planterette/base/debian:f92c9052/usr"), MustAbs("/usr"), 0}}}, nil, nil}, {"stat", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/usr"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/usr", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/usr", "/sysroot/usr", uintptr(0x4005), false}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/usr"), MustAbs("/var/lib/planterette/base/debian:f92c9052/usr"), MustAbs("/usr"), 0}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/usr"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/usr", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/usr", "/sysroot/usr", uintptr(0x4005), false}, nil, nil),
|
||||||
{"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/var"), MustAbs("/var/lib/planterette/base/debian:f92c9052/var"), MustAbs("/var"), 0}}}, nil, nil}, {"stat", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/var"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/var", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/var", "/sysroot/var", uintptr(0x4005), false}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/var"), MustAbs("/var/lib/planterette/base/debian:f92c9052/var"), MustAbs("/var"), 0}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/var"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/var", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/var", "/sysroot/var", uintptr(0x4005), false}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ type (
|
|||||||
Args []string
|
Args []string
|
||||||
// Deliver SIGINT to the initial process on context cancellation.
|
// Deliver SIGINT to the initial process on context cancellation.
|
||||||
ForwardCancel bool
|
ForwardCancel bool
|
||||||
// time to wait for linger processes after death of initial process
|
// Time to wait for processes lingering after the initial process terminates.
|
||||||
AdoptWaitDelay time.Duration
|
AdoptWaitDelay time.Duration
|
||||||
|
|
||||||
// Mapped Uid in user namespace.
|
// Mapped Uid in user namespace.
|
||||||
@@ -99,17 +99,66 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Start starts the container init. The init process blocks until Serve is called.
|
// A StartError contains additional information on a container startup failure.
|
||||||
func (p *Container) Start() error {
|
type StartError struct {
|
||||||
if p.cmd != nil {
|
// Fatal suggests whether this error should be considered fatal for the entire program.
|
||||||
return errors.New("container: already started")
|
Fatal bool
|
||||||
|
// Step refers to the part of the setup this error is returned from.
|
||||||
|
Step string
|
||||||
|
// Err is the underlying error.
|
||||||
|
Err error
|
||||||
|
// Origin is whether this error originated from the [Container.Start] method.
|
||||||
|
Origin bool
|
||||||
|
// Passthrough is whether the Error method is passed through to Err.
|
||||||
|
Passthrough bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *StartError) Unwrap() error { return e.Err }
|
||||||
|
func (e *StartError) Error() string {
|
||||||
|
if e.Passthrough {
|
||||||
|
return e.Err.Error()
|
||||||
}
|
}
|
||||||
if p.Ops == nil || len(*p.Ops) == 0 {
|
if e.Origin {
|
||||||
return errors.New("container: starting an empty container")
|
return e.Step
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(p.ctx)
|
{
|
||||||
p.cancel = cancel
|
var syscallError *os.SyscallError
|
||||||
|
if errors.As(e.Err, &syscallError) && syscallError != nil {
|
||||||
|
return e.Step + " " + syscallError.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.Step + ": " + e.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message returns a user-facing error message.
|
||||||
|
func (e *StartError) Message() string {
|
||||||
|
if e.Passthrough {
|
||||||
|
switch {
|
||||||
|
case errors.As(e.Err, new(*os.PathError)),
|
||||||
|
errors.As(e.Err, new(*os.SyscallError)):
|
||||||
|
return "cannot " + e.Err.Error()
|
||||||
|
|
||||||
|
default:
|
||||||
|
return e.Err.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if e.Origin {
|
||||||
|
return e.Step
|
||||||
|
}
|
||||||
|
return "cannot " + e.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts the container init. The init process blocks until Serve is called.
|
||||||
|
func (p *Container) Start() error {
|
||||||
|
if p == nil || p.cmd == nil ||
|
||||||
|
p.Ops == nil || len(*p.Ops) == 0 {
|
||||||
|
return errors.New("container: starting an invalid container")
|
||||||
|
}
|
||||||
|
if p.cmd.Process != nil {
|
||||||
|
return errors.New("container: already started")
|
||||||
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
@@ -131,9 +180,17 @@ func (p *Container) Start() error {
|
|||||||
p.AdoptWaitDelay = 0
|
p.AdoptWaitDelay = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
p.cmd = exec.CommandContext(ctx, MustExecutable())
|
if p.cmd.Stdin == nil {
|
||||||
|
p.cmd.Stdin = p.Stdin
|
||||||
|
}
|
||||||
|
if p.cmd.Stdout == nil {
|
||||||
|
p.cmd.Stdout = p.Stdout
|
||||||
|
}
|
||||||
|
if p.cmd.Stderr == nil {
|
||||||
|
p.cmd.Stderr = p.Stderr
|
||||||
|
}
|
||||||
|
|
||||||
p.cmd.Args = []string{initName}
|
p.cmd.Args = []string{initName}
|
||||||
p.cmd.Stdin, p.cmd.Stdout, p.cmd.Stderr = p.Stdin, p.Stdout, p.Stderr
|
|
||||||
p.cmd.WaitDelay = p.WaitDelay
|
p.cmd.WaitDelay = p.WaitDelay
|
||||||
if p.Cancel != nil {
|
if p.Cancel != nil {
|
||||||
p.cmd.Cancel = func() error { return p.Cancel(p.cmd) }
|
p.cmd.Cancel = func() error { return p.Cancel(p.cmd) }
|
||||||
@@ -167,8 +224,7 @@ func (p *Container) Start() error {
|
|||||||
|
|
||||||
// place setup pipe before user supplied extra files, this is later restored by init
|
// place setup pipe before user supplied extra files, this is later restored by init
|
||||||
if fd, e, err := Setup(&p.cmd.ExtraFiles); err != nil {
|
if fd, e, err := Setup(&p.cmd.ExtraFiles); err != nil {
|
||||||
return wrapErrSuffix(err,
|
return &StartError{true, "set up params stream", err, false, false}
|
||||||
"cannot create shim setup pipe:")
|
|
||||||
} else {
|
} else {
|
||||||
p.setup = e
|
p.setup = e
|
||||||
p.cmd.Env = []string{setupEnv + "=" + strconv.Itoa(fd)}
|
p.cmd.Env = []string{setupEnv + "=" + strconv.Itoa(fd)}
|
||||||
@@ -183,8 +239,7 @@ func (p *Container) Start() error {
|
|||||||
done <- func() error { // setup depending on per-thread state must happen here
|
done <- func() error { // setup depending on per-thread state must happen here
|
||||||
// PR_SET_NO_NEW_PRIVS: depends on per-thread state but acts on all processes created from that thread
|
// PR_SET_NO_NEW_PRIVS: depends on per-thread state but acts on all processes created from that thread
|
||||||
if err := SetNoNewPrivs(); err != nil {
|
if err := SetNoNewPrivs(); err != nil {
|
||||||
return wrapErrSuffix(err,
|
return &StartError{true, "prctl(PR_SET_NO_NEW_PRIVS)", err, false, false}
|
||||||
"prctl(PR_SET_NO_NEW_PRIVS):")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// landlock: depends on per-thread state but acts on a process group
|
// landlock: depends on per-thread state but acts on a process group
|
||||||
@@ -200,28 +255,24 @@ func (p *Container) Start() error {
|
|||||||
// already covered by namespaces (pid)
|
// already covered by namespaces (pid)
|
||||||
goto landlockOut
|
goto landlockOut
|
||||||
}
|
}
|
||||||
return wrapErrSuffix(err,
|
return &StartError{false, "get landlock ABI", err, false, false}
|
||||||
"landlock does not appear to be enabled:")
|
|
||||||
} else if abi < 6 {
|
} else if abi < 6 {
|
||||||
if p.HostAbstract {
|
if p.HostAbstract {
|
||||||
// see above comment
|
// see above comment
|
||||||
goto landlockOut
|
goto landlockOut
|
||||||
}
|
}
|
||||||
return msg.WrapErr(ENOSYS,
|
return &StartError{false, "kernel version too old for LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET", ENOSYS, true, false}
|
||||||
"kernel version too old for LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET")
|
|
||||||
} else {
|
} else {
|
||||||
msg.Verbosef("landlock abi version %d", abi)
|
msg.Verbosef("landlock abi version %d", abi)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rulesetFd, err := rulesetAttr.Create(0); err != nil {
|
if rulesetFd, err := rulesetAttr.Create(0); err != nil {
|
||||||
return wrapErrSuffix(err,
|
return &StartError{true, "create landlock ruleset", err, false, false}
|
||||||
"cannot create landlock ruleset:")
|
|
||||||
} else {
|
} else {
|
||||||
msg.Verbosef("enforcing landlock ruleset %s", rulesetAttr)
|
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 wrapErrSuffix(err,
|
return &StartError{true, "enforce landlock ruleset", err, false, false}
|
||||||
"cannot enforce landlock ruleset:")
|
|
||||||
}
|
}
|
||||||
if err = Close(rulesetFd); err != nil {
|
if err = Close(rulesetFd); err != nil {
|
||||||
msg.Verbosef("cannot close landlock ruleset: %v", err)
|
msg.Verbosef("cannot close landlock ruleset: %v", err)
|
||||||
@@ -234,7 +285,7 @@ func (p *Container) Start() error {
|
|||||||
|
|
||||||
msg.Verbose("starting container init")
|
msg.Verbose("starting container init")
|
||||||
if err := p.cmd.Start(); err != nil {
|
if err := p.cmd.Start(); err != nil {
|
||||||
return msg.WrapErr(err, err.Error())
|
return &StartError{false, "start container init", err, false, true}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}()
|
}()
|
||||||
@@ -257,7 +308,7 @@ func (p *Container) Serve() error {
|
|||||||
|
|
||||||
if p.Path == nil {
|
if p.Path == nil {
|
||||||
p.cancel()
|
p.cancel()
|
||||||
return msg.WrapErr(EINVAL, "invalid executable pathname")
|
return &StartError{false, "invalid executable pathname", EINVAL, true, false}
|
||||||
}
|
}
|
||||||
|
|
||||||
// do not transmit nil
|
// do not transmit nil
|
||||||
@@ -285,7 +336,7 @@ func (p *Container) Serve() error {
|
|||||||
|
|
||||||
// Wait waits for the container init process to exit and releases any resources associated with the [Container].
|
// Wait waits for the container init process to exit and releases any resources associated with the [Container].
|
||||||
func (p *Container) Wait() error {
|
func (p *Container) Wait() error {
|
||||||
if p.cmd == nil {
|
if p.cmd == nil || p.cmd.Process == nil {
|
||||||
return EINVAL
|
return EINVAL
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,6 +348,36 @@ func (p *Container) Wait() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StdinPipe calls the [exec.Cmd] method with the same name.
|
||||||
|
func (p *Container) StdinPipe() (w io.WriteCloser, err error) {
|
||||||
|
if p.Stdin != nil {
|
||||||
|
return nil, errors.New("container: Stdin already set")
|
||||||
|
}
|
||||||
|
w, err = p.cmd.StdinPipe()
|
||||||
|
p.Stdin = p.cmd.Stdin
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// StdoutPipe calls the [exec.Cmd] method with the same name.
|
||||||
|
func (p *Container) StdoutPipe() (r io.ReadCloser, err error) {
|
||||||
|
if p.Stdout != nil {
|
||||||
|
return nil, errors.New("container: Stdout already set")
|
||||||
|
}
|
||||||
|
r, err = p.cmd.StdoutPipe()
|
||||||
|
p.Stdout = p.cmd.Stdout
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// StderrPipe calls the [exec.Cmd] method with the same name.
|
||||||
|
func (p *Container) StderrPipe() (r io.ReadCloser, err error) {
|
||||||
|
if p.Stderr != nil {
|
||||||
|
return nil, errors.New("container: Stderr already set")
|
||||||
|
}
|
||||||
|
r, err = p.cmd.StderrPipe()
|
||||||
|
p.Stderr = p.cmd.Stderr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Container) String() string {
|
func (p *Container) String() string {
|
||||||
return fmt.Sprintf("argv: %q, filter: %v, rules: %d, flags: %#x, presets: %#x",
|
return fmt.Sprintf("argv: %q, filter: %v, rules: %d, flags: %#x, presets: %#x",
|
||||||
p.Args, !p.SeccompDisable, len(p.SeccompRules), int(p.SeccompFlags), int(p.SeccompPresets))
|
p.Args, !p.SeccompDisable, len(p.SeccompRules), int(p.SeccompFlags), int(p.SeccompPresets))
|
||||||
@@ -312,7 +393,11 @@ 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) *Container {
|
||||||
return &Container{ctx: ctx, Params: Params{Ops: new(Ops)}}
|
p := &Container{ctx: ctx, Params: Params{Ops: new(Ops)}}
|
||||||
|
c, cancel := context.WithCancel(ctx)
|
||||||
|
p.cancel = cancel
|
||||||
|
p.cmd = exec.CommandContext(c, MustExecutable())
|
||||||
|
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.
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -26,6 +28,143 @@ import (
|
|||||||
"hakurei.app/ldd"
|
"hakurei.app/ldd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestStartError(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
s string
|
||||||
|
is error
|
||||||
|
isF error
|
||||||
|
msg string
|
||||||
|
}{
|
||||||
|
{"params env", &container.StartError{
|
||||||
|
Fatal: true,
|
||||||
|
Step: "set up params stream",
|
||||||
|
Err: container.ErrReceiveEnv,
|
||||||
|
},
|
||||||
|
"set up params stream: environment variable not set",
|
||||||
|
container.ErrReceiveEnv, syscall.EBADF,
|
||||||
|
"cannot set up params stream: environment variable not set"},
|
||||||
|
|
||||||
|
{"params", &container.StartError{
|
||||||
|
Fatal: true,
|
||||||
|
Step: "set up params stream",
|
||||||
|
Err: &os.SyscallError{Syscall: "pipe2", Err: syscall.EBADF},
|
||||||
|
},
|
||||||
|
"set up params stream pipe2: bad file descriptor",
|
||||||
|
syscall.EBADF, os.ErrInvalid,
|
||||||
|
"cannot set up params stream pipe2: bad file descriptor"},
|
||||||
|
|
||||||
|
{"PR_SET_NO_NEW_PRIVS", &container.StartError{
|
||||||
|
Fatal: true,
|
||||||
|
Step: "prctl(PR_SET_NO_NEW_PRIVS)",
|
||||||
|
Err: syscall.EPERM,
|
||||||
|
},
|
||||||
|
"prctl(PR_SET_NO_NEW_PRIVS): operation not permitted",
|
||||||
|
syscall.EPERM, syscall.EACCES,
|
||||||
|
"cannot prctl(PR_SET_NO_NEW_PRIVS): operation not permitted"},
|
||||||
|
|
||||||
|
{"landlock abi", &container.StartError{
|
||||||
|
Step: "get landlock ABI",
|
||||||
|
Err: syscall.ENOSYS,
|
||||||
|
},
|
||||||
|
"get landlock ABI: function not implemented",
|
||||||
|
syscall.ENOSYS, syscall.ENOEXEC,
|
||||||
|
"cannot get landlock ABI: function not implemented"},
|
||||||
|
|
||||||
|
{"landlock old", &container.StartError{
|
||||||
|
Step: "kernel version too old for LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET",
|
||||||
|
Err: syscall.ENOSYS,
|
||||||
|
Origin: true,
|
||||||
|
},
|
||||||
|
"kernel version too old for LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET",
|
||||||
|
syscall.ENOSYS, syscall.ENOSPC,
|
||||||
|
"kernel version too old for LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET"},
|
||||||
|
|
||||||
|
{"landlock create", &container.StartError{
|
||||||
|
Fatal: true,
|
||||||
|
Step: "create landlock ruleset",
|
||||||
|
Err: syscall.EBADFD,
|
||||||
|
},
|
||||||
|
"create landlock ruleset: file descriptor in bad state",
|
||||||
|
syscall.EBADFD, syscall.EBADF,
|
||||||
|
"cannot create landlock ruleset: file descriptor in bad state"},
|
||||||
|
|
||||||
|
{"landlock enforce", &container.StartError{
|
||||||
|
Fatal: true,
|
||||||
|
Step: "enforce landlock ruleset",
|
||||||
|
Err: syscall.ENOTRECOVERABLE,
|
||||||
|
},
|
||||||
|
"enforce landlock ruleset: state not recoverable",
|
||||||
|
syscall.ENOTRECOVERABLE, syscall.ETIMEDOUT,
|
||||||
|
"cannot enforce landlock ruleset: state not recoverable"},
|
||||||
|
|
||||||
|
{"start", &container.StartError{
|
||||||
|
Step: "start container init",
|
||||||
|
Err: &os.PathError{
|
||||||
|
Op: "fork/exec",
|
||||||
|
Path: "/proc/nonexistent",
|
||||||
|
Err: syscall.ENOENT,
|
||||||
|
}, Passthrough: true,
|
||||||
|
},
|
||||||
|
"fork/exec /proc/nonexistent: no such file or directory",
|
||||||
|
syscall.ENOENT, syscall.ENOSYS,
|
||||||
|
"cannot fork/exec /proc/nonexistent: no such file or directory"},
|
||||||
|
|
||||||
|
{"start syscall", &container.StartError{
|
||||||
|
Step: "start container init",
|
||||||
|
Err: &os.SyscallError{
|
||||||
|
Syscall: "open",
|
||||||
|
Err: syscall.ENOSYS,
|
||||||
|
}, Passthrough: true,
|
||||||
|
},
|
||||||
|
"open: function not implemented",
|
||||||
|
syscall.ENOSYS, syscall.ENOENT,
|
||||||
|
"cannot open: function not implemented"},
|
||||||
|
|
||||||
|
{"start other", &container.StartError{
|
||||||
|
Step: "start container init",
|
||||||
|
Err: &net.OpError{
|
||||||
|
Op: "dial",
|
||||||
|
Net: "unix",
|
||||||
|
Err: syscall.ECONNREFUSED,
|
||||||
|
}, Passthrough: true,
|
||||||
|
},
|
||||||
|
"dial unix: connection refused",
|
||||||
|
syscall.ECONNREFUSED, syscall.ECONNABORTED,
|
||||||
|
"dial unix: connection refused"},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Run("error", func(t *testing.T) {
|
||||||
|
if got := tc.err.Error(); got != tc.s {
|
||||||
|
t.Errorf("Error: %q, want %q", got, tc.s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("is", func(t *testing.T) {
|
||||||
|
if !errors.Is(tc.err, tc.is) {
|
||||||
|
t.Error("Is: unexpected false")
|
||||||
|
}
|
||||||
|
if errors.Is(tc.err, tc.isF) {
|
||||||
|
t.Errorf("Is: unexpected true")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("msg", func(t *testing.T) {
|
||||||
|
if got, ok := container.GetErrorMessage(tc.err); !ok {
|
||||||
|
if tc.msg != "" {
|
||||||
|
t.Errorf("GetErrorMessage: err does not implement MessageError")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} else if got != tc.msg {
|
||||||
|
t.Errorf("GetErrorMessage: %q, want %q", got, tc.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ignore = "\x00"
|
ignore = "\x00"
|
||||||
ignoreV = -1
|
ignoreV = -1
|
||||||
@@ -75,7 +214,7 @@ var containerTestCases = []struct {
|
|||||||
1000, 100, nil, 0, seccomp.PresetExt},
|
1000, 100, nil, 0, seccomp.PresetExt},
|
||||||
{"custom rules", true, true, true, false,
|
{"custom rules", true, true, true, false,
|
||||||
emptyOps, emptyMnt,
|
emptyOps, emptyMnt,
|
||||||
1, 31, []seccomp.NativeRule{{seccomp.ScmpSyscall(syscall.SYS_SETUID), seccomp.ScmpErrno(syscall.EPERM), nil}}, 0, seccomp.PresetExt},
|
1, 31, []seccomp.NativeRule{{Syscall: seccomp.ScmpSyscall(syscall.SYS_SETUID), Errno: seccomp.ScmpErrno(syscall.EPERM)}}, 0, seccomp.PresetExt},
|
||||||
|
|
||||||
{"tmpfs", true, false, false, true,
|
{"tmpfs", true, false, false, true,
|
||||||
earlyOps(new(container.Ops).
|
earlyOps(new(container.Ops).
|
||||||
@@ -100,6 +239,7 @@ var containerTestCases = []struct {
|
|||||||
ent("/tty", "/dev/tty", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
ent("/tty", "/dev/tty", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
||||||
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/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),
|
||||||
),
|
),
|
||||||
1971, 100, nil, 0, seccomp.PresetStrict},
|
1971, 100, nil, 0, seccomp.PresetStrict},
|
||||||
|
|
||||||
@@ -116,6 +256,7 @@ var containerTestCases = []struct {
|
|||||||
ent("/urandom", "/dev/urandom", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
ent("/urandom", "/dev/urandom", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
||||||
ent("/tty", "/dev/tty", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
ent("/tty", "/dev/tty", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
||||||
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),
|
||||||
),
|
),
|
||||||
1971, 100, nil, 0, seccomp.PresetStrict},
|
1971, 100, nil, 0, seccomp.PresetStrict},
|
||||||
|
|
||||||
@@ -215,9 +356,11 @@ func TestContainer(t *testing.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
|
||||||
if err := c.Wait(); !errors.Is(err, wantErr) {
|
if err := c.Wait(); !reflect.DeepEqual(err, wantErr) {
|
||||||
container.GetOutput().PrintBaseErr(err, "wait:")
|
if m, ok := container.InternalMessageFromError(err); ok {
|
||||||
t.Errorf("Wait: error = %v, want %v", err, wantErr)
|
t.Error(m)
|
||||||
|
}
|
||||||
|
t.Errorf("Wait: error = %#v, want %#v", err, wantErr)
|
||||||
}
|
}
|
||||||
if ps := c.ProcessState(); ps == nil {
|
if ps := c.ProcessState(); ps == nil {
|
||||||
t.Errorf("ProcessState unexpectedly returned nil")
|
t.Errorf("ProcessState unexpectedly returned nil")
|
||||||
@@ -231,7 +374,9 @@ func TestContainer(t *testing.T) {
|
|||||||
}, func(t *testing.T, c *container.Container) {
|
}, func(t *testing.T, c *container.Container) {
|
||||||
var exitError *exec.ExitError
|
var exitError *exec.ExitError
|
||||||
if err := c.Wait(); !errors.As(err, &exitError) {
|
if err := c.Wait(); !errors.As(err, &exitError) {
|
||||||
container.GetOutput().PrintBaseErr(err, "wait:")
|
if m, ok := container.InternalMessageFromError(err); ok {
|
||||||
|
t.Error(m)
|
||||||
|
}
|
||||||
t.Errorf("Wait: error = %v", err)
|
t.Errorf("Wait: error = %v", err)
|
||||||
}
|
}
|
||||||
if code := exitError.ExitCode(); code != blockExitCodeInterrupt {
|
if code := exitError.ExitCode(); code != blockExitCodeInterrupt {
|
||||||
@@ -311,17 +456,26 @@ func TestContainer(t *testing.T) {
|
|||||||
|
|
||||||
if err := c.Start(); err != nil {
|
if err := c.Start(); err != nil {
|
||||||
_, _ = output.WriteTo(os.Stdout)
|
_, _ = output.WriteTo(os.Stdout)
|
||||||
container.GetOutput().PrintBaseErr(err, "start:")
|
if m, ok := container.InternalMessageFromError(err); ok {
|
||||||
t.Fatalf("cannot start container: %v", err)
|
t.Fatal(m)
|
||||||
|
} else {
|
||||||
|
t.Fatalf("cannot start container: %v", err)
|
||||||
|
}
|
||||||
} else if err = c.Serve(); err != nil {
|
} else if err = c.Serve(); err != nil {
|
||||||
_, _ = output.WriteTo(os.Stdout)
|
_, _ = output.WriteTo(os.Stdout)
|
||||||
container.GetOutput().PrintBaseErr(err, "serve:")
|
if m, ok := container.InternalMessageFromError(err); ok {
|
||||||
t.Errorf("cannot serve setup params: %v", err)
|
t.Error(m)
|
||||||
|
} else {
|
||||||
|
t.Errorf("cannot serve setup params: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := c.Wait(); err != nil {
|
if err := c.Wait(); err != nil {
|
||||||
_, _ = output.WriteTo(os.Stdout)
|
_, _ = output.WriteTo(os.Stdout)
|
||||||
container.GetOutput().PrintBaseErr(err, "wait:")
|
if m, ok := container.InternalMessageFromError(err); ok {
|
||||||
t.Fatalf("wait: %v", err)
|
t.Fatal(m)
|
||||||
|
} else {
|
||||||
|
t.Fatalf("wait: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -374,11 +528,17 @@ func testContainerCancel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := c.Start(); err != nil {
|
if err := c.Start(); err != nil {
|
||||||
container.GetOutput().PrintBaseErr(err, "start:")
|
if m, ok := container.InternalMessageFromError(err); ok {
|
||||||
t.Fatalf("cannot start container: %v", err)
|
t.Fatal(m)
|
||||||
|
} else {
|
||||||
|
t.Fatalf("cannot start container: %v", err)
|
||||||
|
}
|
||||||
} else if err = c.Serve(); err != nil {
|
} else if err = c.Serve(); err != nil {
|
||||||
container.GetOutput().PrintBaseErr(err, "serve:")
|
if m, ok := container.InternalMessageFromError(err); ok {
|
||||||
t.Errorf("cannot serve setup params: %v", err)
|
t.Error(m)
|
||||||
|
} else {
|
||||||
|
t.Errorf("cannot serve setup params: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
<-ready
|
<-ready
|
||||||
cancel()
|
cancel()
|
||||||
|
|||||||
@@ -138,8 +138,6 @@ type syscallDispatcher interface {
|
|||||||
resume() bool
|
resume() bool
|
||||||
// beforeExit provides [Msg.BeforeExit].
|
// beforeExit provides [Msg.BeforeExit].
|
||||||
beforeExit()
|
beforeExit()
|
||||||
// printBaseErr provides [Msg.PrintBaseErr].
|
|
||||||
printBaseErr(err error, fallback string)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// direct implements syscallDispatcher on the current kernel.
|
// direct implements syscallDispatcher on the current kernel.
|
||||||
@@ -225,7 +223,7 @@ func (direct) pivotRoot(newroot, putold string) (err error) {
|
|||||||
return syscall.PivotRoot(newroot, putold)
|
return syscall.PivotRoot(newroot, putold)
|
||||||
}
|
}
|
||||||
func (direct) mount(source, target, fstype string, flags uintptr, data string) (err error) {
|
func (direct) mount(source, target, fstype string, flags uintptr, data string) (err error) {
|
||||||
return syscall.Mount(source, target, fstype, flags, data)
|
return mount(source, target, fstype, flags, data)
|
||||||
}
|
}
|
||||||
func (direct) unmount(target string, flags int) (err error) {
|
func (direct) unmount(target string, flags int) (err error) {
|
||||||
return syscall.Unmount(target, flags)
|
return syscall.Unmount(target, flags)
|
||||||
@@ -234,12 +232,11 @@ 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(format string, v ...any) { log.Printf(format, v...) }
|
||||||
func (direct) fatal(v ...any) { log.Fatal(v...) }
|
func (direct) fatal(v ...any) { log.Fatal(v...) }
|
||||||
func (direct) fatalf(format string, v ...any) { log.Fatalf(format, v...) }
|
func (direct) fatalf(format string, v ...any) { log.Fatalf(format, v...) }
|
||||||
func (direct) verbose(v ...any) { msg.Verbose(v...) }
|
func (direct) verbose(v ...any) { msg.Verbose(v...) }
|
||||||
func (direct) verbosef(format string, v ...any) { msg.Verbosef(format, v...) }
|
func (direct) verbosef(format string, v ...any) { msg.Verbosef(format, v...) }
|
||||||
func (direct) suspend() { msg.Suspend() }
|
func (direct) suspend() { msg.Suspend() }
|
||||||
func (direct) resume() bool { return msg.Resume() }
|
func (direct) resume() bool { return msg.Resume() }
|
||||||
func (direct) beforeExit() { msg.BeforeExit() }
|
func (direct) beforeExit() { msg.BeforeExit() }
|
||||||
func (direct) printBaseErr(err error, fallback string) { msg.PrintBaseErr(err, fallback) }
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package container
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
@@ -11,16 +10,14 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/seccomp"
|
||||||
|
"hakurei.app/container/stub"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errUnique = errors.New("unique error injected by the test suite")
|
|
||||||
|
|
||||||
type opValidTestCase struct {
|
type opValidTestCase struct {
|
||||||
name string
|
name string
|
||||||
op Op
|
op Op
|
||||||
@@ -28,9 +25,15 @@ type opValidTestCase struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func checkOpsValid(t *testing.T, testCases []opValidTestCase) {
|
func checkOpsValid(t *testing.T, testCases []opValidTestCase) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
t.Run("valid", func(t *testing.T) {
|
t.Run("valid", func(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
if got := tc.op.Valid(); got != tc.want {
|
if got := tc.op.Valid(); got != tc.want {
|
||||||
t.Errorf("Valid: %v, want %v", got, tc.want)
|
t.Errorf("Valid: %v, want %v", got, tc.want)
|
||||||
}
|
}
|
||||||
@@ -46,9 +49,15 @@ type opsBuilderTestCase struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func checkOpsBuilder(t *testing.T, testCases []opsBuilderTestCase) {
|
func checkOpsBuilder(t *testing.T, testCases []opsBuilderTestCase) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
t.Run("build", func(t *testing.T) {
|
t.Run("build", func(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
if !slices.EqualFunc(*tc.ops, tc.want, func(op Op, v Op) bool { return op.Is(v) }) {
|
if !slices.EqualFunc(*tc.ops, tc.want, func(op Op, v Op) bool { return op.Is(v) }) {
|
||||||
t.Errorf("Ops: %#v, want %#v", tc.ops, tc.want)
|
t.Errorf("Ops: %#v, want %#v", tc.ops, tc.want)
|
||||||
}
|
}
|
||||||
@@ -64,9 +73,15 @@ type opIsTestCase struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func checkOpIs(t *testing.T, testCases []opIsTestCase) {
|
func checkOpIs(t *testing.T, testCases []opIsTestCase) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
t.Run("is", func(t *testing.T) {
|
t.Run("is", func(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
if got := tc.op.Is(tc.v); got != tc.want {
|
if got := tc.op.Is(tc.v); got != tc.want {
|
||||||
t.Errorf("Is: %v, want %v", got, tc.want)
|
t.Errorf("Is: %v, want %v", got, tc.want)
|
||||||
}
|
}
|
||||||
@@ -84,16 +99,26 @@ type opMetaTestCase struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func checkOpMeta(t *testing.T, testCases []opMetaTestCase) {
|
func checkOpMeta(t *testing.T, testCases []opMetaTestCase) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
t.Run("meta", func(t *testing.T) {
|
t.Run("meta", func(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
t.Run("prefix", func(t *testing.T) {
|
t.Run("prefix", func(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
if got := tc.op.prefix(); got != tc.wantPrefix {
|
if got := tc.op.prefix(); got != tc.wantPrefix {
|
||||||
t.Errorf("prefix: %q, want %q", got, tc.wantPrefix)
|
t.Errorf("prefix: %q, want %q", got, tc.wantPrefix)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("string", func(t *testing.T) {
|
t.Run("string", func(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
if got := tc.op.String(); got != tc.wantString {
|
if got := tc.op.String(); got != tc.wantString {
|
||||||
t.Errorf("String: %s, want %s", got, tc.wantString)
|
t.Errorf("String: %s, want %s", got, tc.wantString)
|
||||||
}
|
}
|
||||||
@@ -103,23 +128,36 @@ func checkOpMeta(t *testing.T, testCases []opMetaTestCase) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// call initialises a [stub.Call].
|
||||||
|
// This keeps composites analysis happy without making the test cases too bloated.
|
||||||
|
func call(name string, args stub.ExpectArgs, ret any, err error) stub.Call {
|
||||||
|
return stub.Call{Name: name, Args: args, Ret: ret, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
type simpleTestCase struct {
|
type simpleTestCase struct {
|
||||||
name string
|
name string
|
||||||
f func(k syscallDispatcher) error
|
f func(k syscallDispatcher) error
|
||||||
want [][]kexpect
|
want stub.Expect
|
||||||
wantErr error
|
wantErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkSimple(t *testing.T, fname string, testCases []simpleTestCase) {
|
func checkSimple(t *testing.T, fname string, testCases []simpleTestCase) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
defer handleExitStub()
|
t.Helper()
|
||||||
k := &kstub{t: t, want: tc.want, wg: new(sync.WaitGroup)}
|
|
||||||
if err := tc.f(k); !errors.Is(err, tc.wantErr) {
|
wait4signal := make(chan struct{})
|
||||||
|
k := &kstub{wait4signal, stub.New(t, func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { return &kstub{wait4signal, s} }, tc.want)}
|
||||||
|
defer stub.HandleExit(t)
|
||||||
|
if err := tc.f(k); !reflect.DeepEqual(err, tc.wantErr) {
|
||||||
t.Errorf("%s: error = %v, want %v", fname, err, tc.wantErr)
|
t.Errorf("%s: error = %v, want %v", fname, err, tc.wantErr)
|
||||||
}
|
}
|
||||||
k.handleIncomplete(func(k *kstub) {
|
k.VisitIncomplete(func(s *stub.Stub[syscallDispatcher]) {
|
||||||
t.Errorf("%s: %d calls, want %d (track %d)", fname, k.pos, len(k.want[k.track]), k.track)
|
t.Helper()
|
||||||
|
|
||||||
|
t.Errorf("%s: %d calls, want %d", fname, s.Pos(), s.Len())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -130,36 +168,45 @@ type opBehaviourTestCase struct {
|
|||||||
params *Params
|
params *Params
|
||||||
op Op
|
op Op
|
||||||
|
|
||||||
early []kexpect
|
early []stub.Call
|
||||||
wantErrEarly error
|
wantErrEarly error
|
||||||
|
|
||||||
apply []kexpect
|
apply []stub.Call
|
||||||
wantErrApply error
|
wantErrApply error
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
|
func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
t.Run("behaviour", func(t *testing.T) {
|
t.Run("behaviour", func(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
defer handleExitStub()
|
t.Helper()
|
||||||
|
|
||||||
state := &setupState{Params: tc.params}
|
state := &setupState{Params: tc.params}
|
||||||
k := &kstub{t: t, want: [][]kexpect{slices.Concat(tc.early, []kexpect{{name: "\x00"}}, tc.apply)}, wg: new(sync.WaitGroup)}
|
k := &kstub{nil, stub.New(t,
|
||||||
|
func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { return &kstub{nil, s} },
|
||||||
|
stub.Expect{Calls: slices.Concat(tc.early, []stub.Call{{Name: stub.CallSeparator}}, tc.apply)},
|
||||||
|
)}
|
||||||
|
defer stub.HandleExit(t)
|
||||||
errEarly := tc.op.early(state, k)
|
errEarly := tc.op.early(state, k)
|
||||||
k.expect("\x00")
|
k.Expects(stub.CallSeparator)
|
||||||
if !errors.Is(errEarly, tc.wantErrEarly) {
|
if !reflect.DeepEqual(errEarly, tc.wantErrEarly) {
|
||||||
t.Errorf("early: error = %v, want %v", errEarly, tc.wantErrEarly)
|
t.Errorf("early: error = %v, want %v", errEarly, tc.wantErrEarly)
|
||||||
}
|
}
|
||||||
if errEarly != nil {
|
if errEarly != nil {
|
||||||
goto out
|
goto out
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tc.op.apply(state, k); !errors.Is(err, tc.wantErrApply) {
|
if err := tc.op.apply(state, k); !reflect.DeepEqual(err, tc.wantErrApply) {
|
||||||
t.Errorf("apply: error = %v, want %v", err, tc.wantErrApply)
|
t.Errorf("apply: error = %v, want %v", err, tc.wantErrApply)
|
||||||
}
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
k.handleIncomplete(func(k *kstub) {
|
k.VisitIncomplete(func(s *stub.Stub[syscallDispatcher]) {
|
||||||
count := k.pos - 1 // separator
|
count := k.Pos() - 1 // separator
|
||||||
if count < len(tc.early) {
|
if count < len(tc.early) {
|
||||||
t.Errorf("early: %d calls, want %d", count, len(tc.early))
|
t.Errorf("early: %d calls, want %d", count, len(tc.early))
|
||||||
} else {
|
} else {
|
||||||
@@ -226,8 +273,6 @@ func (writeErrOsFile) Stat() (fs.FileInfo, error) { panic("unreachable") }
|
|||||||
func (writeErrOsFile) Read([]byte) (int, error) { panic("unreachable") }
|
func (writeErrOsFile) Read([]byte) (int, error) { panic("unreachable") }
|
||||||
func (writeErrOsFile) Close() error { panic("unreachable") }
|
func (writeErrOsFile) Close() error { panic("unreachable") }
|
||||||
|
|
||||||
type expectArgs = [5]any
|
|
||||||
|
|
||||||
type isDirFi bool
|
type isDirFi bool
|
||||||
|
|
||||||
func (isDirFi) Name() string { panic("unreachable") }
|
func (isDirFi) Name() string { panic("unreachable") }
|
||||||
@@ -252,184 +297,94 @@ func (nameDentry) IsDir() bool { panic("unreachable") }
|
|||||||
func (nameDentry) Type() fs.FileMode { panic("unreachable") }
|
func (nameDentry) Type() fs.FileMode { panic("unreachable") }
|
||||||
func (nameDentry) Info() (fs.FileInfo, error) { panic("unreachable") }
|
func (nameDentry) Info() (fs.FileInfo, error) { panic("unreachable") }
|
||||||
|
|
||||||
type kexpect struct {
|
const (
|
||||||
name string
|
// magicWait4Signal must be used in a single pair of signal and wait4 calls across two goroutines
|
||||||
args expectArgs
|
// originating from the same toplevel kstub.
|
||||||
ret any
|
// To enable this behaviour this value must be the last element of the args field in the wait4 call
|
||||||
err error
|
// and the ret value of the signal call.
|
||||||
}
|
magicWait4Signal = 0xdef
|
||||||
|
)
|
||||||
func (k *kexpect) error(ok ...bool) error {
|
|
||||||
if !slices.Contains(ok, false) {
|
|
||||||
return k.err
|
|
||||||
}
|
|
||||||
return syscall.ENOTRECOVERABLE
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleExitStub() {
|
|
||||||
r := recover()
|
|
||||||
if r == 0xdeadbeef {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if r != nil {
|
|
||||||
panic(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type kstub struct {
|
type kstub struct {
|
||||||
t *testing.T
|
wait4signal chan struct{}
|
||||||
|
*stub.Stub[syscallDispatcher]
|
||||||
want [][]kexpect
|
|
||||||
// pos is the current position in want[track].
|
|
||||||
pos int
|
|
||||||
// track is the current active want.
|
|
||||||
track int
|
|
||||||
// sub stores addresses of kstub created by new.
|
|
||||||
sub []*kstub
|
|
||||||
// wg waits for all descendants to complete.
|
|
||||||
wg *sync.WaitGroup
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleIncomplete calls f on an incomplete k and all its descendants.
|
func (k *kstub) new(f func(k syscallDispatcher)) { k.Helper(); k.New(f) }
|
||||||
func (k *kstub) handleIncomplete(f func(k *kstub)) {
|
|
||||||
k.wg.Wait()
|
|
||||||
|
|
||||||
if k.want != nil && len(k.want[k.track]) != k.pos {
|
func (k *kstub) lockOSThread() { k.Helper(); k.Expects("lockOSThread") }
|
||||||
f(k)
|
|
||||||
}
|
|
||||||
for _, sk := range k.sub {
|
|
||||||
sk.handleIncomplete(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// expect checks name and returns the current kexpect and advances pos.
|
|
||||||
func (k *kstub) expect(name string) (expect *kexpect) {
|
|
||||||
if len(k.want[k.track]) == k.pos {
|
|
||||||
k.t.Fatal("expect: want too short")
|
|
||||||
}
|
|
||||||
expect = &k.want[k.track][k.pos]
|
|
||||||
if name != expect.name {
|
|
||||||
if expect.name == "\x00" {
|
|
||||||
k.t.Fatalf("expect: func = %s, separator overrun", name)
|
|
||||||
}
|
|
||||||
if name == "\x00" {
|
|
||||||
k.t.Fatalf("expect: separator, want %s", expect.name)
|
|
||||||
}
|
|
||||||
k.t.Fatalf("expect: func = %s, want %s", name, expect.name)
|
|
||||||
}
|
|
||||||
k.pos++
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkArg checks an argument comparable with the == operator. Avoid using this with pointers.
|
|
||||||
func checkArg[T comparable](k *kstub, arg string, got T, n int) bool {
|
|
||||||
if k.pos == 0 {
|
|
||||||
panic("invalid call to checkArg")
|
|
||||||
}
|
|
||||||
expect := k.want[k.track][k.pos-1]
|
|
||||||
want, ok := expect.args[n].(T)
|
|
||||||
if !ok || got != want {
|
|
||||||
k.t.Errorf("%s: %s = %#v, want %#v (%d)", expect.name, arg, got, want, k.pos-1)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkArgReflect checks an argument of any type.
|
|
||||||
func checkArgReflect(k *kstub, arg string, got any, n int) bool {
|
|
||||||
if k.pos == 0 {
|
|
||||||
panic("invalid call to checkArgReflect")
|
|
||||||
}
|
|
||||||
expect := k.want[k.track][k.pos-1]
|
|
||||||
want := expect.args[n]
|
|
||||||
if !reflect.DeepEqual(got, want) {
|
|
||||||
k.t.Errorf("%s: %s = %#v, want %#v (%d)", expect.name, arg, got, want, k.pos-1)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *kstub) new(f func(k syscallDispatcher)) {
|
|
||||||
k.expect("new")
|
|
||||||
if len(k.want) <= k.track+1 {
|
|
||||||
k.t.Fatalf("new: track overrun")
|
|
||||||
}
|
|
||||||
sk := &kstub{t: k.t, want: k.want, track: len(k.sub) + 1, wg: k.wg}
|
|
||||||
k.sub = append(k.sub, sk)
|
|
||||||
k.wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer k.wg.Done()
|
|
||||||
defer handleExitStub()
|
|
||||||
f(sk)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *kstub) lockOSThread() { k.expect("lockOSThread") }
|
|
||||||
|
|
||||||
func (k *kstub) setPtracer(pid uintptr) error {
|
func (k *kstub) setPtracer(pid uintptr) error {
|
||||||
return k.expect("setPtracer").error(
|
k.Helper()
|
||||||
checkArg(k, "pid", pid, 0))
|
return k.Expects("setPtracer").Error(
|
||||||
|
stub.CheckArg(k.Stub, "pid", pid, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) setDumpable(dumpable uintptr) error {
|
func (k *kstub) setDumpable(dumpable uintptr) error {
|
||||||
return k.expect("setDumpable").error(
|
k.Helper()
|
||||||
checkArg(k, "dumpable", dumpable, 0))
|
return k.Expects("setDumpable").Error(
|
||||||
|
stub.CheckArg(k.Stub, "dumpable", dumpable, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) setNoNewPrivs() error { return k.expect("setNoNewPrivs").err }
|
func (k *kstub) setNoNewPrivs() error { k.Helper(); return k.Expects("setNoNewPrivs").Err }
|
||||||
func (k *kstub) lastcap() uintptr { return k.expect("lastcap").ret.(uintptr) }
|
func (k *kstub) lastcap() uintptr { k.Helper(); 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 {
|
||||||
return k.expect("capset").error(
|
k.Helper()
|
||||||
checkArgReflect(k, "hdrp", hdrp, 0),
|
return k.Expects("capset").Error(
|
||||||
checkArgReflect(k, "datap", datap, 1))
|
stub.CheckArgReflect(k.Stub, "hdrp", hdrp, 0),
|
||||||
|
stub.CheckArgReflect(k.Stub, "datap", datap, 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) capBoundingSetDrop(cap uintptr) error {
|
func (k *kstub) capBoundingSetDrop(cap uintptr) error {
|
||||||
return k.expect("capBoundingSetDrop").error(
|
k.Helper()
|
||||||
checkArg(k, "cap", cap, 0))
|
return k.Expects("capBoundingSetDrop").Error(
|
||||||
|
stub.CheckArg(k.Stub, "cap", cap, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) capAmbientClearAll() error { return k.expect("capAmbientClearAll").err }
|
func (k *kstub) capAmbientClearAll() error { k.Helper(); return k.Expects("capAmbientClearAll").Err }
|
||||||
|
|
||||||
func (k *kstub) capAmbientRaise(cap uintptr) error {
|
func (k *kstub) capAmbientRaise(cap uintptr) error {
|
||||||
return k.expect("capAmbientRaise").error(
|
k.Helper()
|
||||||
checkArg(k, "cap", cap, 0))
|
return k.Expects("capAmbientRaise").Error(
|
||||||
|
stub.CheckArg(k.Stub, "cap", cap, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) isatty(fd int) bool {
|
func (k *kstub) isatty(fd int) bool {
|
||||||
expect := k.expect("isatty")
|
k.Helper()
|
||||||
if !checkArg(k, "fd", fd, 0) {
|
expect := k.Expects("isatty")
|
||||||
k.t.FailNow()
|
if !stub.CheckArg(k.Stub, "fd", fd, 0) {
|
||||||
|
k.FailNow()
|
||||||
}
|
}
|
||||||
return expect.ret.(bool)
|
return expect.Ret.(bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) receive(key string, e any, fdp *uintptr) (closeFunc func() error, err error) {
|
func (k *kstub) receive(key string, e any, fdp *uintptr) (closeFunc func() error, err error) {
|
||||||
expect := k.expect("receive")
|
k.Helper()
|
||||||
|
expect := k.Expects("receive")
|
||||||
|
|
||||||
var closed bool
|
var closed bool
|
||||||
closeFunc = func() error {
|
closeFunc = func() error {
|
||||||
if closed {
|
if closed {
|
||||||
k.t.Error("closeFunc called more than once")
|
k.Error("closeFunc called more than once")
|
||||||
return os.ErrClosed
|
return os.ErrClosed
|
||||||
}
|
}
|
||||||
closed = true
|
closed = true
|
||||||
|
|
||||||
if expect.ret != nil {
|
if expect.Ret != nil {
|
||||||
// use return stored in kexpect for closeFunc instead
|
// use return stored in kexpect for closeFunc instead
|
||||||
return expect.ret.(error)
|
return expect.Ret.(error)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
err = expect.error(
|
err = expect.Error(
|
||||||
checkArg(k, "key", key, 0),
|
stub.CheckArg(k.Stub, "key", key, 0),
|
||||||
checkArgReflect(k, "e", e, 1),
|
stub.CheckArgReflect(k.Stub, "e", e, 1),
|
||||||
checkArgReflect(k, "fdp", fdp, 2))
|
stub.CheckArgReflect(k.Stub, "fdp", fdp, 2))
|
||||||
|
|
||||||
// 3 is unused so stores params
|
// 3 is unused so stores params
|
||||||
if expect.args[3] != nil {
|
if expect.Args[3] != nil {
|
||||||
if v, ok := expect.args[3].(*initParams); ok && v != nil {
|
if v, ok := expect.Args[3].(*initParams); ok && v != nil {
|
||||||
if p, ok0 := e.(*initParams); ok0 && p != nil {
|
if p, ok0 := e.(*initParams); ok0 && p != nil {
|
||||||
*p = *v
|
*p = *v
|
||||||
}
|
}
|
||||||
@@ -437,8 +392,8 @@ func (k *kstub) receive(key string, e any, fdp *uintptr) (closeFunc func() error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 4 is unused so stores fd
|
// 4 is unused so stores fd
|
||||||
if expect.args[4] != nil {
|
if expect.Args[4] != nil {
|
||||||
if v, ok := expect.args[4].(uintptr); ok && v >= 3 {
|
if v, ok := expect.Args[4].(uintptr); ok && v >= 3 {
|
||||||
if fdp != nil {
|
if fdp != nil {
|
||||||
*fdp = v
|
*fdp = v
|
||||||
}
|
}
|
||||||
@@ -449,246 +404,291 @@ func (k *kstub) receive(key string, e any, fdp *uintptr) (closeFunc func() error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) bindMount(source, target string, flags uintptr, eq bool) error {
|
func (k *kstub) bindMount(source, target string, flags uintptr, eq bool) error {
|
||||||
return k.expect("bindMount").error(
|
k.Helper()
|
||||||
checkArg(k, "source", source, 0),
|
return k.Expects("bindMount").Error(
|
||||||
checkArg(k, "target", target, 1),
|
stub.CheckArg(k.Stub, "source", source, 0),
|
||||||
checkArg(k, "flags", flags, 2),
|
stub.CheckArg(k.Stub, "target", target, 1),
|
||||||
checkArg(k, "eq", eq, 3))
|
stub.CheckArg(k.Stub, "flags", flags, 2),
|
||||||
|
stub.CheckArg(k.Stub, "eq", eq, 3))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) remount(target string, flags uintptr) error {
|
func (k *kstub) remount(target string, flags uintptr) error {
|
||||||
return k.expect("remount").error(
|
k.Helper()
|
||||||
checkArg(k, "target", target, 0),
|
return k.Expects("remount").Error(
|
||||||
checkArg(k, "flags", flags, 1))
|
stub.CheckArg(k.Stub, "target", target, 0),
|
||||||
|
stub.CheckArg(k.Stub, "flags", flags, 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error {
|
func (k *kstub) mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error {
|
||||||
return k.expect("mountTmpfs").error(
|
k.Helper()
|
||||||
checkArg(k, "fsname", fsname, 0),
|
return k.Expects("mountTmpfs").Error(
|
||||||
checkArg(k, "target", target, 1),
|
stub.CheckArg(k.Stub, "fsname", fsname, 0),
|
||||||
checkArg(k, "flags", flags, 2),
|
stub.CheckArg(k.Stub, "target", target, 1),
|
||||||
checkArg(k, "size", size, 3),
|
stub.CheckArg(k.Stub, "flags", flags, 2),
|
||||||
checkArg(k, "perm", perm, 4))
|
stub.CheckArg(k.Stub, "size", size, 3),
|
||||||
|
stub.CheckArg(k.Stub, "perm", perm, 4))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) ensureFile(name string, perm, pperm os.FileMode) error {
|
func (k *kstub) ensureFile(name string, perm, pperm os.FileMode) error {
|
||||||
|
k.Helper()
|
||||||
return k.expect("ensureFile").error(
|
return k.Expects("ensureFile").Error(
|
||||||
checkArg(k, "name", name, 0),
|
stub.CheckArg(k.Stub, "name", name, 0),
|
||||||
checkArg(k, "perm", perm, 1),
|
stub.CheckArg(k.Stub, "perm", perm, 1),
|
||||||
checkArg(k, "pperm", pperm, 2))
|
stub.CheckArg(k.Stub, "pperm", pperm, 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) seccompLoad(rules []seccomp.NativeRule, flags seccomp.ExportFlag) error {
|
func (k *kstub) seccompLoad(rules []seccomp.NativeRule, flags seccomp.ExportFlag) error {
|
||||||
return k.expect("seccompLoad").error(
|
k.Helper()
|
||||||
checkArgReflect(k, "rules", rules, 0),
|
return k.Expects("seccompLoad").Error(
|
||||||
checkArg(k, "flags", flags, 1))
|
stub.CheckArgReflect(k.Stub, "rules", rules, 0),
|
||||||
|
stub.CheckArg(k.Stub, "flags", flags, 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) notify(c chan<- os.Signal, sig ...os.Signal) {
|
func (k *kstub) notify(c chan<- os.Signal, sig ...os.Signal) {
|
||||||
expect := k.expect("notify")
|
k.Helper()
|
||||||
if c == nil || expect.error(
|
expect := k.Expects("notify")
|
||||||
checkArgReflect(k, "sig", sig, 1)) != nil {
|
if c == nil || expect.Error(
|
||||||
k.t.FailNow()
|
stub.CheckArgReflect(k.Stub, "sig", sig, 1)) != nil {
|
||||||
|
k.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
// export channel for external instrumentation
|
// export channel for external instrumentation
|
||||||
if chanf, ok := expect.args[0].(func(c chan<- os.Signal)); ok && chanf != nil {
|
if chanf, ok := expect.Args[0].(func(c chan<- os.Signal)); ok && chanf != nil {
|
||||||
chanf(c)
|
chanf(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) start(c *exec.Cmd) error {
|
func (k *kstub) start(c *exec.Cmd) error {
|
||||||
expect := k.expect("start")
|
k.Helper()
|
||||||
err := expect.error(
|
expect := k.Expects("start")
|
||||||
checkArg(k, "c.Path", c.Path, 0),
|
err := expect.Error(
|
||||||
checkArgReflect(k, "c.Args", c.Args, 1),
|
stub.CheckArg(k.Stub, "c.Path", c.Path, 0),
|
||||||
checkArgReflect(k, "c.Env", c.Env, 2),
|
stub.CheckArgReflect(k.Stub, "c.Args", c.Args, 1),
|
||||||
checkArg(k, "c.Dir", c.Dir, 3))
|
stub.CheckArgReflect(k.Stub, "c.Env", c.Env, 2),
|
||||||
|
stub.CheckArg(k.Stub, "c.Dir", c.Dir, 3))
|
||||||
|
|
||||||
if process, ok := expect.ret.(*os.Process); ok && process != nil {
|
if process, ok := expect.Ret.(*os.Process); ok && process != nil {
|
||||||
c.Process = process
|
c.Process = process
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) signal(c *exec.Cmd, sig os.Signal) error {
|
func (k *kstub) signal(c *exec.Cmd, sig os.Signal) error {
|
||||||
return k.expect("signal").error(
|
k.Helper()
|
||||||
checkArg(k, "c.Path", c.Path, 0),
|
expect := k.Expects("signal")
|
||||||
checkArgReflect(k, "c.Args", c.Args, 1),
|
if v, ok := expect.Ret.(int); ok && v == magicWait4Signal {
|
||||||
checkArgReflect(k, "c.Env", c.Env, 2),
|
if k.wait4signal == nil {
|
||||||
checkArg(k, "c.Dir", c.Dir, 3),
|
panic("kstub not initialised for wait4 simulation")
|
||||||
checkArg(k, "sig", sig, 4))
|
}
|
||||||
|
defer func() { close(k.wait4signal) }()
|
||||||
|
}
|
||||||
|
return expect.Error(
|
||||||
|
stub.CheckArg(k.Stub, "c.Path", c.Path, 0),
|
||||||
|
stub.CheckArgReflect(k.Stub, "c.Args", c.Args, 1),
|
||||||
|
stub.CheckArgReflect(k.Stub, "c.Env", c.Env, 2),
|
||||||
|
stub.CheckArg(k.Stub, "c.Dir", c.Dir, 3),
|
||||||
|
stub.CheckArg(k.Stub, "sig", sig, 4))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) evalSymlinks(path string) (string, error) {
|
func (k *kstub) evalSymlinks(path string) (string, error) {
|
||||||
expect := k.expect("evalSymlinks")
|
k.Helper()
|
||||||
return expect.ret.(string), expect.error(
|
expect := k.Expects("evalSymlinks")
|
||||||
checkArg(k, "path", path, 0))
|
return expect.Ret.(string), expect.Error(
|
||||||
|
stub.CheckArg(k.Stub, "path", path, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) exit(code int) {
|
func (k *kstub) exit(code int) {
|
||||||
k.expect("exit")
|
k.Helper()
|
||||||
if !checkArg(k, "code", code, 0) {
|
k.Expects("exit")
|
||||||
k.t.FailNow()
|
if !stub.CheckArg(k.Stub, "code", code, 0) {
|
||||||
|
k.FailNow()
|
||||||
}
|
}
|
||||||
panic(0xdeadbeef)
|
panic(stub.PanicExit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) getpid() int { return k.expect("getpid").ret.(int) }
|
func (k *kstub) getpid() int { k.Helper(); return k.Expects("getpid").Ret.(int) }
|
||||||
|
|
||||||
func (k *kstub) stat(name string) (os.FileInfo, error) {
|
func (k *kstub) stat(name string) (os.FileInfo, error) {
|
||||||
expect := k.expect("stat")
|
k.Helper()
|
||||||
return expect.ret.(os.FileInfo), expect.error(
|
expect := k.Expects("stat")
|
||||||
checkArg(k, "name", name, 0))
|
return expect.Ret.(os.FileInfo), expect.Error(
|
||||||
|
stub.CheckArg(k.Stub, "name", name, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) mkdir(name string, perm os.FileMode) error {
|
func (k *kstub) mkdir(name string, perm os.FileMode) error {
|
||||||
return k.expect("mkdir").error(
|
k.Helper()
|
||||||
checkArg(k, "name", name, 0),
|
return k.Expects("mkdir").Error(
|
||||||
checkArg(k, "perm", perm, 1))
|
stub.CheckArg(k.Stub, "name", name, 0),
|
||||||
|
stub.CheckArg(k.Stub, "perm", perm, 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) mkdirTemp(dir, pattern string) (string, error) {
|
func (k *kstub) mkdirTemp(dir, pattern string) (string, error) {
|
||||||
expect := k.expect("mkdirTemp")
|
k.Helper()
|
||||||
return expect.ret.(string), expect.error(
|
expect := k.Expects("mkdirTemp")
|
||||||
checkArg(k, "dir", dir, 0),
|
return expect.Ret.(string), expect.Error(
|
||||||
checkArg(k, "pattern", pattern, 1))
|
stub.CheckArg(k.Stub, "dir", dir, 0),
|
||||||
|
stub.CheckArg(k.Stub, "pattern", pattern, 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) mkdirAll(path string, perm os.FileMode) error {
|
func (k *kstub) mkdirAll(path string, perm os.FileMode) error {
|
||||||
return k.expect("mkdirAll").error(
|
k.Helper()
|
||||||
checkArg(k, "path", path, 0),
|
return k.Expects("mkdirAll").Error(
|
||||||
checkArg(k, "perm", perm, 1))
|
stub.CheckArg(k.Stub, "path", path, 0),
|
||||||
|
stub.CheckArg(k.Stub, "perm", perm, 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) readdir(name string) ([]os.DirEntry, error) {
|
func (k *kstub) readdir(name string) ([]os.DirEntry, error) {
|
||||||
expect := k.expect("readdir")
|
k.Helper()
|
||||||
return expect.ret.([]os.DirEntry), expect.error(
|
expect := k.Expects("readdir")
|
||||||
checkArg(k, "name", name, 0))
|
return expect.Ret.([]os.DirEntry), expect.Error(
|
||||||
|
stub.CheckArg(k.Stub, "name", name, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) openNew(name string) (osFile, error) {
|
func (k *kstub) openNew(name string) (osFile, error) {
|
||||||
expect := k.expect("openNew")
|
k.Helper()
|
||||||
return expect.ret.(osFile), expect.error(
|
expect := k.Expects("openNew")
|
||||||
checkArg(k, "name", name, 0))
|
return expect.Ret.(osFile), expect.Error(
|
||||||
|
stub.CheckArg(k.Stub, "name", name, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) writeFile(name string, data []byte, perm os.FileMode) error {
|
func (k *kstub) writeFile(name string, data []byte, perm os.FileMode) error {
|
||||||
return k.expect("writeFile").error(
|
k.Helper()
|
||||||
checkArg(k, "name", name, 0),
|
return k.Expects("writeFile").Error(
|
||||||
checkArgReflect(k, "data", data, 1),
|
stub.CheckArg(k.Stub, "name", name, 0),
|
||||||
checkArg(k, "perm", perm, 2))
|
stub.CheckArgReflect(k.Stub, "data", data, 1),
|
||||||
|
stub.CheckArg(k.Stub, "perm", perm, 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) createTemp(dir, pattern string) (osFile, error) {
|
func (k *kstub) createTemp(dir, pattern string) (osFile, error) {
|
||||||
expect := k.expect("createTemp")
|
k.Helper()
|
||||||
return expect.ret.(osFile), expect.error(
|
expect := k.Expects("createTemp")
|
||||||
checkArg(k, "dir", dir, 0),
|
return expect.Ret.(osFile), expect.Error(
|
||||||
checkArg(k, "pattern", pattern, 1))
|
stub.CheckArg(k.Stub, "dir", dir, 0),
|
||||||
|
stub.CheckArg(k.Stub, "pattern", pattern, 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) remove(name string) error {
|
func (k *kstub) remove(name string) error {
|
||||||
return k.expect("remove").error(
|
k.Helper()
|
||||||
checkArg(k, "name", name, 0))
|
return k.Expects("remove").Error(
|
||||||
|
stub.CheckArg(k.Stub, "name", name, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) newFile(fd uintptr, name string) *os.File {
|
func (k *kstub) newFile(fd uintptr, name string) *os.File {
|
||||||
expect := k.expect("newFile")
|
k.Helper()
|
||||||
if expect.error(
|
expect := k.Expects("newFile")
|
||||||
checkArg(k, "fd", fd, 0),
|
if expect.Error(
|
||||||
checkArg(k, "name", name, 1)) != nil {
|
stub.CheckArg(k.Stub, "fd", fd, 0),
|
||||||
k.t.FailNow()
|
stub.CheckArg(k.Stub, "name", name, 1)) != nil {
|
||||||
|
k.FailNow()
|
||||||
}
|
}
|
||||||
return expect.ret.(*os.File)
|
return expect.Ret.(*os.File)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) symlink(oldname, newname string) error {
|
func (k *kstub) symlink(oldname, newname string) error {
|
||||||
return k.expect("symlink").error(
|
k.Helper()
|
||||||
checkArg(k, "oldname", oldname, 0),
|
return k.Expects("symlink").Error(
|
||||||
checkArg(k, "newname", newname, 1))
|
stub.CheckArg(k.Stub, "oldname", oldname, 0),
|
||||||
|
stub.CheckArg(k.Stub, "newname", newname, 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) readlink(name string) (string, error) {
|
func (k *kstub) readlink(name string) (string, error) {
|
||||||
expect := k.expect("readlink")
|
k.Helper()
|
||||||
return expect.ret.(string), expect.error(
|
expect := k.Expects("readlink")
|
||||||
checkArg(k, "name", name, 0))
|
return expect.Ret.(string), expect.Error(
|
||||||
|
stub.CheckArg(k.Stub, "name", name, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) umask(mask int) (oldmask int) {
|
func (k *kstub) umask(mask int) (oldmask int) {
|
||||||
expect := k.expect("umask")
|
k.Helper()
|
||||||
if !checkArg(k, "mask", mask, 0) {
|
expect := k.Expects("umask")
|
||||||
k.t.FailNow()
|
if !stub.CheckArg(k.Stub, "mask", mask, 0) {
|
||||||
|
k.FailNow()
|
||||||
}
|
}
|
||||||
return expect.ret.(int)
|
return expect.Ret.(int)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) sethostname(p []byte) (err error) {
|
func (k *kstub) sethostname(p []byte) (err error) {
|
||||||
return k.expect("sethostname").error(
|
k.Helper()
|
||||||
checkArgReflect(k, "p", p, 0))
|
return k.Expects("sethostname").Error(
|
||||||
|
stub.CheckArgReflect(k.Stub, "p", p, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) chdir(path string) (err error) {
|
func (k *kstub) chdir(path string) (err error) {
|
||||||
return k.expect("chdir").error(
|
k.Helper()
|
||||||
checkArg(k, "path", path, 0))
|
return k.Expects("chdir").Error(
|
||||||
|
stub.CheckArg(k.Stub, "path", path, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) fchdir(fd int) (err error) {
|
func (k *kstub) fchdir(fd int) (err error) {
|
||||||
return k.expect("fchdir").error(
|
k.Helper()
|
||||||
checkArg(k, "fd", fd, 0))
|
return k.Expects("fchdir").Error(
|
||||||
|
stub.CheckArg(k.Stub, "fd", fd, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) open(path string, mode int, perm uint32) (fd int, err error) {
|
func (k *kstub) open(path string, mode int, perm uint32) (fd int, err error) {
|
||||||
expect := k.expect("open")
|
k.Helper()
|
||||||
return expect.ret.(int), expect.error(
|
expect := k.Expects("open")
|
||||||
checkArg(k, "path", path, 0),
|
return expect.Ret.(int), expect.Error(
|
||||||
checkArg(k, "mode", mode, 1),
|
stub.CheckArg(k.Stub, "path", path, 0),
|
||||||
checkArg(k, "perm", perm, 2))
|
stub.CheckArg(k.Stub, "mode", mode, 1),
|
||||||
|
stub.CheckArg(k.Stub, "perm", perm, 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) close(fd int) (err error) {
|
func (k *kstub) close(fd int) (err error) {
|
||||||
return k.expect("close").error(
|
k.Helper()
|
||||||
checkArg(k, "fd", fd, 0))
|
return k.Expects("close").Error(
|
||||||
|
stub.CheckArg(k.Stub, "fd", fd, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) pivotRoot(newroot, putold string) (err error) {
|
func (k *kstub) pivotRoot(newroot, putold string) (err error) {
|
||||||
return k.expect("pivotRoot").error(
|
k.Helper()
|
||||||
checkArg(k, "newroot", newroot, 0),
|
return k.Expects("pivotRoot").Error(
|
||||||
checkArg(k, "putold", putold, 1))
|
stub.CheckArg(k.Stub, "newroot", newroot, 0),
|
||||||
|
stub.CheckArg(k.Stub, "putold", putold, 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) mount(source, target, fstype string, flags uintptr, data string) (err error) {
|
func (k *kstub) mount(source, target, fstype string, flags uintptr, data string) (err error) {
|
||||||
return k.expect("mount").error(
|
k.Helper()
|
||||||
checkArg(k, "source", source, 0),
|
return k.Expects("mount").Error(
|
||||||
checkArg(k, "target", target, 1),
|
stub.CheckArg(k.Stub, "source", source, 0),
|
||||||
checkArg(k, "fstype", fstype, 2),
|
stub.CheckArg(k.Stub, "target", target, 1),
|
||||||
checkArg(k, "flags", flags, 3),
|
stub.CheckArg(k.Stub, "fstype", fstype, 2),
|
||||||
checkArg(k, "data", data, 4))
|
stub.CheckArg(k.Stub, "flags", flags, 3),
|
||||||
|
stub.CheckArg(k.Stub, "data", data, 4))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) unmount(target string, flags int) (err error) {
|
func (k *kstub) unmount(target string, flags int) (err error) {
|
||||||
return k.expect("unmount").error(
|
k.Helper()
|
||||||
checkArg(k, "target", target, 0),
|
return k.Expects("unmount").Error(
|
||||||
checkArg(k, "flags", flags, 1))
|
stub.CheckArg(k.Stub, "target", target, 0),
|
||||||
|
stub.CheckArg(k.Stub, "flags", flags, 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) wait4(pid int, wstatus *syscall.WaitStatus, options int, rusage *syscall.Rusage) (wpid int, err error) {
|
func (k *kstub) wait4(pid int, wstatus *syscall.WaitStatus, options int, rusage *syscall.Rusage) (wpid int, err error) {
|
||||||
expect := k.expect("wait4")
|
k.Helper()
|
||||||
// special case to prevent leaking the wait4 goroutine when testing initEntrypoint
|
expect := k.Expects("wait4")
|
||||||
if v, ok := expect.args[4].(int); ok && v == 0xdeadbeef {
|
if v, ok := expect.Args[4].(int); ok {
|
||||||
k.t.Log("terminating current goroutine as requested by kexpect")
|
switch v {
|
||||||
panic(0xdeadbeef)
|
case stub.PanicExit: // special case to prevent leaking the wait4 goroutine while testing initEntrypoint
|
||||||
|
panic(stub.PanicExit)
|
||||||
|
|
||||||
|
case magicWait4Signal: // block until corresponding signal call
|
||||||
|
if k.wait4signal == nil {
|
||||||
|
panic("kstub not initialised for wait4 simulation")
|
||||||
|
}
|
||||||
|
<-k.wait4signal
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wpid = expect.ret.(int)
|
wpid = expect.Ret.(int)
|
||||||
err = expect.error(
|
err = expect.Error(
|
||||||
checkArg(k, "pid", pid, 0),
|
stub.CheckArg(k.Stub, "pid", pid, 0),
|
||||||
checkArg(k, "options", options, 2))
|
stub.CheckArg(k.Stub, "options", options, 2))
|
||||||
|
|
||||||
if wstatusV, ok := expect.args[1].(syscall.WaitStatus); wstatus != nil && ok {
|
if wstatusV, ok := expect.Args[1].(syscall.WaitStatus); wstatus != nil && ok {
|
||||||
*wstatus = wstatusV
|
*wstatus = wstatusV
|
||||||
}
|
}
|
||||||
if rusageV, ok := expect.args[3].(syscall.Rusage); rusage != nil && ok {
|
if rusageV, ok := expect.Args[3].(syscall.Rusage); rusage != nil && ok {
|
||||||
*rusage = rusageV
|
*rusage = rusageV
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -696,53 +696,50 @@ func (k *kstub) wait4(pid int, wstatus *syscall.WaitStatus, options int, rusage
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) printf(format string, v ...any) {
|
func (k *kstub) printf(format string, v ...any) {
|
||||||
if k.expect("printf").error(
|
k.Helper()
|
||||||
checkArg(k, "format", format, 0),
|
if k.Expects("printf").Error(
|
||||||
checkArgReflect(k, "v", v, 1)) != nil {
|
stub.CheckArg(k.Stub, "format", format, 0),
|
||||||
k.t.FailNow()
|
stub.CheckArgReflect(k.Stub, "v", v, 1)) != nil {
|
||||||
|
k.FailNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) fatal(v ...any) {
|
func (k *kstub) fatal(v ...any) {
|
||||||
if k.expect("fatal").error(
|
k.Helper()
|
||||||
checkArgReflect(k, "v", v, 0)) != nil {
|
if k.Expects("fatal").Error(
|
||||||
k.t.FailNow()
|
stub.CheckArgReflect(k.Stub, "v", v, 0)) != nil {
|
||||||
|
k.FailNow()
|
||||||
}
|
}
|
||||||
panic(0xdeadbeef)
|
panic(stub.PanicExit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) fatalf(format string, v ...any) {
|
func (k *kstub) fatalf(format string, v ...any) {
|
||||||
if k.expect("fatalf").error(
|
k.Helper()
|
||||||
checkArg(k, "format", format, 0),
|
if k.Expects("fatalf").Error(
|
||||||
checkArgReflect(k, "v", v, 1)) != nil {
|
stub.CheckArg(k.Stub, "format", format, 0),
|
||||||
k.t.FailNow()
|
stub.CheckArgReflect(k.Stub, "v", v, 1)) != nil {
|
||||||
|
k.FailNow()
|
||||||
}
|
}
|
||||||
panic(0xdeadbeef)
|
panic(stub.PanicExit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) verbose(v ...any) {
|
func (k *kstub) verbose(v ...any) {
|
||||||
if k.expect("verbose").error(
|
k.Helper()
|
||||||
checkArgReflect(k, "v", v, 0)) != nil {
|
if k.Expects("verbose").Error(
|
||||||
k.t.FailNow()
|
stub.CheckArgReflect(k.Stub, "v", v, 0)) != nil {
|
||||||
|
k.FailNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) verbosef(format string, v ...any) {
|
func (k *kstub) verbosef(format string, v ...any) {
|
||||||
if k.expect("verbosef").error(
|
k.Helper()
|
||||||
checkArg(k, "format", format, 0),
|
if k.Expects("verbosef").Error(
|
||||||
checkArgReflect(k, "v", v, 1)) != nil {
|
stub.CheckArg(k.Stub, "format", format, 0),
|
||||||
k.t.FailNow()
|
stub.CheckArgReflect(k.Stub, "v", v, 1)) != nil {
|
||||||
|
k.FailNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) suspend() { k.expect("suspend") }
|
func (k *kstub) suspend() { k.Helper(); k.Expects("suspend") }
|
||||||
func (k *kstub) resume() bool { return k.expect("resume").ret.(bool) }
|
func (k *kstub) resume() bool { k.Helper(); return k.Expects("resume").Ret.(bool) }
|
||||||
func (k *kstub) beforeExit() { k.expect("beforeExit") }
|
func (k *kstub) beforeExit() { k.Helper(); k.Expects("beforeExit") }
|
||||||
|
|
||||||
func (k *kstub) printBaseErr(err error, fallback string) {
|
|
||||||
if k.expect("printBaseErr").error(
|
|
||||||
checkArgReflect(k, "err", err, 0),
|
|
||||||
checkArg(k, "fallback", fallback, 1)) != nil {
|
|
||||||
k.t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
112
container/errors.go
Normal file
112
container/errors.go
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
package container
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"hakurei.app/container/vfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// messageFromError returns a printable error message for a supported concrete type.
|
||||||
|
func messageFromError(err error) (string, bool) {
|
||||||
|
if m, ok := messagePrefixP[MountError]("cannot ", err); ok {
|
||||||
|
return m, ok
|
||||||
|
}
|
||||||
|
if m, ok := messagePrefixP[os.PathError]("cannot ", err); ok {
|
||||||
|
return m, ok
|
||||||
|
}
|
||||||
|
if m, ok := messagePrefixP[AbsoluteError]("", err); ok {
|
||||||
|
return m, ok
|
||||||
|
}
|
||||||
|
if m, ok := messagePrefix[OpRepeatError]("", err); ok {
|
||||||
|
return m, ok
|
||||||
|
}
|
||||||
|
if m, ok := messagePrefix[OpStateError]("", err); ok {
|
||||||
|
return m, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
if m, ok := messagePrefixP[vfs.DecoderError]("cannot ", err); ok {
|
||||||
|
return m, ok
|
||||||
|
}
|
||||||
|
if m, ok := messagePrefix[TmpfsSizeError]("", err); ok {
|
||||||
|
return m, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
return zeroString, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// messagePrefix checks and prefixes the error message of a non-pointer error.
|
||||||
|
// While this is usable for pointer errors, such use should be avoided as nil check is omitted.
|
||||||
|
func messagePrefix[T error](prefix string, err error) (string, bool) {
|
||||||
|
var targetError T
|
||||||
|
if errors.As(err, &targetError) {
|
||||||
|
return prefix + targetError.Error(), true
|
||||||
|
}
|
||||||
|
return zeroString, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// messagePrefixP checks and prefixes the error message of a pointer error.
|
||||||
|
func messagePrefixP[V any, T interface {
|
||||||
|
*V
|
||||||
|
error
|
||||||
|
}](prefix string, err error) (string, bool) {
|
||||||
|
var targetError T
|
||||||
|
if errors.As(err, &targetError) && targetError != nil {
|
||||||
|
return prefix + targetError.Error(), true
|
||||||
|
}
|
||||||
|
return zeroString, false
|
||||||
|
}
|
||||||
|
|
||||||
|
type MountError struct {
|
||||||
|
Source, Target, Fstype string
|
||||||
|
|
||||||
|
Flags uintptr
|
||||||
|
Data string
|
||||||
|
syscall.Errno
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *MountError) Unwrap() error {
|
||||||
|
if e.Errno == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return e.Errno
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *MountError) Error() string {
|
||||||
|
if e.Flags&syscall.MS_BIND != 0 {
|
||||||
|
if e.Flags&syscall.MS_REMOUNT != 0 {
|
||||||
|
return "remount " + e.Target + ": " + e.Errno.Error()
|
||||||
|
}
|
||||||
|
return "bind " + e.Source + " on " + e.Target + ": " + e.Errno.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Fstype != FstypeNULL {
|
||||||
|
return "mount " + e.Fstype + " on " + e.Target + ": " + e.Errno.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback case: if this is reached, the conditions for it to occur should be handled above
|
||||||
|
return "mount " + e.Target + ": " + e.Errno.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// errnoFallback returns the concrete errno from an error, or a [os.PathError] fallback.
|
||||||
|
func errnoFallback(op, path string, err error) (syscall.Errno, *os.PathError) {
|
||||||
|
var errno syscall.Errno
|
||||||
|
if !errors.As(err, &errno) {
|
||||||
|
return 0, &os.PathError{Op: op, Path: path, Err: err}
|
||||||
|
}
|
||||||
|
return errno, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mount wraps syscall.Mount for error handling.
|
||||||
|
func mount(source, target, fstype string, flags uintptr, data string) error {
|
||||||
|
err := syscall.Mount(source, target, fstype, flags, data)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if errno, pathError := errnoFallback("mount", target, err); pathError != nil {
|
||||||
|
return pathError
|
||||||
|
} else {
|
||||||
|
return &MountError{source, target, fstype, flags, data, errno}
|
||||||
|
}
|
||||||
|
}
|
||||||
168
container/errors_test.go
Normal file
168
container/errors_test.go
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
package container
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/stub"
|
||||||
|
"hakurei.app/container/vfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMessageFromError(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
want string
|
||||||
|
wantOk bool
|
||||||
|
}{
|
||||||
|
{"mount", &MountError{
|
||||||
|
Source: SourceTmpfsEphemeral,
|
||||||
|
Target: "/sysroot/tmp",
|
||||||
|
Fstype: FstypeTmpfs,
|
||||||
|
Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
|
||||||
|
Data: zeroString,
|
||||||
|
Errno: syscall.EINVAL,
|
||||||
|
}, "cannot mount tmpfs on /sysroot/tmp: invalid argument", true},
|
||||||
|
|
||||||
|
{"path", &os.PathError{
|
||||||
|
Op: "mount",
|
||||||
|
Path: "/sysroot",
|
||||||
|
Err: stub.UniqueError(0xdeadbeef),
|
||||||
|
}, "cannot mount /sysroot: unique error 3735928559 injected by the test suite", true},
|
||||||
|
|
||||||
|
{"absolute", &AbsoluteError{"etc/mtab"},
|
||||||
|
`path "etc/mtab" is not absolute`, true},
|
||||||
|
|
||||||
|
{"repeat", OpRepeatError("autoetc"),
|
||||||
|
"autoetc is not repeatable", true},
|
||||||
|
|
||||||
|
{"state", OpStateError("overlay"),
|
||||||
|
"impossible overlay state reached", true},
|
||||||
|
|
||||||
|
{"vfs parse", &vfs.DecoderError{Op: "parse", Line: 0xdeadbeef, Err: &strconv.NumError{Func: "Atoi", Num: "meow", Err: strconv.ErrSyntax}},
|
||||||
|
`cannot parse mountinfo at line 3735928559: numeric field "meow" invalid syntax`, true},
|
||||||
|
|
||||||
|
{"tmpfs", TmpfsSizeError(-1),
|
||||||
|
"tmpfs size -1 out of bounds", true},
|
||||||
|
|
||||||
|
{"unsupported", stub.UniqueError(0xdeadbeef), zeroString, false},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
got, ok := messageFromError(tc.err)
|
||||||
|
if got != tc.want {
|
||||||
|
t.Errorf("messageFromError: %q, want %q", got, tc.want)
|
||||||
|
}
|
||||||
|
if ok != tc.wantOk {
|
||||||
|
t.Errorf("messageFromError: ok = %v, want %v", ok, tc.wantOk)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMountError(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
errno syscall.Errno
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"bind", &MountError{
|
||||||
|
Source: "/host/nix/store",
|
||||||
|
Target: "/sysroot/nix/store",
|
||||||
|
Fstype: FstypeNULL,
|
||||||
|
Flags: syscall.MS_SILENT | syscall.MS_BIND | syscall.MS_REC,
|
||||||
|
Data: zeroString,
|
||||||
|
Errno: syscall.ENOSYS,
|
||||||
|
}, syscall.ENOSYS,
|
||||||
|
"bind /host/nix/store on /sysroot/nix/store: function not implemented"},
|
||||||
|
|
||||||
|
{"remount", &MountError{
|
||||||
|
Source: SourceNone,
|
||||||
|
Target: "/sysroot/nix/store",
|
||||||
|
Fstype: FstypeNULL,
|
||||||
|
Flags: syscall.MS_SILENT | syscall.MS_BIND | syscall.MS_REMOUNT,
|
||||||
|
Data: zeroString,
|
||||||
|
Errno: syscall.EPERM,
|
||||||
|
}, syscall.EPERM,
|
||||||
|
"remount /sysroot/nix/store: operation not permitted"},
|
||||||
|
|
||||||
|
{"overlay", &MountError{
|
||||||
|
Source: SourceOverlay,
|
||||||
|
Target: sysrootPath,
|
||||||
|
Fstype: FstypeOverlay,
|
||||||
|
Data: `lowerdir=/host/var/lib/planterette/base/debian\:f92c9052`,
|
||||||
|
Errno: syscall.EINVAL,
|
||||||
|
}, syscall.EINVAL,
|
||||||
|
"mount overlay on /sysroot: invalid argument"},
|
||||||
|
|
||||||
|
{"fallback", &MountError{
|
||||||
|
Source: SourceNone,
|
||||||
|
Target: sysrootPath,
|
||||||
|
Fstype: FstypeNULL,
|
||||||
|
Errno: syscall.ENOTRECOVERABLE,
|
||||||
|
}, syscall.ENOTRECOVERABLE,
|
||||||
|
"mount /sysroot: state not recoverable"},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Run("is", func(t *testing.T) {
|
||||||
|
if !errors.Is(tc.err, tc.errno) {
|
||||||
|
t.Errorf("Is: %#v is not %v", tc.err, tc.errno)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("error", func(t *testing.T) {
|
||||||
|
if got := tc.err.Error(); got != tc.want {
|
||||||
|
t.Errorf("Error: %q, want %q", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("zero", func(t *testing.T) {
|
||||||
|
if errors.Is(new(MountError), syscall.Errno(0)) {
|
||||||
|
t.Errorf("Is: zero MountError unexpected true")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrnoFallback(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
wantErrno syscall.Errno
|
||||||
|
wantPath *os.PathError
|
||||||
|
}{
|
||||||
|
{"mount", &MountError{
|
||||||
|
Errno: syscall.ENOTRECOVERABLE,
|
||||||
|
}, syscall.ENOTRECOVERABLE, nil},
|
||||||
|
|
||||||
|
{"path errno", &os.PathError{
|
||||||
|
Err: syscall.ETIMEDOUT,
|
||||||
|
}, syscall.ETIMEDOUT, nil},
|
||||||
|
|
||||||
|
{"fallback", stub.UniqueError(0xcafebabe), 0, &os.PathError{
|
||||||
|
Op: "fallback",
|
||||||
|
Path: "/proc/nonexistent",
|
||||||
|
Err: stub.UniqueError(0xcafebabe),
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
errno, err := errnoFallback(tc.name, Nonexistent, tc.err)
|
||||||
|
if errno != tc.wantErrno {
|
||||||
|
t.Errorf("errnoFallback: errno = %v, want %v", errno, tc.wantErrno)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(err, tc.wantPath) {
|
||||||
|
t.Errorf("errnoFallback: pathError = %#v, want %#v", err, tc.wantPath)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InternalMessageFromError exports messageFromError for other tests.
|
||||||
|
func InternalMessageFromError(err error) (string, bool) { return messageFromError(err) }
|
||||||
@@ -68,6 +68,16 @@ const (
|
|||||||
nrAutoRoot
|
nrAutoRoot
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// OpRepeatError is returned applying a repeated nonrepeatable [Op].
|
||||||
|
type OpRepeatError string
|
||||||
|
|
||||||
|
func (e OpRepeatError) Error() string { return string(e) + " is not repeatable" }
|
||||||
|
|
||||||
|
// OpStateError indicates an impossible internal state has been reached in an [Op].
|
||||||
|
type OpStateError string
|
||||||
|
|
||||||
|
func (o OpStateError) Error() string { return "impossible " + string(o) + " state reached" }
|
||||||
|
|
||||||
// initParams are params passed from parent.
|
// initParams are params passed from parent.
|
||||||
type initParams struct {
|
type initParams struct {
|
||||||
Params
|
Params
|
||||||
@@ -106,7 +116,7 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
|||||||
if errors.Is(err, EBADF) {
|
if errors.Is(err, EBADF) {
|
||||||
k.fatal("invalid setup descriptor")
|
k.fatal("invalid setup descriptor")
|
||||||
}
|
}
|
||||||
if errors.Is(err, ErrNotSet) {
|
if errors.Is(err, ErrReceiveEnv) {
|
||||||
k.fatal("HAKUREI_SETUP not set")
|
k.fatal("HAKUREI_SETUP not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,10 +184,11 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := op.early(state, k); err != nil {
|
if err := op.early(state, k); err != nil {
|
||||||
k.printBaseErr(err,
|
if m, ok := messageFromError(err); ok {
|
||||||
fmt.Sprintf("cannot prepare op at index %d:", i))
|
k.fatal(m)
|
||||||
k.beforeExit()
|
} else {
|
||||||
k.exit(1)
|
k.fatalf("cannot prepare op at index %d: %v", i, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,10 +225,11 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
|||||||
// ops already checked during early setup
|
// ops already checked during early setup
|
||||||
k.verbosef("%s %s", op.prefix(), op)
|
k.verbosef("%s %s", op.prefix(), op)
|
||||||
if err := op.apply(state, k); err != nil {
|
if err := op.apply(state, k); err != nil {
|
||||||
k.printBaseErr(err,
|
if m, ok := messageFromError(err); ok {
|
||||||
fmt.Sprintf("cannot apply op at index %d:", i))
|
k.fatal(m)
|
||||||
k.beforeExit()
|
} else {
|
||||||
k.exit(1)
|
k.fatalf("cannot apply op at index %d: %v", i, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -43,7 +43,7 @@ func (b *BindMountOp) Valid() bool {
|
|||||||
func (b *BindMountOp) early(_ *setupState, k syscallDispatcher) error {
|
func (b *BindMountOp) early(_ *setupState, k syscallDispatcher) error {
|
||||||
if b.Flags&BindEnsure != 0 {
|
if b.Flags&BindEnsure != 0 {
|
||||||
if err := k.mkdirAll(b.Source.String(), 0700); err != nil {
|
if err := k.mkdirAll(b.Source.String(), 0700); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ func (b *BindMountOp) early(_ *setupState, k syscallDispatcher) error {
|
|||||||
// leave sourceFinal as nil
|
// leave sourceFinal as nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
} else {
|
} else {
|
||||||
b.sourceFinal, err = NewAbs(pathname)
|
b.sourceFinal, err = NewAbs(pathname)
|
||||||
return err
|
return err
|
||||||
@@ -63,7 +63,7 @@ func (b *BindMountOp) apply(_ *setupState, k syscallDispatcher) error {
|
|||||||
if b.sourceFinal == nil {
|
if b.sourceFinal == nil {
|
||||||
if b.Flags&BindOptional == 0 {
|
if b.Flags&BindOptional == 0 {
|
||||||
// unreachable
|
// unreachable
|
||||||
return msg.WrapErr(os.ErrClosed, "impossible bind state reached")
|
return OpStateError("bind")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -74,10 +74,10 @@ func (b *BindMountOp) apply(_ *setupState, k syscallDispatcher) error {
|
|||||||
// this perm value emulates bwrap behaviour as it clears bits from 0755 based on
|
// this perm value emulates bwrap behaviour as it clears bits from 0755 based on
|
||||||
// op->perms which is never set for any bind setup op so always results in 0700
|
// op->perms which is never set for any bind setup op so always results in 0700
|
||||||
if fi, err := k.stat(source); err != nil {
|
if fi, err := k.stat(source); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
} else if fi.IsDir() {
|
} else if fi.IsDir() {
|
||||||
if err = k.mkdirAll(target, 0700); err != nil {
|
if err = k.mkdirAll(target, 0700); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
}
|
}
|
||||||
} else if err = k.ensureFile(target, 0444, 0700); err != nil {
|
} else if err = k.ensureFile(target, 0444, 0700); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/stub"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBindMountOp(t *testing.T) {
|
func TestBindMountOp(t *testing.T) {
|
||||||
@@ -12,138 +14,138 @@ func TestBindMountOp(t *testing.T) {
|
|||||||
{"ENOENT not optional", new(Params), &BindMountOp{
|
{"ENOENT not optional", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/bin/"),
|
Source: MustAbs("/bin/"),
|
||||||
Target: MustAbs("/bin/"),
|
Target: MustAbs("/bin/"),
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"evalSymlinks", expectArgs{"/bin/"}, "", syscall.ENOENT},
|
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "", syscall.ENOENT),
|
||||||
}, wrapErrSelf(syscall.ENOENT), nil, nil},
|
}, syscall.ENOENT, nil, nil},
|
||||||
|
|
||||||
{"skip optional", new(Params), &BindMountOp{
|
{"skip optional", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/bin/"),
|
Source: MustAbs("/bin/"),
|
||||||
Target: MustAbs("/bin/"),
|
Target: MustAbs("/bin/"),
|
||||||
Flags: BindOptional,
|
Flags: BindOptional,
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"evalSymlinks", 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: MustAbs("/bin/"),
|
||||||
Target: MustAbs("/bin/"),
|
Target: MustAbs("/bin/"),
|
||||||
Flags: BindOptional,
|
Flags: BindOptional,
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"evalSymlinks", expectArgs{"/bin/"}, "/usr/bin", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", nil),
|
||||||
}, nil, []kexpect{
|
}, nil, []stub.Call{
|
||||||
{"stat", expectArgs{"/host/usr/bin"}, isDirFi(true), nil},
|
call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(true), nil),
|
||||||
{"mkdirAll", expectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil),
|
||||||
{"bindMount", expectArgs{"/host/usr/bin", "/sysroot/bin", uintptr(0x4005), false}, nil, nil},
|
call("bindMount", stub.ExpectArgs{"/host/usr/bin", "/sysroot/bin", uintptr(0x4005), false}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"ensureFile device", new(Params), &BindMountOp{
|
{"ensureFile device", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/dev/null"),
|
Source: MustAbs("/dev/null"),
|
||||||
Target: MustAbs("/dev/null"),
|
Target: MustAbs("/dev/null"),
|
||||||
Flags: BindWritable | BindDevice,
|
Flags: BindWritable | BindDevice,
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"evalSymlinks", expectArgs{"/dev/null"}, "/dev/null", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/dev/null"}, "/dev/null", nil),
|
||||||
}, nil, []kexpect{
|
}, nil, []stub.Call{
|
||||||
{"stat", expectArgs{"/host/dev/null"}, isDirFi(false), nil},
|
call("stat", stub.ExpectArgs{"/host/dev/null"}, isDirFi(false), nil),
|
||||||
{"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0700)}, nil, errUnique},
|
call("ensureFile", stub.ExpectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0700)}, nil, stub.UniqueError(5)),
|
||||||
}, errUnique},
|
}, stub.UniqueError(5)},
|
||||||
|
|
||||||
{"mkdirAll ensure", new(Params), &BindMountOp{
|
{"mkdirAll ensure", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/bin/"),
|
Source: MustAbs("/bin/"),
|
||||||
Target: MustAbs("/bin/"),
|
Target: MustAbs("/bin/"),
|
||||||
Flags: BindEnsure,
|
Flags: BindEnsure,
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/bin/", os.FileMode(0700)}, nil, errUnique},
|
call("mkdirAll", stub.ExpectArgs{"/bin/", os.FileMode(0700)}, nil, stub.UniqueError(4)),
|
||||||
}, wrapErrSelf(errUnique), nil, nil},
|
}, stub.UniqueError(4), nil, nil},
|
||||||
|
|
||||||
{"success ensure", new(Params), &BindMountOp{
|
{"success ensure", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/bin/"),
|
Source: MustAbs("/bin/"),
|
||||||
Target: MustAbs("/usr/bin/"),
|
Target: MustAbs("/usr/bin/"),
|
||||||
Flags: BindEnsure,
|
Flags: BindEnsure,
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/bin/", os.FileMode(0700)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/bin/", os.FileMode(0700)}, nil, nil),
|
||||||
{"evalSymlinks", expectArgs{"/bin/"}, "/usr/bin", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", nil),
|
||||||
}, nil, []kexpect{
|
}, nil, []stub.Call{
|
||||||
{"stat", expectArgs{"/host/usr/bin"}, isDirFi(true), nil},
|
call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(true), nil),
|
||||||
{"mkdirAll", expectArgs{"/sysroot/usr/bin", os.FileMode(0700)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/usr/bin", os.FileMode(0700)}, nil, nil),
|
||||||
{"bindMount", expectArgs{"/host/usr/bin", "/sysroot/usr/bin", uintptr(0x4005), false}, nil, nil},
|
call("bindMount", stub.ExpectArgs{"/host/usr/bin", "/sysroot/usr/bin", uintptr(0x4005), false}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"success device ro", new(Params), &BindMountOp{
|
{"success device ro", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/dev/null"),
|
Source: MustAbs("/dev/null"),
|
||||||
Target: MustAbs("/dev/null"),
|
Target: MustAbs("/dev/null"),
|
||||||
Flags: BindDevice,
|
Flags: BindDevice,
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"evalSymlinks", expectArgs{"/dev/null"}, "/dev/null", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/dev/null"}, "/dev/null", nil),
|
||||||
}, nil, []kexpect{
|
}, nil, []stub.Call{
|
||||||
{"stat", expectArgs{"/host/dev/null"}, isDirFi(false), nil},
|
call("stat", stub.ExpectArgs{"/host/dev/null"}, isDirFi(false), nil),
|
||||||
{"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0700)}, nil, nil},
|
call("ensureFile", stub.ExpectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0700)}, nil, nil),
|
||||||
{"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0x4001), false}, nil, nil},
|
call("bindMount", stub.ExpectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0x4001), false}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"success device", new(Params), &BindMountOp{
|
{"success device", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/dev/null"),
|
Source: MustAbs("/dev/null"),
|
||||||
Target: MustAbs("/dev/null"),
|
Target: MustAbs("/dev/null"),
|
||||||
Flags: BindWritable | BindDevice,
|
Flags: BindWritable | BindDevice,
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"evalSymlinks", expectArgs{"/dev/null"}, "/dev/null", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/dev/null"}, "/dev/null", nil),
|
||||||
}, nil, []kexpect{
|
}, nil, []stub.Call{
|
||||||
{"stat", expectArgs{"/host/dev/null"}, isDirFi(false), nil},
|
call("stat", stub.ExpectArgs{"/host/dev/null"}, isDirFi(false), nil),
|
||||||
{"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0700)}, nil, nil},
|
call("ensureFile", stub.ExpectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0700)}, nil, nil),
|
||||||
{"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0x4000), false}, nil, nil},
|
call("bindMount", stub.ExpectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0x4000), false}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"evalSymlinks", new(Params), &BindMountOp{
|
{"evalSymlinks", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/bin/"),
|
Source: MustAbs("/bin/"),
|
||||||
Target: MustAbs("/bin/"),
|
Target: MustAbs("/bin/"),
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"evalSymlinks", expectArgs{"/bin/"}, "/usr/bin", errUnique},
|
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", stub.UniqueError(3)),
|
||||||
}, wrapErrSelf(errUnique), nil, nil},
|
}, stub.UniqueError(3), nil, nil},
|
||||||
|
|
||||||
{"stat", new(Params), &BindMountOp{
|
{"stat", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/bin/"),
|
Source: MustAbs("/bin/"),
|
||||||
Target: MustAbs("/bin/"),
|
Target: MustAbs("/bin/"),
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"evalSymlinks", expectArgs{"/bin/"}, "/usr/bin", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", nil),
|
||||||
}, nil, []kexpect{
|
}, nil, []stub.Call{
|
||||||
{"stat", expectArgs{"/host/usr/bin"}, isDirFi(true), errUnique},
|
call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(true), stub.UniqueError(2)),
|
||||||
}, wrapErrSelf(errUnique)},
|
}, stub.UniqueError(2)},
|
||||||
|
|
||||||
{"mkdirAll", new(Params), &BindMountOp{
|
{"mkdirAll", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/bin/"),
|
Source: MustAbs("/bin/"),
|
||||||
Target: MustAbs("/bin/"),
|
Target: MustAbs("/bin/"),
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"evalSymlinks", expectArgs{"/bin/"}, "/usr/bin", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", nil),
|
||||||
}, nil, []kexpect{
|
}, nil, []stub.Call{
|
||||||
{"stat", expectArgs{"/host/usr/bin"}, isDirFi(true), nil},
|
call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(true), nil),
|
||||||
{"mkdirAll", expectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, errUnique},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, stub.UniqueError(1)),
|
||||||
}, wrapErrSelf(errUnique)},
|
}, stub.UniqueError(1)},
|
||||||
|
|
||||||
{"bindMount", new(Params), &BindMountOp{
|
{"bindMount", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/bin/"),
|
Source: MustAbs("/bin/"),
|
||||||
Target: MustAbs("/bin/"),
|
Target: MustAbs("/bin/"),
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"evalSymlinks", expectArgs{"/bin/"}, "/usr/bin", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", nil),
|
||||||
}, nil, []kexpect{
|
}, nil, []stub.Call{
|
||||||
{"stat", expectArgs{"/host/usr/bin"}, isDirFi(true), nil},
|
call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(true), nil),
|
||||||
{"mkdirAll", expectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil),
|
||||||
{"bindMount", expectArgs{"/host/usr/bin", "/sysroot/bin", uintptr(0x4005), false}, nil, errUnique},
|
call("bindMount", stub.ExpectArgs{"/host/usr/bin", "/sysroot/bin", uintptr(0x4005), false}, nil, stub.UniqueError(0)),
|
||||||
}, errUnique},
|
}, stub.UniqueError(0)},
|
||||||
|
|
||||||
{"success", new(Params), &BindMountOp{
|
{"success", new(Params), &BindMountOp{
|
||||||
Source: MustAbs("/bin/"),
|
Source: MustAbs("/bin/"),
|
||||||
Target: MustAbs("/bin/"),
|
Target: MustAbs("/bin/"),
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"evalSymlinks", expectArgs{"/bin/"}, "/usr/bin", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", nil),
|
||||||
}, nil, []kexpect{
|
}, nil, []stub.Call{
|
||||||
{"stat", expectArgs{"/host/usr/bin"}, isDirFi(true), nil},
|
call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(true), nil),
|
||||||
{"mkdirAll", expectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil),
|
||||||
{"bindMount", expectArgs{"/host/usr/bin", "/sysroot/bin", uintptr(0x4005), false}, nil, nil},
|
call("bindMount", stub.ExpectArgs{"/host/usr/bin", "/sysroot/bin", uintptr(0x4005), false}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("unreachable", func(t *testing.T) {
|
t.Run("unreachable", func(t *testing.T) {
|
||||||
t.Run("nil sourceFinal not optional", func(t *testing.T) {
|
t.Run("nil sourceFinal not optional", func(t *testing.T) {
|
||||||
wantErr := msg.WrapErr(os.ErrClosed, "impossible bind state reached")
|
wantErr := OpStateError("bind")
|
||||||
if err := new(BindMountOp).apply(nil, nil); !errors.Is(err, wantErr) {
|
if err := new(BindMountOp).apply(nil, nil); !errors.Is(err, wantErr) {
|
||||||
t.Errorf("apply: error = %v, want %v", err, wantErr)
|
t.Errorf("apply: error = %v, want %v", err, wantErr)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
FHSProc+"self/fd/"+string(rune(i+'0')),
|
FHSProc+"self/fd/"+string(rune(i+'0')),
|
||||||
path.Join(target, name),
|
path.Join(target, name),
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, pair := range [][2]string{
|
for _, pair := range [][2]string{
|
||||||
@@ -68,21 +68,21 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
{"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 {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
devShmPath := path.Join(target, "shm")
|
||||||
devPtsPath := path.Join(target, "pts")
|
devPtsPath := path.Join(target, "pts")
|
||||||
for _, name := range []string{path.Join(target, "shm"), devPtsPath} {
|
for _, name := range []string{devShmPath, devPtsPath} {
|
||||||
if err := k.mkdir(name, state.ParentPerm); err != nil {
|
if err := k.mkdir(name, state.ParentPerm); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := k.mount(SourceDevpts, devPtsPath, FstypeDevpts, MS_NOSUID|MS_NOEXEC,
|
if err := k.mount(SourceDevpts, devPtsPath, FstypeDevpts, MS_NOSUID|MS_NOEXEC,
|
||||||
"newinstance,ptmxmode=0666,mode=620"); err != nil {
|
"newinstance,ptmxmode=0666,mode=620"); err != nil {
|
||||||
return wrapErrSuffix(err,
|
return err
|
||||||
fmt.Sprintf("cannot mount devpts on %q:", devPtsPath))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.RetainSession {
|
if state.RetainSession {
|
||||||
@@ -92,7 +92,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if name, err := k.readlink(hostProc.stdout()); err != nil {
|
if name, err := k.readlink(hostProc.stdout()); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
} else if err = k.bindMount(
|
} else if err = k.bindMount(
|
||||||
toHost(name),
|
toHost(name),
|
||||||
consolePath,
|
consolePath,
|
||||||
@@ -107,18 +107,21 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
if d.Mqueue {
|
if d.Mqueue {
|
||||||
mqueueTarget := path.Join(target, "mqueue")
|
mqueueTarget := path.Join(target, "mqueue")
|
||||||
if err := k.mkdir(mqueueTarget, state.ParentPerm); err != nil {
|
if err := k.mkdir(mqueueTarget, state.ParentPerm); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
}
|
}
|
||||||
if err := k.mount(SourceMqueue, mqueueTarget, FstypeMqueue, MS_NOSUID|MS_NOEXEC|MS_NODEV, zeroString); err != nil {
|
if err := k.mount(SourceMqueue, mqueueTarget, FstypeMqueue, MS_NOSUID|MS_NOEXEC|MS_NODEV, zeroString); err != nil {
|
||||||
return wrapErrSuffix(err, "cannot mount mqueue:")
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.Write {
|
if d.Write {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return wrapErrSuffix(k.remount(target, MS_RDONLY),
|
|
||||||
fmt.Sprintf("cannot remount %q:", target))
|
if err := k.remount(target, MS_RDONLY); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return k.mountTmpfs(SourceTmpfs, devShmPath, MS_NOSUID|MS_NODEV, 0, 01777)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *MountDevOp) Is(op Op) bool {
|
func (d *MountDevOp) Is(op Op) bool {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,7 @@ type MkdirOp struct {
|
|||||||
func (m *MkdirOp) Valid() bool { return m != nil && m.Path != nil }
|
func (m *MkdirOp) Valid() bool { return m != nil && m.Path != nil }
|
||||||
func (m *MkdirOp) early(*setupState, syscallDispatcher) error { return nil }
|
func (m *MkdirOp) early(*setupState, syscallDispatcher) error { return nil }
|
||||||
func (m *MkdirOp) apply(_ *setupState, k syscallDispatcher) error {
|
func (m *MkdirOp) apply(_ *setupState, k syscallDispatcher) error {
|
||||||
return wrapErrSelf(k.mkdirAll(toSysroot(m.Path.String()), m.Perm))
|
return k.mkdirAll(toSysroot(m.Path.String()), m.Perm)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MkdirOp) Is(op Op) bool {
|
func (m *MkdirOp) Is(op Op) bool {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package container
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/stub"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMkdirOp(t *testing.T) {
|
func TestMkdirOp(t *testing.T) {
|
||||||
@@ -10,8 +12,8 @@ func TestMkdirOp(t *testing.T) {
|
|||||||
{"success", new(Params), &MkdirOp{
|
{"success", new(Params), &MkdirOp{
|
||||||
Path: MustAbs("/.hakurei"),
|
Path: MustAbs("/.hakurei"),
|
||||||
Perm: 0500,
|
Perm: 0500,
|
||||||
}, nil, nil, []kexpect{
|
}, nil, nil, []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/sysroot/.hakurei", os.FileMode(0500)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/.hakurei", os.FileMode(0500)}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package container
|
|||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -19,6 +18,39 @@ const (
|
|||||||
|
|
||||||
func init() { gob.Register(new(MountOverlayOp)) }
|
func init() { gob.Register(new(MountOverlayOp)) }
|
||||||
|
|
||||||
|
const (
|
||||||
|
// OverlayEphemeralUnexpectedUpper is set when [MountOverlayOp.Work] is nil
|
||||||
|
// and [MountOverlayOp.Upper] holds an unexpected value.
|
||||||
|
OverlayEphemeralUnexpectedUpper = iota
|
||||||
|
// OverlayReadonlyLower is set when [MountOverlayOp.Lower] contains less than
|
||||||
|
// two entries when mounting readonly.
|
||||||
|
OverlayReadonlyLower
|
||||||
|
// OverlayEmptyLower is set when [MountOverlayOp.Lower] has length of zero.
|
||||||
|
OverlayEmptyLower
|
||||||
|
)
|
||||||
|
|
||||||
|
// OverlayArgumentError is returned for [MountOverlayOp] supplied with invalid argument.
|
||||||
|
type OverlayArgumentError struct {
|
||||||
|
Type uintptr
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *OverlayArgumentError) Error() string {
|
||||||
|
switch e.Type {
|
||||||
|
case OverlayEphemeralUnexpectedUpper:
|
||||||
|
return fmt.Sprintf("upperdir has unexpected value %q", e.Value)
|
||||||
|
|
||||||
|
case OverlayReadonlyLower:
|
||||||
|
return "readonly overlay requires at least two lowerdir"
|
||||||
|
|
||||||
|
case OverlayEmptyLower:
|
||||||
|
return "overlay requires at least one lowerdir"
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("invalid overlay argument error %#x", e.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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 *Absolute, layers ...*Absolute) *Ops {
|
||||||
*f = append(*f, &MountOverlayOp{
|
*f = append(*f, &MountOverlayOp{
|
||||||
@@ -89,7 +121,7 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
|
|||||||
o.ephemeral = true // intermediate root not yet available
|
o.ephemeral = true // intermediate root not yet available
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return msg.WrapErr(fs.ErrInvalid, fmt.Sprintf("upperdir has unexpected value %q", o.Upper))
|
return &OverlayArgumentError{OverlayEphemeralUnexpectedUpper, o.Upper.String()}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// readonly handled in apply
|
// readonly handled in apply
|
||||||
@@ -97,12 +129,12 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
|
|||||||
if !o.ephemeral {
|
if !o.ephemeral {
|
||||||
if o.Upper != o.Work && (o.Upper == nil || o.Work == nil) {
|
if o.Upper != o.Work && (o.Upper == nil || o.Work == nil) {
|
||||||
// unreachable
|
// unreachable
|
||||||
return msg.WrapErr(fs.ErrClosed, "impossible overlay state reached")
|
return OpStateError("overlay")
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.Upper != nil {
|
if o.Upper != nil {
|
||||||
if v, err := k.evalSymlinks(o.Upper.String()); err != nil {
|
if v, err := k.evalSymlinks(o.Upper.String()); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
} else {
|
} else {
|
||||||
o.upper = EscapeOverlayDataSegment(toHost(v))
|
o.upper = EscapeOverlayDataSegment(toHost(v))
|
||||||
}
|
}
|
||||||
@@ -110,7 +142,7 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
|
|||||||
|
|
||||||
if o.Work != nil {
|
if o.Work != nil {
|
||||||
if v, err := k.evalSymlinks(o.Work.String()); err != nil {
|
if v, err := k.evalSymlinks(o.Work.String()); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
} else {
|
} else {
|
||||||
o.work = EscapeOverlayDataSegment(toHost(v))
|
o.work = EscapeOverlayDataSegment(toHost(v))
|
||||||
}
|
}
|
||||||
@@ -120,7 +152,7 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
|
|||||||
o.lower = make([]string, len(o.Lower))
|
o.lower = make([]string, len(o.Lower))
|
||||||
for i, a := range o.Lower { // nil checked in Valid
|
for i, a := range o.Lower { // nil checked in Valid
|
||||||
if v, err := k.evalSymlinks(a.String()); err != nil {
|
if v, err := k.evalSymlinks(a.String()); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
} else {
|
} else {
|
||||||
o.lower[i] = EscapeOverlayDataSegment(toHost(v))
|
o.lower[i] = EscapeOverlayDataSegment(toHost(v))
|
||||||
}
|
}
|
||||||
@@ -134,17 +166,17 @@ func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
target = toSysroot(target)
|
target = toSysroot(target)
|
||||||
}
|
}
|
||||||
if err := k.mkdirAll(target, state.ParentPerm); err != nil {
|
if err := k.mkdirAll(target, state.ParentPerm); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
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(FHSRoot, intermediatePatternOverlayUpper); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
}
|
}
|
||||||
if o.work, err = k.mkdirTemp(FHSRoot, intermediatePatternOverlayWork); err != nil {
|
if o.work, err = k.mkdirTemp(FHSRoot, intermediatePatternOverlayWork); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,12 +184,12 @@ func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
|
|
||||||
if o.upper == zeroString && o.work == zeroString { // readonly
|
if o.upper == zeroString && o.work == zeroString { // readonly
|
||||||
if len(o.Lower) < 2 {
|
if len(o.Lower) < 2 {
|
||||||
return msg.WrapErr(fs.ErrInvalid, "readonly overlay requires at least two lowerdir")
|
return &OverlayArgumentError{OverlayReadonlyLower, zeroString}
|
||||||
}
|
}
|
||||||
// "upperdir=" and "workdir=" may be omitted. In that case the overlay will be read-only
|
// "upperdir=" and "workdir=" may be omitted. In that case the overlay will be read-only
|
||||||
} else {
|
} else {
|
||||||
if len(o.Lower) == 0 {
|
if len(o.Lower) == 0 {
|
||||||
return msg.WrapErr(fs.ErrInvalid, "overlay requires at least one lowerdir")
|
return &OverlayArgumentError{OverlayEmptyLower, zeroString}
|
||||||
}
|
}
|
||||||
options = append(options,
|
options = append(options,
|
||||||
OptionOverlayUpperdir+"="+o.upper,
|
OptionOverlayUpperdir+"="+o.upper,
|
||||||
@@ -167,8 +199,7 @@ func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
OptionOverlayLowerdir+"="+strings.Join(o.lower, SpecialOverlayPath),
|
OptionOverlayLowerdir+"="+strings.Join(o.lower, SpecialOverlayPath),
|
||||||
OptionOverlayUserxattr)
|
OptionOverlayUserxattr)
|
||||||
|
|
||||||
return wrapErrSuffix(k.mount(SourceOverlay, target, FstypeOverlay, 0, strings.Join(options, SpecialOverlayOption)),
|
return k.mount(SourceOverlay, target, FstypeOverlay, 0, strings.Join(options, SpecialOverlayOption))
|
||||||
fmt.Sprintf("cannot mount overlay on %q:", o.Target))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *MountOverlayOp) Is(op Op) bool {
|
func (o *MountOverlayOp) Is(op Op) bool {
|
||||||
|
|||||||
@@ -2,12 +2,40 @@ package container
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"io/fs"
|
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/stub"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMountOverlayOp(t *testing.T) {
|
func TestMountOverlayOp(t *testing.T) {
|
||||||
|
t.Run("argument error", func(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
err *OverlayArgumentError
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"unexpected upper", &OverlayArgumentError{OverlayEphemeralUnexpectedUpper, "/proc/"},
|
||||||
|
`upperdir has unexpected value "/proc/"`},
|
||||||
|
|
||||||
|
{"lower ro short", &OverlayArgumentError{OverlayReadonlyLower, zeroString},
|
||||||
|
"readonly overlay requires at least two lowerdir"},
|
||||||
|
|
||||||
|
{"lower short", &OverlayArgumentError{OverlayEmptyLower, zeroString},
|
||||||
|
"overlay requires at least one lowerdir"},
|
||||||
|
|
||||||
|
{"oob", &OverlayArgumentError{0xdeadbeef, zeroString},
|
||||||
|
"invalid overlay argument error 0xdeadbeef"},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if got := tc.err.Error(); got != tc.want {
|
||||||
|
t.Errorf("Error: %q, want %q", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||||
{"mkdirTemp invalid ephemeral", &Params{ParentPerm: 0705}, &MountOverlayOp{
|
{"mkdirTemp invalid ephemeral", &Params{ParentPerm: 0705}, &MountOverlayOp{
|
||||||
Target: MustAbs("/"),
|
Target: MustAbs("/"),
|
||||||
@@ -16,7 +44,7 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
MustAbs("/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"),
|
MustAbs("/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"),
|
||||||
},
|
},
|
||||||
Upper: MustAbs("/proc/"),
|
Upper: MustAbs("/proc/"),
|
||||||
}, nil, msg.WrapErr(fs.ErrInvalid, `upperdir has unexpected value "/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: MustAbs("/"),
|
||||||
@@ -25,13 +53,13 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
MustAbs("/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"),
|
MustAbs("/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"),
|
||||||
},
|
},
|
||||||
Upper: MustAbs("/"),
|
Upper: MustAbs("/"),
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"evalSymlinks", 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),
|
||||||
{"evalSymlinks", 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),
|
||||||
}, nil, []kexpect{
|
}, nil, []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/sysroot", os.FileMode(0705)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0705)}, nil, nil),
|
||||||
{"mkdirTemp", expectArgs{"/", "overlay.upper.*"}, "overlay.upper.32768", errUnique},
|
call("mkdirTemp", stub.ExpectArgs{"/", "overlay.upper.*"}, "overlay.upper.32768", stub.UniqueError(6)),
|
||||||
}, wrapErrSelf(errUnique)},
|
}, stub.UniqueError(6)},
|
||||||
|
|
||||||
{"mkdirTemp work ephemeral", &Params{ParentPerm: 0705}, &MountOverlayOp{
|
{"mkdirTemp work ephemeral", &Params{ParentPerm: 0705}, &MountOverlayOp{
|
||||||
Target: MustAbs("/"),
|
Target: MustAbs("/"),
|
||||||
@@ -40,14 +68,14 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
MustAbs("/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"),
|
MustAbs("/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"),
|
||||||
},
|
},
|
||||||
Upper: MustAbs("/"),
|
Upper: MustAbs("/"),
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"evalSymlinks", 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),
|
||||||
{"evalSymlinks", 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),
|
||||||
}, nil, []kexpect{
|
}, nil, []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/sysroot", os.FileMode(0705)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0705)}, nil, nil),
|
||||||
{"mkdirTemp", expectArgs{"/", "overlay.upper.*"}, "overlay.upper.32768", nil},
|
call("mkdirTemp", stub.ExpectArgs{"/", "overlay.upper.*"}, "overlay.upper.32768", nil),
|
||||||
{"mkdirTemp", expectArgs{"/", "overlay.work.*"}, "overlay.work.32768", errUnique},
|
call("mkdirTemp", stub.ExpectArgs{"/", "overlay.work.*"}, "overlay.work.32768", stub.UniqueError(5)),
|
||||||
}, wrapErrSelf(errUnique)},
|
}, stub.UniqueError(5)},
|
||||||
|
|
||||||
{"success ephemeral", &Params{ParentPerm: 0705}, &MountOverlayOp{
|
{"success ephemeral", &Params{ParentPerm: 0705}, &MountOverlayOp{
|
||||||
Target: MustAbs("/"),
|
Target: MustAbs("/"),
|
||||||
@@ -56,20 +84,20 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
MustAbs("/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"),
|
MustAbs("/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"),
|
||||||
},
|
},
|
||||||
Upper: MustAbs("/"),
|
Upper: MustAbs("/"),
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"evalSymlinks", 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),
|
||||||
{"evalSymlinks", 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),
|
||||||
}, nil, []kexpect{
|
}, nil, []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/sysroot", os.FileMode(0705)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0705)}, nil, nil),
|
||||||
{"mkdirTemp", expectArgs{"/", "overlay.upper.*"}, "overlay.upper.32768", nil},
|
call("mkdirTemp", stub.ExpectArgs{"/", "overlay.upper.*"}, "overlay.upper.32768", nil),
|
||||||
{"mkdirTemp", expectArgs{"/", "overlay.work.*"}, "overlay.work.32768", nil},
|
call("mkdirTemp", stub.ExpectArgs{"/", "overlay.work.*"}, "overlay.work.32768", nil),
|
||||||
{"mount", expectArgs{"overlay", "/sysroot", "overlay", uintptr(0), "" +
|
call("mount", stub.ExpectArgs{"overlay", "/sysroot", "overlay", uintptr(0), "" +
|
||||||
"upperdir=overlay.upper.32768," +
|
"upperdir=overlay.upper.32768," +
|
||||||
"workdir=overlay.work.32768," +
|
"workdir=overlay.work.32768," +
|
||||||
"lowerdir=" +
|
"lowerdir=" +
|
||||||
`/host/var/lib/planterette/base/debian\:f92c9052:` +
|
`/host/var/lib/planterette/base/debian\:f92c9052:` +
|
||||||
`/host/var/lib/planterette/app/org.chromium.Chromium@debian\:f92c9052,` +
|
`/host/var/lib/planterette/app/org.chromium.Chromium@debian\:f92c9052,` +
|
||||||
"userxattr"}, nil, nil},
|
"userxattr"}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"short lower ro", &Params{ParentPerm: 0755}, &MountOverlayOp{
|
{"short lower ro", &Params{ParentPerm: 0755}, &MountOverlayOp{
|
||||||
@@ -77,11 +105,11 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
Lower: []*Absolute{
|
Lower: []*Absolute{
|
||||||
MustAbs("/mnt-root/nix/.ro-store"),
|
MustAbs("/mnt-root/nix/.ro-store"),
|
||||||
},
|
},
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"evalSymlinks", 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),
|
||||||
}, nil, []kexpect{
|
}, nil, []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/sysroot/nix/store", os.FileMode(0755)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0755)}, nil, nil),
|
||||||
}, msg.WrapErr(fs.ErrInvalid, "readonly overlay requires at least two lowerdir")},
|
}, &OverlayArgumentError{OverlayReadonlyLower, zeroString}},
|
||||||
|
|
||||||
{"success ro noPrefix", &Params{ParentPerm: 0755}, &MountOverlayOp{
|
{"success ro noPrefix", &Params{ParentPerm: 0755}, &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: MustAbs("/nix/store"),
|
||||||
@@ -90,16 +118,16 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
MustAbs("/mnt-root/nix/.ro-store0"),
|
MustAbs("/mnt-root/nix/.ro-store0"),
|
||||||
},
|
},
|
||||||
noPrefix: true,
|
noPrefix: true,
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"evalSymlinks", 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),
|
||||||
{"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/.ro-store0", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/.ro-store0", nil),
|
||||||
}, nil, []kexpect{
|
}, nil, []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/nix/store", os.FileMode(0755)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/nix/store", os.FileMode(0755)}, nil, nil),
|
||||||
{"mount", expectArgs{"overlay", "/nix/store", "overlay", uintptr(0), "" +
|
call("mount", stub.ExpectArgs{"overlay", "/nix/store", "overlay", uintptr(0), "" +
|
||||||
"lowerdir=" +
|
"lowerdir=" +
|
||||||
"/host/mnt-root/nix/.ro-store:" +
|
"/host/mnt-root/nix/.ro-store:" +
|
||||||
"/host/mnt-root/nix/.ro-store0," +
|
"/host/mnt-root/nix/.ro-store0," +
|
||||||
"userxattr"}, nil, nil},
|
"userxattr"}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"success ro", &Params{ParentPerm: 0755}, &MountOverlayOp{
|
{"success ro", &Params{ParentPerm: 0755}, &MountOverlayOp{
|
||||||
@@ -108,102 +136,102 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
MustAbs("/mnt-root/nix/.ro-store"),
|
MustAbs("/mnt-root/nix/.ro-store"),
|
||||||
MustAbs("/mnt-root/nix/.ro-store0"),
|
MustAbs("/mnt-root/nix/.ro-store0"),
|
||||||
},
|
},
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"evalSymlinks", 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),
|
||||||
{"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/.ro-store0", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/.ro-store0", nil),
|
||||||
}, nil, []kexpect{
|
}, nil, []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/sysroot/nix/store", os.FileMode(0755)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0755)}, nil, nil),
|
||||||
{"mount", expectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "" +
|
call("mount", stub.ExpectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "" +
|
||||||
"lowerdir=" +
|
"lowerdir=" +
|
||||||
"/host/mnt-root/nix/.ro-store:" +
|
"/host/mnt-root/nix/.ro-store:" +
|
||||||
"/host/mnt-root/nix/.ro-store0," +
|
"/host/mnt-root/nix/.ro-store0," +
|
||||||
"userxattr"}, nil, nil},
|
"userxattr"}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"nil lower", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
{"nil lower", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: MustAbs("/nix/store"),
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"evalSymlinks", 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),
|
||||||
{"evalSymlinks", 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),
|
||||||
}, nil, []kexpect{
|
}, nil, []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil),
|
||||||
}, msg.WrapErr(fs.ErrInvalid, "overlay requires at least one lowerdir")},
|
}, &OverlayArgumentError{OverlayEmptyLower, zeroString}},
|
||||||
|
|
||||||
{"evalSymlinks upper", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
{"evalSymlinks upper", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", errUnique},
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", stub.UniqueError(4)),
|
||||||
}, wrapErrSelf(errUnique), nil, nil},
|
}, stub.UniqueError(4), nil, nil},
|
||||||
|
|
||||||
{"evalSymlinks work", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
{"evalSymlinks work", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"evalSymlinks", 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),
|
||||||
{"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", errUnique},
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", stub.UniqueError(3)),
|
||||||
}, wrapErrSelf(errUnique), nil, nil},
|
}, stub.UniqueError(3), nil, nil},
|
||||||
|
|
||||||
{"evalSymlinks lower", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
{"evalSymlinks lower", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"evalSymlinks", 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),
|
||||||
{"evalSymlinks", 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),
|
||||||
{"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", errUnique},
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", stub.UniqueError(2)),
|
||||||
}, wrapErrSelf(errUnique), nil, nil},
|
}, stub.UniqueError(2), nil, nil},
|
||||||
|
|
||||||
{"mkdirAll", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
{"mkdirAll", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"evalSymlinks", 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),
|
||||||
{"evalSymlinks", 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),
|
||||||
{"evalSymlinks", 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),
|
||||||
}, nil, []kexpect{
|
}, nil, []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, errUnique},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, stub.UniqueError(1)),
|
||||||
}, wrapErrSelf(errUnique)},
|
}, stub.UniqueError(1)},
|
||||||
|
|
||||||
{"mount", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
{"mount", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"evalSymlinks", 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),
|
||||||
{"evalSymlinks", 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),
|
||||||
{"evalSymlinks", 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),
|
||||||
}, nil, []kexpect{
|
}, nil, []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil),
|
||||||
{"mount", expectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "upperdir=/host/mnt-root/nix/.rw-store/.upper,workdir=/host/mnt-root/nix/.rw-store/.work,lowerdir=/host/mnt-root/nix/ro-store,userxattr"}, nil, errUnique},
|
call("mount", stub.ExpectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "upperdir=/host/mnt-root/nix/.rw-store/.upper,workdir=/host/mnt-root/nix/.rw-store/.work,lowerdir=/host/mnt-root/nix/ro-store,userxattr"}, nil, stub.UniqueError(0)),
|
||||||
}, wrapErrSuffix(errUnique, `cannot mount overlay on "/nix/store":`)},
|
}, stub.UniqueError(0)},
|
||||||
|
|
||||||
{"success single layer", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
{"success single layer", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
||||||
Target: MustAbs("/nix/store"),
|
Target: MustAbs("/nix/store"),
|
||||||
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"evalSymlinks", 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),
|
||||||
{"evalSymlinks", 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),
|
||||||
{"evalSymlinks", 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),
|
||||||
}, nil, []kexpect{
|
}, nil, []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil),
|
||||||
{"mount", expectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "" +
|
call("mount", stub.ExpectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "" +
|
||||||
"upperdir=/host/mnt-root/nix/.rw-store/.upper," +
|
"upperdir=/host/mnt-root/nix/.rw-store/.upper," +
|
||||||
"workdir=/host/mnt-root/nix/.rw-store/.work," +
|
"workdir=/host/mnt-root/nix/.rw-store/.work," +
|
||||||
"lowerdir=/host/mnt-root/nix/ro-store," +
|
"lowerdir=/host/mnt-root/nix/ro-store," +
|
||||||
"userxattr"}, nil, nil},
|
"userxattr"}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"success", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
{"success", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
||||||
@@ -217,17 +245,17 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||||
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
Work: MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"evalSymlinks", 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),
|
||||||
{"evalSymlinks", 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),
|
||||||
{"evalSymlinks", 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),
|
||||||
{"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/ro-store0", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/ro-store0", nil),
|
||||||
{"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store1"}, "/mnt-root/nix/ro-store1", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store1"}, "/mnt-root/nix/ro-store1", nil),
|
||||||
{"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store2"}, "/mnt-root/nix/ro-store2", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store2"}, "/mnt-root/nix/ro-store2", nil),
|
||||||
{"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store3"}, "/mnt-root/nix/ro-store3", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store3"}, "/mnt-root/nix/ro-store3", nil),
|
||||||
}, nil, []kexpect{
|
}, nil, []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil),
|
||||||
{"mount", expectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "" +
|
call("mount", stub.ExpectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "" +
|
||||||
"upperdir=/host/mnt-root/nix/.rw-store/.upper," +
|
"upperdir=/host/mnt-root/nix/.rw-store/.upper," +
|
||||||
"workdir=/host/mnt-root/nix/.rw-store/.work," +
|
"workdir=/host/mnt-root/nix/.rw-store/.work," +
|
||||||
"lowerdir=" +
|
"lowerdir=" +
|
||||||
@@ -236,13 +264,13 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
"/host/mnt-root/nix/ro-store1:" +
|
"/host/mnt-root/nix/ro-store1:" +
|
||||||
"/host/mnt-root/nix/ro-store2:" +
|
"/host/mnt-root/nix/ro-store2:" +
|
||||||
"/host/mnt-root/nix/ro-store3," +
|
"/host/mnt-root/nix/ro-store3," +
|
||||||
"userxattr"}, nil, nil},
|
"userxattr"}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("unreachable", func(t *testing.T) {
|
t.Run("unreachable", func(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 := msg.WrapErr(fs.ErrClosed, "impossible overlay state reached")
|
wantErr := OpStateError("overlay")
|
||||||
if err := (&MountOverlayOp{
|
if err := (&MountOverlayOp{
|
||||||
Work: MustAbs("/"),
|
Work: MustAbs("/"),
|
||||||
}).early(nil, nil); !errors.Is(err, wantErr) {
|
}).early(nil, nil); !errors.Is(err, wantErr) {
|
||||||
|
|||||||
@@ -39,13 +39,11 @@ 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(FHSRoot, intermediatePatternTmpfile); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
} else if _, err = f.Write(t.Data); err != nil {
|
} else if _, err = f.Write(t.Data); err != nil {
|
||||||
return wrapErrSuffix(err,
|
return err
|
||||||
"cannot write to intermediate file:")
|
|
||||||
} else if err = f.Close(); err != nil {
|
} else if err = f.Close(); err != nil {
|
||||||
return wrapErrSuffix(err,
|
return err
|
||||||
"cannot close intermediate file:")
|
|
||||||
} else {
|
} else {
|
||||||
tmpPath = f.Name()
|
tmpPath = f.Name()
|
||||||
}
|
}
|
||||||
@@ -61,7 +59,7 @@ func (t *TmpfileOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if err = k.remove(tmpPath); err != nil {
|
} else if err = k.remove(tmpPath); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package container
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/stub"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTmpfileOp(t *testing.T) {
|
func TestTmpfileOp(t *testing.T) {
|
||||||
@@ -16,59 +18,59 @@ func TestTmpfileOp(t *testing.T) {
|
|||||||
{"createTemp", &Params{ParentPerm: 0700}, &TmpfileOp{
|
{"createTemp", &Params{ParentPerm: 0700}, &TmpfileOp{
|
||||||
Path: samplePath,
|
Path: samplePath,
|
||||||
Data: sampleData,
|
Data: sampleData,
|
||||||
}, nil, nil, []kexpect{
|
}, nil, nil, []stub.Call{
|
||||||
{"createTemp", expectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), errUnique},
|
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), stub.UniqueError(5)),
|
||||||
}, wrapErrSelf(errUnique)},
|
}, stub.UniqueError(5)},
|
||||||
|
|
||||||
{"Write", &Params{ParentPerm: 0700}, &TmpfileOp{
|
{"Write", &Params{ParentPerm: 0700}, &TmpfileOp{
|
||||||
Path: samplePath,
|
Path: samplePath,
|
||||||
Data: sampleData,
|
Data: sampleData,
|
||||||
}, nil, nil, []kexpect{
|
}, nil, nil, []stub.Call{
|
||||||
{"createTemp", expectArgs{"/", "tmp.*"}, writeErrOsFile{errUnique}, nil},
|
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, writeErrOsFile{stub.UniqueError(4)}, nil),
|
||||||
}, wrapErrSuffix(errUnique, "cannot write to intermediate file:")},
|
}, stub.UniqueError(4)},
|
||||||
|
|
||||||
{"Close", &Params{ParentPerm: 0700}, &TmpfileOp{
|
{"Close", &Params{ParentPerm: 0700}, &TmpfileOp{
|
||||||
Path: samplePath,
|
Path: samplePath,
|
||||||
Data: sampleData,
|
Data: sampleData,
|
||||||
}, nil, nil, []kexpect{
|
}, nil, nil, []stub.Call{
|
||||||
{"createTemp", expectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, errUnique), nil},
|
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, stub.UniqueError(3)), nil),
|
||||||
}, wrapErrSuffix(errUnique, "cannot close intermediate file:")},
|
}, stub.UniqueError(3)},
|
||||||
|
|
||||||
{"ensureFile", &Params{ParentPerm: 0700}, &TmpfileOp{
|
{"ensureFile", &Params{ParentPerm: 0700}, &TmpfileOp{
|
||||||
Path: samplePath,
|
Path: samplePath,
|
||||||
Data: sampleData,
|
Data: sampleData,
|
||||||
}, nil, nil, []kexpect{
|
}, nil, nil, []stub.Call{
|
||||||
{"createTemp", expectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), nil},
|
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), nil),
|
||||||
{"ensureFile", expectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, errUnique},
|
call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, stub.UniqueError(2)),
|
||||||
}, errUnique},
|
}, stub.UniqueError(2)},
|
||||||
|
|
||||||
{"bindMount", &Params{ParentPerm: 0700}, &TmpfileOp{
|
{"bindMount", &Params{ParentPerm: 0700}, &TmpfileOp{
|
||||||
Path: samplePath,
|
Path: samplePath,
|
||||||
Data: sampleData,
|
Data: sampleData,
|
||||||
}, nil, nil, []kexpect{
|
}, nil, nil, []stub.Call{
|
||||||
{"createTemp", expectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), nil},
|
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), nil),
|
||||||
{"ensureFile", expectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil},
|
call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil),
|
||||||
{"bindMount", expectArgs{"tmp.32768", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, errUnique},
|
call("bindMount", stub.ExpectArgs{"tmp.32768", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, stub.UniqueError(1)),
|
||||||
}, errUnique},
|
}, stub.UniqueError(1)},
|
||||||
|
|
||||||
{"remove", &Params{ParentPerm: 0700}, &TmpfileOp{
|
{"remove", &Params{ParentPerm: 0700}, &TmpfileOp{
|
||||||
Path: samplePath,
|
Path: samplePath,
|
||||||
Data: sampleData,
|
Data: sampleData,
|
||||||
}, nil, nil, []kexpect{
|
}, nil, nil, []stub.Call{
|
||||||
{"createTemp", expectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), nil},
|
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), nil),
|
||||||
{"ensureFile", expectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil},
|
call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil),
|
||||||
{"bindMount", expectArgs{"tmp.32768", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, nil},
|
call("bindMount", stub.ExpectArgs{"tmp.32768", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, nil),
|
||||||
{"remove", expectArgs{"tmp.32768"}, nil, errUnique},
|
call("remove", stub.ExpectArgs{"tmp.32768"}, nil, stub.UniqueError(0)),
|
||||||
}, wrapErrSelf(errUnique)},
|
}, stub.UniqueError(0)},
|
||||||
|
|
||||||
{"success", &Params{ParentPerm: 0700}, &TmpfileOp{
|
{"success", &Params{ParentPerm: 0700}, &TmpfileOp{
|
||||||
Path: samplePath,
|
Path: samplePath,
|
||||||
Data: sampleData,
|
Data: sampleData,
|
||||||
}, nil, nil, []kexpect{
|
}, nil, nil, []stub.Call{
|
||||||
{"createTemp", expectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), nil},
|
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), nil),
|
||||||
{"ensureFile", expectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil},
|
call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil),
|
||||||
{"bindMount", expectArgs{"tmp.32768", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, nil},
|
call("bindMount", stub.ExpectArgs{"tmp.32768", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, nil),
|
||||||
{"remove", expectArgs{"tmp.32768"}, nil, nil},
|
call("remove", stub.ExpectArgs{"tmp.32768"}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -22,10 +22,9 @@ func (p *MountProcOp) early(*setupState, syscallDispatcher) error { return nil }
|
|||||||
func (p *MountProcOp) apply(state *setupState, k syscallDispatcher) error {
|
func (p *MountProcOp) apply(state *setupState, k syscallDispatcher) error {
|
||||||
target := toSysroot(p.Target.String())
|
target := toSysroot(p.Target.String())
|
||||||
if err := k.mkdirAll(target, state.ParentPerm); err != nil {
|
if err := k.mkdirAll(target, state.ParentPerm); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
}
|
}
|
||||||
return wrapErrSuffix(k.mount(SourceProc, target, FstypeProc, MS_NOSUID|MS_NOEXEC|MS_NODEV, zeroString),
|
return k.mount(SourceProc, target, FstypeProc, MS_NOSUID|MS_NOEXEC|MS_NODEV, zeroString)
|
||||||
fmt.Sprintf("cannot mount proc on %q:", p.Target.String()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *MountProcOp) Is(op Op) bool {
|
func (p *MountProcOp) Is(op Op) bool {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package container
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/stub"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMountProcOp(t *testing.T) {
|
func TestMountProcOp(t *testing.T) {
|
||||||
@@ -10,16 +12,16 @@ func TestMountProcOp(t *testing.T) {
|
|||||||
{"mkdir", &Params{ParentPerm: 0755},
|
{"mkdir", &Params{ParentPerm: 0755},
|
||||||
&MountProcOp{
|
&MountProcOp{
|
||||||
Target: MustAbs("/proc/"),
|
Target: MustAbs("/proc/"),
|
||||||
}, nil, nil, []kexpect{
|
}, nil, nil, []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, errUnique},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, stub.UniqueError(0)),
|
||||||
}, wrapErrSelf(errUnique)},
|
}, stub.UniqueError(0)},
|
||||||
|
|
||||||
{"success", &Params{ParentPerm: 0700},
|
{"success", &Params{ParentPerm: 0700},
|
||||||
&MountProcOp{
|
&MountProcOp{
|
||||||
Target: MustAbs("/proc/"),
|
Target: MustAbs("/proc/"),
|
||||||
}, nil, nil, []kexpect{
|
}, nil, nil, []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/sysroot/proc", os.FileMode(0700)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0700)}, nil, nil),
|
||||||
{"mount", expectArgs{"proc", "/sysroot/proc", "proc", uintptr(0xe), ""}, nil, nil},
|
call("mount", stub.ExpectArgs{"proc", "/sysroot/proc", "proc", uintptr(0xe), ""}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ type RemountOp struct {
|
|||||||
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(_ *setupState, k syscallDispatcher) error {
|
||||||
return wrapErrSuffix(k.remount(toSysroot(r.Target.String()), r.Flags),
|
return k.remount(toSysroot(r.Target.String()), r.Flags)
|
||||||
fmt.Sprintf("cannot remount %q:", r.Target))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RemountOp) Is(op Op) bool {
|
func (r *RemountOp) Is(op Op) bool {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package container
|
|||||||
import (
|
import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/stub"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRemountOp(t *testing.T) {
|
func TestRemountOp(t *testing.T) {
|
||||||
@@ -10,8 +12,8 @@ func TestRemountOp(t *testing.T) {
|
|||||||
{"success", new(Params), &RemountOp{
|
{"success", new(Params), &RemountOp{
|
||||||
Target: MustAbs("/"),
|
Target: MustAbs("/"),
|
||||||
Flags: syscall.MS_RDONLY,
|
Flags: syscall.MS_RDONLY,
|
||||||
}, nil, nil, []kexpect{
|
}, nil, nil, []stub.Call{
|
||||||
{"remount", expectArgs{"/sysroot", uintptr(1)}, nil, nil},
|
call("remount", stub.ExpectArgs{"/sysroot", uintptr(1)}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package container
|
|||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
|
||||||
"path"
|
"path"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,10 +29,10 @@ 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 !isAbs(l.LinkName) {
|
||||||
return msg.WrapErr(fs.ErrInvalid, fmt.Sprintf("path %q is not absolute", l.LinkName))
|
return &AbsoluteError{l.LinkName}
|
||||||
}
|
}
|
||||||
if name, err := k.readlink(l.LinkName); err != nil {
|
if name, err := k.readlink(l.LinkName); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
} else {
|
} else {
|
||||||
l.LinkName = name
|
l.LinkName = name
|
||||||
}
|
}
|
||||||
@@ -44,9 +43,9 @@ func (l *SymlinkOp) early(_ *setupState, k syscallDispatcher) error {
|
|||||||
func (l *SymlinkOp) apply(state *setupState, k syscallDispatcher) error {
|
func (l *SymlinkOp) apply(state *setupState, k syscallDispatcher) error {
|
||||||
target := toSysroot(l.Target.String())
|
target := toSysroot(l.Target.String())
|
||||||
if err := k.mkdirAll(path.Dir(target), state.ParentPerm); err != nil {
|
if err := k.mkdirAll(path.Dir(target), state.ParentPerm); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
}
|
}
|
||||||
return wrapErrSelf(k.symlink(l.LinkName, target))
|
return k.symlink(l.LinkName, target)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *SymlinkOp) Is(op Op) bool {
|
func (l *SymlinkOp) Is(op Op) bool {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/fs"
|
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/stub"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSymlinkOp(t *testing.T) {
|
func TestSymlinkOp(t *testing.T) {
|
||||||
@@ -11,41 +12,41 @@ func TestSymlinkOp(t *testing.T) {
|
|||||||
{"mkdir", &Params{ParentPerm: 0700}, &SymlinkOp{
|
{"mkdir", &Params{ParentPerm: 0700}, &SymlinkOp{
|
||||||
Target: MustAbs("/etc/nixos"),
|
Target: MustAbs("/etc/nixos"),
|
||||||
LinkName: "/etc/static/nixos",
|
LinkName: "/etc/static/nixos",
|
||||||
}, nil, nil, []kexpect{
|
}, nil, nil, []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/sysroot/etc", os.FileMode(0700)}, nil, errUnique},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/etc", os.FileMode(0700)}, nil, stub.UniqueError(1)),
|
||||||
}, wrapErrSelf(errUnique)},
|
}, stub.UniqueError(1)},
|
||||||
|
|
||||||
{"abs", &Params{ParentPerm: 0755}, &SymlinkOp{
|
{"abs", &Params{ParentPerm: 0755}, &SymlinkOp{
|
||||||
Target: MustAbs("/etc/mtab"),
|
Target: MustAbs("/etc/mtab"),
|
||||||
LinkName: "etc/mtab",
|
LinkName: "etc/mtab",
|
||||||
Dereference: true,
|
Dereference: true,
|
||||||
}, nil, msg.WrapErr(fs.ErrInvalid, `path "etc/mtab" is not absolute`), nil, nil},
|
}, nil, &AbsoluteError{"etc/mtab"}, nil, nil},
|
||||||
|
|
||||||
{"readlink", &Params{ParentPerm: 0755}, &SymlinkOp{
|
{"readlink", &Params{ParentPerm: 0755}, &SymlinkOp{
|
||||||
Target: MustAbs("/etc/mtab"),
|
Target: MustAbs("/etc/mtab"),
|
||||||
LinkName: "/etc/mtab",
|
LinkName: "/etc/mtab",
|
||||||
Dereference: true,
|
Dereference: true,
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"readlink", expectArgs{"/etc/mtab"}, "/proc/mounts", errUnique},
|
call("readlink", stub.ExpectArgs{"/etc/mtab"}, "/proc/mounts", stub.UniqueError(0)),
|
||||||
}, wrapErrSelf(errUnique), nil, nil},
|
}, stub.UniqueError(0), nil, nil},
|
||||||
|
|
||||||
{"success noderef", &Params{ParentPerm: 0700}, &SymlinkOp{
|
{"success noderef", &Params{ParentPerm: 0700}, &SymlinkOp{
|
||||||
Target: MustAbs("/etc/nixos"),
|
Target: MustAbs("/etc/nixos"),
|
||||||
LinkName: "/etc/static/nixos",
|
LinkName: "/etc/static/nixos",
|
||||||
}, nil, nil, []kexpect{
|
}, nil, nil, []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/sysroot/etc", os.FileMode(0700)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/etc", os.FileMode(0700)}, nil, nil),
|
||||||
{"symlink", expectArgs{"/etc/static/nixos", "/sysroot/etc/nixos"}, nil, nil},
|
call("symlink", stub.ExpectArgs{"/etc/static/nixos", "/sysroot/etc/nixos"}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"success", &Params{ParentPerm: 0755}, &SymlinkOp{
|
{"success", &Params{ParentPerm: 0755}, &SymlinkOp{
|
||||||
Target: MustAbs("/etc/mtab"),
|
Target: MustAbs("/etc/mtab"),
|
||||||
LinkName: "/etc/mtab",
|
LinkName: "/etc/mtab",
|
||||||
Dereference: true,
|
Dereference: true,
|
||||||
}, []kexpect{
|
}, []stub.Call{
|
||||||
{"readlink", expectArgs{"/etc/mtab"}, "/proc/mounts", nil},
|
call("readlink", stub.ExpectArgs{"/etc/mtab"}, "/proc/mounts", nil),
|
||||||
}, nil, []kexpect{
|
}, nil, []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/sysroot/etc", os.FileMode(0755)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/etc", os.FileMode(0755)}, nil, nil),
|
||||||
{"symlink", expectArgs{"/proc/mounts", "/sysroot/etc/mtab"}, nil, nil},
|
call("symlink", stub.ExpectArgs{"/proc/mounts", "/sysroot/etc/mtab"}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,20 @@ package container
|
|||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
. "syscall"
|
. "syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { gob.Register(new(MountTmpfsOp)) }
|
func init() { gob.Register(new(MountTmpfsOp)) }
|
||||||
|
|
||||||
|
type TmpfsSizeError int
|
||||||
|
|
||||||
|
func (e TmpfsSizeError) Error() string {
|
||||||
|
return "tmpfs size " + strconv.Itoa(int(e)) + " out of bounds"
|
||||||
|
}
|
||||||
|
|
||||||
// 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 *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})
|
||||||
@@ -36,7 +42,7 @@ func (t *MountTmpfsOp) Valid() bool { return t !=
|
|||||||
func (t *MountTmpfsOp) early(*setupState, syscallDispatcher) error { return nil }
|
func (t *MountTmpfsOp) early(*setupState, syscallDispatcher) error { return nil }
|
||||||
func (t *MountTmpfsOp) apply(_ *setupState, k syscallDispatcher) error {
|
func (t *MountTmpfsOp) apply(_ *setupState, k syscallDispatcher) error {
|
||||||
if t.Size < 0 || t.Size > math.MaxUint>>1 {
|
if t.Size < 0 || t.Size > math.MaxUint>>1 {
|
||||||
return msg.WrapErr(fs.ErrInvalid, fmt.Sprintf("size %d out of bounds", t.Size))
|
return TmpfsSizeError(t.Size)
|
||||||
}
|
}
|
||||||
return k.mountTmpfs(t.FSName, toSysroot(t.Path.String()), t.Flags, t.Size, t.Perm)
|
return k.mountTmpfs(t.FSName, toSysroot(t.Path.String()), t.Flags, t.Size, t.Perm)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,40 @@
|
|||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/fs"
|
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/stub"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMountTmpfsOp(t *testing.T) {
|
func TestMountTmpfsOp(t *testing.T) {
|
||||||
|
t.Run("size error", func(t *testing.T) {
|
||||||
|
tmpfsSizeError := TmpfsSizeError(-1)
|
||||||
|
want := "tmpfs size -1 out of bounds"
|
||||||
|
if got := tmpfsSizeError.Error(); got != want {
|
||||||
|
t.Errorf("Error: %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||||
{"size oob", new(Params), &MountTmpfsOp{
|
{"size oob", new(Params), &MountTmpfsOp{
|
||||||
Size: -1,
|
Size: -1,
|
||||||
}, nil, nil, nil, msg.WrapErr(fs.ErrInvalid, "size -1 out of bounds")},
|
}, nil, nil, nil, TmpfsSizeError(-1)},
|
||||||
|
|
||||||
{"success", new(Params), &MountTmpfsOp{
|
{"success", new(Params), &MountTmpfsOp{
|
||||||
FSName: "ephemeral",
|
FSName: "ephemeral",
|
||||||
Path: MustAbs("/run/user/1000/"),
|
Path: MustAbs("/run/user/1000/"),
|
||||||
Size: 1 << 10,
|
Size: 1 << 10,
|
||||||
Perm: 0700,
|
Perm: 0700,
|
||||||
}, nil, nil, []kexpect{
|
}, nil, nil, []stub.Call{
|
||||||
{"mountTmpfs", expectArgs{
|
call("mountTmpfs", stub.ExpectArgs{
|
||||||
"ephemeral", // fsname
|
"ephemeral", // fsname
|
||||||
"/sysroot/run/user/1000", // target
|
"/sysroot/run/user/1000", // target
|
||||||
uintptr(0), // flags
|
uintptr(0), // flags
|
||||||
0x400, // size
|
0x400, // size
|
||||||
os.FileMode(0700), // perm
|
os.FileMode(0700), // perm
|
||||||
}, nil, nil},
|
}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ const (
|
|||||||
// Note that any source value is allowed when fstype is [FstypeOverlay].
|
// Note that any source value is allowed when fstype is [FstypeOverlay].
|
||||||
SourceOverlay = "overlay"
|
SourceOverlay = "overlay"
|
||||||
|
|
||||||
|
// SourceTmpfs is used when mounting tmpfs.
|
||||||
|
SourceTmpfs = "tmpfs"
|
||||||
// SourceTmpfsRootfs is used when mounting the tmpfs instance backing the intermediate root.
|
// SourceTmpfsRootfs is used when mounting the tmpfs instance backing the intermediate root.
|
||||||
SourceTmpfsRootfs = "rootfs"
|
SourceTmpfsRootfs = "rootfs"
|
||||||
// SourceTmpfsDevtmpfs is used when mounting tmpfs representing a subset of host devtmpfs.
|
// SourceTmpfsDevtmpfs is used when mounting tmpfs representing a subset of host devtmpfs.
|
||||||
@@ -104,8 +106,7 @@ func (p *procPaths) bindMount(source, target string, flags uintptr, eq bool) err
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 wrapErrSuffix(err,
|
return err
|
||||||
fmt.Sprintf("cannot mount %q on %q:", source, target))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.k.remount(target, flags)
|
return p.k.remount(target, flags)
|
||||||
@@ -117,7 +118,7 @@ func (p *procPaths) remount(target string, flags uintptr) error {
|
|||||||
|
|
||||||
var targetFinal string
|
var targetFinal string
|
||||||
if v, err := p.k.evalSymlinks(target); err != nil {
|
if v, err := p.k.evalSymlinks(target); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
} else {
|
} else {
|
||||||
targetFinal = v
|
targetFinal = v
|
||||||
if targetFinal != target {
|
if targetFinal != target {
|
||||||
@@ -133,14 +134,12 @@ func (p *procPaths) remount(target string, flags uintptr) error {
|
|||||||
destFd, err = p.k.open(targetFinal, O_PATH|O_CLOEXEC, 0)
|
destFd, err = p.k.open(targetFinal, O_PATH|O_CLOEXEC, 0)
|
||||||
return
|
return
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return wrapErrSuffix(err,
|
return &os.PathError{Op: "open", Path: targetFinal, Err: err}
|
||||||
fmt.Sprintf("cannot open %q:", targetFinal))
|
|
||||||
}
|
}
|
||||||
if v, err := p.k.readlink(p.fd(destFd)); err != nil {
|
if v, err := p.k.readlink(p.fd(destFd)); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
} else if err = p.k.close(destFd); err != nil {
|
} else if err = p.k.close(destFd); err != nil {
|
||||||
return wrapErrSuffix(err,
|
return &os.PathError{Op: "close", Path: targetFinal, Err: err}
|
||||||
fmt.Sprintf("cannot close %q:", targetFinal))
|
|
||||||
} else {
|
} else {
|
||||||
targetKFinal = v
|
targetKFinal = v
|
||||||
}
|
}
|
||||||
@@ -150,17 +149,11 @@ func (p *procPaths) remount(target string, flags uintptr) error {
|
|||||||
return p.mountinfo(func(d *vfs.MountInfoDecoder) error {
|
return p.mountinfo(func(d *vfs.MountInfoDecoder) error {
|
||||||
n, err := d.Unfold(targetKFinal)
|
n, err := d.Unfold(targetKFinal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ESTALE) {
|
return err
|
||||||
return msg.WrapErr(err,
|
|
||||||
fmt.Sprintf("mount point %q never appeared in mountinfo", targetKFinal))
|
|
||||||
}
|
|
||||||
return wrapErrSuffix(err,
|
|
||||||
"cannot unfold mount hierarchy:")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = remountWithFlags(p.k, n, mf); err != nil {
|
if err = remountWithFlags(p.k, n, mf); err != nil {
|
||||||
return wrapErrSuffix(err,
|
return err
|
||||||
fmt.Sprintf("cannot remount %q:", n.Clean))
|
|
||||||
}
|
}
|
||||||
if flags&MS_REC == 0 {
|
if flags&MS_REC == 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -172,11 +165,8 @@ func (p *procPaths) remount(target string, flags uintptr) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err = remountWithFlags(p.k, cur, mf)
|
if err = remountWithFlags(p.k, cur, mf); err != nil && !errors.Is(err, EACCES) {
|
||||||
|
return err
|
||||||
if err != nil && !errors.Is(err, EACCES) {
|
|
||||||
return wrapErrSuffix(err,
|
|
||||||
fmt.Sprintf("cannot propagate flags to %q:", cur.Clean))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,15 +195,13 @@ func mountTmpfs(k syscallDispatcher, fsname, target string, flags uintptr, size
|
|||||||
// syscallDispatcher.mountTmpfs must not be called from this function
|
// syscallDispatcher.mountTmpfs must not be called from this function
|
||||||
|
|
||||||
if err := k.mkdirAll(target, parentPerm(perm)); err != nil {
|
if err := k.mkdirAll(target, parentPerm(perm)); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
}
|
}
|
||||||
opt := fmt.Sprintf("mode=%#o", perm)
|
opt := fmt.Sprintf("mode=%#o", perm)
|
||||||
if size > 0 {
|
if size > 0 {
|
||||||
opt += fmt.Sprintf(",size=%d", size)
|
opt += fmt.Sprintf(",size=%d", size)
|
||||||
}
|
}
|
||||||
return wrapErrSuffix(
|
return k.mount(fsname, target, FstypeTmpfs, flags, opt)
|
||||||
k.mount(fsname, target, FstypeTmpfs, flags, opt),
|
|
||||||
fmt.Sprintf("cannot mount tmpfs on %q:", target))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parentPerm(perm os.FileMode) os.FileMode {
|
func parentPerm(perm os.FileMode) os.FileMode {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/stub"
|
||||||
"hakurei.app/container/vfs"
|
"hakurei.app/container/vfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -12,25 +13,25 @@ func TestBindMount(t *testing.T) {
|
|||||||
checkSimple(t, "bindMount", []simpleTestCase{
|
checkSimple(t, "bindMount", []simpleTestCase{
|
||||||
{"mount", func(k syscallDispatcher) error {
|
{"mount", func(k syscallDispatcher) error {
|
||||||
return newProcPaths(k, hostPath).bindMount("/host/nix", "/sysroot/nix", syscall.MS_RDONLY, true)
|
return newProcPaths(k, hostPath).bindMount("/host/nix", "/sysroot/nix", syscall.MS_RDONLY, true)
|
||||||
}, [][]kexpect{{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
{"verbosef", expectArgs{"resolved %q flags %#x", []any{"/sysroot/nix", uintptr(1)}}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"resolved %q flags %#x", []any{"/sysroot/nix", uintptr(1)}}, nil, nil),
|
||||||
{"mount", expectArgs{"/host/nix", "/sysroot/nix", "", uintptr(0x9000), ""}, nil, errUnique},
|
call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/nix", "", uintptr(0x9000), ""}, nil, stub.UniqueError(0xbad)),
|
||||||
}}, wrapErrSuffix(errUnique, `cannot mount "/host/nix" on "/sysroot/nix":`)},
|
}}, stub.UniqueError(0xbad)},
|
||||||
|
|
||||||
{"success ne", func(k syscallDispatcher) error {
|
{"success ne", func(k syscallDispatcher) error {
|
||||||
return newProcPaths(k, hostPath).bindMount("/host/nix", "/sysroot/.host-nix", syscall.MS_RDONLY, false)
|
return newProcPaths(k, hostPath).bindMount("/host/nix", "/sysroot/.host-nix", syscall.MS_RDONLY, false)
|
||||||
}, [][]kexpect{{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
{"verbosef", expectArgs{"resolved %q on %q flags %#x", []any{"/host/nix", "/sysroot/.host-nix", uintptr(1)}}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"resolved %q on %q flags %#x", []any{"/host/nix", "/sysroot/.host-nix", uintptr(1)}}, nil, nil),
|
||||||
{"mount", expectArgs{"/host/nix", "/sysroot/.host-nix", "", uintptr(0x9000), ""}, nil, nil},
|
call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/.host-nix", "", uintptr(0x9000), ""}, nil, nil),
|
||||||
{"remount", 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 syscallDispatcher) error {
|
||||||
return newProcPaths(k, hostPath).bindMount("/host/nix", "/sysroot/nix", syscall.MS_RDONLY, true)
|
return newProcPaths(k, hostPath).bindMount("/host/nix", "/sysroot/nix", syscall.MS_RDONLY, true)
|
||||||
}, [][]kexpect{{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
{"verbosef", expectArgs{"resolved %q flags %#x", []any{"/sysroot/nix", uintptr(1)}}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"resolved %q flags %#x", []any{"/sysroot/nix", uintptr(1)}}, nil, nil),
|
||||||
{"mount", expectArgs{"/host/nix", "/sysroot/nix", "", uintptr(0x9000), ""}, nil, nil},
|
call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/nix", "", uintptr(0x9000), ""}, nil, nil),
|
||||||
{"remount", expectArgs{"/sysroot/nix", uintptr(1)}, nil, nil},
|
call("remount", stub.ExpectArgs{"/sysroot/nix", uintptr(1)}, nil, nil),
|
||||||
}}, nil},
|
}}, nil},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -81,138 +82,138 @@ func TestRemount(t *testing.T) {
|
|||||||
checkSimple(t, "remount", []simpleTestCase{
|
checkSimple(t, "remount", []simpleTestCase{
|
||||||
{"evalSymlinks", func(k syscallDispatcher) error {
|
{"evalSymlinks", func(k syscallDispatcher) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, [][]kexpect{{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
{"evalSymlinks", expectArgs{"/sysroot/nix"}, "/sysroot/nix", errUnique},
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", stub.UniqueError(6)),
|
||||||
}}, wrapErrSelf(errUnique)},
|
}}, stub.UniqueError(6)},
|
||||||
|
|
||||||
{"open", func(k syscallDispatcher) error {
|
{"open", func(k syscallDispatcher) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, [][]kexpect{{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
{"evalSymlinks", expectArgs{"/sysroot/nix"}, "/sysroot/nix", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||||
{"open", expectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, errUnique},
|
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, stub.UniqueError(5)),
|
||||||
}}, wrapErrSuffix(errUnique, `cannot open "/sysroot/nix":`)},
|
}}, &os.PathError{Op: "open", Path: "/sysroot/nix", Err: stub.UniqueError(5)}},
|
||||||
|
|
||||||
{"readlink", func(k syscallDispatcher) error {
|
{"readlink", func(k syscallDispatcher) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, [][]kexpect{{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
{"evalSymlinks", expectArgs{"/sysroot/nix"}, "/sysroot/nix", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||||
{"open", expectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil},
|
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||||
{"readlink", expectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/nix", errUnique},
|
call("readlink", stub.ExpectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/nix", stub.UniqueError(4)),
|
||||||
}}, wrapErrSelf(errUnique)},
|
}}, stub.UniqueError(4)},
|
||||||
|
|
||||||
{"close", func(k syscallDispatcher) error {
|
{"close", func(k syscallDispatcher) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, [][]kexpect{{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
{"evalSymlinks", expectArgs{"/sysroot/nix"}, "/sysroot/nix", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||||
{"open", expectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil},
|
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||||
{"readlink", expectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/nix", nil},
|
call("readlink", stub.ExpectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/nix", nil),
|
||||||
{"close", expectArgs{0xdeadbeef}, nil, errUnique},
|
call("close", stub.ExpectArgs{0xdeadbeef}, nil, stub.UniqueError(3)),
|
||||||
}}, wrapErrSuffix(errUnique, `cannot close "/sysroot/nix":`)},
|
}}, &os.PathError{Op: "close", Path: "/sysroot/nix", Err: stub.UniqueError(3)}},
|
||||||
|
|
||||||
{"mountinfo stale", func(k syscallDispatcher) error {
|
{"mountinfo no match", func(k syscallDispatcher) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, [][]kexpect{{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
{"evalSymlinks", expectArgs{"/sysroot/nix"}, "/sysroot/.hakurei", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/.hakurei", nil),
|
||||||
{"verbosef", expectArgs{"target resolves to %q", []any{"/sysroot/.hakurei"}}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"target resolves to %q", []any{"/sysroot/.hakurei"}}, nil, nil),
|
||||||
{"open", expectArgs{"/sysroot/.hakurei", 0x280000, uint32(0)}, 0xdeadbeef, nil},
|
call("open", stub.ExpectArgs{"/sysroot/.hakurei", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||||
{"readlink", expectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/.hakurei", nil},
|
call("readlink", stub.ExpectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/.hakurei", nil),
|
||||||
{"close", expectArgs{0xdeadbeef}, nil, nil},
|
call("close", stub.ExpectArgs{0xdeadbeef}, nil, nil),
|
||||||
{"openNew", expectArgs{"/host/proc/self/mountinfo"}, newConstFile(sampleMountinfoNix), nil},
|
call("openNew", stub.ExpectArgs{"/host/proc/self/mountinfo"}, newConstFile(sampleMountinfoNix), nil),
|
||||||
}}, msg.WrapErr(syscall.ESTALE, `mount point "/sysroot/.hakurei" never appeared in mountinfo`)},
|
}}, &vfs.DecoderError{Op: "unfold", Line: -1, Err: vfs.UnfoldTargetError("/sysroot/.hakurei")}},
|
||||||
|
|
||||||
{"mountinfo", func(k syscallDispatcher) error {
|
{"mountinfo", func(k syscallDispatcher) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, [][]kexpect{{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
{"evalSymlinks", expectArgs{"/sysroot/nix"}, "/sysroot/nix", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||||
{"open", expectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil},
|
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||||
{"readlink", expectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/nix", nil},
|
call("readlink", stub.ExpectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/nix", nil),
|
||||||
{"close", expectArgs{0xdeadbeef}, nil, nil},
|
call("close", stub.ExpectArgs{0xdeadbeef}, nil, nil),
|
||||||
{"openNew", expectArgs{"/host/proc/self/mountinfo"}, newConstFile("\x00"), nil},
|
call("openNew", stub.ExpectArgs{"/host/proc/self/mountinfo"}, newConstFile("\x00"), nil),
|
||||||
}}, wrapErrSuffix(vfs.ErrMountInfoFields, `cannot parse mountinfo:`)},
|
}}, &vfs.DecoderError{Op: "parse", Line: 0, Err: vfs.ErrMountInfoFields}},
|
||||||
|
|
||||||
{"mount", func(k syscallDispatcher) error {
|
{"mount", func(k syscallDispatcher) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, [][]kexpect{{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
{"evalSymlinks", expectArgs{"/sysroot/nix"}, "/sysroot/nix", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||||
{"open", expectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil},
|
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||||
{"readlink", expectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/nix", nil},
|
call("readlink", stub.ExpectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/nix", nil),
|
||||||
{"close", expectArgs{0xdeadbeef}, nil, nil},
|
call("close", stub.ExpectArgs{0xdeadbeef}, nil, nil),
|
||||||
{"openNew", expectArgs{"/host/proc/self/mountinfo"}, newConstFile(sampleMountinfoNix), nil},
|
call("openNew", stub.ExpectArgs{"/host/proc/self/mountinfo"}, newConstFile(sampleMountinfoNix), nil),
|
||||||
{"mount", expectArgs{"none", "/sysroot/nix", "", uintptr(0x209027), ""}, nil, errUnique},
|
call("mount", stub.ExpectArgs{"none", "/sysroot/nix", "", uintptr(0x209027), ""}, nil, stub.UniqueError(2)),
|
||||||
}}, wrapErrSuffix(errUnique, `cannot remount "/sysroot/nix":`)},
|
}}, stub.UniqueError(2)},
|
||||||
|
|
||||||
{"mount propagate", func(k syscallDispatcher) error {
|
{"mount propagate", func(k syscallDispatcher) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, [][]kexpect{{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
{"evalSymlinks", expectArgs{"/sysroot/nix"}, "/sysroot/nix", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||||
{"open", expectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil},
|
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||||
{"readlink", expectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/nix", nil},
|
call("readlink", stub.ExpectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/nix", nil),
|
||||||
{"close", expectArgs{0xdeadbeef}, nil, nil},
|
call("close", stub.ExpectArgs{0xdeadbeef}, nil, nil),
|
||||||
{"openNew", expectArgs{"/host/proc/self/mountinfo"}, newConstFile(sampleMountinfoNix), nil},
|
call("openNew", stub.ExpectArgs{"/host/proc/self/mountinfo"}, newConstFile(sampleMountinfoNix), nil),
|
||||||
{"mount", expectArgs{"none", "/sysroot/nix", "", uintptr(0x209027), ""}, nil, nil},
|
call("mount", stub.ExpectArgs{"none", "/sysroot/nix", "", uintptr(0x209027), ""}, nil, nil),
|
||||||
{"mount", expectArgs{"none", "/sysroot/nix/.ro-store", "", uintptr(0x209027), ""}, nil, errUnique},
|
call("mount", stub.ExpectArgs{"none", "/sysroot/nix/.ro-store", "", uintptr(0x209027), ""}, nil, stub.UniqueError(1)),
|
||||||
}}, wrapErrSuffix(errUnique, `cannot propagate flags to "/sysroot/nix/.ro-store":`)},
|
}}, stub.UniqueError(1)},
|
||||||
|
|
||||||
{"success toplevel", func(k syscallDispatcher) error {
|
{"success toplevel", func(k syscallDispatcher) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/bin", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount("/sysroot/bin", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, [][]kexpect{{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
{"evalSymlinks", expectArgs{"/sysroot/bin"}, "/sysroot/bin", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/bin"}, "/sysroot/bin", nil),
|
||||||
{"open", expectArgs{"/sysroot/bin", 0x280000, uint32(0)}, 0xbabe, nil},
|
call("open", stub.ExpectArgs{"/sysroot/bin", 0x280000, uint32(0)}, 0xbabe, nil),
|
||||||
{"readlink", expectArgs{"/host/proc/self/fd/47806"}, "/sysroot/bin", nil},
|
call("readlink", stub.ExpectArgs{"/host/proc/self/fd/47806"}, "/sysroot/bin", nil),
|
||||||
{"close", expectArgs{0xbabe}, nil, nil},
|
call("close", stub.ExpectArgs{0xbabe}, nil, nil),
|
||||||
{"openNew", expectArgs{"/host/proc/self/mountinfo"}, newConstFile(sampleMountinfoNix), nil},
|
call("openNew", stub.ExpectArgs{"/host/proc/self/mountinfo"}, newConstFile(sampleMountinfoNix), nil),
|
||||||
{"mount", 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 syscallDispatcher) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, [][]kexpect{{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
{"evalSymlinks", expectArgs{"/sysroot/nix"}, "/sysroot/nix", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||||
{"open", expectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil},
|
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||||
{"readlink", expectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/nix", nil},
|
call("readlink", stub.ExpectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/nix", nil),
|
||||||
{"close", expectArgs{0xdeadbeef}, nil, nil},
|
call("close", stub.ExpectArgs{0xdeadbeef}, nil, nil),
|
||||||
{"openNew", expectArgs{"/host/proc/self/mountinfo"}, newConstFile(sampleMountinfoNix), nil},
|
call("openNew", stub.ExpectArgs{"/host/proc/self/mountinfo"}, newConstFile(sampleMountinfoNix), nil),
|
||||||
{"mount", expectArgs{"none", "/sysroot/nix", "", uintptr(0x209027), ""}, nil, nil},
|
call("mount", stub.ExpectArgs{"none", "/sysroot/nix", "", uintptr(0x209027), ""}, nil, nil),
|
||||||
{"mount", expectArgs{"none", "/sysroot/nix/.ro-store", "", uintptr(0x209027), ""}, nil, syscall.EACCES},
|
call("mount", stub.ExpectArgs{"none", "/sysroot/nix/.ro-store", "", uintptr(0x209027), ""}, nil, syscall.EACCES),
|
||||||
{"mount", 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 syscallDispatcher) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, [][]kexpect{{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
{"evalSymlinks", expectArgs{"/sysroot/nix"}, "/sysroot/nix", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||||
{"open", expectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil},
|
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||||
{"readlink", expectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/nix", nil},
|
call("readlink", stub.ExpectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/nix", nil),
|
||||||
{"close", expectArgs{0xdeadbeef}, nil, nil},
|
call("close", stub.ExpectArgs{0xdeadbeef}, nil, nil),
|
||||||
{"openNew", expectArgs{"/host/proc/self/mountinfo"}, newConstFile(sampleMountinfoNix), nil},
|
call("openNew", stub.ExpectArgs{"/host/proc/self/mountinfo"}, newConstFile(sampleMountinfoNix), nil),
|
||||||
{"mount", 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 syscallDispatcher) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, [][]kexpect{{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
{"evalSymlinks", expectArgs{"/sysroot/nix"}, "/sysroot/nix", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||||
{"open", expectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil},
|
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||||
{"readlink", expectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/nix", nil},
|
call("readlink", stub.ExpectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/nix", nil),
|
||||||
{"close", expectArgs{0xdeadbeef}, nil, nil},
|
call("close", stub.ExpectArgs{0xdeadbeef}, nil, nil),
|
||||||
{"openNew", expectArgs{"/host/proc/self/mountinfo"}, newConstFile(sampleMountinfoNix), nil},
|
call("openNew", stub.ExpectArgs{"/host/proc/self/mountinfo"}, newConstFile(sampleMountinfoNix), nil),
|
||||||
{"mount", expectArgs{"none", "/sysroot/nix", "", uintptr(0x209027), ""}, nil, nil},
|
call("mount", stub.ExpectArgs{"none", "/sysroot/nix", "", uintptr(0x209027), ""}, nil, nil),
|
||||||
{"mount", expectArgs{"none", "/sysroot/nix/.ro-store", "", uintptr(0x209027), ""}, nil, nil},
|
call("mount", stub.ExpectArgs{"none", "/sysroot/nix/.ro-store", "", uintptr(0x209027), ""}, nil, nil),
|
||||||
{"mount", 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 syscallDispatcher) error {
|
||||||
return newProcPaths(k, hostPath).remount("/sysroot/.nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
return newProcPaths(k, hostPath).remount("/sysroot/.nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||||
}, [][]kexpect{{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
{"evalSymlinks", expectArgs{"/sysroot/.nix"}, "/sysroot/NIX", nil},
|
call("evalSymlinks", stub.ExpectArgs{"/sysroot/.nix"}, "/sysroot/NIX", nil),
|
||||||
{"verbosef", expectArgs{"target resolves to %q", []any{"/sysroot/NIX"}}, nil, nil},
|
call("verbosef", stub.ExpectArgs{"target resolves to %q", []any{"/sysroot/NIX"}}, nil, nil),
|
||||||
{"open", expectArgs{"/sysroot/NIX", 0x280000, uint32(0)}, 0xdeadbeef, nil},
|
call("open", stub.ExpectArgs{"/sysroot/NIX", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||||
{"readlink", expectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/nix", nil},
|
call("readlink", stub.ExpectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/nix", nil),
|
||||||
{"close", expectArgs{0xdeadbeef}, nil, nil},
|
call("close", stub.ExpectArgs{0xdeadbeef}, nil, nil),
|
||||||
{"openNew", expectArgs{"/host/proc/self/mountinfo"}, newConstFile(sampleMountinfoNix), nil},
|
call("openNew", stub.ExpectArgs{"/host/proc/self/mountinfo"}, newConstFile(sampleMountinfoNix), nil),
|
||||||
{"mount", expectArgs{"none", "/sysroot/nix", "", uintptr(0x209027), ""}, nil, nil},
|
call("mount", stub.ExpectArgs{"none", "/sysroot/nix", "", uintptr(0x209027), ""}, nil, nil),
|
||||||
{"mount", expectArgs{"none", "/sysroot/nix/.ro-store", "", uintptr(0x209027), ""}, nil, nil},
|
call("mount", stub.ExpectArgs{"none", "/sysroot/nix/.ro-store", "", uintptr(0x209027), ""}, nil, nil),
|
||||||
{"mount", expectArgs{"none", "/sysroot/nix/store", "", uintptr(0x209027), ""}, nil, nil},
|
call("mount", stub.ExpectArgs{"none", "/sysroot/nix/store", "", uintptr(0x209027), ""}, nil, nil),
|
||||||
}}, nil},
|
}}, nil},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -221,18 +222,18 @@ func TestRemountWithFlags(t *testing.T) {
|
|||||||
checkSimple(t, "remountWithFlags", []simpleTestCase{
|
checkSimple(t, "remountWithFlags", []simpleTestCase{
|
||||||
{"noop unmatched", func(k syscallDispatcher) error {
|
{"noop unmatched", func(k syscallDispatcher) error {
|
||||||
return remountWithFlags(k, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime,cat"}}, 0)
|
return remountWithFlags(k, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime,cat"}}, 0)
|
||||||
}, [][]kexpect{{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
{"verbosef", 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 syscallDispatcher) error {
|
||||||
return remountWithFlags(k, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime"}}, 0)
|
return remountWithFlags(k, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime"}}, 0)
|
||||||
}, nil, nil},
|
}, stub.Expect{}, nil},
|
||||||
|
|
||||||
{"success", func(k syscallDispatcher) error {
|
{"success", func(k syscallDispatcher) error {
|
||||||
return remountWithFlags(k, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime"}}, syscall.MS_RDONLY)
|
return remountWithFlags(k, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime"}}, syscall.MS_RDONLY)
|
||||||
}, [][]kexpect{{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
{"mount", expectArgs{"none", "", "", uintptr(0x209021), ""}, nil, nil},
|
call("mount", stub.ExpectArgs{"none", "", "", uintptr(0x209021), ""}, nil, nil),
|
||||||
}}, nil},
|
}}, nil},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -241,22 +242,22 @@ func TestMountTmpfs(t *testing.T) {
|
|||||||
checkSimple(t, "mountTmpfs", []simpleTestCase{
|
checkSimple(t, "mountTmpfs", []simpleTestCase{
|
||||||
{"mkdirAll", func(k syscallDispatcher) error {
|
{"mkdirAll", func(k syscallDispatcher) 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)
|
||||||
}, [][]kexpect{{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/sysroot/run/user/1000", os.FileMode(0700)}, nil, errUnique},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/run/user/1000", os.FileMode(0700)}, nil, stub.UniqueError(0)),
|
||||||
}}, wrapErrSelf(errUnique)},
|
}}, stub.UniqueError(0)},
|
||||||
|
|
||||||
{"success no size", func(k syscallDispatcher) error {
|
{"success no size", func(k syscallDispatcher) error {
|
||||||
return mountTmpfs(k, "ephemeral", "/sysroot/run/user/1000", 0, 0, 0710)
|
return mountTmpfs(k, "ephemeral", "/sysroot/run/user/1000", 0, 0, 0710)
|
||||||
}, [][]kexpect{{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/sysroot/run/user/1000", os.FileMode(0750)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/run/user/1000", os.FileMode(0750)}, nil, nil),
|
||||||
{"mount", 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 syscallDispatcher) 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)
|
||||||
}, [][]kexpect{{
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
{"mkdirAll", expectArgs{"/sysroot/run/user/1000", os.FileMode(0700)}, nil, nil},
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/run/user/1000", os.FileMode(0700)}, nil, nil),
|
||||||
{"mount", expectArgs{"ephemeral", "/sysroot/run/user/1000", "tmpfs", uintptr(0), "mode=0700,size=1024"}, nil, nil},
|
call("mount", stub.ExpectArgs{"ephemeral", "/sysroot/run/user/1000", "tmpfs", uintptr(0), "mode=0700,size=1024"}, nil, nil),
|
||||||
}}, nil},
|
}}, nil},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,24 +2,34 @@ package container
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// MessageError is an error with a user-facing message.
|
||||||
|
type MessageError interface {
|
||||||
|
// Message returns a user-facing error message.
|
||||||
|
Message() string
|
||||||
|
|
||||||
|
error
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetErrorMessage returns whether an error implements [MessageError], and the message if it does.
|
||||||
|
func GetErrorMessage(err error) (string, bool) {
|
||||||
|
var e MessageError
|
||||||
|
if !errors.As(err, &e) || e == nil {
|
||||||
|
return zeroString, false
|
||||||
|
}
|
||||||
|
return e.Message(), true
|
||||||
|
}
|
||||||
|
|
||||||
type Msg interface {
|
type Msg interface {
|
||||||
IsVerbose() bool
|
IsVerbose() bool
|
||||||
Verbose(v ...any)
|
Verbose(v ...any)
|
||||||
Verbosef(format string, v ...any)
|
Verbosef(format string, v ...any)
|
||||||
WrapErr(err error, a ...any) error
|
|
||||||
PrintBaseErr(err error, fallback string)
|
|
||||||
|
|
||||||
Suspend()
|
Suspend()
|
||||||
Resume() bool
|
Resume() bool
|
||||||
|
|
||||||
BeforeExit()
|
BeforeExit()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,32 +47,6 @@ func (msg *DefaultMsg) Verbosef(format string, v ...any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkedWrappedErr implements error with strict checks for wrapped values.
|
|
||||||
type checkedWrappedErr struct {
|
|
||||||
err error
|
|
||||||
a []any
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *checkedWrappedErr) Error() string { return fmt.Sprintf("%v, a = %s", c.err, c.a) }
|
|
||||||
func (c *checkedWrappedErr) Is(err error) bool {
|
|
||||||
var concreteErr *checkedWrappedErr
|
|
||||||
if !errors.As(err, &concreteErr) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return reflect.DeepEqual(c, concreteErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (msg *DefaultMsg) WrapErr(err error, a ...any) error {
|
|
||||||
// provide a mostly bulletproof path to bypass this behaviour in tests
|
|
||||||
if testing.Testing() && os.Getenv("GOPATH") != Nonexistent {
|
|
||||||
return &checkedWrappedErr{err, a}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println(a...)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
func (msg *DefaultMsg) PrintBaseErr(err error, fallback string) { log.Println(fallback, err) }
|
|
||||||
|
|
||||||
func (msg *DefaultMsg) Suspend() { msg.inactive.Store(true) }
|
func (msg *DefaultMsg) Suspend() { msg.inactive.Store(true) }
|
||||||
func (msg *DefaultMsg) Resume() bool { return msg.inactive.CompareAndSwap(true, false) }
|
func (msg *DefaultMsg) Resume() bool { return msg.inactive.CompareAndSwap(true, false) }
|
||||||
func (msg *DefaultMsg) BeforeExit() {}
|
func (msg *DefaultMsg) BeforeExit() {}
|
||||||
|
|||||||
@@ -9,13 +9,36 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/internal/hlog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDefaultMsg(t *testing.T) {
|
func TestMessageError(t *testing.T) {
|
||||||
// bypass WrapErr testing behaviour
|
testCases := []struct {
|
||||||
t.Setenv("GOPATH", container.Nonexistent)
|
name string
|
||||||
|
err error
|
||||||
|
want string
|
||||||
|
wantOk bool
|
||||||
|
}{
|
||||||
|
{"nil", nil, "", false},
|
||||||
|
{"new", errors.New(":3"), "", false},
|
||||||
|
{"start", &container.StartError{
|
||||||
|
Step: "meow",
|
||||||
|
Err: syscall.ENOTRECOVERABLE,
|
||||||
|
}, "cannot meow: state not recoverable", true},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
got, ok := container.GetErrorMessage(tc.err)
|
||||||
|
if got != tc.want {
|
||||||
|
t.Errorf("GetErrorMessage: %q, want %q", got, tc.want)
|
||||||
|
}
|
||||||
|
if ok != tc.wantOk {
|
||||||
|
t.Errorf("GetErrorMessage: ok = %v, want %v", ok, tc.wantOk)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultMsg(t *testing.T) {
|
||||||
{
|
{
|
||||||
w := log.Writer()
|
w := log.Writer()
|
||||||
f := log.Flags()
|
f := log.Flags()
|
||||||
@@ -48,21 +71,6 @@ func TestDefaultMsg(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("wrapErr", func(t *testing.T) {
|
|
||||||
buf := new(strings.Builder)
|
|
||||||
log.SetOutput(buf)
|
|
||||||
log.SetFlags(0)
|
|
||||||
if err := msg.WrapErr(syscall.EBADE, "\x00", "\x00"); err != syscall.EBADE {
|
|
||||||
t.Errorf("WrapErr: %v", err)
|
|
||||||
}
|
|
||||||
msg.PrintBaseErr(syscall.ENOTRECOVERABLE, "cannot cuddle cat:")
|
|
||||||
|
|
||||||
want := "\x00 \x00\ncannot cuddle cat: state not recoverable\n"
|
|
||||||
if buf.String() != want {
|
|
||||||
t.Errorf("WrapErr: %q, want %q", buf.String(), want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("inactive", func(t *testing.T) {
|
t.Run("inactive", func(t *testing.T) {
|
||||||
{
|
{
|
||||||
inactive := msg.Resume()
|
inactive := msg.Resume()
|
||||||
@@ -83,25 +91,6 @@ func TestDefaultMsg(t *testing.T) {
|
|||||||
|
|
||||||
// the function is a noop
|
// the function is a noop
|
||||||
t.Run("beforeExit", func(t *testing.T) { msg.BeforeExit() })
|
t.Run("beforeExit", func(t *testing.T) { msg.BeforeExit() })
|
||||||
|
|
||||||
t.Run("checkedWrappedErr", func(t *testing.T) {
|
|
||||||
// temporarily re-enable testing behaviour
|
|
||||||
t.Setenv("GOPATH", "")
|
|
||||||
wrappedErr := msg.WrapErr(syscall.ENOTRECOVERABLE, "cannot cuddle cat:", syscall.ENOTRECOVERABLE)
|
|
||||||
|
|
||||||
t.Run("string", func(t *testing.T) {
|
|
||||||
want := "state not recoverable, a = [cannot cuddle cat: state not recoverable]"
|
|
||||||
if got := wrappedErr.Error(); got != want {
|
|
||||||
t.Errorf("Error: %q, want %q", got, want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("bad concrete type", func(t *testing.T) {
|
|
||||||
if errors.Is(wrappedErr, syscall.ENOTRECOVERABLE) {
|
|
||||||
t.Error("incorrect type assertion")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type panicWriter struct{}
|
type panicWriter struct{}
|
||||||
@@ -139,9 +128,6 @@ func (out *testOutput) Verbosef(format string, v ...any) {
|
|||||||
out.t.Logf(format, v...)
|
out.t.Logf(format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (out *testOutput) WrapErr(err error, a ...any) error { return hlog.WrapErr(err, a...) }
|
|
||||||
func (out *testOutput) PrintBaseErr(err error, fallback string) { hlog.PrintBaseError(err, fallback) }
|
|
||||||
|
|
||||||
func (out *testOutput) Suspend() {
|
func (out *testOutput) Suspend() {
|
||||||
if out.suspended.CompareAndSwap(false, true) {
|
if out.suspended.CompareAndSwap(false, true) {
|
||||||
out.Verbose("suspend called")
|
out.Verbose("suspend called")
|
||||||
|
|||||||
@@ -10,17 +10,3 @@ func SetOutput(v Msg) {
|
|||||||
msg = v
|
msg = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func wrapErrSuffix(err error, a ...any) error {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return msg.WrapErr(err, append(a, err)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapErrSelf(err error) error {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return msg.WrapErr(err, err.Error())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
"syscall"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,80 +29,13 @@ func TestGetSetOutput(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWrapErr(t *testing.T) {
|
|
||||||
{
|
|
||||||
out := GetOutput()
|
|
||||||
t.Cleanup(func() { SetOutput(out) })
|
|
||||||
}
|
|
||||||
|
|
||||||
var wrapFp *func(error, ...any) error
|
|
||||||
s := new(stubOutput)
|
|
||||||
SetOutput(s)
|
|
||||||
wrapFp = &s.wrapF
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
f func(t *testing.T)
|
|
||||||
wantErr error
|
|
||||||
wantA []any
|
|
||||||
}{
|
|
||||||
{"suffix nil", func(t *testing.T) {
|
|
||||||
if err := wrapErrSuffix(nil, "\x00"); err != nil {
|
|
||||||
t.Errorf("wrapErrSuffix: %v", err)
|
|
||||||
}
|
|
||||||
}, nil, nil},
|
|
||||||
{"suffix val", func(t *testing.T) {
|
|
||||||
if err := wrapErrSuffix(syscall.ENOTRECOVERABLE, "\x00\x00"); err != syscall.ENOTRECOVERABLE {
|
|
||||||
t.Errorf("wrapErrSuffix: %v", err)
|
|
||||||
}
|
|
||||||
}, syscall.ENOTRECOVERABLE, []any{"\x00\x00", syscall.ENOTRECOVERABLE}},
|
|
||||||
{"self nil", func(t *testing.T) {
|
|
||||||
if err := wrapErrSelf(nil); err != nil {
|
|
||||||
t.Errorf("wrapErrSelf: %v", err)
|
|
||||||
}
|
|
||||||
}, nil, nil},
|
|
||||||
{"self val", func(t *testing.T) {
|
|
||||||
if err := wrapErrSelf(syscall.ENOTRECOVERABLE); err != syscall.ENOTRECOVERABLE {
|
|
||||||
t.Errorf("wrapErrSelf: %v", err)
|
|
||||||
}
|
|
||||||
}, syscall.ENOTRECOVERABLE, []any{"state not recoverable"}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
var (
|
|
||||||
gotErr error
|
|
||||||
gotA []any
|
|
||||||
)
|
|
||||||
*wrapFp = func(err error, a ...any) error { gotErr = err; gotA = a; return err }
|
|
||||||
|
|
||||||
tc.f(t)
|
|
||||||
if gotErr != tc.wantErr {
|
|
||||||
t.Errorf("WrapErr: err = %v, want %v", gotErr, tc.wantErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(gotA, tc.wantA) {
|
|
||||||
t.Errorf("WrapErr: a = %v, want %v", gotA, tc.wantA)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type stubOutput struct {
|
type stubOutput struct {
|
||||||
wrapF func(error, ...any) error
|
wrapF func(error, ...any) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*stubOutput) IsVerbose() bool { panic("unreachable") }
|
func (*stubOutput) IsVerbose() bool { panic("unreachable") }
|
||||||
func (*stubOutput) Verbose(...any) { panic("unreachable") }
|
func (*stubOutput) Verbose(...any) { panic("unreachable") }
|
||||||
func (*stubOutput) Verbosef(string, ...any) { panic("unreachable") }
|
func (*stubOutput) Verbosef(string, ...any) { panic("unreachable") }
|
||||||
func (*stubOutput) PrintBaseErr(error, string) { panic("unreachable") }
|
func (*stubOutput) Suspend() { panic("unreachable") }
|
||||||
func (*stubOutput) Suspend() { panic("unreachable") }
|
func (*stubOutput) Resume() bool { panic("unreachable") }
|
||||||
func (*stubOutput) Resume() bool { panic("unreachable") }
|
func (*stubOutput) BeforeExit() { panic("unreachable") }
|
||||||
func (*stubOutput) BeforeExit() { panic("unreachable") }
|
|
||||||
|
|
||||||
func (s *stubOutput) WrapErr(err error, v ...any) error {
|
|
||||||
if s.wrapF == nil {
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
return s.wrapF(err, v...)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,11 +8,6 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
ErrNotSet = errors.New("environment variable not set")
|
|
||||||
ErrFdFormat = errors.New("bad file descriptor representation")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Setup appends the read end of a pipe for setup params transmission and returns its fd.
|
// Setup appends the read end of a pipe for setup params transmission and returns its fd.
|
||||||
func Setup(extraFiles *[]*os.File) (int, *gob.Encoder, error) {
|
func Setup(extraFiles *[]*os.File) (int, *gob.Encoder, error) {
|
||||||
if r, w, err := os.Pipe(); err != nil {
|
if r, w, err := os.Pipe(); err != nil {
|
||||||
@@ -24,19 +19,23 @@ func Setup(extraFiles *[]*os.File) (int, *gob.Encoder, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrReceiveEnv = errors.New("environment variable not set")
|
||||||
|
)
|
||||||
|
|
||||||
// Receive retrieves setup fd from the environment and receives params.
|
// Receive retrieves setup fd from the environment and receives params.
|
||||||
func Receive(key string, e any, fdp *uintptr) (func() error, error) {
|
func Receive(key string, e any, fdp *uintptr) (func() error, error) {
|
||||||
var setup *os.File
|
var setup *os.File
|
||||||
|
|
||||||
if s, ok := os.LookupEnv(key); !ok {
|
if s, ok := os.LookupEnv(key); !ok {
|
||||||
return nil, ErrNotSet
|
return nil, ErrReceiveEnv
|
||||||
} else {
|
} else {
|
||||||
if fd, err := strconv.Atoi(s); err != nil {
|
if fd, err := strconv.Atoi(s); err != nil {
|
||||||
return nil, ErrFdFormat
|
return nil, errors.Unwrap(err)
|
||||||
} else {
|
} else {
|
||||||
setup = os.NewFile(uintptr(fd), "setup")
|
setup = os.NewFile(uintptr(fd), "setup")
|
||||||
if setup == nil {
|
if setup == nil {
|
||||||
return nil, syscall.EBADF
|
return nil, syscall.EDOM
|
||||||
}
|
}
|
||||||
if fdp != nil {
|
if fdp != nil {
|
||||||
*fdp = setup.Fd()
|
*fdp = setup.Fd()
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ func TestSetupReceive(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := container.Receive(key, nil, nil); !errors.Is(err, container.ErrNotSet) {
|
if _, err := container.Receive(key, nil, nil); !errors.Is(err, container.ErrReceiveEnv) {
|
||||||
t.Errorf("Receive: error = %v, want %v", err, container.ErrNotSet)
|
t.Errorf("Receive: error = %v, want %v", err, container.ErrReceiveEnv)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -38,8 +38,8 @@ func TestSetupReceive(t *testing.T) {
|
|||||||
const key = "TEST_ENV_FORMAT"
|
const key = "TEST_ENV_FORMAT"
|
||||||
t.Setenv(key, "")
|
t.Setenv(key, "")
|
||||||
|
|
||||||
if _, err := container.Receive(key, nil, nil); !errors.Is(err, container.ErrFdFormat) {
|
if _, err := container.Receive(key, nil, nil); !errors.Is(err, strconv.ErrSyntax) {
|
||||||
t.Errorf("Receive: error = %v, want %v", err, container.ErrFdFormat)
|
t.Errorf("Receive: error = %v, want %v", err, strconv.ErrSyntax)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -47,8 +47,8 @@ func TestSetupReceive(t *testing.T) {
|
|||||||
const key = "TEST_ENV_RANGE"
|
const key = "TEST_ENV_RANGE"
|
||||||
t.Setenv(key, "-1")
|
t.Setenv(key, "-1")
|
||||||
|
|
||||||
if _, err := container.Receive(key, nil, nil); !errors.Is(err, syscall.EBADF) {
|
if _, err := container.Receive(key, nil, nil); !errors.Is(err, syscall.EDOM) {
|
||||||
t.Errorf("Receive: error = %v, want %v", err, syscall.EBADF)
|
t.Errorf("Receive: error = %v, want %v", err, syscall.EDOM)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package container
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@@ -103,30 +102,29 @@ func toHost(name string) string {
|
|||||||
|
|
||||||
func createFile(name string, perm, pperm os.FileMode, content []byte) error {
|
func createFile(name string, perm, pperm os.FileMode, content []byte) error {
|
||||||
if err := os.MkdirAll(path.Dir(name), pperm); err != nil {
|
if err := os.MkdirAll(path.Dir(name), pperm); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
}
|
}
|
||||||
f, err := os.OpenFile(name, syscall.O_CREAT|syscall.O_EXCL|syscall.O_WRONLY, perm)
|
f, err := os.OpenFile(name, syscall.O_CREAT|syscall.O_EXCL|syscall.O_WRONLY, perm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
}
|
}
|
||||||
if content != nil {
|
if content != nil {
|
||||||
_, err = f.Write(content)
|
_, err = f.Write(content)
|
||||||
}
|
}
|
||||||
return errors.Join(f.Close(), wrapErrSelf(err))
|
return errors.Join(f.Close(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureFile(name string, perm, pperm os.FileMode) error {
|
func ensureFile(name string, perm, pperm os.FileMode) error {
|
||||||
fi, err := os.Stat(name)
|
fi, err := os.Stat(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
}
|
}
|
||||||
return createFile(name, perm, pperm, nil)
|
return createFile(name, perm, pperm, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if mode := fi.Mode(); mode&fs.ModeDir != 0 || mode&fs.ModeSymlink != 0 {
|
if mode := fi.Mode(); mode&fs.ModeDir != 0 || mode&fs.ModeSymlink != 0 {
|
||||||
err = msg.WrapErr(syscall.EISDIR,
|
err = &os.PathError{Op: "ensure", Path: name, Err: syscall.EISDIR}
|
||||||
fmt.Sprintf("path %q is a directory", name))
|
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -147,15 +145,14 @@ func (p *procPaths) stdout() string { return p.self + "/fd/1" }
|
|||||||
func (p *procPaths) fd(fd int) string { return p.self + "/fd/" + strconv.Itoa(fd) }
|
func (p *procPaths) fd(fd int) string { return p.self + "/fd/" + strconv.Itoa(fd) }
|
||||||
func (p *procPaths) mountinfo(f func(d *vfs.MountInfoDecoder) error) error {
|
func (p *procPaths) mountinfo(f func(d *vfs.MountInfoDecoder) error) error {
|
||||||
if r, err := p.k.openNew(p.self + "/mountinfo"); err != nil {
|
if r, err := p.k.openNew(p.self + "/mountinfo"); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
} else {
|
} else {
|
||||||
d := vfs.NewMountInfoDecoder(r)
|
d := vfs.NewMountInfoDecoder(r)
|
||||||
err0 := f(d)
|
err0 := f(d)
|
||||||
if err = r.Close(); err != nil {
|
if err = r.Close(); err != nil {
|
||||||
return wrapErrSelf(err)
|
return err
|
||||||
} else if err = d.Err(); err != nil {
|
} else if err = d.Err(); err != nil {
|
||||||
return wrapErrSuffix(err,
|
return err
|
||||||
"cannot parse mountinfo:")
|
|
||||||
}
|
}
|
||||||
return err0
|
return err0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
@@ -56,20 +54,27 @@ func InternalToHostOvlEscape(s string) string { return EscapeOverlayDataSegment(
|
|||||||
|
|
||||||
func TestCreateFile(t *testing.T) {
|
func TestCreateFile(t *testing.T) {
|
||||||
t.Run("nonexistent", func(t *testing.T) {
|
t.Run("nonexistent", func(t *testing.T) {
|
||||||
if err := createFile(path.Join(Nonexistent, ":3"), 0644, 0755, nil); !errors.Is(err, wrapErrSelf(&os.PathError{
|
t.Run("mkdir", func(t *testing.T) {
|
||||||
Op: "mkdir",
|
wantErr := &os.PathError{
|
||||||
Path: "/proc/nonexistent",
|
Op: "mkdir",
|
||||||
Err: syscall.ENOENT,
|
Path: "/proc/nonexistent",
|
||||||
})) {
|
Err: syscall.ENOENT,
|
||||||
t.Errorf("createFile: error = %v", err)
|
}
|
||||||
}
|
if err := createFile(path.Join(Nonexistent, ":3"), 0644, 0755, nil); !reflect.DeepEqual(err, wantErr) {
|
||||||
if err := createFile(path.Join(Nonexistent), 0644, 0755, nil); !errors.Is(err, wrapErrSelf(&os.PathError{
|
t.Errorf("createFile: error = %#v, want %#v", err, wantErr)
|
||||||
Op: "open",
|
}
|
||||||
Path: "/proc/nonexistent",
|
})
|
||||||
Err: syscall.ENOENT,
|
|
||||||
})) {
|
t.Run("open", func(t *testing.T) {
|
||||||
t.Errorf("createFile: error = %v", err)
|
wantErr := &os.PathError{
|
||||||
}
|
Op: "open",
|
||||||
|
Path: "/proc/nonexistent",
|
||||||
|
Err: syscall.ENOENT,
|
||||||
|
}
|
||||||
|
if err := createFile(path.Join(Nonexistent), 0644, 0755, nil); !reflect.DeepEqual(err, wantErr) {
|
||||||
|
t.Errorf("createFile: error = %#v, want %#v", err, wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("touch", func(t *testing.T) {
|
t.Run("touch", func(t *testing.T) {
|
||||||
@@ -120,13 +125,13 @@ func TestEnsureFile(t *testing.T) {
|
|||||||
t.Fatalf("Chmod: error = %v", err)
|
t.Fatalf("Chmod: error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantErr := wrapErrSelf(&os.PathError{
|
wantErr := &os.PathError{
|
||||||
Op: "stat",
|
Op: "stat",
|
||||||
Path: pathname,
|
Path: pathname,
|
||||||
Err: syscall.EACCES,
|
Err: syscall.EACCES,
|
||||||
})
|
}
|
||||||
if err := ensureFile(pathname, 0644, 0755); !errors.Is(err, wantErr) {
|
if err := ensureFile(pathname, 0644, 0755); !reflect.DeepEqual(err, wantErr) {
|
||||||
t.Errorf("ensureFile: error = %v, want %v", err, wantErr)
|
t.Errorf("ensureFile: error = %#v, want %#v", err, wantErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Chmod(tempDir, 0755); err != nil {
|
if err := os.Chmod(tempDir, 0755); err != nil {
|
||||||
@@ -136,9 +141,9 @@ func TestEnsureFile(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("directory", func(t *testing.T) {
|
t.Run("directory", func(t *testing.T) {
|
||||||
pathname := t.TempDir()
|
pathname := t.TempDir()
|
||||||
wantErr := msg.WrapErr(syscall.EISDIR, fmt.Sprintf("path %q is a directory", pathname))
|
wantErr := &os.PathError{Op: "ensure", Path: pathname, Err: syscall.EISDIR}
|
||||||
if err := ensureFile(pathname, 0644, 0755); !errors.Is(err, wantErr) {
|
if err := ensureFile(pathname, 0644, 0755); !reflect.DeepEqual(err, wantErr) {
|
||||||
t.Errorf("ensureFile: error = %v, want %v", err, wantErr)
|
t.Errorf("ensureFile: error = %#v, want %#v", err, wantErr)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -177,12 +182,12 @@ func TestProcPaths(t *testing.T) {
|
|||||||
t.Run("mountinfo", func(t *testing.T) {
|
t.Run("mountinfo", func(t *testing.T) {
|
||||||
t.Run("nonexistent", func(t *testing.T) {
|
t.Run("nonexistent", func(t *testing.T) {
|
||||||
nonexistentProc := newProcPaths(direct{}, t.TempDir())
|
nonexistentProc := newProcPaths(direct{}, t.TempDir())
|
||||||
wantErr := wrapErrSelf(&os.PathError{
|
wantErr := &os.PathError{
|
||||||
Op: "open",
|
Op: "open",
|
||||||
Path: nonexistentProc.self + "/mountinfo",
|
Path: nonexistentProc.self + "/mountinfo",
|
||||||
Err: syscall.ENOENT,
|
Err: syscall.ENOENT,
|
||||||
})
|
}
|
||||||
if err := nonexistentProc.mountinfo(func(*vfs.MountInfoDecoder) error { return syscall.EINVAL }); !errors.Is(err, wantErr) {
|
if err := nonexistentProc.mountinfo(func(*vfs.MountInfoDecoder) error { return syscall.EINVAL }); !reflect.DeepEqual(err, wantErr) {
|
||||||
t.Errorf("mountinfo: error = %v, want %v", err, wantErr)
|
t.Errorf("mountinfo: error = %v, want %v", err, wantErr)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -217,11 +222,11 @@ func TestProcPaths(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("closed", func(t *testing.T) {
|
t.Run("closed", func(t *testing.T) {
|
||||||
p := newProcPaths(direct{}, tempDir)
|
p := newProcPaths(direct{}, tempDir)
|
||||||
wantErr := wrapErrSelf(&os.PathError{
|
wantErr := &os.PathError{
|
||||||
Op: "close",
|
Op: "close",
|
||||||
Path: p.self + "/mountinfo",
|
Path: p.self + "/mountinfo",
|
||||||
Err: os.ErrClosed,
|
Err: os.ErrClosed,
|
||||||
})
|
}
|
||||||
if err := p.mountinfo(func(d *vfs.MountInfoDecoder) error {
|
if err := p.mountinfo(func(d *vfs.MountInfoDecoder) error {
|
||||||
v := reflect.ValueOf(d).Elem().FieldByName("s").Elem().FieldByName("r")
|
v := reflect.ValueOf(d).Elem().FieldByName("s").Elem().FieldByName("r")
|
||||||
v = reflect.NewAt(v.Type(), unsafe.Pointer(v.UnsafeAddr()))
|
v = reflect.NewAt(v.Type(), unsafe.Pointer(v.UnsafeAddr()))
|
||||||
@@ -231,8 +236,8 @@ func TestProcPaths(t *testing.T) {
|
|||||||
} else {
|
} else {
|
||||||
return f.Close()
|
return f.Close()
|
||||||
}
|
}
|
||||||
}); !errors.Is(err, wantErr) {
|
}); !reflect.DeepEqual(err, wantErr) {
|
||||||
t.Errorf("mountinfo: error = %v, want %v", err, wantErr)
|
t.Errorf("mountinfo: error = %#v, want %#v", err, wantErr)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -242,8 +247,8 @@ func TestProcPaths(t *testing.T) {
|
|||||||
t.Fatalf("WriteFile: error = %v", err)
|
t.Fatalf("WriteFile: error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantErr := wrapErrSuffix(vfs.ErrMountInfoFields, "cannot parse mountinfo:")
|
wantErr := &vfs.DecoderError{Op: "parse", Line: 0, Err: vfs.ErrMountInfoFields}
|
||||||
if err := newProcPaths(direct{}, tempDir).mountinfo(func(d *vfs.MountInfoDecoder) error { return d.Decode(new(*vfs.MountInfo)) }); !errors.Is(err, wantErr) {
|
if err := newProcPaths(direct{}, tempDir).mountinfo(func(d *vfs.MountInfoDecoder) error { return d.Decode(new(*vfs.MountInfo)) }); !reflect.DeepEqual(err, wantErr) {
|
||||||
t.Fatalf("mountinfo: error = %v, want %v", err, wantErr)
|
t.Fatalf("mountinfo: error = %v, want %v", err, wantErr)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
37
container/stub/call.go
Normal file
37
container/stub/call.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package stub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExpectArgs is an array primarily for storing expected function arguments.
|
||||||
|
// Its actual use is defined by the implementation.
|
||||||
|
type ExpectArgs = [5]any
|
||||||
|
|
||||||
|
// An Expect stores expected calls of a goroutine.
|
||||||
|
type Expect struct {
|
||||||
|
Calls []Call
|
||||||
|
|
||||||
|
// Tracks are handed out to descendant goroutines in order.
|
||||||
|
Tracks []Expect
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Call holds expected arguments of a function call and its outcome.
|
||||||
|
type Call struct {
|
||||||
|
// Name is the function Name of this call. Must be unique.
|
||||||
|
Name string
|
||||||
|
// Args are the expected arguments of this Call.
|
||||||
|
Args ExpectArgs
|
||||||
|
// Ret is the return value of this Call.
|
||||||
|
Ret any
|
||||||
|
// Err is the returned error of this Call.
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns [Call.Err] if all arguments are true, or [ErrCheck] otherwise.
|
||||||
|
func (k *Call) Error(ok ...bool) error {
|
||||||
|
if !slices.Contains(ok, false) {
|
||||||
|
return k.Err
|
||||||
|
}
|
||||||
|
return ErrCheck
|
||||||
|
}
|
||||||
23
container/stub/call_test.go
Normal file
23
container/stub/call_test.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package stub_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/stub"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCallError(t *testing.T) {
|
||||||
|
t.Run("contains false", func(t *testing.T) {
|
||||||
|
if err := new(stub.Call).Error(true, false, true); !reflect.DeepEqual(err, stub.ErrCheck) {
|
||||||
|
t.Errorf("Error: %#v, want %#v", err, stub.ErrCheck)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("passthrough", func(t *testing.T) {
|
||||||
|
wantErr := stub.UniqueError(0xbabe)
|
||||||
|
if err := (&stub.Call{Err: wantErr}).Error(true); !reflect.DeepEqual(err, wantErr) {
|
||||||
|
t.Errorf("Error: %#v, want %#v", err, wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
25
container/stub/errors.go
Normal file
25
container/stub/errors.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package stub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrCheck = errors.New("one or more arguments did not match")
|
||||||
|
)
|
||||||
|
|
||||||
|
// UniqueError is an error that only equivalates to other [UniqueError] with the same magic value.
|
||||||
|
type UniqueError uintptr
|
||||||
|
|
||||||
|
func (e UniqueError) Error() string {
|
||||||
|
return "unique error " + strconv.Itoa(int(e)) + " injected by the test suite"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e UniqueError) Is(target error) bool {
|
||||||
|
var u UniqueError
|
||||||
|
if !errors.As(target, &u) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return e == u
|
||||||
|
}
|
||||||
35
container/stub/errors_test.go
Normal file
35
container/stub/errors_test.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package stub_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/stub"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUniqueError(t *testing.T) {
|
||||||
|
t.Run("format", func(t *testing.T) {
|
||||||
|
want := "unique error 2989 injected by the test suite"
|
||||||
|
if got := stub.UniqueError(0xbad).Error(); got != want {
|
||||||
|
t.Errorf("Error: %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("is", func(t *testing.T) {
|
||||||
|
t.Run("type", func(t *testing.T) {
|
||||||
|
if errors.Is(stub.UniqueError(0), syscall.ENOTRECOVERABLE) {
|
||||||
|
t.Error("Is: unexpected true")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("val", func(t *testing.T) {
|
||||||
|
if errors.Is(stub.UniqueError(0), stub.UniqueError(1)) {
|
||||||
|
t.Error("Is: unexpected true")
|
||||||
|
}
|
||||||
|
if !errors.Is(stub.UniqueError(0xbad), stub.UniqueError(0xbad)) {
|
||||||
|
t.Error("Is: unexpected false")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
44
container/stub/exit.go
Normal file
44
container/stub/exit.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package stub
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// PanicExit is a magic panic value treated as a simulated exit.
|
||||||
|
const PanicExit = 0xdeadbeef
|
||||||
|
|
||||||
|
const (
|
||||||
|
panicFailNow = 0xcafe0000 + iota
|
||||||
|
panicFatal
|
||||||
|
panicFatalf
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandleExit must be deferred before calling with the stub.
|
||||||
|
func HandleExit(t testing.TB) {
|
||||||
|
switch r := recover(); r {
|
||||||
|
case PanicExit:
|
||||||
|
break
|
||||||
|
|
||||||
|
case panicFailNow:
|
||||||
|
t.FailNow()
|
||||||
|
|
||||||
|
case panicFatal, panicFatalf, nil:
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleExitNew handles exits from goroutines created by [Stub.New].
|
||||||
|
func handleExitNew(t testing.TB) {
|
||||||
|
switch r := recover(); r {
|
||||||
|
case PanicExit, panicFatal, panicFatalf, nil:
|
||||||
|
break
|
||||||
|
|
||||||
|
case panicFailNow:
|
||||||
|
t.Fail()
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
93
container/stub/exit_test.go
Normal file
93
container/stub/exit_test.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package stub_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
_ "unsafe"
|
||||||
|
|
||||||
|
"hakurei.app/container/stub"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:linkname handleExitNew hakurei.app/container/stub.handleExitNew
|
||||||
|
func handleExitNew(_ testing.TB)
|
||||||
|
|
||||||
|
// overrideTFailNow overrides the Fail and FailNow method.
|
||||||
|
type overrideTFailNow struct {
|
||||||
|
*testing.T
|
||||||
|
failNow bool
|
||||||
|
fail bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *overrideTFailNow) FailNow() {
|
||||||
|
if o.failNow {
|
||||||
|
o.Errorf("attempted to FailNow twice")
|
||||||
|
}
|
||||||
|
o.failNow = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *overrideTFailNow) Fail() {
|
||||||
|
if o.fail {
|
||||||
|
o.Errorf("attempted to Fail twice")
|
||||||
|
}
|
||||||
|
o.fail = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleExit(t *testing.T) {
|
||||||
|
t.Run("exit", func(t *testing.T) {
|
||||||
|
defer stub.HandleExit(t)
|
||||||
|
panic(stub.PanicExit)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("goexit", func(t *testing.T) {
|
||||||
|
t.Run("FailNow", func(t *testing.T) {
|
||||||
|
ot := &overrideTFailNow{T: t}
|
||||||
|
defer func() {
|
||||||
|
if !ot.failNow {
|
||||||
|
t.Errorf("FailNow was never called")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
defer stub.HandleExit(ot)
|
||||||
|
panic(0xcafe0000)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Fail", func(t *testing.T) {
|
||||||
|
ot := &overrideTFailNow{T: t}
|
||||||
|
defer func() {
|
||||||
|
if !ot.fail {
|
||||||
|
t.Errorf("Fail was never called")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
defer handleExitNew(ot)
|
||||||
|
panic(0xcafe0000)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil", func(t *testing.T) {
|
||||||
|
defer stub.HandleExit(t)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("passthrough", func(t *testing.T) {
|
||||||
|
t.Run("toplevel", func(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
want := 0xcafebabe
|
||||||
|
if r := recover(); r != want {
|
||||||
|
t.Errorf("recover: %v, want %v", r, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
}()
|
||||||
|
defer stub.HandleExit(t)
|
||||||
|
panic(0xcafebabe)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("new", func(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
want := 0xcafe
|
||||||
|
if r := recover(); r != want {
|
||||||
|
t.Errorf("recover: %v, want %v", r, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
}()
|
||||||
|
defer handleExitNew(t)
|
||||||
|
panic(0xcafe)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
148
container/stub/stub.go
Normal file
148
container/stub/stub.go
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
// Package stub provides function call level stubbing and validation
|
||||||
|
// for library functions that are impossible to check otherwise.
|
||||||
|
package stub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// this should prevent stub from being inadvertently imported outside tests
|
||||||
|
var _ = func() {
|
||||||
|
if !testing.Testing() {
|
||||||
|
panic("stub imported while not in a test")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// A CallSeparator denotes an injected separation between two groups of calls.
|
||||||
|
CallSeparator = "\x00"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Stub is a collection of tracks of expected calls.
|
||||||
|
type Stub[K any] struct {
|
||||||
|
testing.TB
|
||||||
|
|
||||||
|
// makeK creates a new K for a descendant [Stub].
|
||||||
|
// This function may be called concurrently.
|
||||||
|
makeK func(s *Stub[K]) K
|
||||||
|
|
||||||
|
// want is a hierarchy of expected calls.
|
||||||
|
want Expect
|
||||||
|
// pos is the current position in [Expect.Calls].
|
||||||
|
pos int
|
||||||
|
// goroutine counts the number of goroutines created by this [Stub].
|
||||||
|
goroutine int
|
||||||
|
// sub stores the addresses of descendant [Stub] created by New.
|
||||||
|
sub []*Stub[K]
|
||||||
|
// wg waits for all descendants to complete.
|
||||||
|
wg *sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a root [Stub].
|
||||||
|
func New[K any](tb testing.TB, makeK func(s *Stub[K]) K, want Expect) *Stub[K] {
|
||||||
|
return &Stub[K]{TB: tb, makeK: makeK, want: want, wg: new(sync.WaitGroup)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stub[K]) FailNow() { panic(panicFailNow) }
|
||||||
|
func (s *Stub[K]) Fatal(args ...any) { s.Error(args...); panic(panicFatal) }
|
||||||
|
func (s *Stub[K]) Fatalf(format string, args ...any) { s.Errorf(format, args...); panic(panicFatalf) }
|
||||||
|
func (s *Stub[K]) SkipNow() { panic("invalid call to SkipNow") }
|
||||||
|
func (s *Stub[K]) Skip(...any) { panic("invalid call to Skip") }
|
||||||
|
func (s *Stub[K]) Skipf(string, ...any) { panic("invalid call to Skipf") }
|
||||||
|
|
||||||
|
// New calls f in a new goroutine
|
||||||
|
func (s *Stub[K]) New(f func(k K)) {
|
||||||
|
s.Helper()
|
||||||
|
|
||||||
|
s.Expects("New")
|
||||||
|
if len(s.want.Tracks) <= s.goroutine {
|
||||||
|
s.Fatal("New: track overrun")
|
||||||
|
}
|
||||||
|
ds := &Stub[K]{TB: s.TB, makeK: s.makeK, want: s.want.Tracks[s.goroutine], wg: s.wg}
|
||||||
|
s.goroutine++
|
||||||
|
s.sub = append(s.sub, ds)
|
||||||
|
s.wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
s.Helper()
|
||||||
|
|
||||||
|
defer s.wg.Done()
|
||||||
|
defer handleExitNew(s.TB)
|
||||||
|
f(s.makeK(ds))
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pos returns the current position of [Stub] in its [Expect.Calls]
|
||||||
|
func (s *Stub[K]) Pos() int { return s.pos }
|
||||||
|
|
||||||
|
// Len returns the length of [Expect.Calls].
|
||||||
|
func (s *Stub[K]) Len() int { return len(s.want.Calls) }
|
||||||
|
|
||||||
|
// VisitIncomplete calls f on an incomplete s and all its descendants.
|
||||||
|
func (s *Stub[K]) VisitIncomplete(f func(s *Stub[K])) {
|
||||||
|
s.Helper()
|
||||||
|
s.wg.Wait()
|
||||||
|
|
||||||
|
if s.want.Calls != nil && len(s.want.Calls) != s.pos {
|
||||||
|
f(s)
|
||||||
|
}
|
||||||
|
for _, ds := range s.sub {
|
||||||
|
ds.VisitIncomplete(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expects checks the name of and returns the current [Call] and advances pos.
|
||||||
|
func (s *Stub[K]) Expects(name string) (expect *Call) {
|
||||||
|
s.Helper()
|
||||||
|
|
||||||
|
if len(s.want.Calls) == s.pos {
|
||||||
|
s.Fatal("Expects: advancing beyond expected calls")
|
||||||
|
}
|
||||||
|
expect = &s.want.Calls[s.pos]
|
||||||
|
if name != expect.Name {
|
||||||
|
if expect.Name == CallSeparator {
|
||||||
|
s.Fatalf("Expects: func = %s, separator overrun", name)
|
||||||
|
}
|
||||||
|
if name == CallSeparator {
|
||||||
|
s.Fatalf("Expects: separator, want %s", expect.Name)
|
||||||
|
}
|
||||||
|
s.Fatalf("Expects: func = %s, want %s", name, expect.Name)
|
||||||
|
}
|
||||||
|
s.pos++
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckArg checks an argument comparable with the == operator. Avoid using this with pointers.
|
||||||
|
func CheckArg[T comparable, K any](s *Stub[K], arg string, got T, n int) bool {
|
||||||
|
s.Helper()
|
||||||
|
|
||||||
|
pos := s.pos - 1
|
||||||
|
if pos < 0 || pos >= len(s.want.Calls) {
|
||||||
|
panic("invalid call to CheckArg")
|
||||||
|
}
|
||||||
|
expect := s.want.Calls[pos]
|
||||||
|
want, ok := expect.Args[n].(T)
|
||||||
|
if !ok || got != want {
|
||||||
|
s.Errorf("%s: %s = %#v, want %#v (%d)", expect.Name, arg, got, want, pos)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckArgReflect checks an argument of any type.
|
||||||
|
func CheckArgReflect[K any](s *Stub[K], arg string, got any, n int) bool {
|
||||||
|
s.Helper()
|
||||||
|
|
||||||
|
pos := s.pos - 1
|
||||||
|
if pos < 0 || pos >= len(s.want.Calls) {
|
||||||
|
panic("invalid call to CheckArgReflect")
|
||||||
|
}
|
||||||
|
expect := s.want.Calls[pos]
|
||||||
|
want := expect.Args[n]
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
s.Errorf("%s: %s = %#v, want %#v (%d)", expect.Name, arg, got, want, pos)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
296
container/stub/stub_test.go
Normal file
296
container/stub/stub_test.go
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
package stub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// stubHolder embeds [Stub].
|
||||||
|
type stubHolder struct{ *Stub[stubHolder] }
|
||||||
|
|
||||||
|
// overrideT allows some methods of [testing.T] to be overridden.
|
||||||
|
type overrideT struct {
|
||||||
|
*testing.T
|
||||||
|
|
||||||
|
error atomic.Pointer[func(args ...any)]
|
||||||
|
errorf atomic.Pointer[func(format string, args ...any)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *overrideT) Error(args ...any) {
|
||||||
|
fp := t.error.Load()
|
||||||
|
if fp == nil || *fp == nil {
|
||||||
|
t.T.Error(args...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
(*fp)(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *overrideT) Errorf(format string, args ...any) {
|
||||||
|
fp := t.errorf.Load()
|
||||||
|
if fp == nil || *fp == nil {
|
||||||
|
t.T.Errorf(format, args...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
(*fp)(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStub(t *testing.T) {
|
||||||
|
t.Run("goexit", func(t *testing.T) {
|
||||||
|
t.Run("FailNow", func(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != panicFailNow {
|
||||||
|
t.Errorf("recover: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
new(stubHolder).FailNow()
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("SkipNow", func(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
want := "invalid call to SkipNow"
|
||||||
|
if r := recover(); r != want {
|
||||||
|
t.Errorf("recover: %v, want %v", r, want)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
new(stubHolder).SkipNow()
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Skip", func(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
want := "invalid call to Skip"
|
||||||
|
if r := recover(); r != want {
|
||||||
|
t.Errorf("recover: %v, want %v", r, want)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
new(stubHolder).Skip()
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Skipf", func(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
want := "invalid call to Skipf"
|
||||||
|
if r := recover(); r != want {
|
||||||
|
t.Errorf("recover: %v, want %v", r, want)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
new(stubHolder).Skipf("")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("new", func(t *testing.T) {
|
||||||
|
t.Run("success", func(t *testing.T) {
|
||||||
|
s := New(t, func(s *Stub[stubHolder]) stubHolder { return stubHolder{s} }, Expect{Calls: []Call{
|
||||||
|
{"New", ExpectArgs{}, nil, nil},
|
||||||
|
}, Tracks: []Expect{{Calls: []Call{
|
||||||
|
{"done", ExpectArgs{0xbabe}, nil, nil},
|
||||||
|
}}}})
|
||||||
|
|
||||||
|
s.New(func(k stubHolder) {
|
||||||
|
expect := k.Expects("done")
|
||||||
|
if expect.Name != "done" {
|
||||||
|
t.Errorf("New: Name = %s, want done", expect.Name)
|
||||||
|
}
|
||||||
|
if expect.Args != (ExpectArgs{0xbabe}) {
|
||||||
|
t.Errorf("New: Args = %#v", expect.Args)
|
||||||
|
}
|
||||||
|
if expect.Ret != nil {
|
||||||
|
t.Errorf("New: Ret = %#v", expect.Ret)
|
||||||
|
}
|
||||||
|
if expect.Err != nil {
|
||||||
|
t.Errorf("New: Err = %#v", expect.Err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if pos := s.Pos(); pos != 1 {
|
||||||
|
t.Errorf("Pos: %d, want 1", pos)
|
||||||
|
}
|
||||||
|
if l := s.Len(); l != 1 {
|
||||||
|
t.Errorf("Len: %d, want 1", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.VisitIncomplete(func(s *Stub[stubHolder]) { panic("unreachable") })
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("overrun", func(t *testing.T) {
|
||||||
|
ot := &overrideT{T: t}
|
||||||
|
ot.error.Store(checkError(t, "New: track overrun"))
|
||||||
|
s := New(ot, func(s *Stub[stubHolder]) stubHolder { return stubHolder{s} }, Expect{Calls: []Call{
|
||||||
|
{"New", ExpectArgs{}, nil, nil},
|
||||||
|
{"panic", ExpectArgs{"unreachable"}, nil, nil},
|
||||||
|
}})
|
||||||
|
func() { defer HandleExit(t); s.New(func(k stubHolder) { panic("unreachable") }) }()
|
||||||
|
|
||||||
|
var visit int
|
||||||
|
s.VisitIncomplete(func(s *Stub[stubHolder]) {
|
||||||
|
visit++
|
||||||
|
if visit > 1 {
|
||||||
|
panic("unexpected visit count")
|
||||||
|
}
|
||||||
|
|
||||||
|
want := Call{"panic", ExpectArgs{"unreachable"}, nil, nil}
|
||||||
|
if got := s.want.Calls[s.pos]; !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("VisitIncomplete: %#v, want %#v", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("expects", func(t *testing.T) {
|
||||||
|
t.Run("overrun", func(t *testing.T) {
|
||||||
|
ot := &overrideT{T: t}
|
||||||
|
ot.error.Store(checkError(t, "Expects: advancing beyond expected calls"))
|
||||||
|
s := New(ot, func(s *Stub[stubHolder]) stubHolder { return stubHolder{s} }, Expect{})
|
||||||
|
func() { defer HandleExit(t); s.Expects("unreachable") }()
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("separator", func(t *testing.T) {
|
||||||
|
t.Run("overrun", func(t *testing.T) {
|
||||||
|
ot := &overrideT{T: t}
|
||||||
|
ot.errorf.Store(checkErrorf(t, "Expects: func = %s, separator overrun", "meow"))
|
||||||
|
s := New(ot, func(s *Stub[stubHolder]) stubHolder { return stubHolder{s} }, Expect{Calls: []Call{
|
||||||
|
{CallSeparator, ExpectArgs{}, nil, nil},
|
||||||
|
}})
|
||||||
|
func() { defer HandleExit(t); s.Expects("meow") }()
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("mismatch", func(t *testing.T) {
|
||||||
|
ot := &overrideT{T: t}
|
||||||
|
ot.errorf.Store(checkErrorf(t, "Expects: separator, want %s", "panic"))
|
||||||
|
s := New(ot, func(s *Stub[stubHolder]) stubHolder { return stubHolder{s} }, Expect{Calls: []Call{
|
||||||
|
{"panic", ExpectArgs{}, nil, nil},
|
||||||
|
}})
|
||||||
|
func() { defer HandleExit(t); s.Expects(CallSeparator) }()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("mismatch", func(t *testing.T) {
|
||||||
|
ot := &overrideT{T: t}
|
||||||
|
ot.errorf.Store(checkErrorf(t, "Expects: func = %s, want %s", "meow", "nya"))
|
||||||
|
s := New(ot, func(s *Stub[stubHolder]) stubHolder { return stubHolder{s} }, Expect{Calls: []Call{
|
||||||
|
{"nya", ExpectArgs{}, nil, nil},
|
||||||
|
}})
|
||||||
|
func() { defer HandleExit(t); s.Expects("meow") }()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckArg(t *testing.T) {
|
||||||
|
t.Run("oob negative", func(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
want := "invalid call to CheckArg"
|
||||||
|
if r := recover(); r != want {
|
||||||
|
t.Errorf("recover: %v, want %v", r, want)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
s := New(t, func(s *Stub[stubHolder]) stubHolder { return stubHolder{s} }, Expect{})
|
||||||
|
CheckArg(s, "unreachable", struct{}{}, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
ot := &overrideT{T: t}
|
||||||
|
s := New(ot, func(s *Stub[stubHolder]) stubHolder { return stubHolder{s} }, Expect{Calls: []Call{
|
||||||
|
{"panic", ExpectArgs{PanicExit}, nil, nil},
|
||||||
|
{"meow", ExpectArgs{-1}, nil, nil},
|
||||||
|
}})
|
||||||
|
t.Run("match", func(t *testing.T) {
|
||||||
|
s.Expects("panic")
|
||||||
|
if !CheckArg(s, "v", PanicExit, 0) {
|
||||||
|
t.Errorf("CheckArg: unexpected false")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("mismatch", func(t *testing.T) {
|
||||||
|
defer HandleExit(t)
|
||||||
|
s.Expects("meow")
|
||||||
|
ot.errorf.Store(checkErrorf(t, "%s: %s = %#v, want %#v (%d)", "meow", "time", 0, -1, 1))
|
||||||
|
if CheckArg(s, "time", 0, 0) {
|
||||||
|
t.Errorf("CheckArg: unexpected true")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("oob", func(t *testing.T) {
|
||||||
|
s.pos++
|
||||||
|
defer func() {
|
||||||
|
want := "invalid call to CheckArg"
|
||||||
|
if r := recover(); r != want {
|
||||||
|
t.Errorf("recover: %v, want %v", r, want)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
CheckArg(s, "unreachable", struct{}{}, 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckArgReflect(t *testing.T) {
|
||||||
|
t.Run("oob lower", func(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
want := "invalid call to CheckArgReflect"
|
||||||
|
if r := recover(); r != want {
|
||||||
|
t.Errorf("recover: %v, want %v", r, want)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
s := New(t, func(s *Stub[stubHolder]) stubHolder { return stubHolder{s} }, Expect{})
|
||||||
|
CheckArgReflect(s, "unreachable", struct{}{}, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
ot := &overrideT{T: t}
|
||||||
|
s := New(ot, func(s *Stub[stubHolder]) stubHolder { return stubHolder{s} }, Expect{Calls: []Call{
|
||||||
|
{"panic", ExpectArgs{PanicExit}, nil, nil},
|
||||||
|
{"meow", ExpectArgs{-1}, nil, nil},
|
||||||
|
}})
|
||||||
|
t.Run("match", func(t *testing.T) {
|
||||||
|
s.Expects("panic")
|
||||||
|
if !CheckArgReflect(s, "v", PanicExit, 0) {
|
||||||
|
t.Errorf("CheckArgReflect: unexpected false")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("mismatch", func(t *testing.T) {
|
||||||
|
defer HandleExit(t)
|
||||||
|
s.Expects("meow")
|
||||||
|
ot.errorf.Store(checkErrorf(t, "%s: %s = %#v, want %#v (%d)", "meow", "time", 0, -1, 1))
|
||||||
|
if CheckArgReflect(s, "time", 0, 0) {
|
||||||
|
t.Errorf("CheckArgReflect: unexpected true")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("oob", func(t *testing.T) {
|
||||||
|
s.pos++
|
||||||
|
defer func() {
|
||||||
|
want := "invalid call to CheckArgReflect"
|
||||||
|
if r := recover(); r != want {
|
||||||
|
t.Errorf("recover: %v, want %v", r, want)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
CheckArgReflect(s, "unreachable", struct{}{}, 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkError(t *testing.T, wantArgs ...any) *func(args ...any) {
|
||||||
|
var called bool
|
||||||
|
f := func(args ...any) {
|
||||||
|
if called {
|
||||||
|
panic("invalid call to error")
|
||||||
|
}
|
||||||
|
called = true
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(args, wantArgs) {
|
||||||
|
t.Errorf("Error: %#v, want %#v", args, wantArgs)
|
||||||
|
}
|
||||||
|
panic(PanicExit)
|
||||||
|
}
|
||||||
|
return &f
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkErrorf(t *testing.T, wantFormat string, wantArgs ...any) *func(format string, args ...any) {
|
||||||
|
var called bool
|
||||||
|
f := func(format string, args ...any) {
|
||||||
|
if called {
|
||||||
|
panic("invalid call to errorf")
|
||||||
|
}
|
||||||
|
called = true
|
||||||
|
|
||||||
|
if format != wantFormat {
|
||||||
|
t.Errorf("Errorf: format = %q, want %q", format, wantFormat)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(args, wantArgs) {
|
||||||
|
t.Errorf("Errorf: args = %#v, want %#v", args, wantArgs)
|
||||||
|
}
|
||||||
|
panic(PanicExit)
|
||||||
|
}
|
||||||
|
return &f
|
||||||
|
}
|
||||||
@@ -24,6 +24,32 @@ var (
|
|||||||
ErrMountInfoSep = errors.New("bad optional fields separator")
|
ErrMountInfoSep = errors.New("bad optional fields separator")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type DecoderError struct {
|
||||||
|
Op string
|
||||||
|
Line int
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DecoderError) Unwrap() error { return e.Err }
|
||||||
|
func (e *DecoderError) Error() string {
|
||||||
|
var s string
|
||||||
|
|
||||||
|
var numError *strconv.NumError
|
||||||
|
switch {
|
||||||
|
case errors.As(e.Err, &numError) && numError != nil:
|
||||||
|
s = "numeric field " + strconv.Quote(numError.Num) + " " + numError.Err.Error()
|
||||||
|
|
||||||
|
default:
|
||||||
|
s = e.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
var atLine string
|
||||||
|
if e.Line >= 0 {
|
||||||
|
atLine = " at line " + strconv.Itoa(e.Line)
|
||||||
|
}
|
||||||
|
return e.Op + " mountinfo" + atLine + ": " + s
|
||||||
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// A MountInfoDecoder reads and decodes proc_pid_mountinfo(5) entries from an input stream.
|
// A MountInfoDecoder reads and decodes proc_pid_mountinfo(5) entries from an input stream.
|
||||||
MountInfoDecoder struct {
|
MountInfoDecoder struct {
|
||||||
@@ -32,6 +58,7 @@ type (
|
|||||||
|
|
||||||
current *MountInfo
|
current *MountInfo
|
||||||
parseErr error
|
parseErr error
|
||||||
|
curLine int
|
||||||
complete bool
|
complete bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,9 +159,12 @@ func (d *MountInfoDecoder) Entries() iter.Seq[*MountInfoEntry] {
|
|||||||
|
|
||||||
func (d *MountInfoDecoder) Err() error {
|
func (d *MountInfoDecoder) Err() error {
|
||||||
if err := d.s.Err(); err != nil {
|
if err := d.s.Err(); err != nil {
|
||||||
return err
|
return &DecoderError{"scan", d.curLine, err}
|
||||||
}
|
}
|
||||||
return d.parseErr
|
if d.parseErr != nil {
|
||||||
|
return &DecoderError{"parse", d.curLine, d.parseErr}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *MountInfoDecoder) scan() bool {
|
func (d *MountInfoDecoder) scan() bool {
|
||||||
@@ -160,6 +190,7 @@ func (d *MountInfoDecoder) scan() bool {
|
|||||||
d.current.Next = m
|
d.current.Next = m
|
||||||
d.current = d.current.Next
|
d.current = d.current.Next
|
||||||
}
|
}
|
||||||
|
d.curLine++
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"iter"
|
"iter"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"reflect"
|
"reflect"
|
||||||
"slices"
|
"slices"
|
||||||
@@ -15,62 +16,102 @@ import (
|
|||||||
"hakurei.app/container/vfs"
|
"hakurei.app/container/vfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestDecoderError(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
err *vfs.DecoderError
|
||||||
|
want string
|
||||||
|
target error
|
||||||
|
targetF error
|
||||||
|
}{
|
||||||
|
{"errno", &vfs.DecoderError{Op: "parse", Line: 0xdeadbeef, Err: syscall.ENOTRECOVERABLE},
|
||||||
|
"parse mountinfo at line 3735928559: state not recoverable", syscall.ENOTRECOVERABLE, syscall.EROFS},
|
||||||
|
|
||||||
|
{"strconv", &vfs.DecoderError{Op: "parse", Line: 0xdeadbeef, Err: &strconv.NumError{Func: "Atoi", Num: "meow", Err: strconv.ErrSyntax}},
|
||||||
|
`parse mountinfo at line 3735928559: numeric field "meow" invalid syntax`, strconv.ErrSyntax, os.ErrInvalid},
|
||||||
|
|
||||||
|
{"unfold", &vfs.DecoderError{Op: "unfold", Line: -1, Err: vfs.UnfoldTargetError("/proc/nonexistent")},
|
||||||
|
"unfold mountinfo: mount point /proc/nonexistent never appeared in mountinfo", vfs.UnfoldTargetError("/proc/nonexistent"), os.ErrNotExist},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Run("error", func(t *testing.T) {
|
||||||
|
if got := tc.err.Error(); got != tc.want {
|
||||||
|
t.Errorf("Error: %s, want %s", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("is", func(t *testing.T) {
|
||||||
|
if !errors.Is(tc.err, tc.target) {
|
||||||
|
t.Errorf("Is: unexpected false")
|
||||||
|
}
|
||||||
|
if errors.Is(tc.err, tc.targetF) {
|
||||||
|
t.Errorf("Is: unexpected true")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMountInfo(t *testing.T) {
|
func TestMountInfo(t *testing.T) {
|
||||||
testCases := []mountInfoTest{
|
testCases := []mountInfoTest{
|
||||||
{"count", sampleMountinfoBase + `
|
{"count", sampleMountinfoBase + `
|
||||||
21 20 0:53/ /mnt/test rw,relatime - tmpfs rw
|
21 20 0:53/ /mnt/test rw,relatime - tmpfs rw
|
||||||
21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`,
|
21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`,
|
||||||
vfs.ErrMountInfoFields, "", nil, nil, nil},
|
&vfs.DecoderError{Op: "parse", Line: 6, Err: vfs.ErrMountInfoFields},
|
||||||
|
"", nil, nil, nil},
|
||||||
|
|
||||||
{"sep", sampleMountinfoBase + `
|
{"sep", sampleMountinfoBase + `
|
||||||
21 20 0:53 / /mnt/test rw,relatime shared:212 _ tmpfs rw
|
21 20 0:53 / /mnt/test rw,relatime shared:212 _ tmpfs rw
|
||||||
21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`,
|
21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`,
|
||||||
vfs.ErrMountInfoSep, "", nil, nil, nil},
|
&vfs.DecoderError{Op: "parse", Line: 6, Err: vfs.ErrMountInfoSep},
|
||||||
|
"", nil, nil, nil},
|
||||||
|
|
||||||
{"id", sampleMountinfoBase + `
|
{"id", sampleMountinfoBase + `
|
||||||
id 20 0:53 / /mnt/test rw,relatime shared:212 - tmpfs rw
|
id 20 0:53 / /mnt/test rw,relatime shared:212 - tmpfs rw
|
||||||
21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`,
|
21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`,
|
||||||
strconv.ErrSyntax, "", nil, nil, nil},
|
&vfs.DecoderError{Op: "parse", Line: 6, Err: &strconv.NumError{Func: "Atoi", Num: "id", Err: strconv.ErrSyntax}},
|
||||||
|
"", nil, nil, nil},
|
||||||
|
|
||||||
{"parent", sampleMountinfoBase + `
|
{"parent", sampleMountinfoBase + `
|
||||||
21 parent 0:53 / /mnt/test rw,relatime shared:212 - tmpfs rw
|
21 parent 0:53 / /mnt/test rw,relatime shared:212 - tmpfs rw
|
||||||
21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`,
|
21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`,
|
||||||
strconv.ErrSyntax, "", nil, nil, nil},
|
&vfs.DecoderError{Op: "parse", Line: 6, Err: &strconv.NumError{Func: "Atoi", Num: "parent", Err: strconv.ErrSyntax}}, "", nil, nil, nil},
|
||||||
|
|
||||||
{"devno", sampleMountinfoBase + `
|
{"devno", sampleMountinfoBase + `
|
||||||
21 20 053 / /mnt/test rw,relatime shared:212 - tmpfs rw
|
21 20 053 / /mnt/test rw,relatime shared:212 - tmpfs rw
|
||||||
21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`,
|
21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`,
|
||||||
nil, "unexpected EOF", nil, nil, nil},
|
nil, "parse mountinfo at line 6: unexpected EOF", nil, nil, nil},
|
||||||
|
|
||||||
{"maj", sampleMountinfoBase + `
|
{"maj", sampleMountinfoBase + `
|
||||||
21 20 maj:53 / /mnt/test rw,relatime shared:212 - tmpfs rw
|
21 20 maj:53 / /mnt/test rw,relatime shared:212 - tmpfs rw
|
||||||
21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`,
|
21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`,
|
||||||
nil, "expected integer", nil, nil, nil},
|
nil, "parse mountinfo at line 6: expected integer", nil, nil, nil},
|
||||||
|
|
||||||
{"min", sampleMountinfoBase + `
|
{"min", sampleMountinfoBase + `
|
||||||
21 20 0:min / /mnt/test rw,relatime shared:212 - tmpfs rw
|
21 20 0:min / /mnt/test rw,relatime shared:212 - tmpfs rw
|
||||||
21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`,
|
21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`,
|
||||||
nil, "expected integer", nil, nil, nil},
|
nil, "parse mountinfo at line 6: expected integer", nil, nil, nil},
|
||||||
|
|
||||||
{"mountroot", sampleMountinfoBase + `
|
{"mountroot", sampleMountinfoBase + `
|
||||||
21 20 0:53 /mnt/test rw,relatime - tmpfs rw
|
21 20 0:53 /mnt/test rw,relatime - tmpfs rw
|
||||||
21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`,
|
21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`,
|
||||||
vfs.ErrMountInfoEmpty, "", nil, nil, nil},
|
&vfs.DecoderError{Op: "parse", Line: 6, Err: vfs.ErrMountInfoEmpty}, "", nil, nil, nil},
|
||||||
|
|
||||||
{"target", sampleMountinfoBase + `
|
{"target", sampleMountinfoBase + `
|
||||||
21 20 0:53 / rw,relatime - tmpfs rw
|
21 20 0:53 / rw,relatime - tmpfs rw
|
||||||
21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`,
|
21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`,
|
||||||
vfs.ErrMountInfoEmpty, "", nil, nil, nil},
|
&vfs.DecoderError{Op: "parse", Line: 6, Err: vfs.ErrMountInfoEmpty}, "", nil, nil, nil},
|
||||||
|
|
||||||
{"vfs options", sampleMountinfoBase + `
|
{"vfs options", sampleMountinfoBase + `
|
||||||
21 20 0:53 / /mnt/test - tmpfs rw
|
21 20 0:53 / /mnt/test - tmpfs rw
|
||||||
21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`,
|
21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`,
|
||||||
vfs.ErrMountInfoEmpty, "", nil, nil, nil},
|
&vfs.DecoderError{Op: "parse", Line: 6, Err: vfs.ErrMountInfoEmpty}, "", nil, nil, nil},
|
||||||
|
|
||||||
{"FS type", sampleMountinfoBase + `
|
{"FS type", sampleMountinfoBase + `
|
||||||
21 20 0:53 / /mnt/test rw,relatime - rw
|
21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755
|
||||||
21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`,
|
21 20 0:53 / /mnt/test rw,relatime - rw`,
|
||||||
vfs.ErrMountInfoEmpty, "", nil, nil, nil},
|
&vfs.DecoderError{Op: "parse", Line: 7, Err: vfs.ErrMountInfoEmpty}, "", nil, nil, nil},
|
||||||
|
|
||||||
{"base", sampleMountinfoBase, nil, "", []*wantMountInfo{
|
{"base", sampleMountinfoBase, nil, "", []*wantMountInfo{
|
||||||
m(15, 20, 0, 3, "/", "/proc", "rw,relatime", o(), "proc", "/proc", "rw", syscall.MS_RELATIME, nil),
|
m(15, 20, 0, 3, "/", "/proc", "rw,relatime", o(), "proc", "/proc", "rw", syscall.MS_RELATIME, nil),
|
||||||
@@ -266,9 +307,9 @@ func (tc *mountInfoTest) check(t *testing.T, d *vfs.MountInfoDecoder, funcName s
|
|||||||
})
|
})
|
||||||
} else if tc.wantNode != nil || tc.wantCollectF != nil {
|
} else if tc.wantNode != nil || tc.wantCollectF != nil {
|
||||||
panic("invalid test case")
|
panic("invalid test case")
|
||||||
} else if _, err := d.Unfold("/"); !errors.Is(err, tc.wantErr) {
|
} else if _, err := d.Unfold("/"); !reflect.DeepEqual(err, tc.wantErr) {
|
||||||
if tc.wantError == "" {
|
if tc.wantError == "" {
|
||||||
t.Errorf("Unfold: error = %v, wantErr %v",
|
t.Errorf("Unfold: error = %#v, wantErr %#v",
|
||||||
err, tc.wantErr)
|
err, tc.wantErr)
|
||||||
} else if err != nil && err.Error() != tc.wantError {
|
} else if err != nil && err.Error() != tc.wantError {
|
||||||
t.Errorf("Unfold: error = %q, wantError %q",
|
t.Errorf("Unfold: error = %q, wantError %q",
|
||||||
@@ -276,9 +317,9 @@ func (tc *mountInfoTest) check(t *testing.T, d *vfs.MountInfoDecoder, funcName s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gotErr(); !errors.Is(err, tc.wantErr) {
|
if err := gotErr(); !reflect.DeepEqual(err, tc.wantErr) {
|
||||||
if tc.wantError == "" {
|
if tc.wantError == "" {
|
||||||
t.Errorf("%s: error = %v, wantErr %v",
|
t.Errorf("%s: error = %#v, wantErr %#v",
|
||||||
funcName, err, tc.wantErr)
|
funcName, err, tc.wantErr)
|
||||||
} else if err != nil && err.Error() != tc.wantError {
|
} else if err != nil && err.Error() != tc.wantError {
|
||||||
t.Errorf("%s: error = %q, wantError %q",
|
t.Errorf("%s: error = %q, wantError %q",
|
||||||
|
|||||||
@@ -4,9 +4,14 @@ import (
|
|||||||
"iter"
|
"iter"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type UnfoldTargetError string
|
||||||
|
|
||||||
|
func (e UnfoldTargetError) Error() string {
|
||||||
|
return "mount point " + string(e) + " never appeared in mountinfo"
|
||||||
|
}
|
||||||
|
|
||||||
// MountInfoNode positions a [MountInfoEntry] in its mount hierarchy.
|
// MountInfoNode positions a [MountInfoEntry] in its mount hierarchy.
|
||||||
type MountInfoNode struct {
|
type MountInfoNode struct {
|
||||||
*MountInfoEntry
|
*MountInfoEntry
|
||||||
@@ -65,7 +70,8 @@ func (d *MountInfoDecoder) Unfold(target string) (*MountInfoNode, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if targetIndex == -1 {
|
if targetIndex == -1 {
|
||||||
return nil, syscall.ESTALE
|
// target does not exist in parsed mountinfo
|
||||||
|
return nil, &DecoderError{Op: "unfold", Line: -1, Err: UnfoldTargetError(targetClean)}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, cur := range mountinfo {
|
for _, cur := range mountinfo {
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
package vfs_test
|
package vfs_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"hakurei.app/container/vfs"
|
"hakurei.app/container/vfs"
|
||||||
@@ -26,7 +24,7 @@ func TestUnfold(t *testing.T) {
|
|||||||
"no match",
|
"no match",
|
||||||
sampleMountinfoBase,
|
sampleMountinfoBase,
|
||||||
"/mnt",
|
"/mnt",
|
||||||
syscall.ESTALE, nil, nil, nil,
|
&vfs.DecoderError{Op: "unfold", Line: -1, Err: vfs.UnfoldTargetError("/mnt")}, nil, nil, nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cover",
|
"cover",
|
||||||
@@ -55,7 +53,7 @@ func TestUnfold(t *testing.T) {
|
|||||||
d := vfs.NewMountInfoDecoder(strings.NewReader(tc.sample))
|
d := vfs.NewMountInfoDecoder(strings.NewReader(tc.sample))
|
||||||
got, err := d.Unfold(tc.target)
|
got, err := d.Unfold(tc.target)
|
||||||
|
|
||||||
if !errors.Is(err, tc.wantErr) {
|
if !reflect.DeepEqual(err, tc.wantErr) {
|
||||||
t.Errorf("Unfold: error = %v, wantErr %v",
|
t.Errorf("Unfold: error = %v, wantErr %v",
|
||||||
err, tc.wantErr)
|
err, tc.wantErr)
|
||||||
}
|
}
|
||||||
|
|||||||
12
flake.lock
generated
12
flake.lock
generated
@@ -7,11 +7,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1753479839,
|
"lastModified": 1756679287,
|
||||||
"narHash": "sha256-E/rPVh7vyPMJUFl2NAew+zibNGfVbANr8BP8nLRbLkQ=",
|
"narHash": "sha256-Xd1vOeY9ccDf5VtVK12yM0FS6qqvfUop8UQlxEB+gTQ=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "0b9bf983db4d064764084cd6748efb1ab8297d1e",
|
"rev": "07fc025fe10487dd80f2ec694f1cd790e752d0e8",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -23,11 +23,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1753345091,
|
"lastModified": 1757020766,
|
||||||
"narHash": "sha256-CdX2Rtvp5I8HGu9swBmYuq+ILwRxpXdJwlpg8jvN4tU=",
|
"narHash": "sha256-PLoSjHRa2bUbi1x9HoXgTx2AiuzNXs54c8omhadyvp0=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "3ff0e34b1383648053bba8ed03f201d3466f90c9",
|
"rev": "fe83bbdde2ccdc2cb9573aa846abe8363f79a97a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestContainer(t *testing.T) {
|
func TestContainer(t *testing.T) {
|
||||||
t.Run("start empty 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(), container.MustAbs(container.Nonexistent), "hakurei", argsWt, false, argF, nil, nil)
|
||||||
|
|
||||||
wantErr := "container: starting an empty 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 {
|
||||||
t.Errorf("Start: error = %v, wantErr %q",
|
t.Errorf("Start: error = %v, wantErr %q",
|
||||||
err, wantErr)
|
err, wantErr)
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -47,6 +49,10 @@ func argFChecked(argsFd, statFd int) (args []string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
containerTimeout = 30 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
// this function tests an implementation of the helper.Helper interface
|
// this function tests an implementation of the helper.Helper interface
|
||||||
func testHelper(t *testing.T, createHelper func(ctx context.Context, setOutput func(stdoutP, stderrP *io.Writer), stat bool) helper.Helper) {
|
func testHelper(t *testing.T, createHelper func(ctx context.Context, setOutput func(stdoutP, stderrP *io.Writer), stat bool) helper.Helper) {
|
||||||
oldWaitDelay := helper.WaitDelay
|
oldWaitDelay := helper.WaitDelay
|
||||||
@@ -54,18 +60,15 @@ func testHelper(t *testing.T, createHelper func(ctx context.Context, setOutput f
|
|||||||
t.Cleanup(func() { helper.WaitDelay = oldWaitDelay })
|
t.Cleanup(func() { helper.WaitDelay = oldWaitDelay })
|
||||||
|
|
||||||
t.Run("start helper with status channel and wait", func(t *testing.T) {
|
t.Run("start helper with status channel and wait", func(t *testing.T) {
|
||||||
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(t.Context(), containerTimeout)
|
||||||
stdout := new(strings.Builder)
|
stdout := new(strings.Builder)
|
||||||
h := createHelper(ctx, func(stdoutP, stderrP *io.Writer) { *stdoutP, *stderrP = stdout, os.Stderr }, true)
|
h := createHelper(ctx, func(stdoutP, stderrP *io.Writer) { *stdoutP, *stderrP = stdout, os.Stderr }, true)
|
||||||
|
|
||||||
t.Run("wait not yet started helper", func(t *testing.T) {
|
t.Run("wait not yet started helper", func(t *testing.T) {
|
||||||
defer func() {
|
if err := h.Wait(); !reflect.DeepEqual(err, syscall.EINVAL) &&
|
||||||
r := recover()
|
!reflect.DeepEqual(err, errors.New("exec: not started")) {
|
||||||
if r == nil {
|
t.Errorf("Wait: error = %v", err)
|
||||||
t.Fatalf("Wait did not panic")
|
}
|
||||||
}
|
|
||||||
}()
|
|
||||||
panic(fmt.Sprintf("unreachable: %v", h.Wait()))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Log("starting helper stub")
|
t.Log("starting helper stub")
|
||||||
@@ -108,7 +111,7 @@ func testHelper(t *testing.T, createHelper func(ctx context.Context, setOutput f
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("start helper and wait", func(t *testing.T) {
|
t.Run("start helper and wait", func(t *testing.T) {
|
||||||
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(t.Context(), containerTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
stdout := new(strings.Builder)
|
stdout := new(strings.Builder)
|
||||||
h := createHelper(ctx, func(stdoutP, stderrP *io.Writer) { *stdoutP, *stderrP = stdout, os.Stderr }, false)
|
h := createHelper(ctx, func(stdoutP, stderrP *io.Writer) { *stdoutP, *stderrP = stdout, os.Stderr }, false)
|
||||||
|
|||||||
@@ -87,7 +87,9 @@ type (
|
|||||||
|
|
||||||
// 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
|
// 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,
|
||||||
|
// 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
|
// pass through all devices
|
||||||
|
|||||||
30
hst/hst.go
30
hst/hst.go
@@ -2,12 +2,42 @@
|
|||||||
package hst
|
package hst
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/seccomp"
|
||||||
"hakurei.app/system"
|
"hakurei.app/system"
|
||||||
"hakurei.app/system/dbus"
|
"hakurei.app/system/dbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// An AppError is returned while starting an app according to [hst.Config].
|
||||||
|
type AppError struct {
|
||||||
|
Step string
|
||||||
|
Err error
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *AppError) Error() string { return e.Err.Error() }
|
||||||
|
func (e *AppError) Unwrap() error { return e.Err }
|
||||||
|
func (e *AppError) Message() string {
|
||||||
|
if e.Msg != "" {
|
||||||
|
return e.Msg
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case errors.As(e.Err, new(*os.PathError)),
|
||||||
|
errors.As(e.Err, new(*os.LinkError)),
|
||||||
|
errors.As(e.Err, new(*os.SyscallError)),
|
||||||
|
errors.As(e.Err, new(*net.OpError)):
|
||||||
|
return "cannot " + e.Error()
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "cannot " + e.Step + ": " + e.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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`)
|
||||||
|
|||||||
@@ -2,11 +2,93 @@ package hst_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/container/stub"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestAppError(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
s string
|
||||||
|
message string
|
||||||
|
is, isF error
|
||||||
|
}{
|
||||||
|
{"message", &hst.AppError{Step: "obtain uid from hsu", Err: stub.UniqueError(0),
|
||||||
|
Msg: "the setuid helper is missing: /run/wrappers/bin/hsu"},
|
||||||
|
"unique error 0 injected by the test suite",
|
||||||
|
"the setuid helper is missing: /run/wrappers/bin/hsu",
|
||||||
|
stub.UniqueError(0), os.ErrNotExist},
|
||||||
|
|
||||||
|
{"os.PathError", &hst.AppError{Step: "passthrough os.PathError",
|
||||||
|
Err: &os.PathError{Op: "stat", Path: "/proc/nonexistent", Err: os.ErrNotExist}},
|
||||||
|
"stat /proc/nonexistent: file does not exist",
|
||||||
|
"cannot stat /proc/nonexistent: file does not exist",
|
||||||
|
os.ErrNotExist, stub.UniqueError(0xdeadbeef)},
|
||||||
|
|
||||||
|
{"os.LinkError", &hst.AppError{Step: "passthrough os.LinkError",
|
||||||
|
Err: &os.LinkError{Op: "link", Old: "/proc/self", New: "/proc/nonexistent", Err: os.ErrNotExist}},
|
||||||
|
"link /proc/self /proc/nonexistent: file does not exist",
|
||||||
|
"cannot link /proc/self /proc/nonexistent: file does not exist",
|
||||||
|
os.ErrNotExist, stub.UniqueError(0xdeadbeef)},
|
||||||
|
|
||||||
|
{"os.SyscallError", &hst.AppError{Step: "passthrough os.SyscallError",
|
||||||
|
Err: &os.SyscallError{Syscall: "meow", Err: syscall.ENOSYS}},
|
||||||
|
"meow: function not implemented",
|
||||||
|
"cannot meow: function not implemented",
|
||||||
|
syscall.ENOSYS, syscall.ENOTRECOVERABLE},
|
||||||
|
|
||||||
|
{"net.OpError", &hst.AppError{Step: "passthrough net.OpError",
|
||||||
|
Err: &net.OpError{Op: "dial", Net: "cat", Err: net.UnknownNetworkError("cat")}},
|
||||||
|
"dial cat: unknown network cat",
|
||||||
|
"cannot dial cat: unknown network cat",
|
||||||
|
net.UnknownNetworkError("cat"), syscall.ENOTRECOVERABLE},
|
||||||
|
|
||||||
|
{"default", &hst.AppError{Step: "initialise container configuration", Err: stub.UniqueError(1)},
|
||||||
|
"unique error 1 injected by the test suite",
|
||||||
|
"cannot initialise container configuration: unique error 1 injected by the test suite",
|
||||||
|
stub.UniqueError(1), os.ErrInvalid},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Run("error", func(t *testing.T) {
|
||||||
|
if got := tc.err.Error(); got != tc.s {
|
||||||
|
t.Errorf("Error: %s, want %s", got, tc.s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("message", func(t *testing.T) {
|
||||||
|
gotMessage, gotMessageOk := container.GetErrorMessage(tc.err)
|
||||||
|
if want := tc.message != "\x00"; gotMessageOk != want {
|
||||||
|
t.Errorf("GetErrorMessage: ok = %v, want %v", gotMessage, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotMessageOk {
|
||||||
|
if gotMessage != tc.message {
|
||||||
|
t.Errorf("GetErrorMessage: %s, want %s", gotMessage, tc.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("is", func(t *testing.T) {
|
||||||
|
if !errors.Is(tc.err, tc.is) {
|
||||||
|
t.Errorf("Is: unexpected false for %v", tc.is)
|
||||||
|
}
|
||||||
|
if errors.Is(tc.err, tc.isF) {
|
||||||
|
t.Errorf("Is: unexpected true for %v", tc.isF)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTemplate(t *testing.T) {
|
func TestTemplate(t *testing.T) {
|
||||||
const want = `{
|
const want = `{
|
||||||
"id": "org.chromium.Chromium",
|
"id": "org.chromium.Chromium",
|
||||||
|
|||||||
@@ -1,61 +1,30 @@
|
|||||||
// Package app defines the generic [App] interface.
|
// Package app implements high-level hakurei container behaviour.
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log"
|
"log"
|
||||||
"syscall"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/app/state"
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/internal/sys"
|
"hakurei.app/internal/sys"
|
||||||
)
|
)
|
||||||
|
|
||||||
type App interface {
|
// Main runs an app according to [hst.Config] and terminates. Main does not return.
|
||||||
// ID returns a copy of [ID] held by App.
|
func Main(ctx context.Context, k sys.State, config *hst.Config) {
|
||||||
ID() state.ID
|
var id state.ID
|
||||||
|
if err := state.NewAppID(&id); err != nil {
|
||||||
// Seal determines the outcome of config as a [SealedApp].
|
log.Fatal(err)
|
||||||
// The value of config might be overwritten and must not be used again.
|
|
||||||
Seal(config *hst.Config) (SealedApp, error)
|
|
||||||
|
|
||||||
String() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type SealedApp interface {
|
|
||||||
// Run commits sealed system setup and starts the app process.
|
|
||||||
Run(rs *RunState) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunState stores the outcome of a call to [SealedApp.Run].
|
|
||||||
type RunState struct {
|
|
||||||
// Time is the exact point in time where the process was created.
|
|
||||||
// Location must be set to UTC.
|
|
||||||
//
|
|
||||||
// Time is nil if no process was ever created.
|
|
||||||
Time *time.Time
|
|
||||||
// RevertErr is stored by the deferred revert call.
|
|
||||||
RevertErr error
|
|
||||||
// WaitErr is the generic error value created by the standard library.
|
|
||||||
WaitErr error
|
|
||||||
|
|
||||||
syscall.WaitStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetStart stores the current time in [RunState] once.
|
|
||||||
func (rs *RunState) SetStart() {
|
|
||||||
if rs.Time != nil {
|
|
||||||
panic("attempted to store time twice")
|
|
||||||
}
|
}
|
||||||
now := time.Now().UTC()
|
|
||||||
rs.Time = &now
|
|
||||||
}
|
|
||||||
|
|
||||||
func MustNew(ctx context.Context, os sys.State) App {
|
var seal outcome
|
||||||
a, err := New(ctx, os)
|
seal.id = &stringPair[state.ID]{id, id.String()}
|
||||||
if err != nil {
|
if err := seal.finalise(ctx, k, config); err != nil {
|
||||||
log.Fatalf("cannot create app: %v", err)
|
printMessageError("cannot seal app:", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
return a
|
|
||||||
|
seal.main()
|
||||||
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"hakurei.app/hst"
|
|
||||||
"hakurei.app/internal/app/state"
|
|
||||||
"hakurei.app/internal/sys"
|
|
||||||
)
|
|
||||||
|
|
||||||
func New(ctx context.Context, os sys.State) (App, error) {
|
|
||||||
a := new(app)
|
|
||||||
a.sys = os
|
|
||||||
a.ctx = ctx
|
|
||||||
|
|
||||||
id := new(state.ID)
|
|
||||||
err := state.NewAppID(id)
|
|
||||||
a.id = newID(id)
|
|
||||||
|
|
||||||
return a, err
|
|
||||||
}
|
|
||||||
|
|
||||||
type app struct {
|
|
||||||
id *stringPair[state.ID]
|
|
||||||
sys sys.State
|
|
||||||
ctx context.Context
|
|
||||||
|
|
||||||
*outcome
|
|
||||||
mu sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *app) ID() state.ID { a.mu.RLock(); defer a.mu.RUnlock(); return a.id.unwrap() }
|
|
||||||
|
|
||||||
func (a *app) String() string {
|
|
||||||
if a == nil {
|
|
||||||
return "(invalid app)"
|
|
||||||
}
|
|
||||||
|
|
||||||
a.mu.RLock()
|
|
||||||
defer a.mu.RUnlock()
|
|
||||||
|
|
||||||
if a.outcome != nil {
|
|
||||||
if a.outcome.user.uid == nil {
|
|
||||||
return fmt.Sprintf("(sealed app %s with invalid uid)", a.id)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("(sealed app %s as uid %s)", a.id, a.outcome.user.uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("(unsealed app %s)", a.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *app) Seal(config *hst.Config) (SealedApp, error) {
|
|
||||||
a.mu.Lock()
|
|
||||||
defer a.mu.Unlock()
|
|
||||||
|
|
||||||
if a.outcome != nil {
|
|
||||||
panic("app sealed twice")
|
|
||||||
}
|
|
||||||
|
|
||||||
seal := new(outcome)
|
|
||||||
seal.id = a.id
|
|
||||||
err := seal.finalise(a.ctx, a.sys, config)
|
|
||||||
if err == nil {
|
|
||||||
a.outcome = seal
|
|
||||||
}
|
|
||||||
return seal, err
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package app_test
|
package app_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
@@ -74,7 +75,7 @@ var testCasesNixos = []sealTestCase{
|
|||||||
0x4c, 0xf0, 0x73, 0xbd,
|
0x4c, 0xf0, 0x73, 0xbd,
|
||||||
0xb4, 0x6e, 0xb5, 0xc1,
|
0xb4, 0x6e, 0xb5, 0xc1,
|
||||||
},
|
},
|
||||||
system.New(1000001).
|
system.New(context.TODO(), 1000001).
|
||||||
Ensure("/tmp/hakurei.1971", 0711).
|
Ensure("/tmp/hakurei.1971", 0711).
|
||||||
Ensure("/tmp/hakurei.1971/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime", acl.Execute).
|
Ensure("/tmp/hakurei.1971/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime", acl.Execute).
|
||||||
Ensure("/tmp/hakurei.1971/runtime/1", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime/1", acl.Read, acl.Write, acl.Execute).
|
Ensure("/tmp/hakurei.1971/runtime/1", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime/1", acl.Read, acl.Write, acl.Execute).
|
||||||
@@ -134,6 +135,7 @@ var testCasesNixos = []sealTestCase{
|
|||||||
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).
|
||||||
Bind(m("/bin"), m("/bin"), 0).
|
Bind(m("/bin"), m("/bin"), 0).
|
||||||
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).
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package app_test
|
package app_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ var testCasesPd = []sealTestCase{
|
|||||||
0xbd, 0x01, 0x78, 0x0e,
|
0xbd, 0x01, 0x78, 0x0e,
|
||||||
0xb9, 0xa6, 0x07, 0xac,
|
0xb9, 0xa6, 0x07, 0xac,
|
||||||
},
|
},
|
||||||
system.New(1000000).
|
system.New(context.TODO(), 1000000).
|
||||||
Ensure("/tmp/hakurei.1971", 0711).
|
Ensure("/tmp/hakurei.1971", 0711).
|
||||||
Ensure("/tmp/hakurei.1971/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime", acl.Execute).
|
Ensure("/tmp/hakurei.1971/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime", acl.Execute).
|
||||||
Ensure("/tmp/hakurei.1971/runtime/0", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime/0", acl.Read, acl.Write, acl.Execute).
|
Ensure("/tmp/hakurei.1971/runtime/0", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime/0", acl.Read, acl.Write, acl.Execute).
|
||||||
@@ -47,6 +48,7 @@ var testCasesPd = []sealTestCase{
|
|||||||
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).
|
||||||
Bind(m("/dev/kvm"), m("/dev/kvm"), container.BindWritable|container.BindDevice|container.BindOptional).
|
Bind(m("/dev/kvm"), m("/dev/kvm"), container.BindWritable|container.BindDevice|container.BindOptional).
|
||||||
Readonly(m("/var/run/nscd"), 0755).
|
Readonly(m("/var/run/nscd"), 0755).
|
||||||
Etc(m("/etc/"), "4a450b6596d7bc15bd01780eb9a607ac").
|
Etc(m("/etc/"), "4a450b6596d7bc15bd01780eb9a607ac").
|
||||||
@@ -114,7 +116,7 @@ var testCasesPd = []sealTestCase{
|
|||||||
0x82, 0xd4, 0x13, 0x36,
|
0x82, 0xd4, 0x13, 0x36,
|
||||||
0x9b, 0x64, 0xce, 0x7c,
|
0x9b, 0x64, 0xce, 0x7c,
|
||||||
},
|
},
|
||||||
system.New(1000009).
|
system.New(context.TODO(), 1000009).
|
||||||
Ensure("/tmp/hakurei.1971", 0711).
|
Ensure("/tmp/hakurei.1971", 0711).
|
||||||
Ensure("/tmp/hakurei.1971/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime", acl.Execute).
|
Ensure("/tmp/hakurei.1971/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime", acl.Execute).
|
||||||
Ensure("/tmp/hakurei.1971/runtime/9", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime/9", acl.Read, acl.Write, acl.Execute).
|
Ensure("/tmp/hakurei.1971/runtime/9", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime/9", acl.Read, acl.Write, acl.Execute).
|
||||||
@@ -182,6 +184,7 @@ var testCasesPd = []sealTestCase{
|
|||||||
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).
|
||||||
Bind(m("/dev/dri"), m("/dev/dri"), container.BindWritable|container.BindDevice|container.BindOptional).
|
Bind(m("/dev/dri"), m("/dev/dri"), container.BindWritable|container.BindDevice|container.BindOptional).
|
||||||
Bind(m("/dev/kvm"), m("/dev/kvm"), container.BindWritable|container.BindDevice|container.BindOptional).
|
Bind(m("/dev/kvm"), m("/dev/kvm"), container.BindWritable|container.BindDevice|container.BindOptional).
|
||||||
Readonly(m("/var/run/nscd"), 0755).
|
Readonly(m("/var/run/nscd"), 0755).
|
||||||
|
|||||||
@@ -11,18 +11,17 @@ import (
|
|||||||
"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"
|
|
||||||
"hakurei.app/internal/sys"
|
"hakurei.app/internal/sys"
|
||||||
"hakurei.app/system"
|
"hakurei.app/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
type sealTestCase struct {
|
type sealTestCase struct {
|
||||||
name string
|
name string
|
||||||
os sys.State
|
os sys.State
|
||||||
config *hst.Config
|
config *hst.Config
|
||||||
id state.ID
|
id state.ID
|
||||||
wantSys *system.I
|
wantSys *system.I
|
||||||
wantContainer *container.Params
|
wantParams *container.Params
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApp(t *testing.T) {
|
func TestApp(t *testing.T) {
|
||||||
@@ -30,35 +29,27 @@ 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) {
|
||||||
a := app.NewWithID(tc.id, tc.os)
|
t.Run("finalise", func(t *testing.T) {
|
||||||
var (
|
sys, params, err := app.FinaliseIParams(t.Context(), tc.os, tc.config, &tc.id)
|
||||||
gotSys *system.I
|
if err != nil {
|
||||||
gotContainer *container.Params
|
if s, ok := container.GetErrorMessage(err); !ok {
|
||||||
)
|
t.Fatalf("Seal: error = %v", err)
|
||||||
if !t.Run("seal", func(t *testing.T) {
|
} else {
|
||||||
if sa, err := a.Seal(tc.config); err != nil {
|
t.Fatalf("Seal: %s", s)
|
||||||
hlog.PrintBaseError(err, "got generic error:")
|
}
|
||||||
t.Errorf("Seal: error = %v", err)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
gotSys, gotContainer = app.AppIParams(a, sa)
|
|
||||||
}
|
}
|
||||||
}) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("compare sys", func(t *testing.T) {
|
t.Run("sys", func(t *testing.T) {
|
||||||
if !gotSys.Equal(tc.wantSys) {
|
if !sys.Equal(tc.wantSys) {
|
||||||
t.Errorf("Seal: sys = %#v, want %#v",
|
t.Errorf("Seal: sys = %#v, want %#v", sys, tc.wantSys)
|
||||||
gotSys, tc.wantSys)
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("compare params", func(t *testing.T) {
|
t.Run("params", func(t *testing.T) {
|
||||||
if !reflect.DeepEqual(gotContainer, tc.wantContainer) {
|
if !reflect.DeepEqual(params, tc.wantParams) {
|
||||||
t.Errorf("seal: params =\n%s\n, want\n%s",
|
t.Errorf("seal: params =\n%s\n, want\n%s", mustMarshal(params), mustMarshal(tc.wantParams))
|
||||||
mustMarshal(gotContainer), mustMarshal(tc.wantContainer))
|
}
|
||||||
}
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -11,20 +11,18 @@ import (
|
|||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/seccomp"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/hlog"
|
|
||||||
"hakurei.app/internal/sys"
|
"hakurei.app/internal/sys"
|
||||||
"hakurei.app/system/dbus"
|
"hakurei.app/system/dbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// in practice there should be less than 30 entries added by the runtime;
|
// in practice there should be less than 30 system mount points
|
||||||
// allocating slightly more as a margin for future expansion
|
|
||||||
const preallocateOpsCount = 1 << 5
|
const preallocateOpsCount = 1 << 5
|
||||||
|
|
||||||
// newContainer initialises [container.Params] via [hst.ContainerConfig].
|
// newContainer initialises [container.Params] via [hst.ContainerConfig].
|
||||||
// Note that remaining container setup must be queued by the caller.
|
// Note that remaining container setup must be queued by the caller.
|
||||||
func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid *int) (*container.Params, map[string]string, error) {
|
func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid *int) (*container.Params, map[string]string, error) {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return nil, nil, hlog.WrapErr(syscall.EBADE, "invalid container configuration")
|
return nil, nil, newWithMessage("invalid container configuration")
|
||||||
}
|
}
|
||||||
|
|
||||||
params := &container.Params{
|
params := &container.Params{
|
||||||
@@ -67,8 +65,6 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
|
|||||||
}
|
}
|
||||||
|
|
||||||
if s.MapRealUID {
|
if s.MapRealUID {
|
||||||
/* some programs fail to connect to dbus session running as a different uid
|
|
||||||
so this workaround is introduced to map priv-side caller uid in container */
|
|
||||||
params.Uid = os.Getuid()
|
params.Uid = os.Getuid()
|
||||||
*uid = params.Uid
|
*uid = params.Uid
|
||||||
params.Gid = os.Getgid()
|
params.Gid = os.Getgid()
|
||||||
@@ -102,8 +98,11 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
|
|||||||
} else {
|
} else {
|
||||||
params.Bind(container.AbsFHSDev, container.AbsFHSDev, container.BindWritable|container.BindDevice)
|
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;
|
/* retrieve paths and hide them if they're made available in the sandbox;
|
||||||
|
|
||||||
this feature tries to improve user experience of permissive defaults, and
|
this feature tries to improve user experience of permissive defaults, and
|
||||||
to warn about issues in custom configuration; it is NOT a security feature
|
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 */
|
and should not be treated as such, ALWAYS be careful with what you bind */
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"hakurei.app/internal/hlog"
|
|
||||||
)
|
|
||||||
|
|
||||||
func PrintRunStateErr(rs *RunState, runErr error) (code int) {
|
|
||||||
code = rs.ExitStatus()
|
|
||||||
|
|
||||||
if runErr != nil {
|
|
||||||
if rs.Time == nil {
|
|
||||||
hlog.PrintBaseError(runErr, "cannot start app:")
|
|
||||||
} else {
|
|
||||||
var e *hlog.BaseError
|
|
||||||
if !hlog.AsBaseError(runErr, &e) {
|
|
||||||
log.Println("wait failed:", runErr)
|
|
||||||
} else {
|
|
||||||
// Wait only returns either *app.ProcessError or *app.StateStoreError wrapped in a *app.BaseError
|
|
||||||
var se *StateStoreError
|
|
||||||
if !errors.As(runErr, &se) {
|
|
||||||
// does not need special handling
|
|
||||||
log.Print(e.Message())
|
|
||||||
} else {
|
|
||||||
// inner error are either unwrapped store errors
|
|
||||||
// or joined errors returned by *appSealTx revert
|
|
||||||
// wrapped in *app.BaseError
|
|
||||||
var ej RevertCompoundError
|
|
||||||
if !errors.As(se.InnerErr, &ej) {
|
|
||||||
// does not require special handling
|
|
||||||
log.Print(e.Message())
|
|
||||||
} else {
|
|
||||||
errs := ej.Unwrap()
|
|
||||||
|
|
||||||
// every error here is wrapped in *app.BaseError
|
|
||||||
for _, ei := range errs {
|
|
||||||
var eb *hlog.BaseError
|
|
||||||
if !errors.As(ei, &eb) {
|
|
||||||
// unreachable
|
|
||||||
log.Println("invalid error type returned by revert:", ei)
|
|
||||||
} else {
|
|
||||||
// print inner *app.BaseError message
|
|
||||||
log.Print(eb.Message())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if code == 0 {
|
|
||||||
code = 126
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if rs.RevertErr != nil {
|
|
||||||
var stateStoreError *StateStoreError
|
|
||||||
if !errors.As(rs.RevertErr, &stateStoreError) || stateStoreError == nil {
|
|
||||||
hlog.PrintBaseError(rs.RevertErr, "generic fault during cleanup:")
|
|
||||||
goto out
|
|
||||||
}
|
|
||||||
|
|
||||||
if stateStoreError.Err != nil {
|
|
||||||
if len(stateStoreError.Err) == 2 {
|
|
||||||
if stateStoreError.Err[0] != nil {
|
|
||||||
if joinedErrs, ok := stateStoreError.Err[0].(interface{ Unwrap() []error }); !ok {
|
|
||||||
hlog.PrintBaseError(stateStoreError.Err[0], "generic fault during revert:")
|
|
||||||
} else {
|
|
||||||
for _, err := range joinedErrs.Unwrap() {
|
|
||||||
if err != nil {
|
|
||||||
hlog.PrintBaseError(err, "fault during revert:")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if stateStoreError.Err[1] != nil {
|
|
||||||
log.Printf("cannot close store: %v", stateStoreError.Err[1])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Printf("fault during cleanup: %v",
|
|
||||||
errors.Join(stateStoreError.Err...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if stateStoreError.OpErr != nil {
|
|
||||||
log.Printf("blind revert due to store fault: %v",
|
|
||||||
stateStoreError.OpErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if stateStoreError.DoErr != nil {
|
|
||||||
hlog.PrintBaseError(stateStoreError.DoErr, "state store operation unsuccessful:")
|
|
||||||
}
|
|
||||||
|
|
||||||
if stateStoreError.Inner && stateStoreError.InnerErr != nil {
|
|
||||||
hlog.PrintBaseError(stateStoreError.InnerErr, "cannot destroy state entry:")
|
|
||||||
}
|
|
||||||
|
|
||||||
out:
|
|
||||||
if code == 0 {
|
|
||||||
code = 128
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if rs.WaitErr != nil {
|
|
||||||
hlog.Verbosef("wait: %v", rs.WaitErr)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// StateStoreError is returned for a failed state save
|
|
||||||
type StateStoreError struct {
|
|
||||||
// whether inner function was called
|
|
||||||
Inner bool
|
|
||||||
// returned by the Save/Destroy method of [state.Cursor]
|
|
||||||
InnerErr error
|
|
||||||
// returned by the Do method of [state.Store]
|
|
||||||
DoErr error
|
|
||||||
// stores an arbitrary store operation error
|
|
||||||
OpErr error
|
|
||||||
// stores arbitrary errors
|
|
||||||
Err []error
|
|
||||||
}
|
|
||||||
|
|
||||||
// save saves arbitrary errors in [StateStoreError] once.
|
|
||||||
func (e *StateStoreError) save(errs ...error) {
|
|
||||||
if len(errs) == 0 || e.Err != nil {
|
|
||||||
panic("invalid call to save")
|
|
||||||
}
|
|
||||||
e.Err = errs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *StateStoreError) equiv(a ...any) error {
|
|
||||||
if e.Inner && e.InnerErr == nil && e.DoErr == nil && e.OpErr == nil && errors.Join(e.Err...) == nil {
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return hlog.WrapErrSuffix(e, a...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *StateStoreError) Error() string {
|
|
||||||
if e.Inner && e.InnerErr != nil {
|
|
||||||
return e.InnerErr.Error()
|
|
||||||
}
|
|
||||||
if e.DoErr != nil {
|
|
||||||
return e.DoErr.Error()
|
|
||||||
}
|
|
||||||
if e.OpErr != nil {
|
|
||||||
return e.OpErr.Error()
|
|
||||||
}
|
|
||||||
if err := errors.Join(e.Err...); err != nil {
|
|
||||||
return err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
// equiv nullifies e for values where this is reached
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *StateStoreError) Unwrap() (errs []error) {
|
|
||||||
errs = make([]error, 0, 3)
|
|
||||||
if e.InnerErr != nil {
|
|
||||||
errs = append(errs, e.InnerErr)
|
|
||||||
}
|
|
||||||
if e.DoErr != nil {
|
|
||||||
errs = append(errs, e.DoErr)
|
|
||||||
}
|
|
||||||
if e.OpErr != nil {
|
|
||||||
errs = append(errs, e.OpErr)
|
|
||||||
}
|
|
||||||
if err := errors.Join(e.Err...); err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// A RevertCompoundError encapsulates errors returned by
|
|
||||||
// the Revert method of [system.I].
|
|
||||||
type RevertCompoundError interface {
|
|
||||||
Error() string
|
|
||||||
Unwrap() []error
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"hakurei.app/container"
|
|
||||||
"hakurei.app/internal/app/state"
|
|
||||||
"hakurei.app/internal/sys"
|
|
||||||
"hakurei.app/system"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewWithID(id state.ID, os sys.State) App {
|
|
||||||
a := new(app)
|
|
||||||
a.id = newID(&id)
|
|
||||||
a.sys = os
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
func AppIParams(a App, sa SealedApp) (*system.I, *container.Params) {
|
|
||||||
v := a.(*app)
|
|
||||||
seal := sa.(*outcome)
|
|
||||||
if v.outcome != seal || v.id != seal.id {
|
|
||||||
panic("broken app/outcome link")
|
|
||||||
}
|
|
||||||
return seal.sys, seal.container
|
|
||||||
}
|
|
||||||
16
internal/app/export_test.go
Normal file
16
internal/app/export_test.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/internal/app/state"
|
||||||
|
"hakurei.app/internal/sys"
|
||||||
|
"hakurei.app/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FinaliseIParams(ctx context.Context, k sys.State, config *hst.Config, id *state.ID) (*system.I, *container.Params, error) {
|
||||||
|
seal := outcome{id: &stringPair[state.ID]{*id, id.String()}}
|
||||||
|
return seal.sys, seal.container, seal.finalise(ctx, k, config)
|
||||||
|
}
|
||||||
339
internal/app/process.go
Normal file
339
internal/app/process.go
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/gob"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/internal"
|
||||||
|
"hakurei.app/internal/app/state"
|
||||||
|
"hakurei.app/internal/hlog"
|
||||||
|
"hakurei.app/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
// duration to wait for shim to exit, after container WaitDelay has elapsed.
|
||||||
|
const shimWaitTimeout = 5 * time.Second
|
||||||
|
|
||||||
|
// mainState holds persistent state bound to outcome.main.
|
||||||
|
type mainState struct {
|
||||||
|
// done is whether beforeExit has been called already.
|
||||||
|
done bool
|
||||||
|
|
||||||
|
// Time is the exact point in time where the process was created.
|
||||||
|
// Location must be set to UTC.
|
||||||
|
//
|
||||||
|
// Time is nil if no process was ever created.
|
||||||
|
Time *time.Time
|
||||||
|
|
||||||
|
seal *outcome
|
||||||
|
store state.Store
|
||||||
|
cancel context.CancelFunc
|
||||||
|
cmd *exec.Cmd
|
||||||
|
cmdWait chan error
|
||||||
|
|
||||||
|
uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// mainNeedsRevert indicates the call to Commit has succeeded.
|
||||||
|
mainNeedsRevert uintptr = 1 << iota
|
||||||
|
// mainNeedsDestroy indicates the instance state entry is present in the store.
|
||||||
|
mainNeedsDestroy
|
||||||
|
)
|
||||||
|
|
||||||
|
// beforeExit must be called immediately before a call to [os.Exit].
|
||||||
|
func (ms mainState) beforeExit(isFault bool) {
|
||||||
|
if ms.done {
|
||||||
|
panic("attempting to call beforeExit twice")
|
||||||
|
}
|
||||||
|
ms.done = true
|
||||||
|
defer hlog.BeforeExit()
|
||||||
|
|
||||||
|
if isFault && ms.cancel != nil {
|
||||||
|
ms.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasErr bool
|
||||||
|
// updates hasErr but does not terminate
|
||||||
|
perror := func(err error, message string) {
|
||||||
|
hasErr = true
|
||||||
|
printMessageError("cannot "+message+":", err)
|
||||||
|
}
|
||||||
|
exitCode := 1
|
||||||
|
defer func() {
|
||||||
|
if hasErr {
|
||||||
|
os.Exit(exitCode)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// this also handles wait for a non-fault termination
|
||||||
|
if ms.cmd != nil && ms.cmdWait != nil {
|
||||||
|
waitDone := make(chan struct{})
|
||||||
|
// TODO(ophestra): enforce this limit early so it does not have to be done twice
|
||||||
|
shimTimeoutCompensated := shimWaitTimeout
|
||||||
|
if ms.seal.waitDelay > MaxShimWaitDelay {
|
||||||
|
shimTimeoutCompensated += MaxShimWaitDelay
|
||||||
|
} else {
|
||||||
|
shimTimeoutCompensated += ms.seal.waitDelay
|
||||||
|
}
|
||||||
|
// this ties waitDone to ctx with the additional compensated timeout duration
|
||||||
|
go func() { <-ms.seal.ctx.Done(); time.Sleep(shimTimeoutCompensated); close(waitDone) }()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-ms.cmdWait:
|
||||||
|
wstatus, ok := ms.cmd.ProcessState.Sys().(syscall.WaitStatus)
|
||||||
|
if ok {
|
||||||
|
if v := wstatus.ExitStatus(); v != 0 {
|
||||||
|
hasErr = true
|
||||||
|
exitCode = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hlog.Load() {
|
||||||
|
if !ok {
|
||||||
|
if err != nil {
|
||||||
|
hlog.Verbosef("wait: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch {
|
||||||
|
case wstatus.Exited():
|
||||||
|
hlog.Verbosef("process %d exited with code %d", ms.cmd.Process.Pid, wstatus.ExitStatus())
|
||||||
|
|
||||||
|
case wstatus.CoreDump():
|
||||||
|
hlog.Verbosef("process %d dumped core", ms.cmd.Process.Pid)
|
||||||
|
|
||||||
|
case wstatus.Signaled():
|
||||||
|
hlog.Verbosef("process %d got %s", ms.cmd.Process.Pid, wstatus.Signal())
|
||||||
|
|
||||||
|
default:
|
||||||
|
hlog.Verbosef("process %d exited with status %#x", ms.cmd.Process.Pid, wstatus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-waitDone:
|
||||||
|
hlog.Resume()
|
||||||
|
// this is only reachable when shim did not exit within shimWaitTimeout, after its WaitDelay has elapsed.
|
||||||
|
// This is different from the container failing to terminate within its timeout period, as that is enforced
|
||||||
|
// by the shim. This path is instead reached when there is a lockup in shim preventing it from completing.
|
||||||
|
log.Printf("process %d did not terminate", ms.cmd.Process.Pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
hlog.Resume()
|
||||||
|
if ms.seal.sync != nil {
|
||||||
|
if err := ms.seal.sync.Close(); err != nil {
|
||||||
|
perror(err, "close wayland security context")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ms.seal.dbusMsg != nil {
|
||||||
|
ms.seal.dbusMsg()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ms.uintptr&mainNeedsRevert != 0 {
|
||||||
|
if ok, err := ms.store.Do(ms.seal.user.identity.unwrap(), func(c state.Cursor) {
|
||||||
|
if ms.uintptr&mainNeedsDestroy != 0 {
|
||||||
|
if err := c.Destroy(ms.seal.id.unwrap()); err != nil {
|
||||||
|
perror(err, "destroy state entry")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var rt system.Enablement
|
||||||
|
if states, err := c.Load(); err != nil {
|
||||||
|
// it is impossible to continue from this point;
|
||||||
|
// revert per-process state here to limit damage
|
||||||
|
ec := system.Process
|
||||||
|
if revertErr := ms.seal.sys.Revert((*system.Criteria)(&ec)); revertErr != nil {
|
||||||
|
var joinError interface {
|
||||||
|
Unwrap() []error
|
||||||
|
error
|
||||||
|
}
|
||||||
|
if !errors.As(revertErr, &joinError) || joinError == nil {
|
||||||
|
perror(revertErr, "revert system setup")
|
||||||
|
} else {
|
||||||
|
for _, v := range joinError.Unwrap() {
|
||||||
|
perror(v, "revert system setup step")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
perror(err, "load instance states")
|
||||||
|
} else {
|
||||||
|
ec := system.Process
|
||||||
|
if l := len(states); l == 0 {
|
||||||
|
ec |= system.User
|
||||||
|
} else {
|
||||||
|
hlog.Verbosef("found %d instances, cleaning up without user-scoped operations", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// accumulate enablements of remaining launchers
|
||||||
|
for i, s := range states {
|
||||||
|
if s.Config != nil {
|
||||||
|
rt |= s.Config.Enablements.Unwrap()
|
||||||
|
} else {
|
||||||
|
log.Printf("state entry %d does not contain config", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ec |= rt ^ (system.EWayland | system.EX11 | system.EDBus | system.EPulse)
|
||||||
|
if hlog.Load() {
|
||||||
|
if ec > 0 {
|
||||||
|
hlog.Verbose("reverting operations scope", system.TypeString(ec))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ms.seal.sys.Revert((*system.Criteria)(&ec)); err != nil {
|
||||||
|
perror(err, "revert system setup")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}); err != nil {
|
||||||
|
if ok {
|
||||||
|
perror(err, "unlock state store")
|
||||||
|
} else {
|
||||||
|
perror(err, "open state store")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ms.uintptr&mainNeedsDestroy != 0 {
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ms.store != nil {
|
||||||
|
if err := ms.store.Close(); err != nil {
|
||||||
|
perror(err, "close state store")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fatal calls printMessageError, performs necessary cleanup, followed by a call to [os.Exit](1).
|
||||||
|
func (ms mainState) fatal(fallback string, ferr error) {
|
||||||
|
printMessageError(fallback, ferr)
|
||||||
|
ms.beforeExit(true)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// main carries out outcome and terminates. main does not return.
|
||||||
|
func (seal *outcome) main() {
|
||||||
|
if !seal.f.CompareAndSwap(false, true) {
|
||||||
|
panic("outcome: attempted to run twice")
|
||||||
|
}
|
||||||
|
|
||||||
|
// read comp value early for early failure
|
||||||
|
hsuPath := internal.MustHsuPath()
|
||||||
|
|
||||||
|
// ms.beforeExit required beyond this point
|
||||||
|
ms := &mainState{seal: seal}
|
||||||
|
|
||||||
|
if err := seal.sys.Commit(); err != nil {
|
||||||
|
ms.fatal("cannot commit system setup:", err)
|
||||||
|
}
|
||||||
|
ms.uintptr |= mainNeedsRevert
|
||||||
|
ms.store = state.NewMulti(seal.runDirPath.String())
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(seal.ctx)
|
||||||
|
defer cancel()
|
||||||
|
ms.cancel = cancel
|
||||||
|
|
||||||
|
ms.cmd = exec.CommandContext(ctx, hsuPath)
|
||||||
|
ms.cmd.Stdin, ms.cmd.Stdout, ms.cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||||
|
ms.cmd.Dir = container.FHSRoot // container init enters final working directory
|
||||||
|
// shim runs in the same session as monitor; see shim.go for behaviour
|
||||||
|
ms.cmd.Cancel = func() error { return ms.cmd.Process.Signal(syscall.SIGCONT) }
|
||||||
|
|
||||||
|
var e *gob.Encoder
|
||||||
|
if fd, encoder, err := container.Setup(&ms.cmd.ExtraFiles); err != nil {
|
||||||
|
ms.fatal("cannot create shim setup pipe:", err)
|
||||||
|
} else {
|
||||||
|
e = encoder
|
||||||
|
ms.cmd.Env = []string{
|
||||||
|
// passed through to shim by hsu
|
||||||
|
shimEnv + "=" + strconv.Itoa(fd),
|
||||||
|
// interpreted by hsu
|
||||||
|
"HAKUREI_APP_ID=" + seal.user.identity.String(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(seal.user.supp) > 0 {
|
||||||
|
hlog.Verbosef("attaching supplementary group ids %s", seal.user.supp)
|
||||||
|
// interpreted by hsu
|
||||||
|
ms.cmd.Env = append(ms.cmd.Env, "HAKUREI_GROUPS="+strings.Join(seal.user.supp, " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
hlog.Verbosef("setuid helper at %s", hsuPath)
|
||||||
|
hlog.Suspend()
|
||||||
|
if err := ms.cmd.Start(); err != nil {
|
||||||
|
ms.fatal("cannot start setuid wrapper:", err)
|
||||||
|
}
|
||||||
|
startTime := time.Now().UTC()
|
||||||
|
ms.cmdWait = make(chan error, 1)
|
||||||
|
// this ties context back to the life of the process
|
||||||
|
go func() { ms.cmdWait <- ms.cmd.Wait(); cancel() }()
|
||||||
|
ms.Time = &startTime
|
||||||
|
|
||||||
|
// unfortunately the I/O here cannot be directly canceled;
|
||||||
|
// the cancellation path leads to fatal in this case so that is fine
|
||||||
|
select {
|
||||||
|
case err := <-func() (setupErr chan error) {
|
||||||
|
setupErr = make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
setupErr <- e.Encode(&shimParams{
|
||||||
|
os.Getpid(),
|
||||||
|
seal.waitDelay,
|
||||||
|
seal.container,
|
||||||
|
hlog.Load(),
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
return
|
||||||
|
}():
|
||||||
|
if err != nil {
|
||||||
|
hlog.Resume()
|
||||||
|
ms.fatal("cannot transmit shim config:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
hlog.Resume()
|
||||||
|
ms.fatal("shim context canceled:", newWithMessageError("shim setup canceled", ctx.Err()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// shim accepted setup payload, create process state
|
||||||
|
if ok, err := ms.store.Do(seal.user.identity.unwrap(), func(c state.Cursor) {
|
||||||
|
if err := c.Save(&state.State{
|
||||||
|
ID: seal.id.unwrap(),
|
||||||
|
PID: ms.cmd.Process.Pid,
|
||||||
|
Time: *ms.Time,
|
||||||
|
}, seal.ct); err != nil {
|
||||||
|
ms.fatal("cannot save state entry:", err)
|
||||||
|
}
|
||||||
|
}); err != nil {
|
||||||
|
if ok {
|
||||||
|
ms.uintptr |= mainNeedsDestroy
|
||||||
|
ms.fatal("cannot unlock state store:", err)
|
||||||
|
} else {
|
||||||
|
ms.fatal("cannot open state store:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// state in store at this point, destroy defunct state entry on termination
|
||||||
|
ms.uintptr |= mainNeedsDestroy
|
||||||
|
|
||||||
|
// beforeExit ties shim process to context
|
||||||
|
ms.beforeExit(false)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// printMessageError prints the error message according to [container.GetErrorMessage],
|
||||||
|
// or fallback prepended to err if an error message is not available.
|
||||||
|
func printMessageError(fallback string, err error) {
|
||||||
|
m, ok := container.GetErrorMessage(err)
|
||||||
|
if !ok {
|
||||||
|
log.Println(fallback, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Print(m)
|
||||||
|
}
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/gob"
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"hakurei.app/container"
|
|
||||||
"hakurei.app/internal"
|
|
||||||
"hakurei.app/internal/app/state"
|
|
||||||
"hakurei.app/internal/hlog"
|
|
||||||
"hakurei.app/system"
|
|
||||||
)
|
|
||||||
|
|
||||||
const shimWaitTimeout = 5 * time.Second
|
|
||||||
|
|
||||||
func (seal *outcome) Run(rs *RunState) error {
|
|
||||||
if !seal.f.CompareAndSwap(false, true) {
|
|
||||||
// run does much more than just starting a process; calling it twice, even if the first call fails, will result
|
|
||||||
// in inconsistent state that is impossible to clean up; return here to limit damage and hopefully give the
|
|
||||||
// other Run a chance to return
|
|
||||||
return errors.New("outcome: attempted to run twice")
|
|
||||||
}
|
|
||||||
|
|
||||||
if rs == nil {
|
|
||||||
panic("invalid state")
|
|
||||||
}
|
|
||||||
|
|
||||||
// read comp value early to allow for early failure
|
|
||||||
hsuPath := internal.MustHsuPath()
|
|
||||||
|
|
||||||
if err := seal.sys.Commit(seal.ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store := state.NewMulti(seal.runDirPath.String())
|
|
||||||
deferredStoreFunc := func(c state.Cursor) error { return nil } // noop until state in store
|
|
||||||
defer func() {
|
|
||||||
var revertErr error
|
|
||||||
storeErr := new(StateStoreError)
|
|
||||||
storeErr.Inner, storeErr.DoErr = store.Do(seal.user.aid.unwrap(), func(c state.Cursor) {
|
|
||||||
revertErr = func() error {
|
|
||||||
storeErr.InnerErr = deferredStoreFunc(c)
|
|
||||||
|
|
||||||
var rt system.Enablement
|
|
||||||
ec := system.Process
|
|
||||||
if states, err := c.Load(); err != nil {
|
|
||||||
// revert per-process state here to limit damage
|
|
||||||
storeErr.OpErr = err
|
|
||||||
return seal.sys.Revert((*system.Criteria)(&ec))
|
|
||||||
} else {
|
|
||||||
if l := len(states); l == 0 {
|
|
||||||
ec |= system.User
|
|
||||||
} else {
|
|
||||||
hlog.Verbosef("found %d instances, cleaning up without user-scoped operations", l)
|
|
||||||
}
|
|
||||||
|
|
||||||
// accumulate enablements of remaining launchers
|
|
||||||
for i, s := range states {
|
|
||||||
if s.Config != nil {
|
|
||||||
rt |= s.Config.Enablements.Unwrap()
|
|
||||||
} else {
|
|
||||||
log.Printf("state entry %d does not contain config", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ec |= rt ^ (system.EWayland | system.EX11 | system.EDBus | system.EPulse)
|
|
||||||
if hlog.Load() {
|
|
||||||
if ec > 0 {
|
|
||||||
hlog.Verbose("reverting operations scope", system.TypeString(ec))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return seal.sys.Revert((*system.Criteria)(&ec))
|
|
||||||
}()
|
|
||||||
})
|
|
||||||
storeErr.save(revertErr, store.Close())
|
|
||||||
rs.RevertErr = storeErr.equiv("error during cleanup:")
|
|
||||||
}()
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(seal.ctx)
|
|
||||||
defer cancel()
|
|
||||||
cmd := exec.CommandContext(ctx, hsuPath)
|
|
||||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
|
||||||
cmd.Dir = container.FHSRoot // container init enters final working directory
|
|
||||||
// shim runs in the same session as monitor; see shim.go for behaviour
|
|
||||||
cmd.Cancel = func() error { return cmd.Process.Signal(syscall.SIGCONT) }
|
|
||||||
|
|
||||||
var e *gob.Encoder
|
|
||||||
if fd, encoder, err := container.Setup(&cmd.ExtraFiles); err != nil {
|
|
||||||
return hlog.WrapErrSuffix(err,
|
|
||||||
"cannot create shim setup pipe:")
|
|
||||||
} else {
|
|
||||||
e = encoder
|
|
||||||
cmd.Env = []string{
|
|
||||||
// passed through to shim by hsu
|
|
||||||
shimEnv + "=" + strconv.Itoa(fd),
|
|
||||||
// interpreted by hsu
|
|
||||||
"HAKUREI_APP_ID=" + seal.user.aid.String(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(seal.user.supp) > 0 {
|
|
||||||
hlog.Verbosef("attaching supplementary group ids %s", seal.user.supp)
|
|
||||||
// interpreted by hsu
|
|
||||||
cmd.Env = append(cmd.Env, "HAKUREI_GROUPS="+strings.Join(seal.user.supp, " "))
|
|
||||||
}
|
|
||||||
|
|
||||||
hlog.Verbosef("setuid helper at %s", hsuPath)
|
|
||||||
hlog.Suspend()
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
return hlog.WrapErrSuffix(err,
|
|
||||||
"cannot start setuid wrapper:")
|
|
||||||
}
|
|
||||||
rs.SetStart()
|
|
||||||
|
|
||||||
// this prevents blocking forever on an early failure
|
|
||||||
waitErr, setupErr := make(chan error, 1), make(chan error, 1)
|
|
||||||
go func() { waitErr <- cmd.Wait(); cancel() }()
|
|
||||||
go func() {
|
|
||||||
setupErr <- e.Encode(&shimParams{
|
|
||||||
os.Getpid(),
|
|
||||||
seal.waitDelay,
|
|
||||||
seal.container,
|
|
||||||
hlog.Load(),
|
|
||||||
})
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case err := <-setupErr:
|
|
||||||
if err != nil {
|
|
||||||
hlog.Resume()
|
|
||||||
return hlog.WrapErrSuffix(err,
|
|
||||||
"cannot transmit shim config:")
|
|
||||||
}
|
|
||||||
|
|
||||||
case <-ctx.Done():
|
|
||||||
hlog.Resume()
|
|
||||||
return hlog.WrapErr(syscall.ECANCELED,
|
|
||||||
"shim setup canceled")
|
|
||||||
}
|
|
||||||
|
|
||||||
// returned after blocking on waitErr
|
|
||||||
var earlyStoreErr = new(StateStoreError)
|
|
||||||
{
|
|
||||||
// shim accepted setup payload, create process state
|
|
||||||
sd := state.State{
|
|
||||||
ID: seal.id.unwrap(),
|
|
||||||
PID: cmd.Process.Pid,
|
|
||||||
Time: *rs.Time,
|
|
||||||
}
|
|
||||||
earlyStoreErr.Inner, earlyStoreErr.DoErr = store.Do(seal.user.aid.unwrap(), func(c state.Cursor) {
|
|
||||||
earlyStoreErr.InnerErr = c.Save(&sd, seal.ct)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// state in store at this point, destroy defunct state entry on return
|
|
||||||
deferredStoreFunc = func(c state.Cursor) error { return c.Destroy(seal.id.unwrap()) }
|
|
||||||
|
|
||||||
waitTimeout := make(chan struct{})
|
|
||||||
go func() { <-seal.ctx.Done(); time.Sleep(shimWaitTimeout); close(waitTimeout) }()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case rs.WaitErr = <-waitErr:
|
|
||||||
rs.WaitStatus = cmd.ProcessState.Sys().(syscall.WaitStatus)
|
|
||||||
if hlog.Load() {
|
|
||||||
switch {
|
|
||||||
case rs.Exited():
|
|
||||||
hlog.Verbosef("process %d exited with code %d", cmd.Process.Pid, rs.ExitStatus())
|
|
||||||
case rs.CoreDump():
|
|
||||||
hlog.Verbosef("process %d dumped core", cmd.Process.Pid)
|
|
||||||
case rs.Signaled():
|
|
||||||
hlog.Verbosef("process %d got %s", cmd.Process.Pid, rs.Signal())
|
|
||||||
default:
|
|
||||||
hlog.Verbosef("process %d exited with status %#x", cmd.Process.Pid, rs.WaitStatus)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case <-waitTimeout:
|
|
||||||
rs.WaitErr = syscall.ETIMEDOUT
|
|
||||||
hlog.Resume()
|
|
||||||
log.Printf("process %d did not terminate", cmd.Process.Pid)
|
|
||||||
}
|
|
||||||
|
|
||||||
hlog.Resume()
|
|
||||||
if seal.sync != nil {
|
|
||||||
if err := seal.sync.Close(); err != nil {
|
|
||||||
log.Printf("cannot close wayland security context: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if seal.dbusMsg != nil {
|
|
||||||
seal.dbusMsg()
|
|
||||||
}
|
|
||||||
|
|
||||||
return earlyStoreErr.equiv("cannot save process state:")
|
|
||||||
}
|
|
||||||
@@ -9,8 +9,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"os/user"
|
||||||
"regexp"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -20,7 +19,6 @@ import (
|
|||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal"
|
|
||||||
"hakurei.app/internal/app/state"
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/internal/hlog"
|
"hakurei.app/internal/hlog"
|
||||||
"hakurei.app/internal/sys"
|
"hakurei.app/internal/sys"
|
||||||
@@ -30,39 +28,12 @@ import (
|
|||||||
"hakurei.app/system/wayland"
|
"hakurei.app/system/wayland"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
func newWithMessage(msg string) error { return newWithMessageError(msg, os.ErrInvalid) }
|
||||||
home = "HOME"
|
func newWithMessageError(msg string, err error) error {
|
||||||
shell = "SHELL"
|
return &hst.AppError{Step: "finalise", Err: err, Msg: msg}
|
||||||
|
}
|
||||||
|
|
||||||
xdgConfigHome = "XDG_CONFIG_HOME"
|
// An outcome is the runnable state of a hakurei container via [hst.Config].
|
||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrIdent = errors.New("invalid identity")
|
|
||||||
ErrName = errors.New("invalid username")
|
|
||||||
|
|
||||||
ErrXDisplay = errors.New(display + " unset")
|
|
||||||
|
|
||||||
ErrPulseCookie = errors.New("pulse cookie not present")
|
|
||||||
ErrPulseSocket = errors.New("pulse socket not present")
|
|
||||||
ErrPulseMode = errors.New("unexpected pulse socket mode")
|
|
||||||
)
|
|
||||||
|
|
||||||
var posixUsername = regexp.MustCompilePOSIX("^[a-z_]([A-Za-z0-9_-]{0,31}|[A-Za-z0-9_-]{0,30}\\$)$")
|
|
||||||
|
|
||||||
// outcome stores copies of various parts of [hst.Config]
|
|
||||||
type outcome struct {
|
type outcome struct {
|
||||||
// copied from initialising [app]
|
// copied from initialising [app]
|
||||||
id *stringPair[state.ID]
|
id *stringPair[state.ID]
|
||||||
@@ -136,8 +107,7 @@ func (share *shareHost) runtime() *container.Absolute {
|
|||||||
|
|
||||||
// hsuUser stores post-hsu credentials and metadata
|
// hsuUser stores post-hsu credentials and metadata
|
||||||
type hsuUser struct {
|
type hsuUser struct {
|
||||||
// identity
|
identity *stringPair[int]
|
||||||
aid *stringPair[int]
|
|
||||||
// target uid resolved by hid:aid
|
// target uid resolved by hid:aid
|
||||||
uid *stringPair[int]
|
uid *stringPair[int]
|
||||||
|
|
||||||
@@ -150,57 +120,80 @@ type hsuUser struct {
|
|||||||
username string
|
username string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Config) error {
|
func (seal *outcome) finalise(ctx context.Context, k sys.State, 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
|
||||||
|
panic("invalid call to finalise")
|
||||||
|
}
|
||||||
if seal.ctx != nil {
|
if seal.ctx != nil {
|
||||||
panic("finalise called twice")
|
// unreachable
|
||||||
|
panic("attempting to finalise twice")
|
||||||
}
|
}
|
||||||
seal.ctx = ctx
|
seal.ctx = ctx
|
||||||
|
|
||||||
if config == nil {
|
if config == nil {
|
||||||
return hlog.WrapErr(syscall.EINVAL, syscall.EINVAL.Error())
|
// unreachable
|
||||||
|
return newWithMessage("invalid configuration")
|
||||||
}
|
}
|
||||||
if config.Home == nil {
|
if config.Home == nil {
|
||||||
return hlog.WrapErr(os.ErrInvalid, "invalid path to home directory")
|
return newWithMessage("invalid path to home directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
// encode initial configuration for state tracking
|
// encode initial configuration for state tracking
|
||||||
ct := new(bytes.Buffer)
|
ct := new(bytes.Buffer)
|
||||||
if err := gob.NewEncoder(ct).Encode(config); err != nil {
|
if err := gob.NewEncoder(ct).Encode(config); err != nil {
|
||||||
return hlog.WrapErrSuffix(err,
|
return &hst.AppError{Step: "encode initial config", Err: err}
|
||||||
"cannot encode initial config:")
|
|
||||||
}
|
}
|
||||||
seal.ct = ct
|
seal.ct = ct
|
||||||
}
|
}
|
||||||
|
|
||||||
// allowed aid range 0 to 9999, this is checked again in hsu
|
// allowed identity range 0 to 9999, this is checked again in hsu
|
||||||
if config.Identity < 0 || config.Identity > 9999 {
|
if config.Identity < 0 || config.Identity > 9999 {
|
||||||
return hlog.WrapErr(ErrIdent,
|
return newWithMessage(fmt.Sprintf("identity %d out of range", config.Identity))
|
||||||
fmt.Sprintf("identity %d out of range", config.Identity))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
seal.user = hsuUser{
|
seal.user = hsuUser{
|
||||||
aid: newInt(config.Identity),
|
identity: newInt(config.Identity),
|
||||||
home: config.Home,
|
home: config.Home,
|
||||||
username: config.Username,
|
username: config.Username,
|
||||||
}
|
}
|
||||||
|
|
||||||
if seal.user.username == "" {
|
if seal.user.username == "" {
|
||||||
seal.user.username = "chronos"
|
seal.user.username = "chronos"
|
||||||
} else if !posixUsername.MatchString(seal.user.username) ||
|
} else if !isValidUsername(seal.user.username) {
|
||||||
len(seal.user.username) >= internal.Sysconf(internal.SC_LOGIN_NAME_MAX) {
|
return newWithMessage(fmt.Sprintf("invalid user name %q", seal.user.username))
|
||||||
return hlog.WrapErr(ErrName,
|
|
||||||
fmt.Sprintf("invalid user name %q", seal.user.username))
|
|
||||||
}
|
|
||||||
if u, err := sys.Uid(seal.user.aid.unwrap()); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
seal.user.uid = newInt(u)
|
|
||||||
}
|
}
|
||||||
|
seal.user.uid = newInt(sys.MustUid(k, seal.user.identity.unwrap()))
|
||||||
|
|
||||||
seal.user.supp = make([]string, len(config.Groups))
|
seal.user.supp = make([]string, len(config.Groups))
|
||||||
for i, name := range config.Groups {
|
for i, name := range config.Groups {
|
||||||
if g, err := sys.LookupGroup(name); err != nil {
|
if g, err := k.LookupGroup(name); err != nil {
|
||||||
return hlog.WrapErr(err,
|
var unknownGroupError user.UnknownGroupError
|
||||||
fmt.Sprintf("unknown group %q", name))
|
if errors.As(err, &unknownGroupError) {
|
||||||
|
return newWithMessageError(fmt.Sprintf("unknown group %q", name), unknownGroupError)
|
||||||
|
} else {
|
||||||
|
return &hst.AppError{Step: "look up group by name", Err: err}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
seal.user.supp[i] = g.Gid
|
seal.user.supp[i] = g.Gid
|
||||||
}
|
}
|
||||||
@@ -212,7 +205,7 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
|
|
||||||
if config.Shell == nil {
|
if config.Shell == nil {
|
||||||
config.Shell = container.AbsFHSRoot.Append("bin", "sh")
|
config.Shell = container.AbsFHSRoot.Append("bin", "sh")
|
||||||
s, _ := sys.LookupEnv(shell)
|
s, _ := k.LookupEnv(shell)
|
||||||
if a, err := container.NewAbs(s); err == nil {
|
if a, err := container.NewAbs(s); err == nil {
|
||||||
config.Shell = a
|
config.Shell = a
|
||||||
}
|
}
|
||||||
@@ -221,10 +214,10 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
// hsu clears the environment so resolve paths early
|
// hsu clears the environment so resolve paths early
|
||||||
if config.Path == nil {
|
if config.Path == nil {
|
||||||
if len(config.Args) > 0 {
|
if len(config.Args) > 0 {
|
||||||
if p, err := sys.LookPath(config.Args[0]); err != nil {
|
if p, err := k.LookPath(config.Args[0]); err != nil {
|
||||||
return hlog.WrapErr(err, err.Error())
|
return &hst.AppError{Step: "look up executable file", Err: err}
|
||||||
} else if config.Path, err = container.NewAbs(p); err != nil {
|
} else if config.Path, err = container.NewAbs(p); err != nil {
|
||||||
return hlog.WrapErr(err, err.Error())
|
return newWithMessageError(err.Error(), err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
config.Path = config.Shell
|
config.Path = config.Shell
|
||||||
@@ -239,7 +232,7 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
|
|
||||||
Filesystem: []hst.FilesystemConfigJSON{
|
Filesystem: []hst.FilesystemConfigJSON{
|
||||||
// autoroot, includes the home directory
|
// autoroot, includes the home directory
|
||||||
{&hst.FSBind{
|
{FilesystemConfig: &hst.FSBind{
|
||||||
Target: container.AbsFHSRoot,
|
Target: container.AbsFHSRoot,
|
||||||
Source: container.AbsFHSRoot,
|
Source: container.AbsFHSRoot,
|
||||||
Write: true,
|
Write: true,
|
||||||
@@ -257,7 +250,7 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
|
|
||||||
// hide nscd from container if present
|
// hide nscd from container if present
|
||||||
nscd := container.AbsFHSVar.Append("run/nscd")
|
nscd := container.AbsFHSVar.Append("run/nscd")
|
||||||
if _, err := sys.Stat(nscd.String()); !errors.Is(err, fs.ErrNotExist) {
|
if _, err := k.Stat(nscd.String()); !errors.Is(err, fs.ErrNotExist) {
|
||||||
conf.Filesystem = append(conf.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSEphemeral{Target: nscd}})
|
conf.Filesystem = append(conf.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSEphemeral{Target: nscd}})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,21 +268,20 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
|
|
||||||
// late nil checks for pd behaviour
|
// late nil checks for pd behaviour
|
||||||
if config.Shell == nil {
|
if config.Shell == nil {
|
||||||
return hlog.WrapErr(syscall.EINVAL, "invalid shell path")
|
return newWithMessage("invalid shell path")
|
||||||
}
|
}
|
||||||
if config.Path == nil {
|
if config.Path == nil {
|
||||||
return hlog.WrapErr(syscall.EINVAL, "invalid program path")
|
return newWithMessage("invalid program path")
|
||||||
}
|
}
|
||||||
|
|
||||||
var mapuid, mapgid *stringPair[int]
|
var mapuid, mapgid *stringPair[int]
|
||||||
{
|
{
|
||||||
var uid, gid int
|
var uid, gid int
|
||||||
var err error
|
var err error
|
||||||
seal.container, seal.env, err = newContainer(config.Container, sys, seal.id.String(), &uid, &gid)
|
seal.container, seal.env, err = newContainer(config.Container, k, seal.id.String(), &uid, &gid)
|
||||||
seal.waitDelay = config.Container.WaitDelay
|
seal.waitDelay = config.Container.WaitDelay
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return hlog.WrapErrSuffix(err,
|
return &hst.AppError{Step: "initialise container configuration", Err: err}
|
||||||
"cannot initialise container configuration:")
|
|
||||||
}
|
}
|
||||||
if len(config.Args) == 0 {
|
if len(config.Args) == 0 {
|
||||||
config.Args = []string{config.Path.String()}
|
config.Args = []string{config.Path.String()}
|
||||||
@@ -310,16 +302,16 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
seal.env[xdgSessionClass] = "user"
|
seal.env[xdgSessionClass] = "user"
|
||||||
seal.env[xdgSessionType] = "tty"
|
seal.env[xdgSessionType] = "tty"
|
||||||
|
|
||||||
share := &shareHost{seal: seal, sc: sys.Paths()}
|
share := &shareHost{seal: seal, sc: k.Paths()}
|
||||||
seal.runDirPath = share.sc.RunDirPath
|
seal.runDirPath = share.sc.RunDirPath
|
||||||
seal.sys = system.New(seal.user.uid.unwrap())
|
seal.sys = system.New(seal.ctx, seal.user.uid.unwrap())
|
||||||
seal.sys.Ensure(share.sc.SharePath.String(), 0711)
|
seal.sys.Ensure(share.sc.SharePath.String(), 0711)
|
||||||
|
|
||||||
{
|
{
|
||||||
runtimeDir := share.sc.SharePath.Append("runtime")
|
runtimeDir := share.sc.SharePath.Append("runtime")
|
||||||
seal.sys.Ensure(runtimeDir.String(), 0700)
|
seal.sys.Ensure(runtimeDir.String(), 0700)
|
||||||
seal.sys.UpdatePermType(system.User, runtimeDir.String(), acl.Execute)
|
seal.sys.UpdatePermType(system.User, runtimeDir.String(), acl.Execute)
|
||||||
runtimeDirInst := runtimeDir.Append(seal.user.aid.String())
|
runtimeDirInst := runtimeDir.Append(seal.user.identity.String())
|
||||||
seal.sys.Ensure(runtimeDirInst.String(), 0700)
|
seal.sys.Ensure(runtimeDirInst.String(), 0700)
|
||||||
seal.sys.UpdatePermType(system.User, runtimeDirInst.String(), acl.Read, acl.Write, acl.Execute)
|
seal.sys.UpdatePermType(system.User, runtimeDirInst.String(), acl.Read, acl.Write, acl.Execute)
|
||||||
seal.container.Tmpfs(container.AbsFHSRunUser, 1<<12, 0755)
|
seal.container.Tmpfs(container.AbsFHSRunUser, 1<<12, 0755)
|
||||||
@@ -330,7 +322,7 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
tmpdir := share.sc.SharePath.Append("tmpdir")
|
tmpdir := share.sc.SharePath.Append("tmpdir")
|
||||||
seal.sys.Ensure(tmpdir.String(), 0700)
|
seal.sys.Ensure(tmpdir.String(), 0700)
|
||||||
seal.sys.UpdatePermType(system.User, tmpdir.String(), acl.Execute)
|
seal.sys.UpdatePermType(system.User, tmpdir.String(), acl.Execute)
|
||||||
tmpdirInst := tmpdir.Append(seal.user.aid.String())
|
tmpdirInst := tmpdir.Append(seal.user.identity.String())
|
||||||
seal.sys.Ensure(tmpdirInst.String(), 01700)
|
seal.sys.Ensure(tmpdirInst.String(), 01700)
|
||||||
seal.sys.UpdatePermType(system.User, tmpdirInst.String(), acl.Read, acl.Write, acl.Execute)
|
seal.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
|
// mount inner /tmp from share so it shares persistence and storage behaviour of host /tmp
|
||||||
@@ -354,14 +346,14 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
}
|
}
|
||||||
|
|
||||||
// pass TERM for proper terminal I/O in initial process
|
// pass TERM for proper terminal I/O in initial process
|
||||||
if t, ok := sys.LookupEnv(term); ok {
|
if t, ok := k.LookupEnv(term); ok {
|
||||||
seal.env[term] = t
|
seal.env[term] = t
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Enablements.Unwrap()&system.EWayland != 0 {
|
if config.Enablements.Unwrap()&system.EWayland != 0 {
|
||||||
// outer wayland socket (usually `/run/user/%d/wayland-%d`)
|
// outer wayland socket (usually `/run/user/%d/wayland-%d`)
|
||||||
var socketPath *container.Absolute
|
var socketPath *container.Absolute
|
||||||
if name, ok := sys.LookupEnv(wayland.WaylandDisplay); !ok {
|
if name, ok := k.LookupEnv(wayland.WaylandDisplay); !ok {
|
||||||
hlog.Verbose(wayland.WaylandDisplay + " is not set, assuming " + wayland.FallbackName)
|
hlog.Verbose(wayland.WaylandDisplay + " is not set, assuming " + wayland.FallbackName)
|
||||||
socketPath = share.sc.RuntimePath.Append(wayland.FallbackName)
|
socketPath = share.sc.RuntimePath.Append(wayland.FallbackName)
|
||||||
} else if a, err := container.NewAbs(name); err != nil {
|
} else if a, err := container.NewAbs(name); err != nil {
|
||||||
@@ -392,9 +384,8 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
}
|
}
|
||||||
|
|
||||||
if config.Enablements.Unwrap()&system.EX11 != 0 {
|
if config.Enablements.Unwrap()&system.EX11 != 0 {
|
||||||
if d, ok := sys.LookupEnv(display); !ok {
|
if d, ok := k.LookupEnv(display); !ok {
|
||||||
return hlog.WrapErr(ErrXDisplay,
|
return newWithMessage("DISPLAY is not set")
|
||||||
"DISPLAY is not set")
|
|
||||||
} else {
|
} else {
|
||||||
socketDir := container.AbsFHSTmp.Append(".X11-unix")
|
socketDir := container.AbsFHSTmp.Append(".X11-unix")
|
||||||
|
|
||||||
@@ -411,14 +402,15 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if socketPath != nil {
|
if socketPath != nil {
|
||||||
if _, err := sys.Stat(socketPath.String()); err != nil {
|
if _, err := k.Stat(socketPath.String()); err != nil {
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
return hlog.WrapErrSuffix(err,
|
return &hst.AppError{Step: fmt.Sprintf("access X11 socket %q", socketPath), Err: err}
|
||||||
fmt.Sprintf("cannot access X11 socket %q:", socketPath))
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
seal.sys.UpdatePermType(system.EX11, socketPath.String(), acl.Read, acl.Write, acl.Execute)
|
seal.sys.UpdatePermType(system.EX11, socketPath.String(), acl.Read, acl.Write, acl.Execute)
|
||||||
d = "unix:" + socketPath.String()
|
if !config.Container.HostAbstract {
|
||||||
|
d = "unix:" + socketPath.String()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -434,26 +426,21 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
// PulseAudio socket (usually `/run/user/%d/pulse/native`)
|
// PulseAudio socket (usually `/run/user/%d/pulse/native`)
|
||||||
pulseSocket := pulseRuntimeDir.Append("native")
|
pulseSocket := pulseRuntimeDir.Append("native")
|
||||||
|
|
||||||
if _, err := sys.Stat(pulseRuntimeDir.String()); err != nil {
|
if _, err := k.Stat(pulseRuntimeDir.String()); err != nil {
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
return hlog.WrapErrSuffix(err,
|
return &hst.AppError{Step: fmt.Sprintf("access PulseAudio directory %q", pulseRuntimeDir), Err: err}
|
||||||
fmt.Sprintf("cannot access PulseAudio directory %q:", pulseRuntimeDir))
|
|
||||||
}
|
}
|
||||||
return hlog.WrapErr(ErrPulseSocket,
|
return newWithMessage(fmt.Sprintf("PulseAudio directory %q not found", pulseRuntimeDir))
|
||||||
fmt.Sprintf("PulseAudio directory %q not found", pulseRuntimeDir))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if s, err := sys.Stat(pulseSocket.String()); err != nil {
|
if s, err := k.Stat(pulseSocket.String()); err != nil {
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
return hlog.WrapErrSuffix(err,
|
return &hst.AppError{Step: fmt.Sprintf("access PulseAudio socket %q", pulseSocket), Err: err}
|
||||||
fmt.Sprintf("cannot access PulseAudio socket %q:", pulseSocket))
|
|
||||||
}
|
}
|
||||||
return hlog.WrapErr(ErrPulseSocket,
|
return newWithMessage(fmt.Sprintf("PulseAudio directory %q found but socket does not exist", pulseRuntimeDir))
|
||||||
fmt.Sprintf("PulseAudio directory %q found but socket does not exist", pulseRuntimeDir))
|
|
||||||
} else {
|
} else {
|
||||||
if m := s.Mode(); m&0o006 != 0o006 {
|
if m := s.Mode(); m&0o006 != 0o006 {
|
||||||
return hlog.WrapErr(ErrPulseMode,
|
return newWithMessage(fmt.Sprintf("unexpected permissions on %q: %s", pulseSocket, m))
|
||||||
fmt.Sprintf("unexpected permissions on %q:", pulseSocket), m)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,15 +452,75 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
seal.env[pulseServer] = "unix:" + innerPulseSocket.String()
|
seal.env[pulseServer] = "unix:" + innerPulseSocket.String()
|
||||||
|
|
||||||
// publish current user's pulse cookie for target user
|
// publish current user's pulse cookie for target user
|
||||||
if src, err := discoverPulseCookie(sys); err != nil {
|
var paCookiePath *container.Absolute
|
||||||
// not fatal
|
{
|
||||||
hlog.Verbose(strings.TrimSpace(err.(*hlog.BaseError).Message()))
|
const paLocateStep = "locate PulseAudio cookie"
|
||||||
} else {
|
|
||||||
|
// 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")
|
innerDst := hst.AbsTmp.Append("/pulse-cookie")
|
||||||
seal.env[pulseCookie] = innerDst.String()
|
seal.env[pulseCookie] = innerDst.String()
|
||||||
var payload *[]byte
|
var payload *[]byte
|
||||||
seal.container.PlaceP(innerDst, &payload)
|
seal.container.PlaceP(innerDst, &payload)
|
||||||
seal.sys.CopyFile(payload, src, 256, 256)
|
seal.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)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -539,8 +586,8 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
seal.container.Env = make([]string, 0, len(seal.env))
|
seal.container.Env = make([]string, 0, len(seal.env))
|
||||||
for k, v := range seal.env {
|
for k, v := range seal.env {
|
||||||
if strings.IndexByte(k, '=') != -1 {
|
if strings.IndexByte(k, '=') != -1 {
|
||||||
return hlog.WrapErr(syscall.EINVAL,
|
return &hst.AppError{Step: "flatten environment", Err: syscall.EINVAL,
|
||||||
fmt.Sprintf("invalid environment variable %s", k))
|
Msg: fmt.Sprintf("invalid environment variable %s", k)}
|
||||||
}
|
}
|
||||||
seal.container.Env = append(seal.container.Env, k+"="+v)
|
seal.container.Env = append(seal.container.Env, k+"="+v)
|
||||||
}
|
}
|
||||||
@@ -553,42 +600,3 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// discoverPulseCookie attempts various standard methods to discover the current user's PulseAudio authentication cookie
|
|
||||||
func discoverPulseCookie(sys sys.State) (string, error) {
|
|
||||||
if p, ok := sys.LookupEnv(pulseCookie); ok {
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// dotfile $HOME/.pulse-cookie
|
|
||||||
if p, ok := sys.LookupEnv(home); ok {
|
|
||||||
p = path.Join(p, ".pulse-cookie")
|
|
||||||
if s, err := sys.Stat(p); err != nil {
|
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
|
||||||
return p, hlog.WrapErrSuffix(err,
|
|
||||||
fmt.Sprintf("cannot access PulseAudio cookie %q:", p))
|
|
||||||
}
|
|
||||||
// not found, try next method
|
|
||||||
} else if !s.IsDir() {
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// $XDG_CONFIG_HOME/pulse/cookie
|
|
||||||
if p, ok := sys.LookupEnv(xdgConfigHome); ok {
|
|
||||||
p = path.Join(p, "pulse", "cookie")
|
|
||||||
if s, err := sys.Stat(p); err != nil {
|
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
|
||||||
return p, hlog.WrapErrSuffix(err,
|
|
||||||
fmt.Sprintf("cannot access PulseAudio cookie %q:", p))
|
|
||||||
}
|
|
||||||
// not found, try next method
|
|
||||||
} else if !s.IsDir() {
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", hlog.WrapErr(ErrPulseCookie,
|
|
||||||
fmt.Sprintf("cannot locate PulseAudio cookie (tried $%s, $%s/pulse/cookie, $%s/.pulse-cookie)",
|
|
||||||
pulseCookie, xdgConfigHome, home))
|
|
||||||
}
|
|
||||||
@@ -45,8 +45,10 @@ const (
|
|||||||
// ShimExitOrphan is returned when the shim is orphaned before monitor delivers a signal.
|
// ShimExitOrphan is returned when the shim is orphaned before monitor delivers a signal.
|
||||||
ShimExitOrphan = 3
|
ShimExitOrphan = 3
|
||||||
|
|
||||||
|
// DefaultShimWaitDelay is used when WaitDelay has its zero value.
|
||||||
DefaultShimWaitDelay = 5 * time.Second
|
DefaultShimWaitDelay = 5 * time.Second
|
||||||
MaxShimWaitDelay = 30 * time.Second
|
// MaxShimWaitDelay is used instead if WaitDelay exceeds its value.
|
||||||
|
MaxShimWaitDelay = 30 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
// ShimMain is the main function of the shim process and runs as the unconstrained target user.
|
// ShimMain is the main function of the shim process and runs as the unconstrained target user.
|
||||||
@@ -65,7 +67,7 @@ func ShimMain() {
|
|||||||
if errors.Is(err, syscall.EBADF) {
|
if errors.Is(err, syscall.EBADF) {
|
||||||
log.Fatal("invalid config descriptor")
|
log.Fatal("invalid config descriptor")
|
||||||
}
|
}
|
||||||
if errors.Is(err, container.ErrNotSet) {
|
if errors.Is(err, container.ErrReceiveEnv) {
|
||||||
log.Fatal("HAKUREI_SHIM not set")
|
log.Fatal("HAKUREI_SHIM not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,11 +157,11 @@ func ShimMain() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := z.Start(); err != nil {
|
if err := z.Start(); err != nil {
|
||||||
hlog.PrintBaseError(err, "cannot start container:")
|
printMessageError("cannot start container:", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if err := z.Serve(); err != nil {
|
if err := z.Serve(); err != nil {
|
||||||
hlog.PrintBaseError(err, "cannot configure container:")
|
printMessageError("cannot configure container:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := seccomp.Load(
|
if err := seccomp.Load(
|
||||||
@@ -27,27 +27,27 @@ type multiStore struct {
|
|||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *multiStore) Do(aid int, f func(c Cursor)) (bool, error) {
|
func (s *multiStore) Do(identity int, f func(c Cursor)) (bool, error) {
|
||||||
s.lock.RLock()
|
s.lock.RLock()
|
||||||
defer s.lock.RUnlock()
|
defer s.lock.RUnlock()
|
||||||
|
|
||||||
// load or initialise new backend
|
// load or initialise new backend
|
||||||
b := new(multiBackend)
|
b := new(multiBackend)
|
||||||
b.lock.Lock()
|
b.lock.Lock()
|
||||||
if v, ok := s.backends.LoadOrStore(aid, b); ok {
|
if v, ok := s.backends.LoadOrStore(identity, b); ok {
|
||||||
b = v.(*multiBackend)
|
b = v.(*multiBackend)
|
||||||
} else {
|
} else {
|
||||||
b.path = path.Join(s.base, strconv.Itoa(aid))
|
b.path = path.Join(s.base, strconv.Itoa(identity))
|
||||||
|
|
||||||
// ensure directory
|
// ensure directory
|
||||||
if err := os.MkdirAll(b.path, 0700); err != nil && !errors.Is(err, fs.ErrExist) {
|
if err := os.MkdirAll(b.path, 0700); err != nil && !errors.Is(err, fs.ErrExist) {
|
||||||
s.backends.CompareAndDelete(aid, b)
|
s.backends.CompareAndDelete(identity, b)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// open locker file
|
// open locker file
|
||||||
if l, err := os.OpenFile(b.path+".lock", os.O_RDWR|os.O_CREATE, 0600); err != nil {
|
if l, err := os.OpenFile(b.path+".lock", os.O_RDWR|os.O_CREATE, 0600); err != nil {
|
||||||
s.backends.CompareAndDelete(aid, b)
|
s.backends.CompareAndDelete(identity, b)
|
||||||
return false, err
|
return false, err
|
||||||
} else {
|
} else {
|
||||||
b.lockfile = l
|
b.lockfile = l
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ type Store interface {
|
|||||||
// Do calls f exactly once and ensures store exclusivity until f returns.
|
// Do calls f exactly once and ensures store exclusivity until f returns.
|
||||||
// Returns whether f is called and any errors during the locking process.
|
// Returns whether f is called and any errors during the locking process.
|
||||||
// Cursor provided to f becomes invalid as soon as f returns.
|
// Cursor provided to f becomes invalid as soon as f returns.
|
||||||
Do(aid int, f func(c Cursor)) (ok bool, err error)
|
Do(identity int, f func(c Cursor)) (ok bool, err error)
|
||||||
|
|
||||||
// List queries the store and returns a list of aids known to the store.
|
// List queries the store and returns a list of aids known to the store.
|
||||||
// Note that some or all returned aids might not have any active apps.
|
// Note that some or all returned aids might not have any active apps.
|
||||||
|
|||||||
@@ -2,12 +2,9 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"hakurei.app/internal/app/state"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func newInt(v int) *stringPair[int] { return &stringPair[int]{v, strconv.Itoa(v)} }
|
func newInt(v int) *stringPair[int] { return &stringPair[int]{v, strconv.Itoa(v)} }
|
||||||
func newID(id *state.ID) *stringPair[state.ID] { return &stringPair[state.ID]{*id, id.String()} }
|
|
||||||
|
|
||||||
// stringPair stores a value and its string representation.
|
// stringPair stores a value and its string representation.
|
||||||
type stringPair[T comparable] struct {
|
type stringPair[T comparable] struct {
|
||||||
|
|||||||
8
internal/app/sysconf.go
Normal file
8
internal/app/sysconf.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
//#include <unistd.h>
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
const _SC_LOGIN_NAME_MAX = C._SC_LOGIN_NAME_MAX
|
||||||
|
|
||||||
|
func sysconf(name C.int) int { return int(C.sysconf(name)) }
|
||||||
12
internal/app/username.go
Normal file
12
internal/app/username.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import "regexp"
|
||||||
|
|
||||||
|
// nameRegex is the default NAME_REGEX value from adduser.
|
||||||
|
var nameRegex = regexp.MustCompilePOSIX(`^[a-zA-Z][a-zA-Z0-9_-]*\$?$`)
|
||||||
|
|
||||||
|
// isValidUsername returns whether the argument is a valid username
|
||||||
|
func isValidUsername(username string) bool {
|
||||||
|
return len(username) < sysconf(_SC_LOGIN_NAME_MAX) &&
|
||||||
|
nameRegex.MatchString(username)
|
||||||
|
}
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
package hlog
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// baseError implements a basic error container
|
|
||||||
type baseError struct {
|
|
||||||
Err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *baseError) Error() string { return e.Err.Error() }
|
|
||||||
func (e *baseError) Unwrap() error { return e.Err }
|
|
||||||
|
|
||||||
// BaseError implements an error container with a user-facing message
|
|
||||||
type BaseError struct {
|
|
||||||
message string
|
|
||||||
baseError
|
|
||||||
}
|
|
||||||
|
|
||||||
// Message returns a user-facing error message
|
|
||||||
func (e *BaseError) Message() string { return e.message }
|
|
||||||
|
|
||||||
// WrapErr wraps an error with a corresponding message.
|
|
||||||
func WrapErr(err error, a ...any) error {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return wrapErr(err, fmt.Sprintln(a...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// WrapErrSuffix wraps an error with a corresponding message with err at the end of the message.
|
|
||||||
func WrapErrSuffix(err error, a ...any) error {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return wrapErr(err, fmt.Sprintln(append(a, err)...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// WrapErrFunc wraps an error with a corresponding message returned by f.
|
|
||||||
func WrapErrFunc(err error, f func(err error) string) error {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return wrapErr(err, f(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapErr(err error, message string) *BaseError {
|
|
||||||
return &BaseError{message, baseError{err}}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
baseErrorType = reflect.TypeFor[*BaseError]()
|
|
||||||
)
|
|
||||||
|
|
||||||
func AsBaseError(err error, target **BaseError) bool {
|
|
||||||
v := reflect.ValueOf(err)
|
|
||||||
if !v.CanConvert(baseErrorType) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
*target = v.Convert(baseErrorType).Interface().(*BaseError)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func PrintBaseError(err error, fallback string) {
|
|
||||||
var e *BaseError
|
|
||||||
|
|
||||||
if AsBaseError(err, &e) {
|
|
||||||
if msg := e.Message(); strings.TrimSpace(msg) != "" {
|
|
||||||
log.Print(msg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Verbose("*"+fallback, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Println(fallback, err)
|
|
||||||
}
|
|
||||||
@@ -2,11 +2,9 @@ package hlog
|
|||||||
|
|
||||||
type Output struct{}
|
type Output struct{}
|
||||||
|
|
||||||
func (Output) IsVerbose() bool { return Load() }
|
func (Output) IsVerbose() bool { return Load() }
|
||||||
func (Output) Verbose(v ...any) { Verbose(v...) }
|
func (Output) Verbose(v ...any) { Verbose(v...) }
|
||||||
func (Output) Verbosef(format string, v ...any) { Verbosef(format, v...) }
|
func (Output) Verbosef(format string, v ...any) { Verbosef(format, v...) }
|
||||||
func (Output) WrapErr(err error, a ...any) error { return WrapErr(err, a...) }
|
func (Output) Suspend() { Suspend() }
|
||||||
func (Output) PrintBaseErr(err error, fallback string) { PrintBaseError(err, fallback) }
|
func (Output) Resume() bool { return Resume() }
|
||||||
func (Output) Suspend() { Suspend() }
|
func (Output) BeforeExit() { BeforeExit() }
|
||||||
func (Output) Resume() bool { return Resume() }
|
|
||||||
func (Output) BeforeExit() { BeforeExit() }
|
|
||||||
|
|||||||
104
internal/sys/hsu.go
Normal file
104
internal/sys/hsu.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package sys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/internal"
|
||||||
|
"hakurei.app/internal/hlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Hsu caches responses from cmd/hsu.
|
||||||
|
type Hsu struct {
|
||||||
|
uidOnce sync.Once
|
||||||
|
uidCopy map[int]struct {
|
||||||
|
uid int
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
uidMu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrHsuAccess = errors.New("current user is not in the hsurc file")
|
||||||
|
|
||||||
|
func (h *Hsu) Uid(identity int) (int, error) {
|
||||||
|
h.uidOnce.Do(func() {
|
||||||
|
h.uidCopy = make(map[int]struct {
|
||||||
|
uid int
|
||||||
|
err error
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
{
|
||||||
|
h.uidMu.RLock()
|
||||||
|
u, ok := h.uidCopy[identity]
|
||||||
|
h.uidMu.RUnlock()
|
||||||
|
if ok {
|
||||||
|
return u.uid, u.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h.uidMu.Lock()
|
||||||
|
defer h.uidMu.Unlock()
|
||||||
|
|
||||||
|
u := struct {
|
||||||
|
uid int
|
||||||
|
err error
|
||||||
|
}{}
|
||||||
|
defer func() { h.uidCopy[identity] = u }()
|
||||||
|
|
||||||
|
u.uid = -1
|
||||||
|
hsuPath := internal.MustHsuPath()
|
||||||
|
|
||||||
|
cmd := exec.Command(hsuPath)
|
||||||
|
cmd.Path = hsuPath
|
||||||
|
cmd.Stderr = os.Stderr // pass through fatal messages
|
||||||
|
cmd.Env = []string{"HAKUREI_APP_ID=" + strconv.Itoa(identity)}
|
||||||
|
cmd.Dir = container.FHSRoot
|
||||||
|
var (
|
||||||
|
p []byte
|
||||||
|
exitError *exec.ExitError
|
||||||
|
)
|
||||||
|
|
||||||
|
const step = "obtain uid from hsu"
|
||||||
|
if p, u.err = cmd.Output(); u.err == nil {
|
||||||
|
u.uid, u.err = strconv.Atoi(string(p))
|
||||||
|
if u.err != nil {
|
||||||
|
u.err = &hst.AppError{Step: step, Err: u.err, Msg: "invalid uid string from hsu"}
|
||||||
|
}
|
||||||
|
} else if errors.As(u.err, &exitError) && exitError != nil && exitError.ExitCode() == 1 {
|
||||||
|
// hsu prints an error message in this case
|
||||||
|
u.err = &hst.AppError{Step: step, Err: ErrHsuAccess}
|
||||||
|
} else if os.IsNotExist(u.err) {
|
||||||
|
u.err = &hst.AppError{Step: step, Err: os.ErrNotExist,
|
||||||
|
Msg: fmt.Sprintf("the setuid helper is missing: %s", hsuPath)}
|
||||||
|
}
|
||||||
|
return u.uid, u.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustUid calls [State.Uid] and terminates on error.
|
||||||
|
func MustUid(s State, identity int) int {
|
||||||
|
uid, err := s.Uid(identity)
|
||||||
|
if err == nil {
|
||||||
|
return uid
|
||||||
|
}
|
||||||
|
|
||||||
|
const fallback = "cannot obtain uid from setuid wrapper:"
|
||||||
|
if errors.Is(err, ErrHsuAccess) {
|
||||||
|
hlog.Verbose("*"+fallback, err)
|
||||||
|
os.Exit(1)
|
||||||
|
return -0xdeadbeef
|
||||||
|
} else if m, ok := container.GetErrorMessage(err); ok {
|
||||||
|
log.Fatal(m)
|
||||||
|
return -0xdeadbeef
|
||||||
|
} else {
|
||||||
|
log.Fatalln(fallback, err)
|
||||||
|
return -0xdeadbeef
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,14 +49,8 @@ type State interface {
|
|||||||
Uid(identity int) (int, error)
|
Uid(identity int) (int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserID obtains user id from hsu by querying uid of identity 0.
|
// MustGetUserID obtains user id from hsu by querying uid of identity 0.
|
||||||
func GetUserID(os State) (int, error) {
|
func MustGetUserID(os State) int { return (MustUid(os, 0) / 10000) - 100 }
|
||||||
if uid, err := os.Uid(0); err != nil {
|
|
||||||
return -1, err
|
|
||||||
} else {
|
|
||||||
return (uid / 10000) - 100, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyPaths is a generic implementation of [hst.Paths].
|
// CopyPaths is a generic implementation of [hst.Paths].
|
||||||
func CopyPaths(os State, v *hst.Paths, userid int) {
|
func CopyPaths(os State, v *hst.Paths, userid int) {
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
package sys
|
package sys
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
@@ -22,13 +18,7 @@ import (
|
|||||||
type Std struct {
|
type Std struct {
|
||||||
paths hst.Paths
|
paths hst.Paths
|
||||||
pathsOnce sync.Once
|
pathsOnce sync.Once
|
||||||
|
Hsu
|
||||||
uidOnce sync.Once
|
|
||||||
uidCopy map[int]struct {
|
|
||||||
uid int
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
uidMu sync.RWMutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Std) Getuid() int { return os.Getuid() }
|
func (s *Std) Getuid() int { return os.Getuid() }
|
||||||
@@ -49,66 +39,6 @@ func (s *Std) Printf(format string, v ...any) { hlog.Verbosef(form
|
|||||||
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
||||||
|
|
||||||
func (s *Std) Paths() hst.Paths {
|
func (s *Std) Paths() hst.Paths {
|
||||||
s.pathsOnce.Do(func() {
|
s.pathsOnce.Do(func() { CopyPaths(s, &s.paths, MustGetUserID(s)) })
|
||||||
if userid, err := GetUserID(s); err != nil {
|
|
||||||
hlog.PrintBaseError(err, "cannot obtain user id from hsu:")
|
|
||||||
hlog.BeforeExit()
|
|
||||||
s.Exit(1)
|
|
||||||
} else {
|
|
||||||
CopyPaths(s, &s.paths, userid)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return s.paths
|
return s.paths
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Std) Uid(identity int) (int, error) {
|
|
||||||
s.uidOnce.Do(func() {
|
|
||||||
s.uidCopy = make(map[int]struct {
|
|
||||||
uid int
|
|
||||||
err error
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
{
|
|
||||||
s.uidMu.RLock()
|
|
||||||
u, ok := s.uidCopy[identity]
|
|
||||||
s.uidMu.RUnlock()
|
|
||||||
if ok {
|
|
||||||
return u.uid, u.err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.uidMu.Lock()
|
|
||||||
defer s.uidMu.Unlock()
|
|
||||||
|
|
||||||
u := struct {
|
|
||||||
uid int
|
|
||||||
err error
|
|
||||||
}{}
|
|
||||||
defer func() { s.uidCopy[identity] = u }()
|
|
||||||
|
|
||||||
u.uid = -1
|
|
||||||
hsuPath := internal.MustHsuPath()
|
|
||||||
|
|
||||||
cmd := exec.Command(hsuPath)
|
|
||||||
cmd.Path = hsuPath
|
|
||||||
cmd.Stderr = os.Stderr // pass through fatal messages
|
|
||||||
cmd.Env = []string{"HAKUREI_APP_ID=" + strconv.Itoa(identity)}
|
|
||||||
cmd.Dir = container.FHSRoot
|
|
||||||
var (
|
|
||||||
p []byte
|
|
||||||
exitError *exec.ExitError
|
|
||||||
)
|
|
||||||
|
|
||||||
if p, u.err = cmd.Output(); u.err == nil {
|
|
||||||
u.uid, u.err = strconv.Atoi(string(p))
|
|
||||||
if u.err != nil {
|
|
||||||
u.err = hlog.WrapErr(u.err, "invalid uid string from hsu")
|
|
||||||
}
|
|
||||||
} else if errors.As(u.err, &exitError) && exitError != nil && exitError.ExitCode() == 1 {
|
|
||||||
u.err = hlog.WrapErr(syscall.EACCES, "") // hsu prints to stderr in this case
|
|
||||||
} else if os.IsNotExist(u.err) {
|
|
||||||
u.err = hlog.WrapErr(os.ErrNotExist, fmt.Sprintf("the setuid helper is missing: %s", hsuPath))
|
|
||||||
}
|
|
||||||
return u.uid, u.err
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
//#include <unistd.h>
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
const SC_LOGIN_NAME_MAX = C._SC_LOGIN_NAME_MAX
|
|
||||||
|
|
||||||
func Sysconf(name C.int) int { return int(C.sysconf(name)) }
|
|
||||||
@@ -35,7 +35,7 @@ package
|
|||||||
|
|
||||||
|
|
||||||
*Default:*
|
*Default:*
|
||||||
` <derivation hakurei-static-x86_64-unknown-linux-musl-0.2.0> `
|
` <derivation hakurei-static-x86_64-unknown-linux-musl-0.2.1> `
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -759,7 +759,7 @@ package
|
|||||||
|
|
||||||
|
|
||||||
*Default:*
|
*Default:*
|
||||||
` <derivation hakurei-hsu-0.2.0> `
|
` <derivation hakurei-hsu-0.2.1> `
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
extraPaths = mkOption {
|
extraPaths = mkOption {
|
||||||
type = anything;
|
type = listOf (attrsOf anything);
|
||||||
default = [ ];
|
default = [ ];
|
||||||
description = ''
|
description = ''
|
||||||
Extra paths to make available to the container.
|
Extra paths to make available to the container.
|
||||||
@@ -261,7 +261,7 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
commonPaths = mkOption {
|
commonPaths = mkOption {
|
||||||
type = types.anything;
|
type = types.listOf (types.attrsOf types.anything);
|
||||||
default = [ ];
|
default = [ ];
|
||||||
description = ''
|
description = ''
|
||||||
Common extra paths to make available to the container.
|
Common extra paths to make available to the container.
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
buildGoModule rec {
|
buildGoModule rec {
|
||||||
pname = "hakurei";
|
pname = "hakurei";
|
||||||
version = "0.2.0";
|
version = "0.2.1";
|
||||||
|
|
||||||
srcFiltered = builtins.path {
|
srcFiltered = builtins.path {
|
||||||
name = "${pname}-src";
|
name = "${pname}-src";
|
||||||
|
|||||||
@@ -9,65 +9,59 @@ import (
|
|||||||
"hakurei.app/system/acl"
|
"hakurei.app/system/acl"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UpdatePerm appends an ephemeral acl update Op.
|
// UpdatePerm calls UpdatePermType with the [Process] criteria.
|
||||||
func (sys *I) UpdatePerm(path string, perms ...acl.Perm) *I {
|
func (sys *I) UpdatePerm(path string, perms ...acl.Perm) *I {
|
||||||
sys.UpdatePermType(Process, path, perms...)
|
sys.UpdatePermType(Process, path, perms...)
|
||||||
|
|
||||||
return sys
|
return sys
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePermType appends an acl update Op.
|
// UpdatePermType maintains [acl.Perms] on a file until its [Enablement] is no longer satisfied.
|
||||||
func (sys *I) UpdatePermType(et Enablement, path string, perms ...acl.Perm) *I {
|
func (sys *I) UpdatePermType(et Enablement, path string, perms ...acl.Perm) *I {
|
||||||
sys.lock.Lock()
|
sys.ops = append(sys.ops, &aclUpdateOp{et, path, perms})
|
||||||
defer sys.lock.Unlock()
|
|
||||||
|
|
||||||
sys.ops = append(sys.ops, &ACL{et, path, perms})
|
|
||||||
|
|
||||||
return sys
|
return sys
|
||||||
}
|
}
|
||||||
|
|
||||||
type ACL struct {
|
// aclUpdateOp implements [I.UpdatePermType].
|
||||||
|
type aclUpdateOp struct {
|
||||||
et Enablement
|
et Enablement
|
||||||
path string
|
path string
|
||||||
perms acl.Perms
|
perms acl.Perms
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACL) Type() Enablement { return a.et }
|
func (a *aclUpdateOp) Type() Enablement { return a.et }
|
||||||
|
|
||||||
func (a *ACL) apply(sys *I) error {
|
func (a *aclUpdateOp) apply(sys *I) error {
|
||||||
msg.Verbose("applying ACL", a)
|
sys.verbose("applying ACL", a)
|
||||||
return wrapErrSuffix(acl.Update(a.path, sys.uid, a.perms...),
|
return newOpError("acl", sys.aclUpdate(a.path, sys.uid, a.perms...), false)
|
||||||
fmt.Sprintf("cannot apply ACL entry to %q:", a.path))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACL) revert(sys *I, ec *Criteria) error {
|
func (a *aclUpdateOp) revert(sys *I, ec *Criteria) error {
|
||||||
if ec.hasType(a) {
|
if ec.hasType(a.Type()) {
|
||||||
msg.Verbose("stripping ACL", a)
|
sys.verbose("stripping ACL", a)
|
||||||
err := acl.Update(a.path, sys.uid)
|
err := sys.aclUpdate(a.path, sys.uid)
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
// the ACL is effectively stripped if the file no longer exists
|
// the ACL is effectively stripped if the file no longer exists
|
||||||
msg.Verbosef("target of ACL %s no longer exists", a)
|
sys.verbosef("target of ACL %s no longer exists", a)
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
return wrapErrSuffix(err,
|
return newOpError("acl", err, true)
|
||||||
fmt.Sprintf("cannot strip ACL entry from %q:", a.path))
|
|
||||||
} else {
|
} else {
|
||||||
msg.Verbose("skipping ACL", a)
|
sys.verbose("skipping ACL", a)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACL) Is(o Op) bool {
|
func (a *aclUpdateOp) Is(o Op) bool {
|
||||||
a0, ok := o.(*ACL)
|
target, ok := o.(*aclUpdateOp)
|
||||||
return ok && a0 != nil &&
|
return ok && a != nil && target != nil &&
|
||||||
a.et == a0.et &&
|
a.et == target.et &&
|
||||||
a.path == a0.path &&
|
a.path == target.path &&
|
||||||
slices.Equal(a.perms, a0.perms)
|
slices.Equal(a.perms, target.perms)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACL) Path() string { return a.path }
|
func (a *aclUpdateOp) Path() string { return a.path }
|
||||||
|
|
||||||
func (a *ACL) String() string {
|
func (a *aclUpdateOp) String() string {
|
||||||
return fmt.Sprintf("%s type: %s path: %q",
|
return fmt.Sprintf("%s type: %s path: %q",
|
||||||
a.perms, TypeString(a.et), a.path)
|
a.perms, TypeString(a.et), a.path)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,8 +29,5 @@ func Update(name string, uid int, perms ...Perm) error {
|
|||||||
(*C.acl_perm_t)(p),
|
(*C.acl_perm_t)(p),
|
||||||
C.size_t(len(perms)),
|
C.size_t(len(perms)),
|
||||||
)
|
)
|
||||||
if r == 0 {
|
return newAclPathError(name, int(r), err)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,156 +0,0 @@
|
|||||||
package acl_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os/exec"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
getFAclInvocation struct {
|
|
||||||
cmd *exec.Cmd
|
|
||||||
val []*getFAclResp
|
|
||||||
pe []error
|
|
||||||
}
|
|
||||||
|
|
||||||
getFAclResp struct {
|
|
||||||
typ fAclType
|
|
||||||
cred int32
|
|
||||||
val fAclPerm
|
|
||||||
|
|
||||||
raw []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
fAclPerm uintptr
|
|
||||||
fAclType uint8
|
|
||||||
)
|
|
||||||
|
|
||||||
const fAclBufSize = 16
|
|
||||||
|
|
||||||
const (
|
|
||||||
fAclPermRead fAclPerm = 1 << iota
|
|
||||||
fAclPermWrite
|
|
||||||
fAclPermExecute
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
fAclTypeUser fAclType = iota
|
|
||||||
fAclTypeGroup
|
|
||||||
fAclTypeMask
|
|
||||||
fAclTypeOther
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *getFAclInvocation) run(name string) error {
|
|
||||||
if c.cmd != nil {
|
|
||||||
panic("attempted to run twice")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.cmd = exec.Command("getfacl", "--omit-header", "--absolute-names", "--numeric", name)
|
|
||||||
|
|
||||||
scanErr := make(chan error, 1)
|
|
||||||
if p, err := c.cmd.StdoutPipe(); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
go c.parse(p, scanErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.cmd.Start(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.Join(<-scanErr, c.cmd.Wait())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *getFAclInvocation) parse(pipe io.Reader, scanErr chan error) {
|
|
||||||
c.val = make([]*getFAclResp, 0, 4+fAclBufSize)
|
|
||||||
|
|
||||||
s := bufio.NewScanner(pipe)
|
|
||||||
for s.Scan() {
|
|
||||||
fields := bytes.SplitN(s.Bytes(), []byte{':'}, 3)
|
|
||||||
if len(fields) != 3 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := getFAclResp{}
|
|
||||||
|
|
||||||
switch string(fields[0]) {
|
|
||||||
case "user":
|
|
||||||
resp.typ = fAclTypeUser
|
|
||||||
case "group":
|
|
||||||
resp.typ = fAclTypeGroup
|
|
||||||
case "mask":
|
|
||||||
resp.typ = fAclTypeMask
|
|
||||||
case "other":
|
|
||||||
resp.typ = fAclTypeOther
|
|
||||||
default:
|
|
||||||
c.pe = append(c.pe, fmt.Errorf("unknown type %s", string(fields[0])))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(fields[1]) == 0 {
|
|
||||||
resp.cred = -1
|
|
||||||
} else {
|
|
||||||
if cred, err := strconv.Atoi(string(fields[1])); err != nil {
|
|
||||||
c.pe = append(c.pe, err)
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
resp.cred = int32(cred)
|
|
||||||
if resp.cred < 0 {
|
|
||||||
c.pe = append(c.pe, fmt.Errorf("credential %d out of range", resp.cred))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(fields[2]) != 3 {
|
|
||||||
c.pe = append(c.pe, fmt.Errorf("invalid perm length %d", len(fields[2])))
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
switch fields[2][0] {
|
|
||||||
case 'r':
|
|
||||||
resp.val |= fAclPermRead
|
|
||||||
case '-':
|
|
||||||
default:
|
|
||||||
c.pe = append(c.pe, fmt.Errorf("invalid perm %v", fields[2][0]))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch fields[2][1] {
|
|
||||||
case 'w':
|
|
||||||
resp.val |= fAclPermWrite
|
|
||||||
case '-':
|
|
||||||
default:
|
|
||||||
c.pe = append(c.pe, fmt.Errorf("invalid perm %v", fields[2][1]))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch fields[2][2] {
|
|
||||||
case 'x':
|
|
||||||
resp.val |= fAclPermExecute
|
|
||||||
case '-':
|
|
||||||
default:
|
|
||||||
c.pe = append(c.pe, fmt.Errorf("invalid perm %v", fields[2][2]))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.raw = make([]byte, len(s.Bytes()))
|
|
||||||
copy(resp.raw, s.Bytes())
|
|
||||||
c.val = append(c.val, &resp)
|
|
||||||
}
|
|
||||||
scanErr <- s.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *getFAclResp) String() string {
|
|
||||||
if r.raw != nil && len(r.raw) > 0 {
|
|
||||||
return string(r.raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
return "(user-initialised resp value)"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *getFAclResp) equals(typ fAclType, cred int32, val fAclPerm) bool {
|
|
||||||
return r.typ == typ && r.cred == cred && r.val == val
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,16 @@
|
|||||||
package acl_test
|
package acl_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"hakurei.app/system/acl"
|
"hakurei.app/system/acl"
|
||||||
@@ -17,7 +23,7 @@ var (
|
|||||||
cred = int32(os.Geteuid())
|
cred = int32(os.Geteuid())
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUpdatePerm(t *testing.T) {
|
func TestUpdate(t *testing.T) {
|
||||||
if os.Getenv("GO_TEST_SKIP_ACL") == "1" {
|
if os.Getenv("GO_TEST_SKIP_ACL") == "1" {
|
||||||
t.Log("acl test skipped")
|
t.Log("acl test skipped")
|
||||||
t.SkipNow()
|
t.SkipNow()
|
||||||
@@ -48,19 +54,19 @@ func TestUpdatePerm(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("default clear mask", func(t *testing.T) {
|
t.Run("default clear mask", func(t *testing.T) {
|
||||||
if err := acl.Update(testFilePath, uid); err != nil {
|
if err := acl.Update(testFilePath, uid); err != nil {
|
||||||
t.Fatalf("UpdatePerm: error = %v", err)
|
t.Fatalf("Update: error = %v", err)
|
||||||
}
|
}
|
||||||
if cur = getfacl(t, testFilePath); len(cur) != 4 {
|
if cur = getfacl(t, testFilePath); len(cur) != 4 {
|
||||||
t.Fatalf("UpdatePerm: %v", cur)
|
t.Fatalf("Update: %v", cur)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("default clear consistency", func(t *testing.T) {
|
t.Run("default clear consistency", func(t *testing.T) {
|
||||||
if err := acl.Update(testFilePath, uid); err != nil {
|
if err := acl.Update(testFilePath, uid); err != nil {
|
||||||
t.Fatalf("UpdatePerm: error = %v", err)
|
t.Fatalf("Update: error = %v", err)
|
||||||
}
|
}
|
||||||
if val := getfacl(t, testFilePath); !reflect.DeepEqual(val, cur) {
|
if val := getfacl(t, testFilePath); !reflect.DeepEqual(val, cur) {
|
||||||
t.Fatalf("UpdatePerm: %v, want %v", val, cur)
|
t.Fatalf("Update: %v, want %v", val, cur)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -77,26 +83,171 @@ func testUpdate(t *testing.T, testFilePath, name string, cur []*getFAclResp, val
|
|||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
if err := acl.Update(testFilePath, uid); err != nil {
|
if err := acl.Update(testFilePath, uid); err != nil {
|
||||||
t.Fatalf("UpdatePerm: error = %v", err)
|
t.Fatalf("Update: error = %v", err)
|
||||||
}
|
}
|
||||||
if v := getfacl(t, testFilePath); !reflect.DeepEqual(v, cur) {
|
if v := getfacl(t, testFilePath); !reflect.DeepEqual(v, cur) {
|
||||||
t.Fatalf("UpdatePerm: %v, want %v", v, cur)
|
t.Fatalf("Update: %v, want %v", v, cur)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := acl.Update(testFilePath, uid, perms...); err != nil {
|
if err := acl.Update(testFilePath, uid, perms...); err != nil {
|
||||||
t.Fatalf("UpdatePerm: error = %v", err)
|
t.Fatalf("Update: error = %v", err)
|
||||||
}
|
}
|
||||||
r := respByCred(getfacl(t, testFilePath), fAclTypeUser, cred)
|
r := respByCred(getfacl(t, testFilePath), fAclTypeUser, cred)
|
||||||
if r == nil {
|
if r == nil {
|
||||||
t.Fatalf("UpdatePerm did not add an ACL entry")
|
t.Fatalf("Update did not add an ACL entry")
|
||||||
}
|
}
|
||||||
if !r.equals(fAclTypeUser, cred, val) {
|
if !r.equals(fAclTypeUser, cred, val) {
|
||||||
t.Fatalf("UpdatePerm(%s) = %s", name, r)
|
t.Fatalf("Update(%s) = %s", name, r)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
getFAclInvocation struct {
|
||||||
|
cmd *exec.Cmd
|
||||||
|
val []*getFAclResp
|
||||||
|
pe []error
|
||||||
|
}
|
||||||
|
|
||||||
|
getFAclResp struct {
|
||||||
|
typ fAclType
|
||||||
|
cred int32
|
||||||
|
val fAclPerm
|
||||||
|
|
||||||
|
raw []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
fAclPerm uintptr
|
||||||
|
fAclType uint8
|
||||||
|
)
|
||||||
|
|
||||||
|
const fAclBufSize = 16
|
||||||
|
|
||||||
|
const (
|
||||||
|
fAclPermRead fAclPerm = 1 << iota
|
||||||
|
fAclPermWrite
|
||||||
|
fAclPermExecute
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
fAclTypeUser fAclType = iota
|
||||||
|
fAclTypeGroup
|
||||||
|
fAclTypeMask
|
||||||
|
fAclTypeOther
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *getFAclInvocation) run(name string) error {
|
||||||
|
if c.cmd != nil {
|
||||||
|
panic("attempted to run twice")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.cmd = exec.Command("getfacl", "--omit-header", "--absolute-names", "--numeric", name)
|
||||||
|
|
||||||
|
scanErr := make(chan error, 1)
|
||||||
|
if p, err := c.cmd.StdoutPipe(); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
go c.parse(p, scanErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.cmd.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Join(<-scanErr, c.cmd.Wait())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *getFAclInvocation) parse(pipe io.Reader, scanErr chan error) {
|
||||||
|
c.val = make([]*getFAclResp, 0, 4+fAclBufSize)
|
||||||
|
|
||||||
|
s := bufio.NewScanner(pipe)
|
||||||
|
for s.Scan() {
|
||||||
|
fields := bytes.SplitN(s.Bytes(), []byte{':'}, 3)
|
||||||
|
if len(fields) != 3 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := getFAclResp{}
|
||||||
|
|
||||||
|
switch string(fields[0]) {
|
||||||
|
case "user":
|
||||||
|
resp.typ = fAclTypeUser
|
||||||
|
case "group":
|
||||||
|
resp.typ = fAclTypeGroup
|
||||||
|
case "mask":
|
||||||
|
resp.typ = fAclTypeMask
|
||||||
|
case "other":
|
||||||
|
resp.typ = fAclTypeOther
|
||||||
|
default:
|
||||||
|
c.pe = append(c.pe, fmt.Errorf("unknown type %s", string(fields[0])))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fields[1]) == 0 {
|
||||||
|
resp.cred = -1
|
||||||
|
} else {
|
||||||
|
if cred, err := strconv.Atoi(string(fields[1])); err != nil {
|
||||||
|
c.pe = append(c.pe, err)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
resp.cred = int32(cred)
|
||||||
|
if resp.cred < 0 {
|
||||||
|
c.pe = append(c.pe, fmt.Errorf("credential %d out of range", resp.cred))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fields[2]) != 3 {
|
||||||
|
c.pe = append(c.pe, fmt.Errorf("invalid perm length %d", len(fields[2])))
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
switch fields[2][0] {
|
||||||
|
case 'r':
|
||||||
|
resp.val |= fAclPermRead
|
||||||
|
case '-':
|
||||||
|
default:
|
||||||
|
c.pe = append(c.pe, fmt.Errorf("invalid perm %v", fields[2][0]))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch fields[2][1] {
|
||||||
|
case 'w':
|
||||||
|
resp.val |= fAclPermWrite
|
||||||
|
case '-':
|
||||||
|
default:
|
||||||
|
c.pe = append(c.pe, fmt.Errorf("invalid perm %v", fields[2][1]))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch fields[2][2] {
|
||||||
|
case 'x':
|
||||||
|
resp.val |= fAclPermExecute
|
||||||
|
case '-':
|
||||||
|
default:
|
||||||
|
c.pe = append(c.pe, fmt.Errorf("invalid perm %v", fields[2][2]))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.raw = make([]byte, len(s.Bytes()))
|
||||||
|
copy(resp.raw, s.Bytes())
|
||||||
|
c.val = append(c.val, &resp)
|
||||||
|
}
|
||||||
|
scanErr <- s.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *getFAclResp) String() string {
|
||||||
|
if r.raw != nil && len(r.raw) > 0 {
|
||||||
|
return string(r.raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "(user-initialised resp value)"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *getFAclResp) equals(typ fAclType, cred int32, val fAclPerm) bool {
|
||||||
|
return r.typ == typ && r.cred == cred && r.val == val
|
||||||
|
}
|
||||||
|
|
||||||
func getfacl(t *testing.T, name string) []*getFAclResp {
|
func getfacl(t *testing.T, name string) []*getFAclResp {
|
||||||
c := new(getFAclInvocation)
|
c := new(getFAclInvocation)
|
||||||
if err := c.run(name); err != nil {
|
if err := c.run(name); err != nil {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
int hakurei_acl_update_file_by_uid(const char *path_p, uid_t uid,
|
int hakurei_acl_update_file_by_uid(const char *path_p, uid_t uid,
|
||||||
acl_perm_t *perms, size_t plen) {
|
acl_perm_t *perms, size_t plen) {
|
||||||
int ret = -1;
|
int ret;
|
||||||
bool v;
|
bool v;
|
||||||
int i;
|
int i;
|
||||||
acl_t acl;
|
acl_t acl;
|
||||||
@@ -15,51 +15,70 @@ int hakurei_acl_update_file_by_uid(const char *path_p, uid_t uid,
|
|||||||
void *qualifier_p;
|
void *qualifier_p;
|
||||||
acl_permset_t permset;
|
acl_permset_t permset;
|
||||||
|
|
||||||
|
ret = -1; /* acl_get_file */
|
||||||
acl = acl_get_file(path_p, ACL_TYPE_ACCESS);
|
acl = acl_get_file(path_p, ACL_TYPE_ACCESS);
|
||||||
if (acl == NULL)
|
if (acl == NULL)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
// prune entries by uid
|
/* prune entries by uid */
|
||||||
for (i = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); i == 1;
|
for (i = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); i == 1;
|
||||||
i = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry)) {
|
i = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry)) {
|
||||||
|
ret = -2; /* acl_get_tag_type */
|
||||||
if (acl_get_tag_type(entry, &tag_type) != 0)
|
if (acl_get_tag_type(entry, &tag_type) != 0)
|
||||||
return -1;
|
goto out;
|
||||||
if (tag_type != ACL_USER)
|
if (tag_type != ACL_USER)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
ret = -3; /* acl_get_qualifier */
|
||||||
qualifier_p = acl_get_qualifier(entry);
|
qualifier_p = acl_get_qualifier(entry);
|
||||||
if (qualifier_p == NULL)
|
if (qualifier_p == NULL)
|
||||||
return -1;
|
goto out;
|
||||||
v = *(uid_t *)qualifier_p == uid;
|
v = *(uid_t *)qualifier_p == uid;
|
||||||
acl_free(qualifier_p);
|
acl_free(qualifier_p);
|
||||||
|
|
||||||
if (!v)
|
if (!v)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
acl_delete_entry(acl, entry);
|
ret = -4; /* acl_delete_entry */
|
||||||
|
if (acl_delete_entry(acl, entry) != 0)
|
||||||
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plen == 0)
|
if (plen == 0)
|
||||||
goto set;
|
goto set;
|
||||||
|
|
||||||
|
ret = -5; /* acl_create_entry */
|
||||||
if (acl_create_entry(&acl, &entry) != 0)
|
if (acl_create_entry(&acl, &entry) != 0)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
|
ret = -6; /* acl_get_permset */
|
||||||
if (acl_get_permset(entry, &permset) != 0)
|
if (acl_get_permset(entry, &permset) != 0)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
|
ret = -7; /* acl_add_perm */
|
||||||
for (i = 0; i < plen; i++) {
|
for (i = 0; i < plen; i++) {
|
||||||
if (acl_add_perm(permset, perms[i]) != 0)
|
if (acl_add_perm(permset, perms[i]) != 0)
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ret = -8; /* acl_set_tag_type */
|
||||||
if (acl_set_tag_type(entry, ACL_USER) != 0)
|
if (acl_set_tag_type(entry, ACL_USER) != 0)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
|
ret = -9; /* acl_set_qualifier */
|
||||||
if (acl_set_qualifier(entry, (void *)&uid) != 0)
|
if (acl_set_qualifier(entry, (void *)&uid) != 0)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
set:
|
set:
|
||||||
|
ret = -10; /* acl_calc_mask */
|
||||||
if (acl_calc_mask(&acl) != 0)
|
if (acl_calc_mask(&acl) != 0)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
|
ret = -11; /* acl_valid */
|
||||||
if (acl_valid(acl) != 0)
|
if (acl_valid(acl) != 0)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
|
ret = -12; /* acl_set_file */
|
||||||
if (acl_set_file(path_p, ACL_TYPE_ACCESS, acl) == 0)
|
if (acl_set_file(path_p, ACL_TYPE_ACCESS, acl) == 0)
|
||||||
ret = 0;
|
ret = 0;
|
||||||
|
|
||||||
|
|||||||
40
system/acl/libacl-helper.go
Normal file
40
system/acl/libacl-helper.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package acl
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
func newAclPathError(name string, r int, err error) error {
|
||||||
|
pathError := &os.PathError{Path: name, Err: err}
|
||||||
|
switch r {
|
||||||
|
case 0:
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case -1:
|
||||||
|
pathError.Op = "acl_get_file"
|
||||||
|
case -2:
|
||||||
|
pathError.Op = "acl_get_tag_type"
|
||||||
|
case -3:
|
||||||
|
pathError.Op = "acl_get_qualifier"
|
||||||
|
case -4:
|
||||||
|
pathError.Op = "acl_delete_entry"
|
||||||
|
case -5:
|
||||||
|
pathError.Op = "acl_create_entry"
|
||||||
|
case -6:
|
||||||
|
pathError.Op = "acl_get_permset"
|
||||||
|
case -7:
|
||||||
|
pathError.Op = "acl_add_perm"
|
||||||
|
case -8:
|
||||||
|
pathError.Op = "acl_set_tag_type"
|
||||||
|
case -9:
|
||||||
|
pathError.Op = "acl_set_qualifier"
|
||||||
|
case -10:
|
||||||
|
pathError.Op = "acl_calc_mask"
|
||||||
|
case -11:
|
||||||
|
pathError.Op = "acl_valid"
|
||||||
|
case -12:
|
||||||
|
pathError.Op = "acl_set_file"
|
||||||
|
|
||||||
|
default: // unreachable
|
||||||
|
pathError.Op = "setfacl"
|
||||||
|
}
|
||||||
|
return pathError
|
||||||
|
}
|
||||||
60
system/acl/libacl-helper_test.go
Normal file
60
system/acl/libacl-helper_test.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package acl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewAclPathError(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
r int
|
||||||
|
err error
|
||||||
|
want error
|
||||||
|
}{
|
||||||
|
{"nil", container.Nonexistent, 0, syscall.ENOTRECOVERABLE, nil},
|
||||||
|
|
||||||
|
{"acl_get_file", container.Nonexistent, -1, syscall.ENOTRECOVERABLE,
|
||||||
|
&os.PathError{Op: "acl_get_file", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}},
|
||||||
|
{"acl_get_tag_type", container.Nonexistent, -2, syscall.ENOTRECOVERABLE,
|
||||||
|
&os.PathError{Op: "acl_get_tag_type", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}},
|
||||||
|
{"acl_get_qualifier", container.Nonexistent, -3, syscall.ENOTRECOVERABLE,
|
||||||
|
&os.PathError{Op: "acl_get_qualifier", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}},
|
||||||
|
{"acl_delete_entry", container.Nonexistent, -4, syscall.ENOTRECOVERABLE,
|
||||||
|
&os.PathError{Op: "acl_delete_entry", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}},
|
||||||
|
{"acl_create_entry", container.Nonexistent, -5, syscall.ENOTRECOVERABLE,
|
||||||
|
&os.PathError{Op: "acl_create_entry", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}},
|
||||||
|
{"acl_get_permset", container.Nonexistent, -6, syscall.ENOTRECOVERABLE,
|
||||||
|
&os.PathError{Op: "acl_get_permset", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}},
|
||||||
|
{"acl_add_perm", container.Nonexistent, -7, syscall.ENOTRECOVERABLE,
|
||||||
|
&os.PathError{Op: "acl_add_perm", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}},
|
||||||
|
{"acl_set_tag_type", container.Nonexistent, -8, syscall.ENOTRECOVERABLE,
|
||||||
|
&os.PathError{Op: "acl_set_tag_type", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}},
|
||||||
|
{"acl_set_qualifier", container.Nonexistent, -9, syscall.ENOTRECOVERABLE,
|
||||||
|
&os.PathError{Op: "acl_set_qualifier", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}},
|
||||||
|
{"acl_calc_mask", container.Nonexistent, -10, syscall.ENOTRECOVERABLE,
|
||||||
|
&os.PathError{Op: "acl_calc_mask", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}},
|
||||||
|
{"acl_valid", container.Nonexistent, -11, syscall.ENOTRECOVERABLE,
|
||||||
|
&os.PathError{Op: "acl_valid", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}},
|
||||||
|
{"acl_set_file", container.Nonexistent, -12, syscall.ENOTRECOVERABLE,
|
||||||
|
&os.PathError{Op: "acl_set_file", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}},
|
||||||
|
|
||||||
|
{"acl", container.Nonexistent, -13, syscall.ENOTRECOVERABLE,
|
||||||
|
&os.PathError{Op: "setfacl", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}},
|
||||||
|
{"invalid", container.Nonexistent, -0xdeadbeef, nil,
|
||||||
|
&os.PathError{Op: "setfacl", Path: container.Nonexistent}},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := newAclPathError(tc.path, tc.r, tc.err)
|
||||||
|
if !reflect.DeepEqual(err, tc.want) {
|
||||||
|
t.Errorf("newAclPathError: %v, want %v", err, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
30
system/acl/perms_test.go
Normal file
30
system/acl/perms_test.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package acl_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/system/acl"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPerms(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
perms acl.Perms
|
||||||
|
}{
|
||||||
|
{"---", acl.Perms{}},
|
||||||
|
{"r--", acl.Perms{acl.Read}},
|
||||||
|
{"-w-", acl.Perms{acl.Write}},
|
||||||
|
{"--x", acl.Perms{acl.Execute}},
|
||||||
|
{"rw-", acl.Perms{acl.Read, acl.Read, acl.Write}},
|
||||||
|
{"r-x", acl.Perms{acl.Read, acl.Execute, acl.Execute}},
|
||||||
|
{"-wx", acl.Perms{acl.Write, acl.Write, acl.Execute, acl.Execute}},
|
||||||
|
{"rwx", acl.Perms{acl.Read, acl.Write, acl.Execute}},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if got := tc.perms.String(); got != tc.name {
|
||||||
|
t.Errorf("String: %q, want %q", got, tc.name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,91 +1,182 @@
|
|||||||
package system
|
package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container/stub"
|
||||||
"hakurei.app/system/acl"
|
"hakurei.app/system/acl"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUpdatePerm(t *testing.T) {
|
func TestACLUpdateOp(t *testing.T) {
|
||||||
testCases := []struct {
|
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||||
path string
|
{"apply aclUpdate", 0xdeadbeef, 0xff,
|
||||||
perms []acl.Perm
|
&aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}, []stub.Call{
|
||||||
}{
|
call("verbose", stub.ExpectArgs{[]any{"applying ACL", &aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}}}, nil, nil),
|
||||||
{"/run/user/1971/hakurei", []acl.Perm{acl.Execute}},
|
call("aclUpdate", stub.ExpectArgs{"/proc/nonexistent", 0xdeadbeef, []acl.Perm{acl.Read, acl.Write, acl.Execute}}, nil, stub.UniqueError(1)),
|
||||||
{"/tmp/hakurei.1971/tmpdir/150", []acl.Perm{acl.Read, acl.Write, acl.Execute}},
|
}, &OpError{Op: "acl", Err: stub.UniqueError(1)}, nil, nil},
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
{"revert aclUpdate", 0xdeadbeef, 0xff,
|
||||||
t.Run(tc.path+permSubTestSuffix(tc.perms), func(t *testing.T) {
|
&aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}, []stub.Call{
|
||||||
sys := New(150)
|
call("verbose", stub.ExpectArgs{[]any{"applying ACL", &aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}}}, nil, nil),
|
||||||
sys.UpdatePerm(tc.path, tc.perms...)
|
call("aclUpdate", stub.ExpectArgs{"/proc/nonexistent", 0xdeadbeef, []acl.Perm{acl.Read, acl.Write, acl.Execute}}, nil, nil),
|
||||||
(&tcOp{Process, tc.path}).test(t, sys.ops, []Op{&ACL{Process, tc.path, tc.perms}}, "UpdatePerm")
|
}, nil, []stub.Call{
|
||||||
})
|
call("verbose", stub.ExpectArgs{[]any{"stripping ACL", &aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}}}, nil, nil),
|
||||||
}
|
call("aclUpdate", stub.ExpectArgs{"/proc/nonexistent", 0xdeadbeef, ([]acl.Perm)(nil)}, nil, stub.UniqueError(0)),
|
||||||
}
|
}, &OpError{Op: "acl", Err: stub.UniqueError(0), Revert: true}},
|
||||||
|
|
||||||
func TestUpdatePermType(t *testing.T) {
|
{"success revert skip", 0xdeadbeef, Process,
|
||||||
testCases := []struct {
|
&aclUpdateOp{User, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}, []stub.Call{
|
||||||
perms []acl.Perm
|
call("verbose", stub.ExpectArgs{[]any{"applying ACL", &aclUpdateOp{User, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}}}, nil, nil),
|
||||||
tcOp
|
call("aclUpdate", stub.ExpectArgs{"/proc/nonexistent", 0xdeadbeef, []acl.Perm{acl.Read, acl.Write, acl.Execute}}, nil, nil),
|
||||||
}{
|
}, nil, []stub.Call{
|
||||||
{[]acl.Perm{acl.Execute}, tcOp{User, "/tmp/hakurei.1971/tmpdir"}},
|
call("verbose", stub.ExpectArgs{[]any{"skipping ACL", &aclUpdateOp{User, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}}}, nil, nil),
|
||||||
{[]acl.Perm{acl.Read, acl.Write, acl.Execute}, tcOp{User, "/tmp/hakurei.1971/tmpdir/150"}},
|
}, nil},
|
||||||
{[]acl.Perm{acl.Execute}, tcOp{Process, "/run/user/1971/hakurei/fcb8a12f7c482d183ade8288c3de78b5"}},
|
|
||||||
{[]acl.Perm{acl.Read}, tcOp{Process, "/tmp/hakurei.1971/fcb8a12f7c482d183ade8288c3de78b5/passwd"}},
|
{"success revert aclUpdate ENOENT", 0xdeadbeef, 0xff,
|
||||||
{[]acl.Perm{acl.Read}, tcOp{Process, "/tmp/hakurei.1971/fcb8a12f7c482d183ade8288c3de78b5/group"}},
|
&aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}, []stub.Call{
|
||||||
{[]acl.Perm{acl.Read, acl.Write, acl.Execute}, tcOp{EWayland, "/run/user/1971/wayland-0"}},
|
call("verbose", stub.ExpectArgs{[]any{"applying ACL", &aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}}}, nil, nil),
|
||||||
}
|
call("aclUpdate", stub.ExpectArgs{"/proc/nonexistent", 0xdeadbeef, []acl.Perm{acl.Read, acl.Write, acl.Execute}}, nil, nil),
|
||||||
|
}, nil, []stub.Call{
|
||||||
for _, tc := range testCases {
|
call("verbose", stub.ExpectArgs{[]any{"stripping ACL", &aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}}}, nil, nil),
|
||||||
t.Run(tc.path+"_"+TypeString(tc.et)+permSubTestSuffix(tc.perms), func(t *testing.T) {
|
call("aclUpdate", stub.ExpectArgs{"/proc/nonexistent", 0xdeadbeef, ([]acl.Perm)(nil)}, nil, &os.PathError{Op: "acl_get_file", Path: "/proc/nonexistent", Err: syscall.ENOENT}),
|
||||||
sys := New(150)
|
call("verbosef", stub.ExpectArgs{"target of ACL %s no longer exists", []any{&aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}}}, nil, nil),
|
||||||
sys.UpdatePermType(tc.et, tc.path, tc.perms...)
|
}, nil},
|
||||||
tc.test(t, sys.ops, []Op{&ACL{tc.et, tc.path, tc.perms}}, "UpdatePermType")
|
|
||||||
})
|
{"success", 0xdeadbeef, 0xff,
|
||||||
}
|
&aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}, []stub.Call{
|
||||||
}
|
call("verbose", stub.ExpectArgs{[]any{"applying ACL", &aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}}}, nil, nil),
|
||||||
|
call("aclUpdate", stub.ExpectArgs{"/proc/nonexistent", 0xdeadbeef, []acl.Perm{acl.Read, acl.Write, acl.Execute}}, nil, nil),
|
||||||
func TestACLString(t *testing.T) {
|
}, nil, []stub.Call{
|
||||||
testCases := []struct {
|
call("verbose", stub.ExpectArgs{[]any{"stripping ACL", &aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}}}, nil, nil),
|
||||||
want string
|
call("aclUpdate", stub.ExpectArgs{"/proc/nonexistent", 0xdeadbeef, ([]acl.Perm)(nil)}, nil, nil),
|
||||||
et Enablement
|
}, nil},
|
||||||
perms []acl.Perm
|
})
|
||||||
}{
|
|
||||||
{`--- type: process path: "/proc/nonexistent"`, Process, []acl.Perm{}},
|
checkOpsBuilder(t, "UpdatePermType", []opsBuilderTestCase{
|
||||||
{`r-- type: user path: "/proc/nonexistent"`, User, []acl.Perm{acl.Read}},
|
{"simple",
|
||||||
{`-w- type: wayland path: "/proc/nonexistent"`, EWayland, []acl.Perm{acl.Write}},
|
0xdeadbeef,
|
||||||
{`--x type: x11 path: "/proc/nonexistent"`, EX11, []acl.Perm{acl.Execute}},
|
func(_ *testing.T, sys *I) {
|
||||||
{`rw- type: dbus path: "/proc/nonexistent"`, EDBus, []acl.Perm{acl.Read, acl.Write}},
|
sys.
|
||||||
{`r-x type: pulseaudio path: "/proc/nonexistent"`, EPulse, []acl.Perm{acl.Read, acl.Execute}},
|
UpdatePerm("/run/user/1971/hakurei", acl.Execute).
|
||||||
{`rwx type: user path: "/proc/nonexistent"`, User, []acl.Perm{acl.Read, acl.Write, acl.Execute}},
|
UpdatePerm("/tmp/hakurei.0/tmpdir/150", acl.Read, acl.Write, acl.Execute)
|
||||||
{`rwx type: process path: "/proc/nonexistent"`, Process, []acl.Perm{acl.Read, acl.Write, acl.Write, acl.Execute}},
|
}, []Op{
|
||||||
}
|
&aclUpdateOp{Process, "/run/user/1971/hakurei", []acl.Perm{acl.Execute}},
|
||||||
|
&aclUpdateOp{Process, "/tmp/hakurei.0/tmpdir/150", []acl.Perm{acl.Read, acl.Write, acl.Execute}},
|
||||||
for _, tc := range testCases {
|
}, stub.Expect{}},
|
||||||
t.Run(tc.want, func(t *testing.T) {
|
|
||||||
a := &ACL{et: tc.et, perms: tc.perms, path: container.Nonexistent}
|
{"tmpdirp", 0xdeadbeef, func(_ *testing.T, sys *I) {
|
||||||
if got := a.String(); got != tc.want {
|
sys.UpdatePermType(User, "/tmp/hakurei.0/tmpdir", acl.Execute)
|
||||||
t.Errorf("String() = %v, want %v",
|
}, []Op{
|
||||||
got, tc.want)
|
&aclUpdateOp{User, "/tmp/hakurei.0/tmpdir", []acl.Perm{acl.Execute}},
|
||||||
}
|
}, stub.Expect{}},
|
||||||
})
|
|
||||||
}
|
{"tmpdir", 0xdeadbeef, func(_ *testing.T, sys *I) {
|
||||||
}
|
sys.UpdatePermType(User, "/tmp/hakurei.0/tmpdir/150", acl.Read, acl.Write, acl.Execute)
|
||||||
|
}, []Op{
|
||||||
func permSubTestSuffix(perms []acl.Perm) (suffix string) {
|
&aclUpdateOp{User, "/tmp/hakurei.0/tmpdir/150", []acl.Perm{acl.Read, acl.Write, acl.Execute}},
|
||||||
for _, perm := range perms {
|
}, stub.Expect{}},
|
||||||
switch perm {
|
|
||||||
case acl.Read:
|
{"share", 0xdeadbeef, func(_ *testing.T, sys *I) {
|
||||||
suffix += "_read"
|
sys.UpdatePermType(Process, "/run/user/1971/hakurei/fcb8a12f7c482d183ade8288c3de78b5", acl.Execute)
|
||||||
case acl.Write:
|
}, []Op{
|
||||||
suffix += "_write"
|
&aclUpdateOp{Process, "/run/user/1971/hakurei/fcb8a12f7c482d183ade8288c3de78b5", []acl.Perm{acl.Execute}},
|
||||||
case acl.Execute:
|
}, stub.Expect{}},
|
||||||
suffix += "_execute"
|
|
||||||
default:
|
{"passwd", 0xdeadbeef, func(_ *testing.T, sys *I) {
|
||||||
panic("unreachable")
|
sys.
|
||||||
}
|
UpdatePermType(Process, "/tmp/hakurei.0/fcb8a12f7c482d183ade8288c3de78b5/passwd", acl.Read).
|
||||||
}
|
UpdatePermType(Process, "/tmp/hakurei.0/fcb8a12f7c482d183ade8288c3de78b5/group", acl.Read)
|
||||||
return
|
}, []Op{
|
||||||
|
&aclUpdateOp{Process, "/tmp/hakurei.0/fcb8a12f7c482d183ade8288c3de78b5/passwd", []acl.Perm{acl.Read}},
|
||||||
|
&aclUpdateOp{Process, "/tmp/hakurei.0/fcb8a12f7c482d183ade8288c3de78b5/group", []acl.Perm{acl.Read}},
|
||||||
|
}, stub.Expect{}},
|
||||||
|
|
||||||
|
{"wayland", 0xdeadbeef, func(_ *testing.T, sys *I) {
|
||||||
|
sys.UpdatePermType(EWayland, "/run/user/1971/wayland-0", acl.Read, acl.Write, acl.Execute)
|
||||||
|
}, []Op{
|
||||||
|
&aclUpdateOp{EWayland, "/run/user/1971/wayland-0", []acl.Perm{acl.Read, acl.Write, acl.Execute}},
|
||||||
|
}, stub.Expect{}},
|
||||||
|
})
|
||||||
|
|
||||||
|
checkOpIs(t, []opIsTestCase{
|
||||||
|
{"nil", (*aclUpdateOp)(nil), (*aclUpdateOp)(nil), false},
|
||||||
|
{"zero", new(aclUpdateOp), new(aclUpdateOp), true},
|
||||||
|
|
||||||
|
{"et differs",
|
||||||
|
&aclUpdateOp{
|
||||||
|
EWayland, "/run/user/1971/wayland-0",
|
||||||
|
[]acl.Perm{acl.Read, acl.Write, acl.Execute},
|
||||||
|
}, &aclUpdateOp{
|
||||||
|
EX11, "/run/user/1971/wayland-0",
|
||||||
|
[]acl.Perm{acl.Read, acl.Write, acl.Execute},
|
||||||
|
}, false},
|
||||||
|
|
||||||
|
{"path differs", &aclUpdateOp{
|
||||||
|
EWayland, "/run/user/1971/wayland-0",
|
||||||
|
[]acl.Perm{acl.Read, acl.Write, acl.Execute},
|
||||||
|
}, &aclUpdateOp{
|
||||||
|
EWayland, "/run/user/1971/wayland-1",
|
||||||
|
[]acl.Perm{acl.Read, acl.Write, acl.Execute},
|
||||||
|
}, false},
|
||||||
|
|
||||||
|
{"perms differs", &aclUpdateOp{
|
||||||
|
EWayland, "/run/user/1971/wayland-0",
|
||||||
|
[]acl.Perm{acl.Read, acl.Write, acl.Execute},
|
||||||
|
}, &aclUpdateOp{
|
||||||
|
EWayland, "/run/user/1971/wayland-0",
|
||||||
|
[]acl.Perm{acl.Read, acl.Write},
|
||||||
|
}, false},
|
||||||
|
|
||||||
|
{"equals", &aclUpdateOp{
|
||||||
|
EWayland, "/run/user/1971/wayland-0",
|
||||||
|
[]acl.Perm{acl.Read, acl.Write, acl.Execute},
|
||||||
|
}, &aclUpdateOp{
|
||||||
|
EWayland, "/run/user/1971/wayland-0",
|
||||||
|
[]acl.Perm{acl.Read, acl.Write, acl.Execute},
|
||||||
|
}, true},
|
||||||
|
})
|
||||||
|
|
||||||
|
checkOpMeta(t, []opMetaTestCase{
|
||||||
|
{"clear",
|
||||||
|
&aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{}},
|
||||||
|
Process, "/proc/nonexistent",
|
||||||
|
`--- type: process path: "/proc/nonexistent"`},
|
||||||
|
|
||||||
|
{"read",
|
||||||
|
&aclUpdateOp{User, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/0", []acl.Perm{acl.Read}},
|
||||||
|
User, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/0",
|
||||||
|
`r-- type: user path: "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/0"`},
|
||||||
|
|
||||||
|
{"write",
|
||||||
|
&aclUpdateOp{User, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/1", []acl.Perm{acl.Write}},
|
||||||
|
User, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/1",
|
||||||
|
`-w- type: user path: "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/1"`},
|
||||||
|
|
||||||
|
{"execute",
|
||||||
|
&aclUpdateOp{User, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/2", []acl.Perm{acl.Execute}},
|
||||||
|
User, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/2",
|
||||||
|
`--x type: user path: "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/2"`},
|
||||||
|
|
||||||
|
{"wayland",
|
||||||
|
&aclUpdateOp{EWayland, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/wayland", []acl.Perm{acl.Read, acl.Write}},
|
||||||
|
EWayland, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/wayland",
|
||||||
|
`rw- type: wayland path: "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/wayland"`},
|
||||||
|
|
||||||
|
{"x11",
|
||||||
|
&aclUpdateOp{EX11, "/tmp/.X11-unix/X0", []acl.Perm{acl.Read, acl.Execute}},
|
||||||
|
EX11, "/tmp/.X11-unix/X0",
|
||||||
|
`r-x type: x11 path: "/tmp/.X11-unix/X0"`},
|
||||||
|
|
||||||
|
{"dbus",
|
||||||
|
&aclUpdateOp{EDBus, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/bus", []acl.Perm{acl.Write, acl.Execute}},
|
||||||
|
EDBus, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/bus",
|
||||||
|
`-wx type: dbus path: "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/bus"`},
|
||||||
|
|
||||||
|
{"pulseaudio",
|
||||||
|
&aclUpdateOp{EPulse, "/run/user/1971/hakurei/27d81d567f8fae7f33278eec45da9446/pulse", []acl.Perm{acl.Read, acl.Write, acl.Execute}},
|
||||||
|
EPulse, "/run/user/1971/hakurei/27d81d567f8fae7f33278eec45da9446/pulse",
|
||||||
|
`rwx type: pulseaudio path: "/run/user/1971/hakurei/27d81d567f8fae7f33278eec45da9446/pulse"`},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
155
system/dbus.go
155
system/dbus.go
@@ -4,11 +4,14 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
"hakurei.app/system/dbus"
|
"hakurei.app/system/dbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,6 +19,7 @@ var (
|
|||||||
ErrDBusConfig = errors.New("dbus config not supplied")
|
ErrDBusConfig = errors.New("dbus config not supplied")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// MustProxyDBus calls ProxyDBus and panics if an error is returned.
|
||||||
func (sys *I) MustProxyDBus(sessionPath string, session *dbus.Config, systemPath string, system *dbus.Config) *I {
|
func (sys *I) MustProxyDBus(sessionPath string, session *dbus.Config, systemPath string, system *dbus.Config) *I {
|
||||||
if _, err := sys.ProxyDBus(session, system, sessionPath, systemPath); err != nil {
|
if _, err := sys.ProxyDBus(session, system, sessionPath, systemPath); err != nil {
|
||||||
panic(err.Error())
|
panic(err.Error())
|
||||||
@@ -24,35 +28,40 @@ func (sys *I) MustProxyDBus(sessionPath string, session *dbus.Config, systemPath
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProxyDBus finalises configuration ahead of time and starts xdg-dbus-proxy via [dbus] and terminates it on revert.
|
||||||
|
// This [Op] is always [Process] scoped.
|
||||||
func (sys *I) ProxyDBus(session, system *dbus.Config, sessionPath, systemPath string) (func(), error) {
|
func (sys *I) ProxyDBus(session, system *dbus.Config, sessionPath, systemPath string) (func(), error) {
|
||||||
d := new(DBus)
|
d := new(dbusProxyOp)
|
||||||
|
|
||||||
// session bus is required as otherwise this is effectively a very expensive noop
|
// session bus is required as otherwise this is effectively a very expensive noop
|
||||||
if session == nil {
|
if session == nil {
|
||||||
return nil, msg.WrapErr(ErrDBusConfig,
|
return nil, newOpErrorMessage("dbus", ErrDBusConfig,
|
||||||
"attempted to create message bus proxy args without session bus config")
|
"attempted to create message bus proxy args without session bus config", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// system bus is optional
|
// system bus is optional
|
||||||
d.system = system != nil
|
d.system = system != nil
|
||||||
|
|
||||||
d.sessionBus[0], d.systemBus[0] = dbus.Address()
|
var sessionBus, systemBus dbus.ProxyPair
|
||||||
d.sessionBus[1], d.systemBus[1] = sessionPath, systemPath
|
sessionBus[0], systemBus[0] = sys.dbusAddress()
|
||||||
d.out = &scanToFmsg{msg: new(strings.Builder)}
|
sessionBus[1], systemBus[1] = sessionPath, systemPath
|
||||||
if final, err := dbus.Finalise(d.sessionBus, d.systemBus, session, system); err != nil {
|
d.out = &linePrefixWriter{println: log.Println, prefix: "(dbus) ", buf: new(strings.Builder)}
|
||||||
|
if final, err := sys.dbusFinalise(sessionBus, systemBus, session, system); err != nil {
|
||||||
if errors.Is(err, syscall.EINVAL) {
|
if errors.Is(err, syscall.EINVAL) {
|
||||||
return nil, msg.WrapErr(err, "message bus proxy configuration contains NUL byte")
|
return nil, newOpErrorMessage("dbus", err,
|
||||||
|
"message bus proxy configuration contains NUL byte", false)
|
||||||
}
|
}
|
||||||
return nil, wrapErrSuffix(err, "cannot finalise message bus proxy:")
|
return nil, newOpErrorMessage("dbus", err,
|
||||||
|
fmt.Sprintf("cannot finalise message bus proxy: %v", err), false)
|
||||||
} else {
|
} else {
|
||||||
if msg.IsVerbose() {
|
if sys.isVerbose() {
|
||||||
msg.Verbose("session bus proxy:", session.Args(d.sessionBus))
|
sys.verbose("session bus proxy:", session.Args(sessionBus))
|
||||||
if system != nil {
|
if system != nil {
|
||||||
msg.Verbose("system bus proxy:", system.Args(d.systemBus))
|
sys.verbose("system bus proxy:", system.Args(systemBus))
|
||||||
}
|
}
|
||||||
|
|
||||||
// this calls the argsWt String method
|
// this calls the argsWt String method
|
||||||
msg.Verbose("message bus proxy final args:", final.WriterTo)
|
sys.verbose("message bus proxy final args:", final.WriterTo)
|
||||||
}
|
}
|
||||||
|
|
||||||
d.final = final
|
d.final = final
|
||||||
@@ -62,100 +71,130 @@ func (sys *I) ProxyDBus(session, system *dbus.Config, sessionPath, systemPath st
|
|||||||
return d.out.Dump, nil
|
return d.out.Dump, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type DBus struct {
|
// dbusProxyOp implements [I.ProxyDBus].
|
||||||
|
type dbusProxyOp struct {
|
||||||
proxy *dbus.Proxy // populated during apply
|
proxy *dbus.Proxy // populated during apply
|
||||||
|
|
||||||
final *dbus.Final
|
final *dbus.Final
|
||||||
out *scanToFmsg
|
out *linePrefixWriter
|
||||||
// whether system bus proxy is enabled
|
// whether system bus proxy is enabled
|
||||||
system bool
|
system bool
|
||||||
|
|
||||||
sessionBus, systemBus dbus.ProxyPair
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DBus) Type() Enablement { return Process }
|
func (d *dbusProxyOp) Type() Enablement { return Process }
|
||||||
|
|
||||||
func (d *DBus) apply(sys *I) error {
|
func (d *dbusProxyOp) apply(sys *I) error {
|
||||||
msg.Verbosef("session bus proxy on %q for upstream %q", d.sessionBus[1], d.sessionBus[0])
|
sys.verbosef("session bus proxy on %q for upstream %q", d.final.Session[1], d.final.Session[0])
|
||||||
if d.system {
|
if d.system {
|
||||||
msg.Verbosef("system bus proxy on %q for upstream %q", d.systemBus[1], d.systemBus[0])
|
sys.verbosef("system bus proxy on %q for upstream %q", d.final.System[1], d.final.System[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
d.proxy = dbus.New(sys.ctx, d.final, d.out)
|
d.proxy = dbus.New(sys.ctx, d.final, d.out)
|
||||||
if err := d.proxy.Start(); err != nil {
|
if err := sys.dbusProxyStart(d.proxy); err != nil {
|
||||||
d.out.Dump()
|
d.out.Dump()
|
||||||
return wrapErrSuffix(err,
|
return newOpErrorMessage("dbus", err,
|
||||||
"cannot start message bus proxy:")
|
fmt.Sprintf("cannot start message bus proxy: %v", err), false)
|
||||||
}
|
}
|
||||||
msg.Verbose("starting message bus proxy", d.proxy)
|
sys.verbose("starting message bus proxy", d.proxy)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DBus) revert(*I, *Criteria) error {
|
func (d *dbusProxyOp) revert(sys *I, _ *Criteria) error {
|
||||||
// criteria ignored here since dbus is always process-scoped
|
// criteria ignored here since dbus is always process-scoped
|
||||||
msg.Verbose("terminating message bus proxy")
|
sys.verbose("terminating message bus proxy")
|
||||||
d.proxy.Close()
|
sys.dbusProxyClose(d.proxy)
|
||||||
defer msg.Verbose("message bus proxy exit")
|
|
||||||
err := d.proxy.Wait()
|
exitMessage := "message bus proxy exit"
|
||||||
|
defer func() { sys.verbose(exitMessage) }()
|
||||||
|
|
||||||
|
err := sys.dbusProxyWait(d.proxy)
|
||||||
if errors.Is(err, context.Canceled) {
|
if errors.Is(err, context.Canceled) {
|
||||||
msg.Verbose("message bus proxy canceled upstream")
|
exitMessage = "message bus proxy canceled upstream"
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
return wrapErrSuffix(err, "message bus proxy error:")
|
return newOpErrorMessage("dbus", err,
|
||||||
|
fmt.Sprintf("message bus proxy error: %v", err), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DBus) Is(o Op) bool {
|
func (d *dbusProxyOp) Is(o Op) bool {
|
||||||
d0, ok := o.(*DBus)
|
target, ok := o.(*dbusProxyOp)
|
||||||
return ok && d0 != nil &&
|
return ok && d != nil && target != nil &&
|
||||||
((d.proxy == nil && d0.proxy == nil) ||
|
d.system == target.system &&
|
||||||
(d.proxy != nil && d0.proxy != nil && d.proxy.String() == d0.proxy.String()))
|
d.final != nil && target.final != nil &&
|
||||||
|
d.final.Session == target.final.Session &&
|
||||||
|
d.final.System == target.final.System &&
|
||||||
|
dbus.EqualAddrEntries(d.final.SessionUpstream, target.final.SessionUpstream) &&
|
||||||
|
dbus.EqualAddrEntries(d.final.SystemUpstream, target.final.SystemUpstream) &&
|
||||||
|
reflect.DeepEqual(d.final.WriterTo, target.final.WriterTo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DBus) Path() string {
|
func (d *dbusProxyOp) Path() string { return container.Nonexistent }
|
||||||
return "(dbus proxy)"
|
func (d *dbusProxyOp) String() string { return d.proxy.String() }
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DBus) String() string {
|
const (
|
||||||
return d.proxy.String()
|
// lpwSizeThreshold is the threshold of bytes written to linePrefixWriter which,
|
||||||
}
|
// if reached or exceeded, causes linePrefixWriter to drop all future writes.
|
||||||
|
lpwSizeThreshold = 1 << 24
|
||||||
|
)
|
||||||
|
|
||||||
type scanToFmsg struct {
|
// linePrefixWriter calls println with a prefix for every line written.
|
||||||
msg *strings.Builder
|
type linePrefixWriter struct {
|
||||||
msgbuf []string
|
prefix string
|
||||||
|
println func(v ...any)
|
||||||
|
|
||||||
|
n int
|
||||||
|
msg []string
|
||||||
|
buf *strings.Builder
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *scanToFmsg) Write(p []byte) (n int, err error) {
|
func (s *linePrefixWriter) Write(p []byte) (n int, err error) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
return s.write(p, 0)
|
return s.write(p, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *scanToFmsg) write(p []byte, a int) (int, error) {
|
func (s *linePrefixWriter) write(p []byte, a int) (int, error) {
|
||||||
|
if s.n >= lpwSizeThreshold {
|
||||||
|
if len(p) == 0 {
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
return a, syscall.ENOMEM
|
||||||
|
}
|
||||||
|
|
||||||
if i := bytes.IndexByte(p, '\n'); i == -1 {
|
if i := bytes.IndexByte(p, '\n'); i == -1 {
|
||||||
n, _ := s.msg.Write(p)
|
n, _ := s.buf.Write(p)
|
||||||
|
s.n += n
|
||||||
return a + n, nil
|
return a + n, nil
|
||||||
} else {
|
} else {
|
||||||
n, _ := s.msg.Write(p[:i])
|
n, _ := s.buf.Write(p[:i])
|
||||||
|
s.n += n + 1
|
||||||
|
|
||||||
// allow container init messages through
|
v := s.buf.String()
|
||||||
v := s.msg.String()
|
|
||||||
if strings.HasPrefix(v, "init: ") {
|
if strings.HasPrefix(v, "init: ") {
|
||||||
log.Println("(dbus) " + v)
|
s.n -= len(v) + 1
|
||||||
|
// pass through container init messages
|
||||||
|
s.println(s.prefix + v)
|
||||||
} else {
|
} else {
|
||||||
s.msgbuf = append(s.msgbuf, v)
|
s.msg = append(s.msg, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.msg.Reset()
|
s.buf.Reset()
|
||||||
return s.write(p[i+1:], a+n+1)
|
return s.write(p[i+1:], a+n+1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *scanToFmsg) Dump() {
|
func (s *linePrefixWriter) Dump() {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
for _, msg := range s.msgbuf {
|
for _, m := range s.msg {
|
||||||
log.Println("(dbus) " + msg)
|
s.println(s.prefix + m)
|
||||||
|
}
|
||||||
|
if s.buf != nil && s.buf.Len() != 0 {
|
||||||
|
s.println("*" + s.prefix + s.buf.String())
|
||||||
|
}
|
||||||
|
if s.n >= lpwSizeThreshold {
|
||||||
|
s.println("+" + s.prefix + "write threshold reached, output may be incomplete")
|
||||||
}
|
}
|
||||||
s.mu.RUnlock()
|
s.mu.RUnlock()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,13 @@ type AddrEntry struct {
|
|||||||
Values [][2]string `json:"values"`
|
Values [][2]string `json:"values"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EqualAddrEntries returns whether two slices of [AddrEntry] are equal.
|
||||||
|
func EqualAddrEntries(entries, target []AddrEntry) bool {
|
||||||
|
return slices.EqualFunc(entries, target, func(a AddrEntry, b AddrEntry) bool {
|
||||||
|
return a.Method == b.Method && slices.Equal(a.Values, b.Values)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Parse parses D-Bus address according to
|
// Parse parses D-Bus address according to
|
||||||
// https://dbus.freedesktop.org/doc/dbus-specification.html#addresses
|
// https://dbus.freedesktop.org/doc/dbus-specification.html#addresses
|
||||||
func Parse(addr []byte) ([]AddrEntry, error) {
|
func Parse(addr []byte) ([]AddrEntry, error) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user