Compare commits
26 Commits
093e30c788
...
v0.3.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
ccc0d98bd7
|
|||
|
a3fd05765e
|
|||
|
c538df7daa
|
|||
|
44e5aa1a36
|
|||
|
cf0e7d8c27
|
|||
|
130add21e5
|
|||
|
5ec4045e24
|
|||
|
be2075f169
|
|||
|
e9fb1d7be5
|
|||
|
dafe9f8efc
|
|||
|
96dd7abd80
|
|||
|
d5fb179012
|
|||
|
462863e290
|
|||
|
2786611b88
|
|||
|
791a1dfa55
|
|||
|
564db6863b
|
|||
|
87781c7658
|
|||
|
0c38fb7b6a
|
|||
|
357cfcddee
|
|||
|
6bf245cf1b
|
|||
|
c8eeb4a4d1
|
|||
|
5785714b64
|
|||
|
422efcf258
|
|||
|
104eeecf65
|
|||
|
bf856f06e5
|
|||
|
1931b54600
|
@@ -14,6 +14,7 @@ import (
|
|||||||
_ "unsafe" // for go:linkname
|
_ "unsafe" // for go:linkname
|
||||||
|
|
||||||
"hakurei.app/command"
|
"hakurei.app/command"
|
||||||
|
"hakurei.app/container"
|
||||||
"hakurei.app/container/check"
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/container/fhs"
|
"hakurei.app/container/fhs"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
@@ -91,7 +92,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
|||||||
|
|
||||||
flagPrivateRuntime, flagPrivateTmpdir bool
|
flagPrivateRuntime, flagPrivateTmpdir bool
|
||||||
|
|
||||||
flagWayland, flagX11, flagDBus, flagPulse bool
|
flagWayland, flagX11, flagDBus, flagPipeWire, flagPulse bool
|
||||||
)
|
)
|
||||||
|
|
||||||
c.NewCommand("run", "Configure and start a permissive container", func(args []string) error {
|
c.NewCommand("run", "Configure and start a permissive container", func(args []string) error {
|
||||||
@@ -146,8 +147,8 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
|||||||
if flagDBus {
|
if flagDBus {
|
||||||
et |= hst.EDBus
|
et |= hst.EDBus
|
||||||
}
|
}
|
||||||
if flagPulse {
|
if flagPipeWire || flagPulse {
|
||||||
et |= hst.EPulse
|
et |= hst.EPipeWire
|
||||||
}
|
}
|
||||||
|
|
||||||
config := &hst.Config{
|
config := &hst.Config{
|
||||||
@@ -186,6 +187,14 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
|||||||
}})
|
}})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// start pipewire-pulse: this most likely exists on host if PipeWire is available
|
||||||
|
if flagPulse {
|
||||||
|
config.Container.Filesystem = append(config.Container.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSDaemon{
|
||||||
|
Target: fhs.AbsRunUser.Append(strconv.Itoa(container.OverflowUid(msg)), "pulse/native"),
|
||||||
|
Exec: shell, Args: []string{"-lc", "exec pipewire-pulse"},
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
|
||||||
config.Container.Filesystem = append(config.Container.Filesystem,
|
config.Container.Filesystem = append(config.Container.Filesystem,
|
||||||
// opportunistically bind kvm
|
// opportunistically bind kvm
|
||||||
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
|
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
|
||||||
@@ -297,8 +306,10 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
|||||||
"Enable direct connection to X11").
|
"Enable direct connection to X11").
|
||||||
Flag(&flagDBus, "dbus", command.BoolFlag(false),
|
Flag(&flagDBus, "dbus", command.BoolFlag(false),
|
||||||
"Enable proxied connection to D-Bus").
|
"Enable proxied connection to D-Bus").
|
||||||
|
Flag(&flagPipeWire, "pipewire", command.BoolFlag(false),
|
||||||
|
"Enable connection to PipeWire via SecurityContext").
|
||||||
Flag(&flagPulse, "pulse", command.BoolFlag(false),
|
Flag(&flagPulse, "pulse", command.BoolFlag(false),
|
||||||
"Enable direct connection to PulseAudio")
|
"Enable PulseAudio compatibility daemon")
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ Commands:
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"run", []string{"run", "-h"}, `
|
"run", []string{"run", "-h"}, `
|
||||||
Usage: hakurei run [-h | --help] [--dbus-config <value>] [--dbus-system <value>] [--mpris] [--dbus-log] [--id <value>] [-a <int>] [-g <value>] [-d <value>] [-u <value>] [--private-runtime] [--private-tmpdir] [--wayland] [-X] [--dbus] [--pulse] COMMAND [OPTIONS]
|
Usage: hakurei run [-h | --help] [--dbus-config <value>] [--dbus-system <value>] [--mpris] [--dbus-log] [--id <value>] [-a <int>] [-g <value>] [-d <value>] [-u <value>] [--private-runtime] [--private-tmpdir] [--wayland] [-X] [--dbus] [--pipewire] [--pulse] COMMAND [OPTIONS]
|
||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
-X Enable direct connection to X11
|
-X Enable direct connection to X11
|
||||||
@@ -58,12 +58,14 @@ Flags:
|
|||||||
Reverse-DNS style Application identifier, leave empty to inherit instance identifier
|
Reverse-DNS style Application identifier, leave empty to inherit instance identifier
|
||||||
-mpris
|
-mpris
|
||||||
Allow owning MPRIS D-Bus path, has no effect if custom config is available
|
Allow owning MPRIS D-Bus path, has no effect if custom config is available
|
||||||
|
-pipewire
|
||||||
|
Enable connection to PipeWire via SecurityContext
|
||||||
-private-runtime
|
-private-runtime
|
||||||
Do not share XDG_RUNTIME_DIR between containers under the same identity
|
Do not share XDG_RUNTIME_DIR between containers under the same identity
|
||||||
-private-tmpdir
|
-private-tmpdir
|
||||||
Do not share TMPDIR between containers under the same identity
|
Do not share TMPDIR between containers under the same identity
|
||||||
-pulse
|
-pulse
|
||||||
Enable direct connection to PulseAudio
|
Enable PulseAudio compatibility daemon
|
||||||
-u string
|
-u string
|
||||||
Passwd user name within sandbox (default "chronos")
|
Passwd user name within sandbox (default "chronos")
|
||||||
-wayland
|
-wayland
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ var (
|
|||||||
PID: 0xbeef,
|
PID: 0xbeef,
|
||||||
ShimPID: 0xcafe,
|
ShimPID: 0xcafe,
|
||||||
Config: &hst.Config{
|
Config: &hst.Config{
|
||||||
Enablements: hst.NewEnablements(hst.EWayland | hst.EPulse),
|
Enablements: hst.NewEnablements(hst.EWayland | hst.EPipeWire),
|
||||||
Identity: 1,
|
Identity: 1,
|
||||||
Container: &hst.ContainerConfig{
|
Container: &hst.ContainerConfig{
|
||||||
Shell: check.MustAbs("/bin/sh"),
|
Shell: check.MustAbs("/bin/sh"),
|
||||||
@@ -62,7 +62,7 @@ func TestPrintShowInstance(t *testing.T) {
|
|||||||
{"nil", nil, nil, false, false, "Error: invalid configuration!\n\n", false},
|
{"nil", nil, nil, false, false, "Error: invalid configuration!\n\n", false},
|
||||||
{"config", nil, hst.Template(), false, false, `App
|
{"config", nil, hst.Template(), false, false, `App
|
||||||
Identity: 9 (org.chromium.Chromium)
|
Identity: 9 (org.chromium.Chromium)
|
||||||
Enablements: wayland, dbus, pulseaudio
|
Enablements: wayland, dbus, pipewire
|
||||||
Groups: video, dialout, plugdev
|
Groups: video, dialout, plugdev
|
||||||
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir
|
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir
|
||||||
Home: /data/data/org.chromium.Chromium
|
Home: /data/data/org.chromium.Chromium
|
||||||
@@ -159,7 +159,7 @@ Session bus
|
|||||||
|
|
||||||
App
|
App
|
||||||
Identity: 9 (org.chromium.Chromium)
|
Identity: 9 (org.chromium.Chromium)
|
||||||
Enablements: wayland, dbus, pulseaudio
|
Enablements: wayland, dbus, pipewire
|
||||||
Groups: video, dialout, plugdev
|
Groups: video, dialout, plugdev
|
||||||
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir
|
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir
|
||||||
Home: /data/data/org.chromium.Chromium
|
Home: /data/data/org.chromium.Chromium
|
||||||
@@ -215,7 +215,7 @@ App
|
|||||||
"enablements": {
|
"enablements": {
|
||||||
"wayland": true,
|
"wayland": true,
|
||||||
"dbus": true,
|
"dbus": true,
|
||||||
"pulse": true
|
"pipewire": true
|
||||||
},
|
},
|
||||||
"session_bus": {
|
"session_bus": {
|
||||||
"see": null,
|
"see": null,
|
||||||
@@ -366,7 +366,7 @@ App
|
|||||||
"enablements": {
|
"enablements": {
|
||||||
"wayland": true,
|
"wayland": true,
|
||||||
"dbus": true,
|
"dbus": true,
|
||||||
"pulse": true
|
"pipewire": true
|
||||||
},
|
},
|
||||||
"session_bus": {
|
"session_bus": {
|
||||||
"see": null,
|
"see": null,
|
||||||
@@ -564,7 +564,7 @@ func TestPrintPs(t *testing.T) {
|
|||||||
"enablements": {
|
"enablements": {
|
||||||
"wayland": true,
|
"wayland": true,
|
||||||
"dbus": true,
|
"dbus": true,
|
||||||
"pulse": true
|
"pipewire": true
|
||||||
},
|
},
|
||||||
"session_bus": {
|
"session_bus": {
|
||||||
"see": null,
|
"see": null,
|
||||||
@@ -715,7 +715,7 @@ func TestPrintPs(t *testing.T) {
|
|||||||
"shim_pid": 51966,
|
"shim_pid": 51966,
|
||||||
"enablements": {
|
"enablements": {
|
||||||
"wayland": true,
|
"wayland": true,
|
||||||
"pulse": true
|
"pipewire": true
|
||||||
},
|
},
|
||||||
"identity": 1,
|
"identity": 1,
|
||||||
"groups": null,
|
"groups": null,
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
allow_wayland ? true,
|
allow_wayland ? true,
|
||||||
allow_x11 ? false,
|
allow_x11 ? false,
|
||||||
allow_dbus ? true,
|
allow_dbus ? true,
|
||||||
allow_pulse ? true,
|
allow_audio ? true,
|
||||||
gpu ? allow_wayland || allow_x11,
|
gpu ? allow_wayland || allow_x11,
|
||||||
}:
|
}:
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ let
|
|||||||
wayland = allow_wayland;
|
wayland = allow_wayland;
|
||||||
x11 = allow_x11;
|
x11 = allow_x11;
|
||||||
dbus = allow_dbus;
|
dbus = allow_dbus;
|
||||||
pulse = allow_pulse;
|
pipewire = allow_audio;
|
||||||
};
|
};
|
||||||
|
|
||||||
mesa = if gpu then mesaWrappers else null;
|
mesa = if gpu then mesaWrappers else null;
|
||||||
|
|||||||
@@ -90,13 +90,13 @@ wait_for_window("hakurei@machine-foot")
|
|||||||
machine.send_chars("clear; wayland-info && touch /tmp/success-client\n")
|
machine.send_chars("clear; wayland-info && touch /tmp/success-client\n")
|
||||||
machine.wait_for_file("/tmp/hakurei.0/tmpdir/2/success-client")
|
machine.wait_for_file("/tmp/hakurei.0/tmpdir/2/success-client")
|
||||||
collect_state_ui("app_wayland")
|
collect_state_ui("app_wayland")
|
||||||
check_state("foot", {"wayland": True, "dbus": True, "pulse": True})
|
check_state("foot", {"wayland": True, "dbus": True, "pipewire": True})
|
||||||
# Verify acl on XDG_RUNTIME_DIR:
|
# Verify acl on XDG_RUNTIME_DIR:
|
||||||
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 10002"))
|
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10002"))
|
||||||
machine.send_chars("exit\n")
|
machine.send_chars("exit\n")
|
||||||
machine.wait_until_fails("pgrep foot")
|
machine.wait_until_fails("pgrep foot")
|
||||||
# Verify acl cleanup on XDG_RUNTIME_DIR:
|
# Verify acl cleanup on XDG_RUNTIME_DIR:
|
||||||
machine.wait_until_fails("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 10002")
|
machine.wait_until_fails("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10002")
|
||||||
|
|
||||||
# Exit Sway and verify process exit status 0:
|
# Exit Sway and verify process exit status 0:
|
||||||
swaymsg("exit", succeed=False)
|
swaymsg("exit", succeed=False)
|
||||||
@@ -107,4 +107,4 @@ print(machine.succeed("find /tmp/hakurei.0 "
|
|||||||
+ "-path '/tmp/hakurei.0/runtime/*/*' -prune -o "
|
+ "-path '/tmp/hakurei.0/runtime/*/*' -prune -o "
|
||||||
+ "-path '/tmp/hakurei.0/tmpdir/*/*' -prune -o "
|
+ "-path '/tmp/hakurei.0/tmpdir/*/*' -prune -o "
|
||||||
+ "-print"))
|
+ "-print"))
|
||||||
print(machine.succeed("find /run/user/1000/hakurei"))
|
print(machine.fail("ls /run/user/1000/hakurei"))
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ func (e *AutoEtcOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func (e *AutoEtcOp) late(*setupState, syscallDispatcher) error { return nil }
|
||||||
|
|
||||||
func (e *AutoEtcOp) hostPath() *check.Absolute { return fhs.AbsEtc.Append(e.hostRel()) }
|
func (e *AutoEtcOp) hostPath() *check.Absolute { return fhs.AbsEtc.Append(e.hostRel()) }
|
||||||
func (e *AutoEtcOp) hostRel() string { return ".host/" + e.Prefix }
|
func (e *AutoEtcOp) hostRel() string { return ".host/" + e.Prefix }
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ func (r *AutoRootOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func (r *AutoRootOp) late(*setupState, syscallDispatcher) error { return nil }
|
||||||
|
|
||||||
func (r *AutoRootOp) Is(op Op) bool {
|
func (r *AutoRootOp) Is(op Op) bool {
|
||||||
vr, ok := op.(*AutoRootOp)
|
vr, ok := op.(*AutoRootOp)
|
||||||
|
|||||||
@@ -759,7 +759,8 @@ func (k *kstub) checkMsg(msg message.Msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) GetLogger() *log.Logger { panic("unreachable") }
|
func (k *kstub) GetLogger() *log.Logger { panic("unreachable") }
|
||||||
func (k *kstub) IsVerbose() bool { panic("unreachable") }
|
|
||||||
|
func (k *kstub) IsVerbose() bool { k.Helper(); return k.Expects("isVerbose").Ret.(bool) }
|
||||||
|
|
||||||
func (k *kstub) SwapVerbose(verbose bool) bool {
|
func (k *kstub) SwapVerbose(verbose bool) bool {
|
||||||
k.Helper()
|
k.Helper()
|
||||||
|
|||||||
@@ -7,31 +7,36 @@ import (
|
|||||||
|
|
||||||
"hakurei.app/container/check"
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/container/vfs"
|
"hakurei.app/container/vfs"
|
||||||
|
"hakurei.app/message"
|
||||||
)
|
)
|
||||||
|
|
||||||
// messageFromError returns a printable error message for a supported concrete type.
|
// messageFromError returns a printable error message for a supported concrete type.
|
||||||
func messageFromError(err error) (string, bool) {
|
func messageFromError(err error) (m string, ok bool) {
|
||||||
if m, ok := messagePrefixP[MountError]("cannot ", err); ok {
|
if m, ok = messagePrefixP[MountError]("cannot ", err); ok {
|
||||||
return m, ok
|
return
|
||||||
}
|
}
|
||||||
if m, ok := messagePrefixP[os.PathError]("cannot ", err); ok {
|
if m, ok = messagePrefixP[os.PathError]("cannot ", err); ok {
|
||||||
return m, ok
|
return
|
||||||
}
|
}
|
||||||
if m, ok := messagePrefixP[check.AbsoluteError]("", err); ok {
|
if m, ok = messagePrefixP[check.AbsoluteError](zeroString, err); ok {
|
||||||
return m, ok
|
return
|
||||||
}
|
}
|
||||||
if m, ok := messagePrefix[OpRepeatError]("", err); ok {
|
if m, ok = messagePrefix[OpRepeatError](zeroString, err); ok {
|
||||||
return m, ok
|
return
|
||||||
}
|
}
|
||||||
if m, ok := messagePrefix[OpStateError]("", err); ok {
|
if m, ok = messagePrefix[OpStateError](zeroString, err); ok {
|
||||||
return m, ok
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if m, ok := messagePrefixP[vfs.DecoderError]("cannot ", err); ok {
|
if m, ok = messagePrefixP[vfs.DecoderError]("cannot ", err); ok {
|
||||||
return m, ok
|
return
|
||||||
}
|
}
|
||||||
if m, ok := messagePrefix[TmpfsSizeError]("", err); ok {
|
if m, ok = messagePrefix[TmpfsSizeError](zeroString, err); ok {
|
||||||
return m, ok
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if m, ok = message.GetMessage(err); ok {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return zeroString, false
|
return zeroString, false
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
@@ -9,6 +10,8 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
. "syscall"
|
. "syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -18,24 +21,28 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
/* intermediate tmpfs mount point
|
/* intermediateHostPath is the pathname of the intermediate tmpfs mount point.
|
||||||
|
|
||||||
this path might seem like a weird choice, however there are many good reasons to use it:
|
This path might seem like a weird choice, however there are many good reasons to use it:
|
||||||
- the contents of this path is never exposed to the container:
|
- The contents of this path is never exposed to the container:
|
||||||
the tmpfs root established here effectively becomes anonymous after pivot_root
|
The tmpfs root established here effectively becomes anonymous after pivot_root.
|
||||||
- it is safe to assume this path exists and is a directory:
|
- It is safe to assume this path exists and is a directory:
|
||||||
this program will not work correctly without a proper /proc and neither will most others
|
This program will not work correctly without a proper /proc and neither will most others.
|
||||||
- this path belongs to the container init:
|
- This path belongs to the container init:
|
||||||
the container init is not any more privileged or trusted than the rest of the container
|
The container init is not any more privileged or trusted than the rest of the container.
|
||||||
- this path is only accessible by init and root:
|
- This path is only accessible by init and root:
|
||||||
the container init sets SUID_DUMP_DISABLE and terminates if that fails;
|
The container init sets SUID_DUMP_DISABLE and terminates if that fails.
|
||||||
|
|
||||||
it should be noted that none of this should become relevant at any point since the resulting
|
It should be noted that none of this should become relevant at any point since the resulting
|
||||||
intermediate root tmpfs should be effectively anonymous */
|
intermediate root tmpfs should be effectively anonymous. */
|
||||||
intermediateHostPath = fhs.Proc + "self/fd"
|
intermediateHostPath = fhs.Proc + "self/fd"
|
||||||
|
|
||||||
// setup params file descriptor
|
// setupEnv is the name of the environment variable holding the string representation of
|
||||||
|
// the read end file descriptor of the setup params pipe.
|
||||||
setupEnv = "HAKUREI_SETUP"
|
setupEnv = "HAKUREI_SETUP"
|
||||||
|
|
||||||
|
// exitUnexpectedWait4 is the exit code if wait4 returns an unexpected errno.
|
||||||
|
exitUnexpectedWait4 = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -49,6 +56,8 @@ type (
|
|||||||
early(state *setupState, k syscallDispatcher) error
|
early(state *setupState, k syscallDispatcher) error
|
||||||
// apply is called in intermediate root.
|
// apply is called in intermediate root.
|
||||||
apply(state *setupState, k syscallDispatcher) error
|
apply(state *setupState, k syscallDispatcher) error
|
||||||
|
// late is called right before starting the initial process.
|
||||||
|
late(state *setupState, k syscallDispatcher) error
|
||||||
|
|
||||||
// prefix returns a log message prefix, and whether this Op prints no identifying message on its own.
|
// prefix returns a log message prefix, and whether this Op prints no identifying message on its own.
|
||||||
prefix() (string, bool)
|
prefix() (string, bool)
|
||||||
@@ -61,11 +70,29 @@ type (
|
|||||||
// setupState persists context between Ops.
|
// setupState persists context between Ops.
|
||||||
setupState struct {
|
setupState struct {
|
||||||
nonrepeatable uintptr
|
nonrepeatable uintptr
|
||||||
|
|
||||||
|
// Whether early reaping has concluded. Must only be accessed in the wait4 loop.
|
||||||
|
processConcluded bool
|
||||||
|
// Process to syscall.WaitStatus populated in the wait4 loop. Freed after early reaping concludes.
|
||||||
|
process map[int]WaitStatus
|
||||||
|
// Synchronises access to process.
|
||||||
|
processMu sync.RWMutex
|
||||||
|
|
||||||
*Params
|
*Params
|
||||||
|
context.Context
|
||||||
message.Msg
|
message.Msg
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// terminated returns whether the specified pid has been reaped, and its
|
||||||
|
// syscall.WaitStatus if it had. This is only usable by [Op].
|
||||||
|
func (state *setupState) terminated(pid int) (wstatus WaitStatus, ok bool) {
|
||||||
|
state.processMu.RLock()
|
||||||
|
wstatus, ok = state.process[pid]
|
||||||
|
state.processMu.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Grow grows the slice Ops points to using [slices.Grow].
|
// Grow grows the slice Ops points to using [slices.Grow].
|
||||||
func (f *Ops) Grow(n int) { *f = slices.Grow(*f, n) }
|
func (f *Ops) Grow(n int) { *f = slices.Grow(*f, n) }
|
||||||
|
|
||||||
@@ -180,7 +207,9 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
k.fatalf(msg, "cannot make / rslave: %v", optionalErrorUnwrap(err))
|
k.fatalf(msg, "cannot make / rslave: %v", optionalErrorUnwrap(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
state := &setupState{Params: ¶ms.Params, Msg: msg}
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
state := &setupState{process: make(map[int]WaitStatus), Params: ¶ms.Params, Msg: msg, Context: ctx}
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
/* early is called right before pivot_root into intermediate root;
|
/* early is called right before pivot_root into intermediate root;
|
||||||
this step is mostly for gathering information that would otherwise be difficult to obtain
|
this step is mostly for gathering information that would otherwise be difficult to obtain
|
||||||
@@ -330,6 +359,97 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
}
|
}
|
||||||
k.umask(oldmask)
|
k.umask(oldmask)
|
||||||
|
|
||||||
|
// winfo represents an exited process from wait4.
|
||||||
|
type winfo struct {
|
||||||
|
wpid int
|
||||||
|
wstatus WaitStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
// info is closed as the wait4 thread terminates
|
||||||
|
// when there are no longer any processes left to reap
|
||||||
|
info := make(chan winfo, 1)
|
||||||
|
|
||||||
|
// whether initial process has started
|
||||||
|
var initialProcessStarted atomic.Bool
|
||||||
|
|
||||||
|
k.new(func(k syscallDispatcher) {
|
||||||
|
k.lockOSThread()
|
||||||
|
|
||||||
|
wait4:
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
wpid = -2
|
||||||
|
wstatus WaitStatus
|
||||||
|
|
||||||
|
// whether initial process has started
|
||||||
|
started bool
|
||||||
|
)
|
||||||
|
|
||||||
|
// keep going until no child process is left
|
||||||
|
for wpid != -1 {
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if wpid != -2 {
|
||||||
|
if !state.processConcluded {
|
||||||
|
state.processMu.Lock()
|
||||||
|
if state.process == nil {
|
||||||
|
// early reaping has already concluded at this point
|
||||||
|
state.processConcluded = true
|
||||||
|
info <- winfo{wpid, wstatus}
|
||||||
|
} else {
|
||||||
|
// initial process has not yet been created, and the
|
||||||
|
// info channel is not yet being received from
|
||||||
|
state.process[wpid] = wstatus
|
||||||
|
}
|
||||||
|
state.processMu.Unlock()
|
||||||
|
} else {
|
||||||
|
info <- winfo{wpid, wstatus}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !started {
|
||||||
|
started = initialProcessStarted.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = EINTR
|
||||||
|
for errors.Is(err, EINTR) {
|
||||||
|
wpid, err = k.wait4(-1, &wstatus, 0, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.Is(err, ECHILD) {
|
||||||
|
k.printf(msg, "unexpected wait4 response: %v", err)
|
||||||
|
} else if !started {
|
||||||
|
// initial process has not yet been reached and all daemons
|
||||||
|
// terminated or none were started in the first place
|
||||||
|
time.Sleep(500 * time.Microsecond)
|
||||||
|
goto wait4
|
||||||
|
}
|
||||||
|
|
||||||
|
close(info)
|
||||||
|
})
|
||||||
|
|
||||||
|
// called right before startup of initial process, all state changes to the
|
||||||
|
// current process is prohibited during late
|
||||||
|
for i, op := range *params.Ops {
|
||||||
|
// ops already checked during early setup
|
||||||
|
if err := op.late(state, k); err != nil {
|
||||||
|
if m, ok := messageFromError(err); ok {
|
||||||
|
k.fatal(msg, m)
|
||||||
|
} else if errors.Is(err, context.DeadlineExceeded) {
|
||||||
|
k.fatalf(msg, "%s deadline exceeded", op.String())
|
||||||
|
} else {
|
||||||
|
k.fatalf(msg, "cannot complete op at index %d: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// early reaping has concluded, this must happen before initial process is created
|
||||||
|
state.processMu.Lock()
|
||||||
|
state.process = nil
|
||||||
|
state.processMu.Unlock()
|
||||||
|
|
||||||
if err := closeSetup(); err != nil {
|
if err := closeSetup(); err != nil {
|
||||||
k.fatalf(msg, "cannot close setup pipe: %v", err)
|
k.fatalf(msg, "cannot close setup pipe: %v", err)
|
||||||
}
|
}
|
||||||
@@ -341,50 +461,11 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
cmd.ExtraFiles = extraFiles
|
cmd.ExtraFiles = extraFiles
|
||||||
cmd.Dir = params.Dir.String()
|
cmd.Dir = params.Dir.String()
|
||||||
|
|
||||||
msg.Verbosef("starting initial program %s", params.Path)
|
msg.Verbosef("starting initial process %s", params.Path)
|
||||||
if err := k.start(cmd); err != nil {
|
if err := k.start(cmd); err != nil {
|
||||||
k.fatalf(msg, "%v", err)
|
k.fatalf(msg, "%v", err)
|
||||||
}
|
}
|
||||||
|
initialProcessStarted.Store(true)
|
||||||
type winfo struct {
|
|
||||||
wpid int
|
|
||||||
wstatus WaitStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
// info is closed as the wait4 thread terminates
|
|
||||||
// when there are no longer any processes left to reap
|
|
||||||
info := make(chan winfo, 1)
|
|
||||||
|
|
||||||
k.new(func(k syscallDispatcher) {
|
|
||||||
k.lockOSThread()
|
|
||||||
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
wpid = -2
|
|
||||||
wstatus WaitStatus
|
|
||||||
)
|
|
||||||
|
|
||||||
// keep going until no child process is left
|
|
||||||
for wpid != -1 {
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if wpid != -2 {
|
|
||||||
info <- winfo{wpid, wstatus}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = EINTR
|
|
||||||
for errors.Is(err, EINTR) {
|
|
||||||
wpid, err = k.wait4(-1, &wstatus, 0, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !errors.Is(err, ECHILD) {
|
|
||||||
k.printf(msg, "unexpected wait4 response: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
close(info)
|
|
||||||
})
|
|
||||||
|
|
||||||
// handle signals to dump withheld messages
|
// handle signals to dump withheld messages
|
||||||
sig := make(chan os.Signal, 2)
|
sig := make(chan os.Signal, 2)
|
||||||
@@ -394,7 +475,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
// closed after residualProcessTimeout has elapsed after initial process death
|
// closed after residualProcessTimeout has elapsed after initial process death
|
||||||
timeout := make(chan struct{})
|
timeout := make(chan struct{})
|
||||||
|
|
||||||
r := 2
|
r := exitUnexpectedWait4
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case s := <-sig:
|
case s := <-sig:
|
||||||
@@ -426,6 +507,9 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if w.wpid == cmd.Process.Pid {
|
if w.wpid == cmd.Process.Pid {
|
||||||
|
// cancel Op context early
|
||||||
|
cancel()
|
||||||
|
|
||||||
// start timeout early
|
// start timeout early
|
||||||
go func() { time.Sleep(params.AdoptWaitDelay); close(timeout) }()
|
go func() { time.Sleep(params.AdoptWaitDelay); close(timeout) }()
|
||||||
|
|
||||||
|
|||||||
@@ -1983,11 +1983,20 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
||||||
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
||||||
call("umask", stub.ExpectArgs{022}, 0, nil),
|
call("umask", stub.ExpectArgs{022}, 0, nil),
|
||||||
|
call("New", stub.ExpectArgs{}, nil, nil),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(13)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(13)}}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.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"}, nil, stub.UniqueError(12)),
|
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, nil, stub.UniqueError(12)),
|
||||||
call("fatalf", stub.ExpectArgs{"%v", []any{stub.UniqueError(12)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"%v", []any{stub.UniqueError(12)}}, nil, nil),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/* wait4 */
|
||||||
|
Tracks: []stub.Expect{{Calls: []stub.Call{
|
||||||
|
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||||
|
|
||||||
|
// this terminates the goroutine at the call, preventing it from leaking while preserving behaviour
|
||||||
|
call("wait4", stub.ExpectArgs{-1, nil, 0, nil, stub.PanicExit}, 0, syscall.ECHILD),
|
||||||
|
}}},
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"lowlastcap signaled cancel forward error", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
{"lowlastcap signaled cancel forward error", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
|
||||||
@@ -2062,10 +2071,10 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
||||||
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
||||||
call("umask", stub.ExpectArgs{022}, 0, nil),
|
call("umask", stub.ExpectArgs{022}, 0, nil),
|
||||||
|
call("New", stub.ExpectArgs{}, nil, nil),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(10)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(10)}}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.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("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
|
||||||
call("New", stub.ExpectArgs{}, nil, nil),
|
|
||||||
call("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- CancelSignal }, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
call("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- CancelSignal }, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
||||||
call("verbose", stub.ExpectArgs{[]any{"forwarding context cancellation"}}, nil, nil),
|
call("verbose", stub.ExpectArgs{[]any{"forwarding context cancellation"}}, nil, nil),
|
||||||
// magicWait4Signal as ret causes wait4 stub to unblock
|
// magicWait4Signal as ret causes wait4 stub to unblock
|
||||||
@@ -2162,10 +2171,10 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
||||||
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
||||||
call("umask", stub.ExpectArgs{022}, 0, nil),
|
call("umask", stub.ExpectArgs{022}, 0, nil),
|
||||||
|
call("New", stub.ExpectArgs{}, nil, nil),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(7)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(7)}}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.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("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
|
||||||
call("New", stub.ExpectArgs{}, nil, nil),
|
|
||||||
call("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- syscall.SIGQUIT }, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
call("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- syscall.SIGQUIT }, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"got %s, forwarding to initial process", []any{"quit"}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"got %s, forwarding to initial process", []any{"quit"}}, nil, nil),
|
||||||
// magicWait4Signal as ret causes wait4 stub to unblock
|
// magicWait4Signal as ret causes wait4 stub to unblock
|
||||||
@@ -2262,10 +2271,10 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
||||||
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
||||||
call("umask", stub.ExpectArgs{022}, 0, nil),
|
call("umask", stub.ExpectArgs{022}, 0, nil),
|
||||||
|
call("New", stub.ExpectArgs{}, nil, nil),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(7)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(7)}}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.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("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
|
||||||
call("New", stub.ExpectArgs{}, nil, nil),
|
|
||||||
call("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- os.Interrupt }, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
call("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- os.Interrupt }, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"got %s", []any{"interrupt"}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"got %s", []any{"interrupt"}}, nil, nil),
|
||||||
call("beforeExit", stub.ExpectArgs{}, nil, nil),
|
call("beforeExit", stub.ExpectArgs{}, nil, nil),
|
||||||
@@ -2353,10 +2362,10 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
||||||
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
||||||
call("umask", stub.ExpectArgs{022}, 0, nil),
|
call("umask", stub.ExpectArgs{022}, 0, nil),
|
||||||
|
call("New", stub.ExpectArgs{}, nil, nil),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(5)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(5)}}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.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("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
|
||||||
call("New", stub.ExpectArgs{}, nil, nil),
|
|
||||||
call("notify", stub.ExpectArgs{nil, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
call("notify", stub.ExpectArgs{nil, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
||||||
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
||||||
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
||||||
@@ -2448,10 +2457,10 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
||||||
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
||||||
call("umask", stub.ExpectArgs{022}, 0, nil),
|
call("umask", stub.ExpectArgs{022}, 0, nil),
|
||||||
|
call("New", stub.ExpectArgs{}, nil, nil),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(3)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(3)}}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.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("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
|
||||||
call("New", stub.ExpectArgs{}, nil, nil),
|
|
||||||
call("notify", stub.ExpectArgs{nil, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
call("notify", stub.ExpectArgs{nil, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
||||||
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
||||||
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
||||||
@@ -2586,10 +2595,10 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil),
|
||||||
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil),
|
||||||
call("umask", stub.ExpectArgs{022}, 0, nil),
|
call("umask", stub.ExpectArgs{022}, 0, nil),
|
||||||
|
call("New", stub.ExpectArgs{}, nil, nil),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(1)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(1)}}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.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("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
|
||||||
call("New", stub.ExpectArgs{}, nil, nil),
|
|
||||||
call("notify", stub.ExpectArgs{nil, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
call("notify", stub.ExpectArgs{nil, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
||||||
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
||||||
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
||||||
@@ -2728,10 +2737,10 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("newFile", stub.ExpectArgs{uintptr(11), "extra file 1"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(11), "extra file 1"}, (*os.File)(nil), nil),
|
||||||
call("newFile", stub.ExpectArgs{uintptr(12), "extra file 2"}, (*os.File)(nil), nil),
|
call("newFile", stub.ExpectArgs{uintptr(12), "extra file 2"}, (*os.File)(nil), nil),
|
||||||
call("umask", stub.ExpectArgs{022}, 0, nil),
|
call("umask", stub.ExpectArgs{022}, 0, nil),
|
||||||
|
call("New", stub.ExpectArgs{}, nil, nil),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(0)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(0)}}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/bin/zsh")}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.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("start", stub.ExpectArgs{"/bin/zsh", []string{"zsh", "-c", "exec vim"}, []string{"DISPLAY=:0"}, "/.hakurei"}, &os.Process{Pid: 0xcafe}, nil),
|
||||||
call("New", stub.ExpectArgs{}, nil, nil),
|
|
||||||
call("notify", stub.ExpectArgs{nil, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
call("notify", stub.ExpectArgs{nil, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil),
|
||||||
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
||||||
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ func (b *BindMountOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
}
|
}
|
||||||
return k.bindMount(state, source, target, flags)
|
return k.bindMount(state, source, target, flags)
|
||||||
}
|
}
|
||||||
|
func (b *BindMountOp) late(*setupState, syscallDispatcher) error { return nil }
|
||||||
|
|
||||||
func (b *BindMountOp) Is(op Op) bool {
|
func (b *BindMountOp) Is(op Op) bool {
|
||||||
vb, ok := op.(*BindMountOp)
|
vb, ok := op.(*BindMountOp)
|
||||||
|
|||||||
134
container/initdaemon.go
Normal file
134
container/initdaemon.go
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
package container
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/gob"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
"hakurei.app/container/fhs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() { gob.Register(new(DaemonOp)) }
|
||||||
|
|
||||||
|
const (
|
||||||
|
// daemonTimeout is the duration a [DaemonOp] is allowed to block before the
|
||||||
|
// [DaemonOp.Target] marker becomes available.
|
||||||
|
daemonTimeout = 5 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
// Daemon appends an [Op] that starts a daemon in the container and blocks until
|
||||||
|
// [DaemonOp.Target] appears.
|
||||||
|
func (f *Ops) Daemon(target, path *check.Absolute, args ...string) *Ops {
|
||||||
|
*f = append(*f, &DaemonOp{target, path, args})
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// DaemonOp starts a daemon in the container and blocks until Target appears.
|
||||||
|
type DaemonOp struct {
|
||||||
|
// Pathname indicating readiness of daemon.
|
||||||
|
Target *check.Absolute
|
||||||
|
// Absolute pathname passed to [exec.Cmd].
|
||||||
|
Path *check.Absolute
|
||||||
|
// Arguments (excl. first) passed to [exec.Cmd].
|
||||||
|
Args []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// earlyTerminationError is returned by [DaemonOp] when a daemon terminates
|
||||||
|
// before [DaemonOp.Target] appears.
|
||||||
|
type earlyTerminationError struct {
|
||||||
|
// Returned by [DaemonOp.String].
|
||||||
|
op string
|
||||||
|
// Copied from wait4 loop.
|
||||||
|
wstatus syscall.WaitStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *earlyTerminationError) Error() string {
|
||||||
|
res := ""
|
||||||
|
switch {
|
||||||
|
case e.wstatus.Exited():
|
||||||
|
res = "exit status " + strconv.Itoa(e.wstatus.ExitStatus())
|
||||||
|
case e.wstatus.Signaled():
|
||||||
|
res = "signal: " + e.wstatus.Signal().String()
|
||||||
|
case e.wstatus.Stopped():
|
||||||
|
res = "stop signal: " + e.wstatus.StopSignal().String()
|
||||||
|
if e.wstatus.StopSignal() == syscall.SIGTRAP && e.wstatus.TrapCause() != 0 {
|
||||||
|
res += " (trap " + strconv.Itoa(e.wstatus.TrapCause()) + ")"
|
||||||
|
}
|
||||||
|
case e.wstatus.Continued():
|
||||||
|
res = "continued"
|
||||||
|
}
|
||||||
|
if e.wstatus.CoreDump() {
|
||||||
|
res += " (core dumped)"
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *earlyTerminationError) Message() string { return e.op + " " + e.Error() }
|
||||||
|
|
||||||
|
func (d *DaemonOp) Valid() bool { return d != nil && d.Target != nil && d.Path != nil }
|
||||||
|
func (d *DaemonOp) early(*setupState, syscallDispatcher) error { return nil }
|
||||||
|
func (d *DaemonOp) apply(*setupState, syscallDispatcher) error { return nil }
|
||||||
|
func (d *DaemonOp) late(state *setupState, k syscallDispatcher) error {
|
||||||
|
cmd := exec.CommandContext(state.Context, d.Path.String(), d.Args...)
|
||||||
|
cmd.Env = state.Env
|
||||||
|
cmd.Dir = fhs.Root
|
||||||
|
if state.IsVerbose() {
|
||||||
|
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
|
||||||
|
}
|
||||||
|
// WaitDelay: left unset because lifetime is bound by AdoptWaitDelay on cancellation
|
||||||
|
cmd.Cancel = func() error { return cmd.Process.Signal(syscall.SIGTERM) }
|
||||||
|
|
||||||
|
state.Verbosef("starting %s", d.String())
|
||||||
|
if err := k.start(cmd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
deadline := time.Now().Add(daemonTimeout)
|
||||||
|
var wstatusErr error
|
||||||
|
|
||||||
|
for {
|
||||||
|
if _, err := k.stat(d.Target.String()); err != nil {
|
||||||
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
|
_ = k.signal(cmd, os.Kill)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if time.Now().After(deadline) {
|
||||||
|
_ = k.signal(cmd, os.Kill)
|
||||||
|
return context.DeadlineExceeded
|
||||||
|
}
|
||||||
|
|
||||||
|
if wstatusErr != nil {
|
||||||
|
return wstatusErr
|
||||||
|
}
|
||||||
|
if wstatus, ok := state.terminated(cmd.Process.Pid); ok {
|
||||||
|
// check once again: process could have satisfied Target between stat and the lookup
|
||||||
|
wstatusErr = &earlyTerminationError{d.String(), wstatus}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(500 * time.Microsecond)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
state.Verbosef("daemon process %d ready", cmd.Process.Pid)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DaemonOp) Is(op Op) bool {
|
||||||
|
vd, ok := op.(*DaemonOp)
|
||||||
|
return ok && d.Valid() && vd.Valid() &&
|
||||||
|
d.Target.Is(vd.Target) && d.Path.Is(vd.Path) &&
|
||||||
|
slices.Equal(d.Args, vd.Args)
|
||||||
|
}
|
||||||
|
func (*DaemonOp) prefix() (string, bool) { return zeroString, false }
|
||||||
|
func (d *DaemonOp) String() string { return fmt.Sprintf("daemon providing %q", d.Target) }
|
||||||
127
container/initdaemon_test.go
Normal file
127
container/initdaemon_test.go
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
package container
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
"hakurei.app/container/stub"
|
||||||
|
"hakurei.app/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEarlyTerminationError(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
want string
|
||||||
|
msg string
|
||||||
|
}{
|
||||||
|
{"exited", &earlyTerminationError{
|
||||||
|
`daemon providing "/run/user/1971/pulse/native"`, 127 << 8,
|
||||||
|
}, "exit status 127", `daemon providing "/run/user/1971/pulse/native" exit status 127`},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if got := tc.err.Error(); got != tc.want {
|
||||||
|
t.Errorf("Error: %q, want %q", got, tc.want)
|
||||||
|
}
|
||||||
|
if got := tc.err.(message.Error).Message(); got != tc.msg {
|
||||||
|
t.Errorf("Message: %s, want %s", got, tc.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDaemonOp(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
checkSimple(t, "DaemonOp.late", []simpleTestCase{
|
||||||
|
{"success", func(k *kstub) error {
|
||||||
|
state := setupState{Params: &Params{Env: []string{"\x00"}}, Context: t.Context(), Msg: k}
|
||||||
|
return (&DaemonOp{
|
||||||
|
Target: check.MustAbs("/run/user/1971/pulse/native"),
|
||||||
|
Path: check.MustAbs("/run/current-system/sw/bin/pipewire-pulse"),
|
||||||
|
Args: []string{"-v"},
|
||||||
|
}).late(&state, k)
|
||||||
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("isVerbose", stub.ExpectArgs{}, true, nil),
|
||||||
|
call("verbosef", stub.ExpectArgs{"starting %s", []any{`daemon providing "/run/user/1971/pulse/native"`}}, nil, nil),
|
||||||
|
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/pipewire-pulse", []string{"/run/current-system/sw/bin/pipewire-pulse", "-v"}, []string{"\x00"}, "/"}, &os.Process{Pid: 0xcafe}, nil),
|
||||||
|
call("stat", stub.ExpectArgs{"/run/user/1971/pulse/native"}, isDirFi(false), os.ErrNotExist),
|
||||||
|
call("stat", stub.ExpectArgs{"/run/user/1971/pulse/native"}, isDirFi(false), os.ErrNotExist),
|
||||||
|
call("stat", stub.ExpectArgs{"/run/user/1971/pulse/native"}, isDirFi(false), os.ErrNotExist),
|
||||||
|
call("stat", stub.ExpectArgs{"/run/user/1971/pulse/native"}, isDirFi(false), nil),
|
||||||
|
call("verbosef", stub.ExpectArgs{"daemon process %d ready", []any{0xcafe}}, nil, nil),
|
||||||
|
}}, nil},
|
||||||
|
})
|
||||||
|
|
||||||
|
checkOpsValid(t, []opValidTestCase{
|
||||||
|
{"nil", (*DaemonOp)(nil), false},
|
||||||
|
{"zero", new(DaemonOp), false},
|
||||||
|
{"valid", &DaemonOp{
|
||||||
|
Target: check.MustAbs("/run/user/1971/pulse/native"),
|
||||||
|
Path: check.MustAbs("/run/current-system/sw/bin/pipewire-pulse"),
|
||||||
|
Args: []string{"-v"},
|
||||||
|
}, true},
|
||||||
|
})
|
||||||
|
|
||||||
|
checkOpsBuilder(t, []opsBuilderTestCase{
|
||||||
|
{"pipewire-pulse", new(Ops).Daemon(
|
||||||
|
check.MustAbs("/run/user/1971/pulse/native"),
|
||||||
|
check.MustAbs("/run/current-system/sw/bin/pipewire-pulse"), "-v",
|
||||||
|
), Ops{
|
||||||
|
&DaemonOp{
|
||||||
|
Target: check.MustAbs("/run/user/1971/pulse/native"),
|
||||||
|
Path: check.MustAbs("/run/current-system/sw/bin/pipewire-pulse"),
|
||||||
|
Args: []string{"-v"},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
|
||||||
|
checkOpIs(t, []opIsTestCase{
|
||||||
|
{"zero", new(DaemonOp), new(DaemonOp), false},
|
||||||
|
|
||||||
|
{"args differs", &DaemonOp{
|
||||||
|
Target: check.MustAbs("/run/user/1971/pulse/native"),
|
||||||
|
Path: check.MustAbs("/run/current-system/sw/bin/pipewire-pulse"),
|
||||||
|
Args: []string{"-v"},
|
||||||
|
}, &DaemonOp{
|
||||||
|
Target: check.MustAbs("/run/user/1971/pulse/native"),
|
||||||
|
Path: check.MustAbs("/run/current-system/sw/bin/pipewire-pulse"),
|
||||||
|
}, false},
|
||||||
|
|
||||||
|
{"path differs", &DaemonOp{
|
||||||
|
Target: check.MustAbs("/run/user/1971/pulse/native"),
|
||||||
|
Path: check.MustAbs("/run/current-system/sw/bin/pipewire"),
|
||||||
|
}, &DaemonOp{
|
||||||
|
Target: check.MustAbs("/run/user/1971/pulse/native"),
|
||||||
|
Path: check.MustAbs("/run/current-system/sw/bin/pipewire-pulse"),
|
||||||
|
}, false},
|
||||||
|
|
||||||
|
{"target differs", &DaemonOp{
|
||||||
|
Target: check.MustAbs("/run/user/65534/pulse/native"),
|
||||||
|
Path: check.MustAbs("/run/current-system/sw/bin/pipewire-pulse"),
|
||||||
|
}, &DaemonOp{
|
||||||
|
Target: check.MustAbs("/run/user/1971/pulse/native"),
|
||||||
|
Path: check.MustAbs("/run/current-system/sw/bin/pipewire-pulse"),
|
||||||
|
}, false},
|
||||||
|
|
||||||
|
{"equals", &DaemonOp{
|
||||||
|
Target: check.MustAbs("/run/user/1971/pulse/native"),
|
||||||
|
Path: check.MustAbs("/run/current-system/sw/bin/pipewire-pulse"),
|
||||||
|
}, &DaemonOp{
|
||||||
|
Target: check.MustAbs("/run/user/1971/pulse/native"),
|
||||||
|
Path: check.MustAbs("/run/current-system/sw/bin/pipewire-pulse"),
|
||||||
|
}, true},
|
||||||
|
})
|
||||||
|
|
||||||
|
checkOpMeta(t, []opMetaTestCase{
|
||||||
|
{"pipewire-pulse", &DaemonOp{
|
||||||
|
Target: check.MustAbs("/run/user/1971/pulse/native"),
|
||||||
|
}, zeroString, `daemon providing "/run/user/1971/pulse/native"`},
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -126,6 +126,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
}
|
}
|
||||||
return k.mountTmpfs(SourceTmpfs, devShmPath, MS_NOSUID|MS_NODEV, 0, 01777)
|
return k.mountTmpfs(SourceTmpfs, devShmPath, MS_NOSUID|MS_NODEV, 0, 01777)
|
||||||
}
|
}
|
||||||
|
func (d *MountDevOp) late(*setupState, syscallDispatcher) error { return nil }
|
||||||
|
|
||||||
func (d *MountDevOp) Is(op Op) bool {
|
func (d *MountDevOp) Is(op Op) bool {
|
||||||
vd, ok := op.(*MountDevOp)
|
vd, ok := op.(*MountDevOp)
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ func (m *MkdirOp) early(*setupState, syscallDispatcher) error { return nil }
|
|||||||
func (m *MkdirOp) apply(_ *setupState, k syscallDispatcher) error {
|
func (m *MkdirOp) apply(_ *setupState, k syscallDispatcher) error {
|
||||||
return k.mkdirAll(toSysroot(m.Path.String()), m.Perm)
|
return k.mkdirAll(toSysroot(m.Path.String()), m.Perm)
|
||||||
}
|
}
|
||||||
|
func (m *MkdirOp) late(*setupState, syscallDispatcher) error { return nil }
|
||||||
|
|
||||||
func (m *MkdirOp) Is(op Op) bool {
|
func (m *MkdirOp) Is(op Op) bool {
|
||||||
vm, ok := op.(*MkdirOp)
|
vm, ok := op.(*MkdirOp)
|
||||||
|
|||||||
@@ -205,6 +205,8 @@ func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
return k.mount(SourceOverlay, target, FstypeOverlay, 0, strings.Join(options, check.SpecialOverlayOption))
|
return k.mount(SourceOverlay, target, FstypeOverlay, 0, strings.Join(options, check.SpecialOverlayOption))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *MountOverlayOp) late(*setupState, syscallDispatcher) error { return nil }
|
||||||
|
|
||||||
func (o *MountOverlayOp) Is(op Op) bool {
|
func (o *MountOverlayOp) Is(op Op) bool {
|
||||||
vo, ok := op.(*MountOverlayOp)
|
vo, ok := op.(*MountOverlayOp)
|
||||||
return ok && o.Valid() && vo.Valid() &&
|
return ok && o.Valid() && vo.Valid() &&
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ func (t *TmpfileOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func (t *TmpfileOp) late(*setupState, syscallDispatcher) error { return nil }
|
||||||
|
|
||||||
func (t *TmpfileOp) Is(op Op) bool {
|
func (t *TmpfileOp) Is(op Op) bool {
|
||||||
vt, ok := op.(*TmpfileOp)
|
vt, ok := op.(*TmpfileOp)
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ func (p *MountProcOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
}
|
}
|
||||||
return k.mount(SourceProc, target, FstypeProc, MS_NOSUID|MS_NOEXEC|MS_NODEV, zeroString)
|
return k.mount(SourceProc, target, FstypeProc, MS_NOSUID|MS_NOEXEC|MS_NODEV, zeroString)
|
||||||
}
|
}
|
||||||
|
func (p *MountProcOp) late(*setupState, syscallDispatcher) error { return nil }
|
||||||
|
|
||||||
func (p *MountProcOp) Is(op Op) bool {
|
func (p *MountProcOp) Is(op Op) bool {
|
||||||
vp, ok := op.(*MountProcOp)
|
vp, ok := op.(*MountProcOp)
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ func (*RemountOp) early(*setupState, syscallDispatcher) error { return nil }
|
|||||||
func (r *RemountOp) apply(state *setupState, k syscallDispatcher) error {
|
func (r *RemountOp) apply(state *setupState, k syscallDispatcher) error {
|
||||||
return k.remount(state, toSysroot(r.Target.String()), r.Flags)
|
return k.remount(state, toSysroot(r.Target.String()), r.Flags)
|
||||||
}
|
}
|
||||||
|
func (r *RemountOp) late(*setupState, syscallDispatcher) error { return nil }
|
||||||
|
|
||||||
func (r *RemountOp) Is(op Op) bool {
|
func (r *RemountOp) Is(op Op) bool {
|
||||||
vr, ok := op.(*RemountOp)
|
vr, ok := op.(*RemountOp)
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ func (l *SymlinkOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
return k.symlink(l.LinkName, target)
|
return k.symlink(l.LinkName, target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *SymlinkOp) late(*setupState, syscallDispatcher) error { return nil }
|
||||||
|
|
||||||
func (l *SymlinkOp) Is(op Op) bool {
|
func (l *SymlinkOp) Is(op Op) bool {
|
||||||
vl, ok := op.(*SymlinkOp)
|
vl, ok := op.(*SymlinkOp)
|
||||||
return ok && l.Valid() && vl.Valid() &&
|
return ok && l.Valid() && vl.Valid() &&
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ func (t *MountTmpfsOp) apply(_ *setupState, k syscallDispatcher) error {
|
|||||||
}
|
}
|
||||||
return k.mountTmpfs(t.FSName, toSysroot(t.Path.String()), t.Flags, t.Size, t.Perm)
|
return k.mountTmpfs(t.FSName, toSysroot(t.Path.String()), t.Flags, t.Size, t.Perm)
|
||||||
}
|
}
|
||||||
|
func (t *MountTmpfsOp) late(*setupState, syscallDispatcher) error { return nil }
|
||||||
|
|
||||||
func (t *MountTmpfsOp) Is(op Op) bool {
|
func (t *MountTmpfsOp) Is(op Op) bool {
|
||||||
vt, ok := op.(*MountTmpfsOp)
|
vt, ok := op.(*MountTmpfsOp)
|
||||||
|
|||||||
3
dist/comp/_hakurei
vendored
3
dist/comp/_hakurei
vendored
@@ -17,7 +17,8 @@ _hakurei_run() {
|
|||||||
'--wayland[Enable connection to Wayland via security-context-v1]' \
|
'--wayland[Enable connection to Wayland via security-context-v1]' \
|
||||||
'-X[Enable direct connection to X11]' \
|
'-X[Enable direct connection to X11]' \
|
||||||
'--dbus[Enable proxied connection to D-Bus]' \
|
'--dbus[Enable proxied connection to D-Bus]' \
|
||||||
'--pulse[Enable direct connection to PulseAudio]' \
|
'--pipewire[Enable connection to PipeWire via SecurityContext]' \
|
||||||
|
'--pulse[Enable PulseAudio compatibility daemon]' \
|
||||||
'--dbus-config[Path to session bus proxy config file]: :_files -g "*.json"' \
|
'--dbus-config[Path to session bus proxy config file]: :_files -g "*.json"' \
|
||||||
'--dbus-system[Path to system bus proxy config file]: :_files -g "*.json"' \
|
'--dbus-system[Path to system bus proxy config file]: :_files -g "*.json"' \
|
||||||
'--mpris[Allow owning MPRIS D-Bus path]' \
|
'--mpris[Allow owning MPRIS D-Bus path]' \
|
||||||
|
|||||||
@@ -23,9 +23,20 @@ type Config struct {
|
|||||||
// System D-Bus proxy configuration.
|
// System D-Bus proxy configuration.
|
||||||
// If set to nil, system bus proxy is disabled.
|
// If set to nil, system bus proxy is disabled.
|
||||||
SystemBus *BusConfig `json:"system_bus,omitempty"`
|
SystemBus *BusConfig `json:"system_bus,omitempty"`
|
||||||
|
|
||||||
// Direct access to wayland socket, no attempt is made to attach security-context-v1
|
// Direct access to wayland socket, no attempt is made to attach security-context-v1
|
||||||
// and the bare socket is made available to the container.
|
// and the bare socket is made available to the container.
|
||||||
|
//
|
||||||
|
// This option is unsupported and most likely enables full control over the Wayland
|
||||||
|
// session. Do not set this to true unless you are sure you know what you are doing.
|
||||||
DirectWayland bool `json:"direct_wayland,omitempty"`
|
DirectWayland bool `json:"direct_wayland,omitempty"`
|
||||||
|
// Direct access to PulseAudio socket, no attempt is made to establish pipewire-pulse
|
||||||
|
// server via a PipeWire socket with a SecurityContext attached and the bare socket
|
||||||
|
// is made available to the container.
|
||||||
|
//
|
||||||
|
// This option is unsupported and enables arbitrary code execution as the PulseAudio
|
||||||
|
// server. Do not set this to true, this is insecure under any configuration.
|
||||||
|
DirectPulse bool `json:"direct_pulse,omitempty"`
|
||||||
|
|
||||||
// Extra acl updates to perform before setuid.
|
// Extra acl updates to perform before setuid.
|
||||||
ExtraPerms []ExtraPermConfig `json:"extra_perms,omitempty"`
|
ExtraPerms []ExtraPermConfig `json:"extra_perms,omitempty"`
|
||||||
@@ -49,6 +60,9 @@ var (
|
|||||||
|
|
||||||
// ErrEnviron is returned by [Config.Validate] if an environment variable name contains '=' or NUL.
|
// ErrEnviron is returned by [Config.Validate] if an environment variable name contains '=' or NUL.
|
||||||
ErrEnviron = errors.New("invalid environment variable name")
|
ErrEnviron = errors.New("invalid environment variable name")
|
||||||
|
|
||||||
|
// ErrInsecure is returned by [Config.Validate] if the configuration is considered insecure.
|
||||||
|
ErrInsecure = errors.New("configuration is insecure")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Validate checks [Config] and returns [AppError] if an invalid value is encountered.
|
// Validate checks [Config] and returns [AppError] if an invalid value is encountered.
|
||||||
@@ -95,6 +109,11 @@ func (config *Config) Validate() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if et := config.Enablements.Unwrap(); !config.DirectPulse && et&EPulse != 0 {
|
||||||
|
return &AppError{Step: "validate configuration", Err: ErrInsecure,
|
||||||
|
Msg: "enablement PulseAudio is insecure and no longer supported"}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,12 @@ func TestConfigValidate(t *testing.T) {
|
|||||||
Env: map[string]string{"TERM\x00": ""},
|
Env: map[string]string{"TERM\x00": ""},
|
||||||
}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrEnviron,
|
}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrEnviron,
|
||||||
Msg: `invalid environment variable "TERM\x00"`}},
|
Msg: `invalid environment variable "TERM\x00"`}},
|
||||||
|
{"insecure pulse", &hst.Config{Enablements: hst.NewEnablements(hst.EPulse), Container: &hst.ContainerConfig{
|
||||||
|
Home: fhs.AbsTmp,
|
||||||
|
Shell: fhs.AbsTmp,
|
||||||
|
Path: fhs.AbsTmp,
|
||||||
|
}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrInsecure,
|
||||||
|
Msg: "enablement PulseAudio is insecure and no longer supported"}},
|
||||||
{"valid", &hst.Config{Container: &hst.ContainerConfig{
|
{"valid", &hst.Config{Container: &hst.ContainerConfig{
|
||||||
Home: fhs.AbsTmp,
|
Home: fhs.AbsTmp,
|
||||||
Shell: fhs.AbsTmp,
|
Shell: fhs.AbsTmp,
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ const (
|
|||||||
EX11
|
EX11
|
||||||
// EDBus enables the per-container xdg-dbus-proxy daemon.
|
// EDBus enables the per-container xdg-dbus-proxy daemon.
|
||||||
EDBus
|
EDBus
|
||||||
|
// EPipeWire exposes a pipewire pathname socket via SecurityContext.
|
||||||
|
EPipeWire
|
||||||
// EPulse copies the PulseAudio cookie to [hst.PrivateTmp] and exposes the PulseAudio socket.
|
// EPulse copies the PulseAudio cookie to [hst.PrivateTmp] and exposes the PulseAudio socket.
|
||||||
EPulse
|
EPulse
|
||||||
|
|
||||||
@@ -35,6 +37,8 @@ func (e Enablement) String() string {
|
|||||||
return "x11"
|
return "x11"
|
||||||
case EDBus:
|
case EDBus:
|
||||||
return "dbus"
|
return "dbus"
|
||||||
|
case EPipeWire:
|
||||||
|
return "pipewire"
|
||||||
case EPulse:
|
case EPulse:
|
||||||
return "pulseaudio"
|
return "pulseaudio"
|
||||||
default:
|
default:
|
||||||
@@ -62,10 +66,11 @@ type Enablements Enablement
|
|||||||
|
|
||||||
// enablementsJSON is the [json] representation of [Enablements].
|
// enablementsJSON is the [json] representation of [Enablements].
|
||||||
type enablementsJSON = struct {
|
type enablementsJSON = struct {
|
||||||
Wayland bool `json:"wayland,omitempty"`
|
Wayland bool `json:"wayland,omitempty"`
|
||||||
X11 bool `json:"x11,omitempty"`
|
X11 bool `json:"x11,omitempty"`
|
||||||
DBus bool `json:"dbus,omitempty"`
|
DBus bool `json:"dbus,omitempty"`
|
||||||
Pulse bool `json:"pulse,omitempty"`
|
PipeWire bool `json:"pipewire,omitempty"`
|
||||||
|
Pulse bool `json:"pulse,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unwrap returns the underlying [Enablement].
|
// Unwrap returns the underlying [Enablement].
|
||||||
@@ -81,10 +86,11 @@ func (e *Enablements) MarshalJSON() ([]byte, error) {
|
|||||||
return nil, syscall.EINVAL
|
return nil, syscall.EINVAL
|
||||||
}
|
}
|
||||||
return json.Marshal(&enablementsJSON{
|
return json.Marshal(&enablementsJSON{
|
||||||
Wayland: Enablement(*e)&EWayland != 0,
|
Wayland: Enablement(*e)&EWayland != 0,
|
||||||
X11: Enablement(*e)&EX11 != 0,
|
X11: Enablement(*e)&EX11 != 0,
|
||||||
DBus: Enablement(*e)&EDBus != 0,
|
DBus: Enablement(*e)&EDBus != 0,
|
||||||
Pulse: Enablement(*e)&EPulse != 0,
|
PipeWire: Enablement(*e)&EPipeWire != 0,
|
||||||
|
Pulse: Enablement(*e)&EPulse != 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,6 +114,9 @@ func (e *Enablements) UnmarshalJSON(data []byte) error {
|
|||||||
if v.DBus {
|
if v.DBus {
|
||||||
ve |= EDBus
|
ve |= EDBus
|
||||||
}
|
}
|
||||||
|
if v.PipeWire {
|
||||||
|
ve |= EPipeWire
|
||||||
|
}
|
||||||
if v.Pulse {
|
if v.Pulse {
|
||||||
ve |= EPulse
|
ve |= EPulse
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ func TestEnablementString(t *testing.T) {
|
|||||||
{hst.EWayland | hst.EDBus | hst.EPulse, "wayland, dbus, pulseaudio"},
|
{hst.EWayland | hst.EDBus | hst.EPulse, "wayland, dbus, pulseaudio"},
|
||||||
{hst.EX11 | hst.EDBus | hst.EPulse, "x11, dbus, pulseaudio"},
|
{hst.EX11 | hst.EDBus | hst.EPulse, "x11, dbus, pulseaudio"},
|
||||||
{hst.EWayland | hst.EX11 | hst.EDBus | hst.EPulse, "wayland, x11, dbus, pulseaudio"},
|
{hst.EWayland | hst.EX11 | hst.EDBus | hst.EPulse, "wayland, x11, dbus, pulseaudio"},
|
||||||
|
{hst.EM - 1, "wayland, x11, dbus, pipewire, pulseaudio"},
|
||||||
|
|
||||||
{1 << 5, "e20"},
|
{1 << 5, "e20"},
|
||||||
{1 << 6, "e40"},
|
{1 << 6, "e40"},
|
||||||
@@ -62,8 +63,9 @@ func TestEnablements(t *testing.T) {
|
|||||||
{"wayland", hst.NewEnablements(hst.EWayland), `{"wayland":true}`, `{"value":{"wayland":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}`},
|
{"x11", hst.NewEnablements(hst.EX11), `{"x11":true}`, `{"value":{"x11":true},"magic":3236757504}`},
|
||||||
{"dbus", hst.NewEnablements(hst.EDBus), `{"dbus":true}`, `{"value":{"dbus":true},"magic":3236757504}`},
|
{"dbus", hst.NewEnablements(hst.EDBus), `{"dbus":true}`, `{"value":{"dbus":true},"magic":3236757504}`},
|
||||||
|
{"pipewire", hst.NewEnablements(hst.EPipeWire), `{"pipewire":true}`, `{"value":{"pipewire":true},"magic":3236757504}`},
|
||||||
{"pulse", hst.NewEnablements(hst.EPulse), `{"pulse":true}`, `{"value":{"pulse":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}`},
|
{"all", hst.NewEnablements(hst.EM - 1), `{"wayland":true,"x11":true,"dbus":true,"pipewire":true,"pulse":true}`, `{"value":{"wayland":true,"x11":true,"dbus":true,"pipewire":true,"pulse":true},"magic":3236757504}`},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
|||||||
12
hst/fs.go
12
hst/fs.go
@@ -45,6 +45,9 @@ type Ops interface {
|
|||||||
Root(host *check.Absolute, flags int) Ops
|
Root(host *check.Absolute, flags int) Ops
|
||||||
// Etc appends an op that expands host /etc into a toplevel symlink mirror with /etc semantics.
|
// Etc appends an op that expands host /etc into a toplevel symlink mirror with /etc semantics.
|
||||||
Etc(host *check.Absolute, prefix string) Ops
|
Etc(host *check.Absolute, prefix string) Ops
|
||||||
|
|
||||||
|
// Daemon appends an op that starts a daemon in the container and blocks until target appears.
|
||||||
|
Daemon(target, path *check.Absolute, args ...string) Ops
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyState holds the address of [Ops] and any relevant application state.
|
// ApplyState holds the address of [Ops] and any relevant application state.
|
||||||
@@ -124,6 +127,12 @@ func (f *FilesystemConfigJSON) MarshalJSON() ([]byte, error) {
|
|||||||
*FSLink
|
*FSLink
|
||||||
}{fsType{FilesystemLink}, cv}
|
}{fsType{FilesystemLink}, cv}
|
||||||
|
|
||||||
|
case *FSDaemon:
|
||||||
|
v = &struct {
|
||||||
|
fsType
|
||||||
|
*FSDaemon
|
||||||
|
}{fsType{FilesystemDaemon}, cv}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, FSImplError{f.FilesystemConfig}
|
return nil, FSImplError{f.FilesystemConfig}
|
||||||
}
|
}
|
||||||
@@ -152,6 +161,9 @@ func (f *FilesystemConfigJSON) UnmarshalJSON(data []byte) error {
|
|||||||
case FilesystemLink:
|
case FilesystemLink:
|
||||||
*f = FilesystemConfigJSON{new(FSLink)}
|
*f = FilesystemConfigJSON{new(FSLink)}
|
||||||
|
|
||||||
|
case FilesystemDaemon:
|
||||||
|
*f = FilesystemConfigJSON{new(FSDaemon)}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return FSTypeError(t.Type)
|
return FSTypeError(t.Type)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,16 @@ func TestFilesystemConfigJSON(t *testing.T) {
|
|||||||
}, nil,
|
}, nil,
|
||||||
`{"type":"link","dst":"/run/current-system","linkname":"/run/current-system","dereference":true}`,
|
`{"type":"link","dst":"/run/current-system","linkname":"/run/current-system","dereference":true}`,
|
||||||
`{"fs":{"type":"link","dst":"/run/current-system","linkname":"/run/current-system","dereference":true},"magic":3236757504}`},
|
`{"fs":{"type":"link","dst":"/run/current-system","linkname":"/run/current-system","dereference":true},"magic":3236757504}`},
|
||||||
|
|
||||||
|
{"daemon", hst.FilesystemConfigJSON{
|
||||||
|
FilesystemConfig: &hst.FSDaemon{
|
||||||
|
Target: m("/run/user/1971/pulse/native"),
|
||||||
|
Exec: m("/run/current-system/sw/bin/pipewire-pulse"),
|
||||||
|
Args: []string{"-v"},
|
||||||
|
},
|
||||||
|
}, nil,
|
||||||
|
`{"type":"daemon","dst":"/run/user/1971/pulse/native","path":"/run/current-system/sw/bin/pipewire-pulse","args":["-v"]}`,
|
||||||
|
`{"fs":{"type":"daemon","dst":"/run/user/1971/pulse/native","path":"/run/current-system/sw/bin/pipewire-pulse","args":["-v"]},"magic":3236757504}`},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
@@ -345,6 +355,10 @@ func (p opsAdapter) Etc(host *check.Absolute, prefix string) hst.Ops {
|
|||||||
return opsAdapter{p.Ops.Etc(host, prefix)}
|
return opsAdapter{p.Ops.Etc(host, prefix)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p opsAdapter) Daemon(target, path *check.Absolute, args ...string) hst.Ops {
|
||||||
|
return opsAdapter{p.Ops.Daemon(target, path, args...)}
|
||||||
|
}
|
||||||
|
|
||||||
func m(pathname string) *check.Absolute { return check.MustAbs(pathname) }
|
func m(pathname string) *check.Absolute { return check.MustAbs(pathname) }
|
||||||
func ms(pathnames ...string) []*check.Absolute {
|
func ms(pathnames ...string) []*check.Absolute {
|
||||||
as := make([]*check.Absolute, len(pathnames))
|
as := make([]*check.Absolute, len(pathnames))
|
||||||
|
|||||||
48
hst/fsdaemon.go
Normal file
48
hst/fsdaemon.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package hst
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/gob"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() { gob.Register(new(FSDaemon)) }
|
||||||
|
|
||||||
|
// FilesystemDaemon is the type string of a daemon.
|
||||||
|
const FilesystemDaemon = "daemon"
|
||||||
|
|
||||||
|
// FSDaemon represents a daemon to be started in the container.
|
||||||
|
type FSDaemon struct {
|
||||||
|
// Pathname indicating readiness of daemon.
|
||||||
|
Target *check.Absolute `json:"dst"`
|
||||||
|
// Absolute pathname to daemon executable file.
|
||||||
|
Exec *check.Absolute `json:"path"`
|
||||||
|
// Arguments (excl. first) passed to daemon.
|
||||||
|
Args []string `json:"args"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *FSDaemon) Valid() bool { return d != nil && d.Target != nil && d.Exec != nil }
|
||||||
|
|
||||||
|
func (d *FSDaemon) Path() *check.Absolute {
|
||||||
|
if !d.Valid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return d.Target
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *FSDaemon) Host() []*check.Absolute { return nil }
|
||||||
|
|
||||||
|
func (d *FSDaemon) Apply(z *ApplyState) {
|
||||||
|
if !d.Valid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.Daemon(d.Target, d.Exec, d.Args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *FSDaemon) String() string {
|
||||||
|
if !d.Valid() {
|
||||||
|
return "<invalid>"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "daemon:" + d.Target.String()
|
||||||
|
}
|
||||||
29
hst/fsdaemon_test.go
Normal file
29
hst/fsdaemon_test.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package hst_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/hst"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFSDaemon(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
checkFs(t, []fsTestCase{
|
||||||
|
{"nil", (*hst.FSDaemon)(nil), false, nil, nil, nil, "<invalid>"},
|
||||||
|
{"zero", new(hst.FSDaemon), false, nil, nil, nil, "<invalid>"},
|
||||||
|
|
||||||
|
{"pipewire-pulse", &hst.FSDaemon{
|
||||||
|
Target: m("/run/user/1971/pulse/native"),
|
||||||
|
Exec: m("/run/current-system/sw/bin/pipewire-pulse"),
|
||||||
|
Args: []string{"-v"},
|
||||||
|
}, true, container.Ops{
|
||||||
|
&container.DaemonOp{
|
||||||
|
Target: m("/run/user/1971/pulse/native"),
|
||||||
|
Path: m("/run/current-system/sw/bin/pipewire-pulse"),
|
||||||
|
Args: []string{"-v"},
|
||||||
|
},
|
||||||
|
}, m("/run/user/1971/pulse/native"), nil, `daemon:/run/user/1971/pulse/native`},
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -70,7 +70,7 @@ func Template() *Config {
|
|||||||
return &Config{
|
return &Config{
|
||||||
ID: "org.chromium.Chromium",
|
ID: "org.chromium.Chromium",
|
||||||
|
|
||||||
Enablements: NewEnablements(EWayland | EDBus | EPulse),
|
Enablements: NewEnablements(EWayland | EDBus | EPipeWire),
|
||||||
|
|
||||||
SessionBus: &BusConfig{
|
SessionBus: &BusConfig{
|
||||||
See: nil,
|
See: nil,
|
||||||
@@ -92,7 +92,6 @@ func Template() *Config {
|
|||||||
Log: false,
|
Log: false,
|
||||||
Filter: true,
|
Filter: true,
|
||||||
},
|
},
|
||||||
DirectWayland: false,
|
|
||||||
|
|
||||||
ExtraPerms: []ExtraPermConfig{
|
ExtraPerms: []ExtraPermConfig{
|
||||||
{Path: fhs.AbsVarLib.Append("hakurei/u0"), Ensure: true, Execute: true},
|
{Path: fhs.AbsVarLib.Append("hakurei/u0"), Ensure: true, Execute: true},
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ func TestTemplate(t *testing.T) {
|
|||||||
"enablements": {
|
"enablements": {
|
||||||
"wayland": true,
|
"wayland": true,
|
||||||
"dbus": true,
|
"dbus": true,
|
||||||
"pulse": true
|
"pipewire": true
|
||||||
},
|
},
|
||||||
"session_bus": {
|
"session_bus": {
|
||||||
"see": null,
|
"see": null,
|
||||||
|
|||||||
@@ -172,6 +172,8 @@ type outcomeStateSys struct {
|
|||||||
|
|
||||||
// Copied from [hst.Config]. Safe for read by spWaylandOp.toSystem only.
|
// Copied from [hst.Config]. Safe for read by spWaylandOp.toSystem only.
|
||||||
directWayland bool
|
directWayland bool
|
||||||
|
// Copied from [hst.Config]. Safe for read by spPulseOp.toSystem only.
|
||||||
|
directPulse bool
|
||||||
// Copied header from [hst.Config]. Safe for read by spFilesystemOp.toSystem only.
|
// Copied header from [hst.Config]. Safe for read by spFilesystemOp.toSystem only.
|
||||||
extraPerms []hst.ExtraPermConfig
|
extraPerms []hst.ExtraPermConfig
|
||||||
// Copied address from [hst.Config]. Safe for read by spDBusOp.toSystem only.
|
// Copied address from [hst.Config]. Safe for read by spDBusOp.toSystem only.
|
||||||
@@ -185,7 +187,8 @@ type outcomeStateSys struct {
|
|||||||
func (s *outcomeState) newSys(config *hst.Config, sys *system.I) *outcomeStateSys {
|
func (s *outcomeState) newSys(config *hst.Config, sys *system.I) *outcomeStateSys {
|
||||||
return &outcomeStateSys{
|
return &outcomeStateSys{
|
||||||
appId: config.ID, et: config.Enablements.Unwrap(),
|
appId: config.ID, et: config.Enablements.Unwrap(),
|
||||||
directWayland: config.DirectWayland, extraPerms: config.ExtraPerms,
|
directWayland: config.DirectWayland, directPulse: config.DirectPulse,
|
||||||
|
extraPerms: config.ExtraPerms,
|
||||||
sessionBus: config.SessionBus, systemBus: config.SystemBus,
|
sessionBus: config.SessionBus, systemBus: config.SystemBus,
|
||||||
sys: sys, outcomeState: s,
|
sys: sys, outcomeState: s,
|
||||||
}
|
}
|
||||||
@@ -292,6 +295,7 @@ func (state *outcomeStateSys) toSystem() error {
|
|||||||
// optional via enablements
|
// optional via enablements
|
||||||
&spWaylandOp{},
|
&spWaylandOp{},
|
||||||
&spX11Op{},
|
&spX11Op{},
|
||||||
|
spPipeWireOp{},
|
||||||
&spPulseOp{},
|
&spPulseOp{},
|
||||||
&spDBusOp{},
|
&spDBusOp{},
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
"hakurei.app/message"
|
"hakurei.app/message"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOutcomeMain(t *testing.T) {
|
func TestOutcomeRun(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
msg := message.New(nil)
|
msg := message.New(nil)
|
||||||
msg.SwapVerbose(testing.Verbose())
|
msg.SwapVerbose(testing.Verbose())
|
||||||
@@ -67,18 +67,8 @@ func TestOutcomeMain(t *testing.T) {
|
|||||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
).
|
).
|
||||||
|
|
||||||
// ensureRuntimeDir
|
// spPipeWireOp
|
||||||
Ensure(m("/run/user/1971"), 0700).
|
PipeWire(m("/tmp/hakurei.0/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/pipewire")).
|
||||||
UpdatePermType(system.User, m("/run/user/1971"), acl.Execute).
|
|
||||||
Ensure(m("/run/user/1971/hakurei"), 0700).
|
|
||||||
UpdatePermType(system.User, m("/run/user/1971/hakurei"), acl.Execute).
|
|
||||||
|
|
||||||
// runtime
|
|
||||||
Ephemeral(system.Process, m("/run/user/1971/hakurei/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), 0700).
|
|
||||||
UpdatePerm(m("/run/user/1971/hakurei/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), acl.Execute).
|
|
||||||
|
|
||||||
// spPulseOp
|
|
||||||
Link(m("/run/user/1971/pulse/native"), m("/run/user/1971/hakurei/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/pulse")).
|
|
||||||
|
|
||||||
// spDBusOp
|
// spDBusOp
|
||||||
MustProxyDBus(
|
MustProxyDBus(
|
||||||
@@ -106,8 +96,7 @@ func TestOutcomeMain(t *testing.T) {
|
|||||||
"GOOGLE_DEFAULT_CLIENT_ID=77185425430.apps.googleusercontent.com",
|
"GOOGLE_DEFAULT_CLIENT_ID=77185425430.apps.googleusercontent.com",
|
||||||
"GOOGLE_DEFAULT_CLIENT_SECRET=OTJgUOQcT7lO7GsGZq2G4IlT",
|
"GOOGLE_DEFAULT_CLIENT_SECRET=OTJgUOQcT7lO7GsGZq2G4IlT",
|
||||||
"HOME=/data/data/org.chromium.Chromium",
|
"HOME=/data/data/org.chromium.Chromium",
|
||||||
"PULSE_COOKIE=/.hakurei/pulse-cookie",
|
"PIPEWIRE_REMOTE=/run/user/1971/pipewire-0",
|
||||||
"PULSE_SERVER=unix:/run/user/1971/pulse/native",
|
|
||||||
"SHELL=/run/current-system/sw/bin/zsh",
|
"SHELL=/run/current-system/sw/bin/zsh",
|
||||||
"TERM=xterm-256color",
|
"TERM=xterm-256color",
|
||||||
"USER=chronos",
|
"USER=chronos",
|
||||||
@@ -144,7 +133,7 @@ func TestOutcomeMain(t *testing.T) {
|
|||||||
Tmpfs(fhs.AbsDevShm, 0, 01777).
|
Tmpfs(fhs.AbsDevShm, 0, 01777).
|
||||||
|
|
||||||
// spRuntimeOp
|
// spRuntimeOp
|
||||||
Tmpfs(fhs.AbsRunUser, 1<<12, 0755).
|
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
||||||
Bind(m("/tmp/hakurei.0/runtime/9"), m("/run/user/1971"), std.BindWritable).
|
Bind(m("/tmp/hakurei.0/runtime/9"), m("/run/user/1971"), std.BindWritable).
|
||||||
|
|
||||||
// spTmpdirOp
|
// spTmpdirOp
|
||||||
@@ -157,9 +146,8 @@ func TestOutcomeMain(t *testing.T) {
|
|||||||
// spWaylandOp
|
// spWaylandOp
|
||||||
Bind(m("/tmp/hakurei.0/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/wayland"), m("/run/user/1971/wayland-0"), 0).
|
Bind(m("/tmp/hakurei.0/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/wayland"), m("/run/user/1971/wayland-0"), 0).
|
||||||
|
|
||||||
// spPulseOp
|
// spPipeWireOp
|
||||||
Bind(m("/run/user/1971/hakurei/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/pulse"), m("/run/user/1971/pulse/native"), 0).
|
Bind(m("/tmp/hakurei.0/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/pipewire"), m("/run/user/1971/pipewire-0"), 0).
|
||||||
Place(m("/.hakurei/pulse-cookie"), bytes.Repeat([]byte{0}, pulseCookieSizeMax)).
|
|
||||||
|
|
||||||
// spDBusOp
|
// spDBusOp
|
||||||
Bind(m("/tmp/hakurei.0/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bus"), m("/run/user/1971/bus"), 0).
|
Bind(m("/tmp/hakurei.0/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bus"), m("/run/user/1971/bus"), 0).
|
||||||
@@ -244,7 +232,7 @@ func TestOutcomeMain(t *testing.T) {
|
|||||||
Tmpfs(hst.AbsPrivateTmp, 4096, 0755).
|
Tmpfs(hst.AbsPrivateTmp, 4096, 0755).
|
||||||
DevWritable(m("/dev/"), true).
|
DevWritable(m("/dev/"), true).
|
||||||
Tmpfs(m("/dev/shm/"), 0, 01777).
|
Tmpfs(m("/dev/shm/"), 0, 01777).
|
||||||
Tmpfs(m("/run/user/"), 4096, 0755).
|
Tmpfs(m("/run/user/"), xdgRuntimeDirSize, 0755).
|
||||||
Bind(m("/tmp/hakurei.0/runtime/0"), m("/run/user/65534"), std.BindWritable).
|
Bind(m("/tmp/hakurei.0/runtime/0"), m("/run/user/65534"), std.BindWritable).
|
||||||
Bind(m("/tmp/hakurei.0/tmpdir/0"), m("/tmp/"), std.BindWritable).
|
Bind(m("/tmp/hakurei.0/tmpdir/0"), m("/tmp/"), std.BindWritable).
|
||||||
Place(m("/etc/passwd"), []byte("chronos:x:65534:65534:Hakurei:/home/chronos:/run/current-system/sw/bin/zsh\n")).
|
Place(m("/etc/passwd"), []byte("chronos:x:65534:65534:Hakurei:/home/chronos:/run/current-system/sw/bin/zsh\n")).
|
||||||
@@ -298,7 +286,7 @@ func TestOutcomeMain(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Filter: true,
|
Filter: true,
|
||||||
},
|
},
|
||||||
Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPulse),
|
Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPipeWire | hst.EPulse),
|
||||||
|
|
||||||
Container: &hst.ContainerConfig{
|
Container: &hst.ContainerConfig{
|
||||||
Filesystem: []hst.FilesystemConfigJSON{
|
Filesystem: []hst.FilesystemConfigJSON{
|
||||||
@@ -347,10 +335,7 @@ func TestOutcomeMain(t *testing.T) {
|
|||||||
Ensure(m("/tmp/hakurei.0/tmpdir/9"), 01700).UpdatePermType(system.User, m("/tmp/hakurei.0/tmpdir/9"), acl.Read, acl.Write, acl.Execute).
|
Ensure(m("/tmp/hakurei.0/tmpdir/9"), 01700).UpdatePermType(system.User, m("/tmp/hakurei.0/tmpdir/9"), acl.Read, acl.Write, acl.Execute).
|
||||||
Ephemeral(system.Process, m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c"), 0711).
|
Ephemeral(system.Process, m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c"), 0711).
|
||||||
Wayland(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/wayland"), m("/run/user/1971/wayland-0"), "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c").
|
Wayland(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/wayland"), m("/run/user/1971/wayland-0"), "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c").
|
||||||
Ensure(m("/run/user/1971"), 0700).UpdatePermType(system.User, m("/run/user/1971"), acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
|
PipeWire(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/pipewire")).
|
||||||
Ensure(m("/run/user/1971/hakurei"), 0700).UpdatePermType(system.User, m("/run/user/1971/hakurei"), acl.Execute).
|
|
||||||
Ephemeral(system.Process, m("/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c"), 0700).UpdatePermType(system.Process, m("/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c"), acl.Execute).
|
|
||||||
Link(m("/run/user/1971/pulse/native"), m("/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c/pulse")).
|
|
||||||
MustProxyDBus(&hst.BusConfig{
|
MustProxyDBus(&hst.BusConfig{
|
||||||
Talk: []string{
|
Talk: []string{
|
||||||
"org.freedesktop.Notifications",
|
"org.freedesktop.Notifications",
|
||||||
@@ -397,8 +382,7 @@ func TestOutcomeMain(t *testing.T) {
|
|||||||
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus",
|
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus",
|
||||||
"DBUS_SYSTEM_BUS_ADDRESS=unix:path=/var/run/dbus/system_bus_socket",
|
"DBUS_SYSTEM_BUS_ADDRESS=unix:path=/var/run/dbus/system_bus_socket",
|
||||||
"HOME=/home/chronos",
|
"HOME=/home/chronos",
|
||||||
"PULSE_COOKIE=" + hst.PrivateTmp + "/pulse-cookie",
|
"PIPEWIRE_REMOTE=/run/user/65534/pipewire-0",
|
||||||
"PULSE_SERVER=unix:/run/user/65534/pulse/native",
|
|
||||||
"SHELL=/run/current-system/sw/bin/zsh",
|
"SHELL=/run/current-system/sw/bin/zsh",
|
||||||
"TERM=xterm-256color",
|
"TERM=xterm-256color",
|
||||||
"USER=chronos",
|
"USER=chronos",
|
||||||
@@ -413,14 +397,13 @@ func TestOutcomeMain(t *testing.T) {
|
|||||||
Tmpfs(hst.AbsPrivateTmp, 4096, 0755).
|
Tmpfs(hst.AbsPrivateTmp, 4096, 0755).
|
||||||
DevWritable(m("/dev/"), true).
|
DevWritable(m("/dev/"), true).
|
||||||
Tmpfs(m("/dev/shm/"), 0, 01777).
|
Tmpfs(m("/dev/shm/"), 0, 01777).
|
||||||
Tmpfs(m("/run/user/"), 4096, 0755).
|
Tmpfs(m("/run/user/"), xdgRuntimeDirSize, 0755).
|
||||||
Bind(m("/tmp/hakurei.0/runtime/9"), m("/run/user/65534"), std.BindWritable).
|
Bind(m("/tmp/hakurei.0/runtime/9"), m("/run/user/65534"), std.BindWritable).
|
||||||
Bind(m("/tmp/hakurei.0/tmpdir/9"), m("/tmp/"), std.BindWritable).
|
Bind(m("/tmp/hakurei.0/tmpdir/9"), m("/tmp/"), std.BindWritable).
|
||||||
Place(m("/etc/passwd"), []byte("chronos:x:65534:65534:Hakurei:/home/chronos:/run/current-system/sw/bin/zsh\n")).
|
Place(m("/etc/passwd"), []byte("chronos:x:65534:65534:Hakurei:/home/chronos:/run/current-system/sw/bin/zsh\n")).
|
||||||
Place(m("/etc/group"), []byte("hakurei:x:65534:\n")).
|
Place(m("/etc/group"), []byte("hakurei:x:65534:\n")).
|
||||||
Bind(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/wayland"), m("/run/user/65534/wayland-0"), 0).
|
Bind(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/wayland"), m("/run/user/65534/wayland-0"), 0).
|
||||||
Bind(m("/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c/pulse"), m("/run/user/65534/pulse/native"), 0).
|
Bind(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/pipewire"), m("/run/user/65534/pipewire-0"), 0).
|
||||||
Place(m(hst.PrivateTmp+"/pulse-cookie"), bytes.Repeat([]byte{0}, pulseCookieSizeMax)).
|
|
||||||
Bind(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/bus"), m("/run/user/65534/bus"), 0).
|
Bind(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/bus"), m("/run/user/65534/bus"), 0).
|
||||||
Bind(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/system_bus_socket"), m("/var/run/dbus/system_bus_socket"), 0).
|
Bind(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/system_bus_socket"), m("/var/run/dbus/system_bus_socket"), 0).
|
||||||
Bind(m("/dev/dri"), m("/dev/dri"), std.BindWritable|std.BindDevice|std.BindOptional).
|
Bind(m("/dev/dri"), m("/dev/dri"), std.BindWritable|std.BindDevice|std.BindOptional).
|
||||||
@@ -440,7 +423,7 @@ func TestOutcomeMain(t *testing.T) {
|
|||||||
|
|
||||||
{"nixos chromium direct wayland", new(stubNixOS), &hst.Config{
|
{"nixos chromium direct wayland", new(stubNixOS), &hst.Config{
|
||||||
ID: "org.chromium.Chromium",
|
ID: "org.chromium.Chromium",
|
||||||
Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPulse),
|
Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPipeWire | hst.EPulse),
|
||||||
Container: &hst.ContainerConfig{
|
Container: &hst.ContainerConfig{
|
||||||
Env: nil,
|
Env: nil,
|
||||||
Filesystem: []hst.FilesystemConfigJSON{
|
Filesystem: []hst.FilesystemConfigJSON{
|
||||||
@@ -502,9 +485,8 @@ func TestOutcomeMain(t *testing.T) {
|
|||||||
Ensure(m("/run/user/1971"), 0700).UpdatePermType(system.User, m("/run/user/1971"), acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
|
Ensure(m("/run/user/1971"), 0700).UpdatePermType(system.User, m("/run/user/1971"), acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
|
||||||
Ensure(m("/run/user/1971/hakurei"), 0700).UpdatePermType(system.User, m("/run/user/1971/hakurei"), acl.Execute).
|
Ensure(m("/run/user/1971/hakurei"), 0700).UpdatePermType(system.User, m("/run/user/1971/hakurei"), acl.Execute).
|
||||||
UpdatePermType(hst.EWayland, m("/run/user/1971/wayland-0"), acl.Read, acl.Write, acl.Execute).
|
UpdatePermType(hst.EWayland, m("/run/user/1971/wayland-0"), acl.Read, acl.Write, acl.Execute).
|
||||||
Ephemeral(system.Process, m("/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1"), 0700).UpdatePermType(system.Process, m("/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1"), acl.Execute).
|
|
||||||
Link(m("/run/user/1971/pulse/native"), m("/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1/pulse")).
|
|
||||||
Ephemeral(system.Process, m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1"), 0711).
|
Ephemeral(system.Process, m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1"), 0711).
|
||||||
|
PipeWire(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/pipewire")).
|
||||||
MustProxyDBus(&hst.BusConfig{
|
MustProxyDBus(&hst.BusConfig{
|
||||||
Talk: []string{
|
Talk: []string{
|
||||||
"org.freedesktop.FileManager1", "org.freedesktop.Notifications",
|
"org.freedesktop.FileManager1", "org.freedesktop.Notifications",
|
||||||
@@ -544,8 +526,7 @@ func TestOutcomeMain(t *testing.T) {
|
|||||||
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1971/bus",
|
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1971/bus",
|
||||||
"DBUS_SYSTEM_BUS_ADDRESS=unix:path=/var/run/dbus/system_bus_socket",
|
"DBUS_SYSTEM_BUS_ADDRESS=unix:path=/var/run/dbus/system_bus_socket",
|
||||||
"HOME=/var/lib/persist/module/hakurei/0/1",
|
"HOME=/var/lib/persist/module/hakurei/0/1",
|
||||||
"PULSE_COOKIE=" + hst.PrivateTmp + "/pulse-cookie",
|
"PIPEWIRE_REMOTE=/run/user/1971/pipewire-0",
|
||||||
"PULSE_SERVER=unix:/run/user/1971/pulse/native",
|
|
||||||
"SHELL=/run/current-system/sw/bin/zsh",
|
"SHELL=/run/current-system/sw/bin/zsh",
|
||||||
"TERM=xterm-256color",
|
"TERM=xterm-256color",
|
||||||
"USER=u0_a1",
|
"USER=u0_a1",
|
||||||
@@ -559,14 +540,13 @@ func TestOutcomeMain(t *testing.T) {
|
|||||||
Tmpfs(hst.AbsPrivateTmp, 4096, 0755).
|
Tmpfs(hst.AbsPrivateTmp, 4096, 0755).
|
||||||
DevWritable(m("/dev/"), true).
|
DevWritable(m("/dev/"), true).
|
||||||
Tmpfs(m("/dev/shm/"), 0, 01777).
|
Tmpfs(m("/dev/shm/"), 0, 01777).
|
||||||
Tmpfs(m("/run/user/"), 4096, 0755).
|
Tmpfs(m("/run/user/"), xdgRuntimeDirSize, 0755).
|
||||||
Bind(m("/tmp/hakurei.0/runtime/1"), m("/run/user/1971"), std.BindWritable).
|
Bind(m("/tmp/hakurei.0/runtime/1"), m("/run/user/1971"), std.BindWritable).
|
||||||
Bind(m("/tmp/hakurei.0/tmpdir/1"), m("/tmp/"), std.BindWritable).
|
Bind(m("/tmp/hakurei.0/tmpdir/1"), m("/tmp/"), std.BindWritable).
|
||||||
Place(m("/etc/passwd"), []byte("u0_a1:x:1971:100:Hakurei:/var/lib/persist/module/hakurei/0/1:/run/current-system/sw/bin/zsh\n")).
|
Place(m("/etc/passwd"), []byte("u0_a1:x:1971:100:Hakurei:/var/lib/persist/module/hakurei/0/1:/run/current-system/sw/bin/zsh\n")).
|
||||||
Place(m("/etc/group"), []byte("hakurei:x:100:\n")).
|
Place(m("/etc/group"), []byte("hakurei:x:100:\n")).
|
||||||
Bind(m("/run/user/1971/wayland-0"), m("/run/user/1971/wayland-0"), 0).
|
Bind(m("/run/user/1971/wayland-0"), m("/run/user/1971/wayland-0"), 0).
|
||||||
Bind(m("/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1/pulse"), m("/run/user/1971/pulse/native"), 0).
|
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/pipewire"), m("/run/user/1971/pipewire-0"), 0).
|
||||||
Place(m(hst.PrivateTmp+"/pulse-cookie"), bytes.Repeat([]byte{0}, pulseCookieSizeMax)).
|
|
||||||
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus"), m("/run/user/1971/bus"), 0).
|
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus"), m("/run/user/1971/bus"), 0).
|
||||||
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket"), m("/var/run/dbus/system_bus_socket"), 0).
|
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket"), m("/var/run/dbus/system_bus_socket"), 0).
|
||||||
Bind(m("/bin"), m("/bin"), 0).
|
Bind(m("/bin"), m("/bin"), 0).
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ func TestShimEntrypoint(t *testing.T) {
|
|||||||
Tmpfs(fhs.AbsDevShm, 0, 01777).
|
Tmpfs(fhs.AbsDevShm, 0, 01777).
|
||||||
|
|
||||||
// spRuntimeOp
|
// spRuntimeOp
|
||||||
Tmpfs(fhs.AbsRunUser, 1<<12, 0755).
|
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
||||||
Bind(m("/tmp/hakurei.10/runtime/9999"), m("/run/user/1000"), std.BindWritable).
|
Bind(m("/tmp/hakurei.10/runtime/9999"), m("/run/user/1000"), std.BindWritable).
|
||||||
|
|
||||||
// spTmpdirOp
|
// spTmpdirOp
|
||||||
|
|||||||
@@ -382,6 +382,10 @@ func (p opsAdapter) Link(target *check.Absolute, linkName string, dereference bo
|
|||||||
return opsAdapter{p.Ops.Link(target, linkName, dereference)}
|
return opsAdapter{p.Ops.Link(target, linkName, dereference)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p opsAdapter) Daemon(target, path *check.Absolute, args ...string) hst.Ops {
|
||||||
|
return opsAdapter{p.Ops.Daemon(target, path, args...)}
|
||||||
|
}
|
||||||
|
|
||||||
func (p opsAdapter) Root(host *check.Absolute, flags int) hst.Ops {
|
func (p opsAdapter) Root(host *check.Absolute, flags int) hst.Ops {
|
||||||
return opsAdapter{p.Ops.Root(host, flags)}
|
return opsAdapter{p.Ops.Root(host, flags)}
|
||||||
}
|
}
|
||||||
|
|||||||
31
internal/outcome/sppipewire.go
Normal file
31
internal/outcome/sppipewire.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package outcome
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/gob"
|
||||||
|
|
||||||
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/internal/pipewire"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() { gob.Register(spPipeWireOp{}) }
|
||||||
|
|
||||||
|
// spPipeWireOp exports the PipeWire server to the container via SecurityContext.
|
||||||
|
// Runs after spRuntimeOp.
|
||||||
|
type spPipeWireOp struct{}
|
||||||
|
|
||||||
|
func (s spPipeWireOp) toSystem(state *outcomeStateSys) error {
|
||||||
|
if state.et&hst.EPipeWire == 0 {
|
||||||
|
return errNotEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
state.sys.PipeWire(state.instance().Append("pipewire"))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s spPipeWireOp) toContainer(state *outcomeStateParams) error {
|
||||||
|
innerPath := state.runtimeDir.Append(pipewire.PW_DEFAULT_REMOTE)
|
||||||
|
state.env[pipewire.Remote] = innerPath.String()
|
||||||
|
state.params.Bind(state.instancePath().Append("pipewire"), innerPath, 0)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
43
internal/outcome/sppipewire_test.go
Normal file
43
internal/outcome/sppipewire_test.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package outcome
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/container/stub"
|
||||||
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/internal/pipewire"
|
||||||
|
"hakurei.app/internal/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSpPipeWireOp(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
config := hst.Template()
|
||||||
|
|
||||||
|
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||||
|
{"not enabled", func(bool, bool) outcomeOp {
|
||||||
|
return spPipeWireOp{}
|
||||||
|
}, func() *hst.Config {
|
||||||
|
c := hst.Template()
|
||||||
|
*c.Enablements = 0
|
||||||
|
return c
|
||||||
|
}, nil, nil, nil, nil, errNotEnabled, nil, nil, nil, nil, nil},
|
||||||
|
|
||||||
|
{"success", func(bool, bool) outcomeOp {
|
||||||
|
return spPipeWireOp{}
|
||||||
|
}, hst.Template, nil, []stub.Call{}, newI().
|
||||||
|
// state.instance
|
||||||
|
Ephemeral(system.Process, m(wantInstancePrefix), 0711).
|
||||||
|
// toSystem
|
||||||
|
PipeWire(
|
||||||
|
m(wantInstancePrefix + "/pipewire"),
|
||||||
|
), sysUsesInstance(nil), nil, insertsOps(afterSpRuntimeOp(nil)), []stub.Call{
|
||||||
|
// this op configures the container state and does not make calls during toContainer
|
||||||
|
}, &container.Params{
|
||||||
|
Ops: new(container.Ops).
|
||||||
|
Bind(m(wantInstancePrefix+"/pipewire"), m("/run/user/1000/pipewire-0"), 0),
|
||||||
|
}, paramsWantEnv(config, map[string]string{
|
||||||
|
pipewire.Remote: "/run/user/1000/pipewire-0",
|
||||||
|
}, nil), nil},
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -29,7 +29,7 @@ type spPulseOp struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *spPulseOp) toSystem(state *outcomeStateSys) error {
|
func (s *spPulseOp) toSystem(state *outcomeStateSys) error {
|
||||||
if state.et&hst.EPulse == 0 {
|
if !state.directPulse || state.et&hst.EPulse == 0 {
|
||||||
return errNotEnabled
|
return errNotEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,24 +18,40 @@ import (
|
|||||||
func TestSpPulseOp(t *testing.T) {
|
func TestSpPulseOp(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
config := hst.Template()
|
newConfig := func() *hst.Config {
|
||||||
|
config := hst.Template()
|
||||||
|
config.DirectPulse = true
|
||||||
|
config.Enablements = hst.NewEnablements(hst.EPulse)
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
config := newConfig()
|
||||||
sampleCookie := bytes.Repeat([]byte{0xfc}, pulseCookieSizeMax)
|
sampleCookie := bytes.Repeat([]byte{0xfc}, pulseCookieSizeMax)
|
||||||
|
|
||||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||||
{"not enabled", func(bool, bool) outcomeOp {
|
{"not enabled", func(bool, bool) outcomeOp {
|
||||||
return new(spPulseOp)
|
return new(spPulseOp)
|
||||||
}, func() *hst.Config {
|
}, func() *hst.Config {
|
||||||
c := hst.Template()
|
c := newConfig()
|
||||||
|
c.DirectPulse = true
|
||||||
*c.Enablements = 0
|
*c.Enablements = 0
|
||||||
return c
|
return c
|
||||||
}, nil, nil, nil, nil, errNotEnabled, nil, nil, nil, nil, nil},
|
}, nil, nil, nil, nil, errNotEnabled, nil, nil, nil, nil, nil},
|
||||||
|
|
||||||
|
{"not enabled direct", func(bool, bool) outcomeOp {
|
||||||
|
return new(spPulseOp)
|
||||||
|
}, func() *hst.Config {
|
||||||
|
c := newConfig()
|
||||||
|
c.DirectPulse = false
|
||||||
|
return c
|
||||||
|
}, nil, nil, nil, nil, errNotEnabled, nil, nil, nil, nil, nil},
|
||||||
|
|
||||||
{"socketDir stat", func(isShim, _ bool) outcomeOp {
|
{"socketDir stat", func(isShim, _ bool) outcomeOp {
|
||||||
if !isShim {
|
if !isShim {
|
||||||
return new(spPulseOp)
|
return new(spPulseOp)
|
||||||
}
|
}
|
||||||
return &spPulseOp{Cookie: (*[256]byte)(sampleCookie)}
|
return &spPulseOp{Cookie: (*[256]byte)(sampleCookie)}
|
||||||
}, hst.Template, nil, []stub.Call{
|
}, newConfig, nil, []stub.Call{
|
||||||
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), stub.UniqueError(2)),
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), stub.UniqueError(2)),
|
||||||
}, nil, nil, &hst.AppError{
|
}, nil, nil, &hst.AppError{
|
||||||
Step: `access PulseAudio directory "/proc/nonexistent/xdg_runtime_dir/pulse"`,
|
Step: `access PulseAudio directory "/proc/nonexistent/xdg_runtime_dir/pulse"`,
|
||||||
@@ -44,7 +60,7 @@ func TestSpPulseOp(t *testing.T) {
|
|||||||
|
|
||||||
{"socketDir nonexistent", func(bool, bool) outcomeOp {
|
{"socketDir nonexistent", func(bool, bool) outcomeOp {
|
||||||
return new(spPulseOp)
|
return new(spPulseOp)
|
||||||
}, hst.Template, nil, []stub.Call{
|
}, newConfig, nil, []stub.Call{
|
||||||
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), os.ErrNotExist),
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), os.ErrNotExist),
|
||||||
}, nil, nil, &hst.AppError{
|
}, nil, nil, &hst.AppError{
|
||||||
Step: "finalise",
|
Step: "finalise",
|
||||||
@@ -54,7 +70,7 @@ func TestSpPulseOp(t *testing.T) {
|
|||||||
|
|
||||||
{"socket stat", func(bool, bool) outcomeOp {
|
{"socket stat", func(bool, bool) outcomeOp {
|
||||||
return new(spPulseOp)
|
return new(spPulseOp)
|
||||||
}, hst.Template, nil, []stub.Call{
|
}, newConfig, nil, []stub.Call{
|
||||||
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
||||||
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, (*stubFi)(nil), stub.UniqueError(1)),
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, (*stubFi)(nil), stub.UniqueError(1)),
|
||||||
}, nil, nil, &hst.AppError{
|
}, nil, nil, &hst.AppError{
|
||||||
@@ -64,7 +80,7 @@ func TestSpPulseOp(t *testing.T) {
|
|||||||
|
|
||||||
{"socket nonexistent", func(bool, bool) outcomeOp {
|
{"socket nonexistent", func(bool, bool) outcomeOp {
|
||||||
return new(spPulseOp)
|
return new(spPulseOp)
|
||||||
}, hst.Template, nil, []stub.Call{
|
}, newConfig, nil, []stub.Call{
|
||||||
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
||||||
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, (*stubFi)(nil), os.ErrNotExist),
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, (*stubFi)(nil), os.ErrNotExist),
|
||||||
}, nil, nil, &hst.AppError{
|
}, nil, nil, &hst.AppError{
|
||||||
@@ -75,7 +91,7 @@ func TestSpPulseOp(t *testing.T) {
|
|||||||
|
|
||||||
{"socket mode", func(bool, bool) outcomeOp {
|
{"socket mode", func(bool, bool) outcomeOp {
|
||||||
return new(spPulseOp)
|
return new(spPulseOp)
|
||||||
}, hst.Template, nil, []stub.Call{
|
}, newConfig, nil, []stub.Call{
|
||||||
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
||||||
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, &stubFi{mode: 0660}, nil),
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, &stubFi{mode: 0660}, nil),
|
||||||
}, nil, nil, &hst.AppError{
|
}, nil, nil, &hst.AppError{
|
||||||
@@ -86,7 +102,7 @@ func TestSpPulseOp(t *testing.T) {
|
|||||||
|
|
||||||
{"cookie notAbs", func(bool, bool) outcomeOp {
|
{"cookie notAbs", func(bool, bool) outcomeOp {
|
||||||
return new(spPulseOp)
|
return new(spPulseOp)
|
||||||
}, hst.Template, nil, []stub.Call{
|
}, newConfig, nil, []stub.Call{
|
||||||
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
||||||
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, &stubFi{mode: 0666}, nil),
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, &stubFi{mode: 0666}, nil),
|
||||||
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, "proc/nonexistent/cookie", nil),
|
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, "proc/nonexistent/cookie", nil),
|
||||||
@@ -97,7 +113,7 @@ func TestSpPulseOp(t *testing.T) {
|
|||||||
|
|
||||||
{"cookie loadFile", func(bool, bool) outcomeOp {
|
{"cookie loadFile", func(bool, bool) outcomeOp {
|
||||||
return new(spPulseOp)
|
return new(spPulseOp)
|
||||||
}, hst.Template, nil, []stub.Call{
|
}, newConfig, nil, []stub.Call{
|
||||||
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
||||||
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, &stubFi{mode: 0666}, nil),
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, &stubFi{mode: 0666}, nil),
|
||||||
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, "/proc/nonexistent/cookie", nil),
|
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, "/proc/nonexistent/cookie", nil),
|
||||||
@@ -118,7 +134,7 @@ func TestSpPulseOp(t *testing.T) {
|
|||||||
op.CookieSize += +0xfd
|
op.CookieSize += +0xfd
|
||||||
}
|
}
|
||||||
return op
|
return op
|
||||||
}, hst.Template, nil, []stub.Call{
|
}, newConfig, nil, []stub.Call{
|
||||||
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
||||||
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, &stubFi{mode: 0666}, nil),
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, &stubFi{mode: 0666}, nil),
|
||||||
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, "/proc/nonexistent/cookie", nil),
|
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, "/proc/nonexistent/cookie", nil),
|
||||||
@@ -150,7 +166,7 @@ func TestSpPulseOp(t *testing.T) {
|
|||||||
sampleCookieTrunc := make([]byte, pulseCookieSizeMax)
|
sampleCookieTrunc := make([]byte, pulseCookieSizeMax)
|
||||||
copy(sampleCookieTrunc, sampleCookie[:len(sampleCookie)-0xe])
|
copy(sampleCookieTrunc, sampleCookie[:len(sampleCookie)-0xe])
|
||||||
return &spPulseOp{Cookie: (*[pulseCookieSizeMax]byte)(sampleCookieTrunc), CookieSize: pulseCookieSizeMax - 0xe}
|
return &spPulseOp{Cookie: (*[pulseCookieSizeMax]byte)(sampleCookieTrunc), CookieSize: pulseCookieSizeMax - 0xe}
|
||||||
}, hst.Template, nil, []stub.Call{
|
}, newConfig, nil, []stub.Call{
|
||||||
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
||||||
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, &stubFi{mode: 0666}, nil),
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, &stubFi{mode: 0666}, nil),
|
||||||
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, "/proc/nonexistent/cookie", nil),
|
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, "/proc/nonexistent/cookie", nil),
|
||||||
@@ -183,7 +199,7 @@ func TestSpPulseOp(t *testing.T) {
|
|||||||
return new(spPulseOp)
|
return new(spPulseOp)
|
||||||
}
|
}
|
||||||
return &spPulseOp{Cookie: (*[pulseCookieSizeMax]byte)(sampleCookie), CookieSize: pulseCookieSizeMax}
|
return &spPulseOp{Cookie: (*[pulseCookieSizeMax]byte)(sampleCookie), CookieSize: pulseCookieSizeMax}
|
||||||
}, hst.Template, nil, []stub.Call{
|
}, newConfig, nil, []stub.Call{
|
||||||
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
||||||
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, &stubFi{mode: 0666}, nil),
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, &stubFi{mode: 0666}, nil),
|
||||||
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, "/proc/nonexistent/cookie", nil),
|
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, "/proc/nonexistent/cookie", nil),
|
||||||
@@ -213,7 +229,7 @@ func TestSpPulseOp(t *testing.T) {
|
|||||||
|
|
||||||
{"success", func(bool, bool) outcomeOp {
|
{"success", func(bool, bool) outcomeOp {
|
||||||
return new(spPulseOp)
|
return new(spPulseOp)
|
||||||
}, hst.Template, nil, []stub.Call{
|
}, newConfig, nil, []stub.Call{
|
||||||
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
||||||
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, &stubFi{mode: 0666}, nil),
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, &stubFi{mode: 0666}, nil),
|
||||||
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, nil, nil),
|
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, nil, nil),
|
||||||
|
|||||||
@@ -91,6 +91,9 @@ func (s *spRuntimeOp) toSystem(state *outcomeStateSys) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// xdgRuntimeDirSize is the size of the filesystem mounted on inner XDG_RUNTIME_DIR.
|
||||||
|
const xdgRuntimeDirSize = 1 << 24
|
||||||
|
|
||||||
func (s *spRuntimeOp) toContainer(state *outcomeStateParams) error {
|
func (s *spRuntimeOp) toContainer(state *outcomeStateParams) error {
|
||||||
state.runtimeDir = fhs.AbsRunUser.Append(state.mapuid.String())
|
state.runtimeDir = fhs.AbsRunUser.Append(state.mapuid.String())
|
||||||
state.env[envXDGRuntimeDir] = state.runtimeDir.String()
|
state.env[envXDGRuntimeDir] = state.runtimeDir.String()
|
||||||
@@ -108,7 +111,7 @@ func (s *spRuntimeOp) toContainer(state *outcomeStateParams) error {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state.params.Tmpfs(fhs.AbsRunUser, 1<<12, 0755)
|
state.params.Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755)
|
||||||
if state.Container.Flags&hst.FShareRuntime != 0 {
|
if state.Container.Flags&hst.FShareRuntime != 0 {
|
||||||
_, runtimeDirInst := s.commonPaths(state.outcomeState)
|
_, runtimeDirInst := s.commonPaths(state.outcomeState)
|
||||||
state.params.Bind(runtimeDirInst, state.runtimeDir, std.BindWritable)
|
state.params.Bind(runtimeDirInst, state.runtimeDir, std.BindWritable)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func TestSpRuntimeOp(t *testing.T) {
|
|||||||
// this op configures the container state and does not make calls during toContainer
|
// this op configures the container state and does not make calls during toContainer
|
||||||
}, &container.Params{
|
}, &container.Params{
|
||||||
Ops: new(container.Ops).
|
Ops: new(container.Ops).
|
||||||
Tmpfs(fhs.AbsRunUser, 1<<12, 0755).
|
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
||||||
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), std.BindWritable),
|
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), std.BindWritable),
|
||||||
}, paramsWantEnv(config, map[string]string{
|
}, paramsWantEnv(config, map[string]string{
|
||||||
"XDG_RUNTIME_DIR": "/run/user/1000",
|
"XDG_RUNTIME_DIR": "/run/user/1000",
|
||||||
@@ -67,7 +67,7 @@ func TestSpRuntimeOp(t *testing.T) {
|
|||||||
// this op configures the container state and does not make calls during toContainer
|
// this op configures the container state and does not make calls during toContainer
|
||||||
}, &container.Params{
|
}, &container.Params{
|
||||||
Ops: new(container.Ops).
|
Ops: new(container.Ops).
|
||||||
Tmpfs(fhs.AbsRunUser, 1<<12, 0755).
|
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
||||||
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), std.BindWritable),
|
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), std.BindWritable),
|
||||||
}, paramsWantEnv(config, map[string]string{
|
}, paramsWantEnv(config, map[string]string{
|
||||||
"XDG_RUNTIME_DIR": "/run/user/1000",
|
"XDG_RUNTIME_DIR": "/run/user/1000",
|
||||||
@@ -94,7 +94,7 @@ func TestSpRuntimeOp(t *testing.T) {
|
|||||||
// this op configures the container state and does not make calls during toContainer
|
// this op configures the container state and does not make calls during toContainer
|
||||||
}, &container.Params{
|
}, &container.Params{
|
||||||
Ops: new(container.Ops).
|
Ops: new(container.Ops).
|
||||||
Tmpfs(fhs.AbsRunUser, 1<<12, 0755).
|
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
||||||
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), std.BindWritable),
|
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), std.BindWritable),
|
||||||
}, paramsWantEnv(config, map[string]string{
|
}, paramsWantEnv(config, map[string]string{
|
||||||
"XDG_RUNTIME_DIR": "/run/user/1000",
|
"XDG_RUNTIME_DIR": "/run/user/1000",
|
||||||
@@ -117,7 +117,7 @@ func TestSpRuntimeOp(t *testing.T) {
|
|||||||
// this op configures the container state and does not make calls during toContainer
|
// this op configures the container state and does not make calls during toContainer
|
||||||
}, &container.Params{
|
}, &container.Params{
|
||||||
Ops: new(container.Ops).
|
Ops: new(container.Ops).
|
||||||
Tmpfs(fhs.AbsRunUser, 1<<12, 0755).
|
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
||||||
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), std.BindWritable),
|
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), std.BindWritable),
|
||||||
}, paramsWantEnv(config, map[string]string{
|
}, paramsWantEnv(config, map[string]string{
|
||||||
"XDG_RUNTIME_DIR": "/run/user/1000",
|
"XDG_RUNTIME_DIR": "/run/user/1000",
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ type Context struct {
|
|||||||
generation Long
|
generation Long
|
||||||
// Pending file descriptors to be sent with the next message.
|
// Pending file descriptors to be sent with the next message.
|
||||||
pendingFiles []int
|
pendingFiles []int
|
||||||
// File count kept track of in [Header].
|
// File count already kept track of in [Header].
|
||||||
headerFiles int
|
headerFiles int
|
||||||
// Files from the server. This is discarded on every Roundtrip so eventProxy
|
// Files from the server. This is discarded on every Roundtrip so eventProxy
|
||||||
// implementations must make sure to close them to avoid leaking fds.
|
// implementations must make sure to close them to avoid leaking fds.
|
||||||
@@ -271,16 +271,20 @@ func (ctx *Context) recvmsg(remaining []byte) (payload []byte, err error) {
|
|||||||
n, oobn, recvflags, err = ctx.conn.Recvmsg(ctx.iovecBuf[len(remaining):], ctx.oobBuf[:], recvmsgFlags)
|
n, oobn, recvflags, err = ctx.conn.Recvmsg(ctx.iovecBuf[len(remaining):], ctx.oobBuf[:], recvmsgFlags)
|
||||||
|
|
||||||
if oob := ctx.oobBuf[:oobn]; len(oob) > 0 {
|
if oob := ctx.oobBuf[:oobn]; len(oob) > 0 {
|
||||||
|
var oobErr error
|
||||||
|
|
||||||
var scm []syscall.SocketControlMessage
|
var scm []syscall.SocketControlMessage
|
||||||
if scm, err = syscall.ParseSocketControlMessage(oob); err != nil {
|
if scm, oobErr = syscall.ParseSocketControlMessage(oob); oobErr != nil {
|
||||||
ctx.closeReceivedFiles()
|
ctx.closeReceivedFiles()
|
||||||
|
err = oobErr
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var fds []int
|
var fds []int
|
||||||
for i := range scm {
|
for i := range scm {
|
||||||
if fds, err = syscall.ParseUnixRights(&scm[i]); err != nil {
|
if fds, oobErr = syscall.ParseUnixRights(&scm[i]); oobErr != nil {
|
||||||
ctx.closeReceivedFiles()
|
ctx.closeReceivedFiles()
|
||||||
|
err = oobErr
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.receivedFiles = append(ctx.receivedFiles, fds...)
|
ctx.receivedFiles = append(ctx.receivedFiles, fds...)
|
||||||
@@ -310,7 +314,7 @@ func (ctx *Context) recvmsg(remaining []byte) (payload []byte, err error) {
|
|||||||
err = syscall.EPIPE // not wrapped as it did not come from the syscall
|
err = syscall.EPIPE // not wrapped as it did not come from the syscall
|
||||||
}
|
}
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
payload = ctx.iovecBuf[:n]
|
payload = ctx.iovecBuf[:len(remaining)+n]
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -581,6 +585,9 @@ func (ctx *Context) roundtrip() (err error) {
|
|||||||
if err = ctx.sendmsg(ctx.buf, ctx.pendingFiles...); err != nil {
|
if err = ctx.sendmsg(ctx.buf, ctx.pendingFiles...); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ctx.buf = ctx.buf[:0]
|
||||||
|
ctx.pendingFiles = ctx.pendingFiles[:0]
|
||||||
|
ctx.headerFiles = 0
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
var danglingFiles DanglingFilesError
|
var danglingFiles DanglingFilesError
|
||||||
@@ -657,10 +664,6 @@ func (ctx *Context) consume(receiveRemaining []byte) (remaining []byte, err erro
|
|||||||
return
|
return
|
||||||
}()
|
}()
|
||||||
|
|
||||||
ctx.buf = ctx.buf[:0]
|
|
||||||
ctx.pendingFiles = ctx.pendingFiles[:0]
|
|
||||||
ctx.headerFiles = 0
|
|
||||||
|
|
||||||
if remaining, err = ctx.recvmsg(receiveRemaining); err != nil {
|
if remaining, err = ctx.recvmsg(receiveRemaining); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -677,11 +680,11 @@ func (ctx *Context) consume(receiveRemaining []byte) (remaining []byte, err erro
|
|||||||
if header.Sequence != ctx.remoteSequence {
|
if header.Sequence != ctx.remoteSequence {
|
||||||
return remaining, UnexpectedSequenceError(header.Sequence)
|
return remaining, UnexpectedSequenceError(header.Sequence)
|
||||||
}
|
}
|
||||||
ctx.remoteSequence++
|
|
||||||
|
|
||||||
if len(remaining) < int(SizeHeader+header.Size) {
|
if len(remaining) < int(SizeHeader+header.Size) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ctx.remoteSequence++
|
||||||
|
|
||||||
proxy, ok := ctx.proxy[header.ID]
|
proxy, ok := ctx.proxy[header.ID]
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -783,6 +786,9 @@ func (ctx *Context) Close() (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remote is the environment (sic) with the remote name.
|
||||||
|
const Remote = "PIPEWIRE_REMOTE"
|
||||||
|
|
||||||
/* modules/module-protocol-native/local-socket.c */
|
/* modules/module-protocol-native/local-socket.c */
|
||||||
|
|
||||||
const DEFAULT_SYSTEM_RUNTIME_DIR = "/run/pipewire"
|
const DEFAULT_SYSTEM_RUNTIME_DIR = "/run/pipewire"
|
||||||
@@ -814,14 +820,13 @@ func connectName(name string, manager bool) (conn *net.UnixConn, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ConnectName connects to a PipeWire remote by name.
|
// ConnectName connects to a PipeWire remote by name.
|
||||||
func ConnectName(name string, manager bool) (ctx *Context, err error) {
|
func ConnectName(name string, manager bool, props SPADict) (ctx *Context, err error) {
|
||||||
var props SPADict
|
|
||||||
if manager {
|
if manager {
|
||||||
props = append(props, SPADictItem{Key: PW_KEY_REMOTE_INTENTION, Value: "manager"})
|
props = append(props, SPADictItem{Key: PW_KEY_REMOTE_INTENTION, Value: "manager"})
|
||||||
}
|
}
|
||||||
|
|
||||||
if name == "" {
|
if name == "" {
|
||||||
if v, ok := os.LookupEnv("PIPEWIRE_REMOTE"); !ok || v == "" {
|
if v, ok := os.LookupEnv(Remote); !ok || v == "" {
|
||||||
name = PW_DEFAULT_REMOTE
|
name = PW_DEFAULT_REMOTE
|
||||||
} else {
|
} else {
|
||||||
name = v
|
name = v
|
||||||
@@ -841,4 +846,6 @@ func ConnectName(name string, manager bool) (ctx *Context, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Connect connects to the PipeWire remote.
|
// Connect connects to the PipeWire remote.
|
||||||
func Connect(manager bool) (ctx *Context, err error) { return ConnectName("", manager) }
|
func Connect(manager bool, props SPADict) (ctx *Context, err error) {
|
||||||
|
return ConnectName("", manager, props)
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ func TestEntryHeader(t *testing.T) {
|
|||||||
{"success high", [entryHeaderSize]byte{0x00, 0xff, 0xca, 0xfe, 0x00, 0x00,
|
{"success high", [entryHeaderSize]byte{0x00, 0xff, 0xca, 0xfe, 0x00, 0x00,
|
||||||
0xff, 0x00}, 0xff, nil},
|
0xff, 0x00}, 0xff, nil},
|
||||||
{"success", [entryHeaderSize]byte{0x00, 0xff, 0xca, 0xfe, 0x00, 0x00,
|
{"success", [entryHeaderSize]byte{0x00, 0xff, 0xca, 0xfe, 0x00, 0x00,
|
||||||
0x09, 0xf6}, hst.EWayland | hst.EPulse, nil},
|
0x09, 0xf6}, hst.EWayland | hst.EPipeWire, nil},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ func (k direct) waylandNew(displayPath, bindPath *check.Absolute, appID, instanc
|
|||||||
return wayland.New(displayPath, bindPath, appID, instanceID)
|
return wayland.New(displayPath, bindPath, appID, instanceID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k direct) pipewireConnect() (*pipewire.Context, error) { return pipewire.Connect(true) }
|
func (k direct) pipewireConnect() (*pipewire.Context, error) { return pipewire.Connect(true, nil) }
|
||||||
|
|
||||||
func (k direct) xcbChangeHosts(mode xcb.HostMode, family xcb.Family, address string) error {
|
func (k direct) xcbChangeHosts(mode xcb.HostMode, family xcb.Family, address string) error {
|
||||||
return xcb.ChangeHosts(mode, family, address)
|
return xcb.ChangeHosts(mode, family, address)
|
||||||
|
|||||||
@@ -196,6 +196,15 @@ in
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
++ optional (app.enablements.pipewire && app.pulse) {
|
||||||
|
type = "daemon";
|
||||||
|
dst = if app.mapRealUid then "/run/user/${toString config.users.users.${username}.uid}/pulse/native" else "/run/user/65534/pulse/native";
|
||||||
|
path = cfg.shell;
|
||||||
|
args = [
|
||||||
|
"-lc"
|
||||||
|
"exec pipewire-pulse"
|
||||||
|
];
|
||||||
|
}
|
||||||
++ [
|
++ [
|
||||||
{
|
{
|
||||||
type = "bind";
|
type = "bind";
|
||||||
|
|||||||
14
options.nix
14
options.nix
@@ -218,7 +218,7 @@ in
|
|||||||
type = nullOr bool;
|
type = nullOr bool;
|
||||||
default = true;
|
default = true;
|
||||||
description = ''
|
description = ''
|
||||||
Whether to share the Wayland socket.
|
Whether to share the Wayland server via security-context-v1.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -238,15 +238,23 @@ in
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
pulse = mkOption {
|
pipewire = mkOption {
|
||||||
type = nullOr bool;
|
type = nullOr bool;
|
||||||
default = true;
|
default = true;
|
||||||
description = ''
|
description = ''
|
||||||
Whether to share the PulseAudio socket and cookie.
|
Whether to share the PipeWire server via SecurityContext.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pulse = mkOption {
|
||||||
|
type = nullOr bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Whether to run the PulseAudio compatibility daemon.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
share = mkOption {
|
share = mkOption {
|
||||||
type = nullOr package;
|
type = nullOr package;
|
||||||
default = null;
|
default = null;
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
|
|
||||||
buildGoModule rec {
|
buildGoModule rec {
|
||||||
pname = "hakurei";
|
pname = "hakurei";
|
||||||
version = "0.3.1";
|
version = "0.3.2";
|
||||||
|
|
||||||
srcFiltered = builtins.path {
|
srcFiltered = builtins.path {
|
||||||
name = "${pname}-src";
|
name = "${pname}-src";
|
||||||
|
|||||||
@@ -133,7 +133,7 @@
|
|||||||
wait_delay = 1;
|
wait_delay = 1;
|
||||||
enablements = {
|
enablements = {
|
||||||
wayland = false;
|
wayland = false;
|
||||||
pulse = false;
|
pipewire = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@
|
|||||||
command = "foot";
|
command = "foot";
|
||||||
enablements = {
|
enablements = {
|
||||||
dbus = false;
|
dbus = false;
|
||||||
pulse = false;
|
pipewire = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@
|
|||||||
command = "foot";
|
command = "foot";
|
||||||
enablements = {
|
enablements = {
|
||||||
dbus = false;
|
dbus = false;
|
||||||
pulse = false;
|
pipewire = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -199,7 +199,7 @@
|
|||||||
wayland = false;
|
wayland = false;
|
||||||
x11 = true;
|
x11 = true;
|
||||||
dbus = false;
|
dbus = false;
|
||||||
pulse = false;
|
pipewire = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -218,7 +218,7 @@
|
|||||||
command = "foot";
|
command = "foot";
|
||||||
enablements = {
|
enablements = {
|
||||||
dbus = false;
|
dbus = false;
|
||||||
pulse = false;
|
pipewire = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -232,7 +232,7 @@
|
|||||||
wayland = false;
|
wayland = false;
|
||||||
x11 = false;
|
x11 = false;
|
||||||
dbus = false;
|
dbus = false;
|
||||||
pulse = false;
|
pipewire = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,9 +15,30 @@
|
|||||||
command = "foot";
|
command = "foot";
|
||||||
enablements = {
|
enablements = {
|
||||||
dbus = false;
|
dbus = false;
|
||||||
pulse = false;
|
pipewire = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
"cat.gensokyo.extern.foot.badDaemon" = {
|
||||||
|
name = "bd-foot";
|
||||||
|
identity = 1;
|
||||||
|
shareUid = true;
|
||||||
|
verbose = true;
|
||||||
|
share = pkgs.foot;
|
||||||
|
packages = [ pkgs.foot ];
|
||||||
|
command = "foot";
|
||||||
|
enablements = {
|
||||||
|
dbus = false;
|
||||||
|
};
|
||||||
|
extraPaths = [
|
||||||
|
{
|
||||||
|
type = "daemon";
|
||||||
|
dst = "/proc/nonexistent";
|
||||||
|
path = "/usr/bin/env";
|
||||||
|
args = [ "false" ];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
extraHomeConfig.home.stateVersion = "23.05";
|
extraHomeConfig.home.stateVersion = "23.05";
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ in
|
|||||||
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus"
|
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus"
|
||||||
"DISPLAY=unix:/tmp/.X11-unix/X0"
|
"DISPLAY=unix:/tmp/.X11-unix/X0"
|
||||||
"HOME=/var/lib/hakurei/u0/a4"
|
"HOME=/var/lib/hakurei/u0/a4"
|
||||||
"PULSE_SERVER=unix:/run/user/65534/pulse/native"
|
"PIPEWIRE_REMOTE=/run/user/65534/pipewire-0"
|
||||||
"SHELL=/run/current-system/sw/bin/bash"
|
"SHELL=/run/current-system/sw/bin/bash"
|
||||||
"TERM=linux"
|
"TERM=linux"
|
||||||
"USER=u0_a4"
|
"USER=u0_a4"
|
||||||
@@ -137,8 +137,12 @@ in
|
|||||||
user = fs "800001ed" {
|
user = fs "800001ed" {
|
||||||
"65534" = fs "800001c0" {
|
"65534" = fs "800001c0" {
|
||||||
bus = fs "10001fd" null null;
|
bus = fs "10001fd" null null;
|
||||||
pulse = fs "800001c0" { native = fs "10001b6" null null; } null;
|
pulse = fs "800001c0" {
|
||||||
|
native = fs "10001ff" null null;
|
||||||
|
pid = fs "1a4" null null;
|
||||||
|
} null;
|
||||||
wayland-0 = fs "1000038" null null;
|
wayland-0 = fs "1000038" null null;
|
||||||
|
pipewire-0 = fs "1000038" null null;
|
||||||
} null;
|
} null;
|
||||||
} null;
|
} null;
|
||||||
} null;
|
} null;
|
||||||
@@ -220,13 +224,13 @@ in
|
|||||||
(ent "/" ignore ignore ignore ignore ignore)
|
(ent "/" ignore ignore ignore ignore ignore)
|
||||||
(ent "/" ignore ignore ignore ignore ignore)
|
(ent "/" ignore ignore ignore ignore ignore)
|
||||||
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=10004,gid=10004")
|
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=10004,gid=10004")
|
||||||
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=10004,gid=10004")
|
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=16384k,mode=755,uid=10004,gid=10004")
|
||||||
(ent "/tmp/hakurei.0/tmpdir/4" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/tmp/hakurei.0/tmpdir/4" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=10004,gid=10004")
|
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=10004,gid=10004")
|
||||||
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=10004,gid=10004")
|
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=10004,gid=10004")
|
||||||
(ent ignore "/run/user/65534/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent ignore "/run/user/65534/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/tmp/.X11-unix" "/tmp/.X11-unix" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/tmp/.X11-unix" "/tmp/.X11-unix" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent ignore "/run/user/65534/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore)
|
(ent ignore "/run/user/65534/pipewire-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent ignore "/run/user/65534/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent ignore "/run/user/65534/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ in
|
|||||||
env = [
|
env = [
|
||||||
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus"
|
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus"
|
||||||
"HOME=/var/lib/hakurei/u0/a3"
|
"HOME=/var/lib/hakurei/u0/a3"
|
||||||
"PULSE_SERVER=unix:/run/user/1000/pulse/native"
|
"PIPEWIRE_REMOTE=/run/user/1000/pipewire-0"
|
||||||
"SHELL=/run/current-system/sw/bin/bash"
|
"SHELL=/run/current-system/sw/bin/bash"
|
||||||
"TERM=linux"
|
"TERM=linux"
|
||||||
"USER=u0_a3"
|
"USER=u0_a3"
|
||||||
@@ -162,8 +162,12 @@ in
|
|||||||
user = fs "800001ed" {
|
user = fs "800001ed" {
|
||||||
"1000" = fs "800001f8" {
|
"1000" = fs "800001f8" {
|
||||||
bus = fs "10001fd" null null;
|
bus = fs "10001fd" null null;
|
||||||
pulse = fs "800001c0" { native = fs "10001b6" null null; } null;
|
pulse = fs "800001c0" {
|
||||||
|
native = fs "10001ff" null null;
|
||||||
|
pid = fs "1a4" null null;
|
||||||
|
} null;
|
||||||
wayland-0 = fs "1000038" null null;
|
wayland-0 = fs "1000038" null null;
|
||||||
|
pipewire-0 = fs "1000038" null null;
|
||||||
} null;
|
} null;
|
||||||
} null;
|
} null;
|
||||||
} null;
|
} null;
|
||||||
@@ -247,13 +251,13 @@ in
|
|||||||
(ent "/" "/dev/pts" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,mode=620,ptmxmode=666")
|
(ent "/" "/dev/pts" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,mode=620,ptmxmode=666")
|
||||||
(ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
|
(ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
|
||||||
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=10003,gid=10003")
|
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=10003,gid=10003")
|
||||||
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=10003,gid=10003")
|
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=16384k,mode=755,uid=10003,gid=10003")
|
||||||
(ent "/tmp/hakurei.0/runtime/3" "/run/user/1000" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/tmp/hakurei.0/runtime/3" "/run/user/1000" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/tmp/hakurei.0/tmpdir/3" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/tmp/hakurei.0/tmpdir/3" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=10003,gid=10003")
|
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=10003,gid=10003")
|
||||||
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=10003,gid=10003")
|
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=10003,gid=10003")
|
||||||
(ent ignore "/run/user/1000/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent ignore "/run/user/1000/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent ignore "/run/user/1000/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore)
|
(ent ignore "/run/user/1000/pipewire-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent ignore "/run/user/1000/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent ignore "/run/user/1000/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
|||||||
@@ -181,7 +181,7 @@
|
|||||||
(ent ignore "/dev/console" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,gid=3,mode=620,ptmxmode=666")
|
(ent ignore "/dev/console" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,gid=3,mode=620,ptmxmode=666")
|
||||||
(ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
|
(ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
|
||||||
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=10000,gid=10000")
|
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=10000,gid=10000")
|
||||||
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=10000,gid=10000")
|
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=16384k,mode=755,uid=10000,gid=10000")
|
||||||
(ent "/tmp/hakurei.0/runtime/0" "/run/user/65534" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/tmp/hakurei.0/runtime/0" "/run/user/65534" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/tmp/hakurei.0/tmpdir/0" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/tmp/hakurei.0/tmpdir/0" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=10000,gid=10000")
|
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=10000,gid=10000")
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ in
|
|||||||
env = [
|
env = [
|
||||||
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus"
|
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus"
|
||||||
"HOME=/var/lib/hakurei/u0/a5"
|
"HOME=/var/lib/hakurei/u0/a5"
|
||||||
"PULSE_SERVER=unix:/run/user/65534/pulse/native"
|
"PIPEWIRE_REMOTE=/run/user/65534/pipewire-0"
|
||||||
"SHELL=/run/current-system/sw/bin/bash"
|
"SHELL=/run/current-system/sw/bin/bash"
|
||||||
"TERM=linux"
|
"TERM=linux"
|
||||||
"USER=u0_a5"
|
"USER=u0_a5"
|
||||||
@@ -160,8 +160,12 @@ in
|
|||||||
user = fs "800001ed" {
|
user = fs "800001ed" {
|
||||||
"65534" = fs "800001f8" {
|
"65534" = fs "800001f8" {
|
||||||
bus = fs "10001fd" null null;
|
bus = fs "10001fd" null null;
|
||||||
pulse = fs "800001c0" { native = fs "10001b6" null null; } null;
|
pulse = fs "800001c0" {
|
||||||
|
native = fs "10001ff" null null;
|
||||||
|
pid = fs "1a4" null null;
|
||||||
|
} null;
|
||||||
wayland-0 = fs "1000038" null null;
|
wayland-0 = fs "1000038" null null;
|
||||||
|
pipewire-0 = fs "1000038" null null;
|
||||||
} null;
|
} null;
|
||||||
} null;
|
} null;
|
||||||
} null;
|
} null;
|
||||||
@@ -245,13 +249,13 @@ in
|
|||||||
(ent ignore "/dev/console" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,gid=3,mode=620,ptmxmode=666")
|
(ent ignore "/dev/console" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,gid=3,mode=620,ptmxmode=666")
|
||||||
(ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
|
(ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
|
||||||
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=10005,gid=10005")
|
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=10005,gid=10005")
|
||||||
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=10005,gid=10005")
|
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=16384k,mode=755,uid=10005,gid=10005")
|
||||||
(ent "/tmp/hakurei.0/runtime/5" "/run/user/65534" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/tmp/hakurei.0/runtime/5" "/run/user/65534" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/tmp/hakurei.0/tmpdir/5" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/tmp/hakurei.0/tmpdir/5" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=10005,gid=10005")
|
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=10005,gid=10005")
|
||||||
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=10005,gid=10005")
|
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=10005,gid=10005")
|
||||||
(ent ignore "/run/user/65534/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent ignore "/run/user/65534/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent ignore "/run/user/65534/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore)
|
(ent ignore "/run/user/65534/pipewire-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent ignore "/run/user/65534/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent ignore "/run/user/65534/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ in
|
|||||||
env = [
|
env = [
|
||||||
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus"
|
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus"
|
||||||
"HOME=/var/lib/hakurei/u0/a1"
|
"HOME=/var/lib/hakurei/u0/a1"
|
||||||
"PULSE_SERVER=unix:/run/user/65534/pulse/native"
|
"PIPEWIRE_REMOTE=/run/user/65534/pipewire-0"
|
||||||
"SHELL=/run/current-system/sw/bin/bash"
|
"SHELL=/run/current-system/sw/bin/bash"
|
||||||
"TERM=linux"
|
"TERM=linux"
|
||||||
"USER=u0_a1"
|
"USER=u0_a1"
|
||||||
@@ -159,8 +159,12 @@ in
|
|||||||
user = fs "800001ed" {
|
user = fs "800001ed" {
|
||||||
"65534" = fs "800001c0" {
|
"65534" = fs "800001c0" {
|
||||||
bus = fs "10001fd" null null;
|
bus = fs "10001fd" null null;
|
||||||
pulse = fs "800001c0" { native = fs "10001b6" null null; } null;
|
pulse = fs "800001c0" {
|
||||||
|
native = fs "10001ff" null null;
|
||||||
|
pid = fs "1a4" null null;
|
||||||
|
} null;
|
||||||
wayland-0 = fs "1000038" null null;
|
wayland-0 = fs "1000038" null null;
|
||||||
|
pipewire-0 = fs "1000038" null null;
|
||||||
} null;
|
} null;
|
||||||
} null;
|
} null;
|
||||||
} null;
|
} null;
|
||||||
@@ -243,12 +247,12 @@ in
|
|||||||
(ent "/" "/dev/pts" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,mode=620,ptmxmode=666")
|
(ent "/" "/dev/pts" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,mode=620,ptmxmode=666")
|
||||||
(ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
|
(ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
|
||||||
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=10001,gid=10001")
|
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=10001,gid=10001")
|
||||||
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=10001,gid=10001")
|
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=16384k,mode=755,uid=10001,gid=10001")
|
||||||
(ent "/" "/tmp" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=10001,gid=10001")
|
(ent "/" "/tmp" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=10001,gid=10001")
|
||||||
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=10001,gid=10001")
|
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=10001,gid=10001")
|
||||||
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=10001,gid=10001")
|
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=10001,gid=10001")
|
||||||
(ent ignore "/run/user/65534/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent ignore "/run/user/65534/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent ignore "/run/user/65534/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore)
|
(ent ignore "/run/user/65534/pipewire-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent ignore "/run/user/65534/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent ignore "/run/user/65534/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ in
|
|||||||
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus"
|
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus"
|
||||||
"DISPLAY=:0"
|
"DISPLAY=:0"
|
||||||
"HOME=/var/lib/hakurei/u0/a2"
|
"HOME=/var/lib/hakurei/u0/a2"
|
||||||
"PULSE_SERVER=unix:/run/user/65534/pulse/native"
|
"PIPEWIRE_REMOTE=/run/user/65534/pipewire-0"
|
||||||
"SHELL=/run/current-system/sw/bin/bash"
|
"SHELL=/run/current-system/sw/bin/bash"
|
||||||
"TERM=linux"
|
"TERM=linux"
|
||||||
"USER=u0_a2"
|
"USER=u0_a2"
|
||||||
@@ -164,8 +164,12 @@ in
|
|||||||
user = fs "800001ed" {
|
user = fs "800001ed" {
|
||||||
"65534" = fs "800001f8" {
|
"65534" = fs "800001f8" {
|
||||||
bus = fs "10001fd" null null;
|
bus = fs "10001fd" null null;
|
||||||
pulse = fs "800001c0" { native = fs "10001b6" null null; } null;
|
pulse = fs "800001c0" {
|
||||||
|
native = fs "10001ff" null null;
|
||||||
|
pid = fs "1a4" null null;
|
||||||
|
} null;
|
||||||
wayland-0 = fs "1000038" null null;
|
wayland-0 = fs "1000038" null null;
|
||||||
|
pipewire-0 = fs "1000038" null null;
|
||||||
} null;
|
} null;
|
||||||
} null;
|
} null;
|
||||||
} null;
|
} null;
|
||||||
@@ -252,14 +256,14 @@ in
|
|||||||
(ent ignore "/dev/console" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,gid=3,mode=620,ptmxmode=666")
|
(ent ignore "/dev/console" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,gid=3,mode=620,ptmxmode=666")
|
||||||
(ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
|
(ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
|
||||||
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=10002,gid=10002")
|
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=10002,gid=10002")
|
||||||
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=10002,gid=10002")
|
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=16384k,mode=755,uid=10002,gid=10002")
|
||||||
(ent "/tmp/hakurei.0/runtime/2" "/run/user/65534" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/tmp/hakurei.0/runtime/2" "/run/user/65534" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/" "/tmp" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=10002,gid=10002")
|
(ent "/" "/tmp" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=10002,gid=10002")
|
||||||
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=10002,gid=10002")
|
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=10002,gid=10002")
|
||||||
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=10002,gid=10002")
|
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=10002,gid=10002")
|
||||||
(ent ignore "/run/user/65534/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent ignore "/run/user/65534/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/tmp/.X11-unix" "/tmp/.X11-unix" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/tmp/.X11-unix" "/tmp/.X11-unix" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent ignore "/run/user/65534/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore)
|
(ent ignore "/run/user/65534/pipewire-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent ignore "/run/user/65534/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent ignore "/run/user/65534/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
|||||||
@@ -83,4 +83,4 @@ swaymsg("exit", succeed=False)
|
|||||||
machine.wait_for_file("/tmp/sway-exit-ok")
|
machine.wait_for_file("/tmp/sway-exit-ok")
|
||||||
|
|
||||||
# Print hakurei runDir contents:
|
# Print hakurei runDir contents:
|
||||||
print(machine.succeed("find /run/user/1000/hakurei"))
|
print(machine.fail("ls /run/user/1000/hakurei"))
|
||||||
|
|||||||
19
test/test.py
19
test/test.py
@@ -160,17 +160,17 @@ machine.succeed("pkill -9 mako")
|
|||||||
# Check revert type selection:
|
# Check revert type selection:
|
||||||
hakurei("-v run --wayland -X --dbus --pulse -u p0 foot && touch /tmp/p0-exit-ok")
|
hakurei("-v run --wayland -X --dbus --pulse -u p0 foot && touch /tmp/p0-exit-ok")
|
||||||
wait_for_window("p0@machine")
|
wait_for_window("p0@machine")
|
||||||
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 10000"))
|
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000"))
|
||||||
hakurei("-v run --wayland -X --dbus --pulse -u p1 foot && touch /tmp/p1-exit-ok")
|
hakurei("-v run --wayland -X --dbus --pulse -u p1 foot && touch /tmp/p1-exit-ok")
|
||||||
wait_for_window("p1@machine")
|
wait_for_window("p1@machine")
|
||||||
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 10000"))
|
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000"))
|
||||||
machine.send_chars("exit\n")
|
machine.send_chars("exit\n")
|
||||||
machine.wait_for_file("/tmp/p1-exit-ok", timeout=15)
|
machine.wait_for_file("/tmp/p1-exit-ok", timeout=15)
|
||||||
# Verify acl is kept alive:
|
# Verify acl is kept alive:
|
||||||
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 10000"))
|
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000"))
|
||||||
machine.send_chars("exit\n")
|
machine.send_chars("exit\n")
|
||||||
machine.wait_for_file("/tmp/p0-exit-ok", timeout=15)
|
machine.wait_for_file("/tmp/p0-exit-ok", timeout=15)
|
||||||
machine.fail("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 10000")
|
machine.fail("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000")
|
||||||
|
|
||||||
# Check invalid identifier fd behaviour:
|
# Check invalid identifier fd behaviour:
|
||||||
machine.fail('echo \'{"container":{"shell":"/proc/nonexistent","home":"/proc/nonexistent","path":"/proc/nonexistent"}}\' | sudo -u alice -i hakurei -v app --identifier-fd 32767 - 2>&1 | tee > /tmp/invalid-identifier-fd')
|
machine.fail('echo \'{"container":{"shell":"/proc/nonexistent","home":"/proc/nonexistent","path":"/proc/nonexistent"}}\' | sudo -u alice -i hakurei -v app --identifier-fd 32767 - 2>&1 | tee > /tmp/invalid-identifier-fd')
|
||||||
@@ -219,15 +219,22 @@ machine.send_chars("exit\n")
|
|||||||
machine.wait_until_fails("pgrep foot", timeout=5)
|
machine.wait_until_fails("pgrep foot", timeout=5)
|
||||||
machine.fail(f"getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep {hakurei_identity(0) + 10000}", timeout=5)
|
machine.fail(f"getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep {hakurei_identity(0) + 10000}", timeout=5)
|
||||||
|
|
||||||
# Test PulseAudio (hakurei does not support PipeWire yet):
|
# Test pipewire-pulse:
|
||||||
swaymsg("exec pa-foot")
|
swaymsg("exec pa-foot")
|
||||||
wait_for_window(f"u0_a{hakurei_identity(1)}@machine")
|
wait_for_window(f"u0_a{hakurei_identity(1)}@machine")
|
||||||
machine.send_chars("clear; pactl info && touch /var/tmp/pulse-ok\n")
|
machine.send_chars("clear; pactl info && touch /var/tmp/pulse-ok\n")
|
||||||
machine.wait_for_file("/var/tmp/pulse-ok", timeout=15)
|
machine.wait_for_file("/var/tmp/pulse-ok", timeout=15)
|
||||||
collect_state_ui("pulse_wayland")
|
collect_state_ui("pulse_wayland")
|
||||||
check_state("pa-foot", {"wayland": True, "pulse": True})
|
check_state("pa-foot", {"wayland": True, "pipewire": True})
|
||||||
|
# Test PipeWire:
|
||||||
|
machine.send_chars("clear; pw-cli i 0 && touch /var/tmp/pw-ok\n")
|
||||||
|
machine.wait_for_file("/var/tmp/pw-ok", timeout=15)
|
||||||
|
collect_state_ui("pipewire_wayland")
|
||||||
machine.send_chars("exit\n")
|
machine.send_chars("exit\n")
|
||||||
machine.wait_until_fails("pgrep foot", timeout=5)
|
machine.wait_until_fails("pgrep foot", timeout=5)
|
||||||
|
# Test PipeWire SecurityContext:
|
||||||
|
machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v run --pulse pactl info")
|
||||||
|
machine.fail("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v run --pulse pactl set-sink-mute @DEFAULT_SINK@ toggle")
|
||||||
|
|
||||||
# Test XWayland (foot does not support X):
|
# Test XWayland (foot does not support X):
|
||||||
swaymsg("exec x11-alacritty")
|
swaymsg("exec x11-alacritty")
|
||||||
|
|||||||
Reference in New Issue
Block a user