Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
e58181a930 | |||
71e70b7b5f | |||
afa1a8043e | |||
1ba1cb8865 | |||
44ba7a5f02 | |||
dc467493d8 | |||
46cd3a28c8 |
@ -17,17 +17,29 @@ import (
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/app"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/internal/hlog"
|
||||
"hakurei.app/system"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
func buildCommand(ctx context.Context, out io.Writer) command.Command {
|
||||
func buildCommand(ctx context.Context, msg container.Msg, early *earlyHardeningErrs, out io.Writer) command.Command {
|
||||
var (
|
||||
flagVerbose bool
|
||||
flagJSON bool
|
||||
)
|
||||
c := command.New(out, log.Printf, "hakurei", func([]string) error { internal.InstallOutput(flagVerbose); return nil }).
|
||||
c := command.New(out, log.Printf, "hakurei", func([]string) error {
|
||||
msg.SwapVerbose(flagVerbose)
|
||||
|
||||
if early.yamaLSM != nil {
|
||||
msg.Verbosef("cannot enable ptrace protection via Yama LSM: %v", early.yamaLSM)
|
||||
// not fatal
|
||||
}
|
||||
|
||||
if early.dumpable != nil {
|
||||
log.Printf("cannot set SUID_DUMP_DISABLE: %s", early.dumpable)
|
||||
// not fatal
|
||||
}
|
||||
|
||||
return nil
|
||||
}).
|
||||
Flag(&flagVerbose, "v", command.BoolFlag(false), "Increase log verbosity").
|
||||
Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output in JSON when applicable")
|
||||
|
||||
@ -39,10 +51,10 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
|
||||
}
|
||||
|
||||
// config extraArgs...
|
||||
config := tryPath(args[0])
|
||||
config := tryPath(msg, args[0])
|
||||
config.Args = append(config.Args, args[1:]...)
|
||||
|
||||
app.Main(ctx, config)
|
||||
app.Main(ctx, msg, config)
|
||||
panic("unreachable")
|
||||
})
|
||||
|
||||
@ -78,9 +90,9 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
|
||||
passwd *user.User
|
||||
passwdOnce sync.Once
|
||||
passwdFunc = func() {
|
||||
us := strconv.Itoa(app.HsuUid(new(app.Hsu).MustID(), flagIdentity))
|
||||
us := strconv.Itoa(app.HsuUid(new(app.Hsu).MustIDMsg(msg), flagIdentity))
|
||||
if u, err := user.LookupId(us); err != nil {
|
||||
hlog.Verbosef("cannot look up uid %s", us)
|
||||
msg.Verbosef("cannot look up uid %s", us)
|
||||
passwd = &user.User{
|
||||
Uid: us,
|
||||
Gid: us,
|
||||
@ -115,18 +127,18 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
|
||||
config.Home = a
|
||||
}
|
||||
|
||||
var e system.Enablement
|
||||
var e hst.Enablement
|
||||
if flagWayland {
|
||||
e |= system.EWayland
|
||||
e |= hst.EWayland
|
||||
}
|
||||
if flagX11 {
|
||||
e |= system.EX11
|
||||
e |= hst.EX11
|
||||
}
|
||||
if flagDBus {
|
||||
e |= system.EDBus
|
||||
e |= hst.EDBus
|
||||
}
|
||||
if flagPulse {
|
||||
e |= system.EPulse
|
||||
e |= hst.EPulse
|
||||
}
|
||||
config.Enablements = hst.NewEnablements(e)
|
||||
|
||||
@ -162,7 +174,7 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
|
||||
}
|
||||
}
|
||||
|
||||
app.Main(ctx, config)
|
||||
app.Main(ctx, msg, config)
|
||||
panic("unreachable")
|
||||
}).
|
||||
Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"),
|
||||
@ -202,9 +214,9 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
|
||||
|
||||
case 1: // instance
|
||||
name := args[0]
|
||||
config, entry := tryShort(name)
|
||||
config, entry := tryShort(msg, name)
|
||||
if config == nil {
|
||||
config = tryPath(name)
|
||||
config = tryPath(msg, name)
|
||||
}
|
||||
printShowInstance(os.Stdout, time.Now().UTC(), entry, config, flagShort, flagJSON)
|
||||
|
||||
@ -219,8 +231,8 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
|
||||
var flagShort bool
|
||||
c.NewCommand("ps", "List active instances", func(args []string) error {
|
||||
var sc hst.Paths
|
||||
app.CopyPaths(&sc, new(app.Hsu).MustID())
|
||||
printPs(os.Stdout, time.Now().UTC(), state.NewMulti(sc.RunDirPath.String()), flagShort, flagJSON)
|
||||
app.CopyPaths().Copy(&sc, new(app.Hsu).MustID())
|
||||
printPs(os.Stdout, time.Now().UTC(), state.NewMulti(msg, sc.RunDirPath.String()), flagShort, flagJSON)
|
||||
return errSuccess
|
||||
}).Flag(&flagShort, "short", command.BoolFlag(false), "Print instance id")
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"hakurei.app/command"
|
||||
"hakurei.app/container"
|
||||
)
|
||||
|
||||
func TestHelp(t *testing.T) {
|
||||
@ -68,7 +69,7 @@ Flags:
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
out := new(bytes.Buffer)
|
||||
c := buildCommand(t.Context(), out)
|
||||
c := buildCommand(t.Context(), container.NewMsg(nil), new(earlyHardeningErrs), out)
|
||||
if err := c.Parse(tc.args); !errors.Is(err, command.ErrHelp) && !errors.Is(err, flag.ErrHelp) {
|
||||
t.Errorf("Parse: error = %v; want %v",
|
||||
err, command.ErrHelp)
|
||||
|
@ -13,8 +13,6 @@ import (
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -24,20 +22,20 @@ var (
|
||||
license string
|
||||
)
|
||||
|
||||
func init() { hlog.Prepare("hakurei") }
|
||||
// earlyHardeningErrs are errors collected while setting up early hardening feature.
|
||||
type earlyHardeningErrs struct{ yamaLSM, dumpable error }
|
||||
|
||||
func main() {
|
||||
// early init path, skips root check and duplicate PR_SET_DUMPABLE
|
||||
container.TryArgv0(hlog.Output{}, hlog.Prepare, internal.InstallOutput)
|
||||
container.TryArgv0(nil)
|
||||
|
||||
if err := container.SetPtracer(0); err != nil {
|
||||
hlog.Verbosef("cannot enable ptrace protection via Yama LSM: %v", err)
|
||||
// not fatal: this program runs as the privileged user
|
||||
}
|
||||
log.SetPrefix("hakurei: ")
|
||||
log.SetFlags(0)
|
||||
msg := container.NewMsg(log.Default())
|
||||
|
||||
if err := container.SetDumpable(container.SUID_DUMP_DISABLE); err != nil {
|
||||
log.Printf("cannot set SUID_DUMP_DISABLE: %s", err)
|
||||
// not fatal: this program runs as the privileged user
|
||||
early := earlyHardeningErrs{
|
||||
yamaLSM: container.SetPtracer(0),
|
||||
dumpable: container.SetDumpable(container.SUID_DUMP_DISABLE),
|
||||
}
|
||||
|
||||
if os.Geteuid() == 0 {
|
||||
@ -48,10 +46,10 @@ func main() {
|
||||
syscall.SIGINT, syscall.SIGTERM)
|
||||
defer stop() // unreachable
|
||||
|
||||
buildCommand(ctx, os.Stderr).MustParse(os.Args[1:], func(err error) {
|
||||
hlog.Verbosef("command returned %v", err)
|
||||
buildCommand(ctx, msg, &early, os.Stderr).MustParse(os.Args[1:], func(err error) {
|
||||
msg.Verbosef("command returned %v", err)
|
||||
if errors.Is(err, errSuccess) {
|
||||
hlog.BeforeExit()
|
||||
msg.BeforeExit()
|
||||
os.Exit(0)
|
||||
}
|
||||
// this catches faulty command handlers that fail to return before this point
|
||||
|
@ -10,20 +10,20 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
|
||||
func tryPath(name string) (config *hst.Config) {
|
||||
func tryPath(msg container.Msg, name string) (config *hst.Config) {
|
||||
var r io.Reader
|
||||
config = new(hst.Config)
|
||||
|
||||
if name != "-" {
|
||||
r = tryFd(name)
|
||||
r = tryFd(msg, name)
|
||||
if r == nil {
|
||||
hlog.Verbose("load configuration from file")
|
||||
msg.Verbose("load configuration from file")
|
||||
|
||||
if f, err := os.Open(name); err != nil {
|
||||
log.Fatalf("cannot access configuration file %q: %s", name, err)
|
||||
@ -49,14 +49,14 @@ func tryPath(name string) (config *hst.Config) {
|
||||
return
|
||||
}
|
||||
|
||||
func tryFd(name string) io.ReadCloser {
|
||||
func tryFd(msg container.Msg, name string) io.ReadCloser {
|
||||
if v, err := strconv.Atoi(name); err != nil {
|
||||
if !errors.Is(err, strconv.ErrSyntax) {
|
||||
hlog.Verbosef("name cannot be interpreted as int64: %v", err)
|
||||
msg.Verbosef("name cannot be interpreted as int64: %v", err)
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
hlog.Verbosef("trying config stream from %d", v)
|
||||
msg.Verbosef("trying config stream from %d", v)
|
||||
fd := uintptr(v)
|
||||
if _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_GETFD, 0); errno != 0 {
|
||||
if errors.Is(errno, syscall.EBADF) {
|
||||
@ -68,7 +68,7 @@ func tryFd(name string) io.ReadCloser {
|
||||
}
|
||||
}
|
||||
|
||||
func tryShort(name string) (config *hst.Config, entry *state.State) {
|
||||
func tryShort(msg container.Msg, name string) (config *hst.Config, entry *state.State) {
|
||||
likePrefix := false
|
||||
if len(name) <= 32 {
|
||||
likePrefix = true
|
||||
@ -86,11 +86,11 @@ func tryShort(name string) (config *hst.Config, entry *state.State) {
|
||||
|
||||
// try to match from state store
|
||||
if likePrefix && len(name) >= 8 {
|
||||
hlog.Verbose("argument looks like prefix")
|
||||
msg.Verbose("argument looks like prefix")
|
||||
|
||||
var sc hst.Paths
|
||||
app.CopyPaths(&sc, new(app.Hsu).MustID())
|
||||
s := state.NewMulti(sc.RunDirPath.String())
|
||||
app.CopyPaths().Copy(&sc, new(app.Hsu).MustID())
|
||||
s := state.NewMulti(msg, sc.RunDirPath.String())
|
||||
if entries, err := state.Join(s); err != nil {
|
||||
log.Printf("cannot join store: %v", err)
|
||||
// drop to fetch from file
|
||||
@ -104,7 +104,7 @@ func tryShort(name string) (config *hst.Config, entry *state.State) {
|
||||
break
|
||||
}
|
||||
|
||||
hlog.Verbosef("instance %s skipped", v)
|
||||
msg.Verbosef("instance %s skipped", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ func printShowSystem(output io.Writer, short, flagJSON bool) {
|
||||
defer t.MustFlush()
|
||||
|
||||
info := &hst.Info{User: new(app.Hsu).MustID()}
|
||||
app.CopyPaths(&info.Paths, info.User)
|
||||
app.CopyPaths().Copy(&info.Paths, info.User)
|
||||
|
||||
if flagJSON {
|
||||
printJSON(output, short, info)
|
||||
|
@ -259,8 +259,6 @@ App
|
||||
"container": {
|
||||
"hostname": "localhost",
|
||||
"wait_delay": -1,
|
||||
"seccomp_flags": 1,
|
||||
"seccomp_presets": 1,
|
||||
"seccomp_compat": true,
|
||||
"devel": true,
|
||||
"userns": true,
|
||||
@ -415,8 +413,6 @@ App
|
||||
"container": {
|
||||
"hostname": "localhost",
|
||||
"wait_delay": -1,
|
||||
"seccomp_flags": 1,
|
||||
"seccomp_presets": 1,
|
||||
"seccomp_compat": true,
|
||||
"devel": true,
|
||||
"userns": true,
|
||||
@ -625,8 +621,6 @@ func Test_printPs(t *testing.T) {
|
||||
"container": {
|
||||
"hostname": "localhost",
|
||||
"wait_delay": -1,
|
||||
"seccomp_flags": 1,
|
||||
"seccomp_presets": 1,
|
||||
"seccomp_compat": true,
|
||||
"devel": true,
|
||||
"userns": true,
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"os"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
@ -92,6 +91,7 @@ func (app *appInfo) toHst(pathSet *appPathSet, pathname *container.Absolute, arg
|
||||
Device: app.Device,
|
||||
Tty: app.Tty || flagDropShell,
|
||||
MapRealUID: app.MapRealUID,
|
||||
Multiarch: app.Multiarch,
|
||||
Filesystem: []hst.FilesystemConfigJSON{
|
||||
{FilesystemConfig: &hst.FSBind{Target: container.AbsFHSEtc, Source: pathSet.cacheDir.Append("etc"), Special: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: pathSet.nixPath.Append("store"), Target: pathNixStore}},
|
||||
@ -113,12 +113,6 @@ func (app *appInfo) toHst(pathSet *appPathSet, pathname *container.Absolute, arg
|
||||
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
|
||||
},
|
||||
}
|
||||
if app.Multiarch {
|
||||
config.Container.SeccompFlags |= seccomp.AllowMultiarch
|
||||
}
|
||||
if app.Bluetooth {
|
||||
config.Container.SeccompFlags |= seccomp.AllowBluetooth
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
|
@ -13,22 +13,21 @@ import (
|
||||
"hakurei.app/command"
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
|
||||
var (
|
||||
errSuccess = errors.New("success")
|
||||
)
|
||||
|
||||
func init() {
|
||||
hlog.Prepare("hpkg")
|
||||
func main() {
|
||||
log.SetPrefix("hpkg: ")
|
||||
log.SetFlags(0)
|
||||
msg := container.NewMsg(log.Default())
|
||||
|
||||
if err := os.Setenv("SHELL", pathShell.String()); err != nil {
|
||||
log.Fatalf("cannot set $SHELL: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
if os.Geteuid() == 0 {
|
||||
log.Fatal("this program must not run as root")
|
||||
}
|
||||
@ -41,7 +40,7 @@ func main() {
|
||||
flagVerbose bool
|
||||
flagDropShell bool
|
||||
)
|
||||
c := command.New(os.Stderr, log.Printf, "hpkg", func([]string) error { internal.InstallOutput(flagVerbose); return nil }).
|
||||
c := command.New(os.Stderr, log.Printf, "hpkg", func([]string) error { msg.SwapVerbose(flagVerbose); return nil }).
|
||||
Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console").
|
||||
Flag(&flagDropShell, "s", command.BoolFlag(false), "Drop to a shell in place of next hakurei action")
|
||||
|
||||
@ -90,12 +89,12 @@ func main() {
|
||||
}
|
||||
cleanup := func() {
|
||||
// should be faster than a native implementation
|
||||
mustRun(chmod, "-R", "+w", workDir.String())
|
||||
mustRun(rm, "-rf", workDir.String())
|
||||
mustRun(msg, chmod, "-R", "+w", workDir.String())
|
||||
mustRun(msg, rm, "-rf", workDir.String())
|
||||
}
|
||||
beforeRunFail.Store(&cleanup)
|
||||
|
||||
mustRun(tar, "-C", workDir.String(), "-xf", pkgPath)
|
||||
mustRun(msg, tar, "-C", workDir.String(), "-xf", pkgPath)
|
||||
|
||||
/*
|
||||
Parse bundle and app metadata, do pre-install checks.
|
||||
@ -148,10 +147,10 @@ func main() {
|
||||
}
|
||||
|
||||
// sec: should compare version string
|
||||
hlog.Verbosef("installing application %q version %q over local %q",
|
||||
msg.Verbosef("installing application %q version %q over local %q",
|
||||
bundle.ID, bundle.Version, a.Version)
|
||||
} else {
|
||||
hlog.Verbosef("application %q clean installation", bundle.ID)
|
||||
msg.Verbosef("application %q clean installation", bundle.ID)
|
||||
// sec: should install credentials
|
||||
}
|
||||
|
||||
@ -159,7 +158,7 @@ func main() {
|
||||
Setup steps for files owned by the target user.
|
||||
*/
|
||||
|
||||
withCacheDir(ctx, "install", []string{
|
||||
withCacheDir(ctx, msg, "install", []string{
|
||||
// export inner bundle path in the environment
|
||||
"export BUNDLE=" + hst.Tmp + "/bundle",
|
||||
// replace inner /etc
|
||||
@ -181,7 +180,7 @@ func main() {
|
||||
}, workDir, bundle, pathSet, flagDropShell, cleanup)
|
||||
|
||||
if bundle.GPU {
|
||||
withCacheDir(ctx, "mesa-wrappers", []string{
|
||||
withCacheDir(ctx, msg, "mesa-wrappers", []string{
|
||||
// link nixGL mesa wrappers
|
||||
"mkdir -p nix/.nixGL",
|
||||
"ln -s " + bundle.Mesa + "/bin/nixGLIntel nix/.nixGL/nixGL",
|
||||
@ -193,7 +192,7 @@ func main() {
|
||||
Activate home-manager generation.
|
||||
*/
|
||||
|
||||
withNixDaemon(ctx, "activate", []string{
|
||||
withNixDaemon(ctx, msg, "activate", []string{
|
||||
// clean up broken links
|
||||
"mkdir -p .local/state/{nix,home-manager}",
|
||||
"chmod -R +w .local/state/{nix,home-manager}",
|
||||
@ -261,7 +260,7 @@ func main() {
|
||||
*/
|
||||
|
||||
if a.GPU && flagAutoDrivers {
|
||||
withNixDaemon(ctx, "nix-gl", []string{
|
||||
withNixDaemon(ctx, msg, "nix-gl", []string{
|
||||
"mkdir -p /nix/.nixGL/auto",
|
||||
"rm -rf /nix/.nixGL/auto",
|
||||
"export NIXPKGS_ALLOW_UNFREE=1",
|
||||
@ -316,7 +315,7 @@ func main() {
|
||||
Spawn app.
|
||||
*/
|
||||
|
||||
mustRunApp(ctx, config, func() {})
|
||||
mustRunApp(ctx, msg, config, func() {})
|
||||
return errSuccess
|
||||
}).
|
||||
Flag(&flagDropShellNixGL, "s", command.BoolFlag(false), "Drop to a shell on nixGL build").
|
||||
@ -324,9 +323,9 @@ func main() {
|
||||
}
|
||||
|
||||
c.MustParse(os.Args[1:], func(err error) {
|
||||
hlog.Verbosef("command returned %v", err)
|
||||
msg.Verbosef("command returned %v", err)
|
||||
if errors.Is(err, errSuccess) {
|
||||
hlog.BeforeExit()
|
||||
msg.BeforeExit()
|
||||
os.Exit(0)
|
||||
}
|
||||
})
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
|
||||
const bash = "bash"
|
||||
@ -51,8 +50,8 @@ func lookPath(file string) string {
|
||||
|
||||
var beforeRunFail = new(atomic.Pointer[func()])
|
||||
|
||||
func mustRun(name string, arg ...string) {
|
||||
hlog.Verbosef("spawning process: %q %q", name, arg)
|
||||
func mustRun(msg container.Msg, name string, arg ...string) {
|
||||
msg.Verbosef("spawning process: %q %q", name, arg)
|
||||
cmd := exec.Command(name, arg...)
|
||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
|
@ -9,14 +9,14 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
|
||||
var hakureiPath = internal.MustHakureiPath()
|
||||
|
||||
func mustRunApp(ctx context.Context, config *hst.Config, beforeFail func()) {
|
||||
func mustRunApp(ctx context.Context, msg container.Msg, config *hst.Config, beforeFail func()) {
|
||||
var (
|
||||
cmd *exec.Cmd
|
||||
st io.WriteCloser
|
||||
@ -26,10 +26,10 @@ func mustRunApp(ctx context.Context, config *hst.Config, beforeFail func()) {
|
||||
beforeFail()
|
||||
log.Fatalf("cannot pipe: %v", err)
|
||||
} else {
|
||||
if hlog.Load() {
|
||||
cmd = exec.CommandContext(ctx, hakureiPath, "-v", "app", "3")
|
||||
if msg.IsVerbose() {
|
||||
cmd = exec.CommandContext(ctx, hakureiPath.String(), "-v", "app", "3")
|
||||
} else {
|
||||
cmd = exec.CommandContext(ctx, hakureiPath, "app", "3")
|
||||
cmd = exec.CommandContext(ctx, hakureiPath.String(), "app", "3")
|
||||
}
|
||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||
cmd.ExtraFiles = []*os.File{r}
|
||||
@ -51,7 +51,8 @@ func mustRunApp(ctx context.Context, config *hst.Config, beforeFail func()) {
|
||||
var exitError *exec.ExitError
|
||||
if errors.As(err, &exitError) {
|
||||
beforeFail()
|
||||
internal.Exit(exitError.ExitCode())
|
||||
msg.BeforeExit()
|
||||
os.Exit(exitError.ExitCode())
|
||||
} else {
|
||||
beforeFail()
|
||||
log.Fatalf("cannot wait: %v", err)
|
||||
|
@ -2,20 +2,20 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal"
|
||||
)
|
||||
|
||||
func withNixDaemon(
|
||||
ctx context.Context,
|
||||
msg container.Msg,
|
||||
action string, command []string, net bool, updateConfig func(config *hst.Config) *hst.Config,
|
||||
app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func(),
|
||||
) {
|
||||
mustRunAppDropShell(ctx, updateConfig(&hst.Config{
|
||||
mustRunAppDropShell(ctx, msg, updateConfig(&hst.Config{
|
||||
ID: app.ID,
|
||||
|
||||
Path: pathShell,
|
||||
@ -42,11 +42,11 @@ func withNixDaemon(
|
||||
Identity: app.Identity,
|
||||
|
||||
Container: &hst.ContainerConfig{
|
||||
Hostname: formatHostname(app.Name) + "-" + action,
|
||||
Userns: true, // nix sandbox requires userns
|
||||
HostNet: net,
|
||||
SeccompFlags: seccomp.AllowMultiarch,
|
||||
Tty: dropShell,
|
||||
Hostname: formatHostname(app.Name) + "-" + action,
|
||||
Userns: true, // nix sandbox requires userns
|
||||
HostNet: net,
|
||||
Multiarch: true,
|
||||
Tty: dropShell,
|
||||
Filesystem: []hst.FilesystemConfigJSON{
|
||||
{FilesystemConfig: &hst.FSBind{Target: container.AbsFHSEtc, Source: pathSet.cacheDir.Append("etc"), Special: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: pathSet.nixPath, Target: pathNix, Write: true}},
|
||||
@ -61,9 +61,10 @@ func withNixDaemon(
|
||||
|
||||
func withCacheDir(
|
||||
ctx context.Context,
|
||||
msg container.Msg,
|
||||
action string, command []string, workDir *container.Absolute,
|
||||
app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func()) {
|
||||
mustRunAppDropShell(ctx, &hst.Config{
|
||||
mustRunAppDropShell(ctx, msg, &hst.Config{
|
||||
ID: app.ID,
|
||||
|
||||
Path: pathShell,
|
||||
@ -81,9 +82,9 @@ func withCacheDir(
|
||||
Identity: app.Identity,
|
||||
|
||||
Container: &hst.ContainerConfig{
|
||||
Hostname: formatHostname(app.Name) + "-" + action,
|
||||
SeccompFlags: seccomp.AllowMultiarch,
|
||||
Tty: dropShell,
|
||||
Hostname: formatHostname(app.Name) + "-" + action,
|
||||
Multiarch: true,
|
||||
Tty: dropShell,
|
||||
Filesystem: []hst.FilesystemConfigJSON{
|
||||
{FilesystemConfig: &hst.FSBind{Target: container.AbsFHSEtc, Source: workDir.Append(container.FHSEtc), Special: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: workDir.Append("nix"), Target: pathNix}},
|
||||
@ -97,12 +98,13 @@ func withCacheDir(
|
||||
}, dropShell, beforeFail)
|
||||
}
|
||||
|
||||
func mustRunAppDropShell(ctx context.Context, config *hst.Config, dropShell bool, beforeFail func()) {
|
||||
func mustRunAppDropShell(ctx context.Context, msg container.Msg, config *hst.Config, dropShell bool, beforeFail func()) {
|
||||
if dropShell {
|
||||
config.Args = []string{bash, "-l"}
|
||||
mustRunApp(ctx, config, beforeFail)
|
||||
mustRunApp(ctx, msg, config, beforeFail)
|
||||
beforeFail()
|
||||
internal.Exit(0)
|
||||
msg.BeforeExit()
|
||||
os.Exit(0)
|
||||
}
|
||||
mustRunApp(ctx, config, beforeFail)
|
||||
mustRunApp(ctx, msg, config, beforeFail)
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ func (r *AutoRootOp) early(state *setupState, k syscallDispatcher) error {
|
||||
r.resolved = make([]*BindMountOp, 0, len(d))
|
||||
for _, ent := range d {
|
||||
name := ent.Name()
|
||||
if IsAutoRootBindable(name) {
|
||||
if IsAutoRootBindable(state, name) {
|
||||
// careful: the Valid method is skipped, make sure this is always valid
|
||||
op := &BindMountOp{
|
||||
Source: r.Host.Append(name),
|
||||
@ -78,7 +78,7 @@ func (r *AutoRootOp) String() string {
|
||||
}
|
||||
|
||||
// IsAutoRootBindable returns whether a dir entry name is selected for AutoRoot.
|
||||
func IsAutoRootBindable(name string) bool {
|
||||
func IsAutoRootBindable(msg Msg, name string) bool {
|
||||
switch name {
|
||||
case "proc", "dev", "tmp", "mnt", "etc":
|
||||
|
||||
|
@ -180,19 +180,26 @@ func TestIsAutoRootBindable(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
want bool
|
||||
log bool
|
||||
}{
|
||||
{"proc", false},
|
||||
{"dev", false},
|
||||
{"tmp", false},
|
||||
{"mnt", false},
|
||||
{"etc", false},
|
||||
{"", false},
|
||||
{"proc", false, false},
|
||||
{"dev", false, false},
|
||||
{"tmp", false, false},
|
||||
{"mnt", false, false},
|
||||
{"etc", false, false},
|
||||
{"", false, true},
|
||||
|
||||
{"var", true},
|
||||
{"var", true, false},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := IsAutoRootBindable(tc.name); got != tc.want {
|
||||
var msg Msg
|
||||
if tc.log {
|
||||
msg = &kstub{nil, stub.New(t, func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { panic("unreachable") }, stub.Expect{Calls: []stub.Call{
|
||||
call("verbose", stub.ExpectArgs{[]any{"got unexpected root entry"}}, nil, nil),
|
||||
}})}
|
||||
}
|
||||
if got := IsAutoRootBindable(msg, tc.name); got != tc.want {
|
||||
t.Errorf("IsAutoRootBindable: %v, want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
|
@ -49,6 +49,7 @@ type (
|
||||
|
||||
cmd *exec.Cmd
|
||||
ctx context.Context
|
||||
msg Msg
|
||||
Params
|
||||
}
|
||||
|
||||
@ -162,10 +163,10 @@ func (p *Container) Start() error {
|
||||
|
||||
// map to overflow id to work around ownership checks
|
||||
if p.Uid < 1 {
|
||||
p.Uid = OverflowUid()
|
||||
p.Uid = OverflowUid(p.msg)
|
||||
}
|
||||
if p.Gid < 1 {
|
||||
p.Gid = OverflowGid()
|
||||
p.Gid = OverflowGid(p.msg)
|
||||
}
|
||||
|
||||
if !p.RetainSession {
|
||||
@ -263,19 +264,19 @@ func (p *Container) Start() error {
|
||||
}
|
||||
return &StartError{false, "kernel version too old for LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET", ENOSYS, true, false}
|
||||
} else {
|
||||
msg.Verbosef("landlock abi version %d", abi)
|
||||
p.msg.Verbosef("landlock abi version %d", abi)
|
||||
}
|
||||
|
||||
if rulesetFd, err := rulesetAttr.Create(0); err != nil {
|
||||
return &StartError{true, "create landlock ruleset", err, false, false}
|
||||
} else {
|
||||
msg.Verbosef("enforcing landlock ruleset %s", rulesetAttr)
|
||||
p.msg.Verbosef("enforcing landlock ruleset %s", rulesetAttr)
|
||||
if err = LandlockRestrictSelf(rulesetFd, 0); err != nil {
|
||||
_ = Close(rulesetFd)
|
||||
return &StartError{true, "enforce landlock ruleset", err, false, false}
|
||||
}
|
||||
if err = Close(rulesetFd); err != nil {
|
||||
msg.Verbosef("cannot close landlock ruleset: %v", err)
|
||||
p.msg.Verbosef("cannot close landlock ruleset: %v", err)
|
||||
// not fatal
|
||||
}
|
||||
}
|
||||
@ -283,7 +284,7 @@ func (p *Container) Start() error {
|
||||
landlockOut:
|
||||
}
|
||||
|
||||
msg.Verbose("starting container init")
|
||||
p.msg.Verbose("starting container init")
|
||||
if err := p.cmd.Start(); err != nil {
|
||||
return &StartError{false, "start container init", err, false, true}
|
||||
}
|
||||
@ -325,7 +326,7 @@ func (p *Container) Serve() error {
|
||||
Getuid(),
|
||||
Getgid(),
|
||||
len(p.ExtraFiles),
|
||||
msg.IsVerbose(),
|
||||
p.msg.IsVerbose(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@ -392,17 +393,21 @@ func (p *Container) ProcessState() *os.ProcessState {
|
||||
}
|
||||
|
||||
// New returns the address to a new instance of [Container] that requires further initialisation before use.
|
||||
func New(ctx context.Context) *Container {
|
||||
p := &Container{ctx: ctx, Params: Params{Ops: new(Ops)}}
|
||||
func New(ctx context.Context, msg Msg) *Container {
|
||||
if msg == nil {
|
||||
msg = NewMsg(nil)
|
||||
}
|
||||
|
||||
p := &Container{ctx: ctx, msg: msg, Params: Params{Ops: new(Ops)}}
|
||||
c, cancel := context.WithCancel(ctx)
|
||||
p.cancel = cancel
|
||||
p.cmd = exec.CommandContext(c, MustExecutable())
|
||||
p.cmd = exec.CommandContext(c, MustExecutable(msg))
|
||||
return p
|
||||
}
|
||||
|
||||
// NewCommand calls [New] and initialises the [Params.Path] and [Params.Args] fields.
|
||||
func NewCommand(ctx context.Context, pathname *Absolute, name string, args ...string) *Container {
|
||||
z := New(ctx)
|
||||
func NewCommand(ctx context.Context, msg Msg, pathname *Absolute, name string, args ...string) *Container {
|
||||
z := New(ctx, msg)
|
||||
z.Path = pathname
|
||||
z.Args = append([]string{name}, args...)
|
||||
return z
|
||||
|
@ -23,8 +23,6 @@ import (
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/container/vfs"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/hlog"
|
||||
"hakurei.app/ldd"
|
||||
)
|
||||
|
||||
@ -351,8 +349,6 @@ var containerTestCases = []struct {
|
||||
}
|
||||
|
||||
func TestContainer(t *testing.T) {
|
||||
replaceOutput(t)
|
||||
|
||||
t.Run("cancel", testContainerCancel(nil, func(t *testing.T, c *container.Container) {
|
||||
wantErr := context.Canceled
|
||||
wantExitCode := 0
|
||||
@ -547,7 +543,8 @@ func testContainerCancel(
|
||||
}
|
||||
|
||||
func TestContainerString(t *testing.T) {
|
||||
c := container.NewCommand(t.Context(), container.MustAbs("/run/current-system/sw/bin/ldd"), "ldd", "/usr/bin/env")
|
||||
msg := container.NewMsg(nil)
|
||||
c := container.NewCommand(t.Context(), msg, container.MustAbs("/run/current-system/sw/bin/ldd"), "ldd", "/usr/bin/env")
|
||||
c.SeccompFlags |= seccomp.AllowMultiarch
|
||||
c.SeccompRules = seccomp.Preset(
|
||||
seccomp.PresetExt|seccomp.PresetDenyNS|seccomp.PresetDenyTTY,
|
||||
@ -689,7 +686,7 @@ var (
|
||||
var helperCommands []func(c command.Command)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
container.TryArgv0(hlog.Output{}, hlog.Prepare, internal.InstallOutput)
|
||||
container.TryArgv0(nil)
|
||||
|
||||
if os.Getenv(envDoCheck) == "1" {
|
||||
c := command.New(os.Stderr, log.Printf, "helper", func(args []string) error {
|
||||
@ -712,12 +709,13 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
|
||||
func helperNewContainerLibPaths(ctx context.Context, libPaths *[]*container.Absolute, args ...string) (c *container.Container) {
|
||||
c = container.NewCommand(ctx, absHelperInnerPath, "helper", args...)
|
||||
msg := container.NewMsg(nil)
|
||||
c = container.NewCommand(ctx, msg, absHelperInnerPath, "helper", args...)
|
||||
c.Env = append(c.Env, envDoCheck+"=1")
|
||||
c.Bind(container.MustAbs(os.Args[0]), absHelperInnerPath, 0)
|
||||
|
||||
// in case test has cgo enabled
|
||||
if entries, err := ldd.Exec(ctx, os.Args[0]); err != nil {
|
||||
if entries, err := ldd.Exec(ctx, msg, os.Args[0]); err != nil {
|
||||
log.Fatalf("ldd: %v", err)
|
||||
} else {
|
||||
*libPaths = ldd.Path(entries)
|
||||
|
@ -3,7 +3,6 @@ package container
|
||||
import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
@ -38,7 +37,7 @@ type syscallDispatcher interface {
|
||||
setNoNewPrivs() error
|
||||
|
||||
// lastcap provides [LastCap].
|
||||
lastcap() uintptr
|
||||
lastcap(msg Msg) uintptr
|
||||
// capset provides capset.
|
||||
capset(hdrp *capHeader, datap *[2]capData) error
|
||||
// capBoundingSetDrop provides capBoundingSetDrop.
|
||||
@ -53,9 +52,9 @@ type syscallDispatcher interface {
|
||||
receive(key string, e any, fdp *uintptr) (closeFunc func() error, err error)
|
||||
|
||||
// bindMount provides procPaths.bindMount.
|
||||
bindMount(source, target string, flags uintptr) error
|
||||
bindMount(msg Msg, source, target string, flags uintptr) error
|
||||
// remount provides procPaths.remount.
|
||||
remount(target string, flags uintptr) error
|
||||
remount(msg Msg, target string, flags uintptr) error
|
||||
// mountTmpfs provides mountTmpfs.
|
||||
mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error
|
||||
// ensureFile provides ensureFile.
|
||||
@ -122,22 +121,12 @@ type syscallDispatcher interface {
|
||||
// wait4 provides syscall.Wait4
|
||||
wait4(pid int, wstatus *syscall.WaitStatus, options int, rusage *syscall.Rusage) (wpid int, err error)
|
||||
|
||||
// printf provides [log.Printf].
|
||||
printf(format string, v ...any)
|
||||
// fatal provides [log.Fatal]
|
||||
fatal(v ...any)
|
||||
// fatalf provides [log.Fatalf]
|
||||
fatalf(format string, v ...any)
|
||||
// verbose provides [Msg.Verbose].
|
||||
verbose(v ...any)
|
||||
// verbosef provides [Msg.Verbosef].
|
||||
verbosef(format string, v ...any)
|
||||
// suspend provides [Msg.Suspend].
|
||||
suspend()
|
||||
// resume provides [Msg.Resume].
|
||||
resume() bool
|
||||
// beforeExit provides [Msg.BeforeExit].
|
||||
beforeExit()
|
||||
// printf provides the Printf method of [log.Logger].
|
||||
printf(msg Msg, format string, v ...any)
|
||||
// fatal provides the Fatal method of [log.Logger]
|
||||
fatal(msg Msg, v ...any)
|
||||
// fatalf provides the Fatalf method of [log.Logger]
|
||||
fatalf(msg Msg, format string, v ...any)
|
||||
}
|
||||
|
||||
// direct implements syscallDispatcher on the current kernel.
|
||||
@ -151,7 +140,7 @@ func (direct) setPtracer(pid uintptr) error { return SetPtracer(pid) }
|
||||
func (direct) setDumpable(dumpable uintptr) error { return SetDumpable(dumpable) }
|
||||
func (direct) setNoNewPrivs() error { return SetNoNewPrivs() }
|
||||
|
||||
func (direct) lastcap() uintptr { return LastCap() }
|
||||
func (direct) lastcap(msg Msg) uintptr { return LastCap(msg) }
|
||||
func (direct) capset(hdrp *capHeader, datap *[2]capData) error { return capset(hdrp, datap) }
|
||||
func (direct) capBoundingSetDrop(cap uintptr) error { return capBoundingSetDrop(cap) }
|
||||
func (direct) capAmbientClearAll() error { return capAmbientClearAll() }
|
||||
@ -161,11 +150,11 @@ func (direct) receive(key string, e any, fdp *uintptr) (func() error, error) {
|
||||
return Receive(key, e, fdp)
|
||||
}
|
||||
|
||||
func (direct) bindMount(source, target string, flags uintptr) error {
|
||||
return hostProc.bindMount(source, target, flags)
|
||||
func (direct) bindMount(msg Msg, source, target string, flags uintptr) error {
|
||||
return hostProc.bindMount(msg, source, target, flags)
|
||||
}
|
||||
func (direct) remount(target string, flags uintptr) error {
|
||||
return hostProc.remount(target, flags)
|
||||
func (direct) remount(msg Msg, target string, flags uintptr) error {
|
||||
return hostProc.remount(msg, target, flags)
|
||||
}
|
||||
func (k direct) mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error {
|
||||
return mountTmpfs(k, fsname, target, flags, size, perm)
|
||||
@ -232,11 +221,6 @@ func (direct) wait4(pid int, wstatus *syscall.WaitStatus, options int, rusage *s
|
||||
return syscall.Wait4(pid, wstatus, options, rusage)
|
||||
}
|
||||
|
||||
func (direct) printf(format string, v ...any) { log.Printf(format, v...) }
|
||||
func (direct) fatal(v ...any) { log.Fatal(v...) }
|
||||
func (direct) fatalf(format string, v ...any) { log.Fatalf(format, v...) }
|
||||
func (direct) verbose(v ...any) { msg.Verbose(v...) }
|
||||
func (direct) verbosef(format string, v ...any) { msg.Verbosef(format, v...) }
|
||||
func (direct) suspend() { msg.Suspend() }
|
||||
func (direct) resume() bool { return msg.Resume() }
|
||||
func (direct) beforeExit() { msg.BeforeExit() }
|
||||
func (direct) printf(msg Msg, format string, v ...any) { msg.GetLogger().Printf(format, v...) }
|
||||
func (direct) fatal(msg Msg, v ...any) { msg.GetLogger().Fatal(v...) }
|
||||
func (direct) fatalf(msg Msg, format string, v ...any) { msg.GetLogger().Fatalf(format, v...) }
|
||||
|
@ -2,8 +2,10 @@ package container
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"reflect"
|
||||
@ -136,7 +138,7 @@ func call(name string, args stub.ExpectArgs, ret any, err error) stub.Call {
|
||||
|
||||
type simpleTestCase struct {
|
||||
name string
|
||||
f func(k syscallDispatcher) error
|
||||
f func(k *kstub) error
|
||||
want stub.Expect
|
||||
wantErr error
|
||||
}
|
||||
@ -185,11 +187,11 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
state := &setupState{Params: tc.params}
|
||||
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)},
|
||||
)}
|
||||
state := &setupState{Params: tc.params, Msg: k}
|
||||
defer stub.HandleExit(t)
|
||||
errEarly := tc.op.early(state, k)
|
||||
k.Expects(stub.CallSeparator)
|
||||
@ -327,7 +329,11 @@ func (k *kstub) setDumpable(dumpable uintptr) error {
|
||||
}
|
||||
|
||||
func (k *kstub) setNoNewPrivs() error { k.Helper(); return k.Expects("setNoNewPrivs").Err }
|
||||
func (k *kstub) lastcap() uintptr { k.Helper(); return k.Expects("lastcap").Ret.(uintptr) }
|
||||
func (k *kstub) lastcap(msg Msg) uintptr {
|
||||
k.Helper()
|
||||
k.checkMsg(msg)
|
||||
return k.Expects("lastcap").Ret.(uintptr)
|
||||
}
|
||||
|
||||
func (k *kstub) capset(hdrp *capHeader, datap *[2]capData) error {
|
||||
k.Helper()
|
||||
@ -403,16 +409,18 @@ func (k *kstub) receive(key string, e any, fdp *uintptr) (closeFunc func() error
|
||||
return
|
||||
}
|
||||
|
||||
func (k *kstub) bindMount(source, target string, flags uintptr) error {
|
||||
func (k *kstub) bindMount(msg Msg, source, target string, flags uintptr) error {
|
||||
k.Helper()
|
||||
k.checkMsg(msg)
|
||||
return k.Expects("bindMount").Error(
|
||||
stub.CheckArg(k.Stub, "source", source, 0),
|
||||
stub.CheckArg(k.Stub, "target", target, 1),
|
||||
stub.CheckArg(k.Stub, "flags", flags, 2))
|
||||
}
|
||||
|
||||
func (k *kstub) remount(target string, flags uintptr) error {
|
||||
func (k *kstub) remount(msg Msg, target string, flags uintptr) error {
|
||||
k.Helper()
|
||||
k.checkMsg(msg)
|
||||
return k.Expects("remount").Error(
|
||||
stub.CheckArg(k.Stub, "target", target, 0),
|
||||
stub.CheckArg(k.Stub, "flags", flags, 1))
|
||||
@ -694,7 +702,7 @@ func (k *kstub) wait4(pid int, wstatus *syscall.WaitStatus, options int, rusage
|
||||
return
|
||||
}
|
||||
|
||||
func (k *kstub) printf(format string, v ...any) {
|
||||
func (k *kstub) printf(_ Msg, format string, v ...any) {
|
||||
k.Helper()
|
||||
if k.Expects("printf").Error(
|
||||
stub.CheckArg(k.Stub, "format", format, 0),
|
||||
@ -703,7 +711,7 @@ func (k *kstub) printf(format string, v ...any) {
|
||||
}
|
||||
}
|
||||
|
||||
func (k *kstub) fatal(v ...any) {
|
||||
func (k *kstub) fatal(_ Msg, v ...any) {
|
||||
k.Helper()
|
||||
if k.Expects("fatal").Error(
|
||||
stub.CheckArgReflect(k.Stub, "v", v, 0)) != nil {
|
||||
@ -712,7 +720,7 @@ func (k *kstub) fatal(v ...any) {
|
||||
panic(stub.PanicExit)
|
||||
}
|
||||
|
||||
func (k *kstub) fatalf(format string, v ...any) {
|
||||
func (k *kstub) fatalf(_ Msg, format string, v ...any) {
|
||||
k.Helper()
|
||||
if k.Expects("fatalf").Error(
|
||||
stub.CheckArg(k.Stub, "format", format, 0),
|
||||
@ -722,7 +730,35 @@ func (k *kstub) fatalf(format string, v ...any) {
|
||||
panic(stub.PanicExit)
|
||||
}
|
||||
|
||||
func (k *kstub) verbose(v ...any) {
|
||||
func (k *kstub) checkMsg(msg Msg) {
|
||||
k.Helper()
|
||||
var target *kstub
|
||||
|
||||
if state, ok := msg.(*setupState); ok {
|
||||
target = state.Msg.(*kstub)
|
||||
} else {
|
||||
target = msg.(*kstub)
|
||||
}
|
||||
|
||||
if k != target {
|
||||
panic(fmt.Sprintf("unexpected Msg: %#v", msg))
|
||||
}
|
||||
}
|
||||
|
||||
func (k *kstub) GetLogger() *log.Logger { panic("unreachable") }
|
||||
func (k *kstub) IsVerbose() bool { panic("unreachable") }
|
||||
|
||||
func (k *kstub) SwapVerbose(verbose bool) bool {
|
||||
k.Helper()
|
||||
expect := k.Expects("swapVerbose")
|
||||
if expect.Error(
|
||||
stub.CheckArg(k.Stub, "verbose", verbose, 0)) != nil {
|
||||
k.FailNow()
|
||||
}
|
||||
return expect.Ret.(bool)
|
||||
}
|
||||
|
||||
func (k *kstub) Verbose(v ...any) {
|
||||
k.Helper()
|
||||
if k.Expects("verbose").Error(
|
||||
stub.CheckArgReflect(k.Stub, "v", v, 0)) != nil {
|
||||
@ -730,7 +766,7 @@ func (k *kstub) verbose(v ...any) {
|
||||
}
|
||||
}
|
||||
|
||||
func (k *kstub) verbosef(format string, v ...any) {
|
||||
func (k *kstub) Verbosef(format string, v ...any) {
|
||||
k.Helper()
|
||||
if k.Expects("verbosef").Error(
|
||||
stub.CheckArg(k.Stub, "format", format, 0),
|
||||
@ -739,6 +775,6 @@ func (k *kstub) verbosef(format string, v ...any) {
|
||||
}
|
||||
}
|
||||
|
||||
func (k *kstub) suspend() { k.Helper(); k.Expects("suspend") }
|
||||
func (k *kstub) resume() bool { k.Helper(); return k.Expects("resume").Ret.(bool) }
|
||||
func (k *kstub) beforeExit() { k.Helper(); k.Expects("beforeExit") }
|
||||
func (k *kstub) Suspend() bool { k.Helper(); return k.Expects("suspend").Ret.(bool) }
|
||||
func (k *kstub) Resume() bool { k.Helper(); return k.Expects("resume").Ret.(bool) }
|
||||
func (k *kstub) BeforeExit() { k.Helper(); k.Expects("beforeExit") }
|
||||
|
@ -1,7 +1,6 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
@ -11,16 +10,16 @@ var (
|
||||
executableOnce sync.Once
|
||||
)
|
||||
|
||||
func copyExecutable() {
|
||||
func copyExecutable(msg Msg) {
|
||||
if name, err := os.Executable(); err != nil {
|
||||
msg.BeforeExit()
|
||||
log.Fatalf("cannot read executable path: %v", err)
|
||||
msg.GetLogger().Fatalf("cannot read executable path: %v", err)
|
||||
} else {
|
||||
executable = name
|
||||
}
|
||||
}
|
||||
|
||||
func MustExecutable() string {
|
||||
executableOnce.Do(copyExecutable)
|
||||
func MustExecutable(msg Msg) string {
|
||||
executableOnce.Do(func() { copyExecutable(msg) })
|
||||
return executable
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
|
||||
func TestExecutable(t *testing.T) {
|
||||
for i := 0; i < 16; i++ {
|
||||
if got := container.MustExecutable(); got != os.Args[0] {
|
||||
if got := container.MustExecutable(container.NewMsg(nil)); got != os.Args[0] {
|
||||
t.Errorf("MustExecutable: %q, want %q",
|
||||
got, os.Args[0])
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package container
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
@ -59,6 +60,7 @@ type (
|
||||
setupState struct {
|
||||
nonrepeatable uintptr
|
||||
*Params
|
||||
Msg
|
||||
}
|
||||
)
|
||||
|
||||
@ -91,20 +93,23 @@ type initParams struct {
|
||||
Verbose bool
|
||||
}
|
||||
|
||||
func Init(prepareLogger func(prefix string), setVerbose func(verbose bool)) {
|
||||
initEntrypoint(direct{}, prepareLogger, setVerbose)
|
||||
// Init is called by [TryArgv0] if the current process is the container init.
|
||||
func Init(msg Msg) {
|
||||
if msg == nil {
|
||||
panic("attempting to call initEntrypoint with nil msg")
|
||||
}
|
||||
initEntrypoint(direct{}, msg)
|
||||
}
|
||||
|
||||
func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setVerbose func(verbose bool)) {
|
||||
func initEntrypoint(k syscallDispatcher, msg Msg) {
|
||||
k.lockOSThread()
|
||||
prepareLogger("init")
|
||||
|
||||
if k.getpid() != 1 {
|
||||
k.fatal("this process must run as pid 1")
|
||||
k.fatal(msg, "this process must run as pid 1")
|
||||
}
|
||||
|
||||
if err := k.setPtracer(0); err != nil {
|
||||
k.verbosef("cannot enable ptrace protection via Yama LSM: %v", err)
|
||||
msg.Verbosef("cannot enable ptrace protection via Yama LSM: %v", err)
|
||||
// not fatal: this program has no additional privileges at initial program start
|
||||
}
|
||||
|
||||
@ -116,65 +121,65 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
||||
)
|
||||
if f, err := k.receive(setupEnv, ¶ms, &setupFd); err != nil {
|
||||
if errors.Is(err, EBADF) {
|
||||
k.fatal("invalid setup descriptor")
|
||||
k.fatal(msg, "invalid setup descriptor")
|
||||
}
|
||||
if errors.Is(err, ErrReceiveEnv) {
|
||||
k.fatal("HAKUREI_SETUP not set")
|
||||
k.fatal(msg, "HAKUREI_SETUP not set")
|
||||
}
|
||||
|
||||
k.fatalf("cannot decode init setup payload: %v", err)
|
||||
k.fatalf(msg, "cannot decode init setup payload: %v", err)
|
||||
} else {
|
||||
if params.Ops == nil {
|
||||
k.fatal("invalid setup parameters")
|
||||
k.fatal(msg, "invalid setup parameters")
|
||||
}
|
||||
if params.ParentPerm == 0 {
|
||||
params.ParentPerm = 0755
|
||||
}
|
||||
|
||||
setVerbose(params.Verbose)
|
||||
k.verbose("received setup parameters")
|
||||
msg.SwapVerbose(params.Verbose)
|
||||
msg.Verbose("received setup parameters")
|
||||
closeSetup = f
|
||||
offsetSetup = int(setupFd + 1)
|
||||
}
|
||||
|
||||
// write uid/gid map here so parent does not need to set dumpable
|
||||
if err := k.setDumpable(SUID_DUMP_USER); err != nil {
|
||||
k.fatalf("cannot set SUID_DUMP_USER: %v", err)
|
||||
k.fatalf(msg, "cannot set SUID_DUMP_USER: %v", err)
|
||||
}
|
||||
if err := k.writeFile(FHSProc+"self/uid_map",
|
||||
append([]byte{}, strconv.Itoa(params.Uid)+" "+strconv.Itoa(params.HostUid)+" 1\n"...),
|
||||
0); err != nil {
|
||||
k.fatalf("%v", err)
|
||||
k.fatalf(msg, "%v", err)
|
||||
}
|
||||
if err := k.writeFile(FHSProc+"self/setgroups",
|
||||
[]byte("deny\n"),
|
||||
0); err != nil && !os.IsNotExist(err) {
|
||||
k.fatalf("%v", err)
|
||||
k.fatalf(msg, "%v", err)
|
||||
}
|
||||
if err := k.writeFile(FHSProc+"self/gid_map",
|
||||
append([]byte{}, strconv.Itoa(params.Gid)+" "+strconv.Itoa(params.HostGid)+" 1\n"...),
|
||||
0); err != nil {
|
||||
k.fatalf("%v", err)
|
||||
k.fatalf(msg, "%v", err)
|
||||
}
|
||||
if err := k.setDumpable(SUID_DUMP_DISABLE); err != nil {
|
||||
k.fatalf("cannot set SUID_DUMP_DISABLE: %v", err)
|
||||
k.fatalf(msg, "cannot set SUID_DUMP_DISABLE: %v", err)
|
||||
}
|
||||
|
||||
oldmask := k.umask(0)
|
||||
if params.Hostname != "" {
|
||||
if err := k.sethostname([]byte(params.Hostname)); err != nil {
|
||||
k.fatalf("cannot set hostname: %v", err)
|
||||
k.fatalf(msg, "cannot set hostname: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// cache sysctl before pivot_root
|
||||
lastcap := k.lastcap()
|
||||
lastcap := k.lastcap(msg)
|
||||
|
||||
if err := k.mount(zeroString, FHSRoot, zeroString, MS_SILENT|MS_SLAVE|MS_REC, zeroString); err != nil {
|
||||
k.fatalf("cannot make / rslave: %v", err)
|
||||
k.fatalf(msg, "cannot make / rslave: %v", err)
|
||||
}
|
||||
|
||||
state := &setupState{Params: ¶ms.Params}
|
||||
state := &setupState{Params: ¶ms.Params, Msg: msg}
|
||||
|
||||
/* early is called right before pivot_root into intermediate root;
|
||||
this step is mostly for gathering information that would otherwise be difficult to obtain
|
||||
@ -182,41 +187,41 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
||||
the state of the mount namespace */
|
||||
for i, op := range *params.Ops {
|
||||
if op == nil || !op.Valid() {
|
||||
k.fatalf("invalid op at index %d", i)
|
||||
k.fatalf(msg, "invalid op at index %d", i)
|
||||
}
|
||||
|
||||
if err := op.early(state, k); err != nil {
|
||||
if m, ok := messageFromError(err); ok {
|
||||
k.fatal(m)
|
||||
k.fatal(msg, m)
|
||||
} else {
|
||||
k.fatalf("cannot prepare op at index %d: %v", i, err)
|
||||
k.fatalf(msg, "cannot prepare op at index %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := k.mount(SourceTmpfsRootfs, intermediateHostPath, FstypeTmpfs, MS_NODEV|MS_NOSUID, zeroString); err != nil {
|
||||
k.fatalf("cannot mount intermediate root: %v", err)
|
||||
k.fatalf(msg, "cannot mount intermediate root: %v", err)
|
||||
}
|
||||
if err := k.chdir(intermediateHostPath); err != nil {
|
||||
k.fatalf("cannot enter intermediate host path: %v", err)
|
||||
k.fatalf(msg, "cannot enter intermediate host path: %v", err)
|
||||
}
|
||||
|
||||
if err := k.mkdir(sysrootDir, 0755); err != nil {
|
||||
k.fatalf("%v", err)
|
||||
k.fatalf(msg, "%v", err)
|
||||
}
|
||||
if err := k.mount(sysrootDir, sysrootDir, zeroString, MS_SILENT|MS_BIND|MS_REC, zeroString); err != nil {
|
||||
k.fatalf("cannot bind sysroot: %v", err)
|
||||
k.fatalf(msg, "cannot bind sysroot: %v", err)
|
||||
}
|
||||
|
||||
if err := k.mkdir(hostDir, 0755); err != nil {
|
||||
k.fatalf("%v", err)
|
||||
k.fatalf(msg, "%v", err)
|
||||
}
|
||||
// pivot_root uncovers intermediateHostPath in hostDir
|
||||
if err := k.pivotRoot(intermediateHostPath, hostDir); err != nil {
|
||||
k.fatalf("cannot pivot into intermediate root: %v", err)
|
||||
k.fatalf(msg, "cannot pivot into intermediate root: %v", err)
|
||||
}
|
||||
if err := k.chdir(FHSRoot); err != nil {
|
||||
k.fatalf("cannot enter intermediate root: %v", err)
|
||||
k.fatalf(msg, "cannot enter intermediate root: %v", err)
|
||||
}
|
||||
|
||||
/* apply is called right after pivot_root and entering the new root;
|
||||
@ -226,23 +231,23 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
||||
for i, op := range *params.Ops {
|
||||
// ops already checked during early setup
|
||||
if prefix, ok := op.prefix(); ok {
|
||||
k.verbosef("%s %s", prefix, op)
|
||||
msg.Verbosef("%s %s", prefix, op)
|
||||
}
|
||||
if err := op.apply(state, k); err != nil {
|
||||
if m, ok := messageFromError(err); ok {
|
||||
k.fatal(m)
|
||||
k.fatal(msg, m)
|
||||
} else {
|
||||
k.fatalf("cannot apply op at index %d: %v", i, err)
|
||||
k.fatalf(msg, "cannot apply op at index %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// setup requiring host root complete at this point
|
||||
if err := k.mount(hostDir, hostDir, zeroString, MS_SILENT|MS_REC|MS_PRIVATE, zeroString); err != nil {
|
||||
k.fatalf("cannot make host root rprivate: %v", err)
|
||||
k.fatalf(msg, "cannot make host root rprivate: %v", err)
|
||||
}
|
||||
if err := k.unmount(hostDir, MNT_DETACH); err != nil {
|
||||
k.fatalf("cannot unmount host root: %v", err)
|
||||
k.fatalf(msg, "cannot unmount host root: %v", err)
|
||||
}
|
||||
|
||||
{
|
||||
@ -251,39 +256,39 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
||||
fd, err = k.open(FHSRoot, O_DIRECTORY|O_RDONLY, 0)
|
||||
return
|
||||
}); err != nil {
|
||||
k.fatalf("cannot open intermediate root: %v", err)
|
||||
k.fatalf(msg, "cannot open intermediate root: %v", err)
|
||||
}
|
||||
if err := k.chdir(sysrootPath); err != nil {
|
||||
k.fatalf("cannot enter sysroot: %v", err)
|
||||
k.fatalf(msg, "cannot enter sysroot: %v", err)
|
||||
}
|
||||
|
||||
if err := k.pivotRoot(".", "."); err != nil {
|
||||
k.fatalf("cannot pivot into sysroot: %v", err)
|
||||
k.fatalf(msg, "cannot pivot into sysroot: %v", err)
|
||||
}
|
||||
if err := k.fchdir(fd); err != nil {
|
||||
k.fatalf("cannot re-enter intermediate root: %v", err)
|
||||
k.fatalf(msg, "cannot re-enter intermediate root: %v", err)
|
||||
}
|
||||
if err := k.unmount(".", MNT_DETACH); err != nil {
|
||||
k.fatalf("cannot unmount intermediate root: %v", err)
|
||||
k.fatalf(msg, "cannot unmount intermediate root: %v", err)
|
||||
}
|
||||
if err := k.chdir(FHSRoot); err != nil {
|
||||
k.fatalf("cannot enter root: %v", err)
|
||||
k.fatalf(msg, "cannot enter root: %v", err)
|
||||
}
|
||||
|
||||
if err := k.close(fd); err != nil {
|
||||
k.fatalf("cannot close intermediate root: %v", err)
|
||||
k.fatalf(msg, "cannot close intermediate root: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := k.capAmbientClearAll(); err != nil {
|
||||
k.fatalf("cannot clear the ambient capability set: %v", err)
|
||||
k.fatalf(msg, "cannot clear the ambient capability set: %v", err)
|
||||
}
|
||||
for i := uintptr(0); i <= lastcap; i++ {
|
||||
if params.Privileged && i == CAP_SYS_ADMIN {
|
||||
continue
|
||||
}
|
||||
if err := k.capBoundingSetDrop(i); err != nil {
|
||||
k.fatalf("cannot drop capability from bounding set: %v", err)
|
||||
k.fatalf(msg, "cannot drop capability from bounding set: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -292,29 +297,29 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
||||
keep[capToIndex(CAP_SYS_ADMIN)] |= capToMask(CAP_SYS_ADMIN)
|
||||
|
||||
if err := k.capAmbientRaise(CAP_SYS_ADMIN); err != nil {
|
||||
k.fatalf("cannot raise CAP_SYS_ADMIN: %v", err)
|
||||
k.fatalf(msg, "cannot raise CAP_SYS_ADMIN: %v", err)
|
||||
}
|
||||
}
|
||||
if err := k.capset(
|
||||
&capHeader{_LINUX_CAPABILITY_VERSION_3, 0},
|
||||
&[2]capData{{0, keep[0], keep[0]}, {0, keep[1], keep[1]}},
|
||||
); err != nil {
|
||||
k.fatalf("cannot capset: %v", err)
|
||||
k.fatalf(msg, "cannot capset: %v", err)
|
||||
}
|
||||
|
||||
if !params.SeccompDisable {
|
||||
rules := params.SeccompRules
|
||||
if len(rules) == 0 { // non-empty rules slice always overrides presets
|
||||
k.verbosef("resolving presets %#x", params.SeccompPresets)
|
||||
msg.Verbosef("resolving presets %#x", params.SeccompPresets)
|
||||
rules = seccomp.Preset(params.SeccompPresets, params.SeccompFlags)
|
||||
}
|
||||
if err := k.seccompLoad(rules, params.SeccompFlags); err != nil {
|
||||
// this also indirectly asserts PR_SET_NO_NEW_PRIVS
|
||||
k.fatalf("cannot load syscall filter: %v", err)
|
||||
k.fatalf(msg, "cannot load syscall filter: %v", err)
|
||||
}
|
||||
k.verbosef("%d filter rules loaded", len(rules))
|
||||
msg.Verbosef("%d filter rules loaded", len(rules))
|
||||
} else {
|
||||
k.verbose("syscall filter not configured")
|
||||
msg.Verbose("syscall filter not configured")
|
||||
}
|
||||
|
||||
extraFiles := make([]*os.File, params.Count)
|
||||
@ -331,14 +336,14 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
||||
cmd.ExtraFiles = extraFiles
|
||||
cmd.Dir = params.Dir.String()
|
||||
|
||||
k.verbosef("starting initial program %s", params.Path)
|
||||
msg.Verbosef("starting initial program %s", params.Path)
|
||||
if err := k.start(cmd); err != nil {
|
||||
k.fatalf("%v", err)
|
||||
k.fatalf(msg, "%v", err)
|
||||
}
|
||||
k.suspend()
|
||||
msg.Suspend()
|
||||
|
||||
if err := closeSetup(); err != nil {
|
||||
k.printf("cannot close setup pipe: %v", err)
|
||||
k.printf(msg, "cannot close setup pipe: %v", err)
|
||||
// not fatal
|
||||
}
|
||||
|
||||
@ -372,7 +377,7 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
||||
}
|
||||
}
|
||||
if !errors.Is(err, ECHILD) {
|
||||
k.printf("unexpected wait4 response: %v", err)
|
||||
k.printf(msg, "unexpected wait4 response: %v", err)
|
||||
}
|
||||
|
||||
close(done)
|
||||
@ -389,50 +394,50 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
||||
for {
|
||||
select {
|
||||
case s := <-sig:
|
||||
if k.resume() {
|
||||
k.verbosef("%s after process start", s.String())
|
||||
if msg.Resume() {
|
||||
msg.Verbosef("%s after process start", s.String())
|
||||
} else {
|
||||
k.verbosef("got %s", s.String())
|
||||
msg.Verbosef("got %s", s.String())
|
||||
}
|
||||
if s == CancelSignal && params.ForwardCancel && cmd.Process != nil {
|
||||
k.verbose("forwarding context cancellation")
|
||||
msg.Verbose("forwarding context cancellation")
|
||||
if err := k.signal(cmd, os.Interrupt); err != nil {
|
||||
k.printf("cannot forward cancellation: %v", err)
|
||||
k.printf(msg, "cannot forward cancellation: %v", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
k.beforeExit()
|
||||
msg.BeforeExit()
|
||||
k.exit(0)
|
||||
|
||||
case w := <-info:
|
||||
if w.wpid == cmd.Process.Pid {
|
||||
// initial process exited, output is most likely available again
|
||||
k.resume()
|
||||
msg.Resume()
|
||||
|
||||
switch {
|
||||
case w.wstatus.Exited():
|
||||
r = w.wstatus.ExitStatus()
|
||||
k.verbosef("initial process exited with code %d", w.wstatus.ExitStatus())
|
||||
msg.Verbosef("initial process exited with code %d", w.wstatus.ExitStatus())
|
||||
|
||||
case w.wstatus.Signaled():
|
||||
r = 128 + int(w.wstatus.Signal())
|
||||
k.verbosef("initial process exited with signal %s", w.wstatus.Signal())
|
||||
msg.Verbosef("initial process exited with signal %s", w.wstatus.Signal())
|
||||
|
||||
default:
|
||||
r = 255
|
||||
k.verbosef("initial process exited with status %#x", w.wstatus)
|
||||
msg.Verbosef("initial process exited with status %#x", w.wstatus)
|
||||
}
|
||||
|
||||
go func() { time.Sleep(params.AdoptWaitDelay); close(timeout) }()
|
||||
}
|
||||
|
||||
case <-done:
|
||||
k.beforeExit()
|
||||
msg.BeforeExit()
|
||||
k.exit(r)
|
||||
|
||||
case <-timeout:
|
||||
k.printf("timeout exceeded waiting for lingering processes")
|
||||
k.beforeExit()
|
||||
k.printf(msg, "timeout exceeded waiting for lingering processes")
|
||||
msg.BeforeExit()
|
||||
k.exit(r)
|
||||
}
|
||||
}
|
||||
@ -441,10 +446,16 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
||||
const initName = "init"
|
||||
|
||||
// TryArgv0 calls [Init] if the last element of argv0 is "init".
|
||||
func TryArgv0(v Msg, prepare func(prefix string), setVerbose func(verbose bool)) {
|
||||
// If a nil msg is passed, the system logger is used instead.
|
||||
func TryArgv0(msg Msg) {
|
||||
if msg == nil {
|
||||
log.SetPrefix(initName + ": ")
|
||||
log.SetFlags(0)
|
||||
msg = NewMsg(log.Default())
|
||||
}
|
||||
|
||||
if len(os.Args) > 0 && path.Base(os.Args[0]) == initName {
|
||||
msg = v
|
||||
Init(prepare, setVerbose)
|
||||
Init(msg)
|
||||
msg.BeforeExit()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
@ -11,25 +11,9 @@ import (
|
||||
)
|
||||
|
||||
func TestInitEntrypoint(t *testing.T) {
|
||||
assertPrefix := func(prefix string) {
|
||||
if prefix != "init" {
|
||||
t.Fatalf("prepareLogger: prefix = %q", prefix)
|
||||
}
|
||||
}
|
||||
|
||||
assertVerboseFalse := func(verbose bool) {
|
||||
if verbose {
|
||||
t.Fatal("setVerbose: verbose = true, want false")
|
||||
}
|
||||
}
|
||||
assertVerboseTrue := func(verbose bool) {
|
||||
if !verbose {
|
||||
t.Fatal("setVerbose: verbose = false, want true")
|
||||
}
|
||||
}
|
||||
|
||||
checkSimple(t, "initEntrypoint", []simpleTestCase{
|
||||
{"getpid", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"getpid", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1<<10, nil),
|
||||
@ -37,7 +21,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"receive bad fd", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"receive bad fd", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -47,7 +31,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"receive not set", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"receive not set", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -57,7 +41,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"receive payload decode", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"receive payload decode", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -67,7 +51,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"receive invalid params", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"receive invalid params", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -91,7 +75,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"setDumpable user", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"setDumpable user", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -112,13 +96,14 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(78), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, stub.UniqueError(77)),
|
||||
call("fatalf", stub.ExpectArgs{"cannot set SUID_DUMP_USER: %v", []any{stub.UniqueError(77)}}, nil, nil),
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"writeFile uid_map", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"writeFile uid_map", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -139,6 +124,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(76), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, stub.UniqueError(75)),
|
||||
@ -146,7 +132,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"writeFile setgroups", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"writeFile setgroups", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -167,6 +153,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(74), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -175,7 +162,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"writeFile gid_map", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"writeFile gid_map", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -196,6 +183,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(72), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -205,7 +193,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"setDumpable disable", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"setDumpable disable", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -226,6 +214,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(70), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -236,7 +225,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"sethostname", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"sethostname", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -257,6 +246,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(68), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -269,7 +259,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"mount rslave root", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"mount rslave root", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -290,6 +280,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(66), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -304,7 +295,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"nil op", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"nil op", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -325,6 +316,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(64), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -341,7 +333,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"invalid op", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"invalid op", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -362,6 +354,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(63), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -378,7 +371,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"early unhandled error", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"early unhandled error", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -399,6 +392,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(62), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -416,7 +410,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"early", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"early", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -437,6 +431,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(60), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -454,7 +449,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"mount ih", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"mount ih", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -475,6 +470,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(59), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -493,7 +489,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"chdir ih", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"chdir ih", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -514,6 +510,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(57), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -533,7 +530,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"mkdir sysroot", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"mkdir sysroot", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -554,6 +551,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(55), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -574,7 +572,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"mount bind sysroot", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"mount bind sysroot", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -595,6 +593,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(53), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -616,7 +615,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"mkdir host", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"mkdir host", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -637,6 +636,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(51), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -659,7 +659,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"pivotRoot ir", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"pivotRoot ir", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -680,6 +680,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(49), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -703,7 +704,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"chdir ir", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"chdir ir", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -724,6 +725,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(47), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -748,7 +750,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"apply unhandled error", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"apply unhandled error", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -769,6 +771,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(45), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -802,7 +805,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"apply", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"apply", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -823,6 +826,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(43), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -856,7 +860,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"mount rprivate host", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"mount rprivate host", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -877,6 +881,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(42), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -911,7 +916,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"unmount host", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"unmount host", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -932,6 +937,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(40), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -967,7 +973,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"open ir", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"open ir", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -988,6 +994,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(38), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -1025,7 +1032,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"chdir sysroot", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"chdir sysroot", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -1046,6 +1053,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(36), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -1084,7 +1092,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"pivotRoot sysroot", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"pivotRoot sysroot", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -1105,6 +1113,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(34), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -1144,7 +1153,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"fchdir ir", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"fchdir ir", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -1165,6 +1174,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(32), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -1205,7 +1215,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"unmount ir", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"unmount ir", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -1226,6 +1236,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(30), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -1267,7 +1278,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"chdir ir", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"chdir ir", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -1288,6 +1299,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(28), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -1330,7 +1342,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"close ir", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"close ir", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -1351,6 +1363,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(26), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -1394,7 +1407,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"capAmbientClearAll", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"capAmbientClearAll", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -1415,6 +1428,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(24), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -1459,7 +1473,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"capBoundingSetDrop", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"capBoundingSetDrop", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -1480,6 +1494,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(22), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -1532,7 +1547,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"capAmbientRaise", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"capAmbientRaise", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -1553,6 +1568,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(20), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -1638,7 +1654,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"capset", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"capset", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -1659,6 +1675,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(18), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -1745,7 +1762,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"seccompLoad", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"seccompLoad", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -1766,6 +1783,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(16), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -1854,7 +1872,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"start", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseFalse); return nil }, stub.Expect{
|
||||
{"start", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
@ -1874,6 +1892,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
SeccompDisable: true,
|
||||
ParentPerm: 0750,
|
||||
}, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(13), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{false}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("16777216 1971 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -1966,7 +1985,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
},
|
||||
}, nil},
|
||||
|
||||
{"lowlastcap signaled cancel forward error", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseFalse); return nil }, stub.Expect{
|
||||
{"lowlastcap signaled cancel forward error", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
/* entrypoint */
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
@ -1987,6 +2006,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
SeccompDisable: true,
|
||||
ParentPerm: 0750,
|
||||
}, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(10), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{false}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("16777216 1971 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -2039,7 +2059,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
call("umask", stub.ExpectArgs{022}, 0, nil),
|
||||
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
||||
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
|
||||
call("suspend", stub.ExpectArgs{}, nil, nil),
|
||||
call("suspend", stub.ExpectArgs{}, true, nil),
|
||||
call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(10)}}, nil, nil),
|
||||
call("New", stub.ExpectArgs{}, nil, nil),
|
||||
call("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- CancelSignal }, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil),
|
||||
@ -2065,7 +2085,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
}}},
|
||||
}, nil},
|
||||
|
||||
{"lowlastcap signaled cancel", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseFalse); return nil }, stub.Expect{
|
||||
{"lowlastcap signaled cancel", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
/* entrypoint */
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
@ -2086,6 +2106,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
SeccompDisable: true,
|
||||
ParentPerm: 0750,
|
||||
}, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(7), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{false}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("16777216 1971 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -2138,7 +2159,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
call("umask", stub.ExpectArgs{022}, 0, nil),
|
||||
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
||||
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
|
||||
call("suspend", stub.ExpectArgs{}, nil, nil),
|
||||
call("suspend", stub.ExpectArgs{}, true, nil),
|
||||
call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(7)}}, nil, nil),
|
||||
call("New", stub.ExpectArgs{}, nil, nil),
|
||||
call("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- os.Interrupt }, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil),
|
||||
@ -2155,7 +2176,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
}}},
|
||||
}, nil},
|
||||
|
||||
{"lowlastcap signaled timeout", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseFalse); return nil }, stub.Expect{
|
||||
{"lowlastcap signaled timeout", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
/* entrypoint */
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
@ -2176,6 +2197,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
SeccompDisable: true,
|
||||
ParentPerm: 0750,
|
||||
}, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(5), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{false}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("16777216 1971 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -2228,7 +2250,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
call("umask", stub.ExpectArgs{022}, 0, nil),
|
||||
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
||||
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
|
||||
call("suspend", stub.ExpectArgs{}, nil, nil),
|
||||
call("suspend", stub.ExpectArgs{}, true, nil),
|
||||
call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(5)}}, nil, nil),
|
||||
call("New", stub.ExpectArgs{}, nil, nil),
|
||||
call("notify", stub.ExpectArgs{nil, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil),
|
||||
@ -2247,7 +2269,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
}}},
|
||||
}, nil},
|
||||
|
||||
{"lowlastcap signaled", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseFalse); return nil }, stub.Expect{
|
||||
{"lowlastcap signaled", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
/* entrypoint */
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
@ -2268,6 +2290,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
SeccompDisable: true,
|
||||
ParentPerm: 0750,
|
||||
}, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(3), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{false}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("16777216 1971 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -2320,7 +2343,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
call("umask", stub.ExpectArgs{022}, 0, nil),
|
||||
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
||||
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
|
||||
call("suspend", stub.ExpectArgs{}, nil, nil),
|
||||
call("suspend", stub.ExpectArgs{}, true, nil),
|
||||
call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(3)}}, nil, nil),
|
||||
call("New", stub.ExpectArgs{}, nil, nil),
|
||||
call("notify", stub.ExpectArgs{nil, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil),
|
||||
@ -2346,7 +2369,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
}}},
|
||||
}, nil},
|
||||
|
||||
{"strangewait nopriv notty noseccomp yamafault", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseFalse); return nil }, stub.Expect{
|
||||
{"strangewait nopriv notty noseccomp yamafault", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
/* entrypoint */
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
@ -2367,6 +2390,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
SeccompDisable: true,
|
||||
ParentPerm: 0750,
|
||||
}, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(1), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{false}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("16777216 1971 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -2455,7 +2479,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
call("umask", stub.ExpectArgs{022}, 0, nil),
|
||||
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
||||
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
|
||||
call("suspend", stub.ExpectArgs{}, nil, nil),
|
||||
call("suspend", stub.ExpectArgs{}, true, nil),
|
||||
call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(1)}}, nil, nil),
|
||||
call("New", stub.ExpectArgs{}, nil, nil),
|
||||
call("notify", stub.ExpectArgs{nil, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil),
|
||||
@ -2481,7 +2505,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
}}},
|
||||
}, nil},
|
||||
|
||||
{"success", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
|
||||
{"success", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||
/* entrypoint */
|
||||
Calls: []stub.Call{
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
@ -2503,6 +2527,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
RetainSession: true,
|
||||
Privileged: true,
|
||||
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(0), nil),
|
||||
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
|
||||
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
|
||||
@ -2594,7 +2619,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
call("umask", stub.ExpectArgs{022}, 0, nil),
|
||||
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{MustAbs("/bin/zsh")}}, nil, nil),
|
||||
call("start", stub.ExpectArgs{"/bin/zsh", []string{"zsh", "-c", "exec vim"}, []string{"DISPLAY=:0"}, "/.hakurei"}, &os.Process{Pid: 0xcafe}, nil),
|
||||
call("suspend", stub.ExpectArgs{}, nil, nil),
|
||||
call("suspend", stub.ExpectArgs{}, true, nil),
|
||||
call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(0)}}, nil, nil),
|
||||
call("New", stub.ExpectArgs{}, nil, nil),
|
||||
call("notify", stub.ExpectArgs{nil, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil),
|
||||
|
@ -59,7 +59,7 @@ func (b *BindMountOp) early(_ *setupState, k syscallDispatcher) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BindMountOp) apply(_ *setupState, k syscallDispatcher) error {
|
||||
func (b *BindMountOp) apply(state *setupState, k syscallDispatcher) error {
|
||||
if b.sourceFinal == nil {
|
||||
if b.Flags&BindOptional == 0 {
|
||||
// unreachable
|
||||
@ -92,11 +92,11 @@ func (b *BindMountOp) apply(_ *setupState, k syscallDispatcher) error {
|
||||
}
|
||||
|
||||
if b.sourceFinal.String() == b.Target.String() {
|
||||
k.verbosef("mounting %q flags %#x", target, flags)
|
||||
state.Verbosef("mounting %q flags %#x", target, flags)
|
||||
} else {
|
||||
k.verbosef("mounting %q on %q flags %#x", source, target, flags)
|
||||
state.Verbosef("mounting %q on %q flags %#x", source, target, flags)
|
||||
}
|
||||
return k.bindMount(source, target, flags)
|
||||
return k.bindMount(state, source, target, flags)
|
||||
}
|
||||
|
||||
func (b *BindMountOp) Is(op Op) bool {
|
||||
|
@ -46,6 +46,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
|
||||
return err
|
||||
}
|
||||
if err := k.bindMount(
|
||||
state,
|
||||
toHost(FHSDev+name),
|
||||
targetPath,
|
||||
0,
|
||||
@ -93,6 +94,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
|
||||
if name, err := k.readlink(hostProc.stdout()); err != nil {
|
||||
return err
|
||||
} else if err = k.bindMount(
|
||||
state,
|
||||
toHost(name),
|
||||
consolePath,
|
||||
0,
|
||||
@ -116,7 +118,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := k.remount(target, MS_RDONLY); err != nil {
|
||||
if err := k.remount(state, target, MS_RDONLY); err != nil {
|
||||
return err
|
||||
}
|
||||
return k.mountTmpfs(SourceTmpfs, devShmPath, MS_NOSUID|MS_NODEV, 0, 01777)
|
||||
|
@ -52,6 +52,7 @@ func (t *TmpfileOp) apply(state *setupState, k syscallDispatcher) error {
|
||||
if err := k.ensureFile(target, 0444, state.ParentPerm); err != nil {
|
||||
return err
|
||||
} else if err = k.bindMount(
|
||||
state,
|
||||
tmpPath,
|
||||
target,
|
||||
syscall.MS_RDONLY|syscall.MS_NODEV,
|
||||
|
@ -21,8 +21,8 @@ type RemountOp struct {
|
||||
|
||||
func (r *RemountOp) Valid() bool { return r != nil && r.Target != nil }
|
||||
func (*RemountOp) early(*setupState, syscallDispatcher) error { return nil }
|
||||
func (r *RemountOp) apply(_ *setupState, k syscallDispatcher) error {
|
||||
return k.remount(toSysroot(r.Target.String()), r.Flags)
|
||||
func (r *RemountOp) apply(state *setupState, k syscallDispatcher) error {
|
||||
return k.remount(state, toSysroot(r.Target.String()), r.Flags)
|
||||
}
|
||||
|
||||
func (r *RemountOp) Is(op Op) bool {
|
||||
|
@ -96,18 +96,17 @@ const (
|
||||
)
|
||||
|
||||
// bindMount mounts source on target and recursively applies flags if MS_REC is set.
|
||||
func (p *procPaths) bindMount(source, target string, flags uintptr) error {
|
||||
func (p *procPaths) bindMount(msg Msg, source, target string, flags uintptr) error {
|
||||
// syscallDispatcher.bindMount and procPaths.remount must not be called from this function
|
||||
|
||||
if err := p.k.mount(source, target, FstypeNULL, MS_SILENT|MS_BIND|flags&MS_REC, zeroString); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.k.remount(target, flags)
|
||||
return p.k.remount(msg, target, flags)
|
||||
}
|
||||
|
||||
// remount applies flags on target, recursively if MS_REC is set.
|
||||
func (p *procPaths) remount(target string, flags uintptr) error {
|
||||
func (p *procPaths) remount(msg Msg, target string, flags uintptr) error {
|
||||
// syscallDispatcher methods bindMount, remount must not be called from this function
|
||||
|
||||
var targetFinal string
|
||||
@ -116,7 +115,7 @@ func (p *procPaths) remount(target string, flags uintptr) error {
|
||||
} else {
|
||||
targetFinal = v
|
||||
if targetFinal != target {
|
||||
p.k.verbosef("target resolves to %q", targetFinal)
|
||||
msg.Verbosef("target resolves to %q", targetFinal)
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,7 +145,7 @@ func (p *procPaths) remount(target string, flags uintptr) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = remountWithFlags(p.k, n, mf); err != nil {
|
||||
if err = remountWithFlags(p.k, msg, n, mf); err != nil {
|
||||
return err
|
||||
}
|
||||
if flags&MS_REC == 0 {
|
||||
@ -159,7 +158,7 @@ func (p *procPaths) remount(target string, flags uintptr) error {
|
||||
continue
|
||||
}
|
||||
|
||||
if err = remountWithFlags(p.k, cur, mf); err != nil && !errors.Is(err, EACCES) {
|
||||
if err = remountWithFlags(p.k, msg, cur, mf); err != nil && !errors.Is(err, EACCES) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -169,12 +168,12 @@ func (p *procPaths) remount(target string, flags uintptr) error {
|
||||
}
|
||||
|
||||
// remountWithFlags remounts mount point described by [vfs.MountInfoNode].
|
||||
func remountWithFlags(k syscallDispatcher, n *vfs.MountInfoNode, mf uintptr) error {
|
||||
func remountWithFlags(k syscallDispatcher, msg Msg, n *vfs.MountInfoNode, mf uintptr) error {
|
||||
// syscallDispatcher methods bindMount, remount must not be called from this function
|
||||
|
||||
kf, unmatched := n.Flags()
|
||||
if len(unmatched) != 0 {
|
||||
k.verbosef("unmatched vfs options: %q", unmatched)
|
||||
msg.Verbosef("unmatched vfs options: %q", unmatched)
|
||||
}
|
||||
|
||||
if kf&mf != mf {
|
||||
|
@ -11,21 +11,21 @@ import (
|
||||
|
||||
func TestBindMount(t *testing.T) {
|
||||
checkSimple(t, "bindMount", []simpleTestCase{
|
||||
{"mount", func(k syscallDispatcher) error {
|
||||
return newProcPaths(k, hostPath).bindMount("/host/nix", "/sysroot/nix", syscall.MS_RDONLY)
|
||||
{"mount", func(k *kstub) error {
|
||||
return newProcPaths(k, hostPath).bindMount(nil, "/host/nix", "/sysroot/nix", syscall.MS_RDONLY)
|
||||
}, stub.Expect{Calls: []stub.Call{
|
||||
call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/nix", "", uintptr(0x9000), ""}, nil, stub.UniqueError(0xbad)),
|
||||
}}, stub.UniqueError(0xbad)},
|
||||
|
||||
{"success ne", func(k syscallDispatcher) error {
|
||||
return newProcPaths(k, hostPath).bindMount("/host/nix", "/sysroot/.host-nix", syscall.MS_RDONLY)
|
||||
{"success ne", func(k *kstub) error {
|
||||
return newProcPaths(k, hostPath).bindMount(k, "/host/nix", "/sysroot/.host-nix", syscall.MS_RDONLY)
|
||||
}, stub.Expect{Calls: []stub.Call{
|
||||
call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/.host-nix", "", uintptr(0x9000), ""}, nil, nil),
|
||||
call("remount", stub.ExpectArgs{"/sysroot/.host-nix", uintptr(1)}, nil, nil),
|
||||
}}, nil},
|
||||
|
||||
{"success", func(k syscallDispatcher) error {
|
||||
return newProcPaths(k, hostPath).bindMount("/host/nix", "/sysroot/nix", syscall.MS_RDONLY)
|
||||
{"success", func(k *kstub) error {
|
||||
return newProcPaths(k, hostPath).bindMount(k, "/host/nix", "/sysroot/nix", syscall.MS_RDONLY)
|
||||
}, stub.Expect{Calls: []stub.Call{
|
||||
call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/nix", "", uintptr(0x9000), ""}, nil, nil),
|
||||
call("remount", stub.ExpectArgs{"/sysroot/nix", uintptr(1)}, nil, nil),
|
||||
@ -77,29 +77,29 @@ func TestRemount(t *testing.T) {
|
||||
416 415 0:30 / /sysroot/nix/store ro,relatime master:5 - overlay overlay rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work`
|
||||
|
||||
checkSimple(t, "remount", []simpleTestCase{
|
||||
{"evalSymlinks", func(k syscallDispatcher) error {
|
||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
{"evalSymlinks", func(k *kstub) error {
|
||||
return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
}, stub.Expect{Calls: []stub.Call{
|
||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", stub.UniqueError(6)),
|
||||
}}, stub.UniqueError(6)},
|
||||
|
||||
{"open", func(k syscallDispatcher) error {
|
||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
{"open", func(k *kstub) error {
|
||||
return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
}, stub.Expect{Calls: []stub.Call{
|
||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, stub.UniqueError(5)),
|
||||
}}, &os.PathError{Op: "open", Path: "/sysroot/nix", Err: stub.UniqueError(5)}},
|
||||
|
||||
{"readlink", func(k syscallDispatcher) error {
|
||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
{"readlink", func(k *kstub) error {
|
||||
return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
}, stub.Expect{Calls: []stub.Call{
|
||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||
call("readlink", stub.ExpectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/nix", stub.UniqueError(4)),
|
||||
}}, stub.UniqueError(4)},
|
||||
|
||||
{"close", func(k syscallDispatcher) error {
|
||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
{"close", func(k *kstub) error {
|
||||
return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
}, stub.Expect{Calls: []stub.Call{
|
||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||
@ -107,8 +107,8 @@ func TestRemount(t *testing.T) {
|
||||
call("close", stub.ExpectArgs{0xdeadbeef}, nil, stub.UniqueError(3)),
|
||||
}}, &os.PathError{Op: "close", Path: "/sysroot/nix", Err: stub.UniqueError(3)}},
|
||||
|
||||
{"mountinfo no match", func(k syscallDispatcher) error {
|
||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
{"mountinfo no match", func(k *kstub) error {
|
||||
return newProcPaths(k, hostPath).remount(k, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
}, stub.Expect{Calls: []stub.Call{
|
||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/.hakurei", nil),
|
||||
call("verbosef", stub.ExpectArgs{"target resolves to %q", []any{"/sysroot/.hakurei"}}, nil, nil),
|
||||
@ -118,8 +118,8 @@ func TestRemount(t *testing.T) {
|
||||
call("openNew", stub.ExpectArgs{"/host/proc/self/mountinfo"}, newConstFile(sampleMountinfoNix), nil),
|
||||
}}, &vfs.DecoderError{Op: "unfold", Line: -1, Err: vfs.UnfoldTargetError("/sysroot/.hakurei")}},
|
||||
|
||||
{"mountinfo", func(k syscallDispatcher) error {
|
||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
{"mountinfo", func(k *kstub) error {
|
||||
return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
}, stub.Expect{Calls: []stub.Call{
|
||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||
@ -128,8 +128,8 @@ func TestRemount(t *testing.T) {
|
||||
call("openNew", stub.ExpectArgs{"/host/proc/self/mountinfo"}, newConstFile("\x00"), nil),
|
||||
}}, &vfs.DecoderError{Op: "parse", Line: 0, Err: vfs.ErrMountInfoFields}},
|
||||
|
||||
{"mount", func(k syscallDispatcher) error {
|
||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
{"mount", func(k *kstub) error {
|
||||
return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
}, stub.Expect{Calls: []stub.Call{
|
||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||
@ -139,8 +139,8 @@ func TestRemount(t *testing.T) {
|
||||
call("mount", stub.ExpectArgs{"none", "/sysroot/nix", "", uintptr(0x209027), ""}, nil, stub.UniqueError(2)),
|
||||
}}, stub.UniqueError(2)},
|
||||
|
||||
{"mount propagate", func(k syscallDispatcher) error {
|
||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
{"mount propagate", func(k *kstub) error {
|
||||
return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
}, stub.Expect{Calls: []stub.Call{
|
||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||
@ -151,8 +151,8 @@ func TestRemount(t *testing.T) {
|
||||
call("mount", stub.ExpectArgs{"none", "/sysroot/nix/.ro-store", "", uintptr(0x209027), ""}, nil, stub.UniqueError(1)),
|
||||
}}, stub.UniqueError(1)},
|
||||
|
||||
{"success toplevel", func(k syscallDispatcher) error {
|
||||
return newProcPaths(k, hostPath).remount("/sysroot/bin", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
{"success toplevel", func(k *kstub) error {
|
||||
return newProcPaths(k, hostPath).remount(nil, "/sysroot/bin", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
}, stub.Expect{Calls: []stub.Call{
|
||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/bin"}, "/sysroot/bin", nil),
|
||||
call("open", stub.ExpectArgs{"/sysroot/bin", 0x280000, uint32(0)}, 0xbabe, nil),
|
||||
@ -162,8 +162,8 @@ func TestRemount(t *testing.T) {
|
||||
call("mount", stub.ExpectArgs{"none", "/sysroot/bin", "", uintptr(0x209027), ""}, nil, nil),
|
||||
}}, nil},
|
||||
|
||||
{"success EACCES", func(k syscallDispatcher) error {
|
||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
{"success EACCES", func(k *kstub) error {
|
||||
return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
}, stub.Expect{Calls: []stub.Call{
|
||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||
@ -175,8 +175,8 @@ func TestRemount(t *testing.T) {
|
||||
call("mount", stub.ExpectArgs{"none", "/sysroot/nix/store", "", uintptr(0x209027), ""}, nil, nil),
|
||||
}}, nil},
|
||||
|
||||
{"success no propagate", func(k syscallDispatcher) error {
|
||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
{"success no propagate", func(k *kstub) error {
|
||||
return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
}, stub.Expect{Calls: []stub.Call{
|
||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||
@ -186,8 +186,8 @@ func TestRemount(t *testing.T) {
|
||||
call("mount", stub.ExpectArgs{"none", "/sysroot/nix", "", uintptr(0x209027), ""}, nil, nil),
|
||||
}}, nil},
|
||||
|
||||
{"success case sensitive", func(k syscallDispatcher) error {
|
||||
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
{"success case sensitive", func(k *kstub) error {
|
||||
return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
}, stub.Expect{Calls: []stub.Call{
|
||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
|
||||
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil),
|
||||
@ -199,8 +199,8 @@ func TestRemount(t *testing.T) {
|
||||
call("mount", stub.ExpectArgs{"none", "/sysroot/nix/store", "", uintptr(0x209027), ""}, nil, nil),
|
||||
}}, nil},
|
||||
|
||||
{"success", func(k syscallDispatcher) error {
|
||||
return newProcPaths(k, hostPath).remount("/sysroot/.nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
{"success", func(k *kstub) error {
|
||||
return newProcPaths(k, hostPath).remount(k, "/sysroot/.nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
|
||||
}, stub.Expect{Calls: []stub.Call{
|
||||
call("evalSymlinks", stub.ExpectArgs{"/sysroot/.nix"}, "/sysroot/NIX", nil),
|
||||
call("verbosef", stub.ExpectArgs{"target resolves to %q", []any{"/sysroot/NIX"}}, nil, nil),
|
||||
@ -217,18 +217,18 @@ func TestRemount(t *testing.T) {
|
||||
|
||||
func TestRemountWithFlags(t *testing.T) {
|
||||
checkSimple(t, "remountWithFlags", []simpleTestCase{
|
||||
{"noop unmatched", func(k syscallDispatcher) error {
|
||||
return remountWithFlags(k, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime,cat"}}, 0)
|
||||
{"noop unmatched", func(k *kstub) error {
|
||||
return remountWithFlags(k, k, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime,cat"}}, 0)
|
||||
}, stub.Expect{Calls: []stub.Call{
|
||||
call("verbosef", stub.ExpectArgs{"unmatched vfs options: %q", []any{[]string{"cat"}}}, nil, nil),
|
||||
}}, nil},
|
||||
|
||||
{"noop", func(k syscallDispatcher) error {
|
||||
return remountWithFlags(k, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime"}}, 0)
|
||||
{"noop", func(k *kstub) error {
|
||||
return remountWithFlags(k, nil, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime"}}, 0)
|
||||
}, stub.Expect{}, nil},
|
||||
|
||||
{"success", func(k syscallDispatcher) error {
|
||||
return remountWithFlags(k, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime"}}, syscall.MS_RDONLY)
|
||||
{"success", func(k *kstub) error {
|
||||
return remountWithFlags(k, nil, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime"}}, syscall.MS_RDONLY)
|
||||
}, stub.Expect{Calls: []stub.Call{
|
||||
call("mount", stub.ExpectArgs{"none", "", "", uintptr(0x209021), ""}, nil, nil),
|
||||
}}, nil},
|
||||
@ -237,20 +237,20 @@ func TestRemountWithFlags(t *testing.T) {
|
||||
|
||||
func TestMountTmpfs(t *testing.T) {
|
||||
checkSimple(t, "mountTmpfs", []simpleTestCase{
|
||||
{"mkdirAll", func(k syscallDispatcher) error {
|
||||
{"mkdirAll", func(k *kstub) error {
|
||||
return mountTmpfs(k, "ephemeral", "/sysroot/run/user/1000", 0, 1<<10, 0700)
|
||||
}, stub.Expect{Calls: []stub.Call{
|
||||
call("mkdirAll", stub.ExpectArgs{"/sysroot/run/user/1000", os.FileMode(0700)}, nil, stub.UniqueError(0)),
|
||||
}}, stub.UniqueError(0)},
|
||||
|
||||
{"success no size", func(k syscallDispatcher) error {
|
||||
{"success no size", func(k *kstub) error {
|
||||
return mountTmpfs(k, "ephemeral", "/sysroot/run/user/1000", 0, 0, 0710)
|
||||
}, stub.Expect{Calls: []stub.Call{
|
||||
call("mkdirAll", stub.ExpectArgs{"/sysroot/run/user/1000", os.FileMode(0750)}, nil, nil),
|
||||
call("mount", stub.ExpectArgs{"ephemeral", "/sysroot/run/user/1000", "tmpfs", uintptr(0), "mode=0710"}, nil, nil),
|
||||
}}, nil},
|
||||
|
||||
{"success", func(k syscallDispatcher) error {
|
||||
{"success", func(k *kstub) error {
|
||||
return mountTmpfs(k, "ephemeral", "/sysroot/run/user/1000", 0, 1<<10, 0700)
|
||||
}, stub.Expect{Calls: []stub.Call{
|
||||
call("mkdirAll", stub.ExpectArgs{"/sysroot/run/user/1000", os.FileMode(0700)}, nil, nil),
|
||||
|
@ -23,45 +23,88 @@ func GetErrorMessage(err error) (string, bool) {
|
||||
return e.Message(), true
|
||||
}
|
||||
|
||||
// Msg is used for package-wide verbose logging.
|
||||
type Msg interface {
|
||||
// GetLogger returns the address of the underlying [log.Logger].
|
||||
GetLogger() *log.Logger
|
||||
|
||||
// IsVerbose atomically loads and returns whether [Msg] has verbose logging enabled.
|
||||
IsVerbose() bool
|
||||
// SwapVerbose atomically stores a new verbose state and returns the previous value held by [Msg].
|
||||
SwapVerbose(verbose bool) bool
|
||||
// Verbose passes its argument to the Println method of the underlying [log.Logger] if IsVerbose returns true.
|
||||
Verbose(v ...any)
|
||||
// Verbosef passes its argument to the Printf method of the underlying [log.Logger] if IsVerbose returns true.
|
||||
Verbosef(format string, v ...any)
|
||||
|
||||
Suspend()
|
||||
// Suspend causes the embedded [Suspendable] to withhold writes to its downstream [io.Writer].
|
||||
// Suspend returns false and is a noop if called between calls to Suspend and Resume.
|
||||
Suspend() bool
|
||||
|
||||
// Resume dumps the entire buffer held by the embedded [Suspendable] and stops withholding future writes.
|
||||
// Resume returns false and is a noop if a call to Suspend does not precede it.
|
||||
Resume() bool
|
||||
|
||||
// BeforeExit runs implementation-specific cleanup code, and optionally prints warnings.
|
||||
// BeforeExit is called before [os.Exit].
|
||||
BeforeExit()
|
||||
}
|
||||
|
||||
type DefaultMsg struct{ inactive atomic.Bool }
|
||||
// defaultMsg is the default implementation of the [Msg] interface.
|
||||
// The zero value is not safe for use. Callers should use the [NewMsg] function instead.
|
||||
type defaultMsg struct {
|
||||
verbose atomic.Bool
|
||||
|
||||
func (msg *DefaultMsg) IsVerbose() bool { return true }
|
||||
func (msg *DefaultMsg) Verbose(v ...any) {
|
||||
if !msg.inactive.Load() {
|
||||
log.Println(v...)
|
||||
logger *log.Logger
|
||||
Suspendable
|
||||
}
|
||||
|
||||
// NewMsg initialises a downstream [log.Logger] for a new [Msg].
|
||||
// The [log.Logger] should no longer be configured after NewMsg returns.
|
||||
// If downstream is nil, a new logger is initialised in its place.
|
||||
func NewMsg(downstream *log.Logger) Msg {
|
||||
if downstream == nil {
|
||||
downstream = log.New(log.Writer(), "container: ", 0)
|
||||
}
|
||||
|
||||
m := defaultMsg{logger: downstream}
|
||||
m.Suspendable.Downstream = downstream.Writer()
|
||||
downstream.SetOutput(&m.Suspendable)
|
||||
return &m
|
||||
}
|
||||
|
||||
func (msg *defaultMsg) GetLogger() *log.Logger { return msg.logger }
|
||||
|
||||
func (msg *defaultMsg) IsVerbose() bool { return msg.verbose.Load() }
|
||||
func (msg *defaultMsg) SwapVerbose(verbose bool) bool { return msg.verbose.Swap(verbose) }
|
||||
func (msg *defaultMsg) Verbose(v ...any) {
|
||||
if msg.verbose.Load() {
|
||||
msg.logger.Println(v...)
|
||||
}
|
||||
}
|
||||
func (msg *DefaultMsg) Verbosef(format string, v ...any) {
|
||||
if !msg.inactive.Load() {
|
||||
log.Printf(format, v...)
|
||||
func (msg *defaultMsg) Verbosef(format string, v ...any) {
|
||||
if msg.verbose.Load() {
|
||||
msg.logger.Printf(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func (msg *DefaultMsg) Suspend() { msg.inactive.Store(true) }
|
||||
func (msg *DefaultMsg) Resume() bool { return msg.inactive.CompareAndSwap(true, false) }
|
||||
func (msg *DefaultMsg) BeforeExit() {}
|
||||
// Resume calls [Suspendable.Resume] and prints a message if buffer was filled
|
||||
// between calls to [Suspendable.Suspend] and Resume.
|
||||
func (msg *defaultMsg) Resume() bool {
|
||||
resumed, dropped, _, err := msg.Suspendable.Resume()
|
||||
if err != nil {
|
||||
// probably going to result in an error as well, so this message is as good as unreachable
|
||||
msg.logger.Printf("cannot dump buffer on resume: %v", err)
|
||||
}
|
||||
if resumed && dropped > 0 {
|
||||
msg.logger.Printf("dropped %d bytes while output is suspended", dropped)
|
||||
}
|
||||
return resumed
|
||||
}
|
||||
|
||||
// msg is the [Msg] implemented used by all exported [container] functions.
|
||||
var msg Msg = new(DefaultMsg)
|
||||
|
||||
// GetOutput returns the current active [Msg] implementation.
|
||||
func GetOutput() Msg { return msg }
|
||||
|
||||
// SetOutput replaces the current active [Msg] implementation.
|
||||
func SetOutput(v Msg) {
|
||||
if v == nil {
|
||||
msg = new(DefaultMsg)
|
||||
} else {
|
||||
msg = v
|
||||
// BeforeExit prints a message if called between calls to [Suspendable.Suspend] and Resume.
|
||||
func (msg *defaultMsg) BeforeExit() {
|
||||
if msg.Resume() {
|
||||
msg.logger.Printf("beforeExit reached on suspended output")
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,16 @@
|
||||
package container_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/stub"
|
||||
)
|
||||
|
||||
func TestMessageError(t *testing.T) {
|
||||
@ -39,146 +41,137 @@ func TestMessageError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDefaultMsg(t *testing.T) {
|
||||
{
|
||||
w := log.Writer()
|
||||
f := log.Flags()
|
||||
t.Cleanup(func() { log.SetOutput(w); log.SetFlags(f) })
|
||||
}
|
||||
msg := new(container.DefaultMsg)
|
||||
// copied from output.go
|
||||
const suspendBufMax = 1 << 24
|
||||
|
||||
t.Run("is verbose", func(t *testing.T) {
|
||||
if !msg.IsVerbose() {
|
||||
t.Error("IsVerbose unexpected outcome")
|
||||
}
|
||||
})
|
||||
t.Run("logger", func(t *testing.T) {
|
||||
t.Run("nil", func(t *testing.T) {
|
||||
got := container.NewMsg(nil).GetLogger()
|
||||
|
||||
t.Run("verbose", func(t *testing.T) {
|
||||
log.SetOutput(panicWriter{})
|
||||
msg.Suspend()
|
||||
msg.Verbose()
|
||||
msg.Verbosef("\x00")
|
||||
msg.Resume()
|
||||
|
||||
buf := new(strings.Builder)
|
||||
log.SetOutput(buf)
|
||||
log.SetFlags(0)
|
||||
msg.Verbose()
|
||||
msg.Verbosef("\x00")
|
||||
|
||||
want := "\n\x00\n"
|
||||
if buf.String() != want {
|
||||
t.Errorf("Verbose: %q, want %q", buf.String(), want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("inactive", func(t *testing.T) {
|
||||
{
|
||||
inactive := msg.Resume()
|
||||
if inactive {
|
||||
t.Cleanup(func() { msg.Suspend() })
|
||||
if out := got.Writer().(*container.Suspendable).Downstream; out != log.Writer() {
|
||||
t.Errorf("GetLogger: Downstream = %#v", out)
|
||||
}
|
||||
}
|
||||
|
||||
if msg.Resume() {
|
||||
t.Error("Resume unexpected outcome")
|
||||
}
|
||||
if prefix := got.Prefix(); prefix != "container: " {
|
||||
t.Errorf("GetLogger: prefix = %q", prefix)
|
||||
}
|
||||
})
|
||||
|
||||
msg.Suspend()
|
||||
if !msg.Resume() {
|
||||
t.Error("Resume unexpected outcome")
|
||||
}
|
||||
t.Run("takeover", func(t *testing.T) {
|
||||
l := log.New(io.Discard, "\x00", 0xdeadbeef)
|
||||
got := container.NewMsg(l)
|
||||
|
||||
if logger := got.GetLogger(); logger != l {
|
||||
t.Errorf("GetLogger: %#v, want %#v", logger, l)
|
||||
}
|
||||
|
||||
if ds := l.Writer().(*container.Suspendable).Downstream; ds != io.Discard {
|
||||
t.Errorf("GetLogger: Downstream = %#v", ds)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// the function is a noop
|
||||
t.Run("beforeExit", func(t *testing.T) { msg.BeforeExit() })
|
||||
}
|
||||
dw := expectWriter{t: t}
|
||||
|
||||
type panicWriter struct{}
|
||||
steps := []struct {
|
||||
name string
|
||||
pt, next []byte
|
||||
err error
|
||||
|
||||
func (panicWriter) Write([]byte) (int, error) { panic("unreachable") }
|
||||
f func(t *testing.T, msg container.Msg)
|
||||
}{
|
||||
{"zero verbose", nil, nil, nil, func(t *testing.T, msg container.Msg) {
|
||||
if msg.IsVerbose() {
|
||||
t.Error("IsVerbose unexpected true")
|
||||
}
|
||||
}},
|
||||
|
||||
func saveRestoreOutput(t *testing.T) {
|
||||
out := container.GetOutput()
|
||||
t.Cleanup(func() { container.SetOutput(out) })
|
||||
}
|
||||
{"swap false", nil, nil, nil, func(t *testing.T, msg container.Msg) {
|
||||
if msg.SwapVerbose(false) {
|
||||
t.Error("SwapVerbose unexpected true")
|
||||
}
|
||||
}},
|
||||
{"write discard", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||
msg.Verbose("\x00")
|
||||
msg.Verbosef("\x00")
|
||||
}},
|
||||
{"verbose false", nil, nil, nil, func(t *testing.T, msg container.Msg) {
|
||||
if msg.IsVerbose() {
|
||||
t.Error("IsVerbose unexpected true")
|
||||
}
|
||||
}},
|
||||
|
||||
func replaceOutput(t *testing.T) {
|
||||
saveRestoreOutput(t)
|
||||
container.SetOutput(&testOutput{t: t})
|
||||
}
|
||||
{"swap true", nil, nil, nil, func(t *testing.T, msg container.Msg) {
|
||||
if msg.SwapVerbose(true) {
|
||||
t.Error("SwapVerbose unexpected true")
|
||||
}
|
||||
}},
|
||||
{"write verbose", []byte("test: \x00\n"), nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||
msg.Verbose("\x00")
|
||||
}},
|
||||
{"write verbosef", []byte(`test: "\x00"` + "\n"), nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||
msg.Verbosef("%q", "\x00")
|
||||
}},
|
||||
{"verbose true", nil, nil, nil, func(t *testing.T, msg container.Msg) {
|
||||
if !msg.IsVerbose() {
|
||||
t.Error("IsVerbose unexpected false")
|
||||
}
|
||||
}},
|
||||
|
||||
type testOutput struct {
|
||||
t *testing.T
|
||||
suspended atomic.Bool
|
||||
}
|
||||
{"resume noop", nil, nil, nil, func(t *testing.T, msg container.Msg) {
|
||||
if msg.Resume() {
|
||||
t.Error("Resume unexpected success")
|
||||
}
|
||||
}},
|
||||
{"beforeExit noop", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||
msg.BeforeExit()
|
||||
}},
|
||||
|
||||
func (out *testOutput) IsVerbose() bool { return testing.Verbose() }
|
||||
{"beforeExit suspend", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||
msg.Suspend()
|
||||
}},
|
||||
{"beforeExit message", []byte("test: beforeExit reached on suspended output\n"), nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||
msg.BeforeExit()
|
||||
}},
|
||||
{"post beforeExit resume noop", nil, nil, nil, func(t *testing.T, msg container.Msg) {
|
||||
if msg.Resume() {
|
||||
t.Error("Resume unexpected success")
|
||||
}
|
||||
}},
|
||||
|
||||
func (out *testOutput) Verbose(v ...any) {
|
||||
if !out.IsVerbose() {
|
||||
return
|
||||
}
|
||||
out.t.Log(v...)
|
||||
}
|
||||
{"suspend", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||
msg.Suspend()
|
||||
}},
|
||||
{"suspend write", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||
msg.GetLogger().Print("\x00")
|
||||
}},
|
||||
{"resume error", []byte("test: \x00\n"), []byte("test: cannot dump buffer on resume: unique error 0 injected by the test suite\n"), stub.UniqueError(0), func(t *testing.T, msg container.Msg) {
|
||||
if !msg.Resume() {
|
||||
t.Error("Resume unexpected failure")
|
||||
}
|
||||
}},
|
||||
|
||||
func (out *testOutput) Verbosef(format string, v ...any) {
|
||||
if !out.IsVerbose() {
|
||||
return
|
||||
}
|
||||
out.t.Logf(format, v...)
|
||||
}
|
||||
|
||||
func (out *testOutput) Suspend() {
|
||||
if out.suspended.CompareAndSwap(false, true) {
|
||||
out.Verbose("suspend called")
|
||||
return
|
||||
}
|
||||
out.Verbose("suspend called on suspended output")
|
||||
}
|
||||
|
||||
func (out *testOutput) Resume() bool {
|
||||
if out.suspended.CompareAndSwap(true, false) {
|
||||
out.Verbose("resume called")
|
||||
return true
|
||||
}
|
||||
out.Verbose("resume called on unsuspended output")
|
||||
return false
|
||||
}
|
||||
|
||||
func (out *testOutput) BeforeExit() { out.Verbose("beforeExit called") }
|
||||
|
||||
func TestGetSetOutput(t *testing.T) {
|
||||
{
|
||||
out := container.GetOutput()
|
||||
t.Cleanup(func() { container.SetOutput(out) })
|
||||
{"suspend drop", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||
msg.Suspend()
|
||||
}},
|
||||
{"suspend write fill", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
|
||||
msg.GetLogger().Print(strings.Repeat("\x00", suspendBufMax))
|
||||
}},
|
||||
{"resume dropped", append([]byte("test: "), bytes.Repeat([]byte{0}, suspendBufMax-6)...), []byte("test: dropped 7 bytes while output is suspended\n"), nil, func(t *testing.T, msg container.Msg) {
|
||||
if !msg.Resume() {
|
||||
t.Error("Resume unexpected failure")
|
||||
}
|
||||
}},
|
||||
}
|
||||
|
||||
t.Run("default", func(t *testing.T) {
|
||||
container.SetOutput(new(stubOutput))
|
||||
if v, ok := container.GetOutput().(*container.DefaultMsg); ok {
|
||||
t.Fatalf("SetOutput: got unexpected output %#v", v)
|
||||
msg := container.NewMsg(log.New(&dw, "test: ", 0))
|
||||
for _, step := range steps {
|
||||
// these share the same writer, so cannot be subtests
|
||||
t.Logf("running step %q", step.name)
|
||||
dw.expect, dw.next, dw.err = step.pt, step.next, step.err
|
||||
step.f(t, msg)
|
||||
if dw.expect != nil {
|
||||
t.Errorf("expect: %q", string(dw.expect))
|
||||
}
|
||||
container.SetOutput(nil)
|
||||
if _, ok := container.GetOutput().(*container.DefaultMsg); !ok {
|
||||
t.Fatalf("SetOutput: got unexpected output %#v", container.GetOutput())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("stub", func(t *testing.T) {
|
||||
container.SetOutput(new(stubOutput))
|
||||
if _, ok := container.GetOutput().(*stubOutput); !ok {
|
||||
t.Fatalf("SetOutput: got unexpected output %#v", container.GetOutput())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type stubOutput struct {
|
||||
wrapF func(error, ...any) error
|
||||
}
|
||||
|
||||
func (*stubOutput) IsVerbose() bool { panic("unreachable") }
|
||||
func (*stubOutput) Verbose(...any) { panic("unreachable") }
|
||||
func (*stubOutput) Verbosef(string, ...any) { panic("unreachable") }
|
||||
func (*stubOutput) Suspend() { panic("unreachable") }
|
||||
func (*stubOutput) Resume() bool { panic("unreachable") }
|
||||
func (*stubOutput) BeforeExit() { panic("unreachable") }
|
||||
|
@ -28,7 +28,7 @@ func TestSuspendable(t *testing.T) {
|
||||
)
|
||||
|
||||
// shares the same writer
|
||||
testCases := []struct {
|
||||
steps := []struct {
|
||||
name string
|
||||
w, pt []byte
|
||||
err error
|
||||
@ -75,25 +75,25 @@ func TestSuspendable(t *testing.T) {
|
||||
var dw expectWriter
|
||||
|
||||
w := container.Suspendable{Downstream: &dw}
|
||||
for _, tc := range testCases {
|
||||
for _, step := range steps {
|
||||
// these share the same writer, so cannot be subtests
|
||||
t.Logf("writing step %q", tc.name)
|
||||
dw.expect, dw.err = tc.pt, tc.err
|
||||
t.Logf("writing step %q", step.name)
|
||||
dw.expect, dw.err = step.pt, step.err
|
||||
|
||||
var (
|
||||
gotN int
|
||||
gotErr error
|
||||
)
|
||||
|
||||
wantN := tc.n
|
||||
wantN := step.n
|
||||
switch wantN {
|
||||
case nSpecialPtEquiv:
|
||||
wantN = len(tc.pt)
|
||||
gotN, gotErr = w.Write(tc.w)
|
||||
wantN = len(step.pt)
|
||||
gotN, gotErr = w.Write(step.w)
|
||||
|
||||
case nSpecialWEquiv:
|
||||
wantN = len(tc.w)
|
||||
gotN, gotErr = w.Write(tc.w)
|
||||
wantN = len(step.w)
|
||||
gotN, gotErr = w.Write(step.w)
|
||||
|
||||
case nSpecialSuspend:
|
||||
s := w.IsSuspended()
|
||||
@ -101,8 +101,8 @@ func TestSuspendable(t *testing.T) {
|
||||
t.Fatal("Suspend: unexpected success")
|
||||
}
|
||||
|
||||
wantN = len(tc.w)
|
||||
gotN, gotErr = w.Write(tc.w)
|
||||
wantN = len(step.w)
|
||||
gotN, gotErr = w.Write(step.w)
|
||||
|
||||
default:
|
||||
if wantN <= nSpecialDump {
|
||||
@ -118,10 +118,10 @@ func TestSuspendable(t *testing.T) {
|
||||
t.Errorf("Resume: dropped = %d, want %d", dropped, wantDropped)
|
||||
}
|
||||
|
||||
wantN = len(tc.pt)
|
||||
wantN = len(step.pt)
|
||||
gotN, gotErr = int(n), err
|
||||
} else {
|
||||
gotN, gotErr = w.Write(tc.w)
|
||||
gotN, gotErr = w.Write(step.w)
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,9 +129,13 @@ func TestSuspendable(t *testing.T) {
|
||||
t.Errorf("Write: n = %d, want %d", gotN, wantN)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(gotErr, tc.wantErr) {
|
||||
if !reflect.DeepEqual(gotErr, step.wantErr) {
|
||||
t.Errorf("Write: %v", gotErr)
|
||||
}
|
||||
|
||||
if dw.expect != nil {
|
||||
t.Errorf("expect: %q", string(dw.expect))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,17 +143,31 @@ func TestSuspendable(t *testing.T) {
|
||||
type expectWriter struct {
|
||||
expect []byte
|
||||
err error
|
||||
|
||||
// optional consecutive write
|
||||
next []byte
|
||||
|
||||
// optional, calls Error on failure if not nil
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func (w *expectWriter) Write(p []byte) (n int, err error) {
|
||||
defer func() { w.expect = nil }()
|
||||
defer func() { w.expect = w.next; w.next = nil }()
|
||||
|
||||
n, err = len(p), w.err
|
||||
if w.expect == nil {
|
||||
return 0, errors.New("unexpected call to Write: " + strconv.Quote(string(p)))
|
||||
n, err = 0, errors.New("unexpected call to Write: "+strconv.Quote(string(p)))
|
||||
if w.t != nil {
|
||||
w.t.Error(err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
if string(p) != string(w.expect) {
|
||||
return 0, errors.New("p = " + strconv.Quote(string(p)) + ", want " + strconv.Quote(string(w.expect)))
|
||||
n, err = 0, errors.New("p = "+strconv.Quote(string(p))+", want "+strconv.Quote(string(w.expect)))
|
||||
if w.t != nil {
|
||||
w.t.Error(err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package container
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
@ -22,26 +21,28 @@ const (
|
||||
kernelCapLastCapPath = FHSProcSys + "kernel/cap_last_cap"
|
||||
)
|
||||
|
||||
func mustReadSysctl() {
|
||||
if v, err := os.ReadFile(kernelOverflowuidPath); err != nil {
|
||||
log.Fatalf("cannot read %q: %v", kernelOverflowuidPath, err)
|
||||
} else if kernelOverflowuid, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil {
|
||||
log.Fatalf("cannot interpret %q: %v", kernelOverflowuidPath, err)
|
||||
}
|
||||
func mustReadSysctl(msg Msg) {
|
||||
sysctlOnce.Do(func() {
|
||||
if v, err := os.ReadFile(kernelOverflowuidPath); err != nil {
|
||||
msg.GetLogger().Fatalf("cannot read %q: %v", kernelOverflowuidPath, err)
|
||||
} else if kernelOverflowuid, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil {
|
||||
msg.GetLogger().Fatalf("cannot interpret %q: %v", kernelOverflowuidPath, err)
|
||||
}
|
||||
|
||||
if v, err := os.ReadFile(kernelOverflowgidPath); err != nil {
|
||||
log.Fatalf("cannot read %q: %v", kernelOverflowgidPath, err)
|
||||
} else if kernelOverflowgid, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil {
|
||||
log.Fatalf("cannot interpret %q: %v", kernelOverflowgidPath, err)
|
||||
}
|
||||
if v, err := os.ReadFile(kernelOverflowgidPath); err != nil {
|
||||
msg.GetLogger().Fatalf("cannot read %q: %v", kernelOverflowgidPath, err)
|
||||
} else if kernelOverflowgid, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil {
|
||||
msg.GetLogger().Fatalf("cannot interpret %q: %v", kernelOverflowgidPath, err)
|
||||
}
|
||||
|
||||
if v, err := os.ReadFile(kernelCapLastCapPath); err != nil {
|
||||
log.Fatalf("cannot read %q: %v", kernelCapLastCapPath, err)
|
||||
} else if kernelCapLastCap, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil {
|
||||
log.Fatalf("cannot interpret %q: %v", kernelCapLastCapPath, err)
|
||||
}
|
||||
if v, err := os.ReadFile(kernelCapLastCapPath); err != nil {
|
||||
msg.GetLogger().Fatalf("cannot read %q: %v", kernelCapLastCapPath, err)
|
||||
} else if kernelCapLastCap, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil {
|
||||
msg.GetLogger().Fatalf("cannot interpret %q: %v", kernelCapLastCapPath, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func OverflowUid() int { sysctlOnce.Do(mustReadSysctl); return kernelOverflowuid }
|
||||
func OverflowGid() int { sysctlOnce.Do(mustReadSysctl); return kernelOverflowgid }
|
||||
func LastCap() uintptr { sysctlOnce.Do(mustReadSysctl); return uintptr(kernelCapLastCap) }
|
||||
func OverflowUid(msg Msg) int { mustReadSysctl(msg); return kernelOverflowuid }
|
||||
func OverflowGid(msg Msg) int { mustReadSysctl(msg); return kernelOverflowgid }
|
||||
func LastCap(msg Msg) uintptr { mustReadSysctl(msg); return uintptr(kernelCapLastCap) }
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
// New initialises a Helper instance with wt as the null-terminated argument writer.
|
||||
func New(
|
||||
ctx context.Context,
|
||||
msg container.Msg,
|
||||
pathname *container.Absolute, name string,
|
||||
wt io.WriterTo,
|
||||
stat bool,
|
||||
@ -26,7 +27,7 @@ func New(
|
||||
var args []string
|
||||
h := new(helperContainer)
|
||||
h.helperFiles, args = newHelperFiles(ctx, wt, stat, argF, extraFiles)
|
||||
h.Container = container.NewCommand(ctx, pathname, name, args...)
|
||||
h.Container = container.NewCommand(ctx, msg, pathname, name, args...)
|
||||
h.WaitDelay = WaitDelay
|
||||
if cmdF != nil {
|
||||
cmdF(h.Container)
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
|
||||
func TestContainer(t *testing.T) {
|
||||
t.Run("start invalid container", func(t *testing.T) {
|
||||
h := helper.New(t.Context(), container.MustAbs(container.Nonexistent), "hakurei", argsWt, false, argF, nil, nil)
|
||||
h := helper.New(t.Context(), nil, container.MustAbs(container.Nonexistent), "hakurei", argsWt, false, argF, nil, nil)
|
||||
|
||||
wantErr := "container: starting an invalid container"
|
||||
if err := h.Start(); err == nil || err.Error() != wantErr {
|
||||
@ -22,7 +22,7 @@ func TestContainer(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("valid new helper nil check", func(t *testing.T) {
|
||||
if got := helper.New(t.Context(), container.MustAbs(container.Nonexistent), "hakurei", argsWt, false, argF, nil, nil); got == nil {
|
||||
if got := helper.New(t.Context(), nil, container.MustAbs(container.Nonexistent), "hakurei", argsWt, false, argF, nil, nil); got == nil {
|
||||
t.Errorf("New(%q, %q) got nil",
|
||||
argsWt, "hakurei")
|
||||
return
|
||||
@ -31,7 +31,7 @@ func TestContainer(t *testing.T) {
|
||||
|
||||
t.Run("implementation compliance", func(t *testing.T) {
|
||||
testHelper(t, func(ctx context.Context, setOutput func(stdoutP, stderrP *io.Writer), stat bool) helper.Helper {
|
||||
return helper.New(ctx, container.MustAbs(os.Args[0]), "helper", argsWt, stat, argF, func(z *container.Container) {
|
||||
return helper.New(ctx, nil, container.MustAbs(os.Args[0]), "helper", argsWt, stat, argF, func(z *container.Container) {
|
||||
setOutput(&z.Stdout, &z.Stderr)
|
||||
z.
|
||||
Bind(container.AbsFHSRoot, container.AbsFHSRoot, 0).
|
||||
|
@ -6,11 +6,18 @@ import (
|
||||
"os/exec"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var FulfillmentTimeout = 2 * time.Second
|
||||
|
||||
func init() {
|
||||
if testing.Testing() {
|
||||
FulfillmentTimeout *= 10
|
||||
}
|
||||
}
|
||||
|
||||
// A File is an extra file with deferred initialisation.
|
||||
type File interface {
|
||||
// Init initialises File state. Init must not be called more than once.
|
||||
|
@ -6,12 +6,10 @@ import (
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/helper"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
container.TryArgv0(hlog.Output{}, hlog.Prepare, internal.InstallOutput)
|
||||
container.TryArgv0(nil)
|
||||
helper.InternalHelperStub()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"time"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
@ -66,10 +65,6 @@ type (
|
||||
// a negative value causes the container to be terminated immediately on cancellation
|
||||
WaitDelay time.Duration `json:"wait_delay,omitempty"`
|
||||
|
||||
// extra seccomp flags
|
||||
SeccompFlags seccomp.ExportFlag `json:"seccomp_flags"`
|
||||
// extra seccomp presets
|
||||
SeccompPresets seccomp.FilterPreset `json:"seccomp_presets"`
|
||||
// disable project-specific filter extensions
|
||||
SeccompCompat bool `json:"seccomp_compat,omitempty"`
|
||||
// allow ptrace and friends
|
||||
|
@ -2,15 +2,56 @@ package hst
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/system"
|
||||
)
|
||||
|
||||
// NewEnablements returns the address of [system.Enablement] as [Enablements].
|
||||
func NewEnablements(e system.Enablement) *Enablements { return (*Enablements)(&e) }
|
||||
// Enablement represents an optional host service to export to the target user.
|
||||
type Enablement byte
|
||||
|
||||
// enablementsJSON is the [json] representation of the [system.Enablement] bit field.
|
||||
const (
|
||||
EWayland Enablement = 1 << iota
|
||||
EX11
|
||||
EDBus
|
||||
EPulse
|
||||
|
||||
EM
|
||||
)
|
||||
|
||||
func (e Enablement) String() string {
|
||||
switch e {
|
||||
case 0:
|
||||
return "(no enablements)"
|
||||
case EWayland:
|
||||
return "wayland"
|
||||
case EX11:
|
||||
return "x11"
|
||||
case EDBus:
|
||||
return "dbus"
|
||||
case EPulse:
|
||||
return "pulseaudio"
|
||||
default:
|
||||
buf := new(strings.Builder)
|
||||
buf.Grow(32)
|
||||
|
||||
for i := Enablement(1); i < EM; i <<= 1 {
|
||||
if e&i != 0 {
|
||||
buf.WriteString(", " + i.String())
|
||||
}
|
||||
}
|
||||
|
||||
if buf.Len() == 0 {
|
||||
return fmt.Sprintf("e%x", byte(e))
|
||||
}
|
||||
return strings.TrimPrefix(buf.String(), ", ")
|
||||
}
|
||||
}
|
||||
|
||||
// NewEnablements returns the address of [Enablement] as [Enablements].
|
||||
func NewEnablements(e Enablement) *Enablements { return (*Enablements)(&e) }
|
||||
|
||||
// enablementsJSON is the [json] representation of the [Enablement] bit field.
|
||||
type enablementsJSON struct {
|
||||
Wayland bool `json:"wayland,omitempty"`
|
||||
X11 bool `json:"x11,omitempty"`
|
||||
@ -18,15 +59,15 @@ type enablementsJSON struct {
|
||||
Pulse bool `json:"pulse,omitempty"`
|
||||
}
|
||||
|
||||
// Enablements is the [json] adapter for [system.Enablement].
|
||||
type Enablements system.Enablement
|
||||
// Enablements is the [json] adapter for [Enablement].
|
||||
type Enablements Enablement
|
||||
|
||||
// Unwrap returns the underlying [system.Enablement].
|
||||
func (e *Enablements) Unwrap() system.Enablement {
|
||||
// Unwrap returns the underlying [Enablement].
|
||||
func (e *Enablements) Unwrap() Enablement {
|
||||
if e == nil {
|
||||
return 0
|
||||
}
|
||||
return system.Enablement(*e)
|
||||
return Enablement(*e)
|
||||
}
|
||||
|
||||
func (e *Enablements) MarshalJSON() ([]byte, error) {
|
||||
@ -34,10 +75,10 @@ func (e *Enablements) MarshalJSON() ([]byte, error) {
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
return json.Marshal(&enablementsJSON{
|
||||
Wayland: system.Enablement(*e)&system.EWayland != 0,
|
||||
X11: system.Enablement(*e)&system.EX11 != 0,
|
||||
DBus: system.Enablement(*e)&system.EDBus != 0,
|
||||
Pulse: system.Enablement(*e)&system.EPulse != 0,
|
||||
Wayland: Enablement(*e)&EWayland != 0,
|
||||
X11: Enablement(*e)&EX11 != 0,
|
||||
DBus: Enablement(*e)&EDBus != 0,
|
||||
Pulse: Enablement(*e)&EPulse != 0,
|
||||
})
|
||||
}
|
||||
|
||||
@ -51,18 +92,18 @@ func (e *Enablements) UnmarshalJSON(data []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
var ve system.Enablement
|
||||
var ve Enablement
|
||||
if v.Wayland {
|
||||
ve |= system.EWayland
|
||||
ve |= EWayland
|
||||
}
|
||||
if v.X11 {
|
||||
ve |= system.EX11
|
||||
ve |= EX11
|
||||
}
|
||||
if v.DBus {
|
||||
ve |= system.EDBus
|
||||
ve |= EDBus
|
||||
}
|
||||
if v.Pulse {
|
||||
ve |= system.EPulse
|
||||
ve |= EPulse
|
||||
}
|
||||
*e = Enablements(ve)
|
||||
return nil
|
||||
|
@ -7,9 +7,44 @@ import (
|
||||
"testing"
|
||||
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/system"
|
||||
)
|
||||
|
||||
func TestEnablementString(t *testing.T) {
|
||||
testCases := []struct {
|
||||
flags hst.Enablement
|
||||
want string
|
||||
}{
|
||||
{0, "(no enablements)"},
|
||||
{hst.EWayland, "wayland"},
|
||||
{hst.EX11, "x11"},
|
||||
{hst.EDBus, "dbus"},
|
||||
{hst.EPulse, "pulseaudio"},
|
||||
{hst.EWayland | hst.EX11, "wayland, x11"},
|
||||
{hst.EWayland | hst.EDBus, "wayland, dbus"},
|
||||
{hst.EWayland | hst.EPulse, "wayland, pulseaudio"},
|
||||
{hst.EX11 | hst.EDBus, "x11, dbus"},
|
||||
{hst.EX11 | hst.EPulse, "x11, pulseaudio"},
|
||||
{hst.EDBus | hst.EPulse, "dbus, pulseaudio"},
|
||||
{hst.EWayland | hst.EX11 | hst.EDBus, "wayland, x11, dbus"},
|
||||
{hst.EWayland | hst.EX11 | hst.EPulse, "wayland, x11, pulseaudio"},
|
||||
{hst.EWayland | hst.EDBus | hst.EPulse, "wayland, dbus, pulseaudio"},
|
||||
{hst.EX11 | hst.EDBus | hst.EPulse, "x11, dbus, pulseaudio"},
|
||||
{hst.EWayland | hst.EX11 | hst.EDBus | hst.EPulse, "wayland, x11, dbus, pulseaudio"},
|
||||
|
||||
{1 << 5, "e20"},
|
||||
{1 << 6, "e40"},
|
||||
{1 << 7, "e80"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.want, func(t *testing.T) {
|
||||
if got := tc.flags.String(); got != tc.want {
|
||||
t.Errorf("String: %q, want %q", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnablements(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
@ -19,11 +54,11 @@ func TestEnablements(t *testing.T) {
|
||||
}{
|
||||
{"nil", nil, "null", `{"value":null,"magic":3236757504}`},
|
||||
{"zero", hst.NewEnablements(0), `{}`, `{"value":{},"magic":3236757504}`},
|
||||
{"wayland", hst.NewEnablements(system.EWayland), `{"wayland":true}`, `{"value":{"wayland":true},"magic":3236757504}`},
|
||||
{"x11", hst.NewEnablements(system.EX11), `{"x11":true}`, `{"value":{"x11":true},"magic":3236757504}`},
|
||||
{"dbus", hst.NewEnablements(system.EDBus), `{"dbus":true}`, `{"value":{"dbus":true},"magic":3236757504}`},
|
||||
{"pulse", hst.NewEnablements(system.EPulse), `{"pulse":true}`, `{"value":{"pulse":true},"magic":3236757504}`},
|
||||
{"all", hst.NewEnablements(system.EWayland | system.EX11 | system.EDBus | system.EPulse), `{"wayland":true,"x11":true,"dbus":true,"pulse":true}`, `{"value":{"wayland":true,"x11":true,"dbus":true,"pulse":true},"magic":3236757504}`},
|
||||
{"wayland", hst.NewEnablements(hst.EWayland), `{"wayland":true}`, `{"value":{"wayland":true},"magic":3236757504}`},
|
||||
{"x11", hst.NewEnablements(hst.EX11), `{"x11":true}`, `{"value":{"x11":true},"magic":3236757504}`},
|
||||
{"dbus", hst.NewEnablements(hst.EDBus), `{"dbus":true}`, `{"value":{"dbus":true},"magic":3236757504}`},
|
||||
{"pulse", hst.NewEnablements(hst.EPulse), `{"pulse":true}`, `{"value":{"pulse":true},"magic":3236757504}`},
|
||||
{"all", hst.NewEnablements(hst.EWayland | hst.EX11 | hst.EDBus | hst.EPulse), `{"wayland":true,"x11":true,"dbus":true,"pulse":true}`, `{"value":{"wayland":true,"x11":true,"dbus":true,"pulse":true},"magic":3236757504}`},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@ -88,7 +123,7 @@ func TestEnablements(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("val", func(t *testing.T) {
|
||||
if got := hst.NewEnablements(system.EWayland | system.EPulse).Unwrap(); got != system.EWayland|system.EPulse {
|
||||
if got := hst.NewEnablements(hst.EWayland | hst.EPulse).Unwrap(); got != hst.EWayland|hst.EPulse {
|
||||
t.Errorf("Unwrap: %v", got)
|
||||
}
|
||||
})
|
||||
|
28
hst/hst.go
28
hst/hst.go
@ -7,8 +7,6 @@ import (
|
||||
"os"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/system"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
@ -71,7 +69,7 @@ func Template() *Config {
|
||||
"--ozone-platform=wayland",
|
||||
},
|
||||
|
||||
Enablements: NewEnablements(system.EWayland | system.EDBus | system.EPulse),
|
||||
Enablements: NewEnablements(EWayland | EDBus | EPulse),
|
||||
|
||||
SessionBus: &dbus.Config{
|
||||
See: nil,
|
||||
@ -107,19 +105,17 @@ func Template() *Config {
|
||||
Groups: []string{"video", "dialout", "plugdev"},
|
||||
|
||||
Container: &ContainerConfig{
|
||||
Hostname: "localhost",
|
||||
Devel: true,
|
||||
Userns: true,
|
||||
HostNet: true,
|
||||
HostAbstract: true,
|
||||
Device: true,
|
||||
WaitDelay: -1,
|
||||
SeccompFlags: seccomp.AllowMultiarch,
|
||||
SeccompPresets: seccomp.PresetExt,
|
||||
SeccompCompat: true,
|
||||
Tty: true,
|
||||
Multiarch: true,
|
||||
MapRealUID: true,
|
||||
Hostname: "localhost",
|
||||
Devel: true,
|
||||
Userns: true,
|
||||
HostNet: true,
|
||||
HostAbstract: true,
|
||||
Device: true,
|
||||
WaitDelay: -1,
|
||||
SeccompCompat: true,
|
||||
Tty: true,
|
||||
Multiarch: true,
|
||||
MapRealUID: true,
|
||||
// example API credentials pulled from Google Chrome
|
||||
// DO NOT USE THESE IN A REAL BROWSER
|
||||
Env: map[string]string{
|
||||
|
@ -166,8 +166,6 @@ func TestTemplate(t *testing.T) {
|
||||
"container": {
|
||||
"hostname": "localhost",
|
||||
"wait_delay": -1,
|
||||
"seccomp_flags": 1,
|
||||
"seccomp_presets": 1,
|
||||
"seccomp_compat": true,
|
||||
"devel": true,
|
||||
"userns": true,
|
||||
|
@ -6,23 +6,24 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app/state"
|
||||
)
|
||||
|
||||
// Main runs an app according to [hst.Config] and terminates. Main does not return.
|
||||
func Main(ctx context.Context, config *hst.Config) {
|
||||
func Main(ctx context.Context, msg container.Msg, config *hst.Config) {
|
||||
var id state.ID
|
||||
if err := state.NewAppID(&id); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
seal := outcome{id: &stringPair[state.ID]{id, id.String()}, syscallDispatcher: direct{}}
|
||||
if err := seal.finalise(ctx, config); err != nil {
|
||||
if err := seal.finalise(ctx, msg, config); err != nil {
|
||||
printMessageError("cannot seal app:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
seal.main()
|
||||
seal.main(msg)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ import (
|
||||
"log"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
|
||||
"hakurei.app/container"
|
||||
)
|
||||
|
||||
type stubNixOS struct {
|
||||
@ -187,10 +189,10 @@ func (k *stubNixOS) cmdOutput(cmd *exec.Cmd) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (k *stubNixOS) overflowUid() int { return 65534 }
|
||||
func (k *stubNixOS) overflowGid() int { return 65534 }
|
||||
func (k *stubNixOS) overflowUid(container.Msg) int { return 65534 }
|
||||
func (k *stubNixOS) overflowGid(container.Msg) int { return 65534 }
|
||||
|
||||
func (k *stubNixOS) mustHsuPath() string { return "/proc/nonexistent/hsu" }
|
||||
func (k *stubNixOS) mustHsuPath() *container.Absolute { return m("/proc/nonexistent/hsu") }
|
||||
|
||||
func (k *stubNixOS) fatalf(format string, v ...any) { panic(fmt.Sprintf(format, v...)) }
|
||||
|
||||
|
@ -37,7 +37,7 @@ func TestApp(t *testing.T) {
|
||||
0xbd, 0x01, 0x78, 0x0e,
|
||||
0xb9, 0xa6, 0x07, 0xac,
|
||||
},
|
||||
system.New(context.TODO(), 1000000).
|
||||
system.New(context.TODO(), container.NewMsg(nil), 1000000).
|
||||
Ensure("/tmp/hakurei.0", 0711).
|
||||
Ensure("/tmp/hakurei.0/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime", acl.Execute).
|
||||
Ensure("/tmp/hakurei.0/runtime/0", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime/0", acl.Read, acl.Write, acl.Execute).
|
||||
@ -121,7 +121,7 @@ func TestApp(t *testing.T) {
|
||||
},
|
||||
Filter: true,
|
||||
},
|
||||
Enablements: hst.NewEnablements(system.EWayland | system.EDBus | system.EPulse),
|
||||
Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPulse),
|
||||
},
|
||||
state.ID{
|
||||
0xeb, 0xf0, 0x83, 0xd1,
|
||||
@ -129,7 +129,7 @@ func TestApp(t *testing.T) {
|
||||
0x82, 0xd4, 0x13, 0x36,
|
||||
0x9b, 0x64, 0xce, 0x7c,
|
||||
},
|
||||
system.New(context.TODO(), 1000009).
|
||||
system.New(context.TODO(), container.NewMsg(nil), 1000009).
|
||||
Ensure("/tmp/hakurei.0", 0711).
|
||||
Ensure("/tmp/hakurei.0/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime", acl.Execute).
|
||||
Ensure("/tmp/hakurei.0/runtime/9", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime/9", acl.Read, acl.Write, acl.Execute).
|
||||
@ -229,7 +229,7 @@ func TestApp(t *testing.T) {
|
||||
&hst.Config{
|
||||
ID: "org.chromium.Chromium",
|
||||
Path: m("/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"),
|
||||
Enablements: hst.NewEnablements(system.EWayland | system.EDBus | system.EPulse),
|
||||
Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPulse),
|
||||
Shell: m("/run/current-system/sw/bin/zsh"),
|
||||
|
||||
Container: &hst.ContainerConfig{
|
||||
@ -280,7 +280,7 @@ func TestApp(t *testing.T) {
|
||||
0x4c, 0xf0, 0x73, 0xbd,
|
||||
0xb4, 0x6e, 0xb5, 0xc1,
|
||||
},
|
||||
system.New(context.TODO(), 1000001).
|
||||
system.New(context.TODO(), container.NewMsg(nil), 1000001).
|
||||
Ensure("/tmp/hakurei.0", 0711).
|
||||
Ensure("/tmp/hakurei.0/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime", acl.Execute).
|
||||
Ensure("/tmp/hakurei.0/runtime/1", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime/1", acl.Read, acl.Write, acl.Execute).
|
||||
@ -288,7 +288,7 @@ func TestApp(t *testing.T) {
|
||||
Ensure("/tmp/hakurei.0/tmpdir/1", 01700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir/1", acl.Read, acl.Write, acl.Execute).
|
||||
Ensure("/run/user/1971/hakurei", 0700).UpdatePermType(system.User, "/run/user/1971/hakurei", acl.Execute).
|
||||
Ensure("/run/user/1971", 0700).UpdatePermType(system.User, "/run/user/1971", acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
|
||||
UpdatePermType(system.EWayland, "/run/user/1971/wayland-0", acl.Read, acl.Write, acl.Execute).
|
||||
UpdatePermType(hst.EWayland, "/run/user/1971/wayland-0", acl.Read, acl.Write, acl.Execute).
|
||||
Ephemeral(system.Process, "/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1", 0700).UpdatePermType(system.Process, "/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1", acl.Execute).
|
||||
Link("/run/user/1971/pulse/native", "/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1/pulse").
|
||||
CopyFile(nil, "/home/ophestra/xdg/config/pulse/cookie", 256, 256).
|
||||
@ -377,7 +377,7 @@ func TestApp(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Run("finalise", func(t *testing.T) {
|
||||
seal := outcome{syscallDispatcher: tc.k, id: &stringPair[state.ID]{tc.id, tc.id.String()}}
|
||||
err := seal.finalise(t.Context(), tc.config)
|
||||
err := seal.finalise(t.Context(), container.NewMsg(nil), tc.config)
|
||||
if err != nil {
|
||||
if s, ok := container.GetErrorMessage(err); !ok {
|
||||
t.Fatalf("Seal: error = %v", err)
|
||||
|
@ -20,6 +20,7 @@ const preallocateOpsCount = 1 << 5
|
||||
// newContainer initialises [container.Params] via [hst.ContainerConfig].
|
||||
// Note that remaining container setup must be queued by the caller.
|
||||
func newContainer(
|
||||
msg container.Msg,
|
||||
k syscallDispatcher,
|
||||
s *hst.ContainerConfig,
|
||||
prefix string,
|
||||
@ -31,12 +32,10 @@ func newContainer(
|
||||
}
|
||||
|
||||
params := &container.Params{
|
||||
Hostname: s.Hostname,
|
||||
SeccompFlags: s.SeccompFlags,
|
||||
SeccompPresets: s.SeccompPresets,
|
||||
RetainSession: s.Tty,
|
||||
HostNet: s.HostNet,
|
||||
HostAbstract: s.HostAbstract,
|
||||
Hostname: s.Hostname,
|
||||
RetainSession: s.Tty,
|
||||
HostNet: s.HostNet,
|
||||
HostAbstract: s.HostAbstract,
|
||||
|
||||
// the container is canceled when shim is requested to exit or receives an interrupt or termination signal;
|
||||
// this behaviour is implemented in the shim
|
||||
@ -73,8 +72,8 @@ func newContainer(
|
||||
params.Gid = k.getgid()
|
||||
*gid = params.Gid
|
||||
} else {
|
||||
*uid = k.overflowUid()
|
||||
*gid = k.overflowGid()
|
||||
*uid = k.overflowUid(msg)
|
||||
*gid = k.overflowGid(msg)
|
||||
}
|
||||
|
||||
filesystem := s.Filesystem
|
||||
@ -126,11 +125,11 @@ func newContainer(
|
||||
// get parent dir of socket
|
||||
dir := path.Dir(pair[1])
|
||||
if dir == "." || dir == container.FHSRoot {
|
||||
k.verbosef("dbus socket %q is in an unusual location", pair[1])
|
||||
msg.Verbosef("dbus socket %q is in an unusual location", pair[1])
|
||||
}
|
||||
hidePaths = append(hidePaths, dir)
|
||||
} else {
|
||||
k.verbosef("dbus socket %q is not absolute", pair[1])
|
||||
msg.Verbosef("dbus socket %q is not absolute", pair[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -138,7 +137,7 @@ func newContainer(
|
||||
}
|
||||
hidePathMatch := make([]bool, len(hidePaths))
|
||||
for i := range hidePaths {
|
||||
if err := evalSymlinks(k, &hidePaths[i]); err != nil {
|
||||
if err := evalSymlinks(msg, k, &hidePaths[i]); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
@ -178,7 +177,7 @@ func newContainer(
|
||||
if autoroot != nil {
|
||||
for _, ent := range autoRootEntries {
|
||||
name := ent.Name()
|
||||
if container.IsAutoRootBindable(name) {
|
||||
if container.IsAutoRootBindable(msg, name) {
|
||||
hidePathSource = append(hidePathSource, autoroot.Source.Append(name))
|
||||
}
|
||||
}
|
||||
@ -193,7 +192,7 @@ func newContainer(
|
||||
}
|
||||
|
||||
hidePathSourceEval[i] = [2]string{a.String(), a.String()}
|
||||
if err := evalSymlinks(k, &hidePathSourceEval[i][0]); err != nil {
|
||||
if err := evalSymlinks(msg, k, &hidePathSourceEval[i][0]); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
@ -209,7 +208,7 @@ func newContainer(
|
||||
return nil, nil, err
|
||||
} else if ok {
|
||||
hidePathMatch[i] = true
|
||||
k.verbosef("hiding path %q from %q", hidePaths[i], p[1])
|
||||
msg.Verbosef("hiding path %q from %q", hidePaths[i], p[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -241,12 +240,12 @@ func newContainer(
|
||||
}
|
||||
|
||||
// evalSymlinks calls syscallDispatcher.evalSymlinks but discards errors unwrapping to [fs.ErrNotExist].
|
||||
func evalSymlinks(k syscallDispatcher, v *string) error {
|
||||
func evalSymlinks(msg container.Msg, k syscallDispatcher, v *string) error {
|
||||
if p, err := k.evalSymlinks(*v); err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
k.verbosef("path %q does not yet exist", *v)
|
||||
msg.Verbosef("path %q does not yet exist", *v)
|
||||
} else {
|
||||
*v = p
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
|
||||
// syscallDispatcher provides methods that make state-dependent system calls as part of their behaviour.
|
||||
@ -45,19 +44,15 @@ type syscallDispatcher interface {
|
||||
cmdOutput(cmd *exec.Cmd) ([]byte, error)
|
||||
|
||||
// overflowUid provides [container.OverflowUid].
|
||||
overflowUid() int
|
||||
overflowUid(msg container.Msg) int
|
||||
// overflowGid provides [container.OverflowGid].
|
||||
overflowGid() int
|
||||
overflowGid(msg container.Msg) int
|
||||
|
||||
// mustHsuPath provides [internal.MustHsuPath].
|
||||
mustHsuPath() string
|
||||
mustHsuPath() *container.Absolute
|
||||
|
||||
// fatalf provides [log.Fatalf].
|
||||
fatalf(format string, v ...any)
|
||||
|
||||
isVerbose() bool
|
||||
verbose(v ...any)
|
||||
verbosef(format string, v ...any)
|
||||
}
|
||||
|
||||
// direct implements syscallDispatcher on the current kernel.
|
||||
@ -87,13 +82,9 @@ func (direct) lookupGroupId(name string) (gid string, err error) {
|
||||
|
||||
func (direct) cmdOutput(cmd *exec.Cmd) ([]byte, error) { return cmd.Output() }
|
||||
|
||||
func (direct) overflowUid() int { return container.OverflowUid() }
|
||||
func (direct) overflowGid() int { return container.OverflowGid() }
|
||||
func (direct) overflowUid(msg container.Msg) int { return container.OverflowUid(msg) }
|
||||
func (direct) overflowGid(msg container.Msg) int { return container.OverflowGid(msg) }
|
||||
|
||||
func (direct) mustHsuPath() string { return internal.MustHsuPath() }
|
||||
func (direct) mustHsuPath() *container.Absolute { return internal.MustHsuPath() }
|
||||
|
||||
func (direct) fatalf(format string, v ...any) { log.Fatalf(format, v...) }
|
||||
|
||||
func (k direct) isVerbose() bool { return hlog.Load() }
|
||||
func (direct) verbose(v ...any) { hlog.Verbose(v...) }
|
||||
func (direct) verbosef(format string, v ...any) { hlog.Verbosef(format, v...) }
|
||||
|
26
internal/app/dispatcher_test.go
Normal file
26
internal/app/dispatcher_test.go
Normal file
@ -0,0 +1,26 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"hakurei.app/container"
|
||||
)
|
||||
|
||||
type panicDispatcher struct{}
|
||||
|
||||
func (panicDispatcher) new(func(k syscallDispatcher)) { panic("unreachable") }
|
||||
func (panicDispatcher) getuid() int { panic("unreachable") }
|
||||
func (panicDispatcher) getgid() int { panic("unreachable") }
|
||||
func (panicDispatcher) lookupEnv(string) (string, bool) { panic("unreachable") }
|
||||
func (panicDispatcher) stat(string) (os.FileInfo, error) { panic("unreachable") }
|
||||
func (panicDispatcher) readdir(string) ([]os.DirEntry, error) { panic("unreachable") }
|
||||
func (panicDispatcher) tempdir() string { panic("unreachable") }
|
||||
func (panicDispatcher) evalSymlinks(string) (string, error) { panic("unreachable") }
|
||||
func (panicDispatcher) lookPath(string) (string, error) { panic("unreachable") }
|
||||
func (panicDispatcher) lookupGroupId(string) (string, error) { panic("unreachable") }
|
||||
func (panicDispatcher) cmdOutput(*exec.Cmd) ([]byte, error) { panic("unreachable") }
|
||||
func (panicDispatcher) overflowUid(container.Msg) int { panic("unreachable") }
|
||||
func (panicDispatcher) overflowGid(container.Msg) int { panic("unreachable") }
|
||||
func (panicDispatcher) mustHsuPath() *container.Absolute { panic("unreachable") }
|
||||
func (panicDispatcher) fatalf(string, ...any) { panic("unreachable") }
|
@ -20,7 +20,6 @@ import (
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/internal/hlog"
|
||||
"hakurei.app/system"
|
||||
"hakurei.app/system/acl"
|
||||
"hakurei.app/system/dbus"
|
||||
@ -120,7 +119,7 @@ type hsuUser struct {
|
||||
username string
|
||||
}
|
||||
|
||||
func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
|
||||
func (k *outcome) finalise(ctx context.Context, msg container.Msg, config *hst.Config) error {
|
||||
const (
|
||||
home = "HOME"
|
||||
shell = "SHELL"
|
||||
@ -183,7 +182,7 @@ func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
|
||||
} else if !isValidUsername(k.user.username) {
|
||||
return newWithMessage(fmt.Sprintf("invalid user name %q", k.user.username))
|
||||
}
|
||||
k.user.uid = newInt(HsuUid(hsu.MustID(), k.user.identity.unwrap()))
|
||||
k.user.uid = newInt(HsuUid(hsu.MustIDMsg(msg), k.user.identity.unwrap()))
|
||||
|
||||
k.user.supp = make([]string, len(config.Groups))
|
||||
for i, name := range config.Groups {
|
||||
@ -201,7 +200,7 @@ func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
|
||||
|
||||
// permissive defaults
|
||||
if config.Container == nil {
|
||||
hlog.Verbose("container configuration not supplied, PROCEED WITH CAUTION")
|
||||
msg.Verbose("container configuration not supplied, PROCEED WITH CAUTION")
|
||||
|
||||
if config.Shell == nil {
|
||||
config.Shell = container.AbsFHSRoot.Append("bin", "sh")
|
||||
@ -242,7 +241,7 @@ func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
|
||||
}
|
||||
|
||||
// bind GPU stuff
|
||||
if config.Enablements.Unwrap()&(system.EX11|system.EWayland) != 0 {
|
||||
if config.Enablements.Unwrap()&(hst.EX11|hst.EWayland) != 0 {
|
||||
conf.Filesystem = append(conf.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("dri"), Device: true, Optional: true}})
|
||||
}
|
||||
// opportunistically bind kvm
|
||||
@ -276,13 +275,14 @@ func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
|
||||
|
||||
// TODO(ophestra): revert this after params to shim
|
||||
share := &shareHost{seal: k}
|
||||
copyPaths(k.syscallDispatcher, &share.sc, hsu.MustID())
|
||||
copyPaths(k.syscallDispatcher).Copy(&share.sc, hsu.MustIDMsg(msg))
|
||||
msg.Verbosef("process share directory at %q, runtime directory at %q", share.sc.SharePath, share.sc.RunDirPath)
|
||||
|
||||
var mapuid, mapgid *stringPair[int]
|
||||
{
|
||||
var uid, gid int
|
||||
var err error
|
||||
k.container, k.env, err = newContainer(k, config.Container, k.id.String(), &share.sc, &uid, &gid)
|
||||
k.container, k.env, err = newContainer(msg, k, config.Container, k.id.String(), &share.sc, &uid, &gid)
|
||||
k.waitDelay = config.Container.WaitDelay
|
||||
if err != nil {
|
||||
return &hst.AppError{Step: "initialise container configuration", Err: err}
|
||||
@ -307,7 +307,7 @@ func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
|
||||
k.env[xdgSessionType] = "tty"
|
||||
|
||||
k.runDirPath = share.sc.RunDirPath
|
||||
k.sys = system.New(k.ctx, k.user.uid.unwrap())
|
||||
k.sys = system.New(k.ctx, msg, k.user.uid.unwrap())
|
||||
k.sys.Ensure(share.sc.SharePath.String(), 0711)
|
||||
|
||||
{
|
||||
@ -353,11 +353,11 @@ func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
|
||||
k.env[term] = t
|
||||
}
|
||||
|
||||
if config.Enablements.Unwrap()&system.EWayland != 0 {
|
||||
if config.Enablements.Unwrap()&hst.EWayland != 0 {
|
||||
// outer wayland socket (usually `/run/user/%d/wayland-%d`)
|
||||
var socketPath *container.Absolute
|
||||
if name, ok := k.lookupEnv(wayland.WaylandDisplay); !ok {
|
||||
hlog.Verbose(wayland.WaylandDisplay + " is not set, assuming " + wayland.FallbackName)
|
||||
msg.Verbose(wayland.WaylandDisplay + " is not set, assuming " + wayland.FallbackName)
|
||||
socketPath = share.sc.RuntimePath.Append(wayland.FallbackName)
|
||||
} else if a, err := container.NewAbs(name); err != nil {
|
||||
socketPath = share.sc.RuntimePath.Append(name)
|
||||
@ -379,14 +379,14 @@ func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
|
||||
k.sys.Wayland(&k.sync, outerPath.String(), socketPath.String(), appID, k.id.String())
|
||||
k.container.Bind(outerPath, innerPath, 0)
|
||||
} else { // bind mount wayland socket (insecure)
|
||||
hlog.Verbose("direct wayland access, PROCEED WITH CAUTION")
|
||||
msg.Verbose("direct wayland access, PROCEED WITH CAUTION")
|
||||
share.ensureRuntimeDir()
|
||||
k.container.Bind(socketPath, innerPath, 0)
|
||||
k.sys.UpdatePermType(system.EWayland, socketPath.String(), acl.Read, acl.Write, acl.Execute)
|
||||
k.sys.UpdatePermType(hst.EWayland, socketPath.String(), acl.Read, acl.Write, acl.Execute)
|
||||
}
|
||||
}
|
||||
|
||||
if config.Enablements.Unwrap()&system.EX11 != 0 {
|
||||
if config.Enablements.Unwrap()&hst.EX11 != 0 {
|
||||
if d, ok := k.lookupEnv(display); !ok {
|
||||
return newWithMessage("DISPLAY is not set")
|
||||
} else {
|
||||
@ -410,7 +410,7 @@ func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
|
||||
return &hst.AppError{Step: fmt.Sprintf("access X11 socket %q", socketPath), Err: err}
|
||||
}
|
||||
} else {
|
||||
k.sys.UpdatePermType(system.EX11, socketPath.String(), acl.Read, acl.Write, acl.Execute)
|
||||
k.sys.UpdatePermType(hst.EX11, socketPath.String(), acl.Read, acl.Write, acl.Execute)
|
||||
if !config.Container.HostAbstract {
|
||||
d = "unix:" + socketPath.String()
|
||||
}
|
||||
@ -423,7 +423,7 @@ func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
|
||||
}
|
||||
}
|
||||
|
||||
if config.Enablements.Unwrap()&system.EPulse != 0 {
|
||||
if config.Enablements.Unwrap()&hst.EPulse != 0 {
|
||||
// PulseAudio runtime directory (usually `/run/user/%d/pulse`)
|
||||
pulseRuntimeDir := share.sc.RuntimePath.Append("pulse")
|
||||
// PulseAudio socket (usually `/run/user/%d/pulse/native`)
|
||||
@ -520,14 +520,14 @@ func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
|
||||
k.container.PlaceP(innerDst, &payload)
|
||||
k.sys.CopyFile(payload, paCookiePath.String(), 256, 256)
|
||||
} else {
|
||||
hlog.Verbose("cannot locate PulseAudio cookie (tried " +
|
||||
msg.Verbose("cannot locate PulseAudio cookie (tried " +
|
||||
"$PULSE_COOKIE, " +
|
||||
"$XDG_CONFIG_HOME/pulse/cookie, " +
|
||||
"$HOME/.pulse-cookie)")
|
||||
}
|
||||
}
|
||||
|
||||
if config.Enablements.Unwrap()&system.EDBus != 0 {
|
||||
if config.Enablements.Unwrap()&hst.EDBus != 0 {
|
||||
// ensure dbus session bus defaults
|
||||
if config.SessionBus == nil {
|
||||
config.SessionBus = dbus.NewConfig(config.ID, true, true)
|
||||
@ -596,8 +596,8 @@ func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
|
||||
}
|
||||
slices.Sort(k.container.Env)
|
||||
|
||||
if hlog.Load() {
|
||||
hlog.Verbosef("created application seal for uid %s (%s) groups: %v, argv: %s, ops: %d",
|
||||
if msg.IsVerbose() {
|
||||
msg.Verbosef("created application seal for uid %s (%s) groups: %v, argv: %s, ops: %d",
|
||||
k.user.uid, k.user.username, config.Groups, k.container.Args, len(*k.container.Ops))
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
|
||||
// Hsu caches responses from cmd/hsu.
|
||||
@ -40,7 +39,7 @@ func (h *Hsu) ID() (int, error) {
|
||||
h.ensureDispatcher()
|
||||
h.idOnce.Do(func() {
|
||||
h.id = -1
|
||||
hsuPath := h.k.mustHsuPath()
|
||||
hsuPath := h.k.mustHsuPath().String()
|
||||
|
||||
cmd := exec.Command(hsuPath)
|
||||
cmd.Path = hsuPath
|
||||
@ -71,7 +70,10 @@ func (h *Hsu) ID() (int, error) {
|
||||
}
|
||||
|
||||
// MustID calls [Hsu.ID] and terminates on error.
|
||||
func (h *Hsu) MustID() int {
|
||||
func (h *Hsu) MustID() int { return h.MustIDMsg(nil) }
|
||||
|
||||
// MustIDMsg implements MustID with a custom [container.Msg].
|
||||
func (h *Hsu) MustIDMsg(msg container.Msg) int {
|
||||
id, err := h.ID()
|
||||
if err == nil {
|
||||
return id
|
||||
@ -79,7 +81,9 @@ func (h *Hsu) MustID() int {
|
||||
|
||||
const fallback = "cannot retrieve user id from setuid wrapper:"
|
||||
if errors.Is(err, ErrHsuAccess) {
|
||||
hlog.Verbose("*"+fallback, err)
|
||||
if msg != nil {
|
||||
msg.Verbose("*"+fallback, err)
|
||||
}
|
||||
os.Exit(1)
|
||||
return -0xdeadbeef
|
||||
} else if m, ok := container.GetErrorMessage(err); ok {
|
||||
|
@ -7,30 +7,53 @@ import (
|
||||
"hakurei.app/hst"
|
||||
)
|
||||
|
||||
// CopyPaths populates a [hst.Paths] struct.
|
||||
func CopyPaths(v *hst.Paths, userid int) { copyPaths(direct{}, v, userid) }
|
||||
// EnvPaths holds paths copied from the environment and is used to create [hst.Paths].
|
||||
type EnvPaths struct {
|
||||
// TempDir is returned by [os.TempDir].
|
||||
TempDir *container.Absolute
|
||||
// RuntimePath is copied from $XDG_RUNTIME_DIR.
|
||||
RuntimePath *container.Absolute
|
||||
}
|
||||
|
||||
// copyPaths populates a [hst.Paths] struct.
|
||||
func copyPaths(k syscallDispatcher, v *hst.Paths, userid int) {
|
||||
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
||||
|
||||
if tempDir, err := container.NewAbs(k.tempdir()); err != nil {
|
||||
k.fatalf("invalid TMPDIR: %v", err)
|
||||
} else {
|
||||
v.TempDir = tempDir
|
||||
// Copy expands [EnvPaths] into [hst.Paths].
|
||||
func (env *EnvPaths) Copy(v *hst.Paths, userid int) {
|
||||
if env == nil || env.TempDir == nil || v == nil {
|
||||
panic("attempting to use an invalid EnvPaths")
|
||||
}
|
||||
|
||||
v.SharePath = v.TempDir.Append("hakurei." + strconv.Itoa(userid))
|
||||
k.verbosef("process share directory at %q", v.SharePath)
|
||||
v.TempDir = env.TempDir
|
||||
v.SharePath = env.TempDir.Append("hakurei." + strconv.Itoa(userid))
|
||||
|
||||
r, _ := k.lookupEnv(xdgRuntimeDir)
|
||||
if a, err := container.NewAbs(r); err != nil {
|
||||
if env.RuntimePath == nil {
|
||||
// fall back to path in share since hakurei has no hard XDG dependency
|
||||
v.RunDirPath = v.SharePath.Append("run")
|
||||
v.RuntimePath = v.RunDirPath.Append("compat")
|
||||
} else {
|
||||
v.RuntimePath = a
|
||||
v.RunDirPath = v.RuntimePath.Append("hakurei")
|
||||
v.RuntimePath = env.RuntimePath
|
||||
v.RunDirPath = env.RuntimePath.Append("hakurei")
|
||||
}
|
||||
k.verbosef("runtime directory at %q", v.RunDirPath)
|
||||
}
|
||||
|
||||
// CopyPaths returns a populated [EnvPaths].
|
||||
func CopyPaths() *EnvPaths { return copyPaths(direct{}) }
|
||||
|
||||
// copyPaths returns a populated [EnvPaths].
|
||||
func copyPaths(k syscallDispatcher) *EnvPaths {
|
||||
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
||||
|
||||
var env EnvPaths
|
||||
|
||||
if tempDir, err := container.NewAbs(k.tempdir()); err != nil {
|
||||
k.fatalf("invalid TMPDIR: %v", err)
|
||||
panic("unreachable")
|
||||
} else {
|
||||
env.TempDir = tempDir
|
||||
}
|
||||
|
||||
r, _ := k.lookupEnv(xdgRuntimeDir)
|
||||
if a, err := container.NewAbs(r); err == nil {
|
||||
env.RuntimePath = a
|
||||
}
|
||||
|
||||
return &env
|
||||
}
|
||||
|
129
internal/app/paths_test.go
Normal file
129
internal/app/paths_test.go
Normal file
@ -0,0 +1,129 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/stub"
|
||||
"hakurei.app/hst"
|
||||
)
|
||||
|
||||
func TestEnvPaths(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
env *EnvPaths
|
||||
want hst.Paths
|
||||
|
||||
wantPanic string
|
||||
}{
|
||||
{"nil", nil, hst.Paths{}, "attempting to use an invalid EnvPaths"},
|
||||
{"zero", new(EnvPaths), hst.Paths{}, "attempting to use an invalid EnvPaths"},
|
||||
|
||||
{"nil tempdir", &EnvPaths{
|
||||
RuntimePath: container.AbsFHSTmp,
|
||||
}, hst.Paths{}, "attempting to use an invalid EnvPaths"},
|
||||
|
||||
{"nil runtime", &EnvPaths{
|
||||
TempDir: container.AbsFHSTmp,
|
||||
}, hst.Paths{
|
||||
TempDir: container.AbsFHSTmp,
|
||||
SharePath: container.AbsFHSTmp.Append("hakurei.3735928559"),
|
||||
RuntimePath: container.AbsFHSTmp.Append("hakurei.3735928559/run/compat"),
|
||||
RunDirPath: container.AbsFHSTmp.Append("hakurei.3735928559/run"),
|
||||
}, ""},
|
||||
|
||||
{"full", &EnvPaths{
|
||||
TempDir: container.AbsFHSTmp,
|
||||
RuntimePath: container.AbsFHSRunUser.Append("1000"),
|
||||
}, hst.Paths{
|
||||
TempDir: container.AbsFHSTmp,
|
||||
SharePath: container.AbsFHSTmp.Append("hakurei.3735928559"),
|
||||
RuntimePath: container.AbsFHSRunUser.Append("1000"),
|
||||
RunDirPath: container.AbsFHSRunUser.Append("1000/hakurei"),
|
||||
}, ""},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if tc.wantPanic != "" {
|
||||
defer func() {
|
||||
if r := recover(); r != tc.wantPanic {
|
||||
t.Errorf("Copy: panic = %#v, want %q", r, tc.wantPanic)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
var sc hst.Paths
|
||||
tc.env.Copy(&sc, 0xdeadbeef)
|
||||
if !reflect.DeepEqual(&sc, &tc.want) {
|
||||
t.Errorf("Copy: %#v, want %#v", sc, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyPaths(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
env map[string]string
|
||||
tmp string
|
||||
fatal string
|
||||
want EnvPaths
|
||||
}{
|
||||
{"invalid tempdir", nil, "\x00",
|
||||
"invalid TMPDIR: path \"\\x00\" is not absolute", EnvPaths{}},
|
||||
{"empty environment", make(map[string]string), container.Nonexistent,
|
||||
"", EnvPaths{TempDir: container.MustAbs(container.Nonexistent)}},
|
||||
{"invalid XDG_RUNTIME_DIR", map[string]string{"XDG_RUNTIME_DIR": "\x00"}, container.Nonexistent,
|
||||
"", EnvPaths{TempDir: container.MustAbs(container.Nonexistent)}},
|
||||
{"full", map[string]string{"XDG_RUNTIME_DIR": "/\x00"}, container.Nonexistent,
|
||||
"", EnvPaths{TempDir: container.MustAbs(container.Nonexistent), RuntimePath: container.MustAbs("/\x00")}},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if tc.fatal != "" {
|
||||
defer stub.HandleExit(t)
|
||||
}
|
||||
|
||||
k := copyPathsDispatcher{t: t, env: tc.env, tmp: tc.tmp, expectsFatal: tc.fatal}
|
||||
got := copyPaths(k)
|
||||
|
||||
if tc.fatal != "" {
|
||||
t.Fatalf("copyPaths: expected fatal %q", tc.fatal)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, &tc.want) {
|
||||
t.Errorf("copyPaths: %#v, want %#v", got, &tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// copyPathsDispatcher implements enough of syscallDispatcher for all copyPaths code paths.
|
||||
type copyPathsDispatcher struct {
|
||||
env map[string]string
|
||||
tmp string
|
||||
|
||||
// must be checked at the conclusion of the test
|
||||
expectsFatal string
|
||||
|
||||
t *testing.T
|
||||
panicDispatcher
|
||||
}
|
||||
|
||||
func (k copyPathsDispatcher) tempdir() string { return k.tmp }
|
||||
func (k copyPathsDispatcher) lookupEnv(key string) (value string, ok bool) {
|
||||
value, ok = k.env[key]
|
||||
return
|
||||
}
|
||||
func (k copyPathsDispatcher) fatalf(format string, v ...any) {
|
||||
if k.expectsFatal == "" {
|
||||
k.t.Fatalf("unexpected call to fatalf: format = %q, v = %#v", format, v)
|
||||
}
|
||||
|
||||
if got := fmt.Sprintf(format, v...); got != k.expectsFatal {
|
||||
k.t.Fatalf("fatalf: %q, want %q", got, k.expectsFatal)
|
||||
}
|
||||
panic(stub.PanicExit)
|
||||
}
|
@ -13,9 +13,9 @@ import (
|
||||
"time"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/internal/hlog"
|
||||
"hakurei.app/system"
|
||||
)
|
||||
|
||||
@ -39,6 +39,7 @@ type mainState struct {
|
||||
cmdWait chan error
|
||||
|
||||
k *outcome
|
||||
container.Msg
|
||||
uintptr
|
||||
}
|
||||
|
||||
@ -55,7 +56,7 @@ func (ms mainState) beforeExit(isFault bool) {
|
||||
panic("attempting to call beforeExit twice")
|
||||
}
|
||||
ms.done = true
|
||||
defer hlog.BeforeExit()
|
||||
defer ms.BeforeExit()
|
||||
|
||||
if isFault && ms.cancel != nil {
|
||||
ms.cancel()
|
||||
@ -97,37 +98,37 @@ func (ms mainState) beforeExit(isFault bool) {
|
||||
}
|
||||
}
|
||||
|
||||
if hlog.Load() {
|
||||
if ms.IsVerbose() {
|
||||
if !ok {
|
||||
if err != nil {
|
||||
hlog.Verbosef("wait: %v", err)
|
||||
ms.Verbosef("wait: %v", err)
|
||||
}
|
||||
} else {
|
||||
switch {
|
||||
case wstatus.Exited():
|
||||
hlog.Verbosef("process %d exited with code %d", ms.cmd.Process.Pid, wstatus.ExitStatus())
|
||||
ms.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)
|
||||
ms.Verbosef("process %d dumped core", ms.cmd.Process.Pid)
|
||||
|
||||
case wstatus.Signaled():
|
||||
hlog.Verbosef("process %d got %s", ms.cmd.Process.Pid, wstatus.Signal())
|
||||
ms.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)
|
||||
ms.Verbosef("process %d exited with status %#x", ms.cmd.Process.Pid, wstatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case <-waitDone:
|
||||
hlog.Resume()
|
||||
ms.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()
|
||||
ms.Resume()
|
||||
if ms.k.sync != nil {
|
||||
if err := ms.k.sync.Close(); err != nil {
|
||||
perror(err, "close wayland security context")
|
||||
@ -146,7 +147,7 @@ func (ms mainState) beforeExit(isFault bool) {
|
||||
}
|
||||
}
|
||||
|
||||
var rt system.Enablement
|
||||
var rt hst.Enablement
|
||||
if states, err := c.Load(); err != nil {
|
||||
// it is impossible to continue from this point;
|
||||
// revert per-process state here to limit damage
|
||||
@ -170,7 +171,7 @@ func (ms mainState) beforeExit(isFault bool) {
|
||||
if l := len(states); l == 0 {
|
||||
ec |= system.User
|
||||
} else {
|
||||
hlog.Verbosef("found %d instances, cleaning up without user-scoped operations", l)
|
||||
ms.Verbosef("found %d instances, cleaning up without user-scoped operations", l)
|
||||
}
|
||||
|
||||
// accumulate enablements of remaining launchers
|
||||
@ -182,10 +183,10 @@ func (ms mainState) beforeExit(isFault bool) {
|
||||
}
|
||||
}
|
||||
|
||||
ec |= rt ^ (system.EWayland | system.EX11 | system.EDBus | system.EPulse)
|
||||
if hlog.Load() {
|
||||
ec |= rt ^ (hst.EWayland | hst.EX11 | hst.EDBus | hst.EPulse)
|
||||
if ms.IsVerbose() {
|
||||
if ec > 0 {
|
||||
hlog.Verbose("reverting operations scope", system.TypeString(ec))
|
||||
ms.Verbose("reverting operations scope", system.TypeString(ec))
|
||||
}
|
||||
}
|
||||
|
||||
@ -219,7 +220,7 @@ func (ms mainState) fatal(fallback string, ferr error) {
|
||||
}
|
||||
|
||||
// main carries out outcome and terminates. main does not return.
|
||||
func (k *outcome) main() {
|
||||
func (k *outcome) main(msg container.Msg) {
|
||||
if !k.active.CompareAndSwap(false, true) {
|
||||
panic("outcome: attempted to run twice")
|
||||
}
|
||||
@ -228,19 +229,19 @@ func (k *outcome) main() {
|
||||
hsuPath := internal.MustHsuPath()
|
||||
|
||||
// ms.beforeExit required beyond this point
|
||||
ms := &mainState{k: k}
|
||||
ms := &mainState{Msg: msg, k: k}
|
||||
|
||||
if err := k.sys.Commit(); err != nil {
|
||||
ms.fatal("cannot commit system setup:", err)
|
||||
}
|
||||
ms.uintptr |= mainNeedsRevert
|
||||
ms.store = state.NewMulti(k.runDirPath.String())
|
||||
ms.store = state.NewMulti(msg, k.runDirPath.String())
|
||||
|
||||
ctx, cancel := context.WithCancel(k.ctx)
|
||||
defer cancel()
|
||||
ms.cancel = cancel
|
||||
|
||||
ms.cmd = exec.CommandContext(ctx, hsuPath)
|
||||
ms.cmd = exec.CommandContext(ctx, hsuPath.String())
|
||||
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
|
||||
@ -260,13 +261,13 @@ func (k *outcome) main() {
|
||||
}
|
||||
|
||||
if len(k.user.supp) > 0 {
|
||||
hlog.Verbosef("attaching supplementary group ids %s", k.user.supp)
|
||||
msg.Verbosef("attaching supplementary group ids %s", k.user.supp)
|
||||
// interpreted by hsu
|
||||
ms.cmd.Env = append(ms.cmd.Env, "HAKUREI_GROUPS="+strings.Join(k.user.supp, " "))
|
||||
}
|
||||
|
||||
hlog.Verbosef("setuid helper at %s", hsuPath)
|
||||
hlog.Suspend()
|
||||
msg.Verbosef("setuid helper at %s", hsuPath)
|
||||
msg.Suspend()
|
||||
if err := ms.cmd.Start(); err != nil {
|
||||
ms.fatal("cannot start setuid wrapper:", err)
|
||||
}
|
||||
@ -286,18 +287,18 @@ func (k *outcome) main() {
|
||||
os.Getpid(),
|
||||
k.waitDelay,
|
||||
k.container,
|
||||
hlog.Load(),
|
||||
msg.IsVerbose(),
|
||||
})
|
||||
}()
|
||||
return
|
||||
}():
|
||||
if err != nil {
|
||||
hlog.Resume()
|
||||
msg.Resume()
|
||||
ms.fatal("cannot transmit shim config:", err)
|
||||
}
|
||||
|
||||
case <-ctx.Done():
|
||||
hlog.Resume()
|
||||
msg.Resume()
|
||||
ms.fatal("shim context canceled:", newWithMessageError("shim setup canceled", ctx.Err()))
|
||||
}
|
||||
|
||||
|
@ -15,8 +15,6 @@ import (
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
|
||||
//#include "shim-signal.h"
|
||||
@ -53,7 +51,9 @@ const (
|
||||
|
||||
// ShimMain is the main function of the shim process and runs as the unconstrained target user.
|
||||
func ShimMain() {
|
||||
hlog.Prepare("shim")
|
||||
log.SetPrefix("shim: ")
|
||||
log.SetFlags(0)
|
||||
msg := container.NewMsg(log.Default())
|
||||
|
||||
if err := container.SetDumpable(container.SUID_DUMP_DISABLE); err != nil {
|
||||
log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
|
||||
@ -73,7 +73,7 @@ func ShimMain() {
|
||||
|
||||
log.Fatalf("cannot receive shim setup params: %v", err)
|
||||
} else {
|
||||
internal.InstallOutput(params.Verbose)
|
||||
msg.SwapVerbose(params.Verbose)
|
||||
closeSetup = f
|
||||
}
|
||||
|
||||
@ -111,12 +111,12 @@ func ShimMain() {
|
||||
}
|
||||
|
||||
// setup has not completed, terminate immediately
|
||||
hlog.Resume()
|
||||
msg.Resume()
|
||||
os.Exit(ShimExitRequest)
|
||||
return
|
||||
|
||||
case 1: // got SIGCONT after adoption: monitor died before delivering signal
|
||||
hlog.BeforeExit()
|
||||
msg.BeforeExit()
|
||||
os.Exit(ShimExitOrphan)
|
||||
return
|
||||
|
||||
@ -144,7 +144,7 @@ func ShimMain() {
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
cancelContainer.Store(&stop)
|
||||
z := container.New(ctx)
|
||||
z := container.New(ctx, msg)
|
||||
z.Params = *params.Container
|
||||
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||
|
||||
|
@ -13,8 +13,8 @@ import (
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
|
||||
// fine-grained locking and access
|
||||
@ -24,16 +24,17 @@ type multiStore struct {
|
||||
// initialised backends
|
||||
backends *sync.Map
|
||||
|
||||
lock sync.RWMutex
|
||||
msg container.Msg
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func (s *multiStore) Do(identity int, f func(c Cursor)) (bool, error) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
// load or initialise new backend
|
||||
b := new(multiBackend)
|
||||
b.lock.Lock()
|
||||
b.mu.Lock()
|
||||
if v, ok := s.backends.LoadOrStore(identity, b); ok {
|
||||
b = v.(*multiBackend)
|
||||
} else {
|
||||
@ -52,7 +53,7 @@ func (s *multiStore) Do(identity int, f func(c Cursor)) (bool, error) {
|
||||
} else {
|
||||
b.lockfile = l
|
||||
}
|
||||
b.lock.Unlock()
|
||||
b.mu.Unlock()
|
||||
}
|
||||
|
||||
// lock backend
|
||||
@ -85,17 +86,17 @@ func (s *multiStore) List() ([]int, error) {
|
||||
for _, e := range entries {
|
||||
// skip non-directories
|
||||
if !e.IsDir() {
|
||||
hlog.Verbosef("skipped non-directory entry %q", e.Name())
|
||||
s.msg.Verbosef("skipped non-directory entry %q", e.Name())
|
||||
continue
|
||||
}
|
||||
|
||||
// skip non-numerical names
|
||||
if v, err := strconv.Atoi(e.Name()); err != nil {
|
||||
hlog.Verbosef("skipped non-aid entry %q", e.Name())
|
||||
s.msg.Verbosef("skipped non-aid entry %q", e.Name())
|
||||
continue
|
||||
} else {
|
||||
if v < 0 || v > 9999 {
|
||||
hlog.Verbosef("skipped out of bounds entry %q", e.Name())
|
||||
s.msg.Verbosef("skipped out of bounds entry %q", e.Name())
|
||||
continue
|
||||
}
|
||||
|
||||
@ -107,8 +108,8 @@ func (s *multiStore) List() ([]int, error) {
|
||||
}
|
||||
|
||||
func (s *multiStore) Close() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
var errs []error
|
||||
s.backends.Range(func(_, value any) bool {
|
||||
@ -126,7 +127,7 @@ type multiBackend struct {
|
||||
// created/opened by prepare
|
||||
lockfile *os.File
|
||||
|
||||
lock sync.RWMutex
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func (b *multiBackend) filename(id *ID) string {
|
||||
@ -169,8 +170,8 @@ func (b *multiBackend) unlockFile() error {
|
||||
// reads all launchers in simpleBackend
|
||||
// file contents are ignored if decode is false
|
||||
func (b *multiBackend) load(decode bool) (Entries, error) {
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
b.mu.RLock()
|
||||
defer b.mu.RUnlock()
|
||||
|
||||
// read directory contents, should only contain files named after ids
|
||||
var entries []os.DirEntry
|
||||
@ -280,8 +281,8 @@ func (b *multiBackend) decodeState(r io.ReadSeeker, state *State) error {
|
||||
|
||||
// Save writes process state to filesystem
|
||||
func (b *multiBackend) Save(state *State, configWriter io.WriterTo) error {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
if configWriter == nil && state.Config == nil {
|
||||
return ErrNoConfig
|
||||
@ -336,8 +337,8 @@ func (b *multiBackend) encodeState(w io.WriteSeeker, state *State, configWriter
|
||||
}
|
||||
|
||||
func (b *multiBackend) Destroy(id ID) error {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
return os.Remove(b.filename(&id))
|
||||
}
|
||||
@ -353,8 +354,8 @@ func (b *multiBackend) Len() (int, error) {
|
||||
}
|
||||
|
||||
func (b *multiBackend) close() error {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
err := b.lockfile.Close()
|
||||
if err == nil || errors.Is(err, os.ErrInvalid) || errors.Is(err, os.ErrClosed) {
|
||||
@ -364,9 +365,10 @@ func (b *multiBackend) close() error {
|
||||
}
|
||||
|
||||
// NewMulti returns an instance of the multi-file store.
|
||||
func NewMulti(runDir string) Store {
|
||||
b := new(multiStore)
|
||||
b.base = path.Join(runDir, "state")
|
||||
b.backends = new(sync.Map)
|
||||
return b
|
||||
func NewMulti(msg container.Msg, runDir string) Store {
|
||||
return &multiStore{
|
||||
msg: msg,
|
||||
base: path.Join(runDir, "state"),
|
||||
backends: new(sync.Map),
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,13 @@
|
||||
package state_test
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/internal/app/state"
|
||||
)
|
||||
|
||||
func TestMulti(t *testing.T) { testStore(t, state.NewMulti(t.TempDir())) }
|
||||
func TestMulti(t *testing.T) {
|
||||
testStore(t, state.NewMulti(container.NewMsg(log.New(log.Writer(), "multi: ", 0)), t.TempDir()))
|
||||
}
|
||||
|
@ -19,9 +19,9 @@ type Store interface {
|
||||
// Cursor provided to f becomes invalid as soon as f returns.
|
||||
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.
|
||||
// Note that some or all returned aids might not have any active apps.
|
||||
List() (aids []int, err error)
|
||||
// List queries the store and returns a list of identities known to the store.
|
||||
// Note that some or all returned identities might not have any active apps.
|
||||
List() (identities []int, err error)
|
||||
|
||||
// Close releases any resources held by Store.
|
||||
Close() error
|
||||
|
@ -16,10 +16,10 @@ import (
|
||||
|
||||
func testStore(t *testing.T, s state.Store) {
|
||||
t.Run("list empty store", func(t *testing.T) {
|
||||
if aids, err := s.List(); err != nil {
|
||||
if identities, err := s.List(); err != nil {
|
||||
t.Fatalf("List: error = %v", err)
|
||||
} else if len(aids) != 0 {
|
||||
t.Fatalf("List: aids = %#v", aids)
|
||||
} else if len(identities) != 0 {
|
||||
t.Fatalf("List: identities = %#v", identities)
|
||||
}
|
||||
})
|
||||
|
||||
@ -39,22 +39,22 @@ func testStore(t *testing.T, s state.Store) {
|
||||
makeState(t, &tc[i].state, &tc[i].ct)
|
||||
}
|
||||
|
||||
do := func(aid int, f func(c state.Cursor)) {
|
||||
if ok, err := s.Do(aid, f); err != nil {
|
||||
do := func(identity int, f func(c state.Cursor)) {
|
||||
if ok, err := s.Do(identity, f); err != nil {
|
||||
t.Fatalf("Do: ok = %v, error = %v", ok, err)
|
||||
}
|
||||
}
|
||||
|
||||
insert := func(i, aid int) {
|
||||
do(aid, func(c state.Cursor) {
|
||||
insert := func(i, identity int) {
|
||||
do(identity, func(c state.Cursor) {
|
||||
if err := c.Save(&tc[i].state, &tc[i].ct); err != nil {
|
||||
t.Fatalf("Save(&tc[%v]): error = %v", i, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
check := func(i, aid int) {
|
||||
do(aid, func(c state.Cursor) {
|
||||
check := func(i, identity int) {
|
||||
do(identity, func(c state.Cursor) {
|
||||
if entries, err := c.Load(); err != nil {
|
||||
t.Fatalf("Load: error = %v", err)
|
||||
} else if got, ok := entries[tc[i].state.ID]; !ok {
|
||||
@ -81,7 +81,7 @@ func testStore(t *testing.T, s state.Store) {
|
||||
insert(insertEntryNoCheck, 0)
|
||||
})
|
||||
|
||||
t.Run("insert entry different aid", func(t *testing.T) {
|
||||
t.Run("insert entry different identity", func(t *testing.T) {
|
||||
insert(insertEntryOtherApp, 1)
|
||||
check(insertEntryOtherApp, 1)
|
||||
})
|
||||
@ -90,14 +90,14 @@ func testStore(t *testing.T, s state.Store) {
|
||||
check(insertEntryNoCheck, 0)
|
||||
})
|
||||
|
||||
t.Run("list aids", func(t *testing.T) {
|
||||
if aids, err := s.List(); err != nil {
|
||||
t.Run("list identities", func(t *testing.T) {
|
||||
if identities, err := s.List(); err != nil {
|
||||
t.Fatalf("List: error = %v", err)
|
||||
} else {
|
||||
slices.Sort(aids)
|
||||
slices.Sort(identities)
|
||||
want := []int{0, 1}
|
||||
if !slices.Equal(aids, want) {
|
||||
t.Fatalf("List() = %#v, want %#v", aids, want)
|
||||
if !slices.Equal(identities, want) {
|
||||
t.Fatalf("List() = %#v, want %#v", identities, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -110,7 +110,7 @@ func testStore(t *testing.T, s state.Store) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("clear aid 1", func(t *testing.T) {
|
||||
t.Run("clear identity 1", func(t *testing.T) {
|
||||
do(1, func(c state.Cursor) {
|
||||
if err := c.Destroy(tc[insertEntryOtherApp].state.ID); err != nil {
|
||||
t.Fatalf("Destroy: error = %v", err)
|
||||
|
@ -1,9 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
|
||||
func Exit(code int) { hlog.BeforeExit(); os.Exit(code) }
|
@ -1,34 +0,0 @@
|
||||
// Package hlog provides various functions for output messages.
|
||||
package hlog
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"hakurei.app/container"
|
||||
)
|
||||
|
||||
var o = &container.Suspendable{Downstream: os.Stderr}
|
||||
|
||||
// Prepare configures the system logger for [Suspend] and [Resume] to take effect.
|
||||
func Prepare(prefix string) { log.SetPrefix(prefix + ": "); log.SetFlags(0); log.SetOutput(o) }
|
||||
|
||||
func Suspend() bool { return o.Suspend() }
|
||||
func Resume() bool {
|
||||
resumed, dropped, _, err := o.Resume()
|
||||
if err != nil {
|
||||
// probably going to result in an error as well,
|
||||
// so this call is as good as unreachable
|
||||
log.Printf("cannot dump buffer on resume: %v", err)
|
||||
}
|
||||
if resumed && dropped > 0 {
|
||||
log.Fatalf("dropped %d bytes while output is suspended", dropped)
|
||||
}
|
||||
return resumed
|
||||
}
|
||||
|
||||
func BeforeExit() {
|
||||
if Resume() {
|
||||
log.Printf("beforeExit reached on suspended output")
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package hlog
|
||||
|
||||
type Output struct{}
|
||||
|
||||
func (Output) IsVerbose() bool { return Load() }
|
||||
func (Output) Verbose(v ...any) { Verbose(v...) }
|
||||
func (Output) Verbosef(format string, v ...any) { Verbosef(format, v...) }
|
||||
func (Output) Suspend() { Suspend() }
|
||||
func (Output) Resume() bool { return Resume() }
|
||||
func (Output) BeforeExit() { BeforeExit() }
|
@ -1,23 +0,0 @@
|
||||
package hlog
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var verbose = new(atomic.Bool)
|
||||
|
||||
func Load() bool { return verbose.Load() }
|
||||
func Store(v bool) { verbose.Store(v) }
|
||||
|
||||
func Verbosef(format string, v ...any) {
|
||||
if verbose.Load() {
|
||||
log.Printf(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func Verbose(v ...any) {
|
||||
if verbose.Load() {
|
||||
log.Println(v...)
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/internal/hlog"
|
||||
"hakurei.app/system"
|
||||
)
|
||||
|
||||
func InstallOutput(verbose bool) {
|
||||
hlog.Store(verbose)
|
||||
container.SetOutput(hlog.Output{})
|
||||
system.SetOutput(hlog.Output{})
|
||||
}
|
@ -2,9 +2,8 @@ package internal
|
||||
|
||||
import (
|
||||
"log"
|
||||
"path"
|
||||
|
||||
"hakurei.app/internal/hlog"
|
||||
"hakurei.app/container"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -12,22 +11,23 @@ var (
|
||||
hsu = compPoison
|
||||
)
|
||||
|
||||
func MustHakureiPath() string {
|
||||
if name, ok := checkPath(hmain); ok {
|
||||
return name
|
||||
}
|
||||
hlog.BeforeExit()
|
||||
log.Fatal("invalid hakurei path, this program is compiled incorrectly")
|
||||
return compPoison // unreachable
|
||||
}
|
||||
// MustHakureiPath returns the absolute path to hakurei, configured at compile time.
|
||||
func MustHakureiPath() *container.Absolute { return mustCheckPath(log.Fatal, "hakurei", hmain) }
|
||||
|
||||
func MustHsuPath() string {
|
||||
if name, ok := checkPath(hsu); ok {
|
||||
return name
|
||||
}
|
||||
hlog.BeforeExit()
|
||||
log.Fatal("invalid hsu path, this program is compiled incorrectly")
|
||||
return compPoison // unreachable
|
||||
}
|
||||
// MustHsuPath returns the absolute path to hakurei, configured at compile time.
|
||||
func MustHsuPath() *container.Absolute { return mustCheckPath(log.Fatal, "hsu", hsu) }
|
||||
|
||||
func checkPath(p string) (string, bool) { return p, p != compPoison && p != "" && path.IsAbs(p) }
|
||||
// mustCheckPath checks a pathname against compPoison, then [container.NewAbs], calling fatal if either step fails.
|
||||
func mustCheckPath(fatal func(v ...any), name, pathname string) *container.Absolute {
|
||||
if pathname != compPoison && pathname != "" {
|
||||
if a, err := container.NewAbs(pathname); err != nil {
|
||||
fatal(err.Error())
|
||||
return nil // unreachable
|
||||
} else {
|
||||
return a
|
||||
}
|
||||
} else {
|
||||
fatal("invalid " + name + " path, this program is compiled incorrectly")
|
||||
return nil // unreachable
|
||||
}
|
||||
}
|
||||
|
43
internal/path_test.go
Normal file
43
internal/path_test.go
Normal file
@ -0,0 +1,43 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
)
|
||||
|
||||
func TestMustCheckPath(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
pathname string
|
||||
wantFatal string
|
||||
}{
|
||||
{"poison", compPoison, "invalid test path, this program is compiled incorrectly"},
|
||||
{"zero", "", "invalid test path, this program is compiled incorrectly"},
|
||||
{"not absolute", "\x00", `path "\x00" is not absolute`},
|
||||
{"success", "/proc/nonexistent", ""},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
fatal := func(v ...any) { t.Fatal(append([]any{"invalid call to fatal:"}, v...)...) }
|
||||
if tc.wantFatal != "" {
|
||||
fatal = func(v ...any) {
|
||||
if len(v) != 1 {
|
||||
t.Errorf("mustCheckPath: fatal %#v", v)
|
||||
} else if gotFatal, ok := v[0].(string); !ok {
|
||||
t.Errorf("mustCheckPath: fatal = %#v", v[0])
|
||||
} else if gotFatal != tc.wantFatal {
|
||||
t.Errorf("mustCheckPath: fatal = %q, want %q", gotFatal, tc.wantFatal)
|
||||
}
|
||||
|
||||
// do not simulate exit
|
||||
}
|
||||
}
|
||||
|
||||
if got := mustCheckPath(fatal, "test", tc.pathname); got != nil && !reflect.DeepEqual(got, container.MustAbs(tc.pathname)) {
|
||||
t.Errorf("mustCheckPath: %q", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@ var (
|
||||
msgStaticGlibc = []byte("not a dynamic executable")
|
||||
)
|
||||
|
||||
func Exec(ctx context.Context, p string) ([]*Entry, error) {
|
||||
func Exec(ctx context.Context, msg container.Msg, p string) ([]*Entry, error) {
|
||||
c, cancel := context.WithTimeout(ctx, lddTimeout)
|
||||
defer cancel()
|
||||
|
||||
@ -33,7 +33,7 @@ func Exec(ctx context.Context, p string) ([]*Entry, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
z := container.NewCommand(c, toolPath, lddName, p)
|
||||
z := container.NewCommand(c, msg, toolPath, lddName, p)
|
||||
z.Hostname = "hakurei-" + lddName
|
||||
z.SeccompFlags |= seccomp.AllowMultiarch
|
||||
z.SeccompPresets |= seccomp.PresetStrict
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"slices"
|
||||
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/system/acl"
|
||||
)
|
||||
|
||||
@ -16,37 +17,37 @@ func (sys *I) UpdatePerm(path string, perms ...acl.Perm) *I {
|
||||
}
|
||||
|
||||
// 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 hst.Enablement, path string, perms ...acl.Perm) *I {
|
||||
sys.ops = append(sys.ops, &aclUpdateOp{et, path, perms})
|
||||
return sys
|
||||
}
|
||||
|
||||
// aclUpdateOp implements [I.UpdatePermType].
|
||||
type aclUpdateOp struct {
|
||||
et Enablement
|
||||
et hst.Enablement
|
||||
path string
|
||||
perms acl.Perms
|
||||
}
|
||||
|
||||
func (a *aclUpdateOp) Type() Enablement { return a.et }
|
||||
func (a *aclUpdateOp) Type() hst.Enablement { return a.et }
|
||||
|
||||
func (a *aclUpdateOp) apply(sys *I) error {
|
||||
sys.verbose("applying ACL", a)
|
||||
sys.msg.Verbose("applying ACL", a)
|
||||
return newOpError("acl", sys.aclUpdate(a.path, sys.uid, a.perms...), false)
|
||||
}
|
||||
|
||||
func (a *aclUpdateOp) revert(sys *I, ec *Criteria) error {
|
||||
if ec.hasType(a.Type()) {
|
||||
sys.verbose("stripping ACL", a)
|
||||
sys.msg.Verbose("stripping ACL", a)
|
||||
err := sys.aclUpdate(a.path, sys.uid)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
// the ACL is effectively stripped if the file no longer exists
|
||||
sys.verbosef("target of ACL %s no longer exists", a)
|
||||
sys.msg.Verbosef("target of ACL %s no longer exists", a)
|
||||
err = nil
|
||||
}
|
||||
return newOpError("acl", err, true)
|
||||
} else {
|
||||
sys.verbose("skipping ACL", a)
|
||||
sys.msg.Verbose("skipping ACL", a)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container/stub"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/system/acl"
|
||||
)
|
||||
|
||||
@ -94,9 +95,9 @@ func TestACLUpdateOp(t *testing.T) {
|
||||
}, stub.Expect{}},
|
||||
|
||||
{"wayland", 0xdeadbeef, func(_ *testing.T, sys *I) {
|
||||
sys.UpdatePermType(EWayland, "/run/user/1971/wayland-0", acl.Read, acl.Write, acl.Execute)
|
||||
sys.UpdatePermType(hst.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}},
|
||||
&aclUpdateOp{hst.EWayland, "/run/user/1971/wayland-0", []acl.Perm{acl.Read, acl.Write, acl.Execute}},
|
||||
}, stub.Expect{}},
|
||||
})
|
||||
|
||||
@ -106,34 +107,34 @@ func TestACLUpdateOp(t *testing.T) {
|
||||
|
||||
{"et differs",
|
||||
&aclUpdateOp{
|
||||
EWayland, "/run/user/1971/wayland-0",
|
||||
hst.EWayland, "/run/user/1971/wayland-0",
|
||||
[]acl.Perm{acl.Read, acl.Write, acl.Execute},
|
||||
}, &aclUpdateOp{
|
||||
EX11, "/run/user/1971/wayland-0",
|
||||
hst.EX11, "/run/user/1971/wayland-0",
|
||||
[]acl.Perm{acl.Read, acl.Write, acl.Execute},
|
||||
}, false},
|
||||
|
||||
{"path differs", &aclUpdateOp{
|
||||
EWayland, "/run/user/1971/wayland-0",
|
||||
hst.EWayland, "/run/user/1971/wayland-0",
|
||||
[]acl.Perm{acl.Read, acl.Write, acl.Execute},
|
||||
}, &aclUpdateOp{
|
||||
EWayland, "/run/user/1971/wayland-1",
|
||||
hst.EWayland, "/run/user/1971/wayland-1",
|
||||
[]acl.Perm{acl.Read, acl.Write, acl.Execute},
|
||||
}, false},
|
||||
|
||||
{"perms differs", &aclUpdateOp{
|
||||
EWayland, "/run/user/1971/wayland-0",
|
||||
hst.EWayland, "/run/user/1971/wayland-0",
|
||||
[]acl.Perm{acl.Read, acl.Write, acl.Execute},
|
||||
}, &aclUpdateOp{
|
||||
EWayland, "/run/user/1971/wayland-0",
|
||||
hst.EWayland, "/run/user/1971/wayland-0",
|
||||
[]acl.Perm{acl.Read, acl.Write},
|
||||
}, false},
|
||||
|
||||
{"equals", &aclUpdateOp{
|
||||
EWayland, "/run/user/1971/wayland-0",
|
||||
hst.EWayland, "/run/user/1971/wayland-0",
|
||||
[]acl.Perm{acl.Read, acl.Write, acl.Execute},
|
||||
}, &aclUpdateOp{
|
||||
EWayland, "/run/user/1971/wayland-0",
|
||||
hst.EWayland, "/run/user/1971/wayland-0",
|
||||
[]acl.Perm{acl.Read, acl.Write, acl.Execute},
|
||||
}, true},
|
||||
})
|
||||
@ -160,23 +161,23 @@ func TestACLUpdateOp(t *testing.T) {
|
||||
`--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",
|
||||
&aclUpdateOp{hst.EWayland, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/wayland", []acl.Perm{acl.Read, acl.Write}},
|
||||
hst.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",
|
||||
&aclUpdateOp{hst.EX11, "/tmp/.X11-unix/X0", []acl.Perm{acl.Read, acl.Execute}},
|
||||
hst.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",
|
||||
&aclUpdateOp{hst.EDBus, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/bus", []acl.Perm{acl.Write, acl.Execute}},
|
||||
hst.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",
|
||||
&aclUpdateOp{hst.EPulse, "/run/user/1971/hakurei/27d81d567f8fae7f33278eec45da9446/pulse", []acl.Perm{acl.Read, acl.Write, acl.Execute}},
|
||||
hst.EPulse, "/run/user/1971/hakurei/27d81d567f8fae7f33278eec45da9446/pulse",
|
||||
`rwx type: pulseaudio path: "/run/user/1971/hakurei/27d81d567f8fae7f33278eec45da9446/pulse"`},
|
||||
})
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
@ -54,14 +55,14 @@ func (sys *I) ProxyDBus(session, system *dbus.Config, sessionPath, systemPath st
|
||||
return nil, newOpErrorMessage("dbus", err,
|
||||
fmt.Sprintf("cannot finalise message bus proxy: %v", err), false)
|
||||
} else {
|
||||
if sys.isVerbose() {
|
||||
sys.verbose("session bus proxy:", session.Args(sessionBus))
|
||||
if sys.msg.IsVerbose() {
|
||||
sys.msg.Verbose("session bus proxy:", session.Args(sessionBus))
|
||||
if system != nil {
|
||||
sys.verbose("system bus proxy:", system.Args(systemBus))
|
||||
sys.msg.Verbose("system bus proxy:", system.Args(systemBus))
|
||||
}
|
||||
|
||||
// this calls the argsWt String method
|
||||
sys.verbose("message bus proxy final args:", final.WriterTo)
|
||||
sys.msg.Verbose("message bus proxy final args:", final.WriterTo)
|
||||
}
|
||||
|
||||
d.final = final
|
||||
@ -81,31 +82,31 @@ type dbusProxyOp struct {
|
||||
system bool
|
||||
}
|
||||
|
||||
func (d *dbusProxyOp) Type() Enablement { return Process }
|
||||
func (d *dbusProxyOp) Type() hst.Enablement { return Process }
|
||||
|
||||
func (d *dbusProxyOp) apply(sys *I) error {
|
||||
sys.verbosef("session bus proxy on %q for upstream %q", d.final.Session[1], d.final.Session[0])
|
||||
sys.msg.Verbosef("session bus proxy on %q for upstream %q", d.final.Session[1], d.final.Session[0])
|
||||
if d.system {
|
||||
sys.verbosef("system bus proxy on %q for upstream %q", d.final.System[1], d.final.System[0])
|
||||
sys.msg.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, sys.msg, d.final, d.out)
|
||||
if err := sys.dbusProxyStart(d.proxy); err != nil {
|
||||
d.out.Dump()
|
||||
return newOpErrorMessage("dbus", err,
|
||||
fmt.Sprintf("cannot start message bus proxy: %v", err), false)
|
||||
}
|
||||
sys.verbose("starting message bus proxy", d.proxy)
|
||||
sys.msg.Verbose("starting message bus proxy", d.proxy)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dbusProxyOp) revert(sys *I, _ *Criteria) error {
|
||||
// criteria ignored here since dbus is always process-scoped
|
||||
sys.verbose("terminating message bus proxy")
|
||||
sys.msg.Verbose("terminating message bus proxy")
|
||||
sys.dbusProxyClose(d.proxy)
|
||||
|
||||
exitMessage := "message bus proxy exit"
|
||||
defer func() { sys.verbose(exitMessage) }()
|
||||
defer func() { sys.msg.Verbose(exitMessage) }()
|
||||
|
||||
err := sys.dbusProxyWait(d.proxy)
|
||||
if errors.Is(err, context.Canceled) {
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/helper"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
@ -92,9 +93,9 @@ func testProxyFinaliseStartWaitCloseString(t *testing.T, useSandbox bool) {
|
||||
|
||||
t.Run("invalid start", func(t *testing.T) {
|
||||
if !useSandbox {
|
||||
p = dbus.NewDirect(t.Context(), nil, nil)
|
||||
p = dbus.NewDirect(t.Context(), container.NewMsg(nil), nil, nil)
|
||||
} else {
|
||||
p = dbus.New(t.Context(), nil, nil)
|
||||
p = dbus.New(t.Context(), container.NewMsg(nil), nil, nil)
|
||||
}
|
||||
|
||||
if err := p.Start(); !errors.Is(err, syscall.ENOTRECOVERABLE) {
|
||||
@ -127,9 +128,9 @@ func testProxyFinaliseStartWaitCloseString(t *testing.T, useSandbox bool) {
|
||||
defer cancel()
|
||||
output := new(strings.Builder)
|
||||
if !useSandbox {
|
||||
p = dbus.NewDirect(ctx, final, output)
|
||||
p = dbus.NewDirect(ctx, container.NewMsg(nil), final, output)
|
||||
} else {
|
||||
p = dbus.New(ctx, final, output)
|
||||
p = dbus.New(ctx, container.NewMsg(nil), final, output)
|
||||
}
|
||||
|
||||
t.Run("invalid wait", func(t *testing.T) {
|
||||
|
@ -3,11 +3,13 @@ package dbus
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"hakurei.app/container"
|
||||
)
|
||||
|
||||
// NewDirect returns a new instance of [Proxy] with its sandbox disabled.
|
||||
func NewDirect(ctx context.Context, final *Final, output io.Writer) *Proxy {
|
||||
p := New(ctx, final, output)
|
||||
func NewDirect(ctx context.Context, msg container.Msg, final *Final, output io.Writer) *Proxy {
|
||||
p := New(ctx, msg, final, output)
|
||||
p.useSandbox = false
|
||||
return p
|
||||
}
|
||||
|
@ -52,14 +52,14 @@ func (p *Proxy) Start() error {
|
||||
}
|
||||
|
||||
var libPaths []*container.Absolute
|
||||
if entries, err := ldd.Exec(ctx, toolPath.String()); err != nil {
|
||||
if entries, err := ldd.Exec(ctx, p.msg, toolPath.String()); err != nil {
|
||||
return err
|
||||
} else {
|
||||
libPaths = ldd.Path(entries)
|
||||
}
|
||||
|
||||
p.helper = helper.New(
|
||||
ctx, toolPath, "xdg-dbus-proxy",
|
||||
ctx, p.msg, toolPath, "xdg-dbus-proxy",
|
||||
p.final, true,
|
||||
argF, func(z *container.Container) {
|
||||
z.SeccompFlags |= seccomp.AllowMultiarch
|
||||
|
@ -6,12 +6,10 @@ import (
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/helper"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
container.TryArgv0(hlog.Output{}, hlog.Prepare, internal.InstallOutput)
|
||||
container.TryArgv0(nil)
|
||||
helper.InternalHelperStub()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/helper"
|
||||
)
|
||||
|
||||
@ -27,6 +28,7 @@ func (e *BadInterfaceError) Error() string {
|
||||
type Proxy struct {
|
||||
helper helper.Helper
|
||||
ctx context.Context
|
||||
msg container.Msg
|
||||
|
||||
cancel context.CancelCauseFunc
|
||||
cause func() error
|
||||
@ -107,6 +109,6 @@ func Finalise(sessionBus, systemBus ProxyPair, session, system *Config) (final *
|
||||
}
|
||||
|
||||
// New returns a new instance of [Proxy].
|
||||
func New(ctx context.Context, final *Final, output io.Writer) *Proxy {
|
||||
return &Proxy{name: ProxyName, ctx: ctx, final: final, output: output, useSandbox: true}
|
||||
func New(ctx context.Context, msg container.Msg, final *Final, output io.Writer) *Proxy {
|
||||
return &Proxy{name: ProxyName, ctx: ctx, msg: msg, final: final, output: output, useSandbox: true}
|
||||
}
|
||||
|
@ -57,10 +57,6 @@ type syscallDispatcher interface {
|
||||
dbusProxyClose(proxy *dbus.Proxy)
|
||||
// dbusProxyWait provides the Wait method of [dbus.Proxy].
|
||||
dbusProxyWait(proxy *dbus.Proxy) error
|
||||
|
||||
isVerbose() bool
|
||||
verbose(v ...any)
|
||||
verbosef(format string, v ...any)
|
||||
}
|
||||
|
||||
// direct implements syscallDispatcher on the current kernel.
|
||||
@ -96,7 +92,3 @@ func (k direct) dbusFinalise(sessionBus, systemBus dbus.ProxyPair, session, syst
|
||||
func (k direct) dbusProxyStart(proxy *dbus.Proxy) error { return proxy.Start() }
|
||||
func (k direct) dbusProxyClose(proxy *dbus.Proxy) { proxy.Close() }
|
||||
func (k direct) dbusProxyWait(proxy *dbus.Proxy) error { return proxy.Wait() }
|
||||
|
||||
func (k direct) isVerbose() bool { return msg.IsVerbose() }
|
||||
func (direct) verbose(v ...any) { msg.Verbose(v...) }
|
||||
func (direct) verbosef(format string, v ...any) { msg.Verbosef(format, v...) }
|
||||
|
@ -3,6 +3,7 @@ package system
|
||||
import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"slices"
|
||||
@ -11,6 +12,7 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"hakurei.app/container/stub"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/system/acl"
|
||||
"hakurei.app/system/dbus"
|
||||
"hakurei.app/system/internal/xcb"
|
||||
@ -25,7 +27,7 @@ func call(name string, args stub.ExpectArgs, ret any, err error) stub.Call {
|
||||
type opBehaviourTestCase struct {
|
||||
name string
|
||||
uid int
|
||||
ec Enablement
|
||||
ec hst.Enablement
|
||||
op Op
|
||||
|
||||
apply []stub.Call
|
||||
@ -141,7 +143,7 @@ type opMetaTestCase struct {
|
||||
name string
|
||||
op Op
|
||||
|
||||
wantType Enablement
|
||||
wantType hst.Enablement
|
||||
wantPath string
|
||||
wantString string
|
||||
}
|
||||
@ -214,10 +216,10 @@ func (r *readerOsFile) Close() error {
|
||||
|
||||
// InternalNew initialises [I] with a stub syscallDispatcher.
|
||||
func InternalNew(t *testing.T, want stub.Expect, uid int) (*I, *stub.Stub[syscallDispatcher]) {
|
||||
k := stub.New(t, func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { return &kstub{s} }, want)
|
||||
sys := New(t.Context(), uid)
|
||||
sys.syscallDispatcher = &kstub{k}
|
||||
return sys, k
|
||||
k := &kstub{stub.New(t, func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { return &kstub{s} }, want)}
|
||||
sys := New(t.Context(), k, uid)
|
||||
sys.syscallDispatcher = k
|
||||
return sys, k.Stub
|
||||
}
|
||||
|
||||
type kstub struct{ *stub.Stub[syscallDispatcher] }
|
||||
@ -357,12 +359,23 @@ func (k *kstub) dbusProxySCW(expect *stub.Call, proxy *dbus.Proxy) error {
|
||||
return expect.Err
|
||||
}
|
||||
|
||||
func (k *kstub) isVerbose() bool { k.Helper(); return k.Expects("isVerbose").Ret.(bool) }
|
||||
func (k *kstub) GetLogger() *log.Logger { panic("unreachable") }
|
||||
|
||||
func (k *kstub) IsVerbose() bool { k.Helper(); return k.Expects("isVerbose").Ret.(bool) }
|
||||
func (k *kstub) SwapVerbose(verbose bool) bool {
|
||||
k.Helper()
|
||||
expect := k.Expects("swapVerbose")
|
||||
if expect.Error(
|
||||
stub.CheckArg(k.Stub, "verbose", verbose, 0)) != nil {
|
||||
k.FailNow()
|
||||
}
|
||||
return expect.Ret.(bool)
|
||||
}
|
||||
|
||||
// ignoreValue marks a value to be ignored by the test suite.
|
||||
type ignoreValue struct{}
|
||||
|
||||
func (k *kstub) verbose(v ...any) {
|
||||
func (k *kstub) Verbose(v ...any) {
|
||||
k.Helper()
|
||||
expect := k.Expects("verbose")
|
||||
|
||||
@ -381,7 +394,7 @@ func (k *kstub) verbose(v ...any) {
|
||||
}
|
||||
}
|
||||
|
||||
func (k *kstub) verbosef(format string, v ...any) {
|
||||
func (k *kstub) Verbosef(format string, v ...any) {
|
||||
k.Helper()
|
||||
if k.Expects("verbosef").Error(
|
||||
stub.CheckArg(k.Stub, "format", format, 0),
|
||||
@ -389,3 +402,7 @@ func (k *kstub) verbosef(format string, v ...any) {
|
||||
k.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func (k *kstub) Suspend() bool { k.Helper(); return k.Expects("suspend").Ret.(bool) }
|
||||
func (k *kstub) Resume() bool { k.Helper(); return k.Expects("resume").Ret.(bool) }
|
||||
func (k *kstub) BeforeExit() { k.Helper(); k.Expects("beforeExit") }
|
||||
|
@ -1,47 +0,0 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Enablement represents an optional host service to export to the target user.
|
||||
type Enablement byte
|
||||
|
||||
const (
|
||||
EWayland Enablement = 1 << iota
|
||||
EX11
|
||||
EDBus
|
||||
EPulse
|
||||
|
||||
EM
|
||||
)
|
||||
|
||||
func (e Enablement) String() string {
|
||||
switch e {
|
||||
case 0:
|
||||
return "(no enablements)"
|
||||
case EWayland:
|
||||
return "wayland"
|
||||
case EX11:
|
||||
return "x11"
|
||||
case EDBus:
|
||||
return "dbus"
|
||||
case EPulse:
|
||||
return "pulseaudio"
|
||||
default:
|
||||
buf := new(strings.Builder)
|
||||
buf.Grow(32)
|
||||
|
||||
for i := Enablement(1); i < EM; i <<= 1 {
|
||||
if e&i != 0 {
|
||||
buf.WriteString(", " + i.String())
|
||||
}
|
||||
}
|
||||
|
||||
if buf.Len() == 0 {
|
||||
return fmt.Sprintf("e%x", byte(e))
|
||||
}
|
||||
return strings.TrimPrefix(buf.String(), ", ")
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
package system_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"hakurei.app/system"
|
||||
)
|
||||
|
||||
func TestEnablementString(t *testing.T) {
|
||||
testCases := []struct {
|
||||
flags system.Enablement
|
||||
want string
|
||||
}{
|
||||
{0, "(no enablements)"},
|
||||
{system.EWayland, "wayland"},
|
||||
{system.EX11, "x11"},
|
||||
{system.EDBus, "dbus"},
|
||||
{system.EPulse, "pulseaudio"},
|
||||
{system.EWayland | system.EX11, "wayland, x11"},
|
||||
{system.EWayland | system.EDBus, "wayland, dbus"},
|
||||
{system.EWayland | system.EPulse, "wayland, pulseaudio"},
|
||||
{system.EX11 | system.EDBus, "x11, dbus"},
|
||||
{system.EX11 | system.EPulse, "x11, pulseaudio"},
|
||||
{system.EDBus | system.EPulse, "dbus, pulseaudio"},
|
||||
{system.EWayland | system.EX11 | system.EDBus, "wayland, x11, dbus"},
|
||||
{system.EWayland | system.EX11 | system.EPulse, "wayland, x11, pulseaudio"},
|
||||
{system.EWayland | system.EDBus | system.EPulse, "wayland, dbus, pulseaudio"},
|
||||
{system.EX11 | system.EDBus | system.EPulse, "x11, dbus, pulseaudio"},
|
||||
{system.EWayland | system.EX11 | system.EDBus | system.EPulse, "wayland, x11, dbus, pulseaudio"},
|
||||
|
||||
{1 << 5, "e20"},
|
||||
{1 << 6, "e40"},
|
||||
{1 << 7, "e80"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.want, func(t *testing.T) {
|
||||
if got := tc.flags.String(); got != tc.want {
|
||||
t.Errorf("String: %q, want %q", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -2,36 +2,38 @@ package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"hakurei.app/hst"
|
||||
)
|
||||
|
||||
// Link calls LinkFileType with the [Process] criteria.
|
||||
func (sys *I) Link(oldname, newname string) *I { return sys.LinkFileType(Process, oldname, newname) }
|
||||
|
||||
// LinkFileType maintains a hardlink until its [Enablement] is no longer satisfied.
|
||||
func (sys *I) LinkFileType(et Enablement, oldname, newname string) *I {
|
||||
func (sys *I) LinkFileType(et hst.Enablement, oldname, newname string) *I {
|
||||
sys.ops = append(sys.ops, &hardlinkOp{et, newname, oldname})
|
||||
return sys
|
||||
}
|
||||
|
||||
// hardlinkOp implements [I.LinkFileType].
|
||||
type hardlinkOp struct {
|
||||
et Enablement
|
||||
et hst.Enablement
|
||||
dst, src string
|
||||
}
|
||||
|
||||
func (l *hardlinkOp) Type() Enablement { return l.et }
|
||||
func (l *hardlinkOp) Type() hst.Enablement { return l.et }
|
||||
|
||||
func (l *hardlinkOp) apply(sys *I) error {
|
||||
sys.verbose("linking", l)
|
||||
sys.msg.Verbose("linking", l)
|
||||
return newOpError("hardlink", sys.link(l.src, l.dst), false)
|
||||
}
|
||||
|
||||
func (l *hardlinkOp) revert(sys *I, ec *Criteria) error {
|
||||
if ec.hasType(l.Type()) {
|
||||
sys.verbosef("removing hard link %q", l.dst)
|
||||
sys.msg.Verbosef("removing hard link %q", l.dst)
|
||||
return newOpError("hardlink", sys.remove(l.dst), true)
|
||||
} else {
|
||||
sys.verbosef("skipping hard link %q", l.dst)
|
||||
sys.msg.Verbosef("skipping hard link %q", l.dst)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -4,32 +4,33 @@ import (
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container/stub"
|
||||
"hakurei.app/hst"
|
||||
)
|
||||
|
||||
func TestHardlinkOp(t *testing.T) {
|
||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||
{"link", 0xdeadbeef, 0xff, &hardlinkOp{EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}, []stub.Call{
|
||||
call("verbose", stub.ExpectArgs{[]any{"linking", &hardlinkOp{EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}}}, nil, nil),
|
||||
{"link", 0xdeadbeef, 0xff, &hardlinkOp{hst.EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}, []stub.Call{
|
||||
call("verbose", stub.ExpectArgs{[]any{"linking", &hardlinkOp{hst.EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}}}, nil, nil),
|
||||
call("link", stub.ExpectArgs{"/run/user/1000/pulse/native", "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}, nil, stub.UniqueError(1)),
|
||||
}, &OpError{Op: "hardlink", Err: stub.UniqueError(1)}, nil, nil},
|
||||
|
||||
{"remove", 0xdeadbeef, 0xff, &hardlinkOp{EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}, []stub.Call{
|
||||
call("verbose", stub.ExpectArgs{[]any{"linking", &hardlinkOp{EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}}}, nil, nil),
|
||||
{"remove", 0xdeadbeef, 0xff, &hardlinkOp{hst.EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}, []stub.Call{
|
||||
call("verbose", stub.ExpectArgs{[]any{"linking", &hardlinkOp{hst.EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}}}, nil, nil),
|
||||
call("link", stub.ExpectArgs{"/run/user/1000/pulse/native", "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}, nil, nil),
|
||||
}, nil, []stub.Call{
|
||||
call("verbosef", stub.ExpectArgs{"removing hard link %q", []any{"/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}}, nil, nil),
|
||||
call("remove", stub.ExpectArgs{"/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}, nil, stub.UniqueError(0)),
|
||||
}, &OpError{Op: "hardlink", Err: stub.UniqueError(0), Revert: true}},
|
||||
|
||||
{"success skip", 0xdeadbeef, EWayland | EX11, &hardlinkOp{EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}, []stub.Call{
|
||||
call("verbose", stub.ExpectArgs{[]any{"linking", &hardlinkOp{EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}}}, nil, nil),
|
||||
{"success skip", 0xdeadbeef, hst.EWayland | hst.EX11, &hardlinkOp{hst.EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}, []stub.Call{
|
||||
call("verbose", stub.ExpectArgs{[]any{"linking", &hardlinkOp{hst.EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}}}, nil, nil),
|
||||
call("link", stub.ExpectArgs{"/run/user/1000/pulse/native", "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}, nil, nil),
|
||||
}, nil, []stub.Call{
|
||||
call("verbosef", stub.ExpectArgs{"skipping hard link %q", []any{"/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}}, nil, nil),
|
||||
}, nil},
|
||||
|
||||
{"success", 0xdeadbeef, 0xff, &hardlinkOp{EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}, []stub.Call{
|
||||
call("verbose", stub.ExpectArgs{[]any{"linking", &hardlinkOp{EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}}}, nil, nil),
|
||||
{"success", 0xdeadbeef, 0xff, &hardlinkOp{hst.EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}, []stub.Call{
|
||||
call("verbose", stub.ExpectArgs{[]any{"linking", &hardlinkOp{hst.EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}}}, nil, nil),
|
||||
call("link", stub.ExpectArgs{"/run/user/1000/pulse/native", "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}, nil, nil),
|
||||
}, nil, []stub.Call{
|
||||
call("verbosef", stub.ExpectArgs{"removing hard link %q", []any{"/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}}, nil, nil),
|
||||
|
@ -4,6 +4,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"hakurei.app/hst"
|
||||
)
|
||||
|
||||
// Ensure ensures the existence of a directory.
|
||||
@ -13,23 +15,23 @@ func (sys *I) Ensure(name string, perm os.FileMode) *I {
|
||||
}
|
||||
|
||||
// Ephemeral ensures the existence of a directory until its [Enablement] is no longer satisfied.
|
||||
func (sys *I) Ephemeral(et Enablement, name string, perm os.FileMode) *I {
|
||||
func (sys *I) Ephemeral(et hst.Enablement, name string, perm os.FileMode) *I {
|
||||
sys.ops = append(sys.ops, &mkdirOp{et, name, perm, true})
|
||||
return sys
|
||||
}
|
||||
|
||||
// mkdirOp implements [I.Ensure] and [I.Ephemeral].
|
||||
type mkdirOp struct {
|
||||
et Enablement
|
||||
et hst.Enablement
|
||||
path string
|
||||
perm os.FileMode
|
||||
ephemeral bool
|
||||
}
|
||||
|
||||
func (m *mkdirOp) Type() Enablement { return m.et }
|
||||
func (m *mkdirOp) Type() hst.Enablement { return m.et }
|
||||
|
||||
func (m *mkdirOp) apply(sys *I) error {
|
||||
sys.verbose("ensuring directory", m)
|
||||
sys.msg.Verbose("ensuring directory", m)
|
||||
|
||||
if err := sys.mkdir(m.path, m.perm); err != nil {
|
||||
if !errors.Is(err, os.ErrExist) {
|
||||
@ -49,10 +51,10 @@ func (m *mkdirOp) revert(sys *I, ec *Criteria) error {
|
||||
}
|
||||
|
||||
if ec.hasType(m.Type()) {
|
||||
sys.verbose("destroying ephemeral directory", m)
|
||||
sys.msg.Verbose("destroying ephemeral directory", m)
|
||||
return newOpError("mkdir", sys.remove(m.path), true)
|
||||
} else {
|
||||
sys.verbose("skipping ephemeral directory", m)
|
||||
sys.msg.Verbose("skipping ephemeral directory", m)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -8,16 +8,6 @@ import (
|
||||
"hakurei.app/container"
|
||||
)
|
||||
|
||||
var msg container.Msg = new(container.DefaultMsg)
|
||||
|
||||
func SetOutput(v container.Msg) {
|
||||
if v == nil {
|
||||
msg = new(container.DefaultMsg)
|
||||
} else {
|
||||
msg = v
|
||||
}
|
||||
}
|
||||
|
||||
// OpError is returned by [I.Commit] and [I.Revert].
|
||||
type OpError struct {
|
||||
Op string
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
|
||||
func TestOpError(t *testing.T) {
|
||||
@ -87,33 +86,6 @@ func TestOpError(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetOutput(t *testing.T) {
|
||||
oldmsg := msg
|
||||
t.Cleanup(func() { msg = oldmsg })
|
||||
msg = nil
|
||||
|
||||
t.Run("nil", func(t *testing.T) {
|
||||
SetOutput(nil)
|
||||
if _, ok := msg.(*container.DefaultMsg); !ok {
|
||||
t.Errorf("SetOutput: %#v", msg)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("hlog", func(t *testing.T) {
|
||||
SetOutput(hlog.Output{})
|
||||
if _, ok := msg.(hlog.Output); !ok {
|
||||
t.Errorf("SetOutput: %#v", msg)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("reset", func(t *testing.T) {
|
||||
SetOutput(nil)
|
||||
if _, ok := msg.(*container.DefaultMsg); !ok {
|
||||
t.Errorf("SetOutput: %#v", msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestPrintJoinedError(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
|
@ -5,11 +5,14 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
)
|
||||
|
||||
const (
|
||||
// User type is reverted at final instance exit.
|
||||
User = EM << iota
|
||||
User = hst.EM << iota
|
||||
// Process type is unconditionally reverted on exit.
|
||||
Process
|
||||
|
||||
@ -17,21 +20,21 @@ const (
|
||||
)
|
||||
|
||||
// Criteria specifies types of Op to revert.
|
||||
type Criteria Enablement
|
||||
type Criteria hst.Enablement
|
||||
|
||||
func (ec *Criteria) hasType(t Enablement) bool {
|
||||
func (ec *Criteria) hasType(t hst.Enablement) bool {
|
||||
// nil criteria: revert everything except User
|
||||
if ec == nil {
|
||||
return t != User
|
||||
}
|
||||
|
||||
return Enablement(*ec)&t != 0
|
||||
return hst.Enablement(*ec)&t != 0
|
||||
}
|
||||
|
||||
// Op is a reversible system operation.
|
||||
type Op interface {
|
||||
// Type returns [Op]'s enablement type, for matching a revert criteria.
|
||||
Type() Enablement
|
||||
Type() hst.Enablement
|
||||
|
||||
apply(sys *I) error
|
||||
revert(sys *I, ec *Criteria) error
|
||||
@ -42,7 +45,7 @@ type Op interface {
|
||||
}
|
||||
|
||||
// TypeString extends [Enablement.String] to support [User] and [Process].
|
||||
func TypeString(e Enablement) string {
|
||||
func TypeString(e hst.Enablement) string {
|
||||
switch e {
|
||||
case User:
|
||||
return "user"
|
||||
@ -65,11 +68,11 @@ func TypeString(e Enablement) string {
|
||||
}
|
||||
|
||||
// New returns the address of a new [I] targeting uid.
|
||||
func New(ctx context.Context, uid int) (sys *I) {
|
||||
if ctx == nil || uid < 0 {
|
||||
func New(ctx context.Context, msg container.Msg, uid int) (sys *I) {
|
||||
if ctx == nil || msg == nil || uid < 0 {
|
||||
panic("invalid call to New")
|
||||
}
|
||||
return &I{ctx: ctx, uid: uid, syscallDispatcher: direct{}}
|
||||
return &I{ctx: ctx, msg: msg, uid: uid, syscallDispatcher: direct{}}
|
||||
}
|
||||
|
||||
// An I provides deferred operating system interaction. [I] must not be copied.
|
||||
@ -86,6 +89,7 @@ type I struct {
|
||||
// the behaviour of Revert is only defined for up to one call
|
||||
reverted bool
|
||||
|
||||
msg container.Msg
|
||||
syscallDispatcher
|
||||
}
|
||||
|
||||
@ -114,14 +118,14 @@ func (sys *I) Commit() error {
|
||||
}
|
||||
sys.committed = true
|
||||
|
||||
sp := New(sys.ctx, sys.uid)
|
||||
sp := New(sys.ctx, sys.msg, sys.uid)
|
||||
sp.syscallDispatcher = sys.syscallDispatcher
|
||||
sp.ops = make([]Op, 0, len(sys.ops)) // prevent copies during commits
|
||||
defer func() {
|
||||
// sp is set to nil when all ops are applied
|
||||
if sp != nil {
|
||||
// rollback partial commit
|
||||
sys.verbosef("commit faulted after %d ops, rolling back partial commit", len(sp.ops))
|
||||
sys.msg.Verbosef("commit faulted after %d ops, rolling back partial commit", len(sp.ops))
|
||||
if err := sp.Revert(nil); err != nil {
|
||||
printJoinedError(sys.println, "cannot revert partial commit:", err)
|
||||
}
|
||||
|
@ -8,19 +8,21 @@ import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/stub"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/system/internal/xcb"
|
||||
)
|
||||
|
||||
func TestCriteria(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
ec, t Enablement
|
||||
ec, t hst.Enablement
|
||||
want bool
|
||||
}{
|
||||
{"nil", 0xff, EWayland, true},
|
||||
{"nil", 0xff, hst.EWayland, true},
|
||||
{"nil user", 0xff, User, false},
|
||||
{"all", EWayland | EX11 | EDBus | EPulse | User | Process, Process, true},
|
||||
{"all", hst.EWayland | hst.EX11 | hst.EDBus | hst.EPulse | User | Process, Process, true},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@ -39,18 +41,18 @@ func TestCriteria(t *testing.T) {
|
||||
|
||||
func TestTypeString(t *testing.T) {
|
||||
testCases := []struct {
|
||||
e Enablement
|
||||
e hst.Enablement
|
||||
want string
|
||||
}{
|
||||
{EWayland, EWayland.String()},
|
||||
{EX11, EX11.String()},
|
||||
{EDBus, EDBus.String()},
|
||||
{EPulse, EPulse.String()},
|
||||
{hst.EWayland, hst.EWayland.String()},
|
||||
{hst.EX11, hst.EX11.String()},
|
||||
{hst.EDBus, hst.EDBus.String()},
|
||||
{hst.EPulse, hst.EPulse.String()},
|
||||
{User, "user"},
|
||||
{Process, "process"},
|
||||
{User | Process, "user, process"},
|
||||
{EWayland | User | Process, "wayland, user, process"},
|
||||
{EX11 | Process, "x11, process"},
|
||||
{hst.EWayland | User | Process, "wayland, user, process"},
|
||||
{hst.EX11 | Process, "x11, process"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@ -71,7 +73,17 @@ func TestNew(t *testing.T) {
|
||||
t.Errorf("recover: %v, want %v", r, want)
|
||||
}
|
||||
}()
|
||||
New(nil, 0)
|
||||
New(nil, container.NewMsg(nil), 0)
|
||||
})
|
||||
|
||||
t.Run("msg", func(t *testing.T) {
|
||||
defer func() {
|
||||
want := "invalid call to New"
|
||||
if r := recover(); r != want {
|
||||
t.Errorf("recover: %v, want %v", r, want)
|
||||
}
|
||||
}()
|
||||
New(t.Context(), nil, 0)
|
||||
})
|
||||
|
||||
t.Run("uid", func(t *testing.T) {
|
||||
@ -81,11 +93,11 @@ func TestNew(t *testing.T) {
|
||||
t.Errorf("recover: %v, want %v", r, want)
|
||||
}
|
||||
}()
|
||||
New(t.Context(), -1)
|
||||
New(t.Context(), container.NewMsg(nil), -1)
|
||||
})
|
||||
})
|
||||
|
||||
sys := New(t.Context(), 0xdeadbeef)
|
||||
sys := New(t.Context(), container.NewMsg(nil), 0xdeadbeef)
|
||||
if sys.ctx == nil {
|
||||
t.Error("New: ctx = nil")
|
||||
}
|
||||
@ -102,51 +114,51 @@ func TestEqual(t *testing.T) {
|
||||
want bool
|
||||
}{
|
||||
{"simple UID",
|
||||
New(t.Context(), 150),
|
||||
New(t.Context(), 150),
|
||||
New(t.Context(), container.NewMsg(nil), 150),
|
||||
New(t.Context(), container.NewMsg(nil), 150),
|
||||
true},
|
||||
|
||||
{"simple UID differ",
|
||||
New(t.Context(), 150),
|
||||
New(t.Context(), 151),
|
||||
New(t.Context(), container.NewMsg(nil), 150),
|
||||
New(t.Context(), container.NewMsg(nil), 151),
|
||||
false},
|
||||
|
||||
{"simple UID nil",
|
||||
New(t.Context(), 150),
|
||||
New(t.Context(), container.NewMsg(nil), 150),
|
||||
nil,
|
||||
false},
|
||||
|
||||
{"op length mismatch",
|
||||
New(t.Context(), 150).
|
||||
New(t.Context(), container.NewMsg(nil), 150).
|
||||
ChangeHosts("chronos"),
|
||||
New(t.Context(), 150).
|
||||
New(t.Context(), container.NewMsg(nil), 150).
|
||||
ChangeHosts("chronos").
|
||||
Ensure("/run", 0755),
|
||||
false},
|
||||
|
||||
{"op value mismatch",
|
||||
New(t.Context(), 150).
|
||||
New(t.Context(), container.NewMsg(nil), 150).
|
||||
ChangeHosts("chronos").
|
||||
Ensure("/run", 0644),
|
||||
New(t.Context(), 150).
|
||||
New(t.Context(), container.NewMsg(nil), 150).
|
||||
ChangeHosts("chronos").
|
||||
Ensure("/run", 0755),
|
||||
false},
|
||||
|
||||
{"op type mismatch",
|
||||
New(t.Context(), 150).
|
||||
New(t.Context(), container.NewMsg(nil), 150).
|
||||
ChangeHosts("chronos").
|
||||
CopyFile(new([]byte), "/home/ophestra/xdg/config/pulse/cookie", 0, 256),
|
||||
New(t.Context(), 150).
|
||||
New(t.Context(), container.NewMsg(nil), 150).
|
||||
ChangeHosts("chronos").
|
||||
Ensure("/run", 0755),
|
||||
false},
|
||||
|
||||
{"op equals",
|
||||
New(t.Context(), 150).
|
||||
New(t.Context(), container.NewMsg(nil), 150).
|
||||
ChangeHosts("chronos").
|
||||
Ensure("/run", 0755),
|
||||
New(t.Context(), 150).
|
||||
New(t.Context(), container.NewMsg(nil), 150).
|
||||
ChangeHosts("chronos").
|
||||
Ensure("/run", 0755),
|
||||
true},
|
||||
@ -165,7 +177,7 @@ func TestCommitRevert(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
f func(sys *I)
|
||||
ec Enablement
|
||||
ec hst.Enablement
|
||||
|
||||
commit []stub.Call
|
||||
wantErrCommit error
|
||||
|
@ -7,6 +7,8 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/hst"
|
||||
)
|
||||
|
||||
// CopyFile reads up to n bytes from src and writes the resulting byte slice to payloadP.
|
||||
@ -26,7 +28,7 @@ type tmpfileOp struct {
|
||||
buf *bytes.Buffer
|
||||
}
|
||||
|
||||
func (t *tmpfileOp) Type() Enablement { return Process }
|
||||
func (t *tmpfileOp) Type() hst.Enablement { return Process }
|
||||
|
||||
func (t *tmpfileOp) apply(sys *I) error {
|
||||
if t.payload == nil {
|
||||
@ -34,7 +36,7 @@ func (t *tmpfileOp) apply(sys *I) error {
|
||||
return errors.New("invalid payload")
|
||||
}
|
||||
|
||||
sys.verbose("copying", t)
|
||||
sys.msg.Verbose("copying", t)
|
||||
|
||||
if b, err := sys.stat(t.src); err != nil {
|
||||
return newOpError("tmpfile", err, false)
|
||||
@ -58,7 +60,7 @@ func (t *tmpfileOp) apply(sys *I) error {
|
||||
_ = r.Close()
|
||||
return newOpError("tmpfile", err, false)
|
||||
}
|
||||
sys.verbosef("copied %d bytes from %q", n, t.src)
|
||||
sys.msg.Verbosef("copied %d bytes from %q", n, t.src)
|
||||
}
|
||||
if err := r.Close(); err != nil {
|
||||
return newOpError("tmpfile", err, false)
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/system/acl"
|
||||
"hakurei.app/system/wayland"
|
||||
)
|
||||
@ -32,7 +33,7 @@ type waylandOp struct {
|
||||
conn waylandConn
|
||||
}
|
||||
|
||||
func (w *waylandOp) Type() Enablement { return Process }
|
||||
func (w *waylandOp) Type() hst.Enablement { return Process }
|
||||
|
||||
func (w *waylandOp) apply(sys *I) error {
|
||||
if w.sync == nil {
|
||||
@ -43,14 +44,14 @@ func (w *waylandOp) apply(sys *I) error {
|
||||
if err := w.conn.Attach(w.src); err != nil {
|
||||
return newOpError("wayland", err, false)
|
||||
} else {
|
||||
sys.verbosef("wayland attached on %q", w.src)
|
||||
sys.msg.Verbosef("wayland attached on %q", w.src)
|
||||
}
|
||||
|
||||
if sp, err := w.conn.Bind(w.dst, w.appID, w.instanceID); err != nil {
|
||||
return newOpError("wayland", err, false)
|
||||
} else {
|
||||
*w.sync = sp
|
||||
sys.verbosef("wayland listening on %q", w.dst)
|
||||
sys.msg.Verbosef("wayland listening on %q", w.dst)
|
||||
if err = sys.chmod(w.dst, 0); err != nil {
|
||||
return newOpError("wayland", err, false)
|
||||
}
|
||||
@ -59,12 +60,12 @@ func (w *waylandOp) apply(sys *I) error {
|
||||
}
|
||||
|
||||
func (w *waylandOp) revert(sys *I, _ *Criteria) error {
|
||||
sys.verbosef("removing wayland socket on %q", w.dst)
|
||||
sys.msg.Verbosef("removing wayland socket on %q", w.dst)
|
||||
if err := sys.remove(w.dst); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return newOpError("wayland", err, true)
|
||||
}
|
||||
|
||||
sys.verbosef("detaching from wayland on %q", w.src)
|
||||
sys.msg.Verbosef("detaching from wayland on %q", w.src)
|
||||
return newOpError("wayland", w.conn.Close(), true)
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/system/internal/xcb"
|
||||
)
|
||||
|
||||
@ -13,21 +14,21 @@ func (sys *I) ChangeHosts(username string) *I {
|
||||
// xhostOp implements [I.ChangeHosts].
|
||||
type xhostOp string
|
||||
|
||||
func (x xhostOp) Type() Enablement { return EX11 }
|
||||
func (x xhostOp) Type() hst.Enablement { return hst.EX11 }
|
||||
|
||||
func (x xhostOp) apply(sys *I) error {
|
||||
sys.verbosef("inserting entry %s to X11", x)
|
||||
sys.msg.Verbosef("inserting entry %s to X11", x)
|
||||
return newOpError("xhost",
|
||||
sys.xcbChangeHosts(xcb.HostModeInsert, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)), false)
|
||||
}
|
||||
|
||||
func (x xhostOp) revert(sys *I, ec *Criteria) error {
|
||||
if ec.hasType(x.Type()) {
|
||||
sys.verbosef("deleting entry %s from X11", x)
|
||||
sys.msg.Verbosef("deleting entry %s from X11", x)
|
||||
return newOpError("xhost",
|
||||
sys.xcbChangeHosts(xcb.HostModeDelete, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)), true)
|
||||
} else {
|
||||
sys.verbosef("skipping entry %s in X11", x)
|
||||
sys.msg.Verbosef("skipping entry %s in X11", x)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -4,17 +4,18 @@ import (
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container/stub"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/system/internal/xcb"
|
||||
)
|
||||
|
||||
func TestXHostOp(t *testing.T) {
|
||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||
{"xcbChangeHosts revert", 0xbeef, EX11, xhostOp("chronos"), []stub.Call{
|
||||
{"xcbChangeHosts revert", 0xbeef, hst.EX11, xhostOp("chronos"), []stub.Call{
|
||||
call("verbosef", stub.ExpectArgs{"inserting entry %s to X11", []any{xhostOp("chronos")}}, nil, nil),
|
||||
call("xcbChangeHosts", stub.ExpectArgs{xcb.HostMode(xcb.HostModeInsert), xcb.Family(xcb.FamilyServerInterpreted), "localuser\x00chronos"}, nil, stub.UniqueError(1)),
|
||||
}, &OpError{Op: "xhost", Err: stub.UniqueError(1)}, nil, nil},
|
||||
|
||||
{"xcbChangeHosts revert", 0xbeef, EX11, xhostOp("chronos"), []stub.Call{
|
||||
{"xcbChangeHosts revert", 0xbeef, hst.EX11, xhostOp("chronos"), []stub.Call{
|
||||
call("verbosef", stub.ExpectArgs{"inserting entry %s to X11", []any{xhostOp("chronos")}}, nil, nil),
|
||||
call("xcbChangeHosts", stub.ExpectArgs{xcb.HostMode(xcb.HostModeInsert), xcb.Family(xcb.FamilyServerInterpreted), "localuser\x00chronos"}, nil, nil),
|
||||
}, nil, []stub.Call{
|
||||
@ -29,7 +30,7 @@ func TestXHostOp(t *testing.T) {
|
||||
call("verbosef", stub.ExpectArgs{"skipping entry %s in X11", []any{xhostOp("chronos")}}, nil, nil),
|
||||
}, nil},
|
||||
|
||||
{"success", 0xbeef, EX11, xhostOp("chronos"), []stub.Call{
|
||||
{"success", 0xbeef, hst.EX11, xhostOp("chronos"), []stub.Call{
|
||||
call("verbosef", stub.ExpectArgs{"inserting entry %s to X11", []any{xhostOp("chronos")}}, nil, nil),
|
||||
call("xcbChangeHosts", stub.ExpectArgs{xcb.HostMode(xcb.HostModeInsert), xcb.Family(xcb.FamilyServerInterpreted), "localuser\x00chronos"}, nil, nil),
|
||||
}, nil, []stub.Call{
|
||||
@ -52,6 +53,6 @@ func TestXHostOp(t *testing.T) {
|
||||
})
|
||||
|
||||
checkOpMeta(t, []opMetaTestCase{
|
||||
{"xhost", xhostOp("chronos"), EX11, "/tmp/.X11-unix", "SI:localuser:chronos"},
|
||||
{"xhost", xhostOp("chronos"), hst.EX11, "/tmp/.X11-unix", "SI:localuser:chronos"},
|
||||
})
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user