internal: remove sys package
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Sandbox (push) Successful in 2m13s
Test / Hakurei (push) Successful in 3m14s
Test / Hpkg (push) Successful in 4m2s
Test / Sandbox (race detector) (push) Successful in 4m39s
Test / Hakurei (race detector) (push) Successful in 5m19s
Test / Flake checks (push) Successful in 1m19s
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Sandbox (push) Successful in 2m13s
Test / Hakurei (push) Successful in 3m14s
Test / Hpkg (push) Successful in 4m2s
Test / Sandbox (race detector) (push) Successful in 4m39s
Test / Hakurei (race detector) (push) Successful in 5m19s
Test / Flake checks (push) Successful in 1m19s
This package is replaced by container/stub. Remove and replace it with unexported implementation for the upcoming test suite rewrite. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
parent
6e3f34f2ec
commit
ae2df2c450
@ -18,7 +18,6 @@ import (
|
||||
"hakurei.app/internal/app"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/internal/hlog"
|
||||
"hakurei.app/internal/sys"
|
||||
"hakurei.app/system"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
@ -43,7 +42,7 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
|
||||
config := tryPath(args[0])
|
||||
config.Args = append(config.Args, args[1:]...)
|
||||
|
||||
app.Main(ctx, std, config)
|
||||
app.Main(ctx, config)
|
||||
panic("unreachable")
|
||||
})
|
||||
|
||||
@ -79,7 +78,7 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
|
||||
passwd *user.User
|
||||
passwdOnce sync.Once
|
||||
passwdFunc = func() {
|
||||
us := strconv.Itoa(sys.MustUid(std, flagIdentity))
|
||||
us := strconv.Itoa(app.HsuUid(new(app.Hsu).MustID(), flagIdentity))
|
||||
if u, err := user.LookupId(us); err != nil {
|
||||
hlog.Verbosef("cannot look up uid %s", us)
|
||||
passwd = &user.User{
|
||||
@ -163,7 +162,7 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
|
||||
}
|
||||
}
|
||||
|
||||
app.Main(ctx, std, config)
|
||||
app.Main(ctx, config)
|
||||
panic("unreachable")
|
||||
}).
|
||||
Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"),
|
||||
@ -219,7 +218,9 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
|
||||
{
|
||||
var flagShort bool
|
||||
c.NewCommand("ps", "List active instances", func(args []string) error {
|
||||
printPs(os.Stdout, time.Now().UTC(), state.NewMulti(std.Paths().RunDirPath.String()), flagShort, flagJSON)
|
||||
var sc hst.Paths
|
||||
app.CopyPaths(&sc, new(app.Hsu).MustID())
|
||||
printPs(os.Stdout, time.Now().UTC(), state.NewMulti(sc.RunDirPath.String()), flagShort, flagJSON)
|
||||
return errSuccess
|
||||
}).Flag(&flagShort, "short", command.BoolFlag(false), "Print instance id")
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ import (
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/hlog"
|
||||
"hakurei.app/internal/sys"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -27,8 +26,6 @@ var (
|
||||
|
||||
func init() { hlog.Prepare("hakurei") }
|
||||
|
||||
var std sys.State = new(sys.Std)
|
||||
|
||||
func main() {
|
||||
// early init path, skips root check and duplicate PR_SET_DUMPABLE
|
||||
container.TryArgv0(hlog.Output{}, hlog.Prepare, internal.InstallOutput)
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
@ -87,7 +88,9 @@ func tryShort(name string) (config *hst.Config, entry *state.State) {
|
||||
if likePrefix && len(name) >= 8 {
|
||||
hlog.Verbose("argument looks like prefix")
|
||||
|
||||
s := state.NewMulti(std.Paths().RunDirPath.String())
|
||||
var sc hst.Paths
|
||||
app.CopyPaths(&sc, new(app.Hsu).MustID())
|
||||
s := state.NewMulti(sc.RunDirPath.String())
|
||||
if entries, err := state.Join(s); err != nil {
|
||||
log.Printf("cannot join store: %v", err)
|
||||
// drop to fetch from file
|
||||
|
@ -12,8 +12,8 @@ import (
|
||||
"time"
|
||||
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/internal/sys"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
@ -21,10 +21,8 @@ func printShowSystem(output io.Writer, short, flagJSON bool) {
|
||||
t := newPrinter(output)
|
||||
defer t.MustFlush()
|
||||
|
||||
info := &hst.Info{
|
||||
Paths: std.Paths(),
|
||||
User: sys.MustGetUserID(std),
|
||||
}
|
||||
info := &hst.Info{User: new(app.Hsu).MustID()}
|
||||
app.CopyPaths(&info.Paths, info.User)
|
||||
|
||||
if flagJSON {
|
||||
printJSON(output, short, info)
|
||||
|
@ -8,19 +8,17 @@ import (
|
||||
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/internal/sys"
|
||||
)
|
||||
|
||||
// Main runs an app according to [hst.Config] and terminates. Main does not return.
|
||||
func Main(ctx context.Context, k sys.State, config *hst.Config) {
|
||||
func Main(ctx context.Context, config *hst.Config) {
|
||||
var id state.ID
|
||||
if err := state.NewAppID(&id); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var seal outcome
|
||||
seal.id = &stringPair[state.ID]{id, id.String()}
|
||||
if err := seal.finalise(ctx, k, config); err != nil {
|
||||
seal := outcome{id: &stringPair[state.ID]{id, id.String()}, syscallDispatcher: direct{}}
|
||||
if err := seal.finalise(ctx, config); err != nil {
|
||||
printMessageError("cannot seal app:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
@ -1,34 +1,24 @@
|
||||
package app_test
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"strconv"
|
||||
|
||||
"hakurei.app/hst"
|
||||
)
|
||||
|
||||
// fs methods are not implemented using a real FS
|
||||
// to help better understand filesystem access behaviour
|
||||
type stubNixOS struct {
|
||||
lookPathErr map[string]error
|
||||
usernameErr map[string]error
|
||||
}
|
||||
|
||||
func (s *stubNixOS) Getuid() int { return 1971 }
|
||||
func (s *stubNixOS) Getgid() int { return 100 }
|
||||
func (s *stubNixOS) TempDir() string { return "/tmp" }
|
||||
func (s *stubNixOS) MustExecutable() string { return "/run/wrappers/bin/hakurei" }
|
||||
func (s *stubNixOS) Exit(code int) { panic("called exit on stub with code " + strconv.Itoa(code)) }
|
||||
func (s *stubNixOS) EvalSymlinks(path string) (string, error) { return path, nil }
|
||||
func (s *stubNixOS) Uid(aid int) (int, error) { return 1000000 + 0*10000 + aid, nil }
|
||||
func (k *stubNixOS) new(func(k syscallDispatcher)) { panic("not implemented") }
|
||||
|
||||
func (s *stubNixOS) Println(v ...any) { log.Println(v...) }
|
||||
func (s *stubNixOS) Printf(format string, v ...any) { log.Printf(format, v...) }
|
||||
func (k *stubNixOS) getuid() int { return 1971 }
|
||||
func (k *stubNixOS) getgid() int { return 100 }
|
||||
|
||||
func (s *stubNixOS) LookupEnv(key string) (string, bool) {
|
||||
func (k *stubNixOS) lookupEnv(key string) (string, bool) {
|
||||
switch key {
|
||||
case "SHELL":
|
||||
return "/run/current-system/sw/bin/zsh", true
|
||||
@ -40,6 +30,8 @@ func (s *stubNixOS) LookupEnv(key string) (string, bool) {
|
||||
return "", false
|
||||
case "HOME":
|
||||
return "/home/ophestra", true
|
||||
case "XDG_RUNTIME_DIR":
|
||||
return "/run/user/1971", true
|
||||
case "XDG_CONFIG_HOME":
|
||||
return "/home/ophestra/xdg/config", true
|
||||
default:
|
||||
@ -47,61 +39,7 @@ func (s *stubNixOS) LookupEnv(key string) (string, bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stubNixOS) LookPath(file string) (string, error) {
|
||||
if s.lookPathErr != nil {
|
||||
if err, ok := s.lookPathErr[file]; ok {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
switch file {
|
||||
case "zsh":
|
||||
return "/run/current-system/sw/bin/zsh", nil
|
||||
default:
|
||||
panic(fmt.Sprintf("attempted to look up unexpected executable %q", file))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stubNixOS) LookupGroup(name string) (*user.Group, error) {
|
||||
switch name {
|
||||
case "video":
|
||||
return &user.Group{Gid: "26", Name: "video"}, nil
|
||||
default:
|
||||
return nil, user.UnknownGroupError(name)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stubNixOS) ReadDir(name string) ([]fs.DirEntry, error) {
|
||||
switch name {
|
||||
case "/":
|
||||
return stubDirEntries("bin", "boot", "dev", "etc", "home", "lib",
|
||||
"lib64", "nix", "proc", "root", "run", "srv", "sys", "tmp", "usr", "var")
|
||||
case "/run":
|
||||
return stubDirEntries("agetty.reload", "binfmt", "booted-system",
|
||||
"credentials", "cryptsetup", "current-system", "dbus", "host", "keys",
|
||||
"libvirt", "libvirtd.pid", "lock", "log", "lvm", "mount", "NetworkManager",
|
||||
"nginx", "nixos", "nscd", "opengl-driver", "pppd", "resolvconf", "sddm",
|
||||
"store", "syncoid", "system", "systemd", "tmpfiles.d", "udev", "udisks2",
|
||||
"user", "utmp", "virtlogd.pid", "wrappers", "zed.pid", "zed.state")
|
||||
case "/etc":
|
||||
return stubDirEntries("alsa", "bashrc", "binfmt.d", "dbus-1", "default",
|
||||
"ethertypes", "fonts", "fstab", "fuse.conf", "group", "host.conf", "hostid",
|
||||
"hostname", "hostname.CHECKSUM", "hosts", "inputrc", "ipsec.d", "issue", "kbd",
|
||||
"libblockdev", "locale.conf", "localtime", "login.defs", "lsb-release", "lvm",
|
||||
"machine-id", "man_db.conf", "modprobe.d", "modules-load.d", "mtab", "nanorc",
|
||||
"netgroup", "NetworkManager", "nix", "nixos", "NIXOS", "nscd.conf", "nsswitch.conf",
|
||||
"opensnitchd", "os-release", "pam", "pam.d", "passwd", "pipewire", "pki", "polkit-1",
|
||||
"profile", "protocols", "qemu", "resolv.conf", "resolvconf.conf", "rpc", "samba",
|
||||
"sddm.conf", "secureboot", "services", "set-environment", "shadow", "shells", "ssh",
|
||||
"ssl", "static", "subgid", "subuid", "sudoers", "sysctl.d", "systemd", "terminfo",
|
||||
"tmpfiles.d", "udev", "udisks2", "UPower", "vconsole.conf", "X11", "zfs", "zinputrc",
|
||||
"zoneinfo", "zprofile", "zshenv", "zshrc")
|
||||
default:
|
||||
panic(fmt.Sprintf("attempted to read unexpected directory %q", name))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stubNixOS) Stat(name string) (fs.FileInfo, error) {
|
||||
func (k *stubNixOS) stat(name string) (fs.FileInfo, error) {
|
||||
switch name {
|
||||
case "/var/run/nscd":
|
||||
return nil, nil
|
||||
@ -118,17 +56,144 @@ func (s *stubNixOS) Stat(name string) (fs.FileInfo, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stubNixOS) Open(name string) (fs.File, error) {
|
||||
func (k *stubNixOS) readdir(name string) ([]fs.DirEntry, error) {
|
||||
switch name {
|
||||
case "/":
|
||||
return stubDirEntries("bin", "boot", "dev", "etc", "home", "lib",
|
||||
"lib64", "nix", "proc", "root", "run", "srv", "sys", "tmp", "usr", "var")
|
||||
|
||||
case "/run":
|
||||
return stubDirEntries("agetty.reload", "binfmt", "booted-system",
|
||||
"credentials", "cryptsetup", "current-system", "dbus", "host", "keys",
|
||||
"libvirt", "libvirtd.pid", "lock", "log", "lvm", "mount", "NetworkManager",
|
||||
"nginx", "nixos", "nscd", "opengl-driver", "pppd", "resolvconf", "sddm",
|
||||
"store", "syncoid", "system", "systemd", "tmpfiles.d", "udev", "udisks2",
|
||||
"user", "utmp", "virtlogd.pid", "wrappers", "zed.pid", "zed.state")
|
||||
|
||||
case "/etc":
|
||||
return stubDirEntries("alsa", "bashrc", "binfmt.d", "dbus-1", "default",
|
||||
"ethertypes", "fonts", "fstab", "fuse.conf", "group", "host.conf", "hostid",
|
||||
"hostname", "hostname.CHECKSUM", "hosts", "inputrc", "ipsec.d", "issue", "kbd",
|
||||
"libblockdev", "locale.conf", "localtime", "login.defs", "lsb-release", "lvm",
|
||||
"machine-id", "man_db.conf", "modprobe.d", "modules-load.d", "mtab", "nanorc",
|
||||
"netgroup", "NetworkManager", "nix", "nixos", "NIXOS", "nscd.conf", "nsswitch.conf",
|
||||
"opensnitchd", "os-release", "pam", "pam.d", "passwd", "pipewire", "pki", "polkit-1",
|
||||
"profile", "protocols", "qemu", "resolv.conf", "resolvconf.conf", "rpc", "samba",
|
||||
"sddm.conf", "secureboot", "services", "set-environment", "shadow", "shells", "ssh",
|
||||
"ssl", "static", "subgid", "subuid", "sudoers", "sysctl.d", "systemd", "terminfo",
|
||||
"tmpfiles.d", "udev", "udisks2", "UPower", "vconsole.conf", "X11", "zfs", "zinputrc",
|
||||
"zoneinfo", "zprofile", "zshenv", "zshrc")
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("attempted to open unexpected file %q", name))
|
||||
panic(fmt.Sprintf("attempted to read unexpected directory %q", name))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stubNixOS) Paths() hst.Paths {
|
||||
return hst.Paths{
|
||||
SharePath: m("/tmp/hakurei.1971"),
|
||||
RuntimePath: m("/run/user/1971"),
|
||||
RunDirPath: m("/run/user/1971/hakurei"),
|
||||
func (k *stubNixOS) tempdir() string { return "/tmp/" }
|
||||
|
||||
func (k *stubNixOS) evalSymlinks(path string) (string, error) {
|
||||
switch path {
|
||||
case "/run/user/1971":
|
||||
return "/run/user/1971", nil
|
||||
case "/tmp/hakurei.0":
|
||||
return "/tmp/hakurei.0", nil
|
||||
case "/run/dbus":
|
||||
return "/run/dbus", nil
|
||||
case "/dev/kvm":
|
||||
return "/dev/kvm", nil
|
||||
case "/etc/":
|
||||
return "/etc/", nil
|
||||
case "/bin":
|
||||
return "/bin", nil
|
||||
case "/boot":
|
||||
return "/boot", nil
|
||||
case "/home":
|
||||
return "/home", nil
|
||||
case "/lib":
|
||||
return "/lib", nil
|
||||
case "/lib64":
|
||||
return "/lib64", nil
|
||||
case "/nix":
|
||||
return "/nix", nil
|
||||
case "/root":
|
||||
return "/root", nil
|
||||
case "/run":
|
||||
return "/run", nil
|
||||
case "/srv":
|
||||
return "/srv", nil
|
||||
case "/sys":
|
||||
return "/sys", nil
|
||||
case "/usr":
|
||||
return "/usr", nil
|
||||
case "/var":
|
||||
return "/var", nil
|
||||
case "/dev/dri":
|
||||
return "/dev/dri", nil
|
||||
case "/usr/bin/":
|
||||
return "/usr/bin/", nil
|
||||
case "/nix/store":
|
||||
return "/nix/store", nil
|
||||
case "/run/current-system":
|
||||
return "/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-nixos-system-satori-25.05.99999999.aaaaaaa", nil
|
||||
case "/sys/block":
|
||||
return "/sys/block", nil
|
||||
case "/sys/bus":
|
||||
return "/sys/bus", nil
|
||||
case "/sys/class":
|
||||
return "/sys/class", nil
|
||||
case "/sys/dev":
|
||||
return "/sys/dev", nil
|
||||
case "/sys/devices":
|
||||
return "/sys/devices", nil
|
||||
case "/run/opengl-driver":
|
||||
return "/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-graphics-drivers", nil
|
||||
case "/var/lib/persist/module/hakurei/0/1":
|
||||
return "/var/lib/persist/module/hakurei/0/1", nil
|
||||
default:
|
||||
panic(fmt.Sprintf("attempted to evaluate unexpected path %q", path))
|
||||
}
|
||||
}
|
||||
|
||||
func (k *stubNixOS) lookPath(file string) (string, error) {
|
||||
if k.lookPathErr != nil {
|
||||
if err, ok := k.lookPathErr[file]; ok {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
switch file {
|
||||
case "zsh":
|
||||
return "/run/current-system/sw/bin/zsh", nil
|
||||
default:
|
||||
panic(fmt.Sprintf("attempted to look up unexpected executable %q", file))
|
||||
}
|
||||
}
|
||||
|
||||
func (k *stubNixOS) lookupGroupId(name string) (string, error) {
|
||||
switch name {
|
||||
case "video":
|
||||
return "26", nil
|
||||
default:
|
||||
return "", user.UnknownGroupError(name)
|
||||
}
|
||||
}
|
||||
|
||||
func (k *stubNixOS) cmdOutput(cmd *exec.Cmd) ([]byte, error) {
|
||||
switch cmd.Path {
|
||||
case "/proc/nonexistent/hsu":
|
||||
return []byte{'0'}, nil
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected cmd %#v", cmd))
|
||||
}
|
||||
}
|
||||
|
||||
func (k *stubNixOS) overflowUid() int { return 65534 }
|
||||
func (k *stubNixOS) overflowGid() int { return 65534 }
|
||||
|
||||
func (k *stubNixOS) mustHsuPath() string { return "/proc/nonexistent/hsu" }
|
||||
|
||||
func (k *stubNixOS) fatalf(format string, v ...any) { panic(fmt.Sprintf(format, v...)) }
|
||||
|
||||
func (k *stubNixOS) isVerbose() bool { return true }
|
||||
func (k *stubNixOS) verbose(v ...any) { log.Print(v...) }
|
||||
func (k *stubNixOS) verbosef(format string, v ...any) { log.Printf(format, v...) }
|
||||
|
@ -1,4 +1,4 @@
|
||||
package app_test
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -13,9 +13,7 @@ import (
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/internal/sys"
|
||||
"hakurei.app/system"
|
||||
"hakurei.app/system/acl"
|
||||
"hakurei.app/system/dbus"
|
||||
@ -24,7 +22,7 @@ import (
|
||||
func TestApp(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
os sys.State
|
||||
k syscallDispatcher
|
||||
config *hst.Config
|
||||
id state.ID
|
||||
wantSys *system.I
|
||||
@ -40,11 +38,11 @@ func TestApp(t *testing.T) {
|
||||
0xb9, 0xa6, 0x07, 0xac,
|
||||
},
|
||||
system.New(context.TODO(), 1000000).
|
||||
Ensure("/tmp/hakurei.1971", 0711).
|
||||
Ensure("/tmp/hakurei.1971/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime", acl.Execute).
|
||||
Ensure("/tmp/hakurei.1971/runtime/0", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime/0", acl.Read, acl.Write, acl.Execute).
|
||||
Ensure("/tmp/hakurei.1971/tmpdir", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/tmpdir", acl.Execute).
|
||||
Ensure("/tmp/hakurei.1971/tmpdir/0", 01700).UpdatePermType(system.User, "/tmp/hakurei.1971/tmpdir/0", acl.Read, acl.Write, acl.Execute),
|
||||
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).
|
||||
Ensure("/tmp/hakurei.0/tmpdir", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir", acl.Execute).
|
||||
Ensure("/tmp/hakurei.0/tmpdir/0", 01700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir/0", acl.Read, acl.Write, acl.Execute),
|
||||
&container.Params{
|
||||
Dir: m("/home/chronos"),
|
||||
Path: m("/run/current-system/sw/bin/zsh"),
|
||||
@ -71,8 +69,8 @@ func TestApp(t *testing.T) {
|
||||
Tmpfs(m("/run/dbus"), 8192, 0755).
|
||||
Remount(m("/dev/"), syscall.MS_RDONLY).
|
||||
Tmpfs(m("/run/user/"), 4096, 0755).
|
||||
Bind(m("/tmp/hakurei.1971/runtime/0"), m("/run/user/65534"), container.BindWritable).
|
||||
Bind(m("/tmp/hakurei.1971/tmpdir/0"), m("/tmp/"), container.BindWritable).
|
||||
Bind(m("/tmp/hakurei.0/runtime/0"), m("/run/user/65534"), container.BindWritable).
|
||||
Bind(m("/tmp/hakurei.0/tmpdir/0"), m("/tmp/"), container.BindWritable).
|
||||
Place(m("/etc/passwd"), []byte("chronos:x:65534:65534:Hakurei:/home/chronos:/run/current-system/sw/bin/zsh\n")).
|
||||
Place(m("/etc/group"), []byte("hakurei:x:65534:\n")).
|
||||
Remount(m("/"), syscall.MS_RDONLY),
|
||||
@ -132,19 +130,19 @@ func TestApp(t *testing.T) {
|
||||
0x9b, 0x64, 0xce, 0x7c,
|
||||
},
|
||||
system.New(context.TODO(), 1000009).
|
||||
Ensure("/tmp/hakurei.1971", 0711).
|
||||
Ensure("/tmp/hakurei.1971/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime", acl.Execute).
|
||||
Ensure("/tmp/hakurei.1971/runtime/9", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime/9", acl.Read, acl.Write, acl.Execute).
|
||||
Ensure("/tmp/hakurei.1971/tmpdir", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/tmpdir", acl.Execute).
|
||||
Ensure("/tmp/hakurei.1971/tmpdir/9", 01700).UpdatePermType(system.User, "/tmp/hakurei.1971/tmpdir/9", acl.Read, acl.Write, acl.Execute).
|
||||
Ephemeral(system.Process, "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c", 0711).
|
||||
Wayland(new(*os.File), "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", "/run/user/1971/wayland-0", "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c").
|
||||
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).
|
||||
Ensure("/tmp/hakurei.0/tmpdir", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir", acl.Execute).
|
||||
Ensure("/tmp/hakurei.0/tmpdir/9", 01700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir/9", acl.Read, acl.Write, acl.Execute).
|
||||
Ephemeral(system.Process, "/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c", 0711).
|
||||
Wayland(new(*os.File), "/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/wayland", "/run/user/1971/wayland-0", "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c").
|
||||
Ensure("/run/user/1971/hakurei", 0700).UpdatePermType(system.User, "/run/user/1971/hakurei", acl.Execute).
|
||||
Ensure("/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
|
||||
Ephemeral(system.Process, "/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c", 0700).UpdatePermType(system.Process, "/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c", acl.Execute).
|
||||
Link("/run/user/1971/pulse/native", "/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c/pulse").
|
||||
CopyFile(new([]byte), "/home/ophestra/xdg/config/pulse/cookie", 256, 256).
|
||||
MustProxyDBus("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/bus", &dbus.Config{
|
||||
MustProxyDBus("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/bus", &dbus.Config{
|
||||
Talk: []string{
|
||||
"org.freedesktop.Notifications",
|
||||
"org.freedesktop.FileManager1",
|
||||
@ -166,7 +164,7 @@ func TestApp(t *testing.T) {
|
||||
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*",
|
||||
},
|
||||
Filter: true,
|
||||
}, "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", &dbus.Config{
|
||||
}, "/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/system_bus_socket", &dbus.Config{
|
||||
Talk: []string{
|
||||
"org.bluez",
|
||||
"org.freedesktop.Avahi",
|
||||
@ -174,8 +172,8 @@ func TestApp(t *testing.T) {
|
||||
},
|
||||
Filter: true,
|
||||
}).
|
||||
UpdatePerm("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/bus", acl.Read, acl.Write).
|
||||
UpdatePerm("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", acl.Read, acl.Write),
|
||||
UpdatePerm("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/bus", acl.Read, acl.Write).
|
||||
UpdatePerm("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/system_bus_socket", acl.Read, acl.Write),
|
||||
&container.Params{
|
||||
Dir: m("/home/chronos"),
|
||||
Path: m("/run/current-system/sw/bin/zsh"),
|
||||
@ -208,15 +206,15 @@ func TestApp(t *testing.T) {
|
||||
Tmpfs(m("/run/dbus"), 8192, 0755).
|
||||
Remount(m("/dev/"), syscall.MS_RDONLY).
|
||||
Tmpfs(m("/run/user/"), 4096, 0755).
|
||||
Bind(m("/tmp/hakurei.1971/runtime/9"), m("/run/user/65534"), container.BindWritable).
|
||||
Bind(m("/tmp/hakurei.1971/tmpdir/9"), m("/tmp/"), container.BindWritable).
|
||||
Bind(m("/tmp/hakurei.0/runtime/9"), m("/run/user/65534"), container.BindWritable).
|
||||
Bind(m("/tmp/hakurei.0/tmpdir/9"), m("/tmp/"), container.BindWritable).
|
||||
Place(m("/etc/passwd"), []byte("chronos:x:65534:65534:Hakurei:/home/chronos:/run/current-system/sw/bin/zsh\n")).
|
||||
Place(m("/etc/group"), []byte("hakurei:x:65534:\n")).
|
||||
Bind(m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"), m("/run/user/65534/wayland-0"), 0).
|
||||
Bind(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/wayland"), m("/run/user/65534/wayland-0"), 0).
|
||||
Bind(m("/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c/pulse"), m("/run/user/65534/pulse/native"), 0).
|
||||
Place(m(hst.Tmp+"/pulse-cookie"), nil).
|
||||
Bind(m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/bus"), m("/run/user/65534/bus"), 0).
|
||||
Bind(m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket"), m("/run/dbus/system_bus_socket"), 0).
|
||||
Bind(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/bus"), m("/run/user/65534/bus"), 0).
|
||||
Bind(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/system_bus_socket"), m("/run/dbus/system_bus_socket"), 0).
|
||||
Remount(m("/"), syscall.MS_RDONLY),
|
||||
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyDevel,
|
||||
HostNet: true,
|
||||
@ -283,19 +281,19 @@ func TestApp(t *testing.T) {
|
||||
0xb4, 0x6e, 0xb5, 0xc1,
|
||||
},
|
||||
system.New(context.TODO(), 1000001).
|
||||
Ensure("/tmp/hakurei.1971", 0711).
|
||||
Ensure("/tmp/hakurei.1971/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime", acl.Execute).
|
||||
Ensure("/tmp/hakurei.1971/runtime/1", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime/1", acl.Read, acl.Write, acl.Execute).
|
||||
Ensure("/tmp/hakurei.1971/tmpdir", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/tmpdir", acl.Execute).
|
||||
Ensure("/tmp/hakurei.1971/tmpdir/1", 01700).UpdatePermType(system.User, "/tmp/hakurei.1971/tmpdir/1", acl.Read, acl.Write, acl.Execute).
|
||||
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).
|
||||
Ensure("/tmp/hakurei.0/tmpdir", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir", acl.Execute).
|
||||
Ensure("/tmp/hakurei.0/tmpdir/1", 01700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir/1", acl.Read, acl.Write, acl.Execute).
|
||||
Ensure("/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).
|
||||
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).
|
||||
Ephemeral(system.Process, "/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1", 0711).
|
||||
MustProxyDBus("/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", &dbus.Config{
|
||||
Ephemeral(system.Process, "/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1", 0711).
|
||||
MustProxyDBus("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus", &dbus.Config{
|
||||
Talk: []string{
|
||||
"org.freedesktop.FileManager1", "org.freedesktop.Notifications",
|
||||
"org.freedesktop.ScreenSaver", "org.freedesktop.secrets",
|
||||
@ -308,7 +306,7 @@ func TestApp(t *testing.T) {
|
||||
},
|
||||
Call: map[string]string{}, Broadcast: map[string]string{},
|
||||
Filter: true,
|
||||
}, "/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", &dbus.Config{
|
||||
}, "/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", &dbus.Config{
|
||||
Talk: []string{
|
||||
"org.bluez",
|
||||
"org.freedesktop.Avahi",
|
||||
@ -316,8 +314,8 @@ func TestApp(t *testing.T) {
|
||||
},
|
||||
Filter: true,
|
||||
}).
|
||||
UpdatePerm("/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", acl.Read, acl.Write).
|
||||
UpdatePerm("/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", acl.Read, acl.Write),
|
||||
UpdatePerm("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus", acl.Read, acl.Write).
|
||||
UpdatePerm("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", acl.Read, acl.Write),
|
||||
&container.Params{
|
||||
Uid: 1971,
|
||||
Gid: 100,
|
||||
@ -358,15 +356,15 @@ func TestApp(t *testing.T) {
|
||||
Bind(m("/var/lib/persist/module/hakurei/0/1"), m("/var/lib/persist/module/hakurei/0/1"), container.BindWritable|container.BindEnsure).
|
||||
Remount(m("/dev/"), syscall.MS_RDONLY).
|
||||
Tmpfs(m("/run/user/"), 4096, 0755).
|
||||
Bind(m("/tmp/hakurei.1971/runtime/1"), m("/run/user/1971"), container.BindWritable).
|
||||
Bind(m("/tmp/hakurei.1971/tmpdir/1"), m("/tmp/"), container.BindWritable).
|
||||
Bind(m("/tmp/hakurei.0/runtime/1"), m("/run/user/1971"), container.BindWritable).
|
||||
Bind(m("/tmp/hakurei.0/tmpdir/1"), m("/tmp/"), container.BindWritable).
|
||||
Place(m("/etc/passwd"), []byte("u0_a1:x:1971:100:Hakurei:/var/lib/persist/module/hakurei/0/1:/run/current-system/sw/bin/zsh\n")).
|
||||
Place(m("/etc/group"), []byte("hakurei:x:100:\n")).
|
||||
Bind(m("/run/user/1971/wayland-0"), m("/run/user/1971/wayland-0"), 0).
|
||||
Bind(m("/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1/pulse"), m("/run/user/1971/pulse/native"), 0).
|
||||
Place(m(hst.Tmp+"/pulse-cookie"), nil).
|
||||
Bind(m("/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus"), m("/run/user/1971/bus"), 0).
|
||||
Bind(m("/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket"), m("/run/dbus/system_bus_socket"), 0).
|
||||
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus"), m("/run/user/1971/bus"), 0).
|
||||
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket"), m("/run/dbus/system_bus_socket"), 0).
|
||||
Remount(m("/"), syscall.MS_RDONLY),
|
||||
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyTTY | seccomp.PresetDenyDevel,
|
||||
HostNet: true,
|
||||
@ -378,7 +376,8 @@ func TestApp(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Run("finalise", func(t *testing.T) {
|
||||
sys, params, err := app.FinaliseIParams(t.Context(), tc.os, tc.config, &tc.id)
|
||||
seal := outcome{syscallDispatcher: tc.k, id: &stringPair[state.ID]{tc.id, tc.id.String()}}
|
||||
err := seal.finalise(t.Context(), tc.config)
|
||||
if err != nil {
|
||||
if s, ok := container.GetErrorMessage(err); !ok {
|
||||
t.Fatalf("Seal: error = %v", err)
|
||||
@ -388,14 +387,14 @@ func TestApp(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("sys", func(t *testing.T) {
|
||||
if !sys.Equal(tc.wantSys) {
|
||||
t.Errorf("Seal: sys = %#v, want %#v", sys, tc.wantSys)
|
||||
if !seal.sys.Equal(tc.wantSys) {
|
||||
t.Errorf("Seal: sys = %#v, want %#v", seal.sys, tc.wantSys)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("params", func(t *testing.T) {
|
||||
if !reflect.DeepEqual(params, tc.wantParams) {
|
||||
t.Errorf("seal: params =\n%s\n, want\n%s", mustMarshal(params), mustMarshal(tc.wantParams))
|
||||
if !reflect.DeepEqual(seal.container, tc.wantParams) {
|
||||
t.Errorf("seal: container =\n%s\n, want\n%s", mustMarshal(seal.container), mustMarshal(tc.wantParams))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/sys"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
@ -20,7 +19,13 @@ const preallocateOpsCount = 1 << 5
|
||||
|
||||
// newContainer initialises [container.Params] via [hst.ContainerConfig].
|
||||
// Note that remaining container setup must be queued by the caller.
|
||||
func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid *int) (*container.Params, map[string]string, error) {
|
||||
func newContainer(
|
||||
k syscallDispatcher,
|
||||
s *hst.ContainerConfig,
|
||||
prefix string,
|
||||
sc *hst.Paths,
|
||||
uid, gid *int,
|
||||
) (*container.Params, map[string]string, error) {
|
||||
if s == nil {
|
||||
return nil, nil, newWithMessage("invalid container configuration")
|
||||
}
|
||||
@ -38,9 +43,7 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
|
||||
ForwardCancel: s.WaitDelay >= 0,
|
||||
}
|
||||
|
||||
as := &hst.ApplyState{
|
||||
AutoEtcPrefix: prefix,
|
||||
}
|
||||
as := &hst.ApplyState{AutoEtcPrefix: prefix}
|
||||
{
|
||||
ops := make(container.Ops, 0, preallocateOpsCount+len(s.Filesystem))
|
||||
params.Ops = &ops
|
||||
@ -65,13 +68,13 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
|
||||
}
|
||||
|
||||
if s.MapRealUID {
|
||||
params.Uid = os.Getuid()
|
||||
params.Uid = k.getuid()
|
||||
*uid = params.Uid
|
||||
params.Gid = os.Getgid()
|
||||
params.Gid = k.getgid()
|
||||
*gid = params.Gid
|
||||
} else {
|
||||
*uid = container.OverflowUid()
|
||||
*gid = container.OverflowGid()
|
||||
*uid = k.overflowUid()
|
||||
*gid = k.overflowGid()
|
||||
}
|
||||
|
||||
filesystem := s.Filesystem
|
||||
@ -107,7 +110,6 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
|
||||
to warn about issues in custom configuration; it is NOT a security feature
|
||||
and should not be treated as such, ALWAYS be careful with what you bind */
|
||||
var hidePaths []string
|
||||
sc := os.Paths()
|
||||
hidePaths = append(hidePaths, sc.RuntimePath.String(), sc.SharePath.String())
|
||||
_, systemBusAddr := dbus.Address()
|
||||
if entries, err := dbus.Parse([]byte(systemBusAddr)); err != nil {
|
||||
@ -124,11 +126,11 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
|
||||
// get parent dir of socket
|
||||
dir := path.Dir(pair[1])
|
||||
if dir == "." || dir == container.FHSRoot {
|
||||
os.Printf("dbus socket %q is in an unusual location", pair[1])
|
||||
k.verbosef("dbus socket %q is in an unusual location", pair[1])
|
||||
}
|
||||
hidePaths = append(hidePaths, dir)
|
||||
} else {
|
||||
os.Printf("dbus socket %q is not absolute", pair[1])
|
||||
k.verbosef("dbus socket %q is not absolute", pair[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -136,7 +138,7 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
|
||||
}
|
||||
hidePathMatch := make([]bool, len(hidePaths))
|
||||
for i := range hidePaths {
|
||||
if err := evalSymlinks(os, &hidePaths[i]); err != nil {
|
||||
if err := evalSymlinks(k, &hidePaths[i]); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
@ -155,7 +157,7 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
|
||||
// AutoRootOp is a collection of many BindMountOp internally
|
||||
var autoRootEntries []fs.DirEntry
|
||||
if autoroot != nil {
|
||||
if d, err := os.ReadDir(autoroot.Source.String()); err != nil {
|
||||
if d, err := k.readdir(autoroot.Source.String()); err != nil {
|
||||
return nil, nil, err
|
||||
} else {
|
||||
// autoroot counter
|
||||
@ -191,7 +193,7 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
|
||||
}
|
||||
|
||||
hidePathSourceEval[i] = [2]string{a.String(), a.String()}
|
||||
if err := evalSymlinks(os, &hidePathSourceEval[i][0]); err != nil {
|
||||
if err := evalSymlinks(k, &hidePathSourceEval[i][0]); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
@ -207,7 +209,7 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
|
||||
return nil, nil, err
|
||||
} else if ok {
|
||||
hidePathMatch[i] = true
|
||||
os.Printf("hiding path %q from %q", hidePaths[i], p[1])
|
||||
k.verbosef("hiding path %q from %q", hidePaths[i], p[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -238,12 +240,13 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
|
||||
return params, maps.Clone(s.Env), nil
|
||||
}
|
||||
|
||||
func evalSymlinks(os sys.State, v *string) error {
|
||||
if p, err := os.EvalSymlinks(*v); err != nil {
|
||||
// evalSymlinks calls syscallDispatcher.evalSymlinks but discards errors unwrapping to [fs.ErrNotExist].
|
||||
func evalSymlinks(k syscallDispatcher, v *string) error {
|
||||
if p, err := k.evalSymlinks(*v); err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
os.Printf("path %q does not yet exist", *v)
|
||||
k.verbosef("path %q does not yet exist", *v)
|
||||
} else {
|
||||
*v = p
|
||||
}
|
||||
|
99
internal/app/dispatcher.go
Normal file
99
internal/app/dispatcher.go
Normal file
@ -0,0 +1,99 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
|
||||
// syscallDispatcher provides methods that make state-dependent system calls as part of their behaviour.
|
||||
type syscallDispatcher interface {
|
||||
// new starts a goroutine with a new instance of syscallDispatcher.
|
||||
// A syscallDispatcher must never be used in any goroutine other than the one owning it,
|
||||
// just synchronising access is not enough, as this is for test instrumentation.
|
||||
new(f func(k syscallDispatcher))
|
||||
|
||||
// getuid provides [os.Getuid].
|
||||
getuid() int
|
||||
// getgid provides [os.Getgid].
|
||||
getgid() int
|
||||
// lookupEnv provides [os.LookupEnv].
|
||||
lookupEnv(key string) (string, bool)
|
||||
// stat provides [os.Stat].
|
||||
stat(name string) (os.FileInfo, error)
|
||||
// readdir provides [os.ReadDir].
|
||||
readdir(name string) ([]os.DirEntry, error)
|
||||
// tempdir provides [os.TempDir].
|
||||
tempdir() string
|
||||
|
||||
// evalSymlinks provides [filepath.EvalSymlinks].
|
||||
evalSymlinks(path string) (string, error)
|
||||
|
||||
// lookPath provides exec.LookPath.
|
||||
lookPath(file string) (string, error)
|
||||
|
||||
// lookupGroupId calls [user.LookupGroup] and returns the Gid field of the resulting [user.Group] struct.
|
||||
lookupGroupId(name string) (string, error)
|
||||
|
||||
// cmdOutput provides the Output method of [exec.Cmd].
|
||||
cmdOutput(cmd *exec.Cmd) ([]byte, error)
|
||||
|
||||
// overflowUid provides [container.OverflowUid].
|
||||
overflowUid() int
|
||||
// overflowGid provides [container.OverflowGid].
|
||||
overflowGid() int
|
||||
|
||||
// mustHsuPath provides [internal.MustHsuPath].
|
||||
mustHsuPath() string
|
||||
|
||||
// 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.
|
||||
type direct struct{}
|
||||
|
||||
func (k direct) new(f func(k syscallDispatcher)) { go f(k) }
|
||||
|
||||
func (direct) getuid() int { return os.Getuid() }
|
||||
func (direct) getgid() int { return os.Getgid() }
|
||||
func (direct) lookupEnv(key string) (string, bool) { return os.LookupEnv(key) }
|
||||
func (direct) stat(name string) (os.FileInfo, error) { return os.Stat(name) }
|
||||
func (direct) readdir(name string) ([]os.DirEntry, error) { return os.ReadDir(name) }
|
||||
func (direct) tempdir() string { return os.TempDir() }
|
||||
|
||||
func (direct) evalSymlinks(path string) (string, error) { return filepath.EvalSymlinks(path) }
|
||||
|
||||
func (direct) lookPath(file string) (string, error) { return exec.LookPath(file) }
|
||||
|
||||
func (direct) lookupGroupId(name string) (gid string, err error) {
|
||||
var group *user.Group
|
||||
group, err = user.LookupGroup(name)
|
||||
if group != nil {
|
||||
gid = group.Gid
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
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) mustHsuPath() string { 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...) }
|
@ -1,16 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/internal/sys"
|
||||
"hakurei.app/system"
|
||||
)
|
||||
|
||||
func FinaliseIParams(ctx context.Context, k sys.State, config *hst.Config, id *state.ID) (*system.I, *container.Params, error) {
|
||||
seal := outcome{id: &stringPair[state.ID]{*id, id.String()}}
|
||||
return seal.sys, seal.container, seal.finalise(ctx, k, config)
|
||||
}
|
@ -21,7 +21,6 @@ import (
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/internal/hlog"
|
||||
"hakurei.app/internal/sys"
|
||||
"hakurei.app/system"
|
||||
"hakurei.app/system/acl"
|
||||
"hakurei.app/system/dbus"
|
||||
@ -54,8 +53,9 @@ type outcome struct {
|
||||
container *container.Params
|
||||
env map[string]string
|
||||
sync *os.File
|
||||
active atomic.Bool
|
||||
|
||||
f atomic.Bool
|
||||
syscallDispatcher
|
||||
}
|
||||
|
||||
// shareHost holds optional share directory state that must not be accessed directly
|
||||
@ -120,7 +120,7 @@ type hsuUser struct {
|
||||
username string
|
||||
}
|
||||
|
||||
func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Config) error {
|
||||
func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
|
||||
const (
|
||||
home = "HOME"
|
||||
shell = "SHELL"
|
||||
@ -144,14 +144,13 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
|
||||
// unreachable
|
||||
panic("invalid call to finalise")
|
||||
}
|
||||
if seal.ctx != nil {
|
||||
if k.ctx != nil {
|
||||
// unreachable
|
||||
panic("attempting to finalise twice")
|
||||
}
|
||||
seal.ctx = ctx
|
||||
k.ctx = ctx
|
||||
|
||||
if config == nil {
|
||||
// unreachable
|
||||
return newWithMessage("invalid configuration")
|
||||
}
|
||||
if config.Home == nil {
|
||||
@ -164,7 +163,7 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
|
||||
if err := gob.NewEncoder(ct).Encode(config); err != nil {
|
||||
return &hst.AppError{Step: "encode initial config", Err: err}
|
||||
}
|
||||
seal.ct = ct
|
||||
k.ct = ct
|
||||
}
|
||||
|
||||
// allowed identity range 0 to 9999, this is checked again in hsu
|
||||
@ -172,22 +171,23 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
|
||||
return newWithMessage(fmt.Sprintf("identity %d out of range", config.Identity))
|
||||
}
|
||||
|
||||
seal.user = hsuUser{
|
||||
k.user = hsuUser{
|
||||
identity: newInt(config.Identity),
|
||||
home: config.Home,
|
||||
username: config.Username,
|
||||
}
|
||||
|
||||
if seal.user.username == "" {
|
||||
seal.user.username = "chronos"
|
||||
} else if !isValidUsername(seal.user.username) {
|
||||
return newWithMessage(fmt.Sprintf("invalid user name %q", seal.user.username))
|
||||
hsu := Hsu{k: k}
|
||||
if k.user.username == "" {
|
||||
k.user.username = "chronos"
|
||||
} else if !isValidUsername(k.user.username) {
|
||||
return newWithMessage(fmt.Sprintf("invalid user name %q", k.user.username))
|
||||
}
|
||||
seal.user.uid = newInt(sys.MustUid(k, seal.user.identity.unwrap()))
|
||||
k.user.uid = newInt(HsuUid(hsu.MustID(), k.user.identity.unwrap()))
|
||||
|
||||
seal.user.supp = make([]string, len(config.Groups))
|
||||
k.user.supp = make([]string, len(config.Groups))
|
||||
for i, name := range config.Groups {
|
||||
if g, err := k.LookupGroup(name); err != nil {
|
||||
if gid, err := k.lookupGroupId(name); err != nil {
|
||||
var unknownGroupError user.UnknownGroupError
|
||||
if errors.As(err, &unknownGroupError) {
|
||||
return newWithMessageError(fmt.Sprintf("unknown group %q", name), unknownGroupError)
|
||||
@ -195,7 +195,7 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
|
||||
return &hst.AppError{Step: "look up group by name", Err: err}
|
||||
}
|
||||
} else {
|
||||
seal.user.supp[i] = g.Gid
|
||||
k.user.supp[i] = gid
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,7 +205,7 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
|
||||
|
||||
if config.Shell == nil {
|
||||
config.Shell = container.AbsFHSRoot.Append("bin", "sh")
|
||||
s, _ := k.LookupEnv(shell)
|
||||
s, _ := k.lookupEnv(shell)
|
||||
if a, err := container.NewAbs(s); err == nil {
|
||||
config.Shell = a
|
||||
}
|
||||
@ -214,7 +214,7 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
|
||||
// hsu clears the environment so resolve paths early
|
||||
if config.Path == nil {
|
||||
if len(config.Args) > 0 {
|
||||
if p, err := k.LookPath(config.Args[0]); err != nil {
|
||||
if p, err := k.lookPath(config.Args[0]); err != nil {
|
||||
return &hst.AppError{Step: "look up executable file", Err: err}
|
||||
} else if config.Path, err = container.NewAbs(p); err != nil {
|
||||
return newWithMessageError(err.Error(), err)
|
||||
@ -250,7 +250,7 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
|
||||
|
||||
// hide nscd from container if present
|
||||
nscd := container.AbsFHSVar.Append("run/nscd")
|
||||
if _, err := k.Stat(nscd.String()); !errors.Is(err, fs.ErrNotExist) {
|
||||
if _, err := k.stat(nscd.String()); !errors.Is(err, fs.ErrNotExist) {
|
||||
conf.Filesystem = append(conf.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSEphemeral{Target: nscd}})
|
||||
}
|
||||
|
||||
@ -274,86 +274,89 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
|
||||
return newWithMessage("invalid program path")
|
||||
}
|
||||
|
||||
// TODO(ophestra): revert this after params to shim
|
||||
share := &shareHost{seal: k}
|
||||
copyPaths(k.syscallDispatcher, &share.sc, hsu.MustID())
|
||||
|
||||
var mapuid, mapgid *stringPair[int]
|
||||
{
|
||||
var uid, gid int
|
||||
var err error
|
||||
seal.container, seal.env, err = newContainer(config.Container, k, seal.id.String(), &uid, &gid)
|
||||
seal.waitDelay = config.Container.WaitDelay
|
||||
k.container, k.env, err = newContainer(k, config.Container, k.id.String(), &share.sc, &uid, &gid)
|
||||
k.waitDelay = config.Container.WaitDelay
|
||||
if err != nil {
|
||||
return &hst.AppError{Step: "initialise container configuration", Err: err}
|
||||
}
|
||||
if len(config.Args) == 0 {
|
||||
config.Args = []string{config.Path.String()}
|
||||
}
|
||||
seal.container.Path = config.Path
|
||||
seal.container.Args = config.Args
|
||||
k.container.Path = config.Path
|
||||
k.container.Args = config.Args
|
||||
|
||||
mapuid = newInt(uid)
|
||||
mapgid = newInt(gid)
|
||||
if seal.env == nil {
|
||||
seal.env = make(map[string]string, 1<<6)
|
||||
if k.env == nil {
|
||||
k.env = make(map[string]string, 1<<6)
|
||||
}
|
||||
}
|
||||
|
||||
// inner XDG_RUNTIME_DIR default formatting of `/run/user/%d` as mapped uid
|
||||
innerRuntimeDir := container.AbsFHSRunUser.Append(mapuid.String())
|
||||
seal.env[xdgRuntimeDir] = innerRuntimeDir.String()
|
||||
seal.env[xdgSessionClass] = "user"
|
||||
seal.env[xdgSessionType] = "tty"
|
||||
k.env[xdgRuntimeDir] = innerRuntimeDir.String()
|
||||
k.env[xdgSessionClass] = "user"
|
||||
k.env[xdgSessionType] = "tty"
|
||||
|
||||
share := &shareHost{seal: seal, sc: k.Paths()}
|
||||
seal.runDirPath = share.sc.RunDirPath
|
||||
seal.sys = system.New(seal.ctx, seal.user.uid.unwrap())
|
||||
seal.sys.Ensure(share.sc.SharePath.String(), 0711)
|
||||
k.runDirPath = share.sc.RunDirPath
|
||||
k.sys = system.New(k.ctx, k.user.uid.unwrap())
|
||||
k.sys.Ensure(share.sc.SharePath.String(), 0711)
|
||||
|
||||
{
|
||||
runtimeDir := share.sc.SharePath.Append("runtime")
|
||||
seal.sys.Ensure(runtimeDir.String(), 0700)
|
||||
seal.sys.UpdatePermType(system.User, runtimeDir.String(), acl.Execute)
|
||||
runtimeDirInst := runtimeDir.Append(seal.user.identity.String())
|
||||
seal.sys.Ensure(runtimeDirInst.String(), 0700)
|
||||
seal.sys.UpdatePermType(system.User, runtimeDirInst.String(), acl.Read, acl.Write, acl.Execute)
|
||||
seal.container.Tmpfs(container.AbsFHSRunUser, 1<<12, 0755)
|
||||
seal.container.Bind(runtimeDirInst, innerRuntimeDir, container.BindWritable)
|
||||
k.sys.Ensure(runtimeDir.String(), 0700)
|
||||
k.sys.UpdatePermType(system.User, runtimeDir.String(), acl.Execute)
|
||||
runtimeDirInst := runtimeDir.Append(k.user.identity.String())
|
||||
k.sys.Ensure(runtimeDirInst.String(), 0700)
|
||||
k.sys.UpdatePermType(system.User, runtimeDirInst.String(), acl.Read, acl.Write, acl.Execute)
|
||||
k.container.Tmpfs(container.AbsFHSRunUser, 1<<12, 0755)
|
||||
k.container.Bind(runtimeDirInst, innerRuntimeDir, container.BindWritable)
|
||||
}
|
||||
|
||||
{
|
||||
tmpdir := share.sc.SharePath.Append("tmpdir")
|
||||
seal.sys.Ensure(tmpdir.String(), 0700)
|
||||
seal.sys.UpdatePermType(system.User, tmpdir.String(), acl.Execute)
|
||||
tmpdirInst := tmpdir.Append(seal.user.identity.String())
|
||||
seal.sys.Ensure(tmpdirInst.String(), 01700)
|
||||
seal.sys.UpdatePermType(system.User, tmpdirInst.String(), acl.Read, acl.Write, acl.Execute)
|
||||
k.sys.Ensure(tmpdir.String(), 0700)
|
||||
k.sys.UpdatePermType(system.User, tmpdir.String(), acl.Execute)
|
||||
tmpdirInst := tmpdir.Append(k.user.identity.String())
|
||||
k.sys.Ensure(tmpdirInst.String(), 01700)
|
||||
k.sys.UpdatePermType(system.User, tmpdirInst.String(), acl.Read, acl.Write, acl.Execute)
|
||||
// mount inner /tmp from share so it shares persistence and storage behaviour of host /tmp
|
||||
seal.container.Bind(tmpdirInst, container.AbsFHSTmp, container.BindWritable)
|
||||
k.container.Bind(tmpdirInst, container.AbsFHSTmp, container.BindWritable)
|
||||
}
|
||||
|
||||
{
|
||||
username := "chronos"
|
||||
if seal.user.username != "" {
|
||||
username = seal.user.username
|
||||
if k.user.username != "" {
|
||||
username = k.user.username
|
||||
}
|
||||
seal.container.Dir = seal.user.home
|
||||
seal.env["HOME"] = seal.user.home.String()
|
||||
seal.env["USER"] = username
|
||||
seal.env[shell] = config.Shell.String()
|
||||
k.container.Dir = k.user.home
|
||||
k.env["HOME"] = k.user.home.String()
|
||||
k.env["USER"] = username
|
||||
k.env[shell] = config.Shell.String()
|
||||
|
||||
seal.container.Place(container.AbsFHSEtc.Append("passwd"),
|
||||
[]byte(username+":x:"+mapuid.String()+":"+mapgid.String()+":Hakurei:"+seal.user.home.String()+":"+config.Shell.String()+"\n"))
|
||||
seal.container.Place(container.AbsFHSEtc.Append("group"),
|
||||
k.container.Place(container.AbsFHSEtc.Append("passwd"),
|
||||
[]byte(username+":x:"+mapuid.String()+":"+mapgid.String()+":Hakurei:"+k.user.home.String()+":"+config.Shell.String()+"\n"))
|
||||
k.container.Place(container.AbsFHSEtc.Append("group"),
|
||||
[]byte("hakurei:x:"+mapgid.String()+":\n"))
|
||||
}
|
||||
|
||||
// pass TERM for proper terminal I/O in initial process
|
||||
if t, ok := k.LookupEnv(term); ok {
|
||||
seal.env[term] = t
|
||||
if t, ok := k.lookupEnv(term); ok {
|
||||
k.env[term] = t
|
||||
}
|
||||
|
||||
if config.Enablements.Unwrap()&system.EWayland != 0 {
|
||||
// outer wayland socket (usually `/run/user/%d/wayland-%d`)
|
||||
var socketPath *container.Absolute
|
||||
if name, ok := k.LookupEnv(wayland.WaylandDisplay); !ok {
|
||||
if name, ok := k.lookupEnv(wayland.WaylandDisplay); !ok {
|
||||
hlog.Verbose(wayland.WaylandDisplay + " is not set, assuming " + wayland.FallbackName)
|
||||
socketPath = share.sc.RuntimePath.Append(wayland.FallbackName)
|
||||
} else if a, err := container.NewAbs(name); err != nil {
|
||||
@ -363,28 +366,28 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
|
||||
}
|
||||
|
||||
innerPath := innerRuntimeDir.Append(wayland.FallbackName)
|
||||
seal.env[wayland.WaylandDisplay] = wayland.FallbackName
|
||||
k.env[wayland.WaylandDisplay] = wayland.FallbackName
|
||||
|
||||
if !config.DirectWayland { // set up security-context-v1
|
||||
appID := config.ID
|
||||
if appID == "" {
|
||||
// use instance ID in case app id is not set
|
||||
appID = "app.hakurei." + seal.id.String()
|
||||
appID = "app.hakurei." + k.id.String()
|
||||
}
|
||||
// downstream socket paths
|
||||
outerPath := share.instance().Append("wayland")
|
||||
seal.sys.Wayland(&seal.sync, outerPath.String(), socketPath.String(), appID, seal.id.String())
|
||||
seal.container.Bind(outerPath, innerPath, 0)
|
||||
k.sys.Wayland(&k.sync, outerPath.String(), socketPath.String(), appID, k.id.String())
|
||||
k.container.Bind(outerPath, innerPath, 0)
|
||||
} else { // bind mount wayland socket (insecure)
|
||||
hlog.Verbose("direct wayland access, PROCEED WITH CAUTION")
|
||||
share.ensureRuntimeDir()
|
||||
seal.container.Bind(socketPath, innerPath, 0)
|
||||
seal.sys.UpdatePermType(system.EWayland, socketPath.String(), acl.Read, acl.Write, acl.Execute)
|
||||
k.container.Bind(socketPath, innerPath, 0)
|
||||
k.sys.UpdatePermType(system.EWayland, socketPath.String(), acl.Read, acl.Write, acl.Execute)
|
||||
}
|
||||
}
|
||||
|
||||
if config.Enablements.Unwrap()&system.EX11 != 0 {
|
||||
if d, ok := k.LookupEnv(display); !ok {
|
||||
if d, ok := k.lookupEnv(display); !ok {
|
||||
return newWithMessage("DISPLAY is not set")
|
||||
} else {
|
||||
socketDir := container.AbsFHSTmp.Append(".X11-unix")
|
||||
@ -402,21 +405,21 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
|
||||
}
|
||||
}
|
||||
if socketPath != nil {
|
||||
if _, err := k.Stat(socketPath.String()); err != nil {
|
||||
if _, err := k.stat(socketPath.String()); err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
return &hst.AppError{Step: fmt.Sprintf("access X11 socket %q", socketPath), Err: err}
|
||||
}
|
||||
} else {
|
||||
seal.sys.UpdatePermType(system.EX11, socketPath.String(), acl.Read, acl.Write, acl.Execute)
|
||||
k.sys.UpdatePermType(system.EX11, socketPath.String(), acl.Read, acl.Write, acl.Execute)
|
||||
if !config.Container.HostAbstract {
|
||||
d = "unix:" + socketPath.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
seal.sys.ChangeHosts("#" + seal.user.uid.String())
|
||||
seal.env[display] = d
|
||||
seal.container.Bind(socketDir, socketDir, 0)
|
||||
k.sys.ChangeHosts("#" + k.user.uid.String())
|
||||
k.env[display] = d
|
||||
k.container.Bind(socketDir, socketDir, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -426,14 +429,14 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
|
||||
// PulseAudio socket (usually `/run/user/%d/pulse/native`)
|
||||
pulseSocket := pulseRuntimeDir.Append("native")
|
||||
|
||||
if _, err := k.Stat(pulseRuntimeDir.String()); err != nil {
|
||||
if _, err := k.stat(pulseRuntimeDir.String()); err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
return &hst.AppError{Step: fmt.Sprintf("access PulseAudio directory %q", pulseRuntimeDir), Err: err}
|
||||
}
|
||||
return newWithMessage(fmt.Sprintf("PulseAudio directory %q not found", pulseRuntimeDir))
|
||||
}
|
||||
|
||||
if s, err := k.Stat(pulseSocket.String()); err != nil {
|
||||
if s, err := k.stat(pulseSocket.String()); err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
return &hst.AppError{Step: fmt.Sprintf("access PulseAudio socket %q", pulseSocket), Err: err}
|
||||
}
|
||||
@ -447,9 +450,9 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
|
||||
// hard link pulse socket into target-executable share
|
||||
innerPulseRuntimeDir := share.runtime().Append("pulse")
|
||||
innerPulseSocket := innerRuntimeDir.Append("pulse", "native")
|
||||
seal.sys.Link(pulseSocket.String(), innerPulseRuntimeDir.String())
|
||||
seal.container.Bind(innerPulseRuntimeDir, innerPulseSocket, 0)
|
||||
seal.env[pulseServer] = "unix:" + innerPulseSocket.String()
|
||||
k.sys.Link(pulseSocket.String(), innerPulseRuntimeDir.String())
|
||||
k.container.Bind(innerPulseRuntimeDir, innerPulseSocket, 0)
|
||||
k.env[pulseServer] = "unix:" + innerPulseSocket.String()
|
||||
|
||||
// publish current user's pulse cookie for target user
|
||||
var paCookiePath *container.Absolute
|
||||
@ -457,7 +460,7 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
|
||||
const paLocateStep = "locate PulseAudio cookie"
|
||||
|
||||
// from environment
|
||||
if p, ok := k.LookupEnv(pulseCookie); ok {
|
||||
if p, ok := k.lookupEnv(pulseCookie); ok {
|
||||
if a, err := container.NewAbs(p); err != nil {
|
||||
return &hst.AppError{Step: paLocateStep, Err: err}
|
||||
} else {
|
||||
@ -468,14 +471,14 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
|
||||
}
|
||||
|
||||
// $HOME/.pulse-cookie
|
||||
if p, ok := k.LookupEnv(home); ok {
|
||||
if p, ok := k.lookupEnv(home); ok {
|
||||
if a, err := container.NewAbs(p); err != nil {
|
||||
return &hst.AppError{Step: paLocateStep, Err: err}
|
||||
} else {
|
||||
paCookiePath = a.Append(".pulse-cookie")
|
||||
}
|
||||
|
||||
if s, err := k.Stat(paCookiePath.String()); err != nil {
|
||||
if s, err := k.stat(paCookiePath.String()); err != nil {
|
||||
paCookiePath = nil
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
return &hst.AppError{Step: "access PulseAudio cookie", Err: err}
|
||||
@ -489,13 +492,13 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
|
||||
}
|
||||
|
||||
// $XDG_CONFIG_HOME/pulse/cookie
|
||||
if p, ok := k.LookupEnv(xdgConfigHome); ok {
|
||||
if p, ok := k.lookupEnv(xdgConfigHome); ok {
|
||||
if a, err := container.NewAbs(p); err != nil {
|
||||
return &hst.AppError{Step: paLocateStep, Err: err}
|
||||
} else {
|
||||
paCookiePath = a.Append("pulse", "cookie")
|
||||
}
|
||||
if s, err := k.Stat(paCookiePath.String()); err != nil {
|
||||
if s, err := k.stat(paCookiePath.String()); err != nil {
|
||||
paCookiePath = nil
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
return &hst.AppError{Step: "access PulseAudio cookie", Err: err}
|
||||
@ -512,10 +515,10 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
|
||||
|
||||
if paCookiePath != nil {
|
||||
innerDst := hst.AbsTmp.Append("/pulse-cookie")
|
||||
seal.env[pulseCookie] = innerDst.String()
|
||||
k.env[pulseCookie] = innerDst.String()
|
||||
var payload *[]byte
|
||||
seal.container.PlaceP(innerDst, &payload)
|
||||
seal.sys.CopyFile(payload, paCookiePath.String(), 256, 256)
|
||||
k.container.PlaceP(innerDst, &payload)
|
||||
k.sys.CopyFile(payload, paCookiePath.String(), 256, 256)
|
||||
} else {
|
||||
hlog.Verbose("cannot locate PulseAudio cookie (tried " +
|
||||
"$PULSE_COOKIE, " +
|
||||
@ -534,30 +537,30 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
|
||||
sessionPath, systemPath := share.instance().Append("bus"), share.instance().Append("system_bus_socket")
|
||||
|
||||
// configure dbus proxy
|
||||
if f, err := seal.sys.ProxyDBus(
|
||||
if f, err := k.sys.ProxyDBus(
|
||||
config.SessionBus, config.SystemBus,
|
||||
sessionPath.String(), systemPath.String(),
|
||||
); err != nil {
|
||||
return err
|
||||
} else {
|
||||
seal.dbusMsg = f
|
||||
k.dbusMsg = f
|
||||
}
|
||||
|
||||
// share proxy sockets
|
||||
sessionInner := innerRuntimeDir.Append("bus")
|
||||
seal.env[dbusSessionBusAddress] = "unix:path=" + sessionInner.String()
|
||||
seal.container.Bind(sessionPath, sessionInner, 0)
|
||||
seal.sys.UpdatePerm(sessionPath.String(), acl.Read, acl.Write)
|
||||
k.env[dbusSessionBusAddress] = "unix:path=" + sessionInner.String()
|
||||
k.container.Bind(sessionPath, sessionInner, 0)
|
||||
k.sys.UpdatePerm(sessionPath.String(), acl.Read, acl.Write)
|
||||
if config.SystemBus != nil {
|
||||
systemInner := container.AbsFHSRun.Append("dbus/system_bus_socket")
|
||||
seal.env[dbusSystemBusAddress] = "unix:path=" + systemInner.String()
|
||||
seal.container.Bind(systemPath, systemInner, 0)
|
||||
seal.sys.UpdatePerm(systemPath.String(), acl.Read, acl.Write)
|
||||
k.env[dbusSystemBusAddress] = "unix:path=" + systemInner.String()
|
||||
k.container.Bind(systemPath, systemInner, 0)
|
||||
k.sys.UpdatePerm(systemPath.String(), acl.Read, acl.Write)
|
||||
}
|
||||
}
|
||||
|
||||
// mount root read-only as the final setup Op
|
||||
seal.container.Remount(container.AbsFHSRoot, syscall.MS_RDONLY)
|
||||
k.container.Remount(container.AbsFHSRoot, syscall.MS_RDONLY)
|
||||
|
||||
// append ExtraPerms last
|
||||
for _, p := range config.ExtraPerms {
|
||||
@ -566,7 +569,7 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
|
||||
}
|
||||
|
||||
if p.Ensure {
|
||||
seal.sys.Ensure(p.Path.String(), 0700)
|
||||
k.sys.Ensure(p.Path.String(), 0700)
|
||||
}
|
||||
|
||||
perms := make(acl.Perms, 0, 3)
|
||||
@ -579,23 +582,23 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
|
||||
if p.Execute {
|
||||
perms = append(perms, acl.Execute)
|
||||
}
|
||||
seal.sys.UpdatePermType(system.User, p.Path.String(), perms...)
|
||||
k.sys.UpdatePermType(system.User, p.Path.String(), perms...)
|
||||
}
|
||||
|
||||
// flatten and sort env for deterministic behaviour
|
||||
seal.container.Env = make([]string, 0, len(seal.env))
|
||||
for k, v := range seal.env {
|
||||
if strings.IndexByte(k, '=') != -1 {
|
||||
k.container.Env = make([]string, 0, len(k.env))
|
||||
for key, value := range k.env {
|
||||
if strings.IndexByte(key, '=') != -1 {
|
||||
return &hst.AppError{Step: "flatten environment", Err: syscall.EINVAL,
|
||||
Msg: fmt.Sprintf("invalid environment variable %s", k)}
|
||||
Msg: fmt.Sprintf("invalid environment variable %s", key)}
|
||||
}
|
||||
seal.container.Env = append(seal.container.Env, k+"="+v)
|
||||
k.container.Env = append(k.container.Env, key+"="+value)
|
||||
}
|
||||
slices.Sort(seal.container.Env)
|
||||
slices.Sort(k.container.Env)
|
||||
|
||||
if hlog.Load() {
|
||||
hlog.Verbosef("created application seal for uid %s (%s) groups: %v, argv: %s, ops: %d",
|
||||
seal.user.uid, seal.user.username, config.Groups, seal.container.Args, len(*seal.container.Ops))
|
||||
k.user.uid, k.user.username, config.Groups, k.container.Args, len(*k.container.Ops))
|
||||
}
|
||||
|
||||
return nil
|
@ -1,4 +1,4 @@
|
||||
package sys
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@ -11,7 +11,6 @@ import (
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
|
||||
@ -20,15 +19,28 @@ type Hsu struct {
|
||||
idOnce sync.Once
|
||||
idErr error
|
||||
id int
|
||||
|
||||
kOnce sync.Once
|
||||
k syscallDispatcher
|
||||
}
|
||||
|
||||
var ErrHsuAccess = errors.New("current user is not in the hsurc file")
|
||||
|
||||
// ensureDispatcher ensures Hsu.k is not nil.
|
||||
func (h *Hsu) ensureDispatcher() {
|
||||
h.kOnce.Do(func() {
|
||||
if h.k == nil {
|
||||
h.k = direct{}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ID returns the current user hsurc identifier. ErrHsuAccess is returned if the current user is not in hsurc.
|
||||
func (h *Hsu) ID() (int, error) {
|
||||
h.ensureDispatcher()
|
||||
h.idOnce.Do(func() {
|
||||
h.id = -1
|
||||
hsuPath := internal.MustHsuPath()
|
||||
hsuPath := h.k.mustHsuPath()
|
||||
|
||||
cmd := exec.Command(hsuPath)
|
||||
cmd.Path = hsuPath
|
||||
@ -41,7 +53,7 @@ func (h *Hsu) ID() (int, error) {
|
||||
)
|
||||
|
||||
const step = "obtain uid from hsu"
|
||||
if p, h.idErr = cmd.Output(); h.idErr == nil {
|
||||
if p, h.idErr = h.k.cmdOutput(cmd); h.idErr == nil {
|
||||
h.id, h.idErr = strconv.Atoi(string(p))
|
||||
if h.idErr != nil {
|
||||
h.idErr = &hst.AppError{Step: step, Err: h.idErr, Msg: "invalid uid string from hsu"}
|
||||
@ -58,22 +70,14 @@ func (h *Hsu) ID() (int, error) {
|
||||
return h.id, h.idErr
|
||||
}
|
||||
|
||||
func (h *Hsu) Uid(identity int) (int, error) {
|
||||
// MustID calls [Hsu.ID] and terminates on error.
|
||||
func (h *Hsu) MustID() int {
|
||||
id, err := h.ID()
|
||||
if err == nil {
|
||||
return 1000000 + id*10000 + identity, nil
|
||||
}
|
||||
return id, err
|
||||
return id
|
||||
}
|
||||
|
||||
// MustUid calls [State.Uid] and terminates on error.
|
||||
func MustUid(s State, identity int) int {
|
||||
uid, err := s.Uid(identity)
|
||||
if err == nil {
|
||||
return uid
|
||||
}
|
||||
|
||||
const fallback = "cannot obtain uid from setuid wrapper:"
|
||||
const fallback = "cannot retrieve user id from setuid wrapper:"
|
||||
if errors.Is(err, ErrHsuAccess) {
|
||||
hlog.Verbose("*"+fallback, err)
|
||||
os.Exit(1)
|
||||
@ -86,3 +90,7 @@ func MustUid(s State, identity int) int {
|
||||
return -0xdeadbeef
|
||||
}
|
||||
}
|
||||
|
||||
// HsuUid returns target uid for the stable hsu uid format.
|
||||
// No bounds check is performed, a value retrieved from hsu is expected.
|
||||
func HsuUid(id, identity int) int { return 1000000 + id*10000 + identity }
|
36
internal/app/paths.go
Normal file
36
internal/app/paths.go
Normal file
@ -0,0 +1,36 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
)
|
||||
|
||||
// CopyPaths populates a [hst.Paths] struct.
|
||||
func CopyPaths(v *hst.Paths, userid int) { copyPaths(direct{}, v, userid) }
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
v.SharePath = v.TempDir.Append("hakurei." + strconv.Itoa(userid))
|
||||
k.verbosef("process share directory at %q", v.SharePath)
|
||||
|
||||
r, _ := k.lookupEnv(xdgRuntimeDir)
|
||||
if a, err := container.NewAbs(r); err != 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")
|
||||
}
|
||||
k.verbosef("runtime directory at %q", v.RunDirPath)
|
||||
}
|
@ -33,12 +33,12 @@ type mainState struct {
|
||||
// Time is nil if no process was ever created.
|
||||
Time *time.Time
|
||||
|
||||
seal *outcome
|
||||
store state.Store
|
||||
cancel context.CancelFunc
|
||||
cmd *exec.Cmd
|
||||
cmdWait chan error
|
||||
|
||||
k *outcome
|
||||
uintptr
|
||||
}
|
||||
|
||||
@ -79,13 +79,13 @@ func (ms mainState) beforeExit(isFault bool) {
|
||||
waitDone := make(chan struct{})
|
||||
// TODO(ophestra): enforce this limit early so it does not have to be done twice
|
||||
shimTimeoutCompensated := shimWaitTimeout
|
||||
if ms.seal.waitDelay > MaxShimWaitDelay {
|
||||
if ms.k.waitDelay > MaxShimWaitDelay {
|
||||
shimTimeoutCompensated += MaxShimWaitDelay
|
||||
} else {
|
||||
shimTimeoutCompensated += ms.seal.waitDelay
|
||||
shimTimeoutCompensated += ms.k.waitDelay
|
||||
}
|
||||
// this ties waitDone to ctx with the additional compensated timeout duration
|
||||
go func() { <-ms.seal.ctx.Done(); time.Sleep(shimTimeoutCompensated); close(waitDone) }()
|
||||
go func() { <-ms.k.ctx.Done(); time.Sleep(shimTimeoutCompensated); close(waitDone) }()
|
||||
|
||||
select {
|
||||
case err := <-ms.cmdWait:
|
||||
@ -128,20 +128,20 @@ func (ms mainState) beforeExit(isFault bool) {
|
||||
}
|
||||
|
||||
hlog.Resume()
|
||||
if ms.seal.sync != nil {
|
||||
if err := ms.seal.sync.Close(); err != nil {
|
||||
if ms.k.sync != nil {
|
||||
if err := ms.k.sync.Close(); err != nil {
|
||||
perror(err, "close wayland security context")
|
||||
}
|
||||
}
|
||||
if ms.seal.dbusMsg != nil {
|
||||
ms.seal.dbusMsg()
|
||||
if ms.k.dbusMsg != nil {
|
||||
ms.k.dbusMsg()
|
||||
}
|
||||
}
|
||||
|
||||
if ms.uintptr&mainNeedsRevert != 0 {
|
||||
if ok, err := ms.store.Do(ms.seal.user.identity.unwrap(), func(c state.Cursor) {
|
||||
if ok, err := ms.store.Do(ms.k.user.identity.unwrap(), func(c state.Cursor) {
|
||||
if ms.uintptr&mainNeedsDestroy != 0 {
|
||||
if err := c.Destroy(ms.seal.id.unwrap()); err != nil {
|
||||
if err := c.Destroy(ms.k.id.unwrap()); err != nil {
|
||||
perror(err, "destroy state entry")
|
||||
}
|
||||
}
|
||||
@ -151,7 +151,7 @@ func (ms mainState) beforeExit(isFault bool) {
|
||||
// it is impossible to continue from this point;
|
||||
// revert per-process state here to limit damage
|
||||
ec := system.Process
|
||||
if revertErr := ms.seal.sys.Revert((*system.Criteria)(&ec)); revertErr != nil {
|
||||
if revertErr := ms.k.sys.Revert((*system.Criteria)(&ec)); revertErr != nil {
|
||||
var joinError interface {
|
||||
Unwrap() []error
|
||||
error
|
||||
@ -189,7 +189,7 @@ func (ms mainState) beforeExit(isFault bool) {
|
||||
}
|
||||
}
|
||||
|
||||
if err = ms.seal.sys.Revert((*system.Criteria)(&ec)); err != nil {
|
||||
if err = ms.k.sys.Revert((*system.Criteria)(&ec)); err != nil {
|
||||
perror(err, "revert system setup")
|
||||
}
|
||||
}
|
||||
@ -219,8 +219,8 @@ func (ms mainState) fatal(fallback string, ferr error) {
|
||||
}
|
||||
|
||||
// main carries out outcome and terminates. main does not return.
|
||||
func (seal *outcome) main() {
|
||||
if !seal.f.CompareAndSwap(false, true) {
|
||||
func (k *outcome) main() {
|
||||
if !k.active.CompareAndSwap(false, true) {
|
||||
panic("outcome: attempted to run twice")
|
||||
}
|
||||
|
||||
@ -228,15 +228,15 @@ func (seal *outcome) main() {
|
||||
hsuPath := internal.MustHsuPath()
|
||||
|
||||
// ms.beforeExit required beyond this point
|
||||
ms := &mainState{seal: seal}
|
||||
ms := &mainState{k: k}
|
||||
|
||||
if err := seal.sys.Commit(); err != nil {
|
||||
if err := k.sys.Commit(); err != nil {
|
||||
ms.fatal("cannot commit system setup:", err)
|
||||
}
|
||||
ms.uintptr |= mainNeedsRevert
|
||||
ms.store = state.NewMulti(seal.runDirPath.String())
|
||||
ms.store = state.NewMulti(k.runDirPath.String())
|
||||
|
||||
ctx, cancel := context.WithCancel(seal.ctx)
|
||||
ctx, cancel := context.WithCancel(k.ctx)
|
||||
defer cancel()
|
||||
ms.cancel = cancel
|
||||
|
||||
@ -255,14 +255,14 @@ func (seal *outcome) main() {
|
||||
// passed through to shim by hsu
|
||||
shimEnv + "=" + strconv.Itoa(fd),
|
||||
// interpreted by hsu
|
||||
"HAKUREI_IDENTITY=" + seal.user.identity.String(),
|
||||
"HAKUREI_IDENTITY=" + k.user.identity.String(),
|
||||
}
|
||||
}
|
||||
|
||||
if len(seal.user.supp) > 0 {
|
||||
hlog.Verbosef("attaching supplementary group ids %s", seal.user.supp)
|
||||
if len(k.user.supp) > 0 {
|
||||
hlog.Verbosef("attaching supplementary group ids %s", k.user.supp)
|
||||
// interpreted by hsu
|
||||
ms.cmd.Env = append(ms.cmd.Env, "HAKUREI_GROUPS="+strings.Join(seal.user.supp, " "))
|
||||
ms.cmd.Env = append(ms.cmd.Env, "HAKUREI_GROUPS="+strings.Join(k.user.supp, " "))
|
||||
}
|
||||
|
||||
hlog.Verbosef("setuid helper at %s", hsuPath)
|
||||
@ -284,8 +284,8 @@ func (seal *outcome) main() {
|
||||
go func() {
|
||||
setupErr <- e.Encode(&shimParams{
|
||||
os.Getpid(),
|
||||
seal.waitDelay,
|
||||
seal.container,
|
||||
k.waitDelay,
|
||||
k.container,
|
||||
hlog.Load(),
|
||||
})
|
||||
}()
|
||||
@ -302,12 +302,12 @@ func (seal *outcome) main() {
|
||||
}
|
||||
|
||||
// shim accepted setup payload, create process state
|
||||
if ok, err := ms.store.Do(seal.user.identity.unwrap(), func(c state.Cursor) {
|
||||
if ok, err := ms.store.Do(k.user.identity.unwrap(), func(c state.Cursor) {
|
||||
if err := c.Save(&state.State{
|
||||
ID: seal.id.unwrap(),
|
||||
ID: k.id.unwrap(),
|
||||
PID: ms.cmd.Process.Pid,
|
||||
Time: *ms.Time,
|
||||
}, seal.ct); err != nil {
|
||||
}, k.ct); err != nil {
|
||||
ms.fatal("cannot save state entry:", err)
|
||||
}
|
||||
}); err != nil {
|
||||
|
@ -1,76 +0,0 @@
|
||||
// Package sys wraps OS interaction library functions.
|
||||
package sys
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"log"
|
||||
"os/user"
|
||||
"strconv"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
|
||||
// State provides safe interaction with operating system state.
|
||||
type State interface {
|
||||
// Getuid provides [os.Getuid].
|
||||
Getuid() int
|
||||
// Getgid provides [os.Getgid].
|
||||
Getgid() int
|
||||
// LookupEnv provides [os.LookupEnv].
|
||||
LookupEnv(key string) (string, bool)
|
||||
// TempDir provides [os.TempDir].
|
||||
TempDir() string
|
||||
// LookPath provides exec.LookPath.
|
||||
LookPath(file string) (string, error)
|
||||
// MustExecutable provides [container.MustExecutable].
|
||||
MustExecutable() string
|
||||
// LookupGroup provides [user.LookupGroup].
|
||||
LookupGroup(name string) (*user.Group, error)
|
||||
// ReadDir provides [os.ReadDir].
|
||||
ReadDir(name string) ([]fs.DirEntry, error)
|
||||
// Stat provides [os.Stat].
|
||||
Stat(name string) (fs.FileInfo, error)
|
||||
// Open provides [os.Open].
|
||||
Open(name string) (fs.File, error)
|
||||
// EvalSymlinks provides filepath.EvalSymlinks.
|
||||
EvalSymlinks(path string) (string, error)
|
||||
// Exit provides [os.Exit].
|
||||
Exit(code int)
|
||||
|
||||
Println(v ...any)
|
||||
Printf(format string, v ...any)
|
||||
|
||||
// Paths returns a populated [hst.Paths] struct.
|
||||
Paths() hst.Paths
|
||||
// Uid invokes hsu and returns target uid.
|
||||
// Any errors returned by Uid is already wrapped [hlog.BaseError].
|
||||
Uid(identity int) (int, error)
|
||||
}
|
||||
|
||||
// MustGetUserID obtains user id from hsu by querying uid of identity 0.
|
||||
func MustGetUserID(os State) int { return (MustUid(os, 0) / 10000) - 100 }
|
||||
|
||||
// CopyPaths is a generic implementation of [hst.Paths].
|
||||
func CopyPaths(os State, v *hst.Paths, userid int) {
|
||||
if tempDir, err := container.NewAbs(os.TempDir()); err != nil {
|
||||
log.Fatalf("invalid TMPDIR: %v", err)
|
||||
} else {
|
||||
v.TempDir = tempDir
|
||||
}
|
||||
|
||||
v.SharePath = v.TempDir.Append("hakurei." + strconv.Itoa(userid))
|
||||
hlog.Verbosef("process share directory at %q", v.SharePath)
|
||||
|
||||
r, _ := os.LookupEnv(xdgRuntimeDir)
|
||||
if a, err := container.NewAbs(r); err != 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")
|
||||
}
|
||||
hlog.Verbosef("runtime directory at %q", v.RunDirPath)
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
|
||||
// Std implements System using the standard library.
|
||||
type Std struct {
|
||||
paths hst.Paths
|
||||
pathsOnce sync.Once
|
||||
Hsu
|
||||
}
|
||||
|
||||
func (s *Std) Getuid() int { return os.Getuid() }
|
||||
func (s *Std) Getgid() int { return os.Getgid() }
|
||||
func (s *Std) LookupEnv(key string) (string, bool) { return os.LookupEnv(key) }
|
||||
func (s *Std) TempDir() string { return os.TempDir() }
|
||||
func (s *Std) LookPath(file string) (string, error) { return exec.LookPath(file) }
|
||||
func (s *Std) MustExecutable() string { return container.MustExecutable() }
|
||||
func (s *Std) LookupGroup(name string) (*user.Group, error) { return user.LookupGroup(name) }
|
||||
func (s *Std) ReadDir(name string) ([]os.DirEntry, error) { return os.ReadDir(name) }
|
||||
func (s *Std) Stat(name string) (fs.FileInfo, error) { return os.Stat(name) }
|
||||
func (s *Std) Open(name string) (fs.File, error) { return os.Open(name) }
|
||||
func (s *Std) EvalSymlinks(path string) (string, error) { return filepath.EvalSymlinks(path) }
|
||||
func (s *Std) Exit(code int) { internal.Exit(code) }
|
||||
func (s *Std) Println(v ...any) { hlog.Verbose(v...) }
|
||||
func (s *Std) Printf(format string, v ...any) { hlog.Verbosef(format, v...) }
|
||||
|
||||
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
||||
|
||||
func (s *Std) Paths() hst.Paths {
|
||||
s.pathsOnce.Do(func() { CopyPaths(s, &s.paths, MustGetUserID(s)) })
|
||||
return s.paths
|
||||
}
|
@ -100,7 +100,7 @@ print(machine.fail("sudo -u alice -i hsu"))
|
||||
# Verify hsu fault behaviour:
|
||||
if denyOutput != "hsu: uid 1001 is not in the hsurc file\n":
|
||||
raise Exception(f"unexpected deny output:\n{denyOutput}")
|
||||
if denyOutputVerbose != "hsu: uid 1001 is not in the hsurc file\nhakurei: *cannot obtain uid from setuid wrapper: current user is not in the hsurc file\n":
|
||||
if denyOutputVerbose != "hsu: uid 1001 is not in the hsurc file\nhakurei: *cannot retrieve user id from setuid wrapper: current user is not in the hsurc file\n":
|
||||
raise Exception(f"unexpected deny verbose output:\n{denyOutputVerbose}")
|
||||
|
||||
# Verify timeout behaviour:
|
||||
|
Loading…
x
Reference in New Issue
Block a user