Compare commits
2 Commits
develop
...
df389e239f
| Author | SHA1 | Date | |
|---|---|---|---|
|
df389e239f
|
|||
|
5af08cb9bf
|
@@ -2,6 +2,7 @@ name: Test
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
- push
|
- push
|
||||||
|
- pull_request
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
hakurei:
|
hakurei:
|
||||||
@@ -72,23 +73,6 @@ jobs:
|
|||||||
path: result/*
|
path: result/*
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
sharefs:
|
|
||||||
name: ShareFS
|
|
||||||
runs-on: nix
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Run NixOS test
|
|
||||||
run: nix build --out-link "result" --print-out-paths --print-build-logs .#checks.x86_64-linux.sharefs
|
|
||||||
|
|
||||||
- name: Upload test output
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: "sharefs-vm-output"
|
|
||||||
path: result/*
|
|
||||||
retention-days: 1
|
|
||||||
|
|
||||||
hpkg:
|
hpkg:
|
||||||
name: Hpkg
|
name: Hpkg
|
||||||
runs-on: nix
|
runs-on: nix
|
||||||
@@ -113,7 +97,6 @@ jobs:
|
|||||||
- race
|
- race
|
||||||
- sandbox
|
- sandbox
|
||||||
- sandbox-race
|
- sandbox-race
|
||||||
- sharefs
|
|
||||||
- hpkg
|
- hpkg
|
||||||
runs-on: nix
|
runs-on: nix
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -27,7 +27,6 @@ go.work.sum
|
|||||||
|
|
||||||
# go generate
|
# go generate
|
||||||
/cmd/hakurei/LICENSE
|
/cmd/hakurei/LICENSE
|
||||||
/internal/pkg/testdata/testtool
|
|
||||||
|
|
||||||
# release
|
# release
|
||||||
/dist/hakurei-*
|
/dist/hakurei-*
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
|||||||
|
|
||||||
flagPrivateRuntime, flagPrivateTmpdir bool
|
flagPrivateRuntime, flagPrivateTmpdir bool
|
||||||
|
|
||||||
flagWayland, flagX11, flagDBus, flagPipeWire, flagPulse bool
|
flagWayland, flagX11, flagDBus, 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 +146,8 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
|||||||
if flagDBus {
|
if flagDBus {
|
||||||
et |= hst.EDBus
|
et |= hst.EDBus
|
||||||
}
|
}
|
||||||
if flagPipeWire || flagPulse {
|
if flagPulse {
|
||||||
et |= hst.EPipeWire
|
et |= hst.EPulse
|
||||||
}
|
}
|
||||||
|
|
||||||
config := &hst.Config{
|
config := &hst.Config{
|
||||||
@@ -297,10 +297,8 @@ 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 PulseAudio compatibility daemon")
|
"Enable direct connection to PulseAudio")
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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] [--pipewire] [--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] [--pulse] COMMAND [OPTIONS]
|
||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
-X Enable direct connection to X11
|
-X Enable direct connection to X11
|
||||||
@@ -58,14 +58,12 @@ 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 PulseAudio compatibility daemon
|
Enable direct connection to PulseAudio
|
||||||
-u string
|
-u string
|
||||||
Passwd user name within sandbox (default "chronos")
|
Passwd user name within sandbox (default "chronos")
|
||||||
-wayland
|
-wayland
|
||||||
|
|||||||
@@ -28,12 +28,12 @@ func printShowSystem(output io.Writer, short, flagJSON bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Printf("Version:\t%s (libwayland %s)\n", hi.Version, hi.WaylandVersion)
|
|
||||||
t.Printf("User:\t%d\n", hi.User)
|
t.Printf("User:\t%d\n", hi.User)
|
||||||
t.Printf("TempDir:\t%s\n", hi.TempDir)
|
t.Printf("TempDir:\t%s\n", hi.TempDir)
|
||||||
t.Printf("SharePath:\t%s\n", hi.SharePath)
|
t.Printf("SharePath:\t%s\n", hi.SharePath)
|
||||||
t.Printf("RuntimePath:\t%s\n", hi.RuntimePath)
|
t.Printf("RuntimePath:\t%s\n", hi.RuntimePath)
|
||||||
t.Printf("RunDirPath:\t%s\n", hi.RunDirPath)
|
t.Printf("RunDirPath:\t%s\n", hi.RunDirPath)
|
||||||
|
t.Printf("Version:\t%s (libwayland %s) (pipewire %s)\n", hi.Version, hi.WaylandVersion, hi.PipeWireVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
// printShowInstance writes a representation of [hst.State] or [hst.Config] to output.
|
// printShowInstance writes a representation of [hst.State] or [hst.Config] to output.
|
||||||
|
|||||||
@@ -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.EPipeWire),
|
Enablements: hst.NewEnablements(hst.EWayland | hst.EPulse),
|
||||||
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, pipewire
|
Enablements: wayland, dbus, pulseaudio
|
||||||
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, pipewire
|
Enablements: wayland, dbus, pulseaudio
|
||||||
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,
|
||||||
"pipewire": true
|
"pulse": true
|
||||||
},
|
},
|
||||||
"session_bus": {
|
"session_bus": {
|
||||||
"see": null,
|
"see": null,
|
||||||
@@ -366,7 +366,7 @@ App
|
|||||||
"enablements": {
|
"enablements": {
|
||||||
"wayland": true,
|
"wayland": true,
|
||||||
"dbus": true,
|
"dbus": true,
|
||||||
"pipewire": true
|
"pulse": 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,
|
||||||
"pipewire": true
|
"pulse": 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,
|
||||||
"pipewire": true
|
"pulse": 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_audio ? true,
|
allow_pulse ? 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;
|
||||||
pipewire = allow_audio;
|
pulse = allow_pulse;
|
||||||
};
|
};
|
||||||
|
|
||||||
mesa = if gpu then mesaWrappers else null;
|
mesa = if gpu then mesaWrappers else null;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
testers,
|
nixosTest,
|
||||||
callPackage,
|
callPackage,
|
||||||
|
|
||||||
system,
|
system,
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
let
|
let
|
||||||
buildPackage = self.buildPackage.${system};
|
buildPackage = self.buildPackage.${system};
|
||||||
in
|
in
|
||||||
testers.nixosTest {
|
nixosTest {
|
||||||
name = "hpkg";
|
name = "hpkg";
|
||||||
nodes.machine = {
|
nodes.machine = {
|
||||||
environment.etc = {
|
environment.etc = {
|
||||||
|
|||||||
@@ -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, "pipewire": True})
|
check_state("foot", {"wayland": True, "dbus": True, "pulse": True})
|
||||||
# Verify acl on XDG_RUNTIME_DIR:
|
# Verify acl on XDG_RUNTIME_DIR:
|
||||||
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10002"))
|
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /run/user/1000 | 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 /tmp/hakurei.0/runtime | grep 10002")
|
machine.wait_until_fails("getfacl --absolute-names --omit-header --numeric /run/user/1000 | 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.fail("ls /run/user/1000/hakurei"))
|
print(machine.succeed("find /run/user/1000/hakurei"))
|
||||||
|
|||||||
204
cmd/mbf/main.go
204
cmd/mbf/main.go
@@ -1,204 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"syscall"
|
|
||||||
"unique"
|
|
||||||
|
|
||||||
"hakurei.app/command"
|
|
||||||
"hakurei.app/container"
|
|
||||||
"hakurei.app/container/check"
|
|
||||||
"hakurei.app/internal/pkg"
|
|
||||||
"hakurei.app/internal/rosa"
|
|
||||||
"hakurei.app/message"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
container.TryArgv0(nil)
|
|
||||||
|
|
||||||
log.SetFlags(0)
|
|
||||||
log.SetPrefix("mbf: ")
|
|
||||||
msg := message.New(log.Default())
|
|
||||||
|
|
||||||
if os.Geteuid() == 0 {
|
|
||||||
log.Fatal("this program must not run as root")
|
|
||||||
}
|
|
||||||
|
|
||||||
var cache *pkg.Cache
|
|
||||||
ctx, stop := signal.NotifyContext(context.Background(),
|
|
||||||
syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
|
||||||
defer stop()
|
|
||||||
defer func() {
|
|
||||||
if cache != nil {
|
|
||||||
cache.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
fmt.Println(r)
|
|
||||||
log.Fatal("consider scrubbing the on-disk cache")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var (
|
|
||||||
flagQuiet bool
|
|
||||||
flagCures int
|
|
||||||
flagBase string
|
|
||||||
flagTShift int
|
|
||||||
)
|
|
||||||
c := command.New(os.Stderr, log.Printf, "mbf", func([]string) (err error) {
|
|
||||||
msg.SwapVerbose(!flagQuiet)
|
|
||||||
|
|
||||||
var base *check.Absolute
|
|
||||||
if flagBase, err = filepath.Abs(flagBase); err != nil {
|
|
||||||
return
|
|
||||||
} else if base, err = check.NewAbs(flagBase); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if cache, err = pkg.Open(ctx, msg, flagCures, base); err == nil {
|
|
||||||
if flagTShift < 0 {
|
|
||||||
cache.SetThreshold(0)
|
|
||||||
} else if flagTShift > 31 {
|
|
||||||
cache.SetThreshold(1 << 31)
|
|
||||||
} else {
|
|
||||||
cache.SetThreshold(1 << flagTShift)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}).Flag(
|
|
||||||
&flagQuiet,
|
|
||||||
"q", command.BoolFlag(false),
|
|
||||||
"Do not print cure messages",
|
|
||||||
).Flag(
|
|
||||||
&flagCures,
|
|
||||||
"cures", command.IntFlag(0),
|
|
||||||
"Maximum number of dependencies to cure at any given time",
|
|
||||||
).Flag(
|
|
||||||
&flagBase,
|
|
||||||
"d", command.StringFlag("cache"),
|
|
||||||
"Directory to store cured artifacts",
|
|
||||||
).Flag(
|
|
||||||
&flagTShift,
|
|
||||||
"tshift", command.IntFlag(-1),
|
|
||||||
"Dependency graph size exponent, to the power of 2",
|
|
||||||
)
|
|
||||||
|
|
||||||
{
|
|
||||||
var flagShifts int
|
|
||||||
c.NewCommand(
|
|
||||||
"scrub", "Examine the on-disk cache for errors",
|
|
||||||
func(args []string) error {
|
|
||||||
if len(args) > 0 {
|
|
||||||
return errors.New("scrub expects no arguments")
|
|
||||||
}
|
|
||||||
if flagShifts < 0 || flagShifts > 31 {
|
|
||||||
flagShifts = 12
|
|
||||||
}
|
|
||||||
return cache.Scrub(runtime.NumCPU() << flagShifts)
|
|
||||||
},
|
|
||||||
).Flag(
|
|
||||||
&flagShifts,
|
|
||||||
"shift", command.IntFlag(12),
|
|
||||||
"Scrub parallelism size exponent, to the power of 2",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.NewCommand(
|
|
||||||
"stage3",
|
|
||||||
"Check for toolchain 3-stage non-determinism",
|
|
||||||
func(args []string) (err error) {
|
|
||||||
_, _, _, stage1 := (rosa.Std - 2).NewLLVM()
|
|
||||||
_, _, _, stage2 := (rosa.Std - 1).NewLLVM()
|
|
||||||
_, _, _, stage3 := rosa.Std.NewLLVM()
|
|
||||||
var (
|
|
||||||
pathname *check.Absolute
|
|
||||||
checksum [2]unique.Handle[pkg.Checksum]
|
|
||||||
)
|
|
||||||
|
|
||||||
if pathname, _, err = cache.Cure(stage1); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Println("stage1:", pathname)
|
|
||||||
|
|
||||||
if pathname, checksum[0], err = cache.Cure(stage2); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Println("stage2:", pathname)
|
|
||||||
if pathname, checksum[1], err = cache.Cure(stage3); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Println("stage3:", pathname)
|
|
||||||
|
|
||||||
if checksum[0] != checksum[1] {
|
|
||||||
err = &pkg.ChecksumMismatchError{
|
|
||||||
Got: checksum[0].Value(),
|
|
||||||
Want: checksum[1].Value(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Println(
|
|
||||||
"stage2 is identical to stage3",
|
|
||||||
"("+pkg.Encode(checksum[0].Value())+")",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
{
|
|
||||||
var (
|
|
||||||
flagDump string
|
|
||||||
)
|
|
||||||
c.NewCommand(
|
|
||||||
"cure",
|
|
||||||
"Cure the named artifact and show its path",
|
|
||||||
func(args []string) error {
|
|
||||||
if len(args) != 1 {
|
|
||||||
return errors.New("cure requires 1 argument")
|
|
||||||
}
|
|
||||||
if p, ok := rosa.ResolveName(args[0]); !ok {
|
|
||||||
return fmt.Errorf("unsupported artifact %q", args[0])
|
|
||||||
} else if flagDump == "" {
|
|
||||||
pathname, _, err := cache.Cure(rosa.Std.Load(p))
|
|
||||||
if err == nil {
|
|
||||||
log.Println(pathname)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
f, err := os.OpenFile(
|
|
||||||
flagDump,
|
|
||||||
os.O_WRONLY|os.O_CREATE|os.O_EXCL,
|
|
||||||
0644,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = cache.EncodeAll(f, rosa.Std.Load(p)); err != nil {
|
|
||||||
_ = f.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return f.Close()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
).
|
|
||||||
Flag(
|
|
||||||
&flagDump,
|
|
||||||
"dump", command.StringFlag(""),
|
|
||||||
"Write IR to specified pathname and terminate",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.MustParse(os.Args[1:], func(err error) {
|
|
||||||
if cache != nil {
|
|
||||||
cache.Close()
|
|
||||||
}
|
|
||||||
log.Fatal(err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,282 +0,0 @@
|
|||||||
#ifndef _GNU_SOURCE
|
|
||||||
#define _GNU_SOURCE /* O_DIRECT */
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
/* TODO(ophestra): remove after 05ce67fea99ca09cd4b6625cff7aec9cc222dd5a reaches a release */
|
|
||||||
#include <sys/syscall.h>
|
|
||||||
|
|
||||||
#include "fuse-operations.h"
|
|
||||||
|
|
||||||
/* MUST_TRANSLATE_PATHNAME translates a userspace pathname to a relative pathname;
|
|
||||||
* the resulting address points to a constant string or part of pathname, it is never heap allocated. */
|
|
||||||
#define MUST_TRANSLATE_PATHNAME(pathname) \
|
|
||||||
do { \
|
|
||||||
if (pathname == NULL) \
|
|
||||||
return -EINVAL; \
|
|
||||||
while (*pathname == '/') \
|
|
||||||
pathname++; \
|
|
||||||
if (*pathname == '\0') \
|
|
||||||
pathname = "."; \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
/* GET_CONTEXT_PRIV obtains fuse context and private data for the calling thread. */
|
|
||||||
#define GET_CONTEXT_PRIV(ctx, priv) \
|
|
||||||
do { \
|
|
||||||
ctx = fuse_get_context(); \
|
|
||||||
priv = ctx->private_data; \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
/* impl_getattr modifies a struct stat from the kernel to present to userspace;
|
|
||||||
* impl_getattr returns a negative errno style error code. */
|
|
||||||
static int impl_getattr(struct fuse_context *ctx, struct stat *statbuf) {
|
|
||||||
/* allowlist of permitted types */
|
|
||||||
if (!S_ISDIR(statbuf->st_mode) && !S_ISREG(statbuf->st_mode) && !S_ISLNK(statbuf->st_mode)) {
|
|
||||||
return -ENOTRECOVERABLE; /* returning an errno causes all operations on the file to return EIO */
|
|
||||||
}
|
|
||||||
|
|
||||||
#define OVERRIDE_PERM(v) (statbuf->st_mode & ~0777) | (v & 0777)
|
|
||||||
if (S_ISDIR(statbuf->st_mode))
|
|
||||||
statbuf->st_mode = OVERRIDE_PERM(SHAREFS_PERM_DIR);
|
|
||||||
else if (S_ISREG(statbuf->st_mode))
|
|
||||||
statbuf->st_mode = OVERRIDE_PERM(SHAREFS_PERM_REG);
|
|
||||||
else
|
|
||||||
statbuf->st_mode = 0; /* should always be symlink in this case */
|
|
||||||
|
|
||||||
statbuf->st_uid = ctx->uid;
|
|
||||||
statbuf->st_gid = SHAREFS_MEDIA_RW_ID;
|
|
||||||
statbuf->st_ctim = statbuf->st_mtim;
|
|
||||||
statbuf->st_nlink = 1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* fuse_operations implementation */
|
|
||||||
|
|
||||||
int sharefs_getattr(const char *pathname, struct stat *statbuf, struct fuse_file_info *fi) {
|
|
||||||
struct fuse_context *ctx;
|
|
||||||
struct sharefs_private *priv;
|
|
||||||
GET_CONTEXT_PRIV(ctx, priv);
|
|
||||||
MUST_TRANSLATE_PATHNAME(pathname);
|
|
||||||
|
|
||||||
(void)fi;
|
|
||||||
|
|
||||||
if (fstatat(priv->dirfd, pathname, statbuf, AT_SYMLINK_NOFOLLOW) == -1)
|
|
||||||
return -errno;
|
|
||||||
return impl_getattr(ctx, statbuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
int sharefs_readdir(const char *pathname, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi, enum fuse_readdir_flags flags) {
|
|
||||||
int fd;
|
|
||||||
DIR *dp;
|
|
||||||
struct stat st;
|
|
||||||
int ret = 0;
|
|
||||||
struct dirent *de;
|
|
||||||
|
|
||||||
struct fuse_context *ctx;
|
|
||||||
struct sharefs_private *priv;
|
|
||||||
GET_CONTEXT_PRIV(ctx, priv);
|
|
||||||
MUST_TRANSLATE_PATHNAME(pathname);
|
|
||||||
|
|
||||||
(void)offset;
|
|
||||||
(void)fi;
|
|
||||||
|
|
||||||
if ((fd = openat(priv->dirfd, pathname, O_RDONLY | O_DIRECTORY | O_CLOEXEC)) == -1)
|
|
||||||
return -errno;
|
|
||||||
if ((dp = fdopendir(fd)) == NULL) {
|
|
||||||
close(fd);
|
|
||||||
return -errno;
|
|
||||||
}
|
|
||||||
|
|
||||||
errno = 0; /* for the next readdir call */
|
|
||||||
while ((de = readdir(dp)) != NULL) {
|
|
||||||
if (flags & FUSE_READDIR_PLUS) {
|
|
||||||
if (fstatat(dirfd(dp), de->d_name, &st, AT_SYMLINK_NOFOLLOW) == -1) {
|
|
||||||
ret = -errno;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((ret = impl_getattr(ctx, &st)) < 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
errno = 0;
|
|
||||||
ret = filler(buf, de->d_name, &st, 0, FUSE_FILL_DIR_PLUS);
|
|
||||||
} else
|
|
||||||
ret = filler(buf, de->d_name, NULL, 0, 0);
|
|
||||||
|
|
||||||
if (ret != 0) {
|
|
||||||
ret = errno != 0 ? -errno : -EIO; /* filler */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
errno = 0; /* for the next readdir call */
|
|
||||||
}
|
|
||||||
if (ret == 0 && errno != 0)
|
|
||||||
ret = -errno; /* readdir */
|
|
||||||
|
|
||||||
closedir(dp);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
int sharefs_mkdir(const char *pathname, mode_t mode) {
|
|
||||||
struct fuse_context *ctx;
|
|
||||||
struct sharefs_private *priv;
|
|
||||||
GET_CONTEXT_PRIV(ctx, priv);
|
|
||||||
MUST_TRANSLATE_PATHNAME(pathname);
|
|
||||||
|
|
||||||
(void)mode;
|
|
||||||
|
|
||||||
if (mkdirat(priv->dirfd, pathname, SHAREFS_PERM_DIR) == -1)
|
|
||||||
return -errno;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int sharefs_unlink(const char *pathname) {
|
|
||||||
struct fuse_context *ctx;
|
|
||||||
struct sharefs_private *priv;
|
|
||||||
GET_CONTEXT_PRIV(ctx, priv);
|
|
||||||
MUST_TRANSLATE_PATHNAME(pathname);
|
|
||||||
|
|
||||||
if (unlinkat(priv->dirfd, pathname, 0) == -1)
|
|
||||||
return -errno;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int sharefs_rmdir(const char *pathname) {
|
|
||||||
struct fuse_context *ctx;
|
|
||||||
struct sharefs_private *priv;
|
|
||||||
GET_CONTEXT_PRIV(ctx, priv);
|
|
||||||
MUST_TRANSLATE_PATHNAME(pathname);
|
|
||||||
|
|
||||||
if (unlinkat(priv->dirfd, pathname, AT_REMOVEDIR) == -1)
|
|
||||||
return -errno;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int sharefs_rename(const char *oldpath, const char *newpath, unsigned int flags) {
|
|
||||||
struct fuse_context *ctx;
|
|
||||||
struct sharefs_private *priv;
|
|
||||||
GET_CONTEXT_PRIV(ctx, priv);
|
|
||||||
MUST_TRANSLATE_PATHNAME(oldpath);
|
|
||||||
MUST_TRANSLATE_PATHNAME(newpath);
|
|
||||||
|
|
||||||
/* TODO(ophestra): replace with wrapper after 05ce67fea99ca09cd4b6625cff7aec9cc222dd5a reaches a release */
|
|
||||||
if (syscall(__NR_renameat2, priv->dirfd, oldpath, priv->dirfd, newpath, flags) == -1)
|
|
||||||
return -errno;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int sharefs_truncate(const char *pathname, off_t length, struct fuse_file_info *fi) {
|
|
||||||
int fd;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
struct fuse_context *ctx;
|
|
||||||
struct sharefs_private *priv;
|
|
||||||
GET_CONTEXT_PRIV(ctx, priv);
|
|
||||||
MUST_TRANSLATE_PATHNAME(pathname);
|
|
||||||
|
|
||||||
(void)fi;
|
|
||||||
|
|
||||||
if ((fd = openat(priv->dirfd, pathname, O_WRONLY | O_CLOEXEC)) == -1)
|
|
||||||
return -errno;
|
|
||||||
if ((ret = ftruncate(fd, length)) == -1)
|
|
||||||
ret = -errno;
|
|
||||||
close(fd);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
int sharefs_utimens(const char *pathname, const struct timespec times[2], struct fuse_file_info *fi) {
|
|
||||||
struct fuse_context *ctx;
|
|
||||||
struct sharefs_private *priv;
|
|
||||||
GET_CONTEXT_PRIV(ctx, priv);
|
|
||||||
MUST_TRANSLATE_PATHNAME(pathname);
|
|
||||||
|
|
||||||
(void)fi;
|
|
||||||
|
|
||||||
if (utimensat(priv->dirfd, pathname, times, AT_SYMLINK_NOFOLLOW) == -1)
|
|
||||||
return -errno;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int sharefs_create(const char *pathname, mode_t mode, struct fuse_file_info *fi) {
|
|
||||||
int fd;
|
|
||||||
|
|
||||||
struct fuse_context *ctx;
|
|
||||||
struct sharefs_private *priv;
|
|
||||||
GET_CONTEXT_PRIV(ctx, priv);
|
|
||||||
MUST_TRANSLATE_PATHNAME(pathname);
|
|
||||||
|
|
||||||
(void)mode;
|
|
||||||
|
|
||||||
if ((fd = openat(priv->dirfd, pathname, fi->flags & ~SHAREFS_FORBIDDEN_FLAGS, SHAREFS_PERM_REG)) == -1)
|
|
||||||
return -errno;
|
|
||||||
fi->fh = fd;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int sharefs_open(const char *pathname, struct fuse_file_info *fi) {
|
|
||||||
int fd;
|
|
||||||
|
|
||||||
struct fuse_context *ctx;
|
|
||||||
struct sharefs_private *priv;
|
|
||||||
GET_CONTEXT_PRIV(ctx, priv);
|
|
||||||
MUST_TRANSLATE_PATHNAME(pathname);
|
|
||||||
|
|
||||||
if ((fd = openat(priv->dirfd, pathname, fi->flags & ~SHAREFS_FORBIDDEN_FLAGS)) == -1)
|
|
||||||
return -errno;
|
|
||||||
fi->fh = fd;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int sharefs_read(const char *pathname, char *buf, size_t count, off_t offset, struct fuse_file_info *fi) {
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
(void)pathname;
|
|
||||||
|
|
||||||
if ((ret = pread(fi->fh, buf, count, offset)) == -1)
|
|
||||||
return -errno;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
int sharefs_write(const char *pathname, const char *buf, size_t count, off_t offset, struct fuse_file_info *fi) {
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
(void)pathname;
|
|
||||||
|
|
||||||
if ((ret = pwrite(fi->fh, buf, count, offset)) == -1)
|
|
||||||
return -errno;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
int sharefs_statfs(const char *pathname, struct statvfs *statbuf) {
|
|
||||||
int fd;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
struct fuse_context *ctx;
|
|
||||||
struct sharefs_private *priv;
|
|
||||||
GET_CONTEXT_PRIV(ctx, priv);
|
|
||||||
MUST_TRANSLATE_PATHNAME(pathname);
|
|
||||||
|
|
||||||
if ((fd = openat(priv->dirfd, pathname, O_RDONLY | O_CLOEXEC)) == -1)
|
|
||||||
return -errno;
|
|
||||||
if ((ret = fstatvfs(fd, statbuf)) == -1)
|
|
||||||
ret = -errno;
|
|
||||||
close(fd);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
int sharefs_release(const char *pathname, struct fuse_file_info *fi) {
|
|
||||||
(void)pathname;
|
|
||||||
|
|
||||||
return close(fi->fh);
|
|
||||||
}
|
|
||||||
|
|
||||||
int sharefs_fsync(const char *pathname, int datasync, struct fuse_file_info *fi) {
|
|
||||||
(void)pathname;
|
|
||||||
|
|
||||||
if (datasync ? fdatasync(fi->fh) : fsync(fi->fh) == -1)
|
|
||||||
return -errno;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
|
|
||||||
#include <fuse.h>
|
|
||||||
#include <fuse_lowlevel.h> /* for fuse_cmdline_help */
|
|
||||||
|
|
||||||
#if (FUSE_VERSION < FUSE_MAKE_VERSION(3, 12))
|
|
||||||
#error This package requires libfuse >= v3.12
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define SHAREFS_MEDIA_RW_ID (1 << 10) - 1 /* owning gid presented to userspace */
|
|
||||||
#define SHAREFS_PERM_DIR 0700 /* permission bits for directories presented to userspace */
|
|
||||||
#define SHAREFS_PERM_REG 0600 /* permission bits for regular files presented to userspace */
|
|
||||||
#define SHAREFS_FORBIDDEN_FLAGS O_DIRECT /* these open flags are cleared unconditionally */
|
|
||||||
|
|
||||||
/* sharefs_private is populated by sharefs_init and contains process-wide context */
|
|
||||||
struct sharefs_private {
|
|
||||||
int dirfd; /* source dirfd opened during sharefs_init */
|
|
||||||
uintptr_t setup; /* cgo handle of opaque setup state */
|
|
||||||
};
|
|
||||||
|
|
||||||
int sharefs_getattr(const char *pathname, struct stat *statbuf, struct fuse_file_info *fi);
|
|
||||||
int sharefs_readdir(const char *pathname, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi, enum fuse_readdir_flags flags);
|
|
||||||
int sharefs_mkdir(const char *pathname, mode_t mode);
|
|
||||||
int sharefs_unlink(const char *pathname);
|
|
||||||
int sharefs_rmdir(const char *pathname);
|
|
||||||
int sharefs_rename(const char *oldpath, const char *newpath, unsigned int flags);
|
|
||||||
int sharefs_truncate(const char *pathname, off_t length, struct fuse_file_info *fi);
|
|
||||||
int sharefs_utimens(const char *pathname, const struct timespec times[2], struct fuse_file_info *fi);
|
|
||||||
int sharefs_create(const char *pathname, mode_t mode, struct fuse_file_info *fi);
|
|
||||||
int sharefs_open(const char *pathname, struct fuse_file_info *fi);
|
|
||||||
int sharefs_read(const char *pathname, char *buf, size_t count, off_t offset, struct fuse_file_info *fi);
|
|
||||||
int sharefs_write(const char *pathname, const char *buf, size_t count, off_t offset, struct fuse_file_info *fi);
|
|
||||||
int sharefs_statfs(const char *pathname, struct statvfs *statbuf);
|
|
||||||
int sharefs_release(const char *pathname, struct fuse_file_info *fi);
|
|
||||||
int sharefs_fsync(const char *pathname, int datasync, struct fuse_file_info *fi);
|
|
||||||
@@ -1,556 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo pkg-config: --static fuse3
|
|
||||||
|
|
||||||
#include "fuse-operations.h"
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
extern void *sharefs_init(struct fuse_conn_info *conn, struct fuse_config *cfg);
|
|
||||||
extern void sharefs_destroy(void *private_data);
|
|
||||||
|
|
||||||
typedef void (*closure)();
|
|
||||||
static inline struct fuse_opt _FUSE_OPT_END() { return (struct fuse_opt)FUSE_OPT_END; };
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/gob"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"os/signal"
|
|
||||||
"path"
|
|
||||||
"runtime"
|
|
||||||
"runtime/cgo"
|
|
||||||
"strconv"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"hakurei.app/container"
|
|
||||||
"hakurei.app/container/check"
|
|
||||||
"hakurei.app/container/std"
|
|
||||||
"hakurei.app/hst"
|
|
||||||
"hakurei.app/internal/helper/proc"
|
|
||||||
"hakurei.app/internal/info"
|
|
||||||
"hakurei.app/message"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
// closure represents a C function pointer.
|
|
||||||
closure = C.closure
|
|
||||||
|
|
||||||
// fuseArgs represents the fuse_args structure.
|
|
||||||
fuseArgs = C.struct_fuse_args
|
|
||||||
|
|
||||||
// setupState holds state used for setup. Its cgo handle is included in
|
|
||||||
// sharefs_private and considered opaque to non-setup callbacks.
|
|
||||||
setupState struct {
|
|
||||||
// Whether sharefs_init failed.
|
|
||||||
initFailed bool
|
|
||||||
|
|
||||||
// Whether to create source directory as root.
|
|
||||||
mkdir bool
|
|
||||||
|
|
||||||
// Open file descriptor to fuse.
|
|
||||||
Fuse int
|
|
||||||
|
|
||||||
// Pathname to open for dirfd.
|
|
||||||
Source *check.Absolute
|
|
||||||
// New uid and gid to set by sharefs_init when starting as root.
|
|
||||||
Setuid, Setgid int
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() { gob.Register(new(setupState)) }
|
|
||||||
|
|
||||||
// destroySetup invalidates the setup [cgo.Handle] in a sharefs_private structure.
|
|
||||||
func destroySetup(private_data unsafe.Pointer) (ok bool) {
|
|
||||||
if private_data == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
priv := (*C.struct_sharefs_private)(private_data)
|
|
||||||
|
|
||||||
if h := cgo.Handle(priv.setup); h != 0 {
|
|
||||||
priv.setup = 0
|
|
||||||
h.Delete()
|
|
||||||
ok = true
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//export sharefs_init
|
|
||||||
func sharefs_init(_ *C.struct_fuse_conn_info, cfg *C.struct_fuse_config) unsafe.Pointer {
|
|
||||||
ctx := C.fuse_get_context()
|
|
||||||
priv := (*C.struct_sharefs_private)(ctx.private_data)
|
|
||||||
setup := cgo.Handle(priv.setup).Value().(*setupState)
|
|
||||||
|
|
||||||
if os.Geteuid() == 0 {
|
|
||||||
log.Println("filesystem daemon must not run as root")
|
|
||||||
goto fail
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.use_ino = C.true
|
|
||||||
cfg.direct_io = C.false
|
|
||||||
// getattr is context-dependent
|
|
||||||
cfg.attr_timeout = 0
|
|
||||||
cfg.entry_timeout = 0
|
|
||||||
cfg.negative_timeout = 0
|
|
||||||
|
|
||||||
// all future filesystem operations happen through this dirfd
|
|
||||||
if fd, err := syscall.Open(setup.Source.String(), syscall.O_DIRECTORY|syscall.O_RDONLY|syscall.O_CLOEXEC, 0); err != nil {
|
|
||||||
log.Printf("cannot open %q: %v", setup.Source, err)
|
|
||||||
goto fail
|
|
||||||
} else if err = syscall.Fchdir(fd); err != nil {
|
|
||||||
_ = syscall.Close(fd)
|
|
||||||
log.Printf("cannot enter %q: %s", setup.Source, err)
|
|
||||||
goto fail
|
|
||||||
} else {
|
|
||||||
priv.dirfd = C.int(fd)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctx.private_data
|
|
||||||
|
|
||||||
fail:
|
|
||||||
setup.initFailed = true
|
|
||||||
C.fuse_exit(ctx.fuse)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//export sharefs_destroy
|
|
||||||
func sharefs_destroy(private_data unsafe.Pointer) {
|
|
||||||
if private_data != nil {
|
|
||||||
destroySetup(private_data)
|
|
||||||
priv := (*C.struct_sharefs_private)(private_data)
|
|
||||||
|
|
||||||
if err := syscall.Close(int(priv.dirfd)); err != nil {
|
|
||||||
log.Printf("cannot close source directory: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// showHelp prints the help message.
|
|
||||||
func showHelp(args *fuseArgs) {
|
|
||||||
executableName := sharefsName
|
|
||||||
if args.argc > 0 {
|
|
||||||
executableName = path.Base(C.GoString(*args.argv))
|
|
||||||
} else if name, err := os.Executable(); err == nil {
|
|
||||||
executableName = path.Base(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("usage: %s [options] <mountpoint>\n\n", executableName)
|
|
||||||
|
|
||||||
fmt.Println("Filesystem options:")
|
|
||||||
fmt.Println(" -o source=/data/media source directory to be mounted")
|
|
||||||
fmt.Println(" -o setuid=1023 uid to run as when starting as root")
|
|
||||||
fmt.Println(" -o setgid=1023 gid to run as when starting as root")
|
|
||||||
|
|
||||||
fmt.Println("\nFUSE options:")
|
|
||||||
C.fuse_cmdline_help()
|
|
||||||
C.fuse_lib_help(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseOpts parses fuse options via fuse_opt_parse.
|
|
||||||
func parseOpts(args *fuseArgs, setup *setupState, log *log.Logger) (ok bool) {
|
|
||||||
var unsafeOpts struct {
|
|
||||||
// Pathname to writable source directory.
|
|
||||||
source *C.char
|
|
||||||
|
|
||||||
// Whether to create source directory as root.
|
|
||||||
mkdir C.int
|
|
||||||
|
|
||||||
// Decimal string representation of uid to set when running as root.
|
|
||||||
setuid *C.char
|
|
||||||
// Decimal string representation of gid to set when running as root.
|
|
||||||
setgid *C.char
|
|
||||||
|
|
||||||
// Decimal string representation of open file descriptor to read setupState from.
|
|
||||||
// This is an internal detail for containerisation and must not be specified directly.
|
|
||||||
setup *C.char
|
|
||||||
}
|
|
||||||
|
|
||||||
if C.fuse_opt_parse(args, unsafe.Pointer(&unsafeOpts), &[]C.struct_fuse_opt{
|
|
||||||
{templ: C.CString("source=%s"), offset: C.ulong(unsafe.Offsetof(unsafeOpts.source)), value: 0},
|
|
||||||
{templ: C.CString("mkdir"), offset: C.ulong(unsafe.Offsetof(unsafeOpts.mkdir)), value: 1},
|
|
||||||
{templ: C.CString("setuid=%s"), offset: C.ulong(unsafe.Offsetof(unsafeOpts.setuid)), value: 0},
|
|
||||||
{templ: C.CString("setgid=%s"), offset: C.ulong(unsafe.Offsetof(unsafeOpts.setgid)), value: 0},
|
|
||||||
|
|
||||||
{templ: C.CString("setup=%s"), offset: C.ulong(unsafe.Offsetof(unsafeOpts.setup)), value: 0},
|
|
||||||
|
|
||||||
C._FUSE_OPT_END(),
|
|
||||||
}[0], nil) == -1 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if unsafeOpts.source != nil {
|
|
||||||
defer C.free(unsafe.Pointer(unsafeOpts.source))
|
|
||||||
}
|
|
||||||
if unsafeOpts.setuid != nil {
|
|
||||||
defer C.free(unsafe.Pointer(unsafeOpts.setuid))
|
|
||||||
}
|
|
||||||
if unsafeOpts.setgid != nil {
|
|
||||||
defer C.free(unsafe.Pointer(unsafeOpts.setgid))
|
|
||||||
}
|
|
||||||
|
|
||||||
if unsafeOpts.setup != nil {
|
|
||||||
defer C.free(unsafe.Pointer(unsafeOpts.setup))
|
|
||||||
|
|
||||||
if v, err := strconv.Atoi(C.GoString(unsafeOpts.setup)); err != nil || v < 3 {
|
|
||||||
log.Println("invalid value for option setup")
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
r := os.NewFile(uintptr(v), "setup")
|
|
||||||
defer func() {
|
|
||||||
if err = r.Close(); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if err = gob.NewDecoder(r).Decode(setup); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if setup.Fuse < 3 {
|
|
||||||
log.Println("invalid file descriptor", setup.Fuse)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if unsafeOpts.source == nil {
|
|
||||||
showHelp(args)
|
|
||||||
return false
|
|
||||||
} else if a, err := check.NewAbs(C.GoString(unsafeOpts.source)); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
setup.Source = a
|
|
||||||
}
|
|
||||||
setup.mkdir = unsafeOpts.mkdir != 0
|
|
||||||
|
|
||||||
if unsafeOpts.setuid == nil {
|
|
||||||
setup.Setuid = -1
|
|
||||||
} else if v, err := strconv.Atoi(C.GoString(unsafeOpts.setuid)); err != nil || v <= 0 {
|
|
||||||
log.Println("invalid value for option setuid")
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
setup.Setuid = v
|
|
||||||
}
|
|
||||||
if unsafeOpts.setgid == nil {
|
|
||||||
setup.Setgid = -1
|
|
||||||
} else if v, err := strconv.Atoi(C.GoString(unsafeOpts.setgid)); err != nil || v <= 0 {
|
|
||||||
log.Println("invalid value for option setgid")
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
setup.Setgid = v
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// copyArgs returns a heap allocated copy of an argument slice in fuse_args representation.
|
|
||||||
func copyArgs(s ...string) fuseArgs {
|
|
||||||
if len(s) == 0 {
|
|
||||||
return fuseArgs{argc: 0, argv: nil, allocated: 0}
|
|
||||||
}
|
|
||||||
args := unsafe.Slice((**C.char)(C.malloc(C.size_t(uintptr(len(s))*unsafe.Sizeof(s[0])))), len(s))
|
|
||||||
for i, arg := range s {
|
|
||||||
args[i] = C.CString(arg)
|
|
||||||
}
|
|
||||||
return fuseArgs{argc: C.int(len(s)), argv: &args[0], allocated: 1}
|
|
||||||
}
|
|
||||||
|
|
||||||
// freeArgs frees the contents of argument list.
|
|
||||||
func freeArgs(args *fuseArgs) { C.fuse_opt_free_args(args) }
|
|
||||||
|
|
||||||
// unsafeAddArgument adds an argument to fuseArgs via fuse_opt_add_arg.
|
|
||||||
// The last byte of arg must be 0.
|
|
||||||
func unsafeAddArgument(args *fuseArgs, arg string) {
|
|
||||||
C.fuse_opt_add_arg(args, (*C.char)(unsafe.Pointer(unsafe.StringData(arg))))
|
|
||||||
}
|
|
||||||
|
|
||||||
func _main(s ...string) (exitCode int) {
|
|
||||||
msg := message.New(log.Default())
|
|
||||||
container.TryArgv0(msg)
|
|
||||||
runtime.LockOSThread()
|
|
||||||
|
|
||||||
// don't mask creation mode, kernel already did that
|
|
||||||
syscall.Umask(0)
|
|
||||||
|
|
||||||
var pinner runtime.Pinner
|
|
||||||
defer pinner.Unpin()
|
|
||||||
|
|
||||||
args := copyArgs(s...)
|
|
||||||
defer freeArgs(&args)
|
|
||||||
|
|
||||||
// this causes the kernel to enforce access control based on
|
|
||||||
// struct stat populated by sharefs_getattr
|
|
||||||
unsafeAddArgument(&args, "-odefault_permissions\x00")
|
|
||||||
|
|
||||||
var priv C.struct_sharefs_private
|
|
||||||
pinner.Pin(&priv)
|
|
||||||
var setup setupState
|
|
||||||
priv.setup = C.uintptr_t(cgo.NewHandle(&setup))
|
|
||||||
defer destroySetup(unsafe.Pointer(&priv))
|
|
||||||
|
|
||||||
var opts C.struct_fuse_cmdline_opts
|
|
||||||
if C.fuse_parse_cmdline(&args, &opts) != 0 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if opts.mountpoint != nil {
|
|
||||||
defer C.free(unsafe.Pointer(opts.mountpoint))
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.show_version != 0 {
|
|
||||||
fmt.Println("hakurei version", info.Version())
|
|
||||||
fmt.Println("FUSE library version", C.GoString(C.fuse_pkgversion()))
|
|
||||||
C.fuse_lowlevel_version()
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.show_help != 0 {
|
|
||||||
showHelp(&args)
|
|
||||||
return 0
|
|
||||||
} else if opts.mountpoint == nil {
|
|
||||||
log.Println("no mountpoint specified")
|
|
||||||
return 2
|
|
||||||
} else {
|
|
||||||
// hack to keep fuse_parse_cmdline happy in the container
|
|
||||||
mountpoint := C.GoString(opts.mountpoint)
|
|
||||||
pathnameArg := -1
|
|
||||||
for i, arg := range s {
|
|
||||||
if arg == mountpoint {
|
|
||||||
pathnameArg = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pathnameArg < 0 {
|
|
||||||
log.Println("mountpoint must be absolute")
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
s[pathnameArg] = container.Nonexistent
|
|
||||||
}
|
|
||||||
|
|
||||||
if !parseOpts(&args, &setup, msg.GetLogger()) {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
asRoot := os.Geteuid() == 0
|
|
||||||
|
|
||||||
if asRoot {
|
|
||||||
if setup.Setuid <= 0 || setup.Setgid <= 0 {
|
|
||||||
log.Println("setuid and setgid must not be 0")
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if setup.Fuse >= 3 {
|
|
||||||
log.Println("filesystem daemon must not run as root")
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if setup.mkdir {
|
|
||||||
if err := os.MkdirAll(setup.Source.String(), 0700); err != nil {
|
|
||||||
if !errors.Is(err, os.ErrExist) {
|
|
||||||
log.Println(err)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
// skip setup for existing source directory
|
|
||||||
} else if err = os.Chown(setup.Source.String(), setup.Setuid, setup.Setgid); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if setup.Fuse < 3 && (setup.Setuid > 0 || setup.Setgid > 0) {
|
|
||||||
log.Println("setuid and setgid has no effect when not starting as root")
|
|
||||||
return 1
|
|
||||||
} else if setup.mkdir {
|
|
||||||
log.Println("mkdir has no effect when not starting as root")
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
op := C.struct_fuse_operations{
|
|
||||||
init: closure(C.sharefs_init),
|
|
||||||
destroy: closure(C.sharefs_destroy),
|
|
||||||
|
|
||||||
// implemented in fuse-helper.c
|
|
||||||
getattr: closure(C.sharefs_getattr),
|
|
||||||
readdir: closure(C.sharefs_readdir),
|
|
||||||
mkdir: closure(C.sharefs_mkdir),
|
|
||||||
unlink: closure(C.sharefs_unlink),
|
|
||||||
rmdir: closure(C.sharefs_rmdir),
|
|
||||||
rename: closure(C.sharefs_rename),
|
|
||||||
truncate: closure(C.sharefs_truncate),
|
|
||||||
utimens: closure(C.sharefs_utimens),
|
|
||||||
create: closure(C.sharefs_create),
|
|
||||||
open: closure(C.sharefs_open),
|
|
||||||
read: closure(C.sharefs_read),
|
|
||||||
write: closure(C.sharefs_write),
|
|
||||||
statfs: closure(C.sharefs_statfs),
|
|
||||||
release: closure(C.sharefs_release),
|
|
||||||
fsync: closure(C.sharefs_fsync),
|
|
||||||
}
|
|
||||||
|
|
||||||
fuse := C.fuse_new_fn(&args, &op, C.size_t(unsafe.Sizeof(op)), unsafe.Pointer(&priv))
|
|
||||||
if fuse == nil {
|
|
||||||
return 3
|
|
||||||
}
|
|
||||||
defer C.fuse_destroy(fuse)
|
|
||||||
se := C.fuse_get_session(fuse)
|
|
||||||
|
|
||||||
if setup.Fuse < 3 {
|
|
||||||
// unconfined, set up mount point and container
|
|
||||||
if C.fuse_mount(fuse, opts.mountpoint) != 0 {
|
|
||||||
return 4
|
|
||||||
}
|
|
||||||
// unmounted by initial process
|
|
||||||
defer func() {
|
|
||||||
if exitCode == 5 {
|
|
||||||
C.fuse_unmount(fuse)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if asRoot {
|
|
||||||
if err := syscall.Setresgid(setup.Setgid, setup.Setgid, setup.Setgid); err != nil {
|
|
||||||
log.Printf("cannot set gid: %v", err)
|
|
||||||
return 5
|
|
||||||
}
|
|
||||||
if err := syscall.Setgroups(nil); err != nil {
|
|
||||||
log.Printf("cannot set supplementary groups: %v", err)
|
|
||||||
return 5
|
|
||||||
}
|
|
||||||
if err := syscall.Setresuid(setup.Setuid, setup.Setuid, setup.Setuid); err != nil {
|
|
||||||
log.Printf("cannot set uid: %v", err)
|
|
||||||
return 5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.SwapVerbose(opts.debug != 0)
|
|
||||||
ctx := context.Background()
|
|
||||||
if opts.foreground != 0 {
|
|
||||||
c, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
|
|
||||||
defer cancel()
|
|
||||||
ctx = c
|
|
||||||
}
|
|
||||||
z := container.New(ctx, msg)
|
|
||||||
z.AllowOrphan = opts.foreground == 0
|
|
||||||
z.Env = os.Environ()
|
|
||||||
|
|
||||||
// keep fuse_parse_cmdline happy in the container
|
|
||||||
z.Tmpfs(check.MustAbs(container.Nonexistent), 1<<10, 0755)
|
|
||||||
|
|
||||||
if a, err := check.NewAbs(container.MustExecutable(msg)); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return 5
|
|
||||||
} else {
|
|
||||||
z.Path = a
|
|
||||||
}
|
|
||||||
z.Args = s
|
|
||||||
z.ForwardCancel = true
|
|
||||||
z.SeccompPresets |= std.PresetStrict
|
|
||||||
z.ParentPerm = 0700
|
|
||||||
z.Bind(setup.Source, setup.Source, std.BindWritable)
|
|
||||||
if !z.AllowOrphan {
|
|
||||||
z.WaitDelay = hst.WaitDelayMax
|
|
||||||
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr
|
|
||||||
}
|
|
||||||
z.Bind(z.Path, z.Path, 0)
|
|
||||||
setup.Fuse = int(proc.ExtraFileSlice(&z.ExtraFiles, os.NewFile(uintptr(C.fuse_session_fd(se)), "fuse")))
|
|
||||||
|
|
||||||
var setupWriter io.WriteCloser
|
|
||||||
if fd, w, err := container.Setup(&z.ExtraFiles); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return 5
|
|
||||||
} else {
|
|
||||||
z.Args = append(z.Args, "-osetup="+strconv.Itoa(fd))
|
|
||||||
setupWriter = w
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := z.Start(); err != nil {
|
|
||||||
if m, ok := message.GetMessage(err); ok {
|
|
||||||
log.Println(m)
|
|
||||||
} else {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
return 5
|
|
||||||
}
|
|
||||||
if err := z.Serve(); err != nil {
|
|
||||||
if m, ok := message.GetMessage(err); ok {
|
|
||||||
log.Println(m)
|
|
||||||
} else {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
return 5
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := gob.NewEncoder(setupWriter).Encode(&setup); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return 5
|
|
||||||
} else if err = setupWriter.Close(); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !z.AllowOrphan {
|
|
||||||
if err := z.Wait(); err != nil {
|
|
||||||
var exitError *exec.ExitError
|
|
||||||
if !errors.As(err, &exitError) || exitError == nil {
|
|
||||||
log.Println(err)
|
|
||||||
return 5
|
|
||||||
}
|
|
||||||
switch code := exitError.ExitCode(); syscall.Signal(code & 0x7f) {
|
|
||||||
case syscall.SIGINT:
|
|
||||||
case syscall.SIGTERM:
|
|
||||||
|
|
||||||
default:
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
} else { // confined
|
|
||||||
C.free(unsafe.Pointer(opts.mountpoint))
|
|
||||||
// must be heap allocated
|
|
||||||
opts.mountpoint = C.CString("/dev/fd/" + strconv.Itoa(setup.Fuse))
|
|
||||||
|
|
||||||
if err := os.Chdir("/"); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if C.fuse_mount(fuse, opts.mountpoint) != 0 {
|
|
||||||
return 4
|
|
||||||
}
|
|
||||||
defer C.fuse_unmount(fuse)
|
|
||||||
|
|
||||||
if C.fuse_set_signal_handlers(se) != 0 {
|
|
||||||
return 6
|
|
||||||
}
|
|
||||||
defer C.fuse_remove_signal_handlers(se)
|
|
||||||
|
|
||||||
if opts.singlethread != 0 {
|
|
||||||
if C.fuse_loop(fuse) != 0 {
|
|
||||||
return 8
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
loopConfig := C.fuse_loop_cfg_create()
|
|
||||||
if loopConfig == nil {
|
|
||||||
return 7
|
|
||||||
}
|
|
||||||
defer C.fuse_loop_cfg_destroy(loopConfig)
|
|
||||||
|
|
||||||
C.fuse_loop_cfg_set_clone_fd(loopConfig, C.uint(opts.clone_fd))
|
|
||||||
|
|
||||||
C.fuse_loop_cfg_set_idle_threads(loopConfig, opts.max_idle_threads)
|
|
||||||
C.fuse_loop_cfg_set_max_threads(loopConfig, opts.max_threads)
|
|
||||||
if C.fuse_loop_mt(fuse, loopConfig) != 0 {
|
|
||||||
return 8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if setup.initFailed {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"log"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"hakurei.app/container/check"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParseOpts(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
args []string
|
|
||||||
want setupState
|
|
||||||
wantLog string
|
|
||||||
wantOk bool
|
|
||||||
}{
|
|
||||||
{"zero length", []string{}, setupState{}, "", false},
|
|
||||||
|
|
||||||
{"not absolute", []string{"sharefs",
|
|
||||||
"-o", "source=nonexistent",
|
|
||||||
"-o", "setuid=1023",
|
|
||||||
"-o", "setgid=1023",
|
|
||||||
}, setupState{}, "sharefs: path \"nonexistent\" is not absolute\n", false},
|
|
||||||
|
|
||||||
{"not specified", []string{"sharefs",
|
|
||||||
"-o", "setuid=1023",
|
|
||||||
"-o", "setgid=1023",
|
|
||||||
}, setupState{}, "", false},
|
|
||||||
|
|
||||||
{"invalid setuid", []string{"sharefs",
|
|
||||||
"-o", "source=/proc/nonexistent",
|
|
||||||
"-o", "setuid=ff",
|
|
||||||
"-o", "setgid=1023",
|
|
||||||
}, setupState{
|
|
||||||
Source: check.MustAbs("/proc/nonexistent"),
|
|
||||||
}, "sharefs: invalid value for option setuid\n", false},
|
|
||||||
|
|
||||||
{"invalid setgid", []string{"sharefs",
|
|
||||||
"-o", "source=/proc/nonexistent",
|
|
||||||
"-o", "setuid=1023",
|
|
||||||
"-o", "setgid=ff",
|
|
||||||
}, setupState{
|
|
||||||
Source: check.MustAbs("/proc/nonexistent"),
|
|
||||||
Setuid: 1023,
|
|
||||||
}, "sharefs: invalid value for option setgid\n", false},
|
|
||||||
|
|
||||||
{"simple", []string{"sharefs",
|
|
||||||
"-o", "source=/proc/nonexistent",
|
|
||||||
}, setupState{
|
|
||||||
Source: check.MustAbs("/proc/nonexistent"),
|
|
||||||
Setuid: -1,
|
|
||||||
Setgid: -1,
|
|
||||||
}, "", true},
|
|
||||||
|
|
||||||
{"root", []string{"sharefs",
|
|
||||||
"-o", "source=/proc/nonexistent",
|
|
||||||
"-o", "setuid=1023",
|
|
||||||
"-o", "setgid=1023",
|
|
||||||
}, setupState{
|
|
||||||
Source: check.MustAbs("/proc/nonexistent"),
|
|
||||||
Setuid: 1023,
|
|
||||||
Setgid: 1023,
|
|
||||||
}, "", true},
|
|
||||||
|
|
||||||
{"setuid", []string{"sharefs",
|
|
||||||
"-o", "source=/proc/nonexistent",
|
|
||||||
"-o", "setuid=1023",
|
|
||||||
}, setupState{
|
|
||||||
Source: check.MustAbs("/proc/nonexistent"),
|
|
||||||
Setuid: 1023,
|
|
||||||
Setgid: -1,
|
|
||||||
}, "", true},
|
|
||||||
|
|
||||||
{"setgid", []string{"sharefs",
|
|
||||||
"-o", "source=/proc/nonexistent",
|
|
||||||
"-o", "setgid=1023",
|
|
||||||
}, setupState{
|
|
||||||
Source: check.MustAbs("/proc/nonexistent"),
|
|
||||||
Setuid: -1,
|
|
||||||
Setgid: 1023,
|
|
||||||
}, "", true},
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var (
|
|
||||||
got setupState
|
|
||||||
buf bytes.Buffer
|
|
||||||
)
|
|
||||||
args := copyArgs(tc.args...)
|
|
||||||
defer freeArgs(&args)
|
|
||||||
unsafeAddArgument(&args, "-odefault_permissions\x00")
|
|
||||||
|
|
||||||
if ok := parseOpts(&args, &got, log.New(&buf, "sharefs: ", 0)); ok != tc.wantOk {
|
|
||||||
t.Errorf("parseOpts: ok = %v, want %v", ok, tc.wantOk)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(&got, &tc.want) {
|
|
||||||
t.Errorf("parseOpts: setup = %#v, want %#v", got, tc.want)
|
|
||||||
}
|
|
||||||
|
|
||||||
if buf.String() != tc.wantLog {
|
|
||||||
t.Errorf("parseOpts: log =\n%s\nwant\n%s", buf.String(), tc.wantLog)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"slices"
|
|
||||||
)
|
|
||||||
|
|
||||||
// sharefsName is the prefix used by log.std in the sharefs process.
|
|
||||||
const sharefsName = "sharefs"
|
|
||||||
|
|
||||||
// handleMountArgs returns an alternative, libfuse-compatible args slice for
|
|
||||||
// args passed by mount -t fuse.sharefs [options] sharefs <mountpoint>.
|
|
||||||
//
|
|
||||||
// In this case, args always has a length of 5 with index 0 being what comes
|
|
||||||
// after "fuse." in the filesystem type, 1 is the uninterpreted string passed
|
|
||||||
// to mount (sharefsName is used as the magic string to enable this hack),
|
|
||||||
// 2 is passed through to libfuse as mountpoint, and 3 is always "-o".
|
|
||||||
func handleMountArgs(args []string) []string {
|
|
||||||
if len(args) == 5 && args[1] == sharefsName && args[3] == "-o" {
|
|
||||||
return []string{sharefsName, args[2], "-o", args[4]}
|
|
||||||
}
|
|
||||||
return slices.Clone(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log.SetFlags(0)
|
|
||||||
log.SetPrefix(sharefsName + ": ")
|
|
||||||
|
|
||||||
os.Exit(_main(handleMountArgs(os.Args)...))
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"slices"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestHandleMountArgs(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
args []string
|
|
||||||
want []string
|
|
||||||
}{
|
|
||||||
{"nil", nil, nil},
|
|
||||||
{"passthrough", []string{"sharefs", "-V"}, []string{"sharefs", "-V"}},
|
|
||||||
{"replace", []string{"/sbin/sharefs", "sharefs", "/sdcard", "-o", "rw"}, []string{"sharefs", "/sdcard", "-o", "rw"}},
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
if got := handleMountArgs(tc.args); !slices.Equal(got, tc.want) {
|
|
||||||
t.Errorf("handleMountArgs: %q, want %q", got, tc.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
{ pkgs, ... }:
|
|
||||||
{
|
|
||||||
users.users = {
|
|
||||||
alice = {
|
|
||||||
isNormalUser = true;
|
|
||||||
description = "Alice Foobar";
|
|
||||||
password = "foobar";
|
|
||||||
uid = 1000;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
home-manager.users.alice.home.stateVersion = "24.11";
|
|
||||||
|
|
||||||
# Automatically login on tty1 as a normal user:
|
|
||||||
services.getty.autologinUser = "alice";
|
|
||||||
|
|
||||||
environment = {
|
|
||||||
# For benchmarking sharefs:
|
|
||||||
systemPackages = [ pkgs.fsmark ];
|
|
||||||
};
|
|
||||||
|
|
||||||
virtualisation = {
|
|
||||||
diskSize = 6 * 1024;
|
|
||||||
|
|
||||||
qemu.options = [
|
|
||||||
# Increase test performance:
|
|
||||||
"-smp 8"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
environment.hakurei = rec {
|
|
||||||
enable = true;
|
|
||||||
stateDir = "/var/lib/hakurei";
|
|
||||||
sharefs.source = "${stateDir}/sdcard";
|
|
||||||
users.alice = 0;
|
|
||||||
|
|
||||||
extraHomeConfig = {
|
|
||||||
home.stateVersion = "23.05";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
testers,
|
|
||||||
|
|
||||||
system,
|
|
||||||
self,
|
|
||||||
}:
|
|
||||||
testers.nixosTest {
|
|
||||||
name = "sharefs";
|
|
||||||
nodes.machine =
|
|
||||||
{ options, pkgs, ... }:
|
|
||||||
let
|
|
||||||
fhs =
|
|
||||||
let
|
|
||||||
hakurei = options.environment.hakurei.package.default;
|
|
||||||
in
|
|
||||||
pkgs.buildFHSEnv {
|
|
||||||
pname = "hakurei-fhs";
|
|
||||||
inherit (hakurei) version;
|
|
||||||
targetPkgs = _: hakurei.targetPkgs;
|
|
||||||
extraOutputsToInstall = [ "dev" ];
|
|
||||||
profile = ''
|
|
||||||
export PKG_CONFIG_PATH="/usr/share/pkgconfig:$PKG_CONFIG_PATH"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
environment.systemPackages = [
|
|
||||||
# For go tests:
|
|
||||||
(pkgs.writeShellScriptBin "sharefs-workload-hakurei-tests" ''
|
|
||||||
cp -r "${self.packages.${system}.hakurei.src}" "/sdcard/hakurei" && cd "/sdcard/hakurei"
|
|
||||||
${fhs}/bin/hakurei-fhs -c 'CC="clang -O3 -Werror" go test ./...'
|
|
||||||
'')
|
|
||||||
];
|
|
||||||
|
|
||||||
imports = [
|
|
||||||
./configuration.nix
|
|
||||||
|
|
||||||
self.nixosModules.hakurei
|
|
||||||
self.inputs.home-manager.nixosModules.home-manager
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript = builtins.readFile ./test.py;
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
start_all()
|
|
||||||
machine.wait_for_unit("multi-user.target")
|
|
||||||
|
|
||||||
# To check sharefs version:
|
|
||||||
print(machine.succeed("sharefs -V"))
|
|
||||||
|
|
||||||
# Make sure sharefs started:
|
|
||||||
machine.wait_for_unit("sdcard.mount")
|
|
||||||
|
|
||||||
machine.succeed("mkdir /mnt")
|
|
||||||
def check_bad_opts_output(opts, want, source="/etc", privileged=False):
|
|
||||||
output = machine.fail(("" if privileged else "sudo -u alice -i ") + f"sharefs -f -o source={source},{opts} /mnt 2>&1")
|
|
||||||
if output != want:
|
|
||||||
raise Exception(f"unexpected output: {output}")
|
|
||||||
|
|
||||||
# Malformed setuid/setgid representation:
|
|
||||||
check_bad_opts_output("setuid=ff", "sharefs: invalid value for option setuid\n")
|
|
||||||
check_bad_opts_output("setgid=ff", "sharefs: invalid value for option setgid\n")
|
|
||||||
|
|
||||||
# Bounds check for setuid/setgid:
|
|
||||||
check_bad_opts_output("setuid=0", "sharefs: invalid value for option setuid\n")
|
|
||||||
check_bad_opts_output("setgid=0", "sharefs: invalid value for option setgid\n")
|
|
||||||
check_bad_opts_output("setuid=-1", "sharefs: invalid value for option setuid\n")
|
|
||||||
check_bad_opts_output("setgid=-1", "sharefs: invalid value for option setgid\n")
|
|
||||||
|
|
||||||
# Non-root setuid/setgid:
|
|
||||||
check_bad_opts_output("setuid=1023", "sharefs: setuid and setgid has no effect when not starting as root\n")
|
|
||||||
check_bad_opts_output("setgid=1023", "sharefs: setuid and setgid has no effect when not starting as root\n")
|
|
||||||
check_bad_opts_output("setuid=1023,setgid=1023", "sharefs: setuid and setgid has no effect when not starting as root\n")
|
|
||||||
check_bad_opts_output("mkdir", "sharefs: mkdir has no effect when not starting as root\n")
|
|
||||||
|
|
||||||
# Starting as root without setuid/setgid:
|
|
||||||
check_bad_opts_output("allow_other", "sharefs: setuid and setgid must not be 0\n", privileged=True)
|
|
||||||
check_bad_opts_output("setuid=1023", "sharefs: setuid and setgid must not be 0\n", privileged=True)
|
|
||||||
check_bad_opts_output("setgid=1023", "sharefs: setuid and setgid must not be 0\n", privileged=True)
|
|
||||||
|
|
||||||
# Make sure nothing actually got mounted:
|
|
||||||
machine.fail("umount /mnt")
|
|
||||||
machine.succeed("rmdir /mnt")
|
|
||||||
|
|
||||||
# Unprivileged mount/unmount:
|
|
||||||
machine.succeed("sudo -u alice -i mkdir /home/alice/{sdcard,persistent}")
|
|
||||||
machine.succeed("sudo -u alice -i sharefs -o source=/home/alice/persistent /home/alice/sdcard")
|
|
||||||
machine.succeed("sudo -u alice -i touch /home/alice/sdcard/check")
|
|
||||||
machine.succeed("sudo -u alice -i umount /home/alice/sdcard")
|
|
||||||
machine.succeed("sudo -u alice -i rm /home/alice/persistent/check")
|
|
||||||
machine.succeed("sudo -u alice -i rmdir /home/alice/{sdcard,persistent}")
|
|
||||||
|
|
||||||
# Benchmark sharefs:
|
|
||||||
machine.succeed("fs_mark -v -d /sdcard/fs_mark -l /tmp/fs_log.txt")
|
|
||||||
machine.copy_from_vm("/tmp/fs_log.txt", "")
|
|
||||||
|
|
||||||
# Check permissions:
|
|
||||||
machine.succeed("sudo -u sharefs touch /var/lib/hakurei/sdcard/fs_mark/.check")
|
|
||||||
machine.succeed("sudo -u sharefs rm /var/lib/hakurei/sdcard/fs_mark/.check")
|
|
||||||
machine.succeed("sudo -u alice rm -rf /sdcard/fs_mark")
|
|
||||||
machine.fail("ls /var/lib/hakurei/sdcard/fs_mark")
|
|
||||||
|
|
||||||
# Run hakurei tests on sharefs:
|
|
||||||
machine.succeed("sudo -u alice -i sharefs-workload-hakurei-tests")
|
|
||||||
@@ -59,7 +59,6 @@ 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,7 +69,6 @@ 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)
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ func TestIsAutoRootBindable(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
var msg message.Msg
|
var msg message.Msg
|
||||||
if tc.log {
|
if tc.log {
|
||||||
msg = &kstub{nil, nil, stub.New(t, func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { panic("unreachable") }, stub.Expect{Calls: []stub.Call{
|
msg = &kstub{nil, stub.New(t, func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { panic("unreachable") }, stub.Expect{Calls: []stub.Call{
|
||||||
call("verbose", stub.ExpectArgs{[]any{"got unexpected root entry"}}, nil, nil),
|
call("verbose", stub.ExpectArgs{[]any{"got unexpected root entry"}}, nil, nil),
|
||||||
}})}
|
}})}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ const (
|
|||||||
|
|
||||||
CAP_SYS_ADMIN = 0x15
|
CAP_SYS_ADMIN = 0x15
|
||||||
CAP_SETPCAP = 0x8
|
CAP_SETPCAP = 0x8
|
||||||
CAP_NET_ADMIN = 0xc
|
|
||||||
CAP_DAC_OVERRIDE = 0x1
|
CAP_DAC_OVERRIDE = 0x1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -9,60 +9,46 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unique"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// AbsoluteError is returned by [NewAbs] and holds the invalid pathname.
|
// AbsoluteError is returned by [NewAbs] and holds the invalid pathname.
|
||||||
type AbsoluteError string
|
type AbsoluteError struct{ Pathname string }
|
||||||
|
|
||||||
func (e AbsoluteError) Error() string {
|
func (e *AbsoluteError) Error() string { return fmt.Sprintf("path %q is not absolute", e.Pathname) }
|
||||||
return fmt.Sprintf("path %q is not absolute", string(e))
|
func (e *AbsoluteError) Is(target error) bool {
|
||||||
}
|
var ce *AbsoluteError
|
||||||
|
|
||||||
func (e AbsoluteError) Is(target error) bool {
|
|
||||||
var ce AbsoluteError
|
|
||||||
if !errors.As(target, &ce) {
|
if !errors.As(target, &ce) {
|
||||||
return errors.Is(target, syscall.EINVAL)
|
return errors.Is(target, syscall.EINVAL)
|
||||||
}
|
}
|
||||||
return e == ce
|
return *e == *ce
|
||||||
}
|
}
|
||||||
|
|
||||||
// Absolute holds a pathname checked to be absolute.
|
// Absolute holds a pathname checked to be absolute.
|
||||||
type Absolute struct{ pathname unique.Handle[string] }
|
type Absolute struct{ pathname string }
|
||||||
|
|
||||||
// ok returns whether [Absolute] is not the zero value.
|
|
||||||
func (a *Absolute) ok() bool { return a != nil && *a != (Absolute{}) }
|
|
||||||
|
|
||||||
// unsafeAbs returns [check.Absolute] on any string value.
|
// unsafeAbs returns [check.Absolute] on any string value.
|
||||||
func unsafeAbs(pathname string) *Absolute {
|
func unsafeAbs(pathname string) *Absolute { return &Absolute{pathname} }
|
||||||
return &Absolute{unique.Make(pathname)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the checked pathname.
|
|
||||||
func (a *Absolute) String() string {
|
func (a *Absolute) String() string {
|
||||||
if !a.ok() {
|
if a.pathname == "" {
|
||||||
panic("attempted use of zero Absolute")
|
panic("attempted use of zero Absolute")
|
||||||
}
|
}
|
||||||
return a.pathname.Value()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle returns the underlying [unique.Handle].
|
|
||||||
func (a *Absolute) Handle() unique.Handle[string] {
|
|
||||||
return a.pathname
|
return a.pathname
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is efficiently compares the underlying pathname.
|
|
||||||
func (a *Absolute) Is(v *Absolute) bool {
|
func (a *Absolute) Is(v *Absolute) bool {
|
||||||
if a == nil && v == nil {
|
if a == nil && v == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return a.ok() && v.ok() && a.pathname == v.pathname
|
return a != nil && v != nil &&
|
||||||
|
a.pathname != "" && v.pathname != "" &&
|
||||||
|
a.pathname == v.pathname
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAbs checks pathname and returns a new [Absolute] if pathname is absolute.
|
// NewAbs checks pathname and returns a new [Absolute] if pathname is absolute.
|
||||||
func NewAbs(pathname string) (*Absolute, error) {
|
func NewAbs(pathname string) (*Absolute, error) {
|
||||||
if !path.IsAbs(pathname) {
|
if !path.IsAbs(pathname) {
|
||||||
return nil, AbsoluteError(pathname)
|
return nil, &AbsoluteError{pathname}
|
||||||
}
|
}
|
||||||
return unsafeAbs(pathname), nil
|
return unsafeAbs(pathname), nil
|
||||||
}
|
}
|
||||||
@@ -84,49 +70,35 @@ func (a *Absolute) Append(elem ...string) *Absolute {
|
|||||||
// Dir calls [path.Dir] with [Absolute] as its argument.
|
// Dir calls [path.Dir] with [Absolute] as its argument.
|
||||||
func (a *Absolute) Dir() *Absolute { return unsafeAbs(path.Dir(a.String())) }
|
func (a *Absolute) Dir() *Absolute { return unsafeAbs(path.Dir(a.String())) }
|
||||||
|
|
||||||
// GobEncode returns the checked pathname.
|
func (a *Absolute) GobEncode() ([]byte, error) { return []byte(a.String()), nil }
|
||||||
func (a *Absolute) GobEncode() ([]byte, error) {
|
|
||||||
return []byte(a.String()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GobDecode stores data if it represents an absolute pathname.
|
|
||||||
func (a *Absolute) GobDecode(data []byte) error {
|
func (a *Absolute) GobDecode(data []byte) error {
|
||||||
pathname := string(data)
|
pathname := string(data)
|
||||||
if !path.IsAbs(pathname) {
|
if !path.IsAbs(pathname) {
|
||||||
return AbsoluteError(pathname)
|
return &AbsoluteError{pathname}
|
||||||
}
|
}
|
||||||
a.pathname = unique.Make(pathname)
|
a.pathname = pathname
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON returns a JSON representation of the checked pathname.
|
func (a *Absolute) MarshalJSON() ([]byte, error) { return json.Marshal(a.String()) }
|
||||||
func (a *Absolute) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(a.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON stores data if it represents an absolute pathname.
|
|
||||||
func (a *Absolute) UnmarshalJSON(data []byte) error {
|
func (a *Absolute) UnmarshalJSON(data []byte) error {
|
||||||
var pathname string
|
var pathname string
|
||||||
if err := json.Unmarshal(data, &pathname); err != nil {
|
if err := json.Unmarshal(data, &pathname); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !path.IsAbs(pathname) {
|
if !path.IsAbs(pathname) {
|
||||||
return AbsoluteError(pathname)
|
return &AbsoluteError{pathname}
|
||||||
}
|
}
|
||||||
a.pathname = unique.Make(pathname)
|
a.pathname = pathname
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SortAbs calls [slices.SortFunc] for a slice of [Absolute].
|
// SortAbs calls [slices.SortFunc] for a slice of [Absolute].
|
||||||
func SortAbs(x []*Absolute) {
|
func SortAbs(x []*Absolute) {
|
||||||
slices.SortFunc(x, func(a, b *Absolute) int {
|
slices.SortFunc(x, func(a, b *Absolute) int { return strings.Compare(a.String(), b.String()) })
|
||||||
return strings.Compare(a.String(), b.String())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CompactAbs calls [slices.CompactFunc] for a slice of [Absolute].
|
// CompactAbs calls [slices.CompactFunc] for a slice of [Absolute].
|
||||||
func CompactAbs(s []*Absolute) []*Absolute {
|
func CompactAbs(s []*Absolute) []*Absolute {
|
||||||
return slices.CompactFunc(s, func(a *Absolute, b *Absolute) bool {
|
return slices.CompactFunc(s, func(a *Absolute, b *Absolute) bool { return a.String() == b.String() })
|
||||||
return a.Is(b)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ func TestAbsoluteError(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{"EINVAL", new(AbsoluteError), syscall.EINVAL, true},
|
{"EINVAL", new(AbsoluteError), syscall.EINVAL, true},
|
||||||
{"not EINVAL", new(AbsoluteError), syscall.EBADE, false},
|
{"not EINVAL", new(AbsoluteError), syscall.EBADE, false},
|
||||||
{"ne val", new(AbsoluteError), AbsoluteError("etc"), false},
|
{"ne val", new(AbsoluteError), &AbsoluteError{Pathname: "etc"}, false},
|
||||||
{"equals", AbsoluteError("etc"), AbsoluteError("etc"), true},
|
{"equals", &AbsoluteError{Pathname: "etc"}, &AbsoluteError{Pathname: "etc"}, true},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
@@ -45,7 +45,7 @@ func TestAbsoluteError(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
want := `path "etc" is not absolute`
|
want := `path "etc" is not absolute`
|
||||||
if got := (AbsoluteError("etc")).Error(); got != want {
|
if got := (&AbsoluteError{Pathname: "etc"}).Error(); got != want {
|
||||||
t.Errorf("Error: %q, want %q", got, want)
|
t.Errorf("Error: %q, want %q", got, want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -62,8 +62,8 @@ func TestNewAbs(t *testing.T) {
|
|||||||
wantErr error
|
wantErr error
|
||||||
}{
|
}{
|
||||||
{"good", "/etc", MustAbs("/etc"), nil},
|
{"good", "/etc", MustAbs("/etc"), nil},
|
||||||
{"not absolute", "etc", nil, AbsoluteError("etc")},
|
{"not absolute", "etc", nil, &AbsoluteError{Pathname: "etc"}},
|
||||||
{"zero", "", nil, AbsoluteError("")},
|
{"zero", "", nil, &AbsoluteError{Pathname: ""}},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
@@ -84,7 +84,7 @@ func TestNewAbs(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
wantPanic := AbsoluteError("etc")
|
wantPanic := &AbsoluteError{Pathname: "etc"}
|
||||||
|
|
||||||
if r := recover(); !reflect.DeepEqual(r, wantPanic) {
|
if r := recover(); !reflect.DeepEqual(r, wantPanic) {
|
||||||
t.Errorf("MustAbs: panic = %v; want %v", r, wantPanic)
|
t.Errorf("MustAbs: panic = %v; want %v", r, wantPanic)
|
||||||
@@ -175,7 +175,7 @@ func TestCodecAbsolute(t *testing.T) {
|
|||||||
|
|
||||||
`"/etc"`, `{"val":"/etc","magic":3236757504}`},
|
`"/etc"`, `{"val":"/etc","magic":3236757504}`},
|
||||||
{"not absolute", nil,
|
{"not absolute", nil,
|
||||||
AbsoluteError("etc"),
|
&AbsoluteError{Pathname: "etc"},
|
||||||
"\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\a\xff\x80\x00\x03etc",
|
"\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\a\xff\x80\x00\x03etc",
|
||||||
",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\x0f\xff\x84\x01\x03etc\x01\xfb\x01\x81\xda\x00\x00\x00",
|
",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\x0f\xff\x84\x01\x03etc\x01\xfb\x01\x81\xda\x00\x00\x00",
|
||||||
|
|
||||||
|
|||||||
@@ -35,8 +35,6 @@ type (
|
|||||||
// Container represents a container environment being prepared or run.
|
// Container represents a container environment being prepared or run.
|
||||||
// None of [Container] methods are safe for concurrent use.
|
// None of [Container] methods are safe for concurrent use.
|
||||||
Container struct {
|
Container struct {
|
||||||
// Whether the container init should stay alive after its parent terminates.
|
|
||||||
AllowOrphan bool
|
|
||||||
// Cgroup fd, nil to disable.
|
// Cgroup fd, nil to disable.
|
||||||
Cgroup *int
|
Cgroup *int
|
||||||
// ExtraFiles passed through to initial process in the container,
|
// ExtraFiles passed through to initial process in the container,
|
||||||
@@ -254,7 +252,8 @@ func (p *Container) Start() error {
|
|||||||
}
|
}
|
||||||
p.cmd.Dir = fhs.Root
|
p.cmd.Dir = fhs.Root
|
||||||
p.cmd.SysProcAttr = &SysProcAttr{
|
p.cmd.SysProcAttr = &SysProcAttr{
|
||||||
Setsid: !p.RetainSession,
|
Setsid: !p.RetainSession,
|
||||||
|
Pdeathsig: SIGKILL,
|
||||||
Cloneflags: CLONE_NEWUSER | CLONE_NEWPID | CLONE_NEWNS |
|
Cloneflags: CLONE_NEWUSER | CLONE_NEWPID | CLONE_NEWNS |
|
||||||
CLONE_NEWIPC | CLONE_NEWUTS | CLONE_NEWCGROUP,
|
CLONE_NEWIPC | CLONE_NEWUTS | CLONE_NEWCGROUP,
|
||||||
|
|
||||||
@@ -263,17 +262,12 @@ func (p *Container) Start() error {
|
|||||||
CAP_SYS_ADMIN,
|
CAP_SYS_ADMIN,
|
||||||
// drop capabilities
|
// drop capabilities
|
||||||
CAP_SETPCAP,
|
CAP_SETPCAP,
|
||||||
// bring up loopback interface
|
|
||||||
CAP_NET_ADMIN,
|
|
||||||
// overlay access to upperdir and workdir
|
// overlay access to upperdir and workdir
|
||||||
CAP_DAC_OVERRIDE,
|
CAP_DAC_OVERRIDE,
|
||||||
},
|
},
|
||||||
|
|
||||||
UseCgroupFD: p.Cgroup != nil,
|
UseCgroupFD: p.Cgroup != nil,
|
||||||
}
|
}
|
||||||
if !p.AllowOrphan {
|
|
||||||
p.cmd.SysProcAttr.Pdeathsig = SIGKILL
|
|
||||||
}
|
|
||||||
if p.cmd.SysProcAttr.UseCgroupFD {
|
if p.cmd.SysProcAttr.UseCgroupFD {
|
||||||
p.cmd.SysProcAttr.CgroupFD = *p.Cgroup
|
p.cmd.SysProcAttr.CgroupFD = *p.Cgroup
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -274,13 +274,13 @@ var containerTestCases = []struct {
|
|||||||
Dev(check.MustAbs("/dev"), true),
|
Dev(check.MustAbs("/dev"), true),
|
||||||
),
|
),
|
||||||
earlyMnt(
|
earlyMnt(
|
||||||
ent("/", "/dev", "ro,nosuid,nodev,relatime", "tmpfs", ignore, ignore),
|
ent("/", "/dev", "ro,nosuid,nodev,relatime", "tmpfs", "devtmpfs", ignore),
|
||||||
ent("/null", "/dev/null", ignore, "devtmpfs", ignore, ignore),
|
ent("/null", "/dev/null", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
||||||
ent("/zero", "/dev/zero", ignore, "devtmpfs", ignore, ignore),
|
ent("/zero", "/dev/zero", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
||||||
ent("/full", "/dev/full", ignore, "devtmpfs", ignore, ignore),
|
ent("/full", "/dev/full", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
||||||
ent("/random", "/dev/random", ignore, "devtmpfs", ignore, ignore),
|
ent("/random", "/dev/random", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
||||||
ent("/urandom", "/dev/urandom", ignore, "devtmpfs", ignore, ignore),
|
ent("/urandom", "/dev/urandom", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
||||||
ent("/tty", "/dev/tty", ignore, "devtmpfs", ignore, ignore),
|
ent("/tty", "/dev/tty", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
||||||
ent("/", "/dev/pts", "rw,nosuid,noexec,relatime", "devpts", "devpts", "rw,mode=620,ptmxmode=666"),
|
ent("/", "/dev/pts", "rw,nosuid,noexec,relatime", "devpts", "devpts", "rw,mode=620,ptmxmode=666"),
|
||||||
ent("/", "/dev/mqueue", "rw,nosuid,nodev,noexec,relatime", "mqueue", "mqueue", "rw"),
|
ent("/", "/dev/mqueue", "rw,nosuid,nodev,noexec,relatime", "mqueue", "mqueue", "rw"),
|
||||||
ent("/", "/dev/shm", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore),
|
ent("/", "/dev/shm", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore),
|
||||||
@@ -292,13 +292,13 @@ var containerTestCases = []struct {
|
|||||||
Dev(check.MustAbs("/dev"), false),
|
Dev(check.MustAbs("/dev"), false),
|
||||||
),
|
),
|
||||||
earlyMnt(
|
earlyMnt(
|
||||||
ent("/", "/dev", "ro,nosuid,nodev,relatime", "tmpfs", ignore, ignore),
|
ent("/", "/dev", "ro,nosuid,nodev,relatime", "tmpfs", "devtmpfs", ignore),
|
||||||
ent("/null", "/dev/null", ignore, "devtmpfs", ignore, ignore),
|
ent("/null", "/dev/null", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
||||||
ent("/zero", "/dev/zero", ignore, "devtmpfs", ignore, ignore),
|
ent("/zero", "/dev/zero", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
||||||
ent("/full", "/dev/full", ignore, "devtmpfs", ignore, ignore),
|
ent("/full", "/dev/full", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
||||||
ent("/random", "/dev/random", ignore, "devtmpfs", ignore, ignore),
|
ent("/random", "/dev/random", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
||||||
ent("/urandom", "/dev/urandom", ignore, "devtmpfs", ignore, ignore),
|
ent("/urandom", "/dev/urandom", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
||||||
ent("/tty", "/dev/tty", ignore, "devtmpfs", ignore, ignore),
|
ent("/tty", "/dev/tty", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
||||||
ent("/", "/dev/pts", "rw,nosuid,noexec,relatime", "devpts", "devpts", "rw,mode=620,ptmxmode=666"),
|
ent("/", "/dev/pts", "rw,nosuid,noexec,relatime", "devpts", "devpts", "rw,mode=620,ptmxmode=666"),
|
||||||
ent("/", "/dev/shm", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore),
|
ent("/", "/dev/shm", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore),
|
||||||
),
|
),
|
||||||
@@ -690,22 +690,11 @@ func init() {
|
|||||||
return fmt.Errorf("got more than %d entries", len(mnt))
|
return fmt.Errorf("got more than %d entries", len(mnt))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ugly hack but should be reliable and is less likely to
|
// ugly hack but should be reliable and is less likely to false negative than comparing by parsed flags
|
||||||
//false negative than comparing by parsed flags
|
cur.VfsOptstr = strings.TrimSuffix(cur.VfsOptstr, ",relatime")
|
||||||
for _, s := range []string{
|
cur.VfsOptstr = strings.TrimSuffix(cur.VfsOptstr, ",noatime")
|
||||||
"relatime",
|
mnt[i].VfsOptstr = strings.TrimSuffix(mnt[i].VfsOptstr, ",relatime")
|
||||||
"noatime",
|
mnt[i].VfsOptstr = strings.TrimSuffix(mnt[i].VfsOptstr, ",noatime")
|
||||||
} {
|
|
||||||
cur.VfsOptstr = strings.TrimSuffix(cur.VfsOptstr, ","+s)
|
|
||||||
mnt[i].VfsOptstr = strings.TrimSuffix(mnt[i].VfsOptstr, ","+s)
|
|
||||||
}
|
|
||||||
for _, s := range []string{
|
|
||||||
"seclabel",
|
|
||||||
"inode64",
|
|
||||||
} {
|
|
||||||
cur.FsOptstr = strings.Replace(cur.FsOptstr, ","+s, "", 1)
|
|
||||||
mnt[i].FsOptstr = strings.Replace(mnt[i].FsOptstr, ","+s, "", 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cur.EqualWithIgnore(mnt[i], "\x00") {
|
if !cur.EqualWithIgnore(mnt[i], "\x00") {
|
||||||
fail = true
|
fail = true
|
||||||
|
|||||||
@@ -61,8 +61,6 @@ type syscallDispatcher interface {
|
|||||||
mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error
|
mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error
|
||||||
// ensureFile provides ensureFile.
|
// ensureFile provides ensureFile.
|
||||||
ensureFile(name string, perm, pperm os.FileMode) error
|
ensureFile(name string, perm, pperm os.FileMode) error
|
||||||
// mustLoopback provides mustLoopback.
|
|
||||||
mustLoopback(msg message.Msg)
|
|
||||||
|
|
||||||
// seccompLoad provides [seccomp.Load].
|
// seccompLoad provides [seccomp.Load].
|
||||||
seccompLoad(rules []std.NativeRule, flags seccomp.ExportFlag) error
|
seccompLoad(rules []std.NativeRule, flags seccomp.ExportFlag) error
|
||||||
@@ -166,7 +164,6 @@ func (k direct) mountTmpfs(fsname, target string, flags uintptr, size int, perm
|
|||||||
func (direct) ensureFile(name string, perm, pperm os.FileMode) error {
|
func (direct) ensureFile(name string, perm, pperm os.FileMode) error {
|
||||||
return ensureFile(name, perm, pperm)
|
return ensureFile(name, perm, pperm)
|
||||||
}
|
}
|
||||||
func (direct) mustLoopback(msg message.Msg) { mustLoopback(msg) }
|
|
||||||
|
|
||||||
func (direct) seccompLoad(rules []std.NativeRule, flags seccomp.ExportFlag) error {
|
func (direct) seccompLoad(rules []std.NativeRule, flags seccomp.ExportFlag) error {
|
||||||
return seccomp.Load(rules, flags)
|
return seccomp.Load(rules, flags)
|
||||||
|
|||||||
@@ -162,8 +162,7 @@ func checkSimple(t *testing.T, fname string, testCases []simpleTestCase) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
wait4signal := make(chan struct{})
|
wait4signal := make(chan struct{})
|
||||||
lockNotify := make(chan struct{})
|
k := &kstub{wait4signal, stub.New(t, func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { return &kstub{wait4signal, s} }, tc.want)}
|
||||||
k := &kstub{wait4signal, lockNotify, stub.New(t, func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { return &kstub{wait4signal, lockNotify, s} }, tc.want)}
|
|
||||||
defer stub.HandleExit(t)
|
defer stub.HandleExit(t)
|
||||||
if err := tc.f(k); !reflect.DeepEqual(err, tc.wantErr) {
|
if err := tc.f(k); !reflect.DeepEqual(err, tc.wantErr) {
|
||||||
t.Errorf("%s: error = %v, want %v", fname, err, tc.wantErr)
|
t.Errorf("%s: error = %v, want %v", fname, err, tc.wantErr)
|
||||||
@@ -201,8 +200,8 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
k := &kstub{nil, nil, stub.New(t,
|
k := &kstub{nil, stub.New(t,
|
||||||
func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { return &kstub{nil, nil, s} },
|
func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { return &kstub{nil, s} },
|
||||||
stub.Expect{Calls: slices.Concat(tc.early, []stub.Call{{Name: stub.CallSeparator}}, tc.apply)},
|
stub.Expect{Calls: slices.Concat(tc.early, []stub.Call{{Name: stub.CallSeparator}}, tc.apply)},
|
||||||
)}
|
)}
|
||||||
state := &setupState{Params: tc.params, Msg: k}
|
state := &setupState{Params: tc.params, Msg: k}
|
||||||
@@ -323,19 +322,12 @@ const (
|
|||||||
|
|
||||||
type kstub struct {
|
type kstub struct {
|
||||||
wait4signal chan struct{}
|
wait4signal chan struct{}
|
||||||
lockNotify chan struct{}
|
|
||||||
*stub.Stub[syscallDispatcher]
|
*stub.Stub[syscallDispatcher]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) new(f func(k syscallDispatcher)) { k.Helper(); k.New(f) }
|
func (k *kstub) new(f func(k syscallDispatcher)) { k.Helper(); k.New(f) }
|
||||||
|
|
||||||
func (k *kstub) lockOSThread() {
|
func (k *kstub) lockOSThread() { k.Helper(); k.Expects("lockOSThread") }
|
||||||
k.Helper()
|
|
||||||
expect := k.Expects("lockOSThread")
|
|
||||||
if k.lockNotify != nil && expect.Ret == magicWait4Signal {
|
|
||||||
<-k.lockNotify
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *kstub) setPtracer(pid uintptr) error {
|
func (k *kstub) setPtracer(pid uintptr) error {
|
||||||
k.Helper()
|
k.Helper()
|
||||||
@@ -465,8 +457,6 @@ func (k *kstub) ensureFile(name string, perm, pperm os.FileMode) error {
|
|||||||
stub.CheckArg(k.Stub, "pperm", pperm, 2))
|
stub.CheckArg(k.Stub, "pperm", pperm, 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*kstub) mustLoopback(message.Msg) { /* noop */ }
|
|
||||||
|
|
||||||
func (k *kstub) seccompLoad(rules []std.NativeRule, flags seccomp.ExportFlag) error {
|
func (k *kstub) seccompLoad(rules []std.NativeRule, flags seccomp.ExportFlag) error {
|
||||||
k.Helper()
|
k.Helper()
|
||||||
return k.Expects("seccompLoad").Error(
|
return k.Expects("seccompLoad").Error(
|
||||||
@@ -482,10 +472,6 @@ func (k *kstub) notify(c chan<- os.Signal, sig ...os.Signal) {
|
|||||||
k.FailNow()
|
k.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
if k.lockNotify != nil && expect.Ret == magicWait4Signal {
|
|
||||||
defer close(k.lockNotify)
|
|
||||||
}
|
|
||||||
|
|
||||||
// export channel for external instrumentation
|
// export channel for external instrumentation
|
||||||
if chanf, ok := expect.Args[0].(func(c chan<- os.Signal)); ok && chanf != nil {
|
if chanf, ok := expect.Args[0].(func(c chan<- os.Signal)); ok && chanf != nil {
|
||||||
chanf(c)
|
chanf(c)
|
||||||
@@ -773,8 +759,7 @@ 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,36 +7,31 @@ 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) (m string, ok bool) {
|
func messageFromError(err error) (string, bool) {
|
||||||
if m, ok = messagePrefixP[MountError]("cannot ", err); ok {
|
if m, ok := messagePrefixP[MountError]("cannot ", err); ok {
|
||||||
return
|
return m, ok
|
||||||
}
|
}
|
||||||
if m, ok = messagePrefixP[os.PathError]("cannot ", err); ok {
|
if m, ok := messagePrefixP[os.PathError]("cannot ", err); ok {
|
||||||
return
|
return m, ok
|
||||||
}
|
}
|
||||||
if m, ok = messagePrefix[check.AbsoluteError](zeroString, err); ok {
|
if m, ok := messagePrefixP[check.AbsoluteError]("", err); ok {
|
||||||
return
|
return m, ok
|
||||||
}
|
}
|
||||||
if m, ok = messagePrefix[OpRepeatError](zeroString, err); ok {
|
if m, ok := messagePrefix[OpRepeatError]("", err); ok {
|
||||||
return
|
return m, ok
|
||||||
}
|
}
|
||||||
if m, ok = messagePrefix[OpStateError](zeroString, err); ok {
|
if m, ok := messagePrefix[OpStateError]("", err); ok {
|
||||||
return
|
return m, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
if m, ok = messagePrefixP[vfs.DecoderError]("cannot ", err); ok {
|
if m, ok := messagePrefixP[vfs.DecoderError]("cannot ", err); ok {
|
||||||
return
|
return m, ok
|
||||||
}
|
}
|
||||||
if m, ok = messagePrefix[TmpfsSizeError](zeroString, err); ok {
|
if m, ok := messagePrefix[TmpfsSizeError]("", err); ok {
|
||||||
return
|
return m, ok
|
||||||
}
|
|
||||||
|
|
||||||
if m, ok = message.GetMessage(err); ok {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return zeroString, false
|
return zeroString, false
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ func TestMessageFromError(t *testing.T) {
|
|||||||
Err: stub.UniqueError(0xdeadbeef),
|
Err: stub.UniqueError(0xdeadbeef),
|
||||||
}, "cannot mount /sysroot: unique error 3735928559 injected by the test suite", true},
|
}, "cannot mount /sysroot: unique error 3735928559 injected by the test suite", true},
|
||||||
|
|
||||||
{"absolute", check.AbsoluteError("etc/mtab"),
|
{"absolute", &check.AbsoluteError{Pathname: "etc/mtab"},
|
||||||
`path "etc/mtab" is not absolute`, true},
|
`path "etc/mtab" is not absolute`, true},
|
||||||
|
|
||||||
{"repeat", OpRepeatError("autoetc"),
|
{"repeat", OpRepeatError("autoetc"),
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ var (
|
|||||||
// AbsRunUser is [RunUser] as [check.Absolute].
|
// AbsRunUser is [RunUser] as [check.Absolute].
|
||||||
AbsRunUser = unsafeAbs(RunUser)
|
AbsRunUser = unsafeAbs(RunUser)
|
||||||
|
|
||||||
// AbsUsr is [Usr] as [check.Absolute].
|
|
||||||
AbsUsr = unsafeAbs(Usr)
|
|
||||||
// AbsUsrBin is [UsrBin] as [check.Absolute].
|
// AbsUsrBin is [UsrBin] as [check.Absolute].
|
||||||
AbsUsrBin = unsafeAbs(UsrBin)
|
AbsUsrBin = unsafeAbs(UsrBin)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
@@ -10,8 +9,6 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
. "syscall"
|
. "syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -21,28 +18,24 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
/* intermediateHostPath is the pathname of the intermediate tmpfs mount point.
|
/* 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"
|
||||||
|
|
||||||
// setupEnv is the name of the environment variable holding the string representation of
|
// setup params file descriptor
|
||||||
// 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 (
|
||||||
@@ -56,8 +49,6 @@ 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)
|
||||||
@@ -70,29 +61,11 @@ 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) }
|
||||||
|
|
||||||
@@ -170,10 +143,6 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
offsetSetup = int(setupFd + 1)
|
offsetSetup = int(setupFd + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !params.HostNet {
|
|
||||||
k.mustLoopback(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// write uid/gid map here so parent does not need to set dumpable
|
// write uid/gid map here so parent does not need to set dumpable
|
||||||
if err := k.setDumpable(SUID_DUMP_USER); err != nil {
|
if err := k.setDumpable(SUID_DUMP_USER); err != nil {
|
||||||
k.fatalf(msg, "cannot set SUID_DUMP_USER: %v", err)
|
k.fatalf(msg, "cannot set SUID_DUMP_USER: %v", err)
|
||||||
@@ -211,9 +180,7 @@ 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))
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
state := &setupState{Params: ¶ms.Params, Msg: msg}
|
||||||
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
|
||||||
@@ -363,97 +330,6 @@ 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)
|
||||||
}
|
}
|
||||||
@@ -465,11 +341,50 @@ 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 process %s", params.Path)
|
msg.Verbosef("starting initial program %s", params.Path)
|
||||||
if err := k.start(cmd); err != nil {
|
if err := k.start(cmd); err != nil {
|
||||||
k.fatalf(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)
|
||||||
@@ -479,7 +394,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 := exitUnexpectedWait4
|
r := 2
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case s := <-sig:
|
case s := <-sig:
|
||||||
@@ -511,9 +426,6 @@ 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,20 +1983,11 @@ 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{}, magicWait4Signal, 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{
|
||||||
@@ -2071,11 +2062,11 @@ 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("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- CancelSignal }, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, magicWait4Signal, 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("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
|
||||||
call("signal", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent", os.Interrupt}, magicWait4Signal, stub.UniqueError(9)),
|
call("signal", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent", os.Interrupt}, magicWait4Signal, stub.UniqueError(9)),
|
||||||
@@ -2090,7 +2081,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
|
|
||||||
/* wait4 */
|
/* wait4 */
|
||||||
Tracks: []stub.Expect{{Calls: []stub.Call{
|
Tracks: []stub.Expect{{Calls: []stub.Call{
|
||||||
call("lockOSThread", stub.ExpectArgs{}, magicWait4Signal, nil),
|
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||||
|
|
||||||
// magicWait4Signal as args[4] causes this to block until simulated signal is delivered
|
// magicWait4Signal as args[4] causes this to block until simulated signal is delivered
|
||||||
call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xfade01ce), 0, nil, magicWait4Signal}, 0xbad, nil),
|
call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xfade01ce), 0, nil, magicWait4Signal}, 0xbad, nil),
|
||||||
@@ -2171,11 +2162,11 @@ 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("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- syscall.SIGQUIT }, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, magicWait4Signal, 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("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
|
||||||
call("signal", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent", syscall.SIGQUIT}, magicWait4Signal, stub.UniqueError(0xfe)),
|
call("signal", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent", syscall.SIGQUIT}, magicWait4Signal, stub.UniqueError(0xfe)),
|
||||||
@@ -2190,7 +2181,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
|
|
||||||
/* wait4 */
|
/* wait4 */
|
||||||
Tracks: []stub.Expect{{Calls: []stub.Call{
|
Tracks: []stub.Expect{{Calls: []stub.Call{
|
||||||
call("lockOSThread", stub.ExpectArgs{}, magicWait4Signal, nil),
|
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||||
|
|
||||||
// magicWait4Signal as args[4] causes this to block until simulated signal is delivered
|
// magicWait4Signal as args[4] causes this to block until simulated signal is delivered
|
||||||
call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xfade01ce), 0, nil, magicWait4Signal}, 0xbad, nil),
|
call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xfade01ce), 0, nil, magicWait4Signal}, 0xbad, nil),
|
||||||
@@ -2271,11 +2262,11 @@ 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("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- os.Interrupt }, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, magicWait4Signal, 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("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),
|
||||||
call("exit", stub.ExpectArgs{0}, nil, nil),
|
call("exit", stub.ExpectArgs{0}, nil, nil),
|
||||||
@@ -2283,7 +2274,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
|
|
||||||
/* wait4 */
|
/* wait4 */
|
||||||
Tracks: []stub.Expect{{Calls: []stub.Call{
|
Tracks: []stub.Expect{{Calls: []stub.Call{
|
||||||
call("lockOSThread", stub.ExpectArgs{}, magicWait4Signal, nil),
|
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||||
|
|
||||||
// this terminates the goroutine at the call, preventing it from leaking while preserving behaviour
|
// 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),
|
call("wait4", stub.ExpectArgs{-1, nil, 0, nil, stub.PanicExit}, 0, syscall.ECHILD),
|
||||||
@@ -2362,11 +2353,11 @@ 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("notify", stub.ExpectArgs{nil, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, magicWait4Signal, nil),
|
call("New", stub.ExpectArgs{}, 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),
|
||||||
call("verbosef", stub.ExpectArgs{"initial process exited with signal %s", []any{syscall.Signal(0x4e)}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"initial process exited with signal %s", []any{syscall.Signal(0x4e)}}, nil, nil),
|
||||||
@@ -2377,7 +2368,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
|
|
||||||
/* wait4 */
|
/* wait4 */
|
||||||
Tracks: []stub.Expect{{Calls: []stub.Call{
|
Tracks: []stub.Expect{{Calls: []stub.Call{
|
||||||
call("lockOSThread", stub.ExpectArgs{}, magicWait4Signal, nil),
|
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||||
|
|
||||||
call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xfade01ce), 0, nil}, 0xbad, nil),
|
call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xfade01ce), 0, nil}, 0xbad, nil),
|
||||||
// this terminates the goroutine at the call, preventing it from leaking while preserving behaviour
|
// this terminates the goroutine at the call, preventing it from leaking while preserving behaviour
|
||||||
@@ -2457,11 +2448,11 @@ 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("notify", stub.ExpectArgs{nil, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, magicWait4Signal, nil),
|
call("New", stub.ExpectArgs{}, 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),
|
||||||
call("verbosef", stub.ExpectArgs{"initial process exited with signal %s", []any{syscall.Signal(0x4e)}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"initial process exited with signal %s", []any{syscall.Signal(0x4e)}}, nil, nil),
|
||||||
@@ -2471,7 +2462,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
|
|
||||||
/* wait4 */
|
/* wait4 */
|
||||||
Tracks: []stub.Expect{{Calls: []stub.Call{
|
Tracks: []stub.Expect{{Calls: []stub.Call{
|
||||||
call("lockOSThread", stub.ExpectArgs{}, magicWait4Signal, nil),
|
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||||
|
|
||||||
call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR),
|
call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR),
|
||||||
call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR),
|
call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR),
|
||||||
@@ -2595,11 +2586,11 @@ 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("notify", stub.ExpectArgs{nil, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, magicWait4Signal, nil),
|
call("New", stub.ExpectArgs{}, 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),
|
||||||
call("verbosef", stub.ExpectArgs{"initial process exited with code %d", []any{1}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"initial process exited with code %d", []any{1}}, nil, nil),
|
||||||
@@ -2609,7 +2600,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
|
|
||||||
/* wait4 */
|
/* wait4 */
|
||||||
Tracks: []stub.Expect{{Calls: []stub.Call{
|
Tracks: []stub.Expect{{Calls: []stub.Call{
|
||||||
call("lockOSThread", stub.ExpectArgs{}, magicWait4Signal, nil),
|
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||||
|
|
||||||
call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR),
|
call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR),
|
||||||
call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR),
|
call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR),
|
||||||
@@ -2737,11 +2728,11 @@ 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("notify", stub.ExpectArgs{nil, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, magicWait4Signal, nil),
|
call("New", stub.ExpectArgs{}, 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),
|
||||||
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
call("verbose", stub.ExpectArgs{[]any{os.ErrInvalid.Error()}}, nil, nil),
|
||||||
@@ -2752,7 +2743,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
|
|
||||||
/* wait4 */
|
/* wait4 */
|
||||||
Tracks: []stub.Expect{{Calls: []stub.Call{
|
Tracks: []stub.Expect{{Calls: []stub.Call{
|
||||||
call("lockOSThread", stub.ExpectArgs{}, magicWait4Signal, nil),
|
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||||
|
|
||||||
call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR),
|
call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR),
|
||||||
call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR),
|
call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR),
|
||||||
|
|||||||
@@ -90,7 +90,6 @@ 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)
|
||||||
|
|||||||
@@ -1,134 +0,0 @@
|
|||||||
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) }
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
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,7 +126,6 @@ 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,7 +27,6 @@ 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,8 +205,6 @@ 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() &&
|
||||||
|
|||||||
@@ -312,10 +312,7 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
|
|
||||||
{"ephemeral", new(Ops).OverlayEphemeral(
|
{"ephemeral", new(Ops).OverlayEphemeral(check.MustAbs("/nix/store"), check.MustAbs("/mnt-root/nix/.ro-store")), Ops{
|
||||||
check.MustAbs("/nix/store"),
|
|
||||||
check.MustAbs("/mnt-root/nix/.ro-store"),
|
|
||||||
), Ops{
|
|
||||||
&MountOverlayOp{
|
&MountOverlayOp{
|
||||||
Target: check.MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
@@ -323,10 +320,7 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
|
|
||||||
{"readonly", new(Ops).OverlayReadonly(
|
{"readonly", new(Ops).OverlayReadonly(check.MustAbs("/nix/store"), check.MustAbs("/mnt-root/nix/.ro-store")), Ops{
|
||||||
check.MustAbs("/nix/store"),
|
|
||||||
check.MustAbs("/mnt-root/nix/.ro-store"),
|
|
||||||
), Ops{
|
|
||||||
&MountOverlayOp{
|
&MountOverlayOp{
|
||||||
Target: check.MustAbs("/nix/store"),
|
Target: check.MustAbs("/nix/store"),
|
||||||
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
Lower: []*check.Absolute{check.MustAbs("/mnt-root/nix/.ro-store")},
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ 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,7 +28,6 @@ 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,7 +26,6 @@ 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)
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ func (l *SymlinkOp) Valid() bool { return l != nil && l.Target != nil && l.LinkN
|
|||||||
func (l *SymlinkOp) early(_ *setupState, k syscallDispatcher) error {
|
func (l *SymlinkOp) early(_ *setupState, k syscallDispatcher) error {
|
||||||
if l.Dereference {
|
if l.Dereference {
|
||||||
if !path.IsAbs(l.LinkName) {
|
if !path.IsAbs(l.LinkName) {
|
||||||
return check.AbsoluteError(l.LinkName)
|
return &check.AbsoluteError{Pathname: l.LinkName}
|
||||||
}
|
}
|
||||||
if name, err := k.readlink(l.LinkName); err != nil {
|
if name, err := k.readlink(l.LinkName); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -50,8 +50,6 @@ 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() &&
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func TestSymlinkOp(t *testing.T) {
|
|||||||
Target: check.MustAbs("/etc/mtab"),
|
Target: check.MustAbs("/etc/mtab"),
|
||||||
LinkName: "etc/mtab",
|
LinkName: "etc/mtab",
|
||||||
Dereference: true,
|
Dereference: true,
|
||||||
}, nil, check.AbsoluteError("etc/mtab"), nil, nil},
|
}, nil, &check.AbsoluteError{Pathname: "etc/mtab"}, nil, nil},
|
||||||
|
|
||||||
{"readlink", &Params{ParentPerm: 0755}, &SymlinkOp{
|
{"readlink", &Params{ParentPerm: 0755}, &SymlinkOp{
|
||||||
Target: check.MustAbs("/etc/mtab"),
|
Target: check.MustAbs("/etc/mtab"),
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ 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)
|
||||||
|
|||||||
@@ -1,269 +0,0 @@
|
|||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
. "syscall"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"hakurei.app/container/std"
|
|
||||||
"hakurei.app/message"
|
|
||||||
)
|
|
||||||
|
|
||||||
// rtnetlink represents a NETLINK_ROUTE socket.
|
|
||||||
type rtnetlink struct {
|
|
||||||
// Sent as part of rtnetlink messages.
|
|
||||||
pid uint32
|
|
||||||
// AF_NETLINK socket.
|
|
||||||
fd int
|
|
||||||
// Whether the socket is open.
|
|
||||||
ok bool
|
|
||||||
// Message sequence number.
|
|
||||||
seq uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// open creates the underlying NETLINK_ROUTE socket.
|
|
||||||
func (s *rtnetlink) open() (err error) {
|
|
||||||
if s.ok || s.fd < 0 {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
s.pid = uint32(Getpid())
|
|
||||||
if s.fd, err = Socket(
|
|
||||||
AF_NETLINK,
|
|
||||||
SOCK_RAW|SOCK_CLOEXEC,
|
|
||||||
NETLINK_ROUTE,
|
|
||||||
); err != nil {
|
|
||||||
return os.NewSyscallError("socket", err)
|
|
||||||
} else if err = Bind(s.fd, &SockaddrNetlink{
|
|
||||||
Family: AF_NETLINK,
|
|
||||||
Pid: s.pid,
|
|
||||||
}); err != nil {
|
|
||||||
_ = s.close()
|
|
||||||
return os.NewSyscallError("bind", err)
|
|
||||||
} else {
|
|
||||||
s.ok = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// close closes the underlying NETLINK_ROUTE socket.
|
|
||||||
func (s *rtnetlink) close() error {
|
|
||||||
if !s.ok {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
s.ok = false
|
|
||||||
err := Close(s.fd)
|
|
||||||
s.fd = -1
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// roundtrip sends a netlink message and handles the reply.
|
|
||||||
func (s *rtnetlink) roundtrip(data []byte) error {
|
|
||||||
if !s.ok {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() { s.seq++ }()
|
|
||||||
|
|
||||||
if err := Sendto(s.fd, data, 0, &SockaddrNetlink{
|
|
||||||
Family: AF_NETLINK,
|
|
||||||
}); err != nil {
|
|
||||||
return os.NewSyscallError("sendto", err)
|
|
||||||
}
|
|
||||||
buf := make([]byte, Getpagesize())
|
|
||||||
|
|
||||||
done:
|
|
||||||
for {
|
|
||||||
p := buf
|
|
||||||
if n, _, err := Recvfrom(s.fd, p, 0); err != nil {
|
|
||||||
return os.NewSyscallError("recvfrom", err)
|
|
||||||
} else if n < NLMSG_HDRLEN {
|
|
||||||
return errors.ErrUnsupported
|
|
||||||
} else {
|
|
||||||
p = p[:n]
|
|
||||||
}
|
|
||||||
|
|
||||||
if msgs, err := ParseNetlinkMessage(p); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
for _, m := range msgs {
|
|
||||||
if m.Header.Seq != s.seq || m.Header.Pid != s.pid {
|
|
||||||
return errors.ErrUnsupported
|
|
||||||
}
|
|
||||||
if m.Header.Type == NLMSG_DONE {
|
|
||||||
break done
|
|
||||||
}
|
|
||||||
if m.Header.Type == NLMSG_ERROR {
|
|
||||||
if len(m.Data) >= 4 {
|
|
||||||
errno := Errno(-std.ScmpInt(binary.NativeEndian.Uint32(m.Data)))
|
|
||||||
if errno == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errno
|
|
||||||
}
|
|
||||||
return errors.ErrUnsupported
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// mustRoundtrip calls roundtrip and terminates via msg for a non-nil error.
|
|
||||||
func (s *rtnetlink) mustRoundtrip(msg message.Msg, data []byte) {
|
|
||||||
err := s.roundtrip(data)
|
|
||||||
if err == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if closeErr := Close(s.fd); closeErr != nil {
|
|
||||||
msg.Verbosef("cannot close: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch err.(type) {
|
|
||||||
case *os.SyscallError:
|
|
||||||
msg.GetLogger().Fatalf("cannot %v", err)
|
|
||||||
|
|
||||||
case Errno:
|
|
||||||
msg.GetLogger().Fatalf("RTNETLINK answers: %v", err)
|
|
||||||
|
|
||||||
default:
|
|
||||||
msg.GetLogger().Fatalln("RTNETLINK answers with unexpected message")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// newaddrLo represents a RTM_NEWADDR message with two addresses.
|
|
||||||
type newaddrLo struct {
|
|
||||||
header NlMsghdr
|
|
||||||
data IfAddrmsg
|
|
||||||
|
|
||||||
r0 RtAttr
|
|
||||||
a0 [4]byte // in_addr
|
|
||||||
r1 RtAttr
|
|
||||||
a1 [4]byte // in_addr
|
|
||||||
}
|
|
||||||
|
|
||||||
// sizeofNewaddrLo is the expected size of newaddrLo.
|
|
||||||
const sizeofNewaddrLo = NLMSG_HDRLEN + SizeofIfAddrmsg + (SizeofRtAttr+4)*2
|
|
||||||
|
|
||||||
// newaddrLo returns the address of a populated newaddrLo.
|
|
||||||
func (s *rtnetlink) newaddrLo(lo int) *newaddrLo {
|
|
||||||
return &newaddrLo{NlMsghdr{
|
|
||||||
Len: sizeofNewaddrLo,
|
|
||||||
Type: RTM_NEWADDR,
|
|
||||||
Flags: NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL,
|
|
||||||
Seq: s.seq,
|
|
||||||
Pid: s.pid,
|
|
||||||
}, IfAddrmsg{
|
|
||||||
Family: AF_INET,
|
|
||||||
Prefixlen: 8,
|
|
||||||
Flags: IFA_F_PERMANENT,
|
|
||||||
Scope: RT_SCOPE_HOST,
|
|
||||||
Index: uint32(lo),
|
|
||||||
}, RtAttr{
|
|
||||||
Len: uint16(SizeofRtAttr + len(newaddrLo{}.a0)),
|
|
||||||
Type: IFA_LOCAL,
|
|
||||||
}, [4]byte{127, 0, 0, 1}, RtAttr{
|
|
||||||
Len: uint16(SizeofRtAttr + len(newaddrLo{}.a1)),
|
|
||||||
Type: IFA_ADDRESS,
|
|
||||||
}, [4]byte{127, 0, 0, 1}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (msg *newaddrLo) toWireFormat() []byte {
|
|
||||||
var buf [sizeofNewaddrLo]byte
|
|
||||||
|
|
||||||
*(*uint32)(unsafe.Pointer(&buf[0:4][0])) = msg.header.Len
|
|
||||||
*(*uint16)(unsafe.Pointer(&buf[4:6][0])) = msg.header.Type
|
|
||||||
*(*uint16)(unsafe.Pointer(&buf[6:8][0])) = msg.header.Flags
|
|
||||||
*(*uint32)(unsafe.Pointer(&buf[8:12][0])) = msg.header.Seq
|
|
||||||
*(*uint32)(unsafe.Pointer(&buf[12:16][0])) = msg.header.Pid
|
|
||||||
|
|
||||||
buf[16] = msg.data.Family
|
|
||||||
buf[17] = msg.data.Prefixlen
|
|
||||||
buf[18] = msg.data.Flags
|
|
||||||
buf[19] = msg.data.Scope
|
|
||||||
*(*uint32)(unsafe.Pointer(&buf[20:24][0])) = msg.data.Index
|
|
||||||
|
|
||||||
*(*uint16)(unsafe.Pointer(&buf[24:26][0])) = msg.r0.Len
|
|
||||||
*(*uint16)(unsafe.Pointer(&buf[26:28][0])) = msg.r0.Type
|
|
||||||
copy(buf[28:32], msg.a0[:])
|
|
||||||
*(*uint16)(unsafe.Pointer(&buf[32:34][0])) = msg.r1.Len
|
|
||||||
*(*uint16)(unsafe.Pointer(&buf[34:36][0])) = msg.r1.Type
|
|
||||||
copy(buf[36:40], msg.a1[:])
|
|
||||||
|
|
||||||
return buf[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// newlinkLo represents a RTM_NEWLINK message.
|
|
||||||
type newlinkLo struct {
|
|
||||||
header NlMsghdr
|
|
||||||
data IfInfomsg
|
|
||||||
}
|
|
||||||
|
|
||||||
// sizeofNewlinkLo is the expected size of newlinkLo.
|
|
||||||
const sizeofNewlinkLo = NLMSG_HDRLEN + SizeofIfInfomsg
|
|
||||||
|
|
||||||
// newlinkLo returns the address of a populated newlinkLo.
|
|
||||||
func (s *rtnetlink) newlinkLo(lo int) *newlinkLo {
|
|
||||||
return &newlinkLo{NlMsghdr{
|
|
||||||
Len: sizeofNewlinkLo,
|
|
||||||
Type: RTM_NEWLINK,
|
|
||||||
Flags: NLM_F_REQUEST | NLM_F_ACK,
|
|
||||||
Seq: s.seq,
|
|
||||||
Pid: s.pid,
|
|
||||||
}, IfInfomsg{
|
|
||||||
Family: AF_UNSPEC,
|
|
||||||
Index: int32(lo),
|
|
||||||
Flags: IFF_UP,
|
|
||||||
Change: IFF_UP,
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (msg *newlinkLo) toWireFormat() []byte {
|
|
||||||
var buf [sizeofNewlinkLo]byte
|
|
||||||
|
|
||||||
*(*uint32)(unsafe.Pointer(&buf[0:4][0])) = msg.header.Len
|
|
||||||
*(*uint16)(unsafe.Pointer(&buf[4:6][0])) = msg.header.Type
|
|
||||||
*(*uint16)(unsafe.Pointer(&buf[6:8][0])) = msg.header.Flags
|
|
||||||
*(*uint32)(unsafe.Pointer(&buf[8:12][0])) = msg.header.Seq
|
|
||||||
*(*uint32)(unsafe.Pointer(&buf[12:16][0])) = msg.header.Pid
|
|
||||||
|
|
||||||
buf[16] = msg.data.Family
|
|
||||||
*(*uint16)(unsafe.Pointer(&buf[18:20][0])) = msg.data.Type
|
|
||||||
*(*int32)(unsafe.Pointer(&buf[20:24][0])) = msg.data.Index
|
|
||||||
*(*uint32)(unsafe.Pointer(&buf[24:28][0])) = msg.data.Flags
|
|
||||||
*(*uint32)(unsafe.Pointer(&buf[28:32][0])) = msg.data.Change
|
|
||||||
|
|
||||||
return buf[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// mustLoopback creates the loopback address and brings the lo interface up.
|
|
||||||
// mustLoopback calls a fatal method of the underlying [log.Logger] of m with a
|
|
||||||
// user-facing error message if RTNETLINK behaves unexpectedly.
|
|
||||||
func mustLoopback(msg message.Msg) {
|
|
||||||
log := msg.GetLogger()
|
|
||||||
|
|
||||||
var lo int
|
|
||||||
if ifi, err := net.InterfaceByName("lo"); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
} else {
|
|
||||||
lo = ifi.Index
|
|
||||||
}
|
|
||||||
|
|
||||||
var s rtnetlink
|
|
||||||
if err := s.open(); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err := s.close(); err != nil {
|
|
||||||
msg.Verbosef("cannot close netlink: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
s.mustRoundtrip(msg, s.newaddrLo(lo).toWireFormat())
|
|
||||||
s.mustRoundtrip(msg, s.newlinkLo(lo).toWireFormat())
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSizeof(t *testing.T) {
|
|
||||||
if got := unsafe.Sizeof(newaddrLo{}); got != sizeofNewaddrLo {
|
|
||||||
t.Fatalf("newaddrLo: sizeof = %#x, want %#x", got, sizeofNewaddrLo)
|
|
||||||
}
|
|
||||||
|
|
||||||
if got := unsafe.Sizeof(newlinkLo{}); got != sizeofNewlinkLo {
|
|
||||||
t.Fatalf("newlinkLo: sizeof = %#x, want %#x", got, sizeofNewlinkLo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRtnetlinkMessage(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
msg interface{ toWireFormat() []byte }
|
|
||||||
want []byte
|
|
||||||
}{
|
|
||||||
{"newaddrLo", (&rtnetlink{pid: 1, seq: 0}).newaddrLo(1), []byte{
|
|
||||||
/* Len */ 0x28, 0, 0, 0,
|
|
||||||
/* Type */ 0x14, 0,
|
|
||||||
/* Flags */ 5, 6,
|
|
||||||
/* Seq */ 0, 0, 0, 0,
|
|
||||||
/* Pid */ 1, 0, 0, 0,
|
|
||||||
|
|
||||||
/* Family */ 2,
|
|
||||||
/* Prefixlen */ 8,
|
|
||||||
/* Flags */ 0x80,
|
|
||||||
/* Scope */ 0xfe,
|
|
||||||
/* Index */ 1, 0, 0, 0,
|
|
||||||
|
|
||||||
/* Len */ 8, 0,
|
|
||||||
/* Type */ 2, 0,
|
|
||||||
/* in_addr */ 127, 0, 0, 1,
|
|
||||||
|
|
||||||
/* Len */ 8, 0,
|
|
||||||
/* Type */ 1, 0,
|
|
||||||
/* in_addr */ 127, 0, 0, 1,
|
|
||||||
}},
|
|
||||||
|
|
||||||
{"newlinkLo", (&rtnetlink{pid: 1, seq: 1}).newlinkLo(1), []byte{
|
|
||||||
/* Len */ 0x20, 0, 0, 0,
|
|
||||||
/* Type */ 0x10, 0,
|
|
||||||
/* Flags */ 5, 0,
|
|
||||||
/* Seq */ 1, 0, 0, 0,
|
|
||||||
/* Pid */ 1, 0, 0, 0,
|
|
||||||
|
|
||||||
/* Family */ 0,
|
|
||||||
/* pad */ 0,
|
|
||||||
/* Type */ 0, 0,
|
|
||||||
/* Index */ 1, 0, 0, 0,
|
|
||||||
/* Flags */ 1, 0, 0, 0,
|
|
||||||
/* Change */ 1, 0, 0, 0,
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
if got := tc.msg.toWireFormat(); string(got) != string(tc.want) {
|
|
||||||
t.Fatalf("toWireFormat: %#v, want %#v", got, tc.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package seccomp_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "hakurei.app/container/seccomp"
|
|
||||||
. "hakurei.app/container/std"
|
|
||||||
)
|
|
||||||
|
|
||||||
var bpfExpected = bpfLookup{
|
|
||||||
{AllowMultiarch | AllowCAN |
|
|
||||||
AllowBluetooth, PresetExt |
|
|
||||||
PresetDenyNS | PresetDenyTTY | PresetDenyDevel |
|
|
||||||
PresetLinux32}: toHash(
|
|
||||||
"a1c4ffa35f4bfbf38061184760b9a09edfcb4964c3b534395e47327b83f3fb61f2f9573ddfcc4772424cc2f5dd12fd32471e6531dbe10e85eda3797dd4fa179f"),
|
|
||||||
|
|
||||||
{0, 0}: toHash(
|
|
||||||
"f3910fd727d087def593e3876c2c6ab9ace71d82ec8cbc992a26223e7bba85e1d7a0b56c5fc6303703f24595825dad8561637edaedd5384b34a6cd080946633c"),
|
|
||||||
{0, PresetExt}: toHash(
|
|
||||||
"741438c5e3f11c36c92ae8c5934f13440675c6e719541c2dbffeda79a10081bcfd9ad8314a60c1d1f53db86c8080c13fffa3bbcf7fe753935679b4b902737286"),
|
|
||||||
{0, PresetStrict}: toHash(
|
|
||||||
"79e9e464d02405c6d74fd2c771bd72a1311e488221c73a9c32db9270219837c54fccec2f36fe2474895547e60c311514567e2e6cf4e7a7fcf909c1ecd1e254a7"),
|
|
||||||
{0, PresetDenyNS | PresetDenyTTY | PresetDenyDevel}: toHash(
|
|
||||||
"3c443715a6c1e557a284862ea8efb70a5d4ecbe67d1226627323e861cd3646fb3e7768ec5b94b93760b7f652cf6916f66e317a4fbf8716d10c3673aa4fc3ae58"),
|
|
||||||
{0, PresetExt | PresetDenyDevel}: toHash(
|
|
||||||
"4448a74e8cc75a4ab63799c4f2cc2a5af63e5f4e8e9b8ac15a1873d647dfa67a4c67b39ed466d8dd32abc64136d401879fc6185c9ab00feeaf59ccf4305f8201"),
|
|
||||||
{0, PresetExt | PresetDenyNS | PresetDenyDevel}: toHash(
|
|
||||||
"c7c86e793cb7192f5f6c735f372cda27eb43ae1045e587f8eadb64c849520a3280b6570a3d7b601d32cddb38021585a2234db38e506cebfd10aa3d6c75440f17"),
|
|
||||||
}
|
|
||||||
@@ -12,7 +12,6 @@ my %syscall_cutoff_arch = (
|
|||||||
"x86" => 340,
|
"x86" => 340,
|
||||||
"x86_64" => 302,
|
"x86_64" => 302,
|
||||||
"aarch64" => 281,
|
"aarch64" => 281,
|
||||||
"riscv64" => 281,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
print <<EOF;
|
print <<EOF;
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
package std
|
|
||||||
|
|
||||||
import "syscall"
|
|
||||||
|
|
||||||
const (
|
|
||||||
SYS_NEWFSTATAT = syscall.SYS_FSTATAT
|
|
||||||
)
|
|
||||||
|
|
||||||
var syscallNumExtra = map[string]ScmpSyscall{
|
|
||||||
"uselib": SNR_USELIB,
|
|
||||||
"clock_adjtime64": SNR_CLOCK_ADJTIME64,
|
|
||||||
"clock_settime64": SNR_CLOCK_SETTIME64,
|
|
||||||
"umount": SNR_UMOUNT,
|
|
||||||
"chown": SNR_CHOWN,
|
|
||||||
"chown32": SNR_CHOWN32,
|
|
||||||
"fchown32": SNR_FCHOWN32,
|
|
||||||
"lchown": SNR_LCHOWN,
|
|
||||||
"lchown32": SNR_LCHOWN32,
|
|
||||||
"setgid32": SNR_SETGID32,
|
|
||||||
"setgroups32": SNR_SETGROUPS32,
|
|
||||||
"setregid32": SNR_SETREGID32,
|
|
||||||
"setresgid32": SNR_SETRESGID32,
|
|
||||||
"setresuid32": SNR_SETRESUID32,
|
|
||||||
"setreuid32": SNR_SETREUID32,
|
|
||||||
"setuid32": SNR_SETUID32,
|
|
||||||
"modify_ldt": SNR_MODIFY_LDT,
|
|
||||||
"subpage_prot": SNR_SUBPAGE_PROT,
|
|
||||||
"switch_endian": SNR_SWITCH_ENDIAN,
|
|
||||||
"vm86": SNR_VM86,
|
|
||||||
"vm86old": SNR_VM86OLD,
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
SNR_USELIB ScmpSyscall = __PNR_uselib
|
|
||||||
SNR_CLOCK_ADJTIME64 ScmpSyscall = __PNR_clock_adjtime64
|
|
||||||
SNR_CLOCK_SETTIME64 ScmpSyscall = __PNR_clock_settime64
|
|
||||||
SNR_UMOUNT ScmpSyscall = __PNR_umount
|
|
||||||
SNR_CHOWN ScmpSyscall = __PNR_chown
|
|
||||||
SNR_CHOWN32 ScmpSyscall = __PNR_chown32
|
|
||||||
SNR_FCHOWN32 ScmpSyscall = __PNR_fchown32
|
|
||||||
SNR_LCHOWN ScmpSyscall = __PNR_lchown
|
|
||||||
SNR_LCHOWN32 ScmpSyscall = __PNR_lchown32
|
|
||||||
SNR_SETGID32 ScmpSyscall = __PNR_setgid32
|
|
||||||
SNR_SETGROUPS32 ScmpSyscall = __PNR_setgroups32
|
|
||||||
SNR_SETREGID32 ScmpSyscall = __PNR_setregid32
|
|
||||||
SNR_SETRESGID32 ScmpSyscall = __PNR_setresgid32
|
|
||||||
SNR_SETRESUID32 ScmpSyscall = __PNR_setresuid32
|
|
||||||
SNR_SETREUID32 ScmpSyscall = __PNR_setreuid32
|
|
||||||
SNR_SETUID32 ScmpSyscall = __PNR_setuid32
|
|
||||||
SNR_MODIFY_LDT ScmpSyscall = __PNR_modify_ldt
|
|
||||||
SNR_SUBPAGE_PROT ScmpSyscall = __PNR_subpage_prot
|
|
||||||
SNR_SWITCH_ENDIAN ScmpSyscall = __PNR_switch_endian
|
|
||||||
SNR_VM86 ScmpSyscall = __PNR_vm86
|
|
||||||
SNR_VM86OLD ScmpSyscall = __PNR_vm86old
|
|
||||||
)
|
|
||||||
@@ -1,719 +0,0 @@
|
|||||||
// mksysnum_linux.pl /usr/include/riscv64-linux-gnu/asm/unistd.h
|
|
||||||
// Code generated by the command above; DO NOT EDIT.
|
|
||||||
|
|
||||||
package std
|
|
||||||
|
|
||||||
import . "syscall"
|
|
||||||
|
|
||||||
var syscallNum = map[string]ScmpSyscall{
|
|
||||||
"io_setup": SNR_IO_SETUP,
|
|
||||||
"io_destroy": SNR_IO_DESTROY,
|
|
||||||
"io_submit": SNR_IO_SUBMIT,
|
|
||||||
"io_cancel": SNR_IO_CANCEL,
|
|
||||||
"io_getevents": SNR_IO_GETEVENTS,
|
|
||||||
"setxattr": SNR_SETXATTR,
|
|
||||||
"lsetxattr": SNR_LSETXATTR,
|
|
||||||
"fsetxattr": SNR_FSETXATTR,
|
|
||||||
"getxattr": SNR_GETXATTR,
|
|
||||||
"lgetxattr": SNR_LGETXATTR,
|
|
||||||
"fgetxattr": SNR_FGETXATTR,
|
|
||||||
"listxattr": SNR_LISTXATTR,
|
|
||||||
"llistxattr": SNR_LLISTXATTR,
|
|
||||||
"flistxattr": SNR_FLISTXATTR,
|
|
||||||
"removexattr": SNR_REMOVEXATTR,
|
|
||||||
"lremovexattr": SNR_LREMOVEXATTR,
|
|
||||||
"fremovexattr": SNR_FREMOVEXATTR,
|
|
||||||
"getcwd": SNR_GETCWD,
|
|
||||||
"lookup_dcookie": SNR_LOOKUP_DCOOKIE,
|
|
||||||
"eventfd2": SNR_EVENTFD2,
|
|
||||||
"epoll_create1": SNR_EPOLL_CREATE1,
|
|
||||||
"epoll_ctl": SNR_EPOLL_CTL,
|
|
||||||
"epoll_pwait": SNR_EPOLL_PWAIT,
|
|
||||||
"dup": SNR_DUP,
|
|
||||||
"dup3": SNR_DUP3,
|
|
||||||
"fcntl": SNR_FCNTL,
|
|
||||||
"inotify_init1": SNR_INOTIFY_INIT1,
|
|
||||||
"inotify_add_watch": SNR_INOTIFY_ADD_WATCH,
|
|
||||||
"inotify_rm_watch": SNR_INOTIFY_RM_WATCH,
|
|
||||||
"ioctl": SNR_IOCTL,
|
|
||||||
"ioprio_set": SNR_IOPRIO_SET,
|
|
||||||
"ioprio_get": SNR_IOPRIO_GET,
|
|
||||||
"flock": SNR_FLOCK,
|
|
||||||
"mknodat": SNR_MKNODAT,
|
|
||||||
"mkdirat": SNR_MKDIRAT,
|
|
||||||
"unlinkat": SNR_UNLINKAT,
|
|
||||||
"symlinkat": SNR_SYMLINKAT,
|
|
||||||
"linkat": SNR_LINKAT,
|
|
||||||
"umount2": SNR_UMOUNT2,
|
|
||||||
"mount": SNR_MOUNT,
|
|
||||||
"pivot_root": SNR_PIVOT_ROOT,
|
|
||||||
"nfsservctl": SNR_NFSSERVCTL,
|
|
||||||
"statfs": SNR_STATFS,
|
|
||||||
"fstatfs": SNR_FSTATFS,
|
|
||||||
"truncate": SNR_TRUNCATE,
|
|
||||||
"ftruncate": SNR_FTRUNCATE,
|
|
||||||
"fallocate": SNR_FALLOCATE,
|
|
||||||
"faccessat": SNR_FACCESSAT,
|
|
||||||
"chdir": SNR_CHDIR,
|
|
||||||
"fchdir": SNR_FCHDIR,
|
|
||||||
"chroot": SNR_CHROOT,
|
|
||||||
"fchmod": SNR_FCHMOD,
|
|
||||||
"fchmodat": SNR_FCHMODAT,
|
|
||||||
"fchownat": SNR_FCHOWNAT,
|
|
||||||
"fchown": SNR_FCHOWN,
|
|
||||||
"openat": SNR_OPENAT,
|
|
||||||
"close": SNR_CLOSE,
|
|
||||||
"vhangup": SNR_VHANGUP,
|
|
||||||
"pipe2": SNR_PIPE2,
|
|
||||||
"quotactl": SNR_QUOTACTL,
|
|
||||||
"getdents64": SNR_GETDENTS64,
|
|
||||||
"lseek": SNR_LSEEK,
|
|
||||||
"read": SNR_READ,
|
|
||||||
"write": SNR_WRITE,
|
|
||||||
"readv": SNR_READV,
|
|
||||||
"writev": SNR_WRITEV,
|
|
||||||
"pread64": SNR_PREAD64,
|
|
||||||
"pwrite64": SNR_PWRITE64,
|
|
||||||
"preadv": SNR_PREADV,
|
|
||||||
"pwritev": SNR_PWRITEV,
|
|
||||||
"sendfile": SNR_SENDFILE,
|
|
||||||
"pselect6": SNR_PSELECT6,
|
|
||||||
"ppoll": SNR_PPOLL,
|
|
||||||
"signalfd4": SNR_SIGNALFD4,
|
|
||||||
"vmsplice": SNR_VMSPLICE,
|
|
||||||
"splice": SNR_SPLICE,
|
|
||||||
"tee": SNR_TEE,
|
|
||||||
"readlinkat": SNR_READLINKAT,
|
|
||||||
"newfstatat": SNR_NEWFSTATAT,
|
|
||||||
"fstat": SNR_FSTAT,
|
|
||||||
"sync": SNR_SYNC,
|
|
||||||
"fsync": SNR_FSYNC,
|
|
||||||
"fdatasync": SNR_FDATASYNC,
|
|
||||||
"sync_file_range": SNR_SYNC_FILE_RANGE,
|
|
||||||
"timerfd_create": SNR_TIMERFD_CREATE,
|
|
||||||
"timerfd_settime": SNR_TIMERFD_SETTIME,
|
|
||||||
"timerfd_gettime": SNR_TIMERFD_GETTIME,
|
|
||||||
"utimensat": SNR_UTIMENSAT,
|
|
||||||
"acct": SNR_ACCT,
|
|
||||||
"capget": SNR_CAPGET,
|
|
||||||
"capset": SNR_CAPSET,
|
|
||||||
"personality": SNR_PERSONALITY,
|
|
||||||
"exit": SNR_EXIT,
|
|
||||||
"exit_group": SNR_EXIT_GROUP,
|
|
||||||
"waitid": SNR_WAITID,
|
|
||||||
"set_tid_address": SNR_SET_TID_ADDRESS,
|
|
||||||
"unshare": SNR_UNSHARE,
|
|
||||||
"futex": SNR_FUTEX,
|
|
||||||
"set_robust_list": SNR_SET_ROBUST_LIST,
|
|
||||||
"get_robust_list": SNR_GET_ROBUST_LIST,
|
|
||||||
"nanosleep": SNR_NANOSLEEP,
|
|
||||||
"getitimer": SNR_GETITIMER,
|
|
||||||
"setitimer": SNR_SETITIMER,
|
|
||||||
"kexec_load": SNR_KEXEC_LOAD,
|
|
||||||
"init_module": SNR_INIT_MODULE,
|
|
||||||
"delete_module": SNR_DELETE_MODULE,
|
|
||||||
"timer_create": SNR_TIMER_CREATE,
|
|
||||||
"timer_gettime": SNR_TIMER_GETTIME,
|
|
||||||
"timer_getoverrun": SNR_TIMER_GETOVERRUN,
|
|
||||||
"timer_settime": SNR_TIMER_SETTIME,
|
|
||||||
"timer_delete": SNR_TIMER_DELETE,
|
|
||||||
"clock_settime": SNR_CLOCK_SETTIME,
|
|
||||||
"clock_gettime": SNR_CLOCK_GETTIME,
|
|
||||||
"clock_getres": SNR_CLOCK_GETRES,
|
|
||||||
"clock_nanosleep": SNR_CLOCK_NANOSLEEP,
|
|
||||||
"syslog": SNR_SYSLOG,
|
|
||||||
"ptrace": SNR_PTRACE,
|
|
||||||
"sched_setparam": SNR_SCHED_SETPARAM,
|
|
||||||
"sched_setscheduler": SNR_SCHED_SETSCHEDULER,
|
|
||||||
"sched_getscheduler": SNR_SCHED_GETSCHEDULER,
|
|
||||||
"sched_getparam": SNR_SCHED_GETPARAM,
|
|
||||||
"sched_setaffinity": SNR_SCHED_SETAFFINITY,
|
|
||||||
"sched_getaffinity": SNR_SCHED_GETAFFINITY,
|
|
||||||
"sched_yield": SNR_SCHED_YIELD,
|
|
||||||
"sched_get_priority_max": SNR_SCHED_GET_PRIORITY_MAX,
|
|
||||||
"sched_get_priority_min": SNR_SCHED_GET_PRIORITY_MIN,
|
|
||||||
"sched_rr_get_interval": SNR_SCHED_RR_GET_INTERVAL,
|
|
||||||
"restart_syscall": SNR_RESTART_SYSCALL,
|
|
||||||
"kill": SNR_KILL,
|
|
||||||
"tkill": SNR_TKILL,
|
|
||||||
"tgkill": SNR_TGKILL,
|
|
||||||
"sigaltstack": SNR_SIGALTSTACK,
|
|
||||||
"rt_sigsuspend": SNR_RT_SIGSUSPEND,
|
|
||||||
"rt_sigaction": SNR_RT_SIGACTION,
|
|
||||||
"rt_sigprocmask": SNR_RT_SIGPROCMASK,
|
|
||||||
"rt_sigpending": SNR_RT_SIGPENDING,
|
|
||||||
"rt_sigtimedwait": SNR_RT_SIGTIMEDWAIT,
|
|
||||||
"rt_sigqueueinfo": SNR_RT_SIGQUEUEINFO,
|
|
||||||
"rt_sigreturn": SNR_RT_SIGRETURN,
|
|
||||||
"setpriority": SNR_SETPRIORITY,
|
|
||||||
"getpriority": SNR_GETPRIORITY,
|
|
||||||
"reboot": SNR_REBOOT,
|
|
||||||
"setregid": SNR_SETREGID,
|
|
||||||
"setgid": SNR_SETGID,
|
|
||||||
"setreuid": SNR_SETREUID,
|
|
||||||
"setuid": SNR_SETUID,
|
|
||||||
"setresuid": SNR_SETRESUID,
|
|
||||||
"getresuid": SNR_GETRESUID,
|
|
||||||
"setresgid": SNR_SETRESGID,
|
|
||||||
"getresgid": SNR_GETRESGID,
|
|
||||||
"setfsuid": SNR_SETFSUID,
|
|
||||||
"setfsgid": SNR_SETFSGID,
|
|
||||||
"times": SNR_TIMES,
|
|
||||||
"setpgid": SNR_SETPGID,
|
|
||||||
"getpgid": SNR_GETPGID,
|
|
||||||
"getsid": SNR_GETSID,
|
|
||||||
"setsid": SNR_SETSID,
|
|
||||||
"getgroups": SNR_GETGROUPS,
|
|
||||||
"setgroups": SNR_SETGROUPS,
|
|
||||||
"uname": SNR_UNAME,
|
|
||||||
"sethostname": SNR_SETHOSTNAME,
|
|
||||||
"setdomainname": SNR_SETDOMAINNAME,
|
|
||||||
"getrlimit": SNR_GETRLIMIT,
|
|
||||||
"setrlimit": SNR_SETRLIMIT,
|
|
||||||
"getrusage": SNR_GETRUSAGE,
|
|
||||||
"umask": SNR_UMASK,
|
|
||||||
"prctl": SNR_PRCTL,
|
|
||||||
"getcpu": SNR_GETCPU,
|
|
||||||
"gettimeofday": SNR_GETTIMEOFDAY,
|
|
||||||
"settimeofday": SNR_SETTIMEOFDAY,
|
|
||||||
"adjtimex": SNR_ADJTIMEX,
|
|
||||||
"getpid": SNR_GETPID,
|
|
||||||
"getppid": SNR_GETPPID,
|
|
||||||
"getuid": SNR_GETUID,
|
|
||||||
"geteuid": SNR_GETEUID,
|
|
||||||
"getgid": SNR_GETGID,
|
|
||||||
"getegid": SNR_GETEGID,
|
|
||||||
"gettid": SNR_GETTID,
|
|
||||||
"sysinfo": SNR_SYSINFO,
|
|
||||||
"mq_open": SNR_MQ_OPEN,
|
|
||||||
"mq_unlink": SNR_MQ_UNLINK,
|
|
||||||
"mq_timedsend": SNR_MQ_TIMEDSEND,
|
|
||||||
"mq_timedreceive": SNR_MQ_TIMEDRECEIVE,
|
|
||||||
"mq_notify": SNR_MQ_NOTIFY,
|
|
||||||
"mq_getsetattr": SNR_MQ_GETSETATTR,
|
|
||||||
"msgget": SNR_MSGGET,
|
|
||||||
"msgctl": SNR_MSGCTL,
|
|
||||||
"msgrcv": SNR_MSGRCV,
|
|
||||||
"msgsnd": SNR_MSGSND,
|
|
||||||
"semget": SNR_SEMGET,
|
|
||||||
"semctl": SNR_SEMCTL,
|
|
||||||
"semtimedop": SNR_SEMTIMEDOP,
|
|
||||||
"semop": SNR_SEMOP,
|
|
||||||
"shmget": SNR_SHMGET,
|
|
||||||
"shmctl": SNR_SHMCTL,
|
|
||||||
"shmat": SNR_SHMAT,
|
|
||||||
"shmdt": SNR_SHMDT,
|
|
||||||
"socket": SNR_SOCKET,
|
|
||||||
"socketpair": SNR_SOCKETPAIR,
|
|
||||||
"bind": SNR_BIND,
|
|
||||||
"listen": SNR_LISTEN,
|
|
||||||
"accept": SNR_ACCEPT,
|
|
||||||
"connect": SNR_CONNECT,
|
|
||||||
"getsockname": SNR_GETSOCKNAME,
|
|
||||||
"getpeername": SNR_GETPEERNAME,
|
|
||||||
"sendto": SNR_SENDTO,
|
|
||||||
"recvfrom": SNR_RECVFROM,
|
|
||||||
"setsockopt": SNR_SETSOCKOPT,
|
|
||||||
"getsockopt": SNR_GETSOCKOPT,
|
|
||||||
"shutdown": SNR_SHUTDOWN,
|
|
||||||
"sendmsg": SNR_SENDMSG,
|
|
||||||
"recvmsg": SNR_RECVMSG,
|
|
||||||
"readahead": SNR_READAHEAD,
|
|
||||||
"brk": SNR_BRK,
|
|
||||||
"munmap": SNR_MUNMAP,
|
|
||||||
"mremap": SNR_MREMAP,
|
|
||||||
"add_key": SNR_ADD_KEY,
|
|
||||||
"request_key": SNR_REQUEST_KEY,
|
|
||||||
"keyctl": SNR_KEYCTL,
|
|
||||||
"clone": SNR_CLONE,
|
|
||||||
"execve": SNR_EXECVE,
|
|
||||||
"mmap": SNR_MMAP,
|
|
||||||
"fadvise64": SNR_FADVISE64,
|
|
||||||
"swapon": SNR_SWAPON,
|
|
||||||
"swapoff": SNR_SWAPOFF,
|
|
||||||
"mprotect": SNR_MPROTECT,
|
|
||||||
"msync": SNR_MSYNC,
|
|
||||||
"mlock": SNR_MLOCK,
|
|
||||||
"munlock": SNR_MUNLOCK,
|
|
||||||
"mlockall": SNR_MLOCKALL,
|
|
||||||
"munlockall": SNR_MUNLOCKALL,
|
|
||||||
"mincore": SNR_MINCORE,
|
|
||||||
"madvise": SNR_MADVISE,
|
|
||||||
"remap_file_pages": SNR_REMAP_FILE_PAGES,
|
|
||||||
"mbind": SNR_MBIND,
|
|
||||||
"get_mempolicy": SNR_GET_MEMPOLICY,
|
|
||||||
"set_mempolicy": SNR_SET_MEMPOLICY,
|
|
||||||
"migrate_pages": SNR_MIGRATE_PAGES,
|
|
||||||
"move_pages": SNR_MOVE_PAGES,
|
|
||||||
"rt_tgsigqueueinfo": SNR_RT_TGSIGQUEUEINFO,
|
|
||||||
"perf_event_open": SNR_PERF_EVENT_OPEN,
|
|
||||||
"accept4": SNR_ACCEPT4,
|
|
||||||
"recvmmsg": SNR_RECVMMSG,
|
|
||||||
"wait4": SNR_WAIT4,
|
|
||||||
"prlimit64": SNR_PRLIMIT64,
|
|
||||||
"fanotify_init": SNR_FANOTIFY_INIT,
|
|
||||||
"fanotify_mark": SNR_FANOTIFY_MARK,
|
|
||||||
"name_to_handle_at": SNR_NAME_TO_HANDLE_AT,
|
|
||||||
"open_by_handle_at": SNR_OPEN_BY_HANDLE_AT,
|
|
||||||
"clock_adjtime": SNR_CLOCK_ADJTIME,
|
|
||||||
"syncfs": SNR_SYNCFS,
|
|
||||||
"setns": SNR_SETNS,
|
|
||||||
"sendmmsg": SNR_SENDMMSG,
|
|
||||||
"process_vm_readv": SNR_PROCESS_VM_READV,
|
|
||||||
"process_vm_writev": SNR_PROCESS_VM_WRITEV,
|
|
||||||
"kcmp": SNR_KCMP,
|
|
||||||
"finit_module": SNR_FINIT_MODULE,
|
|
||||||
"sched_setattr": SNR_SCHED_SETATTR,
|
|
||||||
"sched_getattr": SNR_SCHED_GETATTR,
|
|
||||||
"renameat2": SNR_RENAMEAT2,
|
|
||||||
"seccomp": SNR_SECCOMP,
|
|
||||||
"getrandom": SNR_GETRANDOM,
|
|
||||||
"memfd_create": SNR_MEMFD_CREATE,
|
|
||||||
"bpf": SNR_BPF,
|
|
||||||
"execveat": SNR_EXECVEAT,
|
|
||||||
"userfaultfd": SNR_USERFAULTFD,
|
|
||||||
"membarrier": SNR_MEMBARRIER,
|
|
||||||
"mlock2": SNR_MLOCK2,
|
|
||||||
"copy_file_range": SNR_COPY_FILE_RANGE,
|
|
||||||
"preadv2": SNR_PREADV2,
|
|
||||||
"pwritev2": SNR_PWRITEV2,
|
|
||||||
"pkey_mprotect": SNR_PKEY_MPROTECT,
|
|
||||||
"pkey_alloc": SNR_PKEY_ALLOC,
|
|
||||||
"pkey_free": SNR_PKEY_FREE,
|
|
||||||
"statx": SNR_STATX,
|
|
||||||
"io_pgetevents": SNR_IO_PGETEVENTS,
|
|
||||||
"rseq": SNR_RSEQ,
|
|
||||||
"kexec_file_load": SNR_KEXEC_FILE_LOAD,
|
|
||||||
"pidfd_send_signal": SNR_PIDFD_SEND_SIGNAL,
|
|
||||||
"io_uring_setup": SNR_IO_URING_SETUP,
|
|
||||||
"io_uring_enter": SNR_IO_URING_ENTER,
|
|
||||||
"io_uring_register": SNR_IO_URING_REGISTER,
|
|
||||||
"open_tree": SNR_OPEN_TREE,
|
|
||||||
"move_mount": SNR_MOVE_MOUNT,
|
|
||||||
"fsopen": SNR_FSOPEN,
|
|
||||||
"fsconfig": SNR_FSCONFIG,
|
|
||||||
"fsmount": SNR_FSMOUNT,
|
|
||||||
"fspick": SNR_FSPICK,
|
|
||||||
"pidfd_open": SNR_PIDFD_OPEN,
|
|
||||||
"clone3": SNR_CLONE3,
|
|
||||||
"close_range": SNR_CLOSE_RANGE,
|
|
||||||
"openat2": SNR_OPENAT2,
|
|
||||||
"pidfd_getfd": SNR_PIDFD_GETFD,
|
|
||||||
"faccessat2": SNR_FACCESSAT2,
|
|
||||||
"process_madvise": SNR_PROCESS_MADVISE,
|
|
||||||
"epoll_pwait2": SNR_EPOLL_PWAIT2,
|
|
||||||
"mount_setattr": SNR_MOUNT_SETATTR,
|
|
||||||
"quotactl_fd": SNR_QUOTACTL_FD,
|
|
||||||
"landlock_create_ruleset": SNR_LANDLOCK_CREATE_RULESET,
|
|
||||||
"landlock_add_rule": SNR_LANDLOCK_ADD_RULE,
|
|
||||||
"landlock_restrict_self": SNR_LANDLOCK_RESTRICT_SELF,
|
|
||||||
"memfd_secret": SNR_MEMFD_SECRET,
|
|
||||||
"process_mrelease": SNR_PROCESS_MRELEASE,
|
|
||||||
"futex_waitv": SNR_FUTEX_WAITV,
|
|
||||||
"set_mempolicy_home_node": SNR_SET_MEMPOLICY_HOME_NODE,
|
|
||||||
"cachestat": SNR_CACHESTAT,
|
|
||||||
"fchmodat2": SNR_FCHMODAT2,
|
|
||||||
"map_shadow_stack": SNR_MAP_SHADOW_STACK,
|
|
||||||
"futex_wake": SNR_FUTEX_WAKE,
|
|
||||||
"futex_wait": SNR_FUTEX_WAIT,
|
|
||||||
"futex_requeue": SNR_FUTEX_REQUEUE,
|
|
||||||
"statmount": SNR_STATMOUNT,
|
|
||||||
"listmount": SNR_LISTMOUNT,
|
|
||||||
"lsm_get_self_attr": SNR_LSM_GET_SELF_ATTR,
|
|
||||||
"lsm_set_self_attr": SNR_LSM_SET_SELF_ATTR,
|
|
||||||
"lsm_list_modules": SNR_LSM_LIST_MODULES,
|
|
||||||
"mseal": SNR_MSEAL,
|
|
||||||
"setxattrat": SNR_SETXATTRAT,
|
|
||||||
"getxattrat": SNR_GETXATTRAT,
|
|
||||||
"listxattrat": SNR_LISTXATTRAT,
|
|
||||||
"removexattrat": SNR_REMOVEXATTRAT,
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
SYS_USERFAULTFD = 282
|
|
||||||
SYS_MEMBARRIER = 283
|
|
||||||
SYS_MLOCK2 = 284
|
|
||||||
SYS_COPY_FILE_RANGE = 285
|
|
||||||
SYS_PREADV2 = 286
|
|
||||||
SYS_PWRITEV2 = 287
|
|
||||||
SYS_PKEY_MPROTECT = 288
|
|
||||||
SYS_PKEY_ALLOC = 289
|
|
||||||
SYS_PKEY_FREE = 290
|
|
||||||
SYS_STATX = 291
|
|
||||||
SYS_IO_PGETEVENTS = 292
|
|
||||||
SYS_RSEQ = 293
|
|
||||||
SYS_KEXEC_FILE_LOAD = 294
|
|
||||||
SYS_PIDFD_SEND_SIGNAL = 424
|
|
||||||
SYS_IO_URING_SETUP = 425
|
|
||||||
SYS_IO_URING_ENTER = 426
|
|
||||||
SYS_IO_URING_REGISTER = 427
|
|
||||||
SYS_OPEN_TREE = 428
|
|
||||||
SYS_MOVE_MOUNT = 429
|
|
||||||
SYS_FSOPEN = 430
|
|
||||||
SYS_FSCONFIG = 431
|
|
||||||
SYS_FSMOUNT = 432
|
|
||||||
SYS_FSPICK = 433
|
|
||||||
SYS_PIDFD_OPEN = 434
|
|
||||||
SYS_CLONE3 = 435
|
|
||||||
SYS_CLOSE_RANGE = 436
|
|
||||||
SYS_OPENAT2 = 437
|
|
||||||
SYS_PIDFD_GETFD = 438
|
|
||||||
SYS_FACCESSAT2 = 439
|
|
||||||
SYS_PROCESS_MADVISE = 440
|
|
||||||
SYS_EPOLL_PWAIT2 = 441
|
|
||||||
SYS_MOUNT_SETATTR = 442
|
|
||||||
SYS_QUOTACTL_FD = 443
|
|
||||||
SYS_LANDLOCK_CREATE_RULESET = 444
|
|
||||||
SYS_LANDLOCK_ADD_RULE = 445
|
|
||||||
SYS_LANDLOCK_RESTRICT_SELF = 446
|
|
||||||
SYS_MEMFD_SECRET = 447
|
|
||||||
SYS_PROCESS_MRELEASE = 448
|
|
||||||
SYS_FUTEX_WAITV = 449
|
|
||||||
SYS_SET_MEMPOLICY_HOME_NODE = 450
|
|
||||||
SYS_CACHESTAT = 451
|
|
||||||
SYS_FCHMODAT2 = 452
|
|
||||||
SYS_MAP_SHADOW_STACK = 453
|
|
||||||
SYS_FUTEX_WAKE = 454
|
|
||||||
SYS_FUTEX_WAIT = 455
|
|
||||||
SYS_FUTEX_REQUEUE = 456
|
|
||||||
SYS_STATMOUNT = 457
|
|
||||||
SYS_LISTMOUNT = 458
|
|
||||||
SYS_LSM_GET_SELF_ATTR = 459
|
|
||||||
SYS_LSM_SET_SELF_ATTR = 460
|
|
||||||
SYS_LSM_LIST_MODULES = 461
|
|
||||||
SYS_MSEAL = 462
|
|
||||||
SYS_SETXATTRAT = 463
|
|
||||||
SYS_GETXATTRAT = 464
|
|
||||||
SYS_LISTXATTRAT = 465
|
|
||||||
SYS_REMOVEXATTRAT = 466
|
|
||||||
SYS_OPEN_TREE_ATTR = 467
|
|
||||||
SYS_FILE_GETATTR = 468
|
|
||||||
SYS_FILE_SETATTR = 469
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
SNR_IO_SETUP ScmpSyscall = SYS_IO_SETUP
|
|
||||||
SNR_IO_DESTROY ScmpSyscall = SYS_IO_DESTROY
|
|
||||||
SNR_IO_SUBMIT ScmpSyscall = SYS_IO_SUBMIT
|
|
||||||
SNR_IO_CANCEL ScmpSyscall = SYS_IO_CANCEL
|
|
||||||
SNR_IO_GETEVENTS ScmpSyscall = SYS_IO_GETEVENTS
|
|
||||||
SNR_SETXATTR ScmpSyscall = SYS_SETXATTR
|
|
||||||
SNR_LSETXATTR ScmpSyscall = SYS_LSETXATTR
|
|
||||||
SNR_FSETXATTR ScmpSyscall = SYS_FSETXATTR
|
|
||||||
SNR_GETXATTR ScmpSyscall = SYS_GETXATTR
|
|
||||||
SNR_LGETXATTR ScmpSyscall = SYS_LGETXATTR
|
|
||||||
SNR_FGETXATTR ScmpSyscall = SYS_FGETXATTR
|
|
||||||
SNR_LISTXATTR ScmpSyscall = SYS_LISTXATTR
|
|
||||||
SNR_LLISTXATTR ScmpSyscall = SYS_LLISTXATTR
|
|
||||||
SNR_FLISTXATTR ScmpSyscall = SYS_FLISTXATTR
|
|
||||||
SNR_REMOVEXATTR ScmpSyscall = SYS_REMOVEXATTR
|
|
||||||
SNR_LREMOVEXATTR ScmpSyscall = SYS_LREMOVEXATTR
|
|
||||||
SNR_FREMOVEXATTR ScmpSyscall = SYS_FREMOVEXATTR
|
|
||||||
SNR_GETCWD ScmpSyscall = SYS_GETCWD
|
|
||||||
SNR_LOOKUP_DCOOKIE ScmpSyscall = SYS_LOOKUP_DCOOKIE
|
|
||||||
SNR_EVENTFD2 ScmpSyscall = SYS_EVENTFD2
|
|
||||||
SNR_EPOLL_CREATE1 ScmpSyscall = SYS_EPOLL_CREATE1
|
|
||||||
SNR_EPOLL_CTL ScmpSyscall = SYS_EPOLL_CTL
|
|
||||||
SNR_EPOLL_PWAIT ScmpSyscall = SYS_EPOLL_PWAIT
|
|
||||||
SNR_DUP ScmpSyscall = SYS_DUP
|
|
||||||
SNR_DUP3 ScmpSyscall = SYS_DUP3
|
|
||||||
SNR_FCNTL ScmpSyscall = SYS_FCNTL
|
|
||||||
SNR_INOTIFY_INIT1 ScmpSyscall = SYS_INOTIFY_INIT1
|
|
||||||
SNR_INOTIFY_ADD_WATCH ScmpSyscall = SYS_INOTIFY_ADD_WATCH
|
|
||||||
SNR_INOTIFY_RM_WATCH ScmpSyscall = SYS_INOTIFY_RM_WATCH
|
|
||||||
SNR_IOCTL ScmpSyscall = SYS_IOCTL
|
|
||||||
SNR_IOPRIO_SET ScmpSyscall = SYS_IOPRIO_SET
|
|
||||||
SNR_IOPRIO_GET ScmpSyscall = SYS_IOPRIO_GET
|
|
||||||
SNR_FLOCK ScmpSyscall = SYS_FLOCK
|
|
||||||
SNR_MKNODAT ScmpSyscall = SYS_MKNODAT
|
|
||||||
SNR_MKDIRAT ScmpSyscall = SYS_MKDIRAT
|
|
||||||
SNR_UNLINKAT ScmpSyscall = SYS_UNLINKAT
|
|
||||||
SNR_SYMLINKAT ScmpSyscall = SYS_SYMLINKAT
|
|
||||||
SNR_LINKAT ScmpSyscall = SYS_LINKAT
|
|
||||||
SNR_UMOUNT2 ScmpSyscall = SYS_UMOUNT2
|
|
||||||
SNR_MOUNT ScmpSyscall = SYS_MOUNT
|
|
||||||
SNR_PIVOT_ROOT ScmpSyscall = SYS_PIVOT_ROOT
|
|
||||||
SNR_NFSSERVCTL ScmpSyscall = SYS_NFSSERVCTL
|
|
||||||
SNR_STATFS ScmpSyscall = SYS_STATFS
|
|
||||||
SNR_FSTATFS ScmpSyscall = SYS_FSTATFS
|
|
||||||
SNR_TRUNCATE ScmpSyscall = SYS_TRUNCATE
|
|
||||||
SNR_FTRUNCATE ScmpSyscall = SYS_FTRUNCATE
|
|
||||||
SNR_FALLOCATE ScmpSyscall = SYS_FALLOCATE
|
|
||||||
SNR_FACCESSAT ScmpSyscall = SYS_FACCESSAT
|
|
||||||
SNR_CHDIR ScmpSyscall = SYS_CHDIR
|
|
||||||
SNR_FCHDIR ScmpSyscall = SYS_FCHDIR
|
|
||||||
SNR_CHROOT ScmpSyscall = SYS_CHROOT
|
|
||||||
SNR_FCHMOD ScmpSyscall = SYS_FCHMOD
|
|
||||||
SNR_FCHMODAT ScmpSyscall = SYS_FCHMODAT
|
|
||||||
SNR_FCHOWNAT ScmpSyscall = SYS_FCHOWNAT
|
|
||||||
SNR_FCHOWN ScmpSyscall = SYS_FCHOWN
|
|
||||||
SNR_OPENAT ScmpSyscall = SYS_OPENAT
|
|
||||||
SNR_CLOSE ScmpSyscall = SYS_CLOSE
|
|
||||||
SNR_VHANGUP ScmpSyscall = SYS_VHANGUP
|
|
||||||
SNR_PIPE2 ScmpSyscall = SYS_PIPE2
|
|
||||||
SNR_QUOTACTL ScmpSyscall = SYS_QUOTACTL
|
|
||||||
SNR_GETDENTS64 ScmpSyscall = SYS_GETDENTS64
|
|
||||||
SNR_LSEEK ScmpSyscall = SYS_LSEEK
|
|
||||||
SNR_READ ScmpSyscall = SYS_READ
|
|
||||||
SNR_WRITE ScmpSyscall = SYS_WRITE
|
|
||||||
SNR_READV ScmpSyscall = SYS_READV
|
|
||||||
SNR_WRITEV ScmpSyscall = SYS_WRITEV
|
|
||||||
SNR_PREAD64 ScmpSyscall = SYS_PREAD64
|
|
||||||
SNR_PWRITE64 ScmpSyscall = SYS_PWRITE64
|
|
||||||
SNR_PREADV ScmpSyscall = SYS_PREADV
|
|
||||||
SNR_PWRITEV ScmpSyscall = SYS_PWRITEV
|
|
||||||
SNR_SENDFILE ScmpSyscall = SYS_SENDFILE
|
|
||||||
SNR_PSELECT6 ScmpSyscall = SYS_PSELECT6
|
|
||||||
SNR_PPOLL ScmpSyscall = SYS_PPOLL
|
|
||||||
SNR_SIGNALFD4 ScmpSyscall = SYS_SIGNALFD4
|
|
||||||
SNR_VMSPLICE ScmpSyscall = SYS_VMSPLICE
|
|
||||||
SNR_SPLICE ScmpSyscall = SYS_SPLICE
|
|
||||||
SNR_TEE ScmpSyscall = SYS_TEE
|
|
||||||
SNR_READLINKAT ScmpSyscall = SYS_READLINKAT
|
|
||||||
SNR_NEWFSTATAT ScmpSyscall = SYS_NEWFSTATAT
|
|
||||||
SNR_FSTAT ScmpSyscall = SYS_FSTAT
|
|
||||||
SNR_SYNC ScmpSyscall = SYS_SYNC
|
|
||||||
SNR_FSYNC ScmpSyscall = SYS_FSYNC
|
|
||||||
SNR_FDATASYNC ScmpSyscall = SYS_FDATASYNC
|
|
||||||
SNR_SYNC_FILE_RANGE ScmpSyscall = SYS_SYNC_FILE_RANGE
|
|
||||||
SNR_TIMERFD_CREATE ScmpSyscall = SYS_TIMERFD_CREATE
|
|
||||||
SNR_TIMERFD_SETTIME ScmpSyscall = SYS_TIMERFD_SETTIME
|
|
||||||
SNR_TIMERFD_GETTIME ScmpSyscall = SYS_TIMERFD_GETTIME
|
|
||||||
SNR_UTIMENSAT ScmpSyscall = SYS_UTIMENSAT
|
|
||||||
SNR_ACCT ScmpSyscall = SYS_ACCT
|
|
||||||
SNR_CAPGET ScmpSyscall = SYS_CAPGET
|
|
||||||
SNR_CAPSET ScmpSyscall = SYS_CAPSET
|
|
||||||
SNR_PERSONALITY ScmpSyscall = SYS_PERSONALITY
|
|
||||||
SNR_EXIT ScmpSyscall = SYS_EXIT
|
|
||||||
SNR_EXIT_GROUP ScmpSyscall = SYS_EXIT_GROUP
|
|
||||||
SNR_WAITID ScmpSyscall = SYS_WAITID
|
|
||||||
SNR_SET_TID_ADDRESS ScmpSyscall = SYS_SET_TID_ADDRESS
|
|
||||||
SNR_UNSHARE ScmpSyscall = SYS_UNSHARE
|
|
||||||
SNR_FUTEX ScmpSyscall = SYS_FUTEX
|
|
||||||
SNR_SET_ROBUST_LIST ScmpSyscall = SYS_SET_ROBUST_LIST
|
|
||||||
SNR_GET_ROBUST_LIST ScmpSyscall = SYS_GET_ROBUST_LIST
|
|
||||||
SNR_NANOSLEEP ScmpSyscall = SYS_NANOSLEEP
|
|
||||||
SNR_GETITIMER ScmpSyscall = SYS_GETITIMER
|
|
||||||
SNR_SETITIMER ScmpSyscall = SYS_SETITIMER
|
|
||||||
SNR_KEXEC_LOAD ScmpSyscall = SYS_KEXEC_LOAD
|
|
||||||
SNR_INIT_MODULE ScmpSyscall = SYS_INIT_MODULE
|
|
||||||
SNR_DELETE_MODULE ScmpSyscall = SYS_DELETE_MODULE
|
|
||||||
SNR_TIMER_CREATE ScmpSyscall = SYS_TIMER_CREATE
|
|
||||||
SNR_TIMER_GETTIME ScmpSyscall = SYS_TIMER_GETTIME
|
|
||||||
SNR_TIMER_GETOVERRUN ScmpSyscall = SYS_TIMER_GETOVERRUN
|
|
||||||
SNR_TIMER_SETTIME ScmpSyscall = SYS_TIMER_SETTIME
|
|
||||||
SNR_TIMER_DELETE ScmpSyscall = SYS_TIMER_DELETE
|
|
||||||
SNR_CLOCK_SETTIME ScmpSyscall = SYS_CLOCK_SETTIME
|
|
||||||
SNR_CLOCK_GETTIME ScmpSyscall = SYS_CLOCK_GETTIME
|
|
||||||
SNR_CLOCK_GETRES ScmpSyscall = SYS_CLOCK_GETRES
|
|
||||||
SNR_CLOCK_NANOSLEEP ScmpSyscall = SYS_CLOCK_NANOSLEEP
|
|
||||||
SNR_SYSLOG ScmpSyscall = SYS_SYSLOG
|
|
||||||
SNR_PTRACE ScmpSyscall = SYS_PTRACE
|
|
||||||
SNR_SCHED_SETPARAM ScmpSyscall = SYS_SCHED_SETPARAM
|
|
||||||
SNR_SCHED_SETSCHEDULER ScmpSyscall = SYS_SCHED_SETSCHEDULER
|
|
||||||
SNR_SCHED_GETSCHEDULER ScmpSyscall = SYS_SCHED_GETSCHEDULER
|
|
||||||
SNR_SCHED_GETPARAM ScmpSyscall = SYS_SCHED_GETPARAM
|
|
||||||
SNR_SCHED_SETAFFINITY ScmpSyscall = SYS_SCHED_SETAFFINITY
|
|
||||||
SNR_SCHED_GETAFFINITY ScmpSyscall = SYS_SCHED_GETAFFINITY
|
|
||||||
SNR_SCHED_YIELD ScmpSyscall = SYS_SCHED_YIELD
|
|
||||||
SNR_SCHED_GET_PRIORITY_MAX ScmpSyscall = SYS_SCHED_GET_PRIORITY_MAX
|
|
||||||
SNR_SCHED_GET_PRIORITY_MIN ScmpSyscall = SYS_SCHED_GET_PRIORITY_MIN
|
|
||||||
SNR_SCHED_RR_GET_INTERVAL ScmpSyscall = SYS_SCHED_RR_GET_INTERVAL
|
|
||||||
SNR_RESTART_SYSCALL ScmpSyscall = SYS_RESTART_SYSCALL
|
|
||||||
SNR_KILL ScmpSyscall = SYS_KILL
|
|
||||||
SNR_TKILL ScmpSyscall = SYS_TKILL
|
|
||||||
SNR_TGKILL ScmpSyscall = SYS_TGKILL
|
|
||||||
SNR_SIGALTSTACK ScmpSyscall = SYS_SIGALTSTACK
|
|
||||||
SNR_RT_SIGSUSPEND ScmpSyscall = SYS_RT_SIGSUSPEND
|
|
||||||
SNR_RT_SIGACTION ScmpSyscall = SYS_RT_SIGACTION
|
|
||||||
SNR_RT_SIGPROCMASK ScmpSyscall = SYS_RT_SIGPROCMASK
|
|
||||||
SNR_RT_SIGPENDING ScmpSyscall = SYS_RT_SIGPENDING
|
|
||||||
SNR_RT_SIGTIMEDWAIT ScmpSyscall = SYS_RT_SIGTIMEDWAIT
|
|
||||||
SNR_RT_SIGQUEUEINFO ScmpSyscall = SYS_RT_SIGQUEUEINFO
|
|
||||||
SNR_RT_SIGRETURN ScmpSyscall = SYS_RT_SIGRETURN
|
|
||||||
SNR_SETPRIORITY ScmpSyscall = SYS_SETPRIORITY
|
|
||||||
SNR_GETPRIORITY ScmpSyscall = SYS_GETPRIORITY
|
|
||||||
SNR_REBOOT ScmpSyscall = SYS_REBOOT
|
|
||||||
SNR_SETREGID ScmpSyscall = SYS_SETREGID
|
|
||||||
SNR_SETGID ScmpSyscall = SYS_SETGID
|
|
||||||
SNR_SETREUID ScmpSyscall = SYS_SETREUID
|
|
||||||
SNR_SETUID ScmpSyscall = SYS_SETUID
|
|
||||||
SNR_SETRESUID ScmpSyscall = SYS_SETRESUID
|
|
||||||
SNR_GETRESUID ScmpSyscall = SYS_GETRESUID
|
|
||||||
SNR_SETRESGID ScmpSyscall = SYS_SETRESGID
|
|
||||||
SNR_GETRESGID ScmpSyscall = SYS_GETRESGID
|
|
||||||
SNR_SETFSUID ScmpSyscall = SYS_SETFSUID
|
|
||||||
SNR_SETFSGID ScmpSyscall = SYS_SETFSGID
|
|
||||||
SNR_TIMES ScmpSyscall = SYS_TIMES
|
|
||||||
SNR_SETPGID ScmpSyscall = SYS_SETPGID
|
|
||||||
SNR_GETPGID ScmpSyscall = SYS_GETPGID
|
|
||||||
SNR_GETSID ScmpSyscall = SYS_GETSID
|
|
||||||
SNR_SETSID ScmpSyscall = SYS_SETSID
|
|
||||||
SNR_GETGROUPS ScmpSyscall = SYS_GETGROUPS
|
|
||||||
SNR_SETGROUPS ScmpSyscall = SYS_SETGROUPS
|
|
||||||
SNR_UNAME ScmpSyscall = SYS_UNAME
|
|
||||||
SNR_SETHOSTNAME ScmpSyscall = SYS_SETHOSTNAME
|
|
||||||
SNR_SETDOMAINNAME ScmpSyscall = SYS_SETDOMAINNAME
|
|
||||||
SNR_GETRLIMIT ScmpSyscall = SYS_GETRLIMIT
|
|
||||||
SNR_SETRLIMIT ScmpSyscall = SYS_SETRLIMIT
|
|
||||||
SNR_GETRUSAGE ScmpSyscall = SYS_GETRUSAGE
|
|
||||||
SNR_UMASK ScmpSyscall = SYS_UMASK
|
|
||||||
SNR_PRCTL ScmpSyscall = SYS_PRCTL
|
|
||||||
SNR_GETCPU ScmpSyscall = SYS_GETCPU
|
|
||||||
SNR_GETTIMEOFDAY ScmpSyscall = SYS_GETTIMEOFDAY
|
|
||||||
SNR_SETTIMEOFDAY ScmpSyscall = SYS_SETTIMEOFDAY
|
|
||||||
SNR_ADJTIMEX ScmpSyscall = SYS_ADJTIMEX
|
|
||||||
SNR_GETPID ScmpSyscall = SYS_GETPID
|
|
||||||
SNR_GETPPID ScmpSyscall = SYS_GETPPID
|
|
||||||
SNR_GETUID ScmpSyscall = SYS_GETUID
|
|
||||||
SNR_GETEUID ScmpSyscall = SYS_GETEUID
|
|
||||||
SNR_GETGID ScmpSyscall = SYS_GETGID
|
|
||||||
SNR_GETEGID ScmpSyscall = SYS_GETEGID
|
|
||||||
SNR_GETTID ScmpSyscall = SYS_GETTID
|
|
||||||
SNR_SYSINFO ScmpSyscall = SYS_SYSINFO
|
|
||||||
SNR_MQ_OPEN ScmpSyscall = SYS_MQ_OPEN
|
|
||||||
SNR_MQ_UNLINK ScmpSyscall = SYS_MQ_UNLINK
|
|
||||||
SNR_MQ_TIMEDSEND ScmpSyscall = SYS_MQ_TIMEDSEND
|
|
||||||
SNR_MQ_TIMEDRECEIVE ScmpSyscall = SYS_MQ_TIMEDRECEIVE
|
|
||||||
SNR_MQ_NOTIFY ScmpSyscall = SYS_MQ_NOTIFY
|
|
||||||
SNR_MQ_GETSETATTR ScmpSyscall = SYS_MQ_GETSETATTR
|
|
||||||
SNR_MSGGET ScmpSyscall = SYS_MSGGET
|
|
||||||
SNR_MSGCTL ScmpSyscall = SYS_MSGCTL
|
|
||||||
SNR_MSGRCV ScmpSyscall = SYS_MSGRCV
|
|
||||||
SNR_MSGSND ScmpSyscall = SYS_MSGSND
|
|
||||||
SNR_SEMGET ScmpSyscall = SYS_SEMGET
|
|
||||||
SNR_SEMCTL ScmpSyscall = SYS_SEMCTL
|
|
||||||
SNR_SEMTIMEDOP ScmpSyscall = SYS_SEMTIMEDOP
|
|
||||||
SNR_SEMOP ScmpSyscall = SYS_SEMOP
|
|
||||||
SNR_SHMGET ScmpSyscall = SYS_SHMGET
|
|
||||||
SNR_SHMCTL ScmpSyscall = SYS_SHMCTL
|
|
||||||
SNR_SHMAT ScmpSyscall = SYS_SHMAT
|
|
||||||
SNR_SHMDT ScmpSyscall = SYS_SHMDT
|
|
||||||
SNR_SOCKET ScmpSyscall = SYS_SOCKET
|
|
||||||
SNR_SOCKETPAIR ScmpSyscall = SYS_SOCKETPAIR
|
|
||||||
SNR_BIND ScmpSyscall = SYS_BIND
|
|
||||||
SNR_LISTEN ScmpSyscall = SYS_LISTEN
|
|
||||||
SNR_ACCEPT ScmpSyscall = SYS_ACCEPT
|
|
||||||
SNR_CONNECT ScmpSyscall = SYS_CONNECT
|
|
||||||
SNR_GETSOCKNAME ScmpSyscall = SYS_GETSOCKNAME
|
|
||||||
SNR_GETPEERNAME ScmpSyscall = SYS_GETPEERNAME
|
|
||||||
SNR_SENDTO ScmpSyscall = SYS_SENDTO
|
|
||||||
SNR_RECVFROM ScmpSyscall = SYS_RECVFROM
|
|
||||||
SNR_SETSOCKOPT ScmpSyscall = SYS_SETSOCKOPT
|
|
||||||
SNR_GETSOCKOPT ScmpSyscall = SYS_GETSOCKOPT
|
|
||||||
SNR_SHUTDOWN ScmpSyscall = SYS_SHUTDOWN
|
|
||||||
SNR_SENDMSG ScmpSyscall = SYS_SENDMSG
|
|
||||||
SNR_RECVMSG ScmpSyscall = SYS_RECVMSG
|
|
||||||
SNR_READAHEAD ScmpSyscall = SYS_READAHEAD
|
|
||||||
SNR_BRK ScmpSyscall = SYS_BRK
|
|
||||||
SNR_MUNMAP ScmpSyscall = SYS_MUNMAP
|
|
||||||
SNR_MREMAP ScmpSyscall = SYS_MREMAP
|
|
||||||
SNR_ADD_KEY ScmpSyscall = SYS_ADD_KEY
|
|
||||||
SNR_REQUEST_KEY ScmpSyscall = SYS_REQUEST_KEY
|
|
||||||
SNR_KEYCTL ScmpSyscall = SYS_KEYCTL
|
|
||||||
SNR_CLONE ScmpSyscall = SYS_CLONE
|
|
||||||
SNR_EXECVE ScmpSyscall = SYS_EXECVE
|
|
||||||
SNR_MMAP ScmpSyscall = SYS_MMAP
|
|
||||||
SNR_FADVISE64 ScmpSyscall = SYS_FADVISE64
|
|
||||||
SNR_SWAPON ScmpSyscall = SYS_SWAPON
|
|
||||||
SNR_SWAPOFF ScmpSyscall = SYS_SWAPOFF
|
|
||||||
SNR_MPROTECT ScmpSyscall = SYS_MPROTECT
|
|
||||||
SNR_MSYNC ScmpSyscall = SYS_MSYNC
|
|
||||||
SNR_MLOCK ScmpSyscall = SYS_MLOCK
|
|
||||||
SNR_MUNLOCK ScmpSyscall = SYS_MUNLOCK
|
|
||||||
SNR_MLOCKALL ScmpSyscall = SYS_MLOCKALL
|
|
||||||
SNR_MUNLOCKALL ScmpSyscall = SYS_MUNLOCKALL
|
|
||||||
SNR_MINCORE ScmpSyscall = SYS_MINCORE
|
|
||||||
SNR_MADVISE ScmpSyscall = SYS_MADVISE
|
|
||||||
SNR_REMAP_FILE_PAGES ScmpSyscall = SYS_REMAP_FILE_PAGES
|
|
||||||
SNR_MBIND ScmpSyscall = SYS_MBIND
|
|
||||||
SNR_GET_MEMPOLICY ScmpSyscall = SYS_GET_MEMPOLICY
|
|
||||||
SNR_SET_MEMPOLICY ScmpSyscall = SYS_SET_MEMPOLICY
|
|
||||||
SNR_MIGRATE_PAGES ScmpSyscall = SYS_MIGRATE_PAGES
|
|
||||||
SNR_MOVE_PAGES ScmpSyscall = SYS_MOVE_PAGES
|
|
||||||
SNR_RT_TGSIGQUEUEINFO ScmpSyscall = SYS_RT_TGSIGQUEUEINFO
|
|
||||||
SNR_PERF_EVENT_OPEN ScmpSyscall = SYS_PERF_EVENT_OPEN
|
|
||||||
SNR_ACCEPT4 ScmpSyscall = SYS_ACCEPT4
|
|
||||||
SNR_RECVMMSG ScmpSyscall = SYS_RECVMMSG
|
|
||||||
SNR_WAIT4 ScmpSyscall = SYS_WAIT4
|
|
||||||
SNR_PRLIMIT64 ScmpSyscall = SYS_PRLIMIT64
|
|
||||||
SNR_FANOTIFY_INIT ScmpSyscall = SYS_FANOTIFY_INIT
|
|
||||||
SNR_FANOTIFY_MARK ScmpSyscall = SYS_FANOTIFY_MARK
|
|
||||||
SNR_NAME_TO_HANDLE_AT ScmpSyscall = SYS_NAME_TO_HANDLE_AT
|
|
||||||
SNR_OPEN_BY_HANDLE_AT ScmpSyscall = SYS_OPEN_BY_HANDLE_AT
|
|
||||||
SNR_CLOCK_ADJTIME ScmpSyscall = SYS_CLOCK_ADJTIME
|
|
||||||
SNR_SYNCFS ScmpSyscall = SYS_SYNCFS
|
|
||||||
SNR_SETNS ScmpSyscall = SYS_SETNS
|
|
||||||
SNR_SENDMMSG ScmpSyscall = SYS_SENDMMSG
|
|
||||||
SNR_PROCESS_VM_READV ScmpSyscall = SYS_PROCESS_VM_READV
|
|
||||||
SNR_PROCESS_VM_WRITEV ScmpSyscall = SYS_PROCESS_VM_WRITEV
|
|
||||||
SNR_KCMP ScmpSyscall = SYS_KCMP
|
|
||||||
SNR_FINIT_MODULE ScmpSyscall = SYS_FINIT_MODULE
|
|
||||||
SNR_SCHED_SETATTR ScmpSyscall = SYS_SCHED_SETATTR
|
|
||||||
SNR_SCHED_GETATTR ScmpSyscall = SYS_SCHED_GETATTR
|
|
||||||
SNR_RENAMEAT2 ScmpSyscall = SYS_RENAMEAT2
|
|
||||||
SNR_SECCOMP ScmpSyscall = SYS_SECCOMP
|
|
||||||
SNR_GETRANDOM ScmpSyscall = SYS_GETRANDOM
|
|
||||||
SNR_MEMFD_CREATE ScmpSyscall = SYS_MEMFD_CREATE
|
|
||||||
SNR_BPF ScmpSyscall = SYS_BPF
|
|
||||||
SNR_EXECVEAT ScmpSyscall = SYS_EXECVEAT
|
|
||||||
SNR_USERFAULTFD ScmpSyscall = SYS_USERFAULTFD
|
|
||||||
SNR_MEMBARRIER ScmpSyscall = SYS_MEMBARRIER
|
|
||||||
SNR_MLOCK2 ScmpSyscall = SYS_MLOCK2
|
|
||||||
SNR_COPY_FILE_RANGE ScmpSyscall = SYS_COPY_FILE_RANGE
|
|
||||||
SNR_PREADV2 ScmpSyscall = SYS_PREADV2
|
|
||||||
SNR_PWRITEV2 ScmpSyscall = SYS_PWRITEV2
|
|
||||||
SNR_PKEY_MPROTECT ScmpSyscall = SYS_PKEY_MPROTECT
|
|
||||||
SNR_PKEY_ALLOC ScmpSyscall = SYS_PKEY_ALLOC
|
|
||||||
SNR_PKEY_FREE ScmpSyscall = SYS_PKEY_FREE
|
|
||||||
SNR_STATX ScmpSyscall = SYS_STATX
|
|
||||||
SNR_IO_PGETEVENTS ScmpSyscall = SYS_IO_PGETEVENTS
|
|
||||||
SNR_RSEQ ScmpSyscall = SYS_RSEQ
|
|
||||||
SNR_KEXEC_FILE_LOAD ScmpSyscall = SYS_KEXEC_FILE_LOAD
|
|
||||||
SNR_PIDFD_SEND_SIGNAL ScmpSyscall = SYS_PIDFD_SEND_SIGNAL
|
|
||||||
SNR_IO_URING_SETUP ScmpSyscall = SYS_IO_URING_SETUP
|
|
||||||
SNR_IO_URING_ENTER ScmpSyscall = SYS_IO_URING_ENTER
|
|
||||||
SNR_IO_URING_REGISTER ScmpSyscall = SYS_IO_URING_REGISTER
|
|
||||||
SNR_OPEN_TREE ScmpSyscall = SYS_OPEN_TREE
|
|
||||||
SNR_MOVE_MOUNT ScmpSyscall = SYS_MOVE_MOUNT
|
|
||||||
SNR_FSOPEN ScmpSyscall = SYS_FSOPEN
|
|
||||||
SNR_FSCONFIG ScmpSyscall = SYS_FSCONFIG
|
|
||||||
SNR_FSMOUNT ScmpSyscall = SYS_FSMOUNT
|
|
||||||
SNR_FSPICK ScmpSyscall = SYS_FSPICK
|
|
||||||
SNR_PIDFD_OPEN ScmpSyscall = SYS_PIDFD_OPEN
|
|
||||||
SNR_CLONE3 ScmpSyscall = SYS_CLONE3
|
|
||||||
SNR_CLOSE_RANGE ScmpSyscall = SYS_CLOSE_RANGE
|
|
||||||
SNR_OPENAT2 ScmpSyscall = SYS_OPENAT2
|
|
||||||
SNR_PIDFD_GETFD ScmpSyscall = SYS_PIDFD_GETFD
|
|
||||||
SNR_FACCESSAT2 ScmpSyscall = SYS_FACCESSAT2
|
|
||||||
SNR_PROCESS_MADVISE ScmpSyscall = SYS_PROCESS_MADVISE
|
|
||||||
SNR_EPOLL_PWAIT2 ScmpSyscall = SYS_EPOLL_PWAIT2
|
|
||||||
SNR_MOUNT_SETATTR ScmpSyscall = SYS_MOUNT_SETATTR
|
|
||||||
SNR_QUOTACTL_FD ScmpSyscall = SYS_QUOTACTL_FD
|
|
||||||
SNR_LANDLOCK_CREATE_RULESET ScmpSyscall = SYS_LANDLOCK_CREATE_RULESET
|
|
||||||
SNR_LANDLOCK_ADD_RULE ScmpSyscall = SYS_LANDLOCK_ADD_RULE
|
|
||||||
SNR_LANDLOCK_RESTRICT_SELF ScmpSyscall = SYS_LANDLOCK_RESTRICT_SELF
|
|
||||||
SNR_MEMFD_SECRET ScmpSyscall = SYS_MEMFD_SECRET
|
|
||||||
SNR_PROCESS_MRELEASE ScmpSyscall = SYS_PROCESS_MRELEASE
|
|
||||||
SNR_FUTEX_WAITV ScmpSyscall = SYS_FUTEX_WAITV
|
|
||||||
SNR_SET_MEMPOLICY_HOME_NODE ScmpSyscall = SYS_SET_MEMPOLICY_HOME_NODE
|
|
||||||
SNR_CACHESTAT ScmpSyscall = SYS_CACHESTAT
|
|
||||||
SNR_FCHMODAT2 ScmpSyscall = SYS_FCHMODAT2
|
|
||||||
SNR_MAP_SHADOW_STACK ScmpSyscall = SYS_MAP_SHADOW_STACK
|
|
||||||
SNR_FUTEX_WAKE ScmpSyscall = SYS_FUTEX_WAKE
|
|
||||||
SNR_FUTEX_WAIT ScmpSyscall = SYS_FUTEX_WAIT
|
|
||||||
SNR_FUTEX_REQUEUE ScmpSyscall = SYS_FUTEX_REQUEUE
|
|
||||||
SNR_STATMOUNT ScmpSyscall = SYS_STATMOUNT
|
|
||||||
SNR_LISTMOUNT ScmpSyscall = SYS_LISTMOUNT
|
|
||||||
SNR_LSM_GET_SELF_ATTR ScmpSyscall = SYS_LSM_GET_SELF_ATTR
|
|
||||||
SNR_LSM_SET_SELF_ATTR ScmpSyscall = SYS_LSM_SET_SELF_ATTR
|
|
||||||
SNR_LSM_LIST_MODULES ScmpSyscall = SYS_LSM_LIST_MODULES
|
|
||||||
SNR_MSEAL ScmpSyscall = SYS_MSEAL
|
|
||||||
SNR_SETXATTRAT ScmpSyscall = SYS_SETXATTRAT
|
|
||||||
SNR_GETXATTRAT ScmpSyscall = SYS_GETXATTRAT
|
|
||||||
SNR_LISTXATTRAT ScmpSyscall = SYS_LISTXATTRAT
|
|
||||||
SNR_REMOVEXATTRAT ScmpSyscall = SYS_REMOVEXATTRAT
|
|
||||||
SNR_OPEN_TREE_ATTR ScmpSyscall = SYS_OPEN_TREE_ATTR
|
|
||||||
SNR_FILE_GETATTR ScmpSyscall = SYS_FILE_GETATTR
|
|
||||||
SNR_FILE_SETATTR ScmpSyscall = SYS_FILE_SETATTR
|
|
||||||
)
|
|
||||||
3
dist/comp/_hakurei
vendored
3
dist/comp/_hakurei
vendored
@@ -17,8 +17,7 @@ _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]' \
|
||||||
'--pipewire[Enable connection to PipeWire via SecurityContext]' \
|
'--pulse[Enable direct connection to PulseAudio]' \
|
||||||
'--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]' \
|
||||||
|
|||||||
12
dist/install.sh
vendored
12
dist/install.sh
vendored
@@ -1,12 +1,12 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
cd "$(dirname -- "$0")" || exit 1
|
cd "$(dirname -- "$0")" || exit 1
|
||||||
|
|
||||||
install -vDm0755 "bin/hakurei" "${DESTDIR}/usr/bin/hakurei"
|
install -vDm0755 "bin/hakurei" "${HAKUREI_INSTALL_PREFIX}/usr/bin/hakurei"
|
||||||
install -vDm0755 "bin/sharefs" "${DESTDIR}/usr/bin/sharefs"
|
install -vDm0755 "bin/hpkg" "${HAKUREI_INSTALL_PREFIX}/usr/bin/hpkg"
|
||||||
|
|
||||||
install -vDm4511 "bin/hsu" "${DESTDIR}/usr/bin/hsu"
|
install -vDm4511 "bin/hsu" "${HAKUREI_INSTALL_PREFIX}/usr/bin/hsu"
|
||||||
if [ ! -f "${DESTDIR}/etc/hsurc" ]; then
|
if [ ! -f "${HAKUREI_INSTALL_PREFIX}/etc/hsurc" ]; then
|
||||||
install -vDm0400 "hsurc.default" "${DESTDIR}/etc/hsurc"
|
install -vDm0400 "hsurc.default" "${HAKUREI_INSTALL_PREFIX}/etc/hsurc"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
install -vDm0644 "comp/_hakurei" "${DESTDIR}/usr/share/zsh/site-functions/_hakurei"
|
install -vDm0644 "comp/_hakurei" "${HAKUREI_INSTALL_PREFIX}/usr/share/zsh/site-functions/_hakurei"
|
||||||
23
dist/release.sh
vendored
23
dist/release.sh
vendored
@@ -1,31 +1,20 @@
|
|||||||
#!/bin/sh -e
|
#!/bin/sh -e
|
||||||
cd "$(dirname -- "$0")/.."
|
cd "$(dirname -- "$0")/.."
|
||||||
VERSION="${HAKUREI_VERSION:-untagged}"
|
VERSION="${HAKUREI_VERSION:-untagged}"
|
||||||
pname="hakurei-${VERSION}-$(go env GOARCH)"
|
pname="hakurei-${VERSION}"
|
||||||
out="${DESTDIR:-dist}/${pname}"
|
out="dist/${pname}"
|
||||||
|
|
||||||
echo '# Preparing distribution files.'
|
|
||||||
mkdir -p "${out}"
|
mkdir -p "${out}"
|
||||||
cp -v "README.md" "dist/hsurc.default" "dist/install.sh" "${out}"
|
cp -v "README.md" "dist/hsurc.default" "dist/install.sh" "${out}"
|
||||||
cp -rv "dist/comp" "${out}"
|
cp -rv "dist/comp" "${out}"
|
||||||
echo
|
|
||||||
|
|
||||||
echo '# Building hakurei.'
|
|
||||||
go generate ./...
|
go generate ./...
|
||||||
go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w
|
go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w -buildid=''
|
||||||
-buildid= -extldflags '-static'
|
|
||||||
-X hakurei.app/internal/info.buildVersion=${VERSION}
|
-X hakurei.app/internal/info.buildVersion=${VERSION}
|
||||||
-X hakurei.app/internal/info.hakureiPath=/usr/bin/hakurei
|
-X hakurei.app/internal/info.hakureiPath=/usr/bin/hakurei
|
||||||
-X hakurei.app/internal/info.hsuPath=/usr/bin/hsu
|
-X hakurei.app/internal/info.hsuPath=/usr/bin/hsu
|
||||||
-X main.hakureiPath=/usr/bin/hakurei" ./...
|
-X main.hakureiPath=/usr/bin/hakurei" ./...
|
||||||
echo
|
|
||||||
|
|
||||||
echo '# Testing hakurei.'
|
rm -f "./${out}.tar.gz" && tar -C dist -czf "${out}.tar.gz" "${pname}"
|
||||||
go test -ldflags='-buildid= -extldflags=-static' ./...
|
rm -rf "./${out}"
|
||||||
echo
|
(cd dist && sha512sum "${pname}.tar.gz" > "${pname}.tar.gz.sha512")
|
||||||
|
|
||||||
echo '# Creating distribution.'
|
|
||||||
rm -f "${out}.tar.gz" && tar -C "${out}/.." -vczf "${out}.tar.gz" "${pname}"
|
|
||||||
rm -rf "${out}"
|
|
||||||
(cd "${out}/.." && sha512sum "${pname}.tar.gz" > "${pname}.tar.gz.sha512")
|
|
||||||
echo
|
|
||||||
|
|||||||
16
flake.lock
generated
16
flake.lock
generated
@@ -7,32 +7,32 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1765384171,
|
"lastModified": 1756679287,
|
||||||
"narHash": "sha256-FuFtkJrW1Z7u+3lhzPRau69E0CNjADku1mLQQflUORo=",
|
"narHash": "sha256-Xd1vOeY9ccDf5VtVK12yM0FS6qqvfUop8UQlxEB+gTQ=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "44777152652bc9eacf8876976fa72cc77ca8b9d8",
|
"rev": "07fc025fe10487dd80f2ec694f1cd790e752d0e8",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"ref": "release-25.11",
|
"ref": "release-25.05",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1765311797,
|
"lastModified": 1757020766,
|
||||||
"narHash": "sha256-mSD5Ob7a+T2RNjvPvOA1dkJHGVrNVl8ZOrAwBjKBDQo=",
|
"narHash": "sha256-PLoSjHRa2bUbi1x9HoXgTx2AiuzNXs54c8omhadyvp0=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "09eb77e94fa25202af8f3e81ddc7353d9970ac1b",
|
"rev": "fe83bbdde2ccdc2cb9573aa846abe8363f79a97a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"ref": "nixos-25.11",
|
"ref": "nixos-25.05",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
|
|||||||
53
flake.nix
53
flake.nix
@@ -2,10 +2,10 @@
|
|||||||
description = "hakurei container tool and nixos module";
|
description = "hakurei container tool and nixos module";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
|
||||||
|
|
||||||
home-manager = {
|
home-manager = {
|
||||||
url = "github:nix-community/home-manager/release-25.11";
|
url = "github:nix-community/home-manager/release-25.05";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -69,8 +69,6 @@
|
|||||||
withRace = true;
|
withRace = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
sharefs = callPackage ./cmd/sharefs/test { inherit system self; };
|
|
||||||
|
|
||||||
hpkg = callPackage ./cmd/hpkg/test { inherit system self; };
|
hpkg = callPackage ./cmd/hpkg/test { inherit system self; };
|
||||||
|
|
||||||
formatting = runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt-rfc-style ]; } ''
|
formatting = runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt-rfc-style ]; } ''
|
||||||
@@ -112,7 +110,7 @@
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
default = hakurei;
|
default = hakurei;
|
||||||
hakurei = pkgs.pkgsStatic.callPackage ./package.nix {
|
hakurei = pkgs.callPackage ./package.nix {
|
||||||
inherit (pkgs)
|
inherit (pkgs)
|
||||||
# passthru.buildInputs
|
# passthru.buildInputs
|
||||||
go
|
go
|
||||||
@@ -138,32 +136,20 @@
|
|||||||
;
|
;
|
||||||
};
|
};
|
||||||
hsu = pkgs.callPackage ./cmd/hsu/package.nix { inherit (self.packages.${system}) hakurei; };
|
hsu = pkgs.callPackage ./cmd/hsu/package.nix { inherit (self.packages.${system}) hakurei; };
|
||||||
sharefs = pkgs.linkFarm "sharefs" {
|
|
||||||
"bin/sharefs" = "${hakurei}/libexec/sharefs";
|
|
||||||
"bin/mount.fuse.sharefs" = "${hakurei}/libexec/sharefs";
|
|
||||||
};
|
|
||||||
|
|
||||||
dist =
|
dist = pkgs.runCommand "${hakurei.name}-dist" { buildInputs = hakurei.targetPkgs ++ [ pkgs.pkgsStatic.musl ]; } ''
|
||||||
pkgs.runCommand "${hakurei.name}-dist"
|
# go requires XDG_CACHE_HOME for the build cache
|
||||||
{
|
export XDG_CACHE_HOME="$(mktemp -d)"
|
||||||
buildInputs = hakurei.targetPkgs ++ [
|
|
||||||
pkgs.pkgsStatic.musl
|
|
||||||
];
|
|
||||||
}
|
|
||||||
''
|
|
||||||
cd $(mktemp -d) \
|
|
||||||
&& cp -r ${hakurei.src}/. . \
|
|
||||||
&& chmod +w cmd && cp -r ${hsu.src}/. cmd/hsu/ \
|
|
||||||
&& chmod -R +w .
|
|
||||||
|
|
||||||
CC="musl-clang -O3 -Werror -Qunused-arguments" \
|
# get a different workdir as go does not like /build
|
||||||
GOCACHE="$(mktemp -d)" \
|
cd $(mktemp -d) \
|
||||||
HAKUREI_TEST_SKIP_ACL=1 \
|
&& cp -r ${hakurei.src}/. . \
|
||||||
PATH="${pkgs.pkgsStatic.musl.bin}/bin:$PATH" \
|
&& chmod +w cmd && cp -r ${hsu.src}/. cmd/hsu/ \
|
||||||
DESTDIR="$out" \
|
&& chmod -R +w .
|
||||||
HAKUREI_VERSION="v${hakurei.version}" \
|
|
||||||
./dist/release.sh
|
export HAKUREI_VERSION="v${hakurei.version}"
|
||||||
'';
|
CC="clang -O3 -Werror" ./dist/release.sh && mkdir $out && cp -v "dist/hakurei-$HAKUREI_VERSION.tar.gz"* $out
|
||||||
|
'';
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -174,10 +160,7 @@
|
|||||||
pkgs = nixpkgsFor.${system};
|
pkgs = nixpkgsFor.${system};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
default = pkgs.mkShell {
|
default = pkgs.mkShell { buildInputs = hakurei.targetPkgs; };
|
||||||
buildInputs = hakurei.targetPkgs;
|
|
||||||
hardeningDisable = [ "fortify" ];
|
|
||||||
};
|
|
||||||
withPackage = pkgs.mkShell { buildInputs = [ hakurei ] ++ hakurei.targetPkgs; };
|
withPackage = pkgs.mkShell { buildInputs = [ hakurei ] ++ hakurei.targetPkgs; };
|
||||||
|
|
||||||
vm =
|
vm =
|
||||||
@@ -202,13 +185,13 @@
|
|||||||
hakurei =
|
hakurei =
|
||||||
let
|
let
|
||||||
# this is used for interactive vm testing during development, where tests might be broken
|
# this is used for interactive vm testing during development, where tests might be broken
|
||||||
package = self.packages.${pkgs.stdenv.hostPlatform.system}.hakurei.override {
|
package = self.packages.${pkgs.system}.hakurei.override {
|
||||||
buildGoModule = previousArgs: pkgs.pkgsStatic.buildGoModule (previousArgs // { doCheck = false; });
|
buildGoModule = previousArgs: pkgs.pkgsStatic.buildGoModule (previousArgs // { doCheck = false; });
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
inherit package;
|
inherit package;
|
||||||
hsuPackage = self.packages.${pkgs.stdenv.hostPlatform.system}.hsu.override { hakurei = package; };
|
hsuPackage = self.packages.${pkgs.system}.hsu.override { hakurei = package; };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,54 +23,9 @@ 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 the PipeWire socket established via SecurityContext::Create, no
|
|
||||||
// attempt is made to start the pipewire-pulse server.
|
|
||||||
//
|
|
||||||
// The SecurityContext machinery is fatally flawed, it blindly sets read and execute
|
|
||||||
// bits on all objects for clients with the lowest achievable privilege level (by
|
|
||||||
// setting PW_KEY_ACCESS to "restricted"). This enables them to call any method
|
|
||||||
// targeting any object, and since Registry::Destroy checks for the read and execute bit,
|
|
||||||
// allows the destruction of any object other than PW_ID_CORE as well. This behaviour
|
|
||||||
// is implemented separately in media-session and wireplumber, with the wireplumber
|
|
||||||
// implementation in Lua via an embedded Lua vm. In all known setups, wireplumber is
|
|
||||||
// in use, and there is no known way to change its behaviour and set permissions
|
|
||||||
// differently without replacing the Lua script. Also, since PipeWire relies on these
|
|
||||||
// permissions to work, reducing them is not possible.
|
|
||||||
//
|
|
||||||
// Currently, the only other sandboxed use case is flatpak, which is not aware of
|
|
||||||
// PipeWire and blindly exposes the bare PulseAudio socket to the container (behaves
|
|
||||||
// like DirectPulse). This socket is backed by the pipewire-pulse compatibility daemon,
|
|
||||||
// which obtains client pid via the SO_PEERCRED option. The PipeWire daemon, pipewire-pulse
|
|
||||||
// daemon and the session manager daemon then separately performs the /.flatpak-info hack
|
|
||||||
// described in https://git.gensokyo.uk/security/hakurei/issues/21. Under such use case,
|
|
||||||
// since the client has no direct access to PipeWire, insecure parts of the protocol are
|
|
||||||
// obscured by pipewire-pulse simply not implementing them, and thus hiding the flaws
|
|
||||||
// described above.
|
|
||||||
//
|
|
||||||
// Hakurei does not rely on the /.flatpak-info hack. Instead, a socket is sets up via
|
|
||||||
// SecurityContext. A pipewire-pulse server connected through it achieves the same
|
|
||||||
// permissions as flatpak does via the /.flatpak-info hack and is maintained for the
|
|
||||||
// life of the container.
|
|
||||||
//
|
|
||||||
// This option is unsupported and enables a denial-of-service attack as the sandboxed
|
|
||||||
// client is able to destroy any client object and thus disconnecting them from PipeWire,
|
|
||||||
// or destroy the SecurityContext object preventing any further container creation.
|
|
||||||
// Do not set this to true, it is insecure under any configuration.
|
|
||||||
DirectPipeWire bool `json:"direct_pipewire,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, it 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"`
|
||||||
@@ -94,9 +49,6 @@ 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.
|
||||||
@@ -143,11 +95,6 @@ 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,12 +53,6 @@ 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,8 +17,6 @@ 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
|
||||||
|
|
||||||
@@ -37,8 +35,6 @@ 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:
|
||||||
@@ -66,11 +62,10 @@ 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"`
|
||||||
PipeWire bool `json:"pipewire,omitempty"`
|
Pulse bool `json:"pulse,omitempty"`
|
||||||
Pulse bool `json:"pulse,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unwrap returns the underlying [Enablement].
|
// Unwrap returns the underlying [Enablement].
|
||||||
@@ -86,11 +81,10 @@ 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,
|
||||||
PipeWire: Enablement(*e)&EPipeWire != 0,
|
Pulse: Enablement(*e)&EPulse != 0,
|
||||||
Pulse: Enablement(*e)&EPulse != 0,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,9 +108,6 @@ 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,7 +32,6 @@ 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"},
|
||||||
@@ -63,9 +62,8 @@ 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.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}`},
|
{"all", hst.NewEnablements(hst.EWayland | hst.EX11 | hst.EDBus | hst.EPulse), `{"wayland":true,"x11":true,"dbus":true,"pulse":true}`, `{"value":{"wayland":true,"x11":true,"dbus":true,"pulse":true},"magic":3236757504}`},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
|||||||
12
hst/fs.go
12
hst/fs.go
@@ -45,9 +45,6 @@ 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.
|
||||||
@@ -127,12 +124,6 @@ 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}
|
||||||
}
|
}
|
||||||
@@ -161,9 +152,6 @@ 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,16 +84,6 @@ 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 {
|
||||||
@@ -355,10 +345,6 @@ 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))
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
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`},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -56,6 +56,8 @@ type Paths struct {
|
|||||||
type Info struct {
|
type Info struct {
|
||||||
// WaylandVersion is the libwayland value of WAYLAND_VERSION.
|
// WaylandVersion is the libwayland value of WAYLAND_VERSION.
|
||||||
WaylandVersion string `json:"WAYLAND_VERSION"`
|
WaylandVersion string `json:"WAYLAND_VERSION"`
|
||||||
|
// PipeWireVersion is the pipewire value of pw_get_headers_version().
|
||||||
|
PipeWireVersion string `json:"pw_get_headers_version"`
|
||||||
|
|
||||||
// Version is a hardcoded version string.
|
// Version is a hardcoded version string.
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
@@ -70,7 +72,7 @@ func Template() *Config {
|
|||||||
return &Config{
|
return &Config{
|
||||||
ID: "org.chromium.Chromium",
|
ID: "org.chromium.Chromium",
|
||||||
|
|
||||||
Enablements: NewEnablements(EWayland | EDBus | EPipeWire),
|
Enablements: NewEnablements(EWayland | EDBus | EPulse),
|
||||||
|
|
||||||
SessionBus: &BusConfig{
|
SessionBus: &BusConfig{
|
||||||
See: nil,
|
See: nil,
|
||||||
@@ -92,6 +94,7 @@ 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,
|
||||||
"pipewire": true
|
"pulse": true
|
||||||
},
|
},
|
||||||
"session_bus": {
|
"session_bus": {
|
||||||
"see": null,
|
"see": null,
|
||||||
|
|||||||
@@ -24,8 +24,9 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestUpdate(t *testing.T) {
|
func TestUpdate(t *testing.T) {
|
||||||
if os.Getenv("HAKUREI_TEST_SKIP_ACL") == "1" {
|
if os.Getenv("GO_TEST_SKIP_ACL") == "1" {
|
||||||
t.Skip("acl test skipped")
|
t.Log("acl test skipped")
|
||||||
|
t.SkipNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
testFilePath := path.Join(t.TempDir(), testFileName)
|
testFilePath := path.Join(t.TempDir(), testFileName)
|
||||||
@@ -142,7 +143,6 @@ func (c *getFAclInvocation) run(name string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.cmd = exec.Command("getfacl", "--omit-header", "--absolute-names", "--numeric", name)
|
c.cmd = exec.Command("getfacl", "--omit-header", "--absolute-names", "--numeric", name)
|
||||||
c.cmd.Stderr = os.Stderr
|
|
||||||
|
|
||||||
scanErr := make(chan error, 1)
|
scanErr := make(chan error, 1)
|
||||||
if p, err := c.cmd.StdoutPipe(); err != nil {
|
if p, err := c.cmd.StdoutPipe(); err != nil {
|
||||||
@@ -254,7 +254,7 @@ func getfacl(t *testing.T, name string) []*getFAclResp {
|
|||||||
t.Fatalf("getfacl: error = %v", err)
|
t.Fatalf("getfacl: error = %v", err)
|
||||||
}
|
}
|
||||||
if len(c.pe) != 0 {
|
if len(c.pe) != 0 {
|
||||||
t.Errorf("errors encountered parsing getfacl output\n%s", errors.Join(c.pe...))
|
t.Errorf("errors encountered parsing getfacl output\n%s", errors.Join(c.pe...).Error())
|
||||||
}
|
}
|
||||||
return c.val
|
return c.val
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,10 +53,6 @@ type syscallDispatcher interface {
|
|||||||
readdir(name string) ([]os.DirEntry, error)
|
readdir(name string) ([]os.DirEntry, error)
|
||||||
// tempdir provides [os.TempDir].
|
// tempdir provides [os.TempDir].
|
||||||
tempdir() string
|
tempdir() string
|
||||||
// mkdir provides [os.Mkdir].
|
|
||||||
mkdir(name string, perm os.FileMode) error
|
|
||||||
// removeAll provides [os.RemoveAll].
|
|
||||||
removeAll(path string) error
|
|
||||||
// exit provides [os.Exit].
|
// exit provides [os.Exit].
|
||||||
exit(code int)
|
exit(code int)
|
||||||
|
|
||||||
@@ -66,8 +62,6 @@ type syscallDispatcher interface {
|
|||||||
// lookupGroupId calls [user.LookupGroup] and returns the Gid field of the resulting [user.Group] struct.
|
// lookupGroupId calls [user.LookupGroup] and returns the Gid field of the resulting [user.Group] struct.
|
||||||
lookupGroupId(name string) (string, error)
|
lookupGroupId(name string) (string, error)
|
||||||
|
|
||||||
// lookPath provides exec.LookPath.
|
|
||||||
lookPath(file string) (string, error)
|
|
||||||
// cmdOutput provides the Output method of [exec.Cmd].
|
// cmdOutput provides the Output method of [exec.Cmd].
|
||||||
cmdOutput(cmd *exec.Cmd) ([]byte, error)
|
cmdOutput(cmd *exec.Cmd) ([]byte, error)
|
||||||
|
|
||||||
@@ -127,8 +121,6 @@ func (direct) stat(name string) (os.FileInfo, error) { return os.Stat(name)
|
|||||||
func (direct) open(name string) (osFile, error) { return os.Open(name) }
|
func (direct) open(name string) (osFile, error) { return os.Open(name) }
|
||||||
func (direct) readdir(name string) ([]os.DirEntry, error) { return os.ReadDir(name) }
|
func (direct) readdir(name string) ([]os.DirEntry, error) { return os.ReadDir(name) }
|
||||||
func (direct) tempdir() string { return os.TempDir() }
|
func (direct) tempdir() string { return os.TempDir() }
|
||||||
func (direct) mkdir(name string, perm os.FileMode) error { return os.Mkdir(name, perm) }
|
|
||||||
func (direct) removeAll(path string) error { return os.RemoveAll(path) }
|
|
||||||
func (direct) exit(code int) { os.Exit(code) }
|
func (direct) exit(code int) { os.Exit(code) }
|
||||||
|
|
||||||
func (direct) evalSymlinks(path string) (string, error) { return filepath.EvalSymlinks(path) }
|
func (direct) evalSymlinks(path string) (string, error) { return filepath.EvalSymlinks(path) }
|
||||||
@@ -142,7 +134,6 @@ func (direct) lookupGroupId(name string) (gid string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (direct) lookPath(file string) (string, error) { return exec.LookPath(file) }
|
|
||||||
func (direct) cmdOutput(cmd *exec.Cmd) ([]byte, error) { return cmd.Output() }
|
func (direct) cmdOutput(cmd *exec.Cmd) ([]byte, error) { return cmd.Output() }
|
||||||
|
|
||||||
func (direct) notifyContext(parent context.Context, signals ...os.Signal) (ctx context.Context, stop context.CancelFunc) {
|
func (direct) notifyContext(parent context.Context, signals ...os.Signal) (ctx context.Context, stop context.CancelFunc) {
|
||||||
|
|||||||
@@ -701,13 +701,10 @@ func (panicDispatcher) stat(string) (os.FileInfo, error) { pa
|
|||||||
func (panicDispatcher) open(string) (osFile, error) { panic("unreachable") }
|
func (panicDispatcher) open(string) (osFile, error) { panic("unreachable") }
|
||||||
func (panicDispatcher) readdir(string) ([]os.DirEntry, error) { panic("unreachable") }
|
func (panicDispatcher) readdir(string) ([]os.DirEntry, error) { panic("unreachable") }
|
||||||
func (panicDispatcher) tempdir() string { panic("unreachable") }
|
func (panicDispatcher) tempdir() string { panic("unreachable") }
|
||||||
func (panicDispatcher) mkdir(string, os.FileMode) error { panic("unreachable") }
|
|
||||||
func (panicDispatcher) removeAll(string) error { panic("unreachable") }
|
|
||||||
func (panicDispatcher) exit(int) { panic("unreachable") }
|
func (panicDispatcher) exit(int) { panic("unreachable") }
|
||||||
func (panicDispatcher) evalSymlinks(string) (string, error) { panic("unreachable") }
|
func (panicDispatcher) evalSymlinks(string) (string, error) { panic("unreachable") }
|
||||||
func (panicDispatcher) prctl(uintptr, uintptr, uintptr) error { panic("unreachable") }
|
func (panicDispatcher) prctl(uintptr, uintptr, uintptr) error { panic("unreachable") }
|
||||||
func (panicDispatcher) lookupGroupId(string) (string, error) { panic("unreachable") }
|
func (panicDispatcher) lookupGroupId(string) (string, error) { panic("unreachable") }
|
||||||
func (panicDispatcher) lookPath(string) (string, error) { panic("unreachable") }
|
|
||||||
func (panicDispatcher) cmdOutput(*exec.Cmd) ([]byte, error) { panic("unreachable") }
|
func (panicDispatcher) cmdOutput(*exec.Cmd) ([]byte, error) { panic("unreachable") }
|
||||||
func (panicDispatcher) overflowUid(message.Msg) int { panic("unreachable") }
|
func (panicDispatcher) overflowUid(message.Msg) int { panic("unreachable") }
|
||||||
func (panicDispatcher) overflowGid(message.Msg) int { panic("unreachable") }
|
func (panicDispatcher) overflowGid(message.Msg) int { panic("unreachable") }
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"hakurei.app/internal/acl"
|
"hakurei.app/internal/acl"
|
||||||
"hakurei.app/internal/env"
|
"hakurei.app/internal/env"
|
||||||
"hakurei.app/internal/info"
|
"hakurei.app/internal/info"
|
||||||
|
"hakurei.app/internal/pipewire"
|
||||||
"hakurei.app/internal/system"
|
"hakurei.app/internal/system"
|
||||||
"hakurei.app/internal/wayland"
|
"hakurei.app/internal/wayland"
|
||||||
"hakurei.app/message"
|
"hakurei.app/message"
|
||||||
@@ -21,7 +22,7 @@ import (
|
|||||||
//
|
//
|
||||||
// This must not be called from within package outcome.
|
// This must not be called from within package outcome.
|
||||||
func Info() *hst.Info {
|
func Info() *hst.Info {
|
||||||
hi := hst.Info{WaylandVersion: wayland.Version,
|
hi := hst.Info{WaylandVersion: wayland.Version, PipeWireVersion: pipewire.Version,
|
||||||
Version: info.Version(), User: new(Hsu).MustID(nil)}
|
Version: info.Version(), User: new(Hsu).MustID(nil)}
|
||||||
env.CopyPaths().Copy(&hi.Paths, hi.User)
|
env.CopyPaths().Copy(&hi.Paths, hi.User)
|
||||||
return &hi
|
return &hi
|
||||||
@@ -70,7 +71,7 @@ type outcomeState struct {
|
|||||||
// Copied from their respective exported values.
|
// Copied from their respective exported values.
|
||||||
mapuid, mapgid *stringPair[int]
|
mapuid, mapgid *stringPair[int]
|
||||||
|
|
||||||
// Copied from [env.Paths] per-process.
|
// Copied from [EnvPaths] per-process.
|
||||||
sc hst.Paths
|
sc hst.Paths
|
||||||
*env.Paths
|
*env.Paths
|
||||||
|
|
||||||
@@ -172,10 +173,6 @@ 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 spPipeWireOp.toSystem only.
|
|
||||||
directPipeWire 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.
|
||||||
@@ -189,8 +186,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, directPipeWire: config.DirectPipeWire, directPulse: config.DirectPulse,
|
directWayland: config.DirectWayland, extraPerms: config.ExtraPerms,
|
||||||
extraPerms: config.ExtraPerms, sessionBus: config.SessionBus, systemBus: config.SystemBus,
|
sessionBus: config.SessionBus, systemBus: config.SystemBus,
|
||||||
sys: sys, outcomeState: s,
|
sys: sys, outcomeState: s,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,10 +254,6 @@ type outcomeStateParams struct {
|
|||||||
// Populated by spRuntimeOp.
|
// Populated by spRuntimeOp.
|
||||||
runtimeDir *check.Absolute
|
runtimeDir *check.Absolute
|
||||||
|
|
||||||
// Path to pipewire-pulse server.
|
|
||||||
// Populated by spPipeWireOp if DirectPipeWire is false.
|
|
||||||
pipewirePulsePath *check.Absolute
|
|
||||||
|
|
||||||
as hst.ApplyState
|
as hst.ApplyState
|
||||||
*outcomeState
|
*outcomeState
|
||||||
}
|
}
|
||||||
@@ -300,7 +293,6 @@ 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 TestOutcomeRun(t *testing.T) {
|
func TestOutcomeMain(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
msg := message.New(nil)
|
msg := message.New(nil)
|
||||||
msg.SwapVerbose(testing.Verbose())
|
msg.SwapVerbose(testing.Verbose())
|
||||||
@@ -67,12 +67,18 @@ func TestOutcomeRun(t *testing.T) {
|
|||||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
).
|
).
|
||||||
|
|
||||||
// spPipeWireOp
|
// ensureRuntimeDir
|
||||||
PipeWire(
|
Ensure(m("/run/user/1971"), 0700).
|
||||||
m("/tmp/hakurei.0/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/pipewire"),
|
UpdatePermType(system.User, m("/run/user/1971"), acl.Execute).
|
||||||
"org.chromium.Chromium",
|
Ensure(m("/run/user/1971/hakurei"), 0700).
|
||||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
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(
|
||||||
@@ -100,6 +106,8 @@ func TestOutcomeRun(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",
|
||||||
|
"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",
|
||||||
@@ -136,7 +144,7 @@ func TestOutcomeRun(t *testing.T) {
|
|||||||
Tmpfs(fhs.AbsDevShm, 0, 01777).
|
Tmpfs(fhs.AbsDevShm, 0, 01777).
|
||||||
|
|
||||||
// spRuntimeOp
|
// spRuntimeOp
|
||||||
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
Tmpfs(fhs.AbsRunUser, 1<<12, 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
|
||||||
@@ -149,6 +157,10 @@ func TestOutcomeRun(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
|
||||||
|
Bind(m("/run/user/1971/hakurei/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/pulse"), m("/run/user/1971/pulse/native"), 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).
|
||||||
Bind(m("/tmp/hakurei.0/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/system_bus_socket"), m("/var/run/dbus/system_bus_socket"), 0).
|
Bind(m("/tmp/hakurei.0/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/system_bus_socket"), m("/var/run/dbus/system_bus_socket"), 0).
|
||||||
@@ -170,7 +182,7 @@ func TestOutcomeRun(t *testing.T) {
|
|||||||
Remount(fhs.AbsRoot, syscall.MS_RDONLY),
|
Remount(fhs.AbsRoot, syscall.MS_RDONLY),
|
||||||
}},
|
}},
|
||||||
|
|
||||||
{"nixos permissive defaults no enablements", new(stubNixOS), &hst.Config{DirectPipeWire: true, Container: &hst.ContainerConfig{
|
{"nixos permissive defaults no enablements", new(stubNixOS), &hst.Config{Container: &hst.ContainerConfig{
|
||||||
Filesystem: []hst.FilesystemConfigJSON{
|
Filesystem: []hst.FilesystemConfigJSON{
|
||||||
{FilesystemConfig: &hst.FSBind{
|
{FilesystemConfig: &hst.FSBind{
|
||||||
Target: fhs.AbsRoot,
|
Target: fhs.AbsRoot,
|
||||||
@@ -232,7 +244,7 @@ func TestOutcomeRun(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/"), xdgRuntimeDirSize, 0755).
|
Tmpfs(m("/run/user/"), 4096, 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")).
|
||||||
@@ -252,8 +264,6 @@ func TestOutcomeRun(t *testing.T) {
|
|||||||
}},
|
}},
|
||||||
|
|
||||||
{"nixos permissive defaults chromium", new(stubNixOS), &hst.Config{
|
{"nixos permissive defaults chromium", new(stubNixOS), &hst.Config{
|
||||||
DirectPipeWire: true,
|
|
||||||
|
|
||||||
ID: "org.chromium.Chromium",
|
ID: "org.chromium.Chromium",
|
||||||
Identity: 9,
|
Identity: 9,
|
||||||
Groups: []string{"video"},
|
Groups: []string{"video"},
|
||||||
@@ -288,7 +298,7 @@ func TestOutcomeRun(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Filter: true,
|
Filter: true,
|
||||||
},
|
},
|
||||||
Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPipeWire | hst.EPulse),
|
Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPulse),
|
||||||
|
|
||||||
Container: &hst.ContainerConfig{
|
Container: &hst.ContainerConfig{
|
||||||
Filesystem: []hst.FilesystemConfigJSON{
|
Filesystem: []hst.FilesystemConfigJSON{
|
||||||
@@ -337,7 +347,10 @@ func TestOutcomeRun(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").
|
||||||
PipeWire(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/pipewire"), "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
|
||||||
|
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",
|
||||||
@@ -384,7 +397,8 @@ func TestOutcomeRun(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",
|
||||||
"PIPEWIRE_REMOTE=/run/user/65534/pipewire-0",
|
"PULSE_COOKIE=" + hst.PrivateTmp + "/pulse-cookie",
|
||||||
|
"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",
|
||||||
@@ -399,13 +413,14 @@ func TestOutcomeRun(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/"), xdgRuntimeDirSize, 0755).
|
Tmpfs(m("/run/user/"), 4096, 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("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/pipewire"), m("/run/user/65534/pipewire-0"), 0).
|
Bind(m("/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c/pulse"), m("/run/user/65534/pulse/native"), 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).
|
||||||
@@ -424,10 +439,8 @@ func TestOutcomeRun(t *testing.T) {
|
|||||||
}},
|
}},
|
||||||
|
|
||||||
{"nixos chromium direct wayland", new(stubNixOS), &hst.Config{
|
{"nixos chromium direct wayland", new(stubNixOS), &hst.Config{
|
||||||
DirectPipeWire: true,
|
|
||||||
|
|
||||||
ID: "org.chromium.Chromium",
|
ID: "org.chromium.Chromium",
|
||||||
Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPipeWire | hst.EPulse),
|
Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPulse),
|
||||||
Container: &hst.ContainerConfig{
|
Container: &hst.ContainerConfig{
|
||||||
Env: nil,
|
Env: nil,
|
||||||
Filesystem: []hst.FilesystemConfigJSON{
|
Filesystem: []hst.FilesystemConfigJSON{
|
||||||
@@ -489,8 +502,9 @@ func TestOutcomeRun(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"), "org.chromium.Chromium", "8e2c76b066dabe574cf073bdb46eb5c1").
|
|
||||||
MustProxyDBus(&hst.BusConfig{
|
MustProxyDBus(&hst.BusConfig{
|
||||||
Talk: []string{
|
Talk: []string{
|
||||||
"org.freedesktop.FileManager1", "org.freedesktop.Notifications",
|
"org.freedesktop.FileManager1", "org.freedesktop.Notifications",
|
||||||
@@ -530,7 +544,8 @@ func TestOutcomeRun(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",
|
||||||
"PIPEWIRE_REMOTE=/run/user/1971/pipewire-0",
|
"PULSE_COOKIE=" + hst.PrivateTmp + "/pulse-cookie",
|
||||||
|
"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",
|
||||||
@@ -544,13 +559,14 @@ func TestOutcomeRun(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/"), xdgRuntimeDirSize, 0755).
|
Tmpfs(m("/run/user/"), 4096, 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("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/pipewire"), m("/run/user/1971/pipewire-0"), 0).
|
Bind(m("/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1/pulse"), m("/run/user/1971/pulse/native"), 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).
|
||||||
@@ -883,16 +899,6 @@ func (k *stubNixOS) lookupGroupId(name string) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *stubNixOS) lookPath(file string) (string, error) {
|
|
||||||
switch file {
|
|
||||||
case "pipewire-pulse":
|
|
||||||
return "/run/current-system/sw/bin/pipewire-pulse", nil
|
|
||||||
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("unexpected file %q", file))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *stubNixOS) cmdOutput(cmd *exec.Cmd) ([]byte, error) {
|
func (k *stubNixOS) cmdOutput(cmd *exec.Cmd) ([]byte, error) {
|
||||||
switch cmd.Path {
|
switch cmd.Path {
|
||||||
case "/proc/nonexistent/hsu":
|
case "/proc/nonexistent/hsu":
|
||||||
|
|||||||
@@ -14,12 +14,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/container/check"
|
|
||||||
"hakurei.app/container/fhs"
|
|
||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/seccomp"
|
||||||
"hakurei.app/container/std"
|
"hakurei.app/container/std"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/pipewire"
|
|
||||||
"hakurei.app/message"
|
"hakurei.app/message"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -86,55 +83,6 @@ func Shim(msg message.Msg) {
|
|||||||
shimEntrypoint(direct{msg})
|
shimEntrypoint(direct{msg})
|
||||||
}
|
}
|
||||||
|
|
||||||
// A shimPrivate holds state of the private work directory owned by shim.
|
|
||||||
type shimPrivate struct {
|
|
||||||
// Path to directory if created.
|
|
||||||
pathname *check.Absolute
|
|
||||||
|
|
||||||
k syscallDispatcher
|
|
||||||
id *stringPair[hst.ID]
|
|
||||||
}
|
|
||||||
|
|
||||||
// unwrap returns the underlying pathname.
|
|
||||||
func (sp *shimPrivate) unwrap() *check.Absolute {
|
|
||||||
if sp.pathname == nil {
|
|
||||||
if a, err := check.NewAbs(sp.k.tempdir()); err != nil {
|
|
||||||
sp.k.fatal(err)
|
|
||||||
panic("unreachable")
|
|
||||||
} else {
|
|
||||||
pathname := a.Append(".hakurei-shim-" + sp.id.String())
|
|
||||||
sp.k.getMsg().Verbosef("creating private work directory %q", pathname)
|
|
||||||
if err = sp.k.mkdir(pathname.String(), 0700); err != nil {
|
|
||||||
sp.k.fatal(err)
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
sp.pathname = pathname
|
|
||||||
return sp.unwrap()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return sp.pathname
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the absolute pathname to the directory held by shimPrivate.
|
|
||||||
func (sp *shimPrivate) String() string { return sp.unwrap().String() }
|
|
||||||
|
|
||||||
// destroy removes the directory held by shimPrivate.
|
|
||||||
func (sp *shimPrivate) destroy() {
|
|
||||||
defer func() { sp.pathname = nil }()
|
|
||||||
if sp.pathname != nil {
|
|
||||||
sp.k.getMsg().Verbosef("destroying private work directory %q", sp.pathname)
|
|
||||||
if err := sp.k.removeAll(sp.pathname.String()); err != nil {
|
|
||||||
sp.k.getMsg().GetLogger().Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// shimPipeWireTimeout is the duration pipewire-pulse is allowed to run before its socket becomes available.
|
|
||||||
shimPipeWireTimeout = 5 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
func shimEntrypoint(k syscallDispatcher) {
|
func shimEntrypoint(k syscallDispatcher) {
|
||||||
msg := k.getMsg()
|
msg := k.getMsg()
|
||||||
if msg == nil {
|
if msg == nil {
|
||||||
@@ -260,7 +208,6 @@ func shimEntrypoint(k syscallDispatcher) {
|
|||||||
|
|
||||||
ctx, stop := k.notifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
ctx, stop := k.notifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||||
cancelContainer.Store(&stop)
|
cancelContainer.Store(&stop)
|
||||||
sp := shimPrivate{k: k, id: state.id}
|
|
||||||
z := container.New(ctx, msg)
|
z := container.New(ctx, msg)
|
||||||
z.Params = *stateParams.params
|
z.Params = *stateParams.params
|
||||||
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr
|
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||||
@@ -268,79 +215,6 @@ func shimEntrypoint(k syscallDispatcher) {
|
|||||||
// bounds and default enforced in finalise.go
|
// bounds and default enforced in finalise.go
|
||||||
z.WaitDelay = state.Shim.WaitDelay
|
z.WaitDelay = state.Shim.WaitDelay
|
||||||
|
|
||||||
if stateParams.pipewirePulsePath != nil {
|
|
||||||
zpw := container.NewCommand(ctx, msg, stateParams.pipewirePulsePath, pipewirePulseName)
|
|
||||||
zpw.Hostname = "hakurei-" + pipewirePulseName
|
|
||||||
zpw.SeccompFlags |= seccomp.AllowMultiarch
|
|
||||||
zpw.SeccompPresets |= std.PresetStrict
|
|
||||||
zpw.Env = []string{
|
|
||||||
// pipewire SecurityContext socket path
|
|
||||||
pipewire.Remote + "=" + stateParams.instancePath().Append("pipewire").String(),
|
|
||||||
// pipewire-pulse socket directory path
|
|
||||||
envXDGRuntimeDir + "=" + sp.String(),
|
|
||||||
}
|
|
||||||
if msg.IsVerbose() {
|
|
||||||
zpw.Stdin, zpw.Stdout, zpw.Stderr = os.Stdin, os.Stdout, os.Stderr
|
|
||||||
}
|
|
||||||
zpw.
|
|
||||||
Bind(fhs.AbsRoot, fhs.AbsRoot, 0).
|
|
||||||
Bind(sp.unwrap(), sp.unwrap(), std.BindWritable).
|
|
||||||
Proc(fhs.AbsProc).Dev(fhs.AbsDev, true)
|
|
||||||
socketPath := sp.unwrap().Append("pulse", "native")
|
|
||||||
innerSocketPath := stateParams.runtimeDir.Append("pulse", "native")
|
|
||||||
|
|
||||||
if err := k.containerStart(zpw); err != nil {
|
|
||||||
sp.destroy()
|
|
||||||
printMessageError(func(v ...any) { k.fatal(fmt.Sprintln(v...)) },
|
|
||||||
"cannot start "+pipewirePulseName+" container:", err)
|
|
||||||
}
|
|
||||||
if err := k.containerServe(zpw); err != nil {
|
|
||||||
sp.destroy()
|
|
||||||
printMessageError(func(v ...any) { k.fatal(fmt.Sprintln(v...)) },
|
|
||||||
"cannot configure "+pipewirePulseName+" container:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
done := make(chan error, 1)
|
|
||||||
k.new(func(k syscallDispatcher, msg message.Msg) { done <- k.containerWait(zpw) })
|
|
||||||
|
|
||||||
socketTimer := time.NewTimer(shimPipeWireTimeout)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-socketTimer.C:
|
|
||||||
sp.destroy()
|
|
||||||
k.fatal(pipewirePulseName + " exceeded deadline before socket appeared")
|
|
||||||
break
|
|
||||||
|
|
||||||
case err := <-done:
|
|
||||||
var exitError *exec.ExitError
|
|
||||||
if !errors.As(err, &exitError) {
|
|
||||||
msg.Verbosef("cannot wait: %v", err)
|
|
||||||
k.exit(127)
|
|
||||||
}
|
|
||||||
sp.destroy()
|
|
||||||
k.fatal(pipewirePulseName + " " + exitError.ProcessState.String())
|
|
||||||
break
|
|
||||||
|
|
||||||
default:
|
|
||||||
if _, err := k.stat(socketPath.String()); err != nil {
|
|
||||||
if !errors.Is(err, os.ErrNotExist) {
|
|
||||||
sp.destroy()
|
|
||||||
k.fatal(err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(500 * time.Microsecond)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
z.Bind(socketPath, innerSocketPath, 0)
|
|
||||||
z.Env = append(z.Env, "PULSE_SERVER=unix:"+innerSocketPath.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := k.containerStart(z); err != nil {
|
if err := k.containerStart(z); err != nil {
|
||||||
var f func(v ...any)
|
var f func(v ...any)
|
||||||
if logger := msg.GetLogger(); logger != nil {
|
if logger := msg.GetLogger(); logger != nil {
|
||||||
@@ -351,11 +225,9 @@ func shimEntrypoint(k syscallDispatcher) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
printMessageError(f, "cannot start container:", err)
|
printMessageError(f, "cannot start container:", err)
|
||||||
sp.destroy()
|
|
||||||
k.exit(hst.ExitFailure)
|
k.exit(hst.ExitFailure)
|
||||||
}
|
}
|
||||||
if err := k.containerServe(z); err != nil {
|
if err := k.containerServe(z); err != nil {
|
||||||
sp.destroy()
|
|
||||||
printMessageError(func(v ...any) { k.fatal(fmt.Sprintln(v...)) },
|
printMessageError(func(v ...any) { k.fatal(fmt.Sprintln(v...)) },
|
||||||
"cannot configure container:", err)
|
"cannot configure container:", err)
|
||||||
}
|
}
|
||||||
@@ -364,13 +236,10 @@ func shimEntrypoint(k syscallDispatcher) {
|
|||||||
seccomp.Preset(std.PresetStrict, seccomp.AllowMultiarch),
|
seccomp.Preset(std.PresetStrict, seccomp.AllowMultiarch),
|
||||||
seccomp.AllowMultiarch,
|
seccomp.AllowMultiarch,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
sp.destroy()
|
|
||||||
k.fatalf("cannot load syscall filter: %v", err)
|
k.fatalf("cannot load syscall filter: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := k.containerWait(z); err != nil {
|
if err := k.containerWait(z); err != nil {
|
||||||
sp.destroy()
|
|
||||||
|
|
||||||
var exitError *exec.ExitError
|
var exitError *exec.ExitError
|
||||||
if !errors.As(err, &exitError) {
|
if !errors.As(err, &exitError) {
|
||||||
if errors.Is(err, context.Canceled) {
|
if errors.Is(err, context.Canceled) {
|
||||||
@@ -381,5 +250,4 @@ func shimEntrypoint(k syscallDispatcher) {
|
|||||||
}
|
}
|
||||||
k.exit(exitError.ExitCode())
|
k.exit(exitError.ExitCode())
|
||||||
}
|
}
|
||||||
sp.destroy()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ func TestShimEntrypoint(t *testing.T) {
|
|||||||
Tmpfs(fhs.AbsDevShm, 0, 01777).
|
Tmpfs(fhs.AbsDevShm, 0, 01777).
|
||||||
|
|
||||||
// spRuntimeOp
|
// spRuntimeOp
|
||||||
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
Tmpfs(fhs.AbsRunUser, 1<<12, 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,10 +382,6 @@ 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)}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
package outcome
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/gob"
|
|
||||||
|
|
||||||
"hakurei.app/container/check"
|
|
||||||
"hakurei.app/hst"
|
|
||||||
"hakurei.app/internal/pipewire"
|
|
||||||
)
|
|
||||||
|
|
||||||
const pipewirePulseName = "pipewire-pulse"
|
|
||||||
|
|
||||||
func init() { gob.Register(new(spPipeWireOp)) }
|
|
||||||
|
|
||||||
// spPipeWireOp exports the PipeWire server to the container via SecurityContext.
|
|
||||||
// Runs after spRuntimeOp.
|
|
||||||
type spPipeWireOp struct {
|
|
||||||
// Path to pipewire-pulse server. Populated during toSystem if DirectPipeWire is false.
|
|
||||||
CompatServerPath *check.Absolute
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *spPipeWireOp) toSystem(state *outcomeStateSys) error {
|
|
||||||
if state.et&hst.EPipeWire == 0 {
|
|
||||||
return errNotEnabled
|
|
||||||
}
|
|
||||||
if !state.directPipeWire {
|
|
||||||
if n, err := state.k.lookPath(pipewirePulseName); err != nil {
|
|
||||||
return &hst.AppError{Step: "look up " + pipewirePulseName, Err: err}
|
|
||||||
} else if s.CompatServerPath, err = check.NewAbs(n); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
appId := state.appId
|
|
||||||
if appId == "" {
|
|
||||||
// use instance ID in case app id is not set
|
|
||||||
appId = "app.hakurei." + state.id.String()
|
|
||||||
}
|
|
||||||
state.sys.PipeWire(state.instance().Append("pipewire"), appId, state.id.String())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *spPipeWireOp) toContainer(state *outcomeStateParams) error {
|
|
||||||
if s.CompatServerPath == nil {
|
|
||||||
innerPath := state.runtimeDir.Append(pipewire.PW_DEFAULT_REMOTE)
|
|
||||||
state.env[pipewire.Remote] = innerPath.String()
|
|
||||||
state.params.Bind(state.instancePath().Append("pipewire"), innerPath, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// pipewire-pulse behaviour implemented in shim.go
|
|
||||||
state.pipewirePulsePath = s.CompatServerPath
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
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 new(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 new(spPipeWireOp)
|
|
||||||
}, func() *hst.Config {
|
|
||||||
c := hst.Template()
|
|
||||||
c.DirectPipeWire = true
|
|
||||||
return c
|
|
||||||
}, nil, []stub.Call{}, newI().
|
|
||||||
// state.instance
|
|
||||||
Ephemeral(system.Process, m(wantInstancePrefix), 0711).
|
|
||||||
// toSystem
|
|
||||||
PipeWire(
|
|
||||||
m(wantInstancePrefix+"/pipewire"),
|
|
||||||
"org.chromium.Chromium",
|
|
||||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
||||||
), 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.directPulse || state.et&hst.EPulse == 0 {
|
if state.et&hst.EPulse == 0 {
|
||||||
return errNotEnabled
|
return errNotEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,40 +18,24 @@ import (
|
|||||||
func TestSpPulseOp(t *testing.T) {
|
func TestSpPulseOp(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
newConfig := func() *hst.Config {
|
config := hst.Template()
|
||||||
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 := newConfig()
|
c := hst.Template()
|
||||||
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)}
|
||||||
}, newConfig, nil, []stub.Call{
|
}, hst.Template, 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"`,
|
||||||
@@ -60,7 +44,7 @@ func TestSpPulseOp(t *testing.T) {
|
|||||||
|
|
||||||
{"socketDir nonexistent", func(bool, bool) outcomeOp {
|
{"socketDir nonexistent", func(bool, bool) outcomeOp {
|
||||||
return new(spPulseOp)
|
return new(spPulseOp)
|
||||||
}, newConfig, nil, []stub.Call{
|
}, hst.Template, 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",
|
||||||
@@ -70,7 +54,7 @@ func TestSpPulseOp(t *testing.T) {
|
|||||||
|
|
||||||
{"socket stat", func(bool, bool) outcomeOp {
|
{"socket stat", func(bool, bool) outcomeOp {
|
||||||
return new(spPulseOp)
|
return new(spPulseOp)
|
||||||
}, newConfig, nil, []stub.Call{
|
}, hst.Template, 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{
|
||||||
@@ -80,7 +64,7 @@ func TestSpPulseOp(t *testing.T) {
|
|||||||
|
|
||||||
{"socket nonexistent", func(bool, bool) outcomeOp {
|
{"socket nonexistent", func(bool, bool) outcomeOp {
|
||||||
return new(spPulseOp)
|
return new(spPulseOp)
|
||||||
}, newConfig, nil, []stub.Call{
|
}, hst.Template, 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{
|
||||||
@@ -91,7 +75,7 @@ func TestSpPulseOp(t *testing.T) {
|
|||||||
|
|
||||||
{"socket mode", func(bool, bool) outcomeOp {
|
{"socket mode", func(bool, bool) outcomeOp {
|
||||||
return new(spPulseOp)
|
return new(spPulseOp)
|
||||||
}, newConfig, nil, []stub.Call{
|
}, hst.Template, 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{
|
||||||
@@ -102,18 +86,18 @@ func TestSpPulseOp(t *testing.T) {
|
|||||||
|
|
||||||
{"cookie notAbs", func(bool, bool) outcomeOp {
|
{"cookie notAbs", func(bool, bool) outcomeOp {
|
||||||
return new(spPulseOp)
|
return new(spPulseOp)
|
||||||
}, newConfig, nil, []stub.Call{
|
}, hst.Template, 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),
|
||||||
}, nil, nil, &hst.AppError{
|
}, nil, nil, &hst.AppError{
|
||||||
Step: "locate PulseAudio cookie",
|
Step: "locate PulseAudio cookie",
|
||||||
Err: check.AbsoluteError("proc/nonexistent/cookie"),
|
Err: &check.AbsoluteError{Pathname: "proc/nonexistent/cookie"},
|
||||||
}, nil, nil, nil, nil, nil},
|
}, nil, nil, nil, nil, nil},
|
||||||
|
|
||||||
{"cookie loadFile", func(bool, bool) outcomeOp {
|
{"cookie loadFile", func(bool, bool) outcomeOp {
|
||||||
return new(spPulseOp)
|
return new(spPulseOp)
|
||||||
}, newConfig, nil, []stub.Call{
|
}, hst.Template, 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),
|
||||||
@@ -134,7 +118,7 @@ func TestSpPulseOp(t *testing.T) {
|
|||||||
op.CookieSize += +0xfd
|
op.CookieSize += +0xfd
|
||||||
}
|
}
|
||||||
return op
|
return op
|
||||||
}, newConfig, nil, []stub.Call{
|
}, hst.Template, 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),
|
||||||
@@ -166,7 +150,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}
|
||||||
}, newConfig, nil, []stub.Call{
|
}, hst.Template, 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),
|
||||||
@@ -199,7 +183,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}
|
||||||
}, newConfig, nil, []stub.Call{
|
}, hst.Template, 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),
|
||||||
@@ -229,7 +213,7 @@ func TestSpPulseOp(t *testing.T) {
|
|||||||
|
|
||||||
{"success", func(bool, bool) outcomeOp {
|
{"success", func(bool, bool) outcomeOp {
|
||||||
return new(spPulseOp)
|
return new(spPulseOp)
|
||||||
}, newConfig, nil, []stub.Call{
|
}, hst.Template, 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),
|
||||||
@@ -272,7 +256,7 @@ func TestDiscoverPulseCookie(t *testing.T) {
|
|||||||
call("verbose", stub.ExpectArgs{[]any{(*check.Absolute)(nil)}}, nil, nil),
|
call("verbose", stub.ExpectArgs{[]any{(*check.Absolute)(nil)}}, nil, nil),
|
||||||
}}, &hst.AppError{
|
}}, &hst.AppError{
|
||||||
Step: "locate PulseAudio cookie",
|
Step: "locate PulseAudio cookie",
|
||||||
Err: check.AbsoluteError("proc/nonexistent/pulse-cookie"),
|
Err: &check.AbsoluteError{Pathname: "proc/nonexistent/pulse-cookie"},
|
||||||
}},
|
}},
|
||||||
|
|
||||||
{"success override", fCheckPathname, stub.Expect{Calls: []stub.Call{
|
{"success override", fCheckPathname, stub.Expect{Calls: []stub.Call{
|
||||||
@@ -286,7 +270,7 @@ func TestDiscoverPulseCookie(t *testing.T) {
|
|||||||
call("verbose", stub.ExpectArgs{[]any{(*check.Absolute)(nil)}}, nil, nil),
|
call("verbose", stub.ExpectArgs{[]any{(*check.Absolute)(nil)}}, nil, nil),
|
||||||
}}, &hst.AppError{
|
}}, &hst.AppError{
|
||||||
Step: "locate PulseAudio cookie",
|
Step: "locate PulseAudio cookie",
|
||||||
Err: check.AbsoluteError("proc/nonexistent/home"),
|
Err: &check.AbsoluteError{Pathname: "proc/nonexistent/home"},
|
||||||
}},
|
}},
|
||||||
|
|
||||||
{"home stat", fCheckPathname, stub.Expect{Calls: []stub.Call{
|
{"home stat", fCheckPathname, stub.Expect{Calls: []stub.Call{
|
||||||
@@ -321,7 +305,7 @@ func TestDiscoverPulseCookie(t *testing.T) {
|
|||||||
call("verbose", stub.ExpectArgs{[]any{(*check.Absolute)(nil)}}, nil, nil),
|
call("verbose", stub.ExpectArgs{[]any{(*check.Absolute)(nil)}}, nil, nil),
|
||||||
}}, &hst.AppError{
|
}}, &hst.AppError{
|
||||||
Step: "locate PulseAudio cookie",
|
Step: "locate PulseAudio cookie",
|
||||||
Err: check.AbsoluteError("proc/nonexistent/xdg"),
|
Err: &check.AbsoluteError{Pathname: "proc/nonexistent/xdg"},
|
||||||
}},
|
}},
|
||||||
|
|
||||||
{"xdg stat", fCheckPathname, stub.Expect{Calls: []stub.Call{
|
{"xdg stat", fCheckPathname, stub.Expect{Calls: []stub.Call{
|
||||||
|
|||||||
@@ -91,9 +91,6 @@ 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()
|
||||||
@@ -111,7 +108,7 @@ func (s *spRuntimeOp) toContainer(state *outcomeStateParams) error {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state.params.Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755)
|
state.params.Tmpfs(fhs.AbsRunUser, 1<<12, 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, xdgRuntimeDirSize, 0755).
|
Tmpfs(fhs.AbsRunUser, 1<<12, 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, xdgRuntimeDirSize, 0755).
|
Tmpfs(fhs.AbsRunUser, 1<<12, 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, xdgRuntimeDirSize, 0755).
|
Tmpfs(fhs.AbsRunUser, 1<<12, 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, xdgRuntimeDirSize, 0755).
|
Tmpfs(fhs.AbsRunUser, 1<<12, 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",
|
||||||
|
|||||||
@@ -1,129 +0,0 @@
|
|||||||
package pipewire
|
|
||||||
|
|
||||||
/* pipewire/client.h */
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_TYPE_INTERFACE_Client = PW_TYPE_INFO_INTERFACE_BASE + "Client"
|
|
||||||
PW_CLIENT_PERM_MASK = PW_PERM_RWXM
|
|
||||||
PW_VERSION_CLIENT = 3
|
|
||||||
|
|
||||||
PW_ID_CLIENT = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_CLIENT_CHANGE_MASK_PROPS = 1 << iota
|
|
||||||
|
|
||||||
PW_CLIENT_CHANGE_MASK_ALL = 1<<iota - 1
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_CLIENT_EVENT_INFO = iota
|
|
||||||
PW_CLIENT_EVENT_PERMISSIONS
|
|
||||||
PW_CLIENT_EVENT_NUM
|
|
||||||
|
|
||||||
PW_VERSION_CLIENT_EVENTS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_CLIENT_METHOD_ADD_LISTENER = iota
|
|
||||||
PW_CLIENT_METHOD_ERROR
|
|
||||||
PW_CLIENT_METHOD_UPDATE_PROPERTIES
|
|
||||||
PW_CLIENT_METHOD_GET_PERMISSIONS
|
|
||||||
PW_CLIENT_METHOD_UPDATE_PERMISSIONS
|
|
||||||
PW_CLIENT_METHOD_NUM
|
|
||||||
|
|
||||||
PW_VERSION_CLIENT_METHODS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
// The ClientInfo event provides client information updates.
|
|
||||||
// This is emitted when binding to a client or when the client info is updated later.
|
|
||||||
type ClientInfo struct {
|
|
||||||
// The global id of the client.
|
|
||||||
ID Int `json:"id"`
|
|
||||||
// The changes emitted by this event.
|
|
||||||
ChangeMask Long `json:"change_mask"`
|
|
||||||
// Properties of this object, valid when change_mask has PW_CLIENT_CHANGE_MASK_PROPS.
|
|
||||||
Properties *SPADict `json:"props"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Opcode satisfies [Message] with a constant value.
|
|
||||||
func (c *ClientInfo) Opcode() byte { return PW_CLIENT_EVENT_INFO }
|
|
||||||
|
|
||||||
// FileCount satisfies [Message] with a constant value.
|
|
||||||
func (c *ClientInfo) FileCount() Int { return 0 }
|
|
||||||
|
|
||||||
// Size satisfies [KnownSize] with a value computed at runtime.
|
|
||||||
func (c *ClientInfo) Size() Word {
|
|
||||||
return SizePrefix +
|
|
||||||
Size(SizeInt) +
|
|
||||||
Size(SizeLong) +
|
|
||||||
c.Properties.Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalBinary satisfies [encoding.BinaryMarshaler] via [Marshal].
|
|
||||||
func (c *ClientInfo) MarshalBinary() ([]byte, error) { return Marshal(c) }
|
|
||||||
|
|
||||||
// UnmarshalBinary satisfies [encoding.BinaryUnmarshaler] via [Unmarshal].
|
|
||||||
func (c *ClientInfo) UnmarshalBinary(data []byte) error { return Unmarshal(data, c) }
|
|
||||||
|
|
||||||
// ClientUpdateProperties is used to update the properties of a client.
|
|
||||||
type ClientUpdateProperties struct {
|
|
||||||
// Properties to update on the client.
|
|
||||||
Properties *SPADict `json:"props"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Opcode satisfies [Message] with a constant value.
|
|
||||||
func (c *ClientUpdateProperties) Opcode() byte { return PW_CLIENT_METHOD_UPDATE_PROPERTIES }
|
|
||||||
|
|
||||||
// FileCount satisfies [Message] with a constant value.
|
|
||||||
func (c *ClientUpdateProperties) FileCount() Int { return 0 }
|
|
||||||
|
|
||||||
// Size satisfies [KnownSize] with a value computed at runtime.
|
|
||||||
func (c *ClientUpdateProperties) Size() Word { return SizePrefix + c.Properties.Size() }
|
|
||||||
|
|
||||||
// MarshalBinary satisfies [encoding.BinaryMarshaler] via [Marshal].
|
|
||||||
func (c *ClientUpdateProperties) MarshalBinary() ([]byte, error) { return Marshal(c) }
|
|
||||||
|
|
||||||
// UnmarshalBinary satisfies [encoding.BinaryUnmarshaler] via [Unmarshal].
|
|
||||||
func (c *ClientUpdateProperties) UnmarshalBinary(data []byte) error { return Unmarshal(data, c) }
|
|
||||||
|
|
||||||
// clientUpdateProperties queues a [ClientUpdateProperties] message for the PipeWire server.
|
|
||||||
// This method should not be called directly, the New function queues this message.
|
|
||||||
func (ctx *Context) clientUpdateProperties(props SPADict) error {
|
|
||||||
return ctx.writeMessage(
|
|
||||||
PW_ID_CLIENT,
|
|
||||||
&ClientUpdateProperties{&props},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client holds state of [PW_TYPE_INTERFACE_Client].
|
|
||||||
type Client struct {
|
|
||||||
// Additional information from the server, populated or updated during [Context.Roundtrip].
|
|
||||||
Info *ClientInfo `json:"info"`
|
|
||||||
|
|
||||||
// Populated by [CoreBoundProps] events targeting [Client].
|
|
||||||
Properties SPADict `json:"props"`
|
|
||||||
|
|
||||||
noRemove
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client *Client) consume(opcode byte, files []int, unmarshal func(v any)) error {
|
|
||||||
closeReceivedFiles(files...)
|
|
||||||
switch opcode {
|
|
||||||
case PW_CLIENT_EVENT_INFO:
|
|
||||||
unmarshal(&client.Info)
|
|
||||||
return nil
|
|
||||||
|
|
||||||
default:
|
|
||||||
panic(&UnsupportedOpcodeError{opcode, client.String()})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client *Client) setBoundProps(event *CoreBoundProps) error {
|
|
||||||
if event.Properties != nil {
|
|
||||||
client.Properties = *event.Properties
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client *Client) String() string { return PW_TYPE_INTERFACE_Registry }
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
package pipewire_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"hakurei.app/internal/pipewire"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestClientInfo(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
encodingTestCases[pipewire.ClientInfo, *pipewire.ClientInfo]{
|
|
||||||
{"sample", samplePWContainer[1][2][1], pipewire.ClientInfo{
|
|
||||||
ID: 34,
|
|
||||||
ChangeMask: pipewire.PW_CLIENT_CHANGE_MASK_PROPS,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_PROTOCOL, Value: "protocol-native"},
|
|
||||||
{Key: pipewire.PW_KEY_CORE_NAME, Value: "pipewire-0"},
|
|
||||||
{Key: pipewire.PW_KEY_SEC_SOCKET, Value: "pipewire-0-manager"},
|
|
||||||
{Key: pipewire.PW_KEY_SEC_PID, Value: "1443"},
|
|
||||||
{Key: pipewire.PW_KEY_SEC_UID, Value: "1000"},
|
|
||||||
{Key: pipewire.PW_KEY_SEC_GID, Value: "100"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_ID, Value: "2"},
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_ID, Value: "34"},
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "34"},
|
|
||||||
}}, nil},
|
|
||||||
|
|
||||||
{"sample*", samplePWContainer[1][3][1], pipewire.ClientInfo{
|
|
||||||
ID: 34,
|
|
||||||
ChangeMask: pipewire.PW_CLIENT_CHANGE_MASK_PROPS,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_PROTOCOL, Value: "protocol-native"},
|
|
||||||
{Key: pipewire.PW_KEY_CORE_NAME, Value: "pipewire-alice-1443"},
|
|
||||||
{Key: pipewire.PW_KEY_SEC_SOCKET, Value: "pipewire-0-manager"},
|
|
||||||
{Key: pipewire.PW_KEY_SEC_PID, Value: "1443"},
|
|
||||||
{Key: pipewire.PW_KEY_SEC_UID, Value: "1000"},
|
|
||||||
{Key: pipewire.PW_KEY_SEC_GID, Value: "100"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_ID, Value: "2"},
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_ID, Value: "34"},
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "34"},
|
|
||||||
{Key: pipewire.PW_KEY_REMOTE_INTENTION, Value: "manager"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_NAME, Value: "pw-container"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_PROCESS_BINARY, Value: "pw-container"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_LANGUAGE, Value: "en_US.UTF-8"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_PROCESS_ID, Value: "1443"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_PROCESS_USER, Value: "alice"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_PROCESS_HOST, Value: "nixos"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_PROCESS_SESSION_ID, Value: "1"},
|
|
||||||
{Key: pipewire.PW_KEY_WINDOW_X11_DISPLAY, Value: ":0"},
|
|
||||||
{Key: "cpu.vm.name", Value: "qemu"},
|
|
||||||
{Key: "log.level", Value: "0"},
|
|
||||||
{Key: pipewire.PW_KEY_CPU_MAX_ALIGN, Value: "32"},
|
|
||||||
{Key: "default.clock.rate", Value: "48000"},
|
|
||||||
{Key: "default.clock.quantum", Value: "1024"},
|
|
||||||
{Key: "default.clock.min-quantum", Value: "32"},
|
|
||||||
{Key: "default.clock.max-quantum", Value: "2048"},
|
|
||||||
{Key: "default.clock.quantum-limit", Value: "8192"},
|
|
||||||
{Key: "default.clock.quantum-floor", Value: "4"},
|
|
||||||
{Key: "default.video.width", Value: "640"},
|
|
||||||
{Key: "default.video.height", Value: "480"},
|
|
||||||
{Key: "default.video.rate.num", Value: "25"},
|
|
||||||
{Key: "default.video.rate.denom", Value: "1"},
|
|
||||||
{Key: "clock.power-of-two-quantum", Value: "true"},
|
|
||||||
{Key: "link.max-buffers", Value: "64"},
|
|
||||||
{Key: "mem.warn-mlock", Value: "false"},
|
|
||||||
{Key: "mem.allow-mlock", Value: "true"},
|
|
||||||
{Key: "settings.check-quantum", Value: "false"},
|
|
||||||
{Key: "settings.check-rate", Value: "false"},
|
|
||||||
{Key: pipewire.PW_KEY_CORE_VERSION, Value: "1.4.7"},
|
|
||||||
}}, nil},
|
|
||||||
|
|
||||||
{"sample**", samplePWContainer[1][4][1], pipewire.ClientInfo{
|
|
||||||
ID: 34,
|
|
||||||
ChangeMask: pipewire.PW_CLIENT_CHANGE_MASK_PROPS,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_PROTOCOL, Value: "protocol-native"},
|
|
||||||
{Key: pipewire.PW_KEY_CORE_NAME, Value: "pipewire-alice-1443"},
|
|
||||||
{Key: pipewire.PW_KEY_SEC_SOCKET, Value: "pipewire-0-manager"},
|
|
||||||
{Key: pipewire.PW_KEY_SEC_PID, Value: "1443"},
|
|
||||||
{Key: pipewire.PW_KEY_SEC_UID, Value: "1000"},
|
|
||||||
{Key: pipewire.PW_KEY_SEC_GID, Value: "100"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_ID, Value: "2"},
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_ID, Value: "34"},
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "34"},
|
|
||||||
{Key: pipewire.PW_KEY_REMOTE_INTENTION, Value: "manager"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_NAME, Value: "pw-container"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_PROCESS_BINARY, Value: "pw-container"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_LANGUAGE, Value: "en_US.UTF-8"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_PROCESS_ID, Value: "1443"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_PROCESS_USER, Value: "alice"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_PROCESS_HOST, Value: "nixos"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_PROCESS_SESSION_ID, Value: "1"},
|
|
||||||
{Key: pipewire.PW_KEY_WINDOW_X11_DISPLAY, Value: ":0"},
|
|
||||||
{Key: "cpu.vm.name", Value: "qemu"},
|
|
||||||
{Key: "log.level", Value: "0"},
|
|
||||||
{Key: pipewire.PW_KEY_CPU_MAX_ALIGN, Value: "32"},
|
|
||||||
{Key: "default.clock.rate", Value: "48000"},
|
|
||||||
{Key: "default.clock.quantum", Value: "1024"},
|
|
||||||
{Key: "default.clock.min-quantum", Value: "32"},
|
|
||||||
{Key: "default.clock.max-quantum", Value: "2048"},
|
|
||||||
{Key: "default.clock.quantum-limit", Value: "8192"},
|
|
||||||
{Key: "default.clock.quantum-floor", Value: "4"},
|
|
||||||
{Key: "default.video.width", Value: "640"},
|
|
||||||
{Key: "default.video.height", Value: "480"},
|
|
||||||
{Key: "default.video.rate.num", Value: "25"},
|
|
||||||
{Key: "default.video.rate.denom", Value: "1"},
|
|
||||||
{Key: "clock.power-of-two-quantum", Value: "true"},
|
|
||||||
{Key: "link.max-buffers", Value: "64"},
|
|
||||||
{Key: "mem.warn-mlock", Value: "false"},
|
|
||||||
{Key: "mem.allow-mlock", Value: "true"},
|
|
||||||
{Key: "settings.check-quantum", Value: "false"},
|
|
||||||
{Key: "settings.check-rate", Value: "false"},
|
|
||||||
{Key: pipewire.PW_KEY_CORE_VERSION, Value: "1.4.7"},
|
|
||||||
{Key: pipewire.PW_KEY_ACCESS, Value: "unrestricted"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
}.run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestClientUpdateProperties(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
encodingTestCases[pipewire.ClientUpdateProperties, *pipewire.ClientUpdateProperties]{
|
|
||||||
{"sample", samplePWContainer[0][1][1], pipewire.ClientUpdateProperties{Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_REMOTE_INTENTION, Value: "manager"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_NAME, Value: "pw-container"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_PROCESS_BINARY, Value: "pw-container"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_LANGUAGE, Value: "en_US.UTF-8"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_PROCESS_ID, Value: "1443"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_PROCESS_USER, Value: "alice"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_PROCESS_HOST, Value: "nixos"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_PROCESS_SESSION_ID, Value: "1"},
|
|
||||||
{Key: pipewire.PW_KEY_WINDOW_X11_DISPLAY, Value: ":0"},
|
|
||||||
{Key: "cpu.vm.name", Value: "qemu"},
|
|
||||||
{Key: "log.level", Value: "0"},
|
|
||||||
{Key: pipewire.PW_KEY_CPU_MAX_ALIGN, Value: "32"},
|
|
||||||
{Key: "default.clock.rate", Value: "48000"},
|
|
||||||
{Key: "default.clock.quantum", Value: "1024"},
|
|
||||||
{Key: "default.clock.min-quantum", Value: "32"},
|
|
||||||
{Key: "default.clock.max-quantum", Value: "2048"},
|
|
||||||
{Key: "default.clock.quantum-limit", Value: "8192"},
|
|
||||||
{Key: "default.clock.quantum-floor", Value: "4"},
|
|
||||||
{Key: "default.video.width", Value: "640"},
|
|
||||||
{Key: "default.video.height", Value: "480"},
|
|
||||||
{Key: "default.video.rate.num", Value: "25"},
|
|
||||||
{Key: "default.video.rate.denom", Value: "1"},
|
|
||||||
{Key: "clock.power-of-two-quantum", Value: "true"},
|
|
||||||
{Key: "link.max-buffers", Value: "64"},
|
|
||||||
{Key: "mem.warn-mlock", Value: "false"},
|
|
||||||
{Key: "mem.allow-mlock", Value: "true"},
|
|
||||||
{Key: "settings.check-quantum", Value: "false"},
|
|
||||||
{Key: "settings.check-rate", Value: "false"},
|
|
||||||
{Key: pipewire.PW_KEY_CORE_VERSION, Value: "1.4.7"},
|
|
||||||
{Key: pipewire.PW_KEY_CORE_NAME, Value: "pipewire-alice-1443"},
|
|
||||||
}}, nil},
|
|
||||||
}.run(t)
|
|
||||||
}
|
|
||||||
85
internal/pipewire/conn.go
Normal file
85
internal/pipewire/conn.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package pipewire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SecurityContext holds resources associated with a PipeWire security context.
|
||||||
|
type SecurityContext struct {
|
||||||
|
// Pipe with its write end passed to the PipeWire security context.
|
||||||
|
closeFds [2]int
|
||||||
|
// Absolute pathname the socket was bound to.
|
||||||
|
bindPath *check.Absolute
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close releases any resources held by [SecurityContext], and prevents further
|
||||||
|
// connections to its associated socket.
|
||||||
|
//
|
||||||
|
// A non-nil error has the concrete type [Error].
|
||||||
|
func (sc *SecurityContext) Close() error {
|
||||||
|
if sc == nil || sc.bindPath == nil {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
e := Error{RCleanup, sc.bindPath.String(), errors.Join(
|
||||||
|
syscall.Close(sc.closeFds[1]),
|
||||||
|
syscall.Close(sc.closeFds[0]),
|
||||||
|
// there is still technically a TOCTOU here but this is internal
|
||||||
|
// and has access to the privileged pipewire socket, so it only
|
||||||
|
// receives trusted input (e.g. from cmd/hakurei) anyway
|
||||||
|
os.Remove(sc.bindPath.String()),
|
||||||
|
)}
|
||||||
|
if e.Errno != nil {
|
||||||
|
return &e
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new security context on the PipeWire remote at remotePath
|
||||||
|
// or auto-detected, and associates it with a new socket bound to bindPath.
|
||||||
|
//
|
||||||
|
// New does not attach a finalizer to the resulting [SecurityContext] struct.
|
||||||
|
// The caller is responsible for calling [SecurityContext.Close].
|
||||||
|
//
|
||||||
|
// A non-nil error unwraps to concrete type [Error].
|
||||||
|
func New(remotePath, bindPath *check.Absolute) (*SecurityContext, error) {
|
||||||
|
// ensure bindPath is available
|
||||||
|
if f, err := os.Create(bindPath.String()); err != nil {
|
||||||
|
return nil, &Error{RCreate, bindPath.String(), err}
|
||||||
|
} else if err = f.Close(); err != nil {
|
||||||
|
return nil, &Error{RCreate, bindPath.String(), err}
|
||||||
|
} else if err = os.Remove(bindPath.String()); err != nil {
|
||||||
|
return nil, &Error{RCreate, bindPath.String(), err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write end passed to PipeWire security context close_fd
|
||||||
|
var closeFds [2]int
|
||||||
|
if err := syscall.Pipe2(closeFds[0:], syscall.O_CLOEXEC); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// zero value causes auto-detect
|
||||||
|
var remotePathVal string
|
||||||
|
if remotePath != nil {
|
||||||
|
remotePathVal = remotePath.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// returned error is already wrapped
|
||||||
|
if err := securityContextBind(
|
||||||
|
bindPath.String(),
|
||||||
|
remotePathVal,
|
||||||
|
closeFds[1],
|
||||||
|
); err != nil {
|
||||||
|
return nil, errors.Join(err, // already wrapped
|
||||||
|
syscall.Close(closeFds[1]),
|
||||||
|
syscall.Close(closeFds[0]),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return &SecurityContext{closeFds, bindPath}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
66
internal/pipewire/conn_test.go
Normal file
66
internal/pipewire/conn_test.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package pipewire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"reflect"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSecurityContextClose(t *testing.T) {
|
||||||
|
// do not parallel: fd test not thread safe
|
||||||
|
|
||||||
|
if err := (*SecurityContext)(nil).Close(); !reflect.DeepEqual(err, os.ErrInvalid) {
|
||||||
|
t.Fatalf("Close: error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ctx SecurityContext
|
||||||
|
if err := syscall.Pipe2(ctx.closeFds[0:], syscall.O_CLOEXEC); err != nil {
|
||||||
|
t.Fatalf("Pipe: error = %v", err)
|
||||||
|
}
|
||||||
|
if f, err := os.Create(path.Join(t.TempDir(), "remove")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
ctx.bindPath = check.MustAbs(f.Name())
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { _ = syscall.Close(ctx.closeFds[0]); _ = syscall.Close(ctx.closeFds[1]) })
|
||||||
|
|
||||||
|
if err := ctx.Close(); err != nil {
|
||||||
|
t.Fatalf("Close: error = %v", err)
|
||||||
|
} else if _, err = os.Stat(ctx.bindPath.String()); err == nil || !errors.Is(err, os.ErrNotExist) {
|
||||||
|
t.Fatalf("Did not remove %q", ctx.bindPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
wantErr := &Error{Cause: RCleanup, Path: ctx.bindPath.String(), Errno: errors.Join(syscall.EBADF, syscall.EBADF, &os.PathError{
|
||||||
|
Op: "remove",
|
||||||
|
Path: ctx.bindPath.String(),
|
||||||
|
Err: syscall.ENOENT,
|
||||||
|
})}
|
||||||
|
if err := ctx.Close(); !reflect.DeepEqual(err, wantErr) {
|
||||||
|
t.Fatalf("Close: error = %#v, want %#v", err, wantErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewEnsure(t *testing.T) {
|
||||||
|
existingDirPath := check.MustAbs(t.TempDir()).Append("dir")
|
||||||
|
if err := os.MkdirAll(existingDirPath.String(), 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
nonexistent := check.MustAbs("/proc/nonexistent")
|
||||||
|
|
||||||
|
wantErr := &Error{RCreate, existingDirPath.String(), &os.PathError{
|
||||||
|
Op: "open",
|
||||||
|
Path: existingDirPath.String(),
|
||||||
|
Err: syscall.EISDIR,
|
||||||
|
}}
|
||||||
|
if _, err := New(
|
||||||
|
nonexistent,
|
||||||
|
existingDirPath,
|
||||||
|
); !reflect.DeepEqual(err, wantErr) {
|
||||||
|
t.Fatalf("New: error = %#v, want %#v", err, wantErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,888 +0,0 @@
|
|||||||
package pipewire_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"hakurei.app/internal/pipewire"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFooterCoreGeneration(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
encodingTestCases[pipewire.Footer[pipewire.FooterCoreGeneration], *pipewire.Footer[pipewire.FooterCoreGeneration]]{
|
|
||||||
|
|
||||||
/* recvmsg 0 */
|
|
||||||
|
|
||||||
{"sample0", samplePWContainer[1][0][2], pipewire.Footer[pipewire.FooterCoreGeneration]{
|
|
||||||
Opcode: pipewire.FOOTER_CORE_OPCODE_GENERATION,
|
|
||||||
Payload: pipewire.FooterCoreGeneration{RegistryGeneration: 0x22},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample1", samplePWContainer[1][5][2], pipewire.Footer[pipewire.FooterCoreGeneration]{
|
|
||||||
Opcode: pipewire.FOOTER_CORE_OPCODE_GENERATION,
|
|
||||||
Payload: pipewire.FooterCoreGeneration{RegistryGeneration: 0x23},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
// happens on the last message, client footer sent in the next roundtrip
|
|
||||||
{"sample2", samplePWContainer[1][42][2], pipewire.Footer[pipewire.FooterCoreGeneration]{
|
|
||||||
Opcode: pipewire.FOOTER_CORE_OPCODE_GENERATION,
|
|
||||||
Payload: pipewire.FooterCoreGeneration{RegistryGeneration: 0x24},
|
|
||||||
}, nil},
|
|
||||||
}.run(t)
|
|
||||||
|
|
||||||
encodingTestCases[pipewire.Footer[pipewire.FooterClientGeneration], *pipewire.Footer[pipewire.FooterClientGeneration]]{
|
|
||||||
|
|
||||||
/* sendmsg 1 */
|
|
||||||
|
|
||||||
{"sample0", samplePWContainer[3][0][2], pipewire.Footer[pipewire.FooterClientGeneration]{
|
|
||||||
Opcode: pipewire.FOOTER_CORE_OPCODE_GENERATION,
|
|
||||||
// triggered by difference in sample1, sample0 is overwritten in the same roundtrip
|
|
||||||
Payload: pipewire.FooterClientGeneration{ClientGeneration: 0x23},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
/* sendmsg 2 */
|
|
||||||
|
|
||||||
{"sample1", samplePWContainer[6][0][2], pipewire.Footer[pipewire.FooterClientGeneration]{
|
|
||||||
// triggered by difference in sample2, last footer in the previous roundtrip
|
|
||||||
Opcode: pipewire.FOOTER_CORE_OPCODE_GENERATION,
|
|
||||||
Payload: pipewire.FooterClientGeneration{ClientGeneration: 0x24},
|
|
||||||
}, nil},
|
|
||||||
}.run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCoreInfo(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
encodingTestCases[pipewire.CoreInfo, *pipewire.CoreInfo]{
|
|
||||||
{"sample", samplePWContainer[1][0][1], pipewire.CoreInfo{
|
|
||||||
ID: 0,
|
|
||||||
Cookie: -2069267610,
|
|
||||||
UserName: "alice",
|
|
||||||
HostName: "nixos",
|
|
||||||
Version: "1.4.7",
|
|
||||||
Name: "pipewire-0",
|
|
||||||
ChangeMask: pipewire.PW_CORE_CHANGE_MASK_PROPS,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_CONFIG_NAME, Value: "pipewire.conf"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_NAME, Value: "pipewire"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_PROCESS_BINARY, Value: "pipewire"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_LANGUAGE, Value: "en_US.UTF-8"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_PROCESS_ID, Value: "1446"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_PROCESS_USER, Value: "alice"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_PROCESS_HOST, Value: "nixos"},
|
|
||||||
{Key: pipewire.PW_KEY_WINDOW_X11_DISPLAY, Value: ":0"},
|
|
||||||
{Key: "cpu.vm.name", Value: "qemu"},
|
|
||||||
{Key: "link.max-buffers", Value: "16"},
|
|
||||||
{Key: pipewire.PW_KEY_CORE_DAEMON, Value: "true"},
|
|
||||||
{Key: pipewire.PW_KEY_CORE_NAME, Value: "pipewire-0"},
|
|
||||||
{Key: "default.clock.min-quantum", Value: "1024"},
|
|
||||||
{Key: pipewire.PW_KEY_CPU_MAX_ALIGN, Value: "32"},
|
|
||||||
{Key: "default.clock.rate", Value: "48000"},
|
|
||||||
{Key: "default.clock.quantum", Value: "1024"},
|
|
||||||
{Key: "default.clock.max-quantum", Value: "2048"},
|
|
||||||
{Key: "default.clock.quantum-limit", Value: "8192"},
|
|
||||||
{Key: "default.clock.quantum-floor", Value: "4"},
|
|
||||||
{Key: "default.video.width", Value: "640"},
|
|
||||||
{Key: "default.video.height", Value: "480"},
|
|
||||||
{Key: "default.video.rate.num", Value: "25"},
|
|
||||||
{Key: "default.video.rate.denom", Value: "1"},
|
|
||||||
{Key: "log.level", Value: "2"},
|
|
||||||
{Key: "clock.power-of-two-quantum", Value: "true"},
|
|
||||||
{Key: "mem.warn-mlock", Value: "false"},
|
|
||||||
{Key: "mem.allow-mlock", Value: "true"},
|
|
||||||
{Key: "settings.check-quantum", Value: "false"},
|
|
||||||
{Key: "settings.check-rate", Value: "false"},
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_ID, Value: "0"},
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "0"}},
|
|
||||||
}, nil},
|
|
||||||
}.run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCoreDone(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
encodingTestCases[pipewire.CoreDone, *pipewire.CoreDone]{
|
|
||||||
{"sample0", samplePWContainer[1][5][1], pipewire.CoreDone{
|
|
||||||
ID: -1,
|
|
||||||
Sequence: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
// matches the Core::Sync sample
|
|
||||||
{"sample1", samplePWContainer[1][41][1], pipewire.CoreDone{
|
|
||||||
ID: 0,
|
|
||||||
Sequence: pipewire.CoreSyncSequenceOffset + 3,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
// matches the second Core::Sync sample
|
|
||||||
{"sample2", samplePWContainer[7][0][1], pipewire.CoreDone{
|
|
||||||
ID: 0,
|
|
||||||
Sequence: pipewire.CoreSyncSequenceOffset + 6,
|
|
||||||
}, nil},
|
|
||||||
}.run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCorePing(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
encodingTestCases[pipewire.CorePing, *pipewire.CorePing]{
|
|
||||||
// handmade sample
|
|
||||||
{"sample", []byte{
|
|
||||||
/* size: rest of data */ 0x20, 0, 0, 0,
|
|
||||||
/* type: Struct */ byte(pipewire.SPA_TYPE_Struct), 0, 0, 0,
|
|
||||||
|
|
||||||
/* size: 4 bytes */ 4, 0, 0, 0,
|
|
||||||
/* type: Int */ byte(pipewire.SPA_TYPE_Int), 0, 0, 0,
|
|
||||||
/* value: -1 */ 0xff, 0xff, 0xff, 0xff,
|
|
||||||
/* padding */ 0, 0, 0, 0,
|
|
||||||
|
|
||||||
/* size: 4 bytes */ 4, 0, 0, 0,
|
|
||||||
/* type: Int */ byte(pipewire.SPA_TYPE_Int), 0, 0, 0,
|
|
||||||
/* value: 0 */ 0, 0, 0, 0,
|
|
||||||
/* padding */ 0, 0, 0, 0,
|
|
||||||
}, pipewire.CorePing{
|
|
||||||
ID: -1,
|
|
||||||
Sequence: 0,
|
|
||||||
}, nil},
|
|
||||||
}.run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCoreError(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
encodingTestCases[pipewire.CoreError, *pipewire.CoreError]{
|
|
||||||
// captured from pw-cli
|
|
||||||
{"pw-cli", []byte{
|
|
||||||
/* size: rest of data */ 0x58, 0, 0, 0,
|
|
||||||
/* type: Struct */ 0xe, 0, 0, 0,
|
|
||||||
|
|
||||||
/* size: 4 bytes */ 4, 0, 0, 0,
|
|
||||||
/* type: Int */ 4, 0, 0, 0,
|
|
||||||
/* value: 2 */ 2, 0, 0, 0,
|
|
||||||
/* padding */ 0, 0, 0, 0,
|
|
||||||
|
|
||||||
/* size: 4 bytes */ 4, 0, 0, 0,
|
|
||||||
/* type: Int */ 4, 0, 0, 0,
|
|
||||||
/* value: 0x67 */ 0x67, 0, 0, 0,
|
|
||||||
/* padding */ 0, 0, 0, 0,
|
|
||||||
|
|
||||||
/* size: 4 bytes */ 4, 0, 0, 0,
|
|
||||||
/* type: Int */ 4, 0, 0, 0,
|
|
||||||
/* value: -1 */ 0xff, 0xff, 0xff, 0xff,
|
|
||||||
/* padding */ 0, 0, 0, 0,
|
|
||||||
|
|
||||||
/* size: 0x1b bytes */ 0x1b, 0, 0, 0,
|
|
||||||
/* type: String */ 8, 0, 0, 0,
|
|
||||||
|
|
||||||
// value: "no permission to destroy 0\x00"
|
|
||||||
0x6e, 0x6f, 0x20, 0x70,
|
|
||||||
0x65, 0x72, 0x6d, 0x69,
|
|
||||||
0x73, 0x73, 0x69, 0x6f,
|
|
||||||
0x6e, 0x20, 0x74, 0x6f,
|
|
||||||
0x20, 0x64, 0x65, 0x73,
|
|
||||||
0x74, 0x72, 0x6f, 0x79,
|
|
||||||
0x20, 0x30, 0,
|
|
||||||
|
|
||||||
/* padding */ 0, 0, 0, 0, 0,
|
|
||||||
}, pipewire.CoreError{
|
|
||||||
ID: 2,
|
|
||||||
Sequence: 0x67,
|
|
||||||
Result: -1,
|
|
||||||
Message: "no permission to destroy 0",
|
|
||||||
}, nil},
|
|
||||||
}.run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCoreRemoveId(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
encodingTestCases[pipewire.CoreRemoveId, *pipewire.CoreRemoveId]{
|
|
||||||
{"sample", []byte{
|
|
||||||
/* size: rest of data */ 0x10, 0, 0, 0,
|
|
||||||
/* type: Struct */ 0xe, 0, 0, 0,
|
|
||||||
|
|
||||||
/* size: 4 bytes */ 4, 0, 0, 0,
|
|
||||||
/* type: Int */ 4, 0, 0, 0,
|
|
||||||
/* value: 3 */ 3, 0, 0, 0,
|
|
||||||
/* padding */ 0, 0, 0, 0,
|
|
||||||
}, pipewire.CoreRemoveId{
|
|
||||||
ID: 3,
|
|
||||||
}, nil},
|
|
||||||
}.run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCoreBoundProps(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
encodingTestCases[pipewire.CoreBoundProps, *pipewire.CoreBoundProps]{
|
|
||||||
|
|
||||||
/* recvmsg 0 */
|
|
||||||
|
|
||||||
{"sample0", samplePWContainer[1][1][1], pipewire.CoreBoundProps{
|
|
||||||
ID: pipewire.PW_ID_CLIENT,
|
|
||||||
GlobalID: 34,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "34"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_ID, Value: "2"},
|
|
||||||
{Key: pipewire.PW_KEY_PROTOCOL, Value: "protocol-native"},
|
|
||||||
{Key: pipewire.PW_KEY_SEC_PID, Value: "1443"},
|
|
||||||
{Key: pipewire.PW_KEY_SEC_UID, Value: "1000"},
|
|
||||||
{Key: pipewire.PW_KEY_SEC_GID, Value: "100"},
|
|
||||||
{Key: pipewire.PW_KEY_SEC_SOCKET, Value: "pipewire-0-manager"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
/* recvmsg 1 */
|
|
||||||
|
|
||||||
{"sample1", samplePWContainer[4][0][1], pipewire.CoreBoundProps{
|
|
||||||
ID: 3,
|
|
||||||
GlobalID: 3,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "3"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
}.run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCoreHello(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
encodingTestCases[pipewire.CoreHello, *pipewire.CoreHello]{
|
|
||||||
{"sample", samplePWContainer[0][0][1], pipewire.CoreHello{
|
|
||||||
Version: pipewire.PW_VERSION_CORE,
|
|
||||||
}, nil},
|
|
||||||
}.run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCoreSync(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
encodingTestCases[pipewire.CoreSync, *pipewire.CoreSync]{
|
|
||||||
{"sample0", samplePWContainer[0][3][1], pipewire.CoreSync{
|
|
||||||
ID: 0,
|
|
||||||
Sequence: pipewire.CoreSyncSequenceOffset + 3,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample1", samplePWContainer[6][1][1], pipewire.CoreSync{
|
|
||||||
ID: 0,
|
|
||||||
Sequence: pipewire.CoreSyncSequenceOffset + 6,
|
|
||||||
}, nil},
|
|
||||||
}.run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCorePong(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
encodingTestCases[pipewire.CorePong, *pipewire.CorePong]{
|
|
||||||
// handmade sample
|
|
||||||
{"sample", []byte{
|
|
||||||
/* size: rest of data */ 0x20, 0, 0, 0,
|
|
||||||
/* type: Struct */ byte(pipewire.SPA_TYPE_Struct), 0, 0, 0,
|
|
||||||
|
|
||||||
/* size: 4 bytes */ 4, 0, 0, 0,
|
|
||||||
/* type: Int */ byte(pipewire.SPA_TYPE_Int), 0, 0, 0,
|
|
||||||
/* value: -1 */ 0xff, 0xff, 0xff, 0xff,
|
|
||||||
/* padding */ 0, 0, 0, 0,
|
|
||||||
|
|
||||||
/* size: 4 bytes */ 4, 0, 0, 0,
|
|
||||||
/* type: Int */ byte(pipewire.SPA_TYPE_Int), 0, 0, 0,
|
|
||||||
/* value: 0 */ 0, 0, 0, 0,
|
|
||||||
/* padding */ 0, 0, 0, 0,
|
|
||||||
}, pipewire.CorePong{
|
|
||||||
ID: -1,
|
|
||||||
Sequence: 0,
|
|
||||||
}, nil},
|
|
||||||
}.run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCoreGetRegistry(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
encodingTestCases[pipewire.CoreGetRegistry, *pipewire.CoreGetRegistry]{
|
|
||||||
{"sample", samplePWContainer[0][2][1], pipewire.CoreGetRegistry{
|
|
||||||
Version: pipewire.PW_VERSION_REGISTRY,
|
|
||||||
// this ends up as the Id of PW_TYPE_INTERFACE_Registry
|
|
||||||
NewID: 2,
|
|
||||||
}, nil},
|
|
||||||
}.run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCoreCreateObject(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
encodingTestCases[pipewire.CoreCreateObject, *pipewire.CoreCreateObject]{
|
|
||||||
{"sample", []byte{
|
|
||||||
/* size: rest of data */ 0x80, 0, 0, 0,
|
|
||||||
/* type: Struct */ 0xe, 0, 0, 0,
|
|
||||||
|
|
||||||
/* size: 0x13 bytes */ 0x13, 0, 0, 0,
|
|
||||||
/* type: String */ 8, 0, 0, 0,
|
|
||||||
|
|
||||||
// value: "spa-device-factory\x00"
|
|
||||||
0x73, 0x70, 0x61, 0x2d,
|
|
||||||
0x64, 0x65, 0x76, 0x69,
|
|
||||||
0x63, 0x65, 0x2d, 0x66,
|
|
||||||
0x61, 0x63, 0x74, 0x6f,
|
|
||||||
0x72, 0x79, 0, 0,
|
|
||||||
0, 0, 0, 0,
|
|
||||||
|
|
||||||
/* size: 0x1a bytes */ 0x1a, 0, 0, 0,
|
|
||||||
/* type: String */ 8, 0, 0, 0,
|
|
||||||
|
|
||||||
// value: "PipeWire:Interface:Device\x00"
|
|
||||||
0x50, 0x69, 0x70, 0x65,
|
|
||||||
0x57, 0x69, 0x72, 0x65,
|
|
||||||
0x3a, 0x49, 0x6e, 0x74,
|
|
||||||
0x65, 0x72, 0x66, 0x61,
|
|
||||||
0x63, 0x65, 0x3a, 0x44,
|
|
||||||
0x65, 0x76, 0x69, 0x63,
|
|
||||||
0x65, 0, 0, 0,
|
|
||||||
0, 0, 0, 0,
|
|
||||||
|
|
||||||
/* size: 4 bytes */ 4, 0, 0, 0,
|
|
||||||
/* type: Int */ 4, 0, 0, 0,
|
|
||||||
/* value: 3 */ 3, 0, 0, 0,
|
|
||||||
/* padding */ 0, 0, 0, 0,
|
|
||||||
|
|
||||||
/* size */ 0x10, 0, 0, 0,
|
|
||||||
/* type: Struct */ 0xe, 0, 0, 0,
|
|
||||||
|
|
||||||
/* size: 4 bytes */ 4, 0, 0, 0,
|
|
||||||
/* type: Int */ 4, 0, 0, 0,
|
|
||||||
/* value: 0 */ 0, 0, 0, 0,
|
|
||||||
/* padding */ 0, 0, 0, 0,
|
|
||||||
|
|
||||||
/* size: 4 bytes */ 4, 0, 0, 0,
|
|
||||||
/* type: Int */ 4, 0, 0, 0,
|
|
||||||
/* value: 0xbad */ 0xad, 0xb, 0, 0,
|
|
||||||
/* padding */ 0, 0, 0, 0,
|
|
||||||
}, pipewire.CoreCreateObject{
|
|
||||||
FactoryName: "spa-device-factory",
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Device,
|
|
||||||
Version: pipewire.PW_VERSION_FACTORY,
|
|
||||||
Properties: &pipewire.SPADict{},
|
|
||||||
NewID: 0xbad,
|
|
||||||
}, nil},
|
|
||||||
}.run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCoreDestroy(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
encodingTestCases[pipewire.CoreDestroy, *pipewire.CoreDestroy]{
|
|
||||||
{"sample", []byte{
|
|
||||||
/* size: rest of data */ 0x10, 0, 0, 0,
|
|
||||||
/* type: Struct */ 0xe, 0, 0, 0,
|
|
||||||
|
|
||||||
/* size: 4 bytes */ 4, 0, 0, 0,
|
|
||||||
/* type: Int */ 4, 0, 0, 0,
|
|
||||||
/* value: 3 */ 3, 0, 0, 0,
|
|
||||||
/* padding */ 0, 0, 0, 0,
|
|
||||||
}, pipewire.CoreDestroy{
|
|
||||||
ID: 3,
|
|
||||||
}, nil},
|
|
||||||
}.run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRegistryGlobal(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
encodingTestCases[pipewire.RegistryGlobal, *pipewire.RegistryGlobal]{
|
|
||||||
{"sample0", samplePWContainer[1][6][1], pipewire.RegistryGlobal{
|
|
||||||
ID: pipewire.PW_ID_CORE,
|
|
||||||
Permissions: pipewire.PW_CORE_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Core,
|
|
||||||
Version: pipewire.PW_VERSION_CORE,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "0"},
|
|
||||||
{Key: pipewire.PW_KEY_CORE_NAME, Value: "pipewire-0"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample1", samplePWContainer[1][7][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 1,
|
|
||||||
Permissions: pipewire.PW_MODULE_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Module,
|
|
||||||
Version: pipewire.PW_VERSION_MODULE,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "1"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-rt"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample2", samplePWContainer[1][8][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 3,
|
|
||||||
Permissions: pipewire.PW_SECURITY_CONTEXT_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_SecurityContext,
|
|
||||||
Version: pipewire.PW_VERSION_SECURITY_CONTEXT,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "3"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample3", samplePWContainer[1][9][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 2,
|
|
||||||
Permissions: pipewire.PW_MODULE_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Module,
|
|
||||||
Version: pipewire.PW_VERSION_MODULE,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "2"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-protocol-native"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample4", samplePWContainer[1][10][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 5,
|
|
||||||
Permissions: pipewire.PW_PROFILER_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Profiler,
|
|
||||||
Version: pipewire.PW_VERSION_PROFILER,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "5"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample5", samplePWContainer[1][11][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 4,
|
|
||||||
Permissions: pipewire.PW_MODULE_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Module,
|
|
||||||
Version: pipewire.PW_VERSION_MODULE,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "4"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-profiler"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample6", samplePWContainer[1][12][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 6,
|
|
||||||
Permissions: pipewire.PW_MODULE_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Module,
|
|
||||||
Version: pipewire.PW_VERSION_MODULE,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "6"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-metadata"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample7", samplePWContainer[1][13][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 7,
|
|
||||||
Permissions: pipewire.PW_FACTORY_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Factory,
|
|
||||||
Version: pipewire.PW_VERSION_FACTORY,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "7"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_ID, Value: "6"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "metadata"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: pipewire.PW_TYPE_INTERFACE_Metadata},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "3"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample8", samplePWContainer[1][14][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 8,
|
|
||||||
Permissions: pipewire.PW_MODULE_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Module,
|
|
||||||
Version: pipewire.PW_VERSION_MODULE,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "8"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-spa-device-factory"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample9", samplePWContainer[1][15][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 9,
|
|
||||||
Permissions: pipewire.PW_FACTORY_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Factory,
|
|
||||||
Version: pipewire.PW_VERSION_FACTORY,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "9"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_ID, Value: "8"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "spa-device-factory"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: pipewire.PW_TYPE_INTERFACE_Device},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "3"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample10", samplePWContainer[1][16][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 10,
|
|
||||||
Permissions: pipewire.PW_MODULE_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Module,
|
|
||||||
Version: pipewire.PW_VERSION_MODULE,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "10"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-spa-node-factory"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample11", samplePWContainer[1][17][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 11,
|
|
||||||
Permissions: pipewire.PW_FACTORY_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Factory,
|
|
||||||
Version: pipewire.PW_VERSION_FACTORY,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "11"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_ID, Value: "10"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "spa-node-factory"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: pipewire.PW_TYPE_INTERFACE_Node},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "3"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample12", samplePWContainer[1][18][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 12,
|
|
||||||
Permissions: pipewire.PW_MODULE_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Module,
|
|
||||||
Version: pipewire.PW_VERSION_MODULE,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "12"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-client-node"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample13", samplePWContainer[1][19][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 13,
|
|
||||||
Permissions: pipewire.PW_FACTORY_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Factory,
|
|
||||||
Version: pipewire.PW_VERSION_FACTORY,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "13"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_ID, Value: "12"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "client-node"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: pipewire.PW_TYPE_INTERFACE_ClientNode},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "6"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample14", samplePWContainer[1][20][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 14,
|
|
||||||
Permissions: pipewire.PW_MODULE_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Module,
|
|
||||||
Version: pipewire.PW_VERSION_MODULE,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "14"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-client-device"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample15", samplePWContainer[1][21][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 15,
|
|
||||||
Permissions: pipewire.PW_FACTORY_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Factory,
|
|
||||||
Version: pipewire.PW_VERSION_FACTORY,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "15"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_ID, Value: "14"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "client-device"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: "Spa:Pointer:Interface:Device"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "0"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample16", samplePWContainer[1][22][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 16,
|
|
||||||
Permissions: pipewire.PW_MODULE_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Module,
|
|
||||||
Version: pipewire.PW_VERSION_MODULE,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "16"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-portal"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample17", samplePWContainer[1][23][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 17,
|
|
||||||
Permissions: pipewire.PW_MODULE_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Module,
|
|
||||||
Version: pipewire.PW_VERSION_MODULE,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "17"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-access"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample18", samplePWContainer[1][24][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 18,
|
|
||||||
Permissions: pipewire.PW_MODULE_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Module,
|
|
||||||
Version: pipewire.PW_VERSION_MODULE,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "18"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-adapter"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample19", samplePWContainer[1][25][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 19,
|
|
||||||
Permissions: pipewire.PW_FACTORY_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Factory,
|
|
||||||
Version: pipewire.PW_VERSION_FACTORY,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "19"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_ID, Value: "18"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "adapter"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: pipewire.PW_TYPE_INTERFACE_Node},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "3"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample20", samplePWContainer[1][26][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 20,
|
|
||||||
Permissions: pipewire.PW_MODULE_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Module,
|
|
||||||
Version: pipewire.PW_VERSION_MODULE,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "20"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-link-factory"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample21", samplePWContainer[1][27][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 21,
|
|
||||||
Permissions: pipewire.PW_FACTORY_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Factory,
|
|
||||||
Version: pipewire.PW_VERSION_FACTORY,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "21"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_ID, Value: "20"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "link-factory"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: pipewire.PW_TYPE_INTERFACE_Link},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "3"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample22", samplePWContainer[1][28][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 22,
|
|
||||||
Permissions: pipewire.PW_MODULE_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Module,
|
|
||||||
Version: pipewire.PW_VERSION_MODULE,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "22"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-session-manager"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample23", samplePWContainer[1][29][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 23,
|
|
||||||
Permissions: pipewire.PW_FACTORY_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Factory,
|
|
||||||
Version: pipewire.PW_VERSION_FACTORY,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "23"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_ID, Value: "22"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "client-endpoint"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: "PipeWire:Interface:ClientEndpoint"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "0"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample24", samplePWContainer[1][30][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 24,
|
|
||||||
Permissions: pipewire.PW_FACTORY_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Factory,
|
|
||||||
Version: pipewire.PW_VERSION_FACTORY,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "24"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_ID, Value: "22"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "client-session"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: "PipeWire:Interface:ClientSession"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "0"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample25", samplePWContainer[1][31][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 25,
|
|
||||||
Permissions: pipewire.PW_FACTORY_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Factory,
|
|
||||||
Version: pipewire.PW_VERSION_FACTORY,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "25"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_ID, Value: "22"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "session"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: "PipeWire:Interface:Session"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "0"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample26", samplePWContainer[1][32][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 26,
|
|
||||||
Permissions: pipewire.PW_FACTORY_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Factory,
|
|
||||||
Version: pipewire.PW_VERSION_FACTORY,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "26"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_ID, Value: "22"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "endpoint"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: "PipeWire:Interface:Endpoint"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "0"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample27", samplePWContainer[1][33][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 27,
|
|
||||||
Permissions: pipewire.PW_FACTORY_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Factory,
|
|
||||||
Version: pipewire.PW_VERSION_FACTORY,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "27"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_ID, Value: "22"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "endpoint-stream"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: "PipeWire:Interface:EndpointStream"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "0"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample28", samplePWContainer[1][34][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 28,
|
|
||||||
Permissions: pipewire.PW_FACTORY_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Factory,
|
|
||||||
Version: pipewire.PW_VERSION_FACTORY,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "28"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_ID, Value: "22"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "endpoint-link"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: "PipeWire:Interface:EndpointLink"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "0"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample29", samplePWContainer[1][35][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 29,
|
|
||||||
Permissions: pipewire.PW_MODULE_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Module,
|
|
||||||
Version: pipewire.PW_VERSION_MODULE,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "29"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-x11-bell"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample30", samplePWContainer[1][36][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 30,
|
|
||||||
Permissions: pipewire.PW_MODULE_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Module,
|
|
||||||
Version: pipewire.PW_VERSION_MODULE,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "30"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-jackdbus-detect"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample31", samplePWContainer[1][37][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 31,
|
|
||||||
Permissions: pipewire.PW_PERM_RWXM, // why is this not PW_NODE_PERM_MASK?
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Node,
|
|
||||||
Version: pipewire.PW_VERSION_NODE,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "31"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_ID, Value: "11"},
|
|
||||||
{Key: pipewire.PW_KEY_PRIORITY_DRIVER, Value: "200000"},
|
|
||||||
{Key: pipewire.PW_KEY_NODE_NAME, Value: "Dummy-Driver"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample32", samplePWContainer[1][38][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 32,
|
|
||||||
Permissions: pipewire.PW_PERM_RWXM, // why is this not PW_NODE_PERM_MASK?
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Node,
|
|
||||||
Version: pipewire.PW_VERSION_NODE,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "32"},
|
|
||||||
{Key: pipewire.PW_KEY_FACTORY_ID, Value: "11"},
|
|
||||||
{Key: pipewire.PW_KEY_PRIORITY_DRIVER, Value: "190000"},
|
|
||||||
{Key: pipewire.PW_KEY_NODE_NAME, Value: "Freewheel-Driver"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample33", samplePWContainer[1][39][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 33,
|
|
||||||
Permissions: pipewire.PW_METADATA_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Metadata,
|
|
||||||
Version: pipewire.PW_VERSION_METADATA,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "33"},
|
|
||||||
{Key: "metadata.name", Value: "settings"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample34", samplePWContainer[1][40][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 34,
|
|
||||||
Permissions: pipewire.PW_CLIENT_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Client,
|
|
||||||
Version: pipewire.PW_VERSION_CLIENT,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "34"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_ID, Value: "2"},
|
|
||||||
{Key: pipewire.PW_KEY_PROTOCOL, Value: "protocol-native"},
|
|
||||||
{Key: pipewire.PW_KEY_SEC_PID, Value: "1443"},
|
|
||||||
{Key: pipewire.PW_KEY_SEC_UID, Value: "1000"},
|
|
||||||
{Key: pipewire.PW_KEY_SEC_GID, Value: "100"},
|
|
||||||
{Key: pipewire.PW_KEY_SEC_SOCKET, Value: "pipewire-0-manager"},
|
|
||||||
{Key: pipewire.PW_KEY_ACCESS, Value: "unrestricted"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_NAME, Value: "pw-container"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"sample35", samplePWContainer[1][42][1], pipewire.RegistryGlobal{
|
|
||||||
ID: 35,
|
|
||||||
Permissions: pipewire.PW_CLIENT_PERM_MASK,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_Client,
|
|
||||||
Version: pipewire.PW_VERSION_CLIENT,
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "35"},
|
|
||||||
{Key: pipewire.PW_KEY_MODULE_ID, Value: "2"},
|
|
||||||
{Key: pipewire.PW_KEY_PROTOCOL, Value: "protocol-native"},
|
|
||||||
{Key: pipewire.PW_KEY_SEC_PID, Value: "1447"},
|
|
||||||
{Key: pipewire.PW_KEY_SEC_UID, Value: "1000"},
|
|
||||||
{Key: pipewire.PW_KEY_SEC_GID, Value: "100"},
|
|
||||||
{Key: pipewire.PW_KEY_SEC_SOCKET, Value: "pipewire-0-manager"},
|
|
||||||
{Key: pipewire.PW_KEY_ACCESS, Value: "unrestricted"},
|
|
||||||
{Key: pipewire.PW_KEY_APP_NAME, Value: "WirePlumber"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
}.run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRegistryGlobalRemove(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
encodingTestCases[pipewire.RegistryGlobalRemove, *pipewire.RegistryGlobalRemove]{
|
|
||||||
{"sample", []byte{
|
|
||||||
/* size: rest of data*/ 0x10, 0, 0, 0,
|
|
||||||
/* type: Struct */ 0xe, 0, 0, 0,
|
|
||||||
/* size: 4 bytes */ 4, 0, 0, 0,
|
|
||||||
/* type: Int */ 4, 0, 0, 0,
|
|
||||||
/* value: 0xbad */ 0xad, 0xb, 0, 0,
|
|
||||||
/* padding */ 0, 0, 0, 0,
|
|
||||||
}, pipewire.RegistryGlobalRemove{
|
|
||||||
ID: 0xbad,
|
|
||||||
}, nil},
|
|
||||||
}.run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRegistryBind(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
encodingTestCases[pipewire.RegistryBind, *pipewire.RegistryBind]{
|
|
||||||
{"sample", samplePWContainer[3][0][1], pipewire.RegistryBind{
|
|
||||||
ID: 3,
|
|
||||||
Type: pipewire.PW_TYPE_INTERFACE_SecurityContext,
|
|
||||||
Version: pipewire.PW_VERSION_SECURITY_CONTEXT,
|
|
||||||
NewID: 3, // registry takes up 2
|
|
||||||
}, nil},
|
|
||||||
}.run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRegistryDestroy(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
encodingTestCases[pipewire.RegistryDestroy, *pipewire.RegistryDestroy]{
|
|
||||||
{"sample", []byte{
|
|
||||||
/* size: rest of data*/ 0x10, 0, 0, 0,
|
|
||||||
/* type: Struct */ 0xe, 0, 0, 0,
|
|
||||||
/* size: 4 bytes */ 4, 0, 0, 0,
|
|
||||||
/* type: Int */ 4, 0, 0, 0,
|
|
||||||
/* value: 0xbad */ 0xad, 0xb, 0, 0,
|
|
||||||
/* padding */ 0, 0, 0, 0,
|
|
||||||
}, pipewire.RegistryDestroy{
|
|
||||||
ID: 0xbad,
|
|
||||||
}, nil},
|
|
||||||
}.run(t)
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
package pipewire
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// SizeHeader is the fixed size of [Header].
|
|
||||||
SizeHeader = 16
|
|
||||||
// SizeMax is the largest value of [Header.Size] that can be represented in its 3-byte segment.
|
|
||||||
SizeMax = 0x00ffffff
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrSizeRange indicates that the value of [Header.Size] cannot be represented in its 3-byte segment.
|
|
||||||
ErrSizeRange = errors.New("size out of range")
|
|
||||||
// ErrBadHeader indicates that the header slice does not have length [HeaderSize].
|
|
||||||
ErrBadHeader = errors.New("incorrect header size")
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Header is the fixed-size message header described in protocol native.
|
|
||||||
type Header struct {
|
|
||||||
// The message id this is the destination resource/proxy id.
|
|
||||||
ID Int `json:"Id"`
|
|
||||||
// The opcode on the resource/proxy interface.
|
|
||||||
Opcode byte `json:"opcode"`
|
|
||||||
// The size of the payload and optional footer of the message.
|
|
||||||
// Note: this value is only 24 bits long in the format.
|
|
||||||
Size uint32 `json:"size"`
|
|
||||||
// An increasing sequence number for each message.
|
|
||||||
Sequence Int `json:"seq"`
|
|
||||||
// Number of file descriptors in this message.
|
|
||||||
FileCount Int `json:"n_fds"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// append appends the protocol native message header to data.
|
|
||||||
//
|
|
||||||
// Callers must perform bounds check on [Header.Size].
|
|
||||||
func (h *Header) append(data []byte) []byte {
|
|
||||||
data = binary.NativeEndian.AppendUint32(data, Word(h.ID))
|
|
||||||
data = binary.NativeEndian.AppendUint32(data, Word(h.Opcode)<<24|h.Size)
|
|
||||||
data = binary.NativeEndian.AppendUint32(data, Word(h.Sequence))
|
|
||||||
data = binary.NativeEndian.AppendUint32(data, Word(h.FileCount))
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalBinary encodes the protocol native message header.
|
|
||||||
func (h *Header) MarshalBinary() (data []byte, err error) {
|
|
||||||
if h.Size&^SizeMax != 0 {
|
|
||||||
return nil, ErrSizeRange
|
|
||||||
}
|
|
||||||
return h.append(make([]byte, 0, SizeHeader)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// unmarshalBinary decodes the protocol native message header.
|
|
||||||
func (h *Header) unmarshalBinary(data [SizeHeader]byte) {
|
|
||||||
h.ID = Int(binary.NativeEndian.Uint32(data[0:4]))
|
|
||||||
h.Size = binary.NativeEndian.Uint32(data[4:8])
|
|
||||||
h.Opcode = byte(h.Size >> 24)
|
|
||||||
h.Size &= SizeMax
|
|
||||||
h.Sequence = Int(binary.NativeEndian.Uint32(data[8:]))
|
|
||||||
h.FileCount = Int(binary.NativeEndian.Uint32(data[12:]))
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalBinary decodes the protocol native message header.
|
|
||||||
func (h *Header) UnmarshalBinary(data []byte) error {
|
|
||||||
if len(data) != SizeHeader {
|
|
||||||
return ErrBadHeader
|
|
||||||
}
|
|
||||||
h.unmarshalBinary(([SizeHeader]byte)(data))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,407 +0,0 @@
|
|||||||
package pipewire_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"hakurei.app/internal/pipewire"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestHeader(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
encodingTestCases[pipewire.Header, *pipewire.Header]{
|
|
||||||
|
|
||||||
/* sendmsg 0 */
|
|
||||||
|
|
||||||
{"PW_CORE_METHOD_HELLO", samplePWContainer[0][0][0], pipewire.Header{
|
|
||||||
ID: pipewire.PW_ID_CORE,
|
|
||||||
Opcode: pipewire.PW_CORE_METHOD_HELLO,
|
|
||||||
Size: 0x18, Sequence: 0, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_CLIENT_METHOD_UPDATE_PROPERTIES", samplePWContainer[0][1][0], pipewire.Header{
|
|
||||||
ID: pipewire.PW_ID_CLIENT,
|
|
||||||
Opcode: pipewire.PW_CLIENT_METHOD_UPDATE_PROPERTIES,
|
|
||||||
Size: 0x600, Sequence: 1, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_CORE_METHOD_GET_REGISTRY", samplePWContainer[0][2][0], pipewire.Header{
|
|
||||||
ID: pipewire.PW_ID_CORE,
|
|
||||||
Opcode: pipewire.PW_CORE_METHOD_GET_REGISTRY,
|
|
||||||
Size: 0x28, Sequence: 2, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_CORE_METHOD_SYNC 0", samplePWContainer[0][3][0], pipewire.Header{
|
|
||||||
ID: pipewire.PW_ID_CORE,
|
|
||||||
Opcode: pipewire.PW_CORE_METHOD_SYNC,
|
|
||||||
Size: 0x28, Sequence: 3, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
/* recvmsg 0 */
|
|
||||||
|
|
||||||
{"PW_CORE_EVENT_INFO", samplePWContainer[1][0][0], pipewire.Header{
|
|
||||||
ID: pipewire.PW_ID_CORE,
|
|
||||||
Opcode: pipewire.PW_CORE_EVENT_INFO,
|
|
||||||
Size: 0x6b8, Sequence: 0, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_CORE_EVENT_BOUND_PROPS 0", samplePWContainer[1][1][0], pipewire.Header{
|
|
||||||
ID: pipewire.PW_ID_CORE,
|
|
||||||
Opcode: pipewire.PW_CORE_EVENT_BOUND_PROPS,
|
|
||||||
Size: 0x198, Sequence: 1, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_CLIENT_EVENT_INFO 0", samplePWContainer[1][2][0], pipewire.Header{
|
|
||||||
ID: pipewire.PW_ID_CLIENT,
|
|
||||||
Opcode: pipewire.PW_CLIENT_EVENT_INFO,
|
|
||||||
Size: 0x1f0, Sequence: 2, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_CLIENT_EVENT_INFO 1", samplePWContainer[1][3][0], pipewire.Header{
|
|
||||||
ID: pipewire.PW_ID_CLIENT,
|
|
||||||
Opcode: pipewire.PW_CLIENT_EVENT_INFO,
|
|
||||||
Size: 0x7a0, Sequence: 3, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_CLIENT_EVENT_INFO 2", samplePWContainer[1][4][0], pipewire.Header{
|
|
||||||
ID: pipewire.PW_ID_CLIENT,
|
|
||||||
Opcode: pipewire.PW_CLIENT_EVENT_INFO,
|
|
||||||
Size: 0x7d0, Sequence: 4, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_CORE_EVENT_DONE 0", samplePWContainer[1][5][0], pipewire.Header{
|
|
||||||
ID: pipewire.PW_ID_CORE,
|
|
||||||
Opcode: pipewire.PW_CORE_EVENT_DONE,
|
|
||||||
Size: 0x58, Sequence: 5, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 0", samplePWContainer[1][6][0], pipewire.Header{
|
|
||||||
ID: 2, // this is specified by Core::GetRegistry in samplePWContainer[0][2][1]
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0xc8, Sequence: 6, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 1", samplePWContainer[1][7][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0xd8, Sequence: 7, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 2", samplePWContainer[1][8][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0xa8, Sequence: 8, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 3", samplePWContainer[1][9][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0xe8, Sequence: 9, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 4", samplePWContainer[1][10][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0xa0, Sequence: 10, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 5", samplePWContainer[1][11][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0xe0, Sequence: 11, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 6", samplePWContainer[1][12][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0xe0, Sequence: 12, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 7", samplePWContainer[1][13][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0x170, Sequence: 13, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 8", samplePWContainer[1][14][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0xe8, Sequence: 14, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 9", samplePWContainer[1][15][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0x178, Sequence: 15, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 10", samplePWContainer[1][16][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0xe8, Sequence: 16, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 11", samplePWContainer[1][17][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0x170, Sequence: 17, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 12", samplePWContainer[1][18][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0xe0, Sequence: 18, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 13", samplePWContainer[1][19][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0x170, Sequence: 19, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 14", samplePWContainer[1][20][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0xe8, Sequence: 20, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 15", samplePWContainer[1][21][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0x170, Sequence: 21, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 16", samplePWContainer[1][22][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0xe0, Sequence: 22, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 17", samplePWContainer[1][23][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0xe0, Sequence: 23, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 18", samplePWContainer[1][24][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0xe0, Sequence: 24, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 19", samplePWContainer[1][25][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0x160, Sequence: 25, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 20", samplePWContainer[1][26][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0xe0, Sequence: 26, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 21", samplePWContainer[1][27][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0x168, Sequence: 27, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 22", samplePWContainer[1][28][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0xe8, Sequence: 28, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 23", samplePWContainer[1][29][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0x178, Sequence: 29, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 24", samplePWContainer[1][30][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0x178, Sequence: 30, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 25", samplePWContainer[1][31][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0x168, Sequence: 31, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 26", samplePWContainer[1][32][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0x170, Sequence: 32, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 27", samplePWContainer[1][33][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0x178, Sequence: 33, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 28", samplePWContainer[1][34][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0x170, Sequence: 34, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 29", samplePWContainer[1][35][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0xe0, Sequence: 35, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 30", samplePWContainer[1][36][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0xe8, Sequence: 36, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 31", samplePWContainer[1][37][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0x118, Sequence: 37, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 32", samplePWContainer[1][38][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0x120, Sequence: 38, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 33", samplePWContainer[1][39][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0xd0, Sequence: 39, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 34", samplePWContainer[1][40][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0x238, Sequence: 40, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_CORE_EVENT_DONE 1", samplePWContainer[1][41][0], pipewire.Header{
|
|
||||||
ID: pipewire.PW_ID_CORE,
|
|
||||||
Opcode: pipewire.PW_CORE_EVENT_DONE,
|
|
||||||
Size: 0x28, Sequence: 41, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_REGISTRY_EVENT_GLOBAL 35", samplePWContainer[1][42][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_EVENT_GLOBAL,
|
|
||||||
Size: 0x268, Sequence: 42, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
/* sendmsg 1 */
|
|
||||||
|
|
||||||
{"PW_REGISTRY_METHOD_BIND", samplePWContainer[3][0][0], pipewire.Header{
|
|
||||||
ID: 2,
|
|
||||||
Opcode: pipewire.PW_REGISTRY_METHOD_BIND,
|
|
||||||
Size: 0x98, Sequence: 4, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
/* recvmsg 1 */
|
|
||||||
|
|
||||||
{"PW_CORE_EVENT_BOUND_PROPS 1", samplePWContainer[4][0][0], pipewire.Header{
|
|
||||||
ID: pipewire.PW_ID_CORE,
|
|
||||||
Opcode: pipewire.PW_CORE_EVENT_BOUND_PROPS,
|
|
||||||
Size: 0x68, Sequence: 43, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
/* sendmsg 2 */
|
|
||||||
|
|
||||||
{"PW_SECURITY_CONTEXT_METHOD_CREATE", samplePWContainer[6][0][0], pipewire.Header{
|
|
||||||
ID: 3,
|
|
||||||
Opcode: pipewire.PW_SECURITY_CONTEXT_METHOD_CREATE,
|
|
||||||
Size: 0xd8, Sequence: 5, FileCount: 2,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_CORE_METHOD_SYNC 1", samplePWContainer[6][1][0], pipewire.Header{
|
|
||||||
ID: pipewire.PW_ID_CORE,
|
|
||||||
Opcode: pipewire.PW_CORE_METHOD_SYNC,
|
|
||||||
Size: 0x28, Sequence: 6, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
/* recvmsg 2 */
|
|
||||||
|
|
||||||
{"PW_CORE_EVENT_DONE 2", samplePWContainer[7][0][0], pipewire.Header{
|
|
||||||
ID: pipewire.PW_ID_CORE,
|
|
||||||
Opcode: pipewire.PW_CORE_EVENT_DONE,
|
|
||||||
Size: 0x28, Sequence: 44, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
/* excerpts */
|
|
||||||
|
|
||||||
{"PW_CORE_EVENT_ERROR", []byte{
|
|
||||||
/* Id: */ 0, 0, 0, 0,
|
|
||||||
/* size: */ 0x60, 0, 0,
|
|
||||||
/* opcode: */ 3,
|
|
||||||
/* seq: */ 0xf5, 0, 0, 0,
|
|
||||||
/* n_fds: */ 0, 0, 0, 0,
|
|
||||||
}, pipewire.Header{
|
|
||||||
ID: pipewire.PW_ID_CORE,
|
|
||||||
Opcode: pipewire.PW_CORE_EVENT_ERROR,
|
|
||||||
Size: 0x60, Sequence: 0xf5, FileCount: 0,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
/* handmade samples */
|
|
||||||
|
|
||||||
{"PW_CORE_EVENT_PING", []byte{
|
|
||||||
/* Id: */ 0, 0, 0, 0,
|
|
||||||
/* size: */ 0xed, 0xb, 0,
|
|
||||||
/* opcode: */ 2,
|
|
||||||
/* seq: */ 0xff, 0xff, 0, 0,
|
|
||||||
/* n_fds: */ 0xfe, 0xca, 0, 0,
|
|
||||||
}, pipewire.Header{
|
|
||||||
ID: pipewire.PW_ID_CORE,
|
|
||||||
Opcode: pipewire.PW_CORE_EVENT_PING,
|
|
||||||
Size: 0xbed, Sequence: 0xffff, FileCount: 0xcafe,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_CORE_METHOD_PONG", []byte{
|
|
||||||
/* Id: */ 0, 0, 0, 0,
|
|
||||||
/* size: */ 0xed, 0xb, 0,
|
|
||||||
/* opcode: */ 3,
|
|
||||||
/* seq: */ 0xff, 0xff, 0, 0,
|
|
||||||
/* n_fds: */ 0xfe, 0xca, 0, 0,
|
|
||||||
}, pipewire.Header{
|
|
||||||
ID: pipewire.PW_ID_CORE,
|
|
||||||
Opcode: pipewire.PW_CORE_METHOD_PONG,
|
|
||||||
Size: 0xbed, Sequence: 0xffff, FileCount: 0xcafe,
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"PW_CORE_METHOD_ERROR", []byte{
|
|
||||||
/* Id: */ 0, 0, 0, 0,
|
|
||||||
/* size: */ 0xad, 0xb, 0,
|
|
||||||
/* opcode: */ 4,
|
|
||||||
/* seq: */ 0xfe, 0xfe, 0, 0,
|
|
||||||
/* n_fds: */ 0xfe, 0xca, 0, 0,
|
|
||||||
}, pipewire.Header{
|
|
||||||
ID: pipewire.PW_ID_CORE,
|
|
||||||
Opcode: pipewire.PW_CORE_METHOD_ERROR,
|
|
||||||
Size: 0xbad, Sequence: 0xfefe, FileCount: 0xcafe,
|
|
||||||
}, nil},
|
|
||||||
}.run(t)
|
|
||||||
|
|
||||||
t.Run("size range", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
if _, err := (&pipewire.Header{Size: 0xff000000}).MarshalBinary(); !reflect.DeepEqual(err, pipewire.ErrSizeRange) {
|
|
||||||
t.Errorf("UnmarshalBinary: error = %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("header size", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
if err := (*pipewire.Header)(nil).UnmarshalBinary(nil); !reflect.DeepEqual(err, pipewire.ErrBadHeader) {
|
|
||||||
t.Errorf("UnmarshalBinary: error = %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
252
internal/pipewire/pipewire-helper.c
Normal file
252
internal/pipewire/pipewire-helper.c
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
#include "pipewire-helper.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
|
||||||
|
#include <spa/utils/result.h>
|
||||||
|
#include <spa/utils/string.h>
|
||||||
|
#include <spa/utils/ansi.h>
|
||||||
|
#include <spa/debug/pod.h>
|
||||||
|
#include <spa/debug/format.h>
|
||||||
|
#include <spa/debug/types.h>
|
||||||
|
#include <spa/debug/file.h>
|
||||||
|
|
||||||
|
#include <pipewire/pipewire.h>
|
||||||
|
#include <pipewire/extensions/security-context.h>
|
||||||
|
|
||||||
|
/* contains most of the state used by hakurei_pw_security_context_bind,
|
||||||
|
* not ideal, but it is too painful to separate state with the abysmal
|
||||||
|
* API of pipewire */
|
||||||
|
struct hakurei_pw_security_context_state {
|
||||||
|
struct pw_main_loop *loop;
|
||||||
|
struct pw_context *context;
|
||||||
|
|
||||||
|
struct pw_core *core;
|
||||||
|
struct spa_hook core_listener;
|
||||||
|
|
||||||
|
struct pw_registry *registry;
|
||||||
|
struct spa_hook registry_listener;
|
||||||
|
|
||||||
|
struct pw_properties *props;
|
||||||
|
|
||||||
|
struct pw_security_context *sec;
|
||||||
|
|
||||||
|
int pending_create;
|
||||||
|
int create_result;
|
||||||
|
int pending;
|
||||||
|
int done;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* for field global of registry_events */
|
||||||
|
static void registry_event_global(
|
||||||
|
void *data, uint32_t id,
|
||||||
|
uint32_t permissions, const char *type, uint32_t version,
|
||||||
|
const struct spa_dict *props) {
|
||||||
|
struct hakurei_pw_security_context_state *state = data;
|
||||||
|
|
||||||
|
if (spa_streq(type, PW_TYPE_INTERFACE_SecurityContext))
|
||||||
|
state->sec = pw_registry_bind(state->registry, id, type, version, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* for field global_remove of registry_events */
|
||||||
|
static void registry_event_global_remove(void *data, uint32_t id) {} /* no-op */
|
||||||
|
|
||||||
|
static const struct pw_registry_events registry_events = {
|
||||||
|
PW_VERSION_REGISTRY_EVENTS,
|
||||||
|
.global = registry_event_global,
|
||||||
|
.global_remove = registry_event_global_remove,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* for field error of core_events */
|
||||||
|
static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message) {
|
||||||
|
struct hakurei_pw_security_context_state *state = data;
|
||||||
|
|
||||||
|
pw_log_error("error id:%u seq:%d res:%d (%s): %s",
|
||||||
|
id, seq, res, spa_strerror(res), message);
|
||||||
|
|
||||||
|
if (seq == SPA_RESULT_ASYNC_SEQ(state->pending_create))
|
||||||
|
state->create_result = res;
|
||||||
|
|
||||||
|
if (id == PW_ID_CORE && res == -EPIPE) {
|
||||||
|
state->done = true;
|
||||||
|
pw_main_loop_quit(state->loop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct pw_core_events core_events = {
|
||||||
|
PW_VERSION_CORE_EVENTS,
|
||||||
|
.error = on_core_error,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* for field done of stack allocated core_events in roundtrip */
|
||||||
|
static void core_event_done(void *data, uint32_t id, int seq) {
|
||||||
|
struct hakurei_pw_security_context_state *state = data;
|
||||||
|
if (id == PW_ID_CORE && seq == state->pending) {
|
||||||
|
state->done = true;
|
||||||
|
pw_main_loop_quit(state->loop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void roundtrip(struct hakurei_pw_security_context_state *state) {
|
||||||
|
struct spa_hook core_listener;
|
||||||
|
static const struct pw_core_events core_events = {
|
||||||
|
PW_VERSION_CORE_EVENTS,
|
||||||
|
.done = core_event_done,
|
||||||
|
};
|
||||||
|
spa_zero(core_listener);
|
||||||
|
pw_core_add_listener(state->core, &core_listener, &core_events, state);
|
||||||
|
|
||||||
|
state->done = false;
|
||||||
|
state->pending = pw_core_sync(state->core, PW_ID_CORE, 0);
|
||||||
|
|
||||||
|
while (!state->done)
|
||||||
|
pw_main_loop_run(state->loop);
|
||||||
|
|
||||||
|
spa_hook_remove(&core_listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
hakurei_pipewire_res hakurei_pw_security_context_bind(
|
||||||
|
char *socket_path,
|
||||||
|
char *remote_path,
|
||||||
|
int close_fd) {
|
||||||
|
hakurei_pipewire_res res = HAKUREI_PIPEWIRE_SUCCESS; /* see pipewire.go for handling */
|
||||||
|
|
||||||
|
struct hakurei_pw_security_context_state state = {0};
|
||||||
|
struct pw_loop *l;
|
||||||
|
struct spa_error_location loc;
|
||||||
|
int listen_fd;
|
||||||
|
struct sockaddr_un sockaddr = {0};
|
||||||
|
|
||||||
|
/* stack allocated because pw_deinit is always called before returning,
|
||||||
|
* in the implementation it actually does nothing with these addresses
|
||||||
|
* and I have no idea why it would even need these, still it is safe to
|
||||||
|
* do this to not risk a future version of pipewire clobbering strings */
|
||||||
|
int fake_argc = 1;
|
||||||
|
char *fake_argv[] = {"hakurei", NULL};
|
||||||
|
/* this makes multiple getenv calls, caller must ensure to NOT setenv
|
||||||
|
* before this function returns */
|
||||||
|
pw_init(&fake_argc, (char ***)&fake_argv);
|
||||||
|
|
||||||
|
/* as far as I can tell, setting engine to "org.flatpak" gets special
|
||||||
|
* treatment, and should never be used here because the .flatpak-info
|
||||||
|
* hack is vulnerable to a confused deputy attack */
|
||||||
|
state.props = pw_properties_new(
|
||||||
|
PW_KEY_SEC_ENGINE, "app.hakurei",
|
||||||
|
PW_KEY_ACCESS, "restricted",
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
/* this is unfortunately required to do ANYTHING with pipewire */
|
||||||
|
state.loop = pw_main_loop_new(NULL);
|
||||||
|
if (state.loop == NULL) {
|
||||||
|
res = HAKUREI_PIPEWIRE_MAINLOOP;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
l = pw_main_loop_get_loop(state.loop);
|
||||||
|
|
||||||
|
/* boilerplate from src/tools/pw-container.c */
|
||||||
|
state.context = pw_context_new(l, NULL, 0);
|
||||||
|
if (state.context == NULL) {
|
||||||
|
res = HAKUREI_PIPEWIRE_CTX;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* boilerplate from src/tools/pw-container.c;
|
||||||
|
* this does not unsetenv, so special handling is not required
|
||||||
|
* unlike for libwayland-client */
|
||||||
|
state.core = pw_context_connect(
|
||||||
|
state.context,
|
||||||
|
pw_properties_new(
|
||||||
|
PW_KEY_REMOTE_INTENTION, "manager",
|
||||||
|
PW_KEY_REMOTE_NAME, remote_path,
|
||||||
|
NULL),
|
||||||
|
0);
|
||||||
|
if (state.core == NULL) {
|
||||||
|
res = HAKUREI_PIPEWIRE_CONNECT;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* obtains the security context */
|
||||||
|
pw_core_add_listener(state.core, &state.core_listener, &core_events, &state);
|
||||||
|
state.registry = pw_core_get_registry(state.core, PW_VERSION_REGISTRY, 0);
|
||||||
|
if (state.registry == NULL) {
|
||||||
|
res = HAKUREI_PIPEWIRE_REGISTRY;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
/* undocumented, this ends up calling registry_method_marshal_add_listener,
|
||||||
|
* which is hard-coded to return 0, note that the function pointer this calls
|
||||||
|
* is uninitialised for some pw_registry objects so if you are using this code
|
||||||
|
* as an example you must keep that in mind */
|
||||||
|
pw_registry_add_listener(state.registry, &state.registry_listener, ®istry_events, &state);
|
||||||
|
roundtrip(&state);
|
||||||
|
if (state.sec == NULL) {
|
||||||
|
res = HAKUREI_PIPEWIRE_NOT_AVAIL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* socket to attach security context */
|
||||||
|
listen_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
if (listen_fd < 0) {
|
||||||
|
res = HAKUREI_PIPEWIRE_SOCKET;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* similar to libwayland, pipewire requires bind and listen to be called
|
||||||
|
* on the socket before being passed to pw_security_context_create */
|
||||||
|
sockaddr.sun_family = AF_UNIX;
|
||||||
|
snprintf(sockaddr.sun_path, sizeof(sockaddr.sun_path), "%s", socket_path);
|
||||||
|
if (bind(listen_fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) != 0) {
|
||||||
|
res = HAKUREI_PIPEWIRE_BIND;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (listen(listen_fd, 0) != 0) {
|
||||||
|
res = HAKUREI_PIPEWIRE_LISTEN;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* attach security context to socket */
|
||||||
|
state.create_result = 0;
|
||||||
|
state.pending_create = pw_security_context_create(state.sec, listen_fd, close_fd, &state.props->dict);
|
||||||
|
if (SPA_RESULT_IS_ASYNC(state.pending_create)) {
|
||||||
|
pw_log_debug("create: %d", state.pending_create);
|
||||||
|
roundtrip(&state);
|
||||||
|
}
|
||||||
|
pw_log_debug("create result: %d", state.create_result);
|
||||||
|
if (state.create_result < 0) {
|
||||||
|
/* spa_strerror */
|
||||||
|
if (SPA_RESULT_IS_ASYNC(-state.create_result))
|
||||||
|
errno = EINPROGRESS;
|
||||||
|
else
|
||||||
|
errno = -state.create_result;
|
||||||
|
|
||||||
|
res = HAKUREI_PIPEWIRE_ATTACH;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
if (listen_fd >= 0)
|
||||||
|
close(listen_fd);
|
||||||
|
if (state.sec != NULL)
|
||||||
|
pw_proxy_destroy((struct pw_proxy *)state.sec);
|
||||||
|
if (state.registry != NULL)
|
||||||
|
pw_proxy_destroy((struct pw_proxy *)state.registry);
|
||||||
|
if (state.core != NULL) {
|
||||||
|
/* these happen after core is checked non-NULL and always succeeds */
|
||||||
|
spa_hook_remove(&state.registry_listener);
|
||||||
|
spa_hook_remove(&state.core_listener);
|
||||||
|
|
||||||
|
pw_core_disconnect(state.core);
|
||||||
|
}
|
||||||
|
if (state.context != NULL)
|
||||||
|
pw_context_destroy(state.context);
|
||||||
|
if (state.loop != NULL)
|
||||||
|
pw_main_loop_destroy(state.loop);
|
||||||
|
pw_properties_free(state.props);
|
||||||
|
pw_deinit();
|
||||||
|
|
||||||
|
free((void *)socket_path);
|
||||||
|
if (remote_path != NULL)
|
||||||
|
free((void *)remote_path);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
40
internal/pipewire/pipewire-helper.h
Normal file
40
internal/pipewire/pipewire-helper.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#include <stdbool.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
HAKUREI_PIPEWIRE_SUCCESS,
|
||||||
|
/* pw_main_loop_new failed, errno */
|
||||||
|
HAKUREI_PIPEWIRE_MAINLOOP,
|
||||||
|
/* pw_context_new failed, errno */
|
||||||
|
HAKUREI_PIPEWIRE_CTX,
|
||||||
|
/* pw_context_connect failed, errno */
|
||||||
|
HAKUREI_PIPEWIRE_CONNECT,
|
||||||
|
/* pw_core_get_registry failed */
|
||||||
|
HAKUREI_PIPEWIRE_REGISTRY,
|
||||||
|
/* no security context object found */
|
||||||
|
HAKUREI_PIPEWIRE_NOT_AVAIL,
|
||||||
|
/* socket failed, errno */
|
||||||
|
HAKUREI_PIPEWIRE_SOCKET,
|
||||||
|
/* bind failed, errno */
|
||||||
|
HAKUREI_PIPEWIRE_BIND,
|
||||||
|
/* listen failed, errno */
|
||||||
|
HAKUREI_PIPEWIRE_LISTEN,
|
||||||
|
/* pw_security_context_create failed, translated errno */
|
||||||
|
HAKUREI_PIPEWIRE_ATTACH,
|
||||||
|
|
||||||
|
/* ensure pathname failed, implemented in conn.go */
|
||||||
|
HAKUREI_PIPEWIRE_CREAT,
|
||||||
|
/* cleanup failed, implemented in conn.go */
|
||||||
|
HAKUREI_PIPEWIRE_CLEANUP,
|
||||||
|
} hakurei_pipewire_res;
|
||||||
|
|
||||||
|
hakurei_pipewire_res hakurei_pw_security_context_bind(
|
||||||
|
char *socket_path,
|
||||||
|
char *remote_path,
|
||||||
|
int close_fd);
|
||||||
|
|
||||||
|
/* returns whether the specified size fits in the sun_path field of sockaddr_un */
|
||||||
|
static inline bool hakurei_pw_is_valid_size_sun_path(size_t sz) {
|
||||||
|
struct sockaddr_un sockaddr;
|
||||||
|
return sz <= sizeof(sockaddr.sun_path);
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,641 +0,0 @@
|
|||||||
package pipewire
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
"math"
|
|
||||||
"reflect"
|
|
||||||
"slices"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
// A Word is a 32-bit unsigned integer.
|
|
||||||
//
|
|
||||||
// Values internal to a message appear to always be aligned to 32-bit boundary.
|
|
||||||
Word = uint32
|
|
||||||
|
|
||||||
// A Bool is a boolean value representing SPA_TYPE_Bool.
|
|
||||||
Bool = bool
|
|
||||||
// An Id is an enumerated value representing SPA_TYPE_Id.
|
|
||||||
Id = Word
|
|
||||||
// An Int is a signed integer value representing SPA_TYPE_Int.
|
|
||||||
Int = int32
|
|
||||||
// A Long is a signed integer value representing SPA_TYPE_Long.
|
|
||||||
Long = int64
|
|
||||||
// A Float is a floating point value representing SPA_TYPE_Float.
|
|
||||||
Float = float32
|
|
||||||
// A Double is a floating point value representing SPA_TYPE_Double.
|
|
||||||
Double = float64
|
|
||||||
// A String is a string value representing SPA_TYPE_String.
|
|
||||||
String = string
|
|
||||||
// Bytes is a byte slice representing SPA_TYPE_Bytes.
|
|
||||||
Bytes = []byte
|
|
||||||
|
|
||||||
// A Fd is a signed integer value representing SPA_TYPE_Fd.
|
|
||||||
Fd Long
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// SizeAlign is the boundary which POD starts are always aligned to.
|
|
||||||
SizeAlign = 8
|
|
||||||
|
|
||||||
// SizeSPrefix is the fixed, unpadded size of the fixed-size prefix encoding POD wire size.
|
|
||||||
SizeSPrefix = 4
|
|
||||||
// SizeTPrefix is the fixed, unpadded size of the fixed-size prefix encoding POD value type.
|
|
||||||
SizeTPrefix = 4
|
|
||||||
// SizePrefix is the fixed, unpadded size of the fixed-size POD prefix.
|
|
||||||
SizePrefix = SizeSPrefix + SizeTPrefix
|
|
||||||
|
|
||||||
// SizeId is the fixed, unpadded size of a [SPA_TYPE_Id] value.
|
|
||||||
SizeId Word = 4
|
|
||||||
// SizeInt is the fixed, unpadded size of a [SPA_TYPE_Int] value.
|
|
||||||
SizeInt Word = 4
|
|
||||||
// SizeLong is the fixed, unpadded size of a [SPA_TYPE_Long] value.
|
|
||||||
SizeLong Word = 8
|
|
||||||
|
|
||||||
// SizeFd is the fixed, unpadded size of a [SPA_TYPE_Fd] value.
|
|
||||||
SizeFd = SizeLong
|
|
||||||
)
|
|
||||||
|
|
||||||
// A KnownSize value has known POD encoded size before serialisation.
|
|
||||||
type KnownSize interface {
|
|
||||||
// Size returns the POD encoded size of the receiver.
|
|
||||||
Size() Word
|
|
||||||
}
|
|
||||||
|
|
||||||
// PaddingSize returns the padding size corresponding to a wire size.
|
|
||||||
func PaddingSize[W Word | int](wireSize W) W { return (SizeAlign - (wireSize)%SizeAlign) % SizeAlign }
|
|
||||||
|
|
||||||
// PaddedSize returns the padded size corresponding to a wire size.
|
|
||||||
func PaddedSize[W Word | int](wireSize W) W { return wireSize + PaddingSize(wireSize) }
|
|
||||||
|
|
||||||
// Size returns prefixed and padded size corresponding to a wire size.
|
|
||||||
func Size[W Word | int](wireSize W) W { return SizePrefix + PaddedSize(wireSize) }
|
|
||||||
|
|
||||||
// SizeString returns prefixed and padded size corresponding to a string.
|
|
||||||
func SizeString[W Word | int](s string) W { return Size(W(len(s)) + 1) }
|
|
||||||
|
|
||||||
// PODMarshaler is the interface implemented by an object that can
|
|
||||||
// marshal itself into PipeWire POD encoding.
|
|
||||||
type PODMarshaler interface {
|
|
||||||
// MarshalPOD encodes the receiver into PipeWire POD encoding,
|
|
||||||
// appends it to data, and returns the result.
|
|
||||||
MarshalPOD(data []byte) ([]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// An UnsupportedTypeError is returned by [Marshal] when attempting
|
|
||||||
// to encode an unsupported value type.
|
|
||||||
type UnsupportedTypeError struct{ Type reflect.Type }
|
|
||||||
|
|
||||||
func (e *UnsupportedTypeError) Error() string { return "unsupported type " + e.Type.String() }
|
|
||||||
|
|
||||||
// An UnsupportedSizeError is returned by [Marshal] when attempting
|
|
||||||
// to encode a value with its encoded size exceeding what could be
|
|
||||||
// represented by the format.
|
|
||||||
type UnsupportedSizeError int
|
|
||||||
|
|
||||||
func (e UnsupportedSizeError) Error() string { return "size " + strconv.Itoa(int(e)) + " out of range" }
|
|
||||||
|
|
||||||
// Marshal returns the PipeWire POD encoding of v.
|
|
||||||
func Marshal(v any) ([]byte, error) {
|
|
||||||
var data []byte
|
|
||||||
if s, ok := v.(KnownSize); ok {
|
|
||||||
data = make([]byte, 0, s.Size())
|
|
||||||
}
|
|
||||||
return MarshalAppend(data, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalAppend appends the PipeWire POD encoding of v to data.
|
|
||||||
func MarshalAppend(data []byte, v any) ([]byte, error) {
|
|
||||||
return marshalValueAppend(data, reflect.ValueOf(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
// appendInner calls f and handles size prefix and padding around the appended data.
|
|
||||||
// f must only append to data.
|
|
||||||
func appendInner(data []byte, f func(data []byte) ([]byte, error)) ([]byte, error) {
|
|
||||||
data = append(data, make([]byte, SizeSPrefix)...)
|
|
||||||
|
|
||||||
rData, err := f(data)
|
|
||||||
if err != nil {
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
|
|
||||||
size := len(rData) - len(data) + SizeSPrefix
|
|
||||||
// compensated for size and type prefix
|
|
||||||
wireSize := size - SizePrefix
|
|
||||||
if wireSize > math.MaxUint32 {
|
|
||||||
return data, UnsupportedSizeError(wireSize)
|
|
||||||
}
|
|
||||||
binary.NativeEndian.PutUint32(rData[len(data)-SizeSPrefix:len(data)], Word(wireSize))
|
|
||||||
rData = append(rData, make([]byte, PaddingSize(size))...)
|
|
||||||
|
|
||||||
return rData, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// marshalValueAppendRaw implements [MarshalAppend] on [reflect.Value].
|
|
||||||
func marshalValueAppend(data []byte, v reflect.Value) ([]byte, error) {
|
|
||||||
if v.CanInterface() && (v.Kind() != reflect.Pointer || !v.IsNil()) {
|
|
||||||
if m, ok := v.Interface().(PODMarshaler); ok {
|
|
||||||
var err error
|
|
||||||
data, err = m.MarshalPOD(data)
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return appendInner(data, func(data []byte) ([]byte, error) { return marshalValueAppendRaw(data, v) })
|
|
||||||
}
|
|
||||||
|
|
||||||
// marshalValueAppendRaw implements [MarshalAppend] on [reflect.Value] without the size prefix.
|
|
||||||
func marshalValueAppendRaw(data []byte, v reflect.Value) ([]byte, error) {
|
|
||||||
if v.CanInterface() {
|
|
||||||
switch c := v.Interface().(type) {
|
|
||||||
case Fd:
|
|
||||||
data = SPA_TYPE_Fd.append(data)
|
|
||||||
data = binary.NativeEndian.AppendUint64(data, uint64(c))
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v.Kind() {
|
|
||||||
case reflect.Uint32:
|
|
||||||
data = SPA_TYPE_Id.append(data)
|
|
||||||
data = binary.NativeEndian.AppendUint32(data, Word(v.Uint()))
|
|
||||||
return data, nil
|
|
||||||
|
|
||||||
case reflect.Int32:
|
|
||||||
data = SPA_TYPE_Int.append(data)
|
|
||||||
data = binary.NativeEndian.AppendUint32(data, Word(v.Int()))
|
|
||||||
return data, nil
|
|
||||||
|
|
||||||
case reflect.Int64:
|
|
||||||
data = SPA_TYPE_Long.append(data)
|
|
||||||
data = binary.NativeEndian.AppendUint64(data, uint64(v.Int()))
|
|
||||||
return data, nil
|
|
||||||
|
|
||||||
case reflect.Struct:
|
|
||||||
data = SPA_TYPE_Struct.append(data)
|
|
||||||
var err error
|
|
||||||
for i := 0; i < v.NumField(); i++ {
|
|
||||||
data, err = marshalValueAppend(data, v.Field(i))
|
|
||||||
if err != nil {
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return data, nil
|
|
||||||
|
|
||||||
case reflect.Pointer:
|
|
||||||
if v.IsNil() {
|
|
||||||
data = SPA_TYPE_None.append(data)
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
return marshalValueAppendRaw(data, v.Elem())
|
|
||||||
|
|
||||||
case reflect.String:
|
|
||||||
data = SPA_TYPE_String.append(data)
|
|
||||||
data = append(data, []byte(v.String())...)
|
|
||||||
data = append(data, 0)
|
|
||||||
return data, nil
|
|
||||||
|
|
||||||
default:
|
|
||||||
return data, &UnsupportedTypeError{v.Type()}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PODUnmarshaler is the interface implemented by an object that can
|
|
||||||
// unmarshal a PipeWire POD encoding representation of itself.
|
|
||||||
type PODUnmarshaler interface {
|
|
||||||
// UnmarshalPOD must be able to decode the form generated by MarshalPOD.
|
|
||||||
// UnmarshalPOD must copy the data if it wishes to retain the data
|
|
||||||
// after returning.
|
|
||||||
UnmarshalPOD(data []byte) (Word, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// An InvalidUnmarshalError describes an invalid argument passed to [Unmarshal].
|
|
||||||
// (The argument to [Unmarshal] must be a non-nil pointer.)
|
|
||||||
type InvalidUnmarshalError struct{ Type reflect.Type }
|
|
||||||
|
|
||||||
func (e *InvalidUnmarshalError) Error() string {
|
|
||||||
if e.Type == nil {
|
|
||||||
return "attempting to unmarshal to nil"
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Type.Kind() != reflect.Pointer {
|
|
||||||
return "attempting to unmarshal to non-pointer type " + e.Type.String()
|
|
||||||
}
|
|
||||||
return "attempting to unmarshal to nil " + e.Type.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnexpectedEOFError describes an unexpected EOF encountered in the middle of decoding POD data.
|
|
||||||
type UnexpectedEOFError uintptr
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ErrEOFPrefix is returned when unexpectedly encountering EOF
|
|
||||||
// decoding the fixed-size POD prefix.
|
|
||||||
ErrEOFPrefix UnexpectedEOFError = iota
|
|
||||||
// ErrEOFData is returned when unexpectedly encountering EOF
|
|
||||||
// establishing POD data bounds.
|
|
||||||
ErrEOFData
|
|
||||||
// ErrEOFDataString is returned when unexpectedly encountering EOF
|
|
||||||
// establishing POD [String] bounds.
|
|
||||||
ErrEOFDataString
|
|
||||||
)
|
|
||||||
|
|
||||||
func (UnexpectedEOFError) Unwrap() error { return io.ErrUnexpectedEOF }
|
|
||||||
func (e UnexpectedEOFError) Error() string {
|
|
||||||
var suffix string
|
|
||||||
switch e {
|
|
||||||
case ErrEOFPrefix:
|
|
||||||
suffix = "decoding fixed-size POD prefix"
|
|
||||||
case ErrEOFData:
|
|
||||||
suffix = "establishing POD data bounds"
|
|
||||||
case ErrEOFDataString:
|
|
||||||
suffix = "establishing POD String bounds"
|
|
||||||
|
|
||||||
default:
|
|
||||||
return "unexpected EOF"
|
|
||||||
}
|
|
||||||
|
|
||||||
return "unexpected EOF " + suffix
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal parses the PipeWire POD encoded data and stores the result
|
|
||||||
// in the value pointed to by v. If v is nil or not a pointer,
|
|
||||||
// Unmarshal returns an [InvalidUnmarshalError].
|
|
||||||
func Unmarshal(data []byte, v any) error {
|
|
||||||
if n, err := UnmarshalNext(data, v); err != nil {
|
|
||||||
return err
|
|
||||||
} else if len(data) > int(n) {
|
|
||||||
return TrailingGarbageError(data[int(n):])
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalNext implements [Unmarshal] but returns the size of message decoded
|
|
||||||
// and skips the final trailing garbage check.
|
|
||||||
func UnmarshalNext(data []byte, v any) (size Word, err error) {
|
|
||||||
rv := reflect.ValueOf(v)
|
|
||||||
if rv.Kind() != reflect.Pointer || rv.IsNil() {
|
|
||||||
return 0, &InvalidUnmarshalError{reflect.TypeOf(v)}
|
|
||||||
}
|
|
||||||
err = unmarshalValue(data, rv.Elem(), &size)
|
|
||||||
// prefix and padding size
|
|
||||||
size = Size(size)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalSetError describes a value that cannot be set during [Unmarshal].
|
|
||||||
// This is likely an unexported struct field.
|
|
||||||
type UnmarshalSetError struct{ Type reflect.Type }
|
|
||||||
|
|
||||||
func (u *UnmarshalSetError) Error() string { return "cannot set " + u.Type.String() }
|
|
||||||
|
|
||||||
// A TrailingGarbageError describes extra bytes after decoding
|
|
||||||
// has completed during [Unmarshal].
|
|
||||||
type TrailingGarbageError []byte
|
|
||||||
|
|
||||||
func (e TrailingGarbageError) Error() string {
|
|
||||||
if len(e) < SizePrefix {
|
|
||||||
return "got " + strconv.Itoa(len(e)) + " bytes of trailing garbage"
|
|
||||||
}
|
|
||||||
return "data has extra values starting with " + SPAKind(binary.NativeEndian.Uint32(e[SizeSPrefix:])).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// A StringTerminationError describes an incorrectly terminated string
|
|
||||||
// encountered during [Unmarshal].
|
|
||||||
type StringTerminationError byte
|
|
||||||
|
|
||||||
func (e StringTerminationError) Error() string {
|
|
||||||
return "got byte " + strconv.Itoa(int(e)) + " instead of NUL"
|
|
||||||
}
|
|
||||||
|
|
||||||
// unmarshalValue implements [Unmarshal] on [reflect.Value] without compensating for prefix and padding size.
|
|
||||||
func unmarshalValue(data []byte, v reflect.Value, wireSizeP *Word) error {
|
|
||||||
if !v.CanSet() {
|
|
||||||
return &UnmarshalSetError{v.Type()}
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.CanInterface() {
|
|
||||||
if v.Kind() == reflect.Pointer {
|
|
||||||
v.Set(reflect.New(v.Type().Elem()))
|
|
||||||
}
|
|
||||||
|
|
||||||
if u, ok := v.Interface().(PODUnmarshaler); ok {
|
|
||||||
var err error
|
|
||||||
*wireSizeP, err = u.UnmarshalPOD(data)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v.Interface().(type) {
|
|
||||||
case Fd:
|
|
||||||
*wireSizeP = SizeFd
|
|
||||||
if err := unmarshalCheckTypeBounds(&data, SPA_TYPE_Fd, wireSizeP); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
v.SetInt(int64(binary.NativeEndian.Uint64(data)))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v.Kind() {
|
|
||||||
case reflect.Uint32:
|
|
||||||
*wireSizeP = SizeId
|
|
||||||
if err := unmarshalCheckTypeBounds(&data, SPA_TYPE_Id, wireSizeP); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
v.SetUint(uint64(binary.NativeEndian.Uint32(data)))
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case reflect.Int32:
|
|
||||||
*wireSizeP = SizeInt
|
|
||||||
if err := unmarshalCheckTypeBounds(&data, SPA_TYPE_Int, wireSizeP); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
v.SetInt(int64(binary.NativeEndian.Uint32(data)))
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case reflect.Int64:
|
|
||||||
*wireSizeP = SizeLong
|
|
||||||
if err := unmarshalCheckTypeBounds(&data, SPA_TYPE_Long, wireSizeP); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
v.SetInt(int64(binary.NativeEndian.Uint64(data)))
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case reflect.Struct:
|
|
||||||
*wireSizeP = 0
|
|
||||||
if err := unmarshalCheckTypeBounds(&data, SPA_TYPE_Struct, wireSizeP); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var fieldWireSize Word
|
|
||||||
for i := 0; i < v.NumField(); i++ {
|
|
||||||
if err := unmarshalValue(data, v.Field(i), &fieldWireSize); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// bounds check completed in successful call to unmarshalValue
|
|
||||||
data = data[Size(fieldWireSize):]
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(data) != 0 {
|
|
||||||
return TrailingGarbageError(data)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case reflect.Pointer:
|
|
||||||
if ok, err := unmarshalHandleNone(&data, wireSizeP); err != nil {
|
|
||||||
return err
|
|
||||||
} else if ok {
|
|
||||||
v.SetZero()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
v.Set(reflect.New(v.Type().Elem()))
|
|
||||||
return unmarshalValue(data, v.Elem(), wireSizeP)
|
|
||||||
|
|
||||||
case reflect.String:
|
|
||||||
*wireSizeP = 0
|
|
||||||
if err := unmarshalCheckTypeBounds(&data, SPA_TYPE_String, wireSizeP); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// string size, one extra NUL byte
|
|
||||||
size := int(*wireSizeP)
|
|
||||||
if len(data) < size {
|
|
||||||
return ErrEOFDataString
|
|
||||||
}
|
|
||||||
|
|
||||||
// the serialised strings still include NUL termination
|
|
||||||
if data[size-1] != 0 {
|
|
||||||
return StringTerminationError(data[size-1])
|
|
||||||
}
|
|
||||||
|
|
||||||
v.SetString(string(data[:size-1]))
|
|
||||||
return nil
|
|
||||||
|
|
||||||
default:
|
|
||||||
return &UnsupportedTypeError{v.Type()}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// unmarshalHandleNone establishes prefix bounds and, for a value of type [SPA_TYPE_None],
|
|
||||||
// validates its size and skips the header. This is for unmarshalling values that can be nil.
|
|
||||||
func unmarshalHandleNone(data *[]byte, wireSizeP *Word) (bool, error) {
|
|
||||||
if len(*data) < SizePrefix {
|
|
||||||
return false, ErrEOFPrefix
|
|
||||||
}
|
|
||||||
|
|
||||||
if SPAKind(binary.NativeEndian.Uint32((*data)[SizeSPrefix:])) != SPA_TYPE_None {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
*wireSizeP = 0
|
|
||||||
if err := unmarshalCheckTypeBounds(data, SPA_TYPE_None, wireSizeP); err != nil {
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(*data) != 0 {
|
|
||||||
return true, TrailingGarbageError(*data)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// An InconsistentSizeError describes an inconsistent size prefix encountered
|
|
||||||
// in data passed to [Unmarshal].
|
|
||||||
type InconsistentSizeError struct{ Prefix, Expect Word }
|
|
||||||
|
|
||||||
func (e InconsistentSizeError) Error() string {
|
|
||||||
return "prefix claims size " + strconv.Itoa(int(e.Prefix)) +
|
|
||||||
" for a " + strconv.Itoa(int(e.Expect)) + "-byte long segment"
|
|
||||||
}
|
|
||||||
|
|
||||||
// An UnexpectedTypeError describes an unexpected type encountered
|
|
||||||
// in data passed to [Unmarshal].
|
|
||||||
type UnexpectedTypeError struct{ Type, Expect SPAKind }
|
|
||||||
|
|
||||||
func (e UnexpectedTypeError) Error() string {
|
|
||||||
return "received " + e.Type.String() + " for a value of type " + e.Expect.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// unmarshalCheckTypeBounds performs bounds checks on data and validates the type and size prefixes.
|
|
||||||
// An expected size of zero skips further bounds checks.
|
|
||||||
func unmarshalCheckTypeBounds(data *[]byte, t SPAKind, sizeP *Word) error {
|
|
||||||
if len(*data) < SizePrefix {
|
|
||||||
return ErrEOFPrefix
|
|
||||||
}
|
|
||||||
|
|
||||||
wantSize := *sizeP
|
|
||||||
gotSize := binary.NativeEndian.Uint32(*data)
|
|
||||||
*sizeP = gotSize
|
|
||||||
|
|
||||||
if wantSize != 0 && gotSize != wantSize {
|
|
||||||
return InconsistentSizeError{gotSize, wantSize}
|
|
||||||
}
|
|
||||||
if len(*data)-SizePrefix < int(gotSize) {
|
|
||||||
return ErrEOFData
|
|
||||||
}
|
|
||||||
|
|
||||||
gotType := SPAKind(binary.NativeEndian.Uint32((*data)[SizeSPrefix:]))
|
|
||||||
if gotType != t {
|
|
||||||
return UnexpectedTypeError{gotType, t}
|
|
||||||
}
|
|
||||||
|
|
||||||
*data = (*data)[SizePrefix : gotSize+SizePrefix]
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// The Footer contains additional messages, not directed to
|
|
||||||
// the destination object defined by the Id field.
|
|
||||||
type Footer[P KnownSize] struct {
|
|
||||||
// The footer opcode.
|
|
||||||
Opcode Id `json:"opcode"`
|
|
||||||
// The footer payload struct.
|
|
||||||
Payload P `json:"payload"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size satisfies [KnownSize] with a usually compile-time known value.
|
|
||||||
func (f *Footer[P]) Size() Word {
|
|
||||||
return SizePrefix +
|
|
||||||
Size(SizeId) +
|
|
||||||
f.Payload.Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalBinary satisfies [encoding.BinaryMarshaler] via [Marshal].
|
|
||||||
func (f *Footer[T]) MarshalBinary() ([]byte, error) { return Marshal(f) }
|
|
||||||
|
|
||||||
// UnmarshalBinary satisfies [encoding.BinaryUnmarshaler] via [Unmarshal].
|
|
||||||
func (f *Footer[T]) UnmarshalBinary(data []byte) error { return Unmarshal(data, f) }
|
|
||||||
|
|
||||||
// SPADictItem represents spa_dict_item.
|
|
||||||
type SPADictItem struct {
|
|
||||||
// Dot-separated string.
|
|
||||||
Key string `json:"key"`
|
|
||||||
// Arbitrary string.
|
|
||||||
//
|
|
||||||
// Integer values are represented in base 10,
|
|
||||||
// boolean values are represented as "true" or "false".
|
|
||||||
Value string `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SPADict represents spa_dict.
|
|
||||||
type SPADict []SPADictItem
|
|
||||||
|
|
||||||
// Size satisfies [KnownSize] with a value computed at runtime.
|
|
||||||
func (d *SPADict) Size() Word {
|
|
||||||
if d == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// struct prefix, NItems value
|
|
||||||
size := SizePrefix + int(Size(SizeInt))
|
|
||||||
for i := range *d {
|
|
||||||
size += SizeString[int]((*d)[i].Key)
|
|
||||||
size += SizeString[int]((*d)[i].Value)
|
|
||||||
}
|
|
||||||
return Word(size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalPOD satisfies [PODMarshaler] as [SPADict] violates the POD type system.
|
|
||||||
func (d *SPADict) MarshalPOD(data []byte) ([]byte, error) {
|
|
||||||
return appendInner(data, func(dataPrefix []byte) (data []byte, err error) {
|
|
||||||
data = SPA_TYPE_Struct.append(dataPrefix)
|
|
||||||
if data, err = MarshalAppend(data, Int(len(*d))); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for i := range *d {
|
|
||||||
if data, err = MarshalAppend(data, (*d)[i].Key); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if data, err = MarshalAppend(data, (*d)[i].Value); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalPOD satisfies [PODUnmarshaler] as [SPADict] violates the POD type system.
|
|
||||||
func (d *SPADict) UnmarshalPOD(data []byte) (Word, error) {
|
|
||||||
var wireSize Word
|
|
||||||
if err := unmarshalCheckTypeBounds(&data, SPA_TYPE_Struct, &wireSize); err != nil {
|
|
||||||
return wireSize, err
|
|
||||||
}
|
|
||||||
// bounds check completed in successful call to unmarshalCheckTypeBounds
|
|
||||||
data = data[:wireSize]
|
|
||||||
|
|
||||||
var count Int
|
|
||||||
if size, err := UnmarshalNext(data, &count); err != nil {
|
|
||||||
return wireSize, err
|
|
||||||
} else {
|
|
||||||
// bounds check completed in successful call to Unmarshal
|
|
||||||
data = data[size:]
|
|
||||||
}
|
|
||||||
|
|
||||||
*d = make([]SPADictItem, count)
|
|
||||||
for i := range *d {
|
|
||||||
if size, err := UnmarshalNext(data, &(*d)[i].Key); err != nil {
|
|
||||||
return wireSize, err
|
|
||||||
} else {
|
|
||||||
// bounds check completed in successful call to Unmarshal
|
|
||||||
data = data[size:]
|
|
||||||
}
|
|
||||||
if size, err := UnmarshalNext(data, &(*d)[i].Value); err != nil {
|
|
||||||
return wireSize, err
|
|
||||||
} else {
|
|
||||||
// bounds check completed in successful call to Unmarshal
|
|
||||||
data = data[size:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(data) != 0 {
|
|
||||||
return wireSize, TrailingGarbageError(data)
|
|
||||||
}
|
|
||||||
return wireSize, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Message is a value that can be transmitted as a message over PipeWire protocol native.
|
|
||||||
type Message interface {
|
|
||||||
// Opcode returns the opcode of this message.
|
|
||||||
Opcode() byte
|
|
||||||
// FileCount returns the number of files associated with this message.
|
|
||||||
FileCount() Int
|
|
||||||
|
|
||||||
KnownSize
|
|
||||||
}
|
|
||||||
|
|
||||||
// A MessageEncoder provides methods for encoding a [Message].
|
|
||||||
type MessageEncoder struct{ Message }
|
|
||||||
|
|
||||||
// SizeMessage returns the size of Message transmitted over protocol native.
|
|
||||||
func (m MessageEncoder) SizeMessage(footer KnownSize) (size Word) {
|
|
||||||
size = SizeHeader + m.Message.Size()
|
|
||||||
if footer != nil {
|
|
||||||
size += footer.Size()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppendMessage appends the protocol native encoding of Message to dst and returns the appended slice.
|
|
||||||
func (m MessageEncoder) AppendMessage(dst []byte, Id, sequence Int, footer KnownSize) (data []byte, err error) {
|
|
||||||
size := m.SizeMessage(footer)
|
|
||||||
if size&^SizeMax != 0 {
|
|
||||||
return dst, ErrSizeRange
|
|
||||||
}
|
|
||||||
|
|
||||||
data = slices.Grow(dst, int(size))
|
|
||||||
data = (&Header{
|
|
||||||
ID: Id,
|
|
||||||
Opcode: m.Message.Opcode(),
|
|
||||||
Size: size - SizeHeader,
|
|
||||||
Sequence: sequence,
|
|
||||||
FileCount: m.Message.FileCount(),
|
|
||||||
}).append(data)
|
|
||||||
data, err = MarshalAppend(data, m.Message)
|
|
||||||
if err == nil && footer != nil {
|
|
||||||
data, err = MarshalAppend(data, footer)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,226 +0,0 @@
|
|||||||
package pipewire_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding"
|
|
||||||
"encoding/gob"
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"hakurei.app/internal/pipewire"
|
|
||||||
)
|
|
||||||
|
|
||||||
type encodingTestCases[V any, S interface {
|
|
||||||
encoding.BinaryMarshaler
|
|
||||||
encoding.BinaryUnmarshaler
|
|
||||||
|
|
||||||
*V
|
|
||||||
}] []struct {
|
|
||||||
// Uninterpreted name of subtest.
|
|
||||||
name string
|
|
||||||
// Encoded data.
|
|
||||||
wantData []byte
|
|
||||||
// Value corresponding to wantData.
|
|
||||||
value V
|
|
||||||
// Expected decoding error. Skips encoding check if non-nil.
|
|
||||||
wantErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
// run runs all test cases as subtests of [testing.T].
|
|
||||||
func (testCases encodingTestCases[V, S]) run(t *testing.T) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
t.Run("decode", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var value V
|
|
||||||
if err := S(&value).UnmarshalBinary(tc.wantData); err != nil {
|
|
||||||
t.Fatalf("UnmarshalBinary: error = %v", err)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(&value, &tc.value) {
|
|
||||||
t.Fatalf("UnmarshalBinary:\n%s\nwant\n%s", mustMarshalJSON(value), mustMarshalJSON(tc.value))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("encode", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
if gotData, err := S(&tc.value).MarshalBinary(); err != nil {
|
|
||||||
t.Fatalf("MarshalBinary: error = %v", err)
|
|
||||||
} else if string(gotData) != string(tc.wantData) {
|
|
||||||
t.Fatalf("MarshalBinary: %#v, want %#v", gotData, tc.wantData)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if s, ok := any(&tc.value).(pipewire.KnownSize); ok {
|
|
||||||
t.Run("size", func(t *testing.T) {
|
|
||||||
if got := int(s.Size()); got != len(tc.wantData) {
|
|
||||||
t.Errorf("Size: %d, want %d", got, len(tc.wantData))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPODErrors(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
err error
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{"UnsupportedTypeError", &pipewire.UnsupportedTypeError{
|
|
||||||
Type: reflect.TypeFor[any](),
|
|
||||||
}, "unsupported type interface {}"},
|
|
||||||
|
|
||||||
{"UnsupportedSizeError", pipewire.UnsupportedSizeError(pipewire.SizeMax + 1), "size 16777216 out of range"},
|
|
||||||
|
|
||||||
{"InvalidUnmarshalError untyped nil", new(pipewire.InvalidUnmarshalError), "attempting to unmarshal to nil"},
|
|
||||||
{"InvalidUnmarshalError non-pointer", &pipewire.InvalidUnmarshalError{
|
|
||||||
Type: reflect.TypeFor[uintptr](),
|
|
||||||
}, "attempting to unmarshal to non-pointer type uintptr"},
|
|
||||||
{"InvalidUnmarshalError nil", &pipewire.InvalidUnmarshalError{
|
|
||||||
Type: reflect.TypeFor[*uintptr](),
|
|
||||||
}, "attempting to unmarshal to nil *uintptr"},
|
|
||||||
|
|
||||||
{"UnexpectedEOFError ErrEOFPrefix", pipewire.ErrEOFPrefix, "unexpected EOF decoding fixed-size POD prefix"},
|
|
||||||
{"UnexpectedEOFError ErrEOFData", pipewire.ErrEOFData, "unexpected EOF establishing POD data bounds"},
|
|
||||||
{"UnexpectedEOFError ErrEOFDataString", pipewire.ErrEOFDataString, "unexpected EOF establishing POD String bounds"},
|
|
||||||
{"UnexpectedEOFError invalid", pipewire.UnexpectedEOFError(0xbad), "unexpected EOF"},
|
|
||||||
|
|
||||||
{"UnmarshalSetError", &pipewire.UnmarshalSetError{
|
|
||||||
Type: reflect.TypeFor[*uintptr](),
|
|
||||||
}, "cannot set *uintptr"},
|
|
||||||
|
|
||||||
{"TrailingGarbageError short", make(pipewire.TrailingGarbageError, 1<<3-1), "got 7 bytes of trailing garbage"},
|
|
||||||
{"TrailingGarbageError String", pipewire.TrailingGarbageError{
|
|
||||||
/* size: */ 0, 0, 0, 0,
|
|
||||||
/* type: */ byte(pipewire.SPA_TYPE_String), 0, 0, 0,
|
|
||||||
}, "data has extra values starting with String"},
|
|
||||||
{"TrailingGarbageError invalid", pipewire.TrailingGarbageError{
|
|
||||||
/* size: */ 0, 0, 0, 0,
|
|
||||||
/* type: */ 0xff, 0xff, 0xff, 0xff,
|
|
||||||
/* garbage: */ 0,
|
|
||||||
}, "data has extra values starting with invalid type field 0xffffffff"},
|
|
||||||
|
|
||||||
{"StringTerminationError", pipewire.StringTerminationError(0xff), "got byte 255 instead of NUL"},
|
|
||||||
|
|
||||||
{"InconsistentSizeError", pipewire.InconsistentSizeError{
|
|
||||||
Prefix: 0xbad,
|
|
||||||
Expect: 0xff,
|
|
||||||
}, "prefix claims size 2989 for a 255-byte long segment"},
|
|
||||||
|
|
||||||
{"UnexpectedTypeError zero", pipewire.UnexpectedTypeError{}, "received invalid type field 0x0 for a value of type invalid type field 0x0"},
|
|
||||||
{"UnexpectedTypeError", pipewire.UnexpectedTypeError{
|
|
||||||
Type: pipewire.SPA_TYPE_String,
|
|
||||||
Expect: pipewire.SPA_TYPE_Array,
|
|
||||||
}, "received String for a value of type Array"},
|
|
||||||
{"UnexpectedTypeError invalid", pipewire.UnexpectedTypeError{
|
|
||||||
Type: 0xdeadbeef,
|
|
||||||
Expect: pipewire.SPA_TYPE_Long,
|
|
||||||
}, "received invalid type field 0xdeadbeef for a value of type Long"},
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var benchmarkSample = func() (sample pipewire.CoreInfo) {
|
|
||||||
if err := sample.UnmarshalBinary(samplePWContainer[1][0][1]); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}()
|
|
||||||
|
|
||||||
func BenchmarkMarshal(b *testing.B) {
|
|
||||||
for b.Loop() {
|
|
||||||
if _, err := benchmarkSample.MarshalBinary(); err != nil {
|
|
||||||
b.Fatalf("MarshalBinary: error = %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkMarshalJSON(b *testing.B) {
|
|
||||||
for b.Loop() {
|
|
||||||
if _, err := json.Marshal(benchmarkSample); err != nil {
|
|
||||||
b.Fatalf("json.Marshal: error = %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkGobEncode(b *testing.B) {
|
|
||||||
e := gob.NewEncoder(io.Discard)
|
|
||||||
type sampleRaw pipewire.CoreInfo
|
|
||||||
|
|
||||||
for b.Loop() {
|
|
||||||
if err := e.Encode((*sampleRaw)(&benchmarkSample)); err != nil {
|
|
||||||
b.Fatalf("(*gob.Encoder).Encode: error = %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkUnmarshal(b *testing.B) {
|
|
||||||
var got pipewire.CoreInfo
|
|
||||||
|
|
||||||
for b.Loop() {
|
|
||||||
if err := got.UnmarshalBinary(samplePWContainer[1][0][1]); err != nil {
|
|
||||||
b.Fatalf("UnmarshalBinary: error = %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkUnmarshalJSON(b *testing.B) {
|
|
||||||
var got pipewire.CoreInfo
|
|
||||||
data, err := json.Marshal(benchmarkSample)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatalf("json.Marshal: error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for b.Loop() {
|
|
||||||
if err = json.Unmarshal(data, &got); err != nil {
|
|
||||||
b.Fatalf("json.Unmarshal: error = %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkGobDecode(b *testing.B) {
|
|
||||||
type sampleRaw pipewire.CoreInfo
|
|
||||||
var buf bytes.Buffer
|
|
||||||
e := gob.NewEncoder(&buf)
|
|
||||||
d := gob.NewDecoder(&buf)
|
|
||||||
|
|
||||||
for b.Loop() {
|
|
||||||
b.StopTimer()
|
|
||||||
if err := e.Encode((*sampleRaw)(&benchmarkSample)); err != nil {
|
|
||||||
b.Fatalf("(*gob.Encoder).Encode: error = %v", err)
|
|
||||||
}
|
|
||||||
b.StartTimer()
|
|
||||||
|
|
||||||
if err := d.Decode(new(sampleRaw)); err != nil {
|
|
||||||
b.Fatalf("(*gob.Encoder).Decode: error = %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mustMarshalJSON calls [json.Marshal] and returns the result.
|
|
||||||
func mustMarshalJSON(v any) string {
|
|
||||||
if data, err := json.Marshal(v); err != nil {
|
|
||||||
panic(err)
|
|
||||||
} else {
|
|
||||||
return string(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,247 +0,0 @@
|
|||||||
package pipewire
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
/* pipewire/extensions/security-context.h */
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_TYPE_INTERFACE_SecurityContext = PW_TYPE_INFO_INTERFACE_BASE + "SecurityContext"
|
|
||||||
PW_SECURITY_CONTEXT_PERM_MASK = PW_PERM_RWX
|
|
||||||
PW_VERSION_SECURITY_CONTEXT = 3
|
|
||||||
|
|
||||||
PW_EXTENSION_MODULE_SECURITY_CONTEXT = PIPEWIRE_MODULE_PREFIX + "module-security-context"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_SECURITY_CONTEXT_EVENT_NUM = iota
|
|
||||||
|
|
||||||
PW_VERSION_SECURITY_CONTEXT_EVENTS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_SECURITY_CONTEXT_METHOD_ADD_LISTENER = iota
|
|
||||||
|
|
||||||
PW_SECURITY_CONTEXT_METHOD_CREATE
|
|
||||||
|
|
||||||
PW_SECURITY_CONTEXT_METHOD_NUM
|
|
||||||
PW_VERSION_SECURITY_CONTEXT_METHODS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
// SecurityContextCreate is sent to create a new security context.
|
|
||||||
//
|
|
||||||
// Creates a new security context with a socket listening FD.
|
|
||||||
// PipeWire will accept new client connections on listen_fd.
|
|
||||||
//
|
|
||||||
// listen_fd must be ready to accept new connections when this
|
|
||||||
// request is sent by the client. In other words, the client must
|
|
||||||
// call bind(2) and listen(2) before sending the FD.
|
|
||||||
//
|
|
||||||
// close_fd is a FD closed by the client when PipeWire should stop
|
|
||||||
// accepting new connections on listen_fd.
|
|
||||||
//
|
|
||||||
// PipeWire must continue to accept connections on listen_fd when
|
|
||||||
// the client which created the security context disconnects.
|
|
||||||
//
|
|
||||||
// After sending this request, closing listen_fd and close_fd
|
|
||||||
// remains the only valid operation on them.
|
|
||||||
type SecurityContextCreate struct {
|
|
||||||
// The offset in the SCM_RIGHTS msg_control message to
|
|
||||||
// the fd to listen on for new connections.
|
|
||||||
ListenFd Fd
|
|
||||||
// The offset in the SCM_RIGHTS msg_control message to
|
|
||||||
// the fd used to stop listening.
|
|
||||||
CloseFd Fd
|
|
||||||
|
|
||||||
// Extra properties. These will be copied on the client
|
|
||||||
// that connects through this context.
|
|
||||||
Properties *SPADict `json:"props"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Opcode satisfies [Message] with a constant value.
|
|
||||||
func (c *SecurityContextCreate) Opcode() byte { return PW_SECURITY_CONTEXT_METHOD_CREATE }
|
|
||||||
|
|
||||||
// FileCount satisfies [Message] with a constant value.
|
|
||||||
func (c *SecurityContextCreate) FileCount() Int { return 2 }
|
|
||||||
|
|
||||||
// Size satisfies [KnownSize] with a value computed at runtime.
|
|
||||||
func (c *SecurityContextCreate) Size() Word {
|
|
||||||
return SizePrefix +
|
|
||||||
Size(SizeFd) +
|
|
||||||
Size(SizeFd) +
|
|
||||||
c.Properties.Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalBinary satisfies [encoding.BinaryMarshaler] via [Marshal].
|
|
||||||
func (c *SecurityContextCreate) MarshalBinary() ([]byte, error) { return Marshal(c) }
|
|
||||||
|
|
||||||
// UnmarshalBinary satisfies [encoding.BinaryUnmarshaler] via [Unmarshal].
|
|
||||||
func (c *SecurityContextCreate) UnmarshalBinary(data []byte) error { return Unmarshal(data, c) }
|
|
||||||
|
|
||||||
// SecurityContext holds state of [PW_TYPE_INTERFACE_SecurityContext].
|
|
||||||
type SecurityContext struct {
|
|
||||||
// Proxy id as tracked by [Context].
|
|
||||||
ID Int `json:"proxy_id"`
|
|
||||||
// Global id as tracked by [Registry].
|
|
||||||
GlobalID Int `json:"id"`
|
|
||||||
|
|
||||||
ctx *Context
|
|
||||||
|
|
||||||
destructible
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSecurityContext queues a [RegistryBind] message for the PipeWire server
|
|
||||||
// and returns the address of the newly allocated [SecurityContext].
|
|
||||||
func (registry *Registry) GetSecurityContext() (securityContext *SecurityContext, err error) {
|
|
||||||
securityContext = &SecurityContext{ctx: registry.ctx}
|
|
||||||
for globalId, object := range registry.Objects {
|
|
||||||
if object.Type == securityContext.String() {
|
|
||||||
securityContext.GlobalID = globalId
|
|
||||||
securityContext.ID, err = registry.bind(securityContext, securityContext.GlobalID, PW_VERSION_SECURITY_CONTEXT)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, UnsupportedObjectTypeError(securityContext.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create queues a [SecurityContextCreate] message for the PipeWire server.
|
|
||||||
func (securityContext *SecurityContext) Create(listenFd, closeFd int, props SPADict) (err error) {
|
|
||||||
if err = securityContext.checkDestroy(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
asCoreError := securityContext.ctx.expectsCoreError(securityContext.ID, &err)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// queued in reverse based on upstream behaviour, unsure why
|
|
||||||
offset := securityContext.ctx.queueFiles(closeFd, listenFd)
|
|
||||||
if err = securityContext.ctx.writeMessage(
|
|
||||||
securityContext.ID,
|
|
||||||
&SecurityContextCreate{ListenFd: offset + 1, CloseFd: offset + 0, Properties: &props},
|
|
||||||
); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = securityContext.ctx.GetCore().Sync(); err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if coreError := asCoreError(); coreError == nil {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
switch syscall.Errno(-coreError.Result) {
|
|
||||||
case syscall.EPERM:
|
|
||||||
return &PermissionError{securityContext.ID, coreError.Message}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return coreError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// securityContextCloser holds onto resources associated to the security context.
|
|
||||||
type securityContextCloser struct {
|
|
||||||
// Pipe with its write end passed to [SecurityContextCreate.CloseFd].
|
|
||||||
closeFds [2]int
|
|
||||||
// Pathname the socket was bound to.
|
|
||||||
pathname string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes both ends of the pipe.
|
|
||||||
func (scc *securityContextCloser) Close() (err error) {
|
|
||||||
err = errors.Join(
|
|
||||||
syscall.Close(scc.closeFds[1]),
|
|
||||||
syscall.Close(scc.closeFds[0]),
|
|
||||||
// there is still technically a TOCTOU here but this is internal
|
|
||||||
// and has access to the privileged pipewire socket, so it only
|
|
||||||
// receives trusted input (e.g. from cmd/hakurei) anyway
|
|
||||||
os.Remove(scc.pathname),
|
|
||||||
)
|
|
||||||
|
|
||||||
// no need for a finalizer anymore
|
|
||||||
runtime.SetFinalizer(scc, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// BindAndCreate binds a new socket to the specified pathname and pass it to Create.
|
|
||||||
// It returns an [io.Closer] corresponding to [SecurityContextCreate.CloseFd].
|
|
||||||
func (securityContext *SecurityContext) BindAndCreate(pathname string, props SPADict) (io.Closer, error) {
|
|
||||||
if err := securityContext.checkDestroy(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var scc securityContextCloser
|
|
||||||
|
|
||||||
// ensure pathname is available
|
|
||||||
if f, err := os.Create(pathname); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if err = f.Close(); err != nil {
|
|
||||||
_ = os.Remove(pathname)
|
|
||||||
return nil, err
|
|
||||||
} else if err = os.Remove(pathname); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
scc.pathname = pathname
|
|
||||||
|
|
||||||
var listenFd int
|
|
||||||
if fd, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0); err != nil {
|
|
||||||
return nil, os.NewSyscallError("socket", err)
|
|
||||||
} else {
|
|
||||||
securityContext.ctx.cleanup(func() error { return syscall.Close(fd) })
|
|
||||||
listenFd = fd
|
|
||||||
}
|
|
||||||
if err := syscall.Bind(listenFd, &syscall.SockaddrUnix{Name: pathname}); err != nil {
|
|
||||||
return nil, os.NewSyscallError("bind", err)
|
|
||||||
} else if err = syscall.Listen(listenFd, 0); err != nil {
|
|
||||||
_ = os.Remove(pathname)
|
|
||||||
return nil, os.NewSyscallError("listen", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := syscall.Pipe2(scc.closeFds[0:], syscall.O_CLOEXEC); err != nil {
|
|
||||||
_ = os.Remove(pathname)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
runtime.SetFinalizer(&scc, (*securityContextCloser).Close)
|
|
||||||
|
|
||||||
if err := securityContext.Create(listenFd, scc.closeFds[1], props); err != nil {
|
|
||||||
_ = scc.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &scc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (securityContext *SecurityContext) consume(opcode byte, files []int, _ func(v any)) error {
|
|
||||||
securityContext.mustCheckDestroy()
|
|
||||||
closeReceivedFiles(files...)
|
|
||||||
switch opcode {
|
|
||||||
// SecurityContext does not receive any events
|
|
||||||
|
|
||||||
default:
|
|
||||||
panic(&UnsupportedOpcodeError{opcode, securityContext.String()})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (securityContext *SecurityContext) setBoundProps(event *CoreBoundProps) error {
|
|
||||||
securityContext.mustCheckDestroy()
|
|
||||||
if securityContext.ID != event.ID {
|
|
||||||
return &InconsistentIdError{Proxy: securityContext, ID: securityContext.ID, ServerID: event.ID}
|
|
||||||
}
|
|
||||||
if securityContext.GlobalID != event.GlobalID {
|
|
||||||
return &InconsistentIdError{Global: true, Proxy: securityContext, ID: securityContext.GlobalID, ServerID: event.GlobalID}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy destroys this [SecurityContext] proxy.
|
|
||||||
func (securityContext *SecurityContext) Destroy() error {
|
|
||||||
return securityContext.destroy(securityContext.ctx, securityContext.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (securityContext *SecurityContext) String() string { return PW_TYPE_INTERFACE_SecurityContext }
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package pipewire_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"hakurei.app/internal/pipewire"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSecurityContextCreate(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
encodingTestCases[pipewire.SecurityContextCreate, *pipewire.SecurityContextCreate]{
|
|
||||||
{"sample", samplePWContainer[6][0][1], pipewire.SecurityContextCreate{
|
|
||||||
ListenFd: 1 /* 21: duplicated from listen_fd */, CloseFd: 0, /* 20: duplicated from close_fd */
|
|
||||||
Properties: &pipewire.SPADict{
|
|
||||||
{Key: pipewire.PW_KEY_SEC_ENGINE, Value: "org.flatpak"},
|
|
||||||
{Key: pipewire.PW_KEY_ACCESS, Value: "restricted"},
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
}.run(t)
|
|
||||||
}
|
|
||||||
@@ -1,837 +0,0 @@
|
|||||||
package pipewire
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A SPAKind describes the kind of data being encoded right after it.
|
|
||||||
//
|
|
||||||
// These do not always follow the same rules, and encoding/decoding
|
|
||||||
// is very much context-dependent. Callers should therefore not
|
|
||||||
// attempt to use these values directly and rely on [Marshal] and
|
|
||||||
// [Unmarshal] and their variants instead.
|
|
||||||
type SPAKind Word
|
|
||||||
|
|
||||||
/* Basic types */
|
|
||||||
const (
|
|
||||||
/* POD's can contain a number of basic SPA types: */
|
|
||||||
|
|
||||||
SPA_TYPE_START SPAKind = 0x00000 + iota
|
|
||||||
|
|
||||||
SPA_TYPE_None // No value or a NULL pointer.
|
|
||||||
SPA_TYPE_Bool // A boolean value.
|
|
||||||
SPA_TYPE_Id // An enumerated value.
|
|
||||||
SPA_TYPE_Int // An integer value, 32-bit.
|
|
||||||
SPA_TYPE_Long // An integer value, 64-bit.
|
|
||||||
SPA_TYPE_Float // A floating point value, 32-bit.
|
|
||||||
SPA_TYPE_Double // A floating point value, 64-bit.
|
|
||||||
SPA_TYPE_String // A string.
|
|
||||||
SPA_TYPE_Bytes // A byte array.
|
|
||||||
SPA_TYPE_Rectangle // A rectangle with width and height.
|
|
||||||
SPA_TYPE_Fraction // A fraction with numerator and denominator.
|
|
||||||
SPA_TYPE_Bitmap // An array of bits.
|
|
||||||
|
|
||||||
/* POD's can be grouped together in these container types: */
|
|
||||||
|
|
||||||
SPA_TYPE_Array // An array of equal sized objects.
|
|
||||||
SPA_TYPE_Struct // A collection of types and objects.
|
|
||||||
SPA_TYPE_Object // An object with properties.
|
|
||||||
SPA_TYPE_Sequence // A timed sequence of POD's.
|
|
||||||
|
|
||||||
/* POD's can also contain some extra types: */
|
|
||||||
|
|
||||||
SPA_TYPE_Pointer // A typed pointer in memory.
|
|
||||||
SPA_TYPE_Fd // A file descriptor.
|
|
||||||
SPA_TYPE_Choice // A choice of values.
|
|
||||||
SPA_TYPE_Pod // A generic type for the POD itself.
|
|
||||||
|
|
||||||
_SPA_TYPE_LAST // not part of ABI
|
|
||||||
)
|
|
||||||
|
|
||||||
// append appends the representation of [SPAKind] to data and returns the appended slice.
|
|
||||||
func (kind SPAKind) append(data []byte) []byte {
|
|
||||||
return binary.NativeEndian.AppendUint32(data, Word(kind))
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the name of the [SPAKind] for basic types.
|
|
||||||
func (kind SPAKind) String() string {
|
|
||||||
switch kind {
|
|
||||||
case SPA_TYPE_None:
|
|
||||||
return "None"
|
|
||||||
case SPA_TYPE_Bool:
|
|
||||||
return "Bool"
|
|
||||||
case SPA_TYPE_Id:
|
|
||||||
return "Id"
|
|
||||||
case SPA_TYPE_Int:
|
|
||||||
return "Int"
|
|
||||||
case SPA_TYPE_Long:
|
|
||||||
return "Long"
|
|
||||||
case SPA_TYPE_Float:
|
|
||||||
return "Float"
|
|
||||||
case SPA_TYPE_Double:
|
|
||||||
return "Double"
|
|
||||||
case SPA_TYPE_String:
|
|
||||||
return "String"
|
|
||||||
case SPA_TYPE_Bytes:
|
|
||||||
return "Bytes"
|
|
||||||
case SPA_TYPE_Rectangle:
|
|
||||||
return "Rectangle"
|
|
||||||
case SPA_TYPE_Fraction:
|
|
||||||
return "Fraction"
|
|
||||||
case SPA_TYPE_Bitmap:
|
|
||||||
return "Bitmap"
|
|
||||||
case SPA_TYPE_Array:
|
|
||||||
return "Array"
|
|
||||||
case SPA_TYPE_Struct:
|
|
||||||
return "Struct"
|
|
||||||
case SPA_TYPE_Object:
|
|
||||||
return "Object"
|
|
||||||
case SPA_TYPE_Sequence:
|
|
||||||
return "Sequence"
|
|
||||||
case SPA_TYPE_Pointer:
|
|
||||||
return "Pointer"
|
|
||||||
case SPA_TYPE_Fd:
|
|
||||||
return "Fd"
|
|
||||||
case SPA_TYPE_Choice:
|
|
||||||
return "Choice"
|
|
||||||
case SPA_TYPE_Pod:
|
|
||||||
return "Pod"
|
|
||||||
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("invalid type field %#x", Word(kind))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pointers */
|
|
||||||
const (
|
|
||||||
SPA_TYPE_POINTER_START = 0x10000 + iota
|
|
||||||
SPA_TYPE_POINTER_Buffer
|
|
||||||
SPA_TYPE_POINTER_Meta
|
|
||||||
SPA_TYPE_POINTER_Dict
|
|
||||||
|
|
||||||
_SPA_TYPE_POINTER_LAST // not part of ABI
|
|
||||||
)
|
|
||||||
|
|
||||||
/* Events */
|
|
||||||
const (
|
|
||||||
SPA_TYPE_EVENT_START = 0x20000 + iota
|
|
||||||
SPA_TYPE_EVENT_Device
|
|
||||||
SPA_TYPE_EVENT_Node
|
|
||||||
|
|
||||||
_SPA_TYPE_EVENT_LAST // not part of ABI
|
|
||||||
)
|
|
||||||
|
|
||||||
/* Commands */
|
|
||||||
const (
|
|
||||||
SPA_TYPE_COMMAND_START = 0x30000 + iota
|
|
||||||
SPA_TYPE_COMMAND_Device
|
|
||||||
SPA_TYPE_COMMAND_Node
|
|
||||||
|
|
||||||
_SPA_TYPE_COMMAND_LAST // not part of ABI
|
|
||||||
)
|
|
||||||
|
|
||||||
/* Objects */
|
|
||||||
const (
|
|
||||||
SPA_TYPE_OBJECT_START = 0x40000 + iota
|
|
||||||
SPA_TYPE_OBJECT_PropInfo
|
|
||||||
SPA_TYPE_OBJECT_Props
|
|
||||||
SPA_TYPE_OBJECT_Format
|
|
||||||
SPA_TYPE_OBJECT_ParamBuffers
|
|
||||||
SPA_TYPE_OBJECT_ParamMeta
|
|
||||||
SPA_TYPE_OBJECT_ParamIO
|
|
||||||
SPA_TYPE_OBJECT_ParamProfile
|
|
||||||
SPA_TYPE_OBJECT_ParamPortConfig
|
|
||||||
SPA_TYPE_OBJECT_ParamRoute
|
|
||||||
SPA_TYPE_OBJECT_Profiler
|
|
||||||
SPA_TYPE_OBJECT_ParamLatency
|
|
||||||
SPA_TYPE_OBJECT_ParamProcessLatency
|
|
||||||
SPA_TYPE_OBJECT_ParamTag
|
|
||||||
_SPA_TYPE_OBJECT_LAST // not part of ABI
|
|
||||||
)
|
|
||||||
|
|
||||||
/* vendor extensions */
|
|
||||||
const (
|
|
||||||
SPA_TYPE_VENDOR_PipeWire = 0x02000000
|
|
||||||
|
|
||||||
SPA_TYPE_VENDOR_Other = 0x7f000000
|
|
||||||
)
|
|
||||||
|
|
||||||
/* spa/include/spa/utils/type.h */
|
|
||||||
|
|
||||||
const (
|
|
||||||
SPA_TYPE_INFO_BASE = "Spa:"
|
|
||||||
|
|
||||||
SPA_TYPE_INFO_Flags = SPA_TYPE_INFO_BASE + "Flags"
|
|
||||||
SPA_TYPE_INFO_FLAGS_BASE = SPA_TYPE_INFO_Flags + ":"
|
|
||||||
|
|
||||||
SPA_TYPE_INFO_Enum = SPA_TYPE_INFO_BASE + "Enum"
|
|
||||||
SPA_TYPE_INFO_ENUM_BASE = SPA_TYPE_INFO_Enum + ":"
|
|
||||||
|
|
||||||
SPA_TYPE_INFO_Pod = SPA_TYPE_INFO_BASE + "Pod"
|
|
||||||
SPA_TYPE_INFO_POD_BASE = SPA_TYPE_INFO_Pod + ":"
|
|
||||||
|
|
||||||
SPA_TYPE_INFO_Struct = SPA_TYPE_INFO_POD_BASE + "Struct"
|
|
||||||
SPA_TYPE_INFO_STRUCT_BASE = SPA_TYPE_INFO_Struct + ":"
|
|
||||||
|
|
||||||
SPA_TYPE_INFO_Object = SPA_TYPE_INFO_POD_BASE + "Object"
|
|
||||||
SPA_TYPE_INFO_OBJECT_BASE = SPA_TYPE_INFO_Object + ":"
|
|
||||||
|
|
||||||
SPA_TYPE_INFO_Pointer = SPA_TYPE_INFO_BASE + "Pointer"
|
|
||||||
SPA_TYPE_INFO_POINTER_BASE = SPA_TYPE_INFO_Pointer + ":"
|
|
||||||
|
|
||||||
SPA_TYPE_INFO_Interface = SPA_TYPE_INFO_POINTER_BASE + "Interface"
|
|
||||||
SPA_TYPE_INFO_INTERFACE_BASE = SPA_TYPE_INFO_Interface + ":"
|
|
||||||
|
|
||||||
SPA_TYPE_INFO_Event = SPA_TYPE_INFO_OBJECT_BASE + "Event"
|
|
||||||
SPA_TYPE_INFO_EVENT_BASE = SPA_TYPE_INFO_Event + ":"
|
|
||||||
|
|
||||||
SPA_TYPE_INFO_Command = SPA_TYPE_INFO_OBJECT_BASE + "Command"
|
|
||||||
SPA_TYPE_INFO_COMMAND_BASE = SPA_TYPE_INFO_Command + ":"
|
|
||||||
)
|
|
||||||
|
|
||||||
/* pipewire/device.h */
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_TYPE_INTERFACE_Device = PW_TYPE_INFO_INTERFACE_BASE + "Device"
|
|
||||||
PW_DEVICE_PERM_MASK = PW_PERM_RWXM
|
|
||||||
PW_VERSION_DEVICE = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_DEVICE_CHANGE_MASK_PROPS = 1 << iota
|
|
||||||
PW_DEVICE_CHANGE_MASK_PARAMS
|
|
||||||
|
|
||||||
PW_DEVICE_CHANGE_MASK_ALL = 1<<iota - 1
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_DEVICE_EVENT_INFO = iota
|
|
||||||
PW_DEVICE_EVENT_PARAM
|
|
||||||
PW_DEVICE_EVENT_NUM
|
|
||||||
|
|
||||||
PW_VERSION_DEVICE_EVENTS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_DEVICE_METHOD_ADD_LISTENER = iota
|
|
||||||
PW_DEVICE_METHOD_SUBSCRIBE_PARAMS
|
|
||||||
PW_DEVICE_METHOD_ENUM_PARAMS
|
|
||||||
PW_DEVICE_METHOD_SET_PARAM
|
|
||||||
PW_DEVICE_METHOD_NUM
|
|
||||||
|
|
||||||
PW_VERSION_DEVICE_METHODS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
/* pipewire/factory.h */
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_TYPE_INTERFACE_Factory = PW_TYPE_INFO_INTERFACE_BASE + "Factory"
|
|
||||||
PW_FACTORY_PERM_MASK = PW_PERM_R | PW_PERM_M
|
|
||||||
PW_VERSION_FACTORY = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_FACTORY_CHANGE_MASK_PROPS = 1 << iota
|
|
||||||
|
|
||||||
PW_FACTORY_CHANGE_MASK_ALL = 1<<iota - 1
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_FACTORY_EVENT_INFO = iota
|
|
||||||
PW_FACTORY_EVENT_NUM
|
|
||||||
|
|
||||||
PW_VERSION_FACTORY_EVENTS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_FACTORY_METHOD_ADD_LISTENER = iota
|
|
||||||
PW_FACTORY_METHOD_NUM
|
|
||||||
|
|
||||||
PW_VERSION_FACTORY_METHODS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
/* pipewire/link.h */
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_TYPE_INTERFACE_Link = PW_TYPE_INFO_INTERFACE_BASE + "Link"
|
|
||||||
PW_LINK_PERM_MASK = PW_PERM_R | PW_PERM_X
|
|
||||||
PW_VERSION_LINK = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_LINK_STATE_ERROR = iota - 2 // the link is in error
|
|
||||||
PW_LINK_STATE_UNLINKED // the link is unlinked
|
|
||||||
PW_LINK_STATE_INIT // the link is initialized
|
|
||||||
PW_LINK_STATE_NEGOTIATING // the link is negotiating formats
|
|
||||||
PW_LINK_STATE_ALLOCATING // the link is allocating buffers
|
|
||||||
PW_LINK_STATE_PAUSED // the link is paused
|
|
||||||
PW_LINK_STATE_ACTIVE // the link is active
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_LINK_CHANGE_MASK_STATE = (1 << iota)
|
|
||||||
PW_LINK_CHANGE_MASK_FORMAT
|
|
||||||
PW_LINK_CHANGE_MASK_PROPS
|
|
||||||
|
|
||||||
PW_LINK_CHANGE_MASK_ALL = 1<<iota - 1
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_LINK_EVENT_INFO = iota
|
|
||||||
PW_LINK_EVENT_NUM
|
|
||||||
|
|
||||||
PW_VERSION_LINK_EVENTS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_LINK_METHOD_ADD_LISTENER = iota
|
|
||||||
PW_LINK_METHOD_NUM
|
|
||||||
|
|
||||||
PW_VERSION_LINK_METHODS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
/* pipewire/module.h */
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_TYPE_INTERFACE_Module = PW_TYPE_INFO_INTERFACE_BASE + "Module"
|
|
||||||
PW_MODULE_PERM_MASK = PW_PERM_R | PW_PERM_M
|
|
||||||
PW_VERSION_MODULE = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_MODULE_CHANGE_MASK_PROPS = 1 << iota
|
|
||||||
|
|
||||||
PW_MODULE_CHANGE_MASK_ALL = 1<<iota - 1
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_MODULE_EVENT_INFO = iota
|
|
||||||
PW_MODULE_EVENT_NUM
|
|
||||||
|
|
||||||
PW_VERSION_MODULE_EVENTS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_MODULE_METHOD_ADD_LISTENER = iota
|
|
||||||
PW_MODULE_METHOD_NUM
|
|
||||||
|
|
||||||
PW_VERSION_MODULE_METHODS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
/* pipewire/impl-module.h */
|
|
||||||
|
|
||||||
const (
|
|
||||||
PIPEWIRE_SYMBOL_MODULE_INIT = "pipewire__module_init"
|
|
||||||
PIPEWIRE_MODULE_PREFIX = "libpipewire-"
|
|
||||||
|
|
||||||
PW_VERSION_IMPL_MODULE_EVENTS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
/* pipewire/node.h */
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_TYPE_INTERFACE_Node = PW_TYPE_INFO_INTERFACE_BASE + "Node"
|
|
||||||
PW_NODE_PERM_MASK = PW_PERM_RWXML
|
|
||||||
PW_VERSION_NODE = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_NODE_STATE_ERROR = iota - 1 // error state
|
|
||||||
PW_NODE_STATE_CREATING // the node is being created
|
|
||||||
PW_NODE_STATE_SUSPENDED // the node is suspended, the device might be closed
|
|
||||||
PW_NODE_STATE_IDLE // the node is running but there is no active port
|
|
||||||
PW_NODE_STATE_RUNNING // the node is running
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_NODE_CHANGE_MASK_INPUT_PORTS = 1 << iota
|
|
||||||
PW_NODE_CHANGE_MASK_OUTPUT_PORTS
|
|
||||||
PW_NODE_CHANGE_MASK_STATE
|
|
||||||
PW_NODE_CHANGE_MASK_PROPS
|
|
||||||
PW_NODE_CHANGE_MASK_PARAMS
|
|
||||||
|
|
||||||
PW_NODE_CHANGE_MASK_ALL = 1<<iota - 1
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_NODE_EVENT_INFO = iota
|
|
||||||
PW_NODE_EVENT_PARAM
|
|
||||||
PW_NODE_EVENT_NUM
|
|
||||||
|
|
||||||
PW_VERSION_NODE_EVENTS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_NODE_METHOD_ADD_LISTENER = iota
|
|
||||||
PW_NODE_METHOD_SUBSCRIBE_PARAMS
|
|
||||||
PW_NODE_METHOD_ENUM_PARAMS
|
|
||||||
PW_NODE_METHOD_SET_PARAM
|
|
||||||
PW_NODE_METHOD_SEND_COMMAND
|
|
||||||
PW_NODE_METHOD_NUM
|
|
||||||
|
|
||||||
PW_VERSION_NODE_METHODS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
/* pipewire/permission.h */
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_PERM_R = 0400 // object can be seen and events can be received
|
|
||||||
PW_PERM_W = 0200 // methods can be called that modify the object
|
|
||||||
PW_PERM_X = 0100 // methods can be called on the object. The W flag must be present in order to call methods that modify the object.
|
|
||||||
PW_PERM_M = 0010 // metadata can be set on object, Since 0.3.9
|
|
||||||
PW_PERM_L = 0020 // a link can be made between a node that doesn't have permission to see the other node, Since 0.3.77
|
|
||||||
|
|
||||||
PW_PERM_RW = PW_PERM_R | PW_PERM_W
|
|
||||||
PW_PERM_RWX = PW_PERM_RW | PW_PERM_X
|
|
||||||
PW_PERM_RWXM = PW_PERM_RWX | PW_PERM_M
|
|
||||||
PW_PERM_RWXML = PW_PERM_RWXM | PW_PERM_L
|
|
||||||
|
|
||||||
PW_PERM_ALL = PW_PERM_RWXM
|
|
||||||
PW_PERM_INVALID Word = 0xffffffff
|
|
||||||
)
|
|
||||||
|
|
||||||
/* pipewire/port.h */
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_TYPE_INTERFACE_Port = PW_TYPE_INFO_INTERFACE_BASE + "Port"
|
|
||||||
PW_PORT_PERM_MASK = PW_PERM_R | PW_PERM_X | PW_PERM_M
|
|
||||||
PW_VERSION_PORT = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_PORT_CHANGE_MASK_PROPS = 1 << iota
|
|
||||||
PW_PORT_CHANGE_MASK_PARAMS
|
|
||||||
|
|
||||||
PW_PORT_CHANGE_MASK_ALL = 1<<iota - 1
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_PORT_EVENT_INFO = iota
|
|
||||||
PW_PORT_EVENT_PARAM
|
|
||||||
PW_PORT_EVENT_NUM
|
|
||||||
|
|
||||||
PW_VERSION_PORT_EVENTS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_PORT_METHOD_ADD_LISTENER = iota
|
|
||||||
PW_PORT_METHOD_SUBSCRIBE_PARAMS
|
|
||||||
PW_PORT_METHOD_ENUM_PARAMS
|
|
||||||
PW_PORT_METHOD_NUM
|
|
||||||
|
|
||||||
PW_VERSION_PORT_METHODS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
/* pipewire/extensions/client-node.h */
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_TYPE_INTERFACE_ClientNode = PW_TYPE_INFO_INTERFACE_BASE + "ClientNode"
|
|
||||||
PW_VERSION_CLIENT_NODE = 6
|
|
||||||
|
|
||||||
PW_EXTENSION_MODULE_CLIENT_NODE = PIPEWIRE_MODULE_PREFIX + "module-client-node"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_CLIENT_NODE_EVENT_TRANSPORT = iota
|
|
||||||
PW_CLIENT_NODE_EVENT_SET_PARAM
|
|
||||||
PW_CLIENT_NODE_EVENT_SET_IO
|
|
||||||
PW_CLIENT_NODE_EVENT_EVENT
|
|
||||||
PW_CLIENT_NODE_EVENT_COMMAND
|
|
||||||
PW_CLIENT_NODE_EVENT_ADD_PORT
|
|
||||||
PW_CLIENT_NODE_EVENT_REMOVE_PORT
|
|
||||||
PW_CLIENT_NODE_EVENT_PORT_SET_PARAM
|
|
||||||
PW_CLIENT_NODE_EVENT_PORT_USE_BUFFERS
|
|
||||||
PW_CLIENT_NODE_EVENT_PORT_SET_IO
|
|
||||||
PW_CLIENT_NODE_EVENT_SET_ACTIVATION
|
|
||||||
PW_CLIENT_NODE_EVENT_PORT_SET_MIX_INFO
|
|
||||||
PW_CLIENT_NODE_EVENT_NUM
|
|
||||||
|
|
||||||
PW_VERSION_CLIENT_NODE_EVENTS = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_CLIENT_NODE_METHOD_ADD_LISTENER = iota
|
|
||||||
PW_CLIENT_NODE_METHOD_GET_NODE
|
|
||||||
PW_CLIENT_NODE_METHOD_UPDATE
|
|
||||||
PW_CLIENT_NODE_METHOD_PORT_UPDATE
|
|
||||||
PW_CLIENT_NODE_METHOD_SET_ACTIVE
|
|
||||||
PW_CLIENT_NODE_METHOD_EVENT
|
|
||||||
PW_CLIENT_NODE_METHOD_PORT_BUFFERS
|
|
||||||
PW_CLIENT_NODE_METHOD_NUM
|
|
||||||
|
|
||||||
PW_VERSION_CLIENT_NODE_METHODS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_CLIENT_NODE_UPDATE_PARAMS = 1 << iota
|
|
||||||
PW_CLIENT_NODE_UPDATE_INFO
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_CLIENT_NODE_PORT_UPDATE_PARAMS = 1 << iota
|
|
||||||
PW_CLIENT_NODE_PORT_UPDATE_INFO
|
|
||||||
)
|
|
||||||
|
|
||||||
/* pipewire/extensions/metadata.h */
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_TYPE_INTERFACE_Metadata = PW_TYPE_INFO_INTERFACE_BASE + "Metadata"
|
|
||||||
PW_METADATA_PERM_MASK = PW_PERM_RWX
|
|
||||||
PW_VERSION_METADATA = 3
|
|
||||||
|
|
||||||
PW_EXTENSION_MODULE_METADATA = PIPEWIRE_MODULE_PREFIX + "module-metadata"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_METADATA_EVENT_PROPERTY = iota
|
|
||||||
PW_METADATA_EVENT_NUM
|
|
||||||
|
|
||||||
PW_VERSION_METADATA_EVENTS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_METADATA_METHOD_ADD_LISTENER = iota
|
|
||||||
PW_METADATA_METHOD_SET_PROPERTY
|
|
||||||
PW_METADATA_METHOD_CLEAR
|
|
||||||
PW_METADATA_METHOD_NUM
|
|
||||||
|
|
||||||
PW_VERSION_METADATA_METHODS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_KEY_METADATA_NAME = "metadata.name"
|
|
||||||
PW_KEY_METADATA_VALUES = "metadata.values"
|
|
||||||
)
|
|
||||||
|
|
||||||
/* pipewire/extensions/profiler.h */
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_TYPE_INTERFACE_Profiler = PW_TYPE_INFO_INTERFACE_BASE + "Profiler"
|
|
||||||
PW_VERSION_PROFILER = 3
|
|
||||||
PW_PROFILER_PERM_MASK = PW_PERM_R
|
|
||||||
|
|
||||||
PW_EXTENSION_MODULE_PROFILER = PIPEWIRE_MODULE_PREFIX + "module-profiler"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_PROFILER_EVENT_PROFILE = iota
|
|
||||||
PW_PROFILER_EVENT_NUM
|
|
||||||
|
|
||||||
PW_VERSION_PROFILER_EVENTS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_PROFILER_METHOD_ADD_LISTENER = iota
|
|
||||||
PW_PROFILER_METHOD_NUM
|
|
||||||
|
|
||||||
PW_VERSION_PROFILER_METHODS = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_KEY_PROFILER_NAME = "profiler.name"
|
|
||||||
)
|
|
||||||
|
|
||||||
/* pipewire/type.h */
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_TYPE_INFO_BASE = "PipeWire:"
|
|
||||||
|
|
||||||
PW_TYPE_INFO_Object = PW_TYPE_INFO_BASE + "Object"
|
|
||||||
PW_TYPE_INFO_OBJECT_BASE = PW_TYPE_INFO_Object + ":"
|
|
||||||
|
|
||||||
PW_TYPE_INFO_Interface = PW_TYPE_INFO_BASE + "Interface"
|
|
||||||
PW_TYPE_INFO_INTERFACE_BASE = PW_TYPE_INFO_Interface + ":"
|
|
||||||
)
|
|
||||||
|
|
||||||
/* pipewire/keys.h */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Key Names
|
|
||||||
*
|
|
||||||
* A collection of keys that are used to add extra information on objects.
|
|
||||||
*
|
|
||||||
* Keys that start with "pipewire." are in general set-once and then
|
|
||||||
* read-only. They are usually used for security sensitive information that
|
|
||||||
* needs to be fixed.
|
|
||||||
*
|
|
||||||
* Properties from other objects can also appear. This usually suggests some
|
|
||||||
* sort of parent/child or owner/owned relationship.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
const (
|
|
||||||
PW_KEY_PROTOCOL = "pipewire.protocol" /* protocol used for connection */
|
|
||||||
PW_KEY_ACCESS = "pipewire.access" /* how the client access is controlled */
|
|
||||||
PW_KEY_CLIENT_ACCESS = "pipewire.client.access" /* how the client wants to be access controlled */
|
|
||||||
|
|
||||||
/** Various keys related to the identity of a client process and its security.
|
|
||||||
* Must be obtained from trusted sources by the protocol and placed as
|
|
||||||
* read-only properties. */
|
|
||||||
|
|
||||||
PW_KEY_SEC_PID = "pipewire.sec.pid" /* Client pid, set by protocol */
|
|
||||||
PW_KEY_SEC_UID = "pipewire.sec.uid" /* Client uid, set by protocol*/
|
|
||||||
PW_KEY_SEC_GID = "pipewire.sec.gid" /* client gid, set by protocol*/
|
|
||||||
PW_KEY_SEC_LABEL = "pipewire.sec.label" /* client security label, set by protocol*/
|
|
||||||
|
|
||||||
PW_KEY_SEC_SOCKET = "pipewire.sec.socket" /* client socket name, set by protocol */
|
|
||||||
|
|
||||||
PW_KEY_SEC_ENGINE = "pipewire.sec.engine" /* client secure context engine, set by protocol. This can also be set by a client when making a new security context. */
|
|
||||||
PW_KEY_SEC_APP_ID = "pipewire.sec.app-id" /* client secure application id */
|
|
||||||
PW_KEY_SEC_INSTANCE_ID = "pipewire.sec.instance-id" /* client secure instance id */
|
|
||||||
|
|
||||||
PW_KEY_LIBRARY_NAME_SYSTEM = "library.name.system" /* name of the system library to use */
|
|
||||||
PW_KEY_LIBRARY_NAME_LOOP = "library.name.loop" /* name of the loop library to use */
|
|
||||||
PW_KEY_LIBRARY_NAME_DBUS = "library.name.dbus" /* name of the dbus library to use */
|
|
||||||
|
|
||||||
/** object properties */
|
|
||||||
|
|
||||||
PW_KEY_OBJECT_PATH = "object.path" /* unique path to construct the object */
|
|
||||||
PW_KEY_OBJECT_ID = "object.id" /* a global object id */
|
|
||||||
PW_KEY_OBJECT_SERIAL = "object.serial" /* a 64 bit object serial number. This is a number incremented for each object that is created. The lower 32 bits are guaranteed to never be SPA_ID_INVALID. */
|
|
||||||
PW_KEY_OBJECT_LINGER = "object.linger" /* the object lives on even after the client that created it has been destroyed */
|
|
||||||
PW_KEY_OBJECT_REGISTER = "object.register" /* If the object should be registered. */
|
|
||||||
PW_KEY_OBJECT_EXPORT = "object.export" /* If the object should be exported, since 0.3.72 */
|
|
||||||
|
|
||||||
/* config */
|
|
||||||
|
|
||||||
PW_KEY_CONFIG_PREFIX = "config.prefix" /* a config prefix directory */
|
|
||||||
PW_KEY_CONFIG_NAME = "config.name" /* a config file name */
|
|
||||||
PW_KEY_CONFIG_OVERRIDE_PREFIX = "config.override.prefix" /* a config override prefix directory */
|
|
||||||
PW_KEY_CONFIG_OVERRIDE_NAME = "config.override.name" /* a config override file name */
|
|
||||||
|
|
||||||
/* loop */
|
|
||||||
|
|
||||||
PW_KEY_LOOP_NAME = "loop.name" /* the name of a loop */
|
|
||||||
PW_KEY_LOOP_CLASS = "loop.class" /* the classes this loop handles, array of strings */
|
|
||||||
PW_KEY_LOOP_RT_PRIO = "loop.rt-prio" /* realtime priority of the loop */
|
|
||||||
PW_KEY_LOOP_CANCEL = "loop.cancel" /* if the loop can be canceled */
|
|
||||||
|
|
||||||
/* context */
|
|
||||||
|
|
||||||
PW_KEY_CONTEXT_PROFILE_MODULES = "context.profile.modules" /* a context profile for modules, deprecated */
|
|
||||||
PW_KEY_USER_NAME = "context.user-name" /* The user name that runs pipewire */
|
|
||||||
PW_KEY_HOST_NAME = "context.host-name" /* The host name of the machine */
|
|
||||||
|
|
||||||
/* core */
|
|
||||||
|
|
||||||
PW_KEY_CORE_NAME = "core.name" /* The name of the core. Default is `pipewire-<username>-<pid>`, overwritten by env(PIPEWIRE_CORE) */
|
|
||||||
PW_KEY_CORE_VERSION = "core.version" /* The version of the core. */
|
|
||||||
PW_KEY_CORE_DAEMON = "core.daemon" /* If the core is listening for connections. */
|
|
||||||
|
|
||||||
PW_KEY_CORE_ID = "core.id" /* the core id */
|
|
||||||
PW_KEY_CORE_MONITORS = "core.monitors" /* the apis monitored by core. */
|
|
||||||
|
|
||||||
/* cpu */
|
|
||||||
|
|
||||||
PW_KEY_CPU_MAX_ALIGN = "cpu.max-align" /* maximum alignment needed to support all CPU optimizations */
|
|
||||||
PW_KEY_CPU_CORES = "cpu.cores" /* number of cores */
|
|
||||||
|
|
||||||
/* priorities */
|
|
||||||
|
|
||||||
PW_KEY_PRIORITY_SESSION = "priority.session" /* priority in session manager */
|
|
||||||
PW_KEY_PRIORITY_DRIVER = "priority.driver" /* priority to be a driver */
|
|
||||||
|
|
||||||
/* remote keys */
|
|
||||||
|
|
||||||
PW_KEY_REMOTE_NAME = "remote.name" /* The name of the remote to connect to, default pipewire-0, overwritten by env(PIPEWIRE_REMOTE). May also be a SPA-JSON array of sockets, to be tried in order. The "internal" remote name and "generic" intention connects to the local PipeWire instance. */
|
|
||||||
PW_KEY_REMOTE_INTENTION = "remote.intention" /* The intention of the remote connection, "generic", "screencast", "manager" */
|
|
||||||
|
|
||||||
/** application keys */
|
|
||||||
|
|
||||||
PW_KEY_APP_NAME = "application.name" /* application name. Ex: "Totem Music Player" */
|
|
||||||
PW_KEY_APP_ID = "application.id" /* a textual id for identifying an application logically. Ex: "org.gnome.Totem" */
|
|
||||||
PW_KEY_APP_VERSION = "application.version" /* application version. Ex: "1.2.0" */
|
|
||||||
PW_KEY_APP_ICON = "application.icon" /* aa base64 blob with PNG image data */
|
|
||||||
PW_KEY_APP_ICON_NAME = "application.icon-name" /* an XDG icon name for the application. Ex: "totem" */
|
|
||||||
PW_KEY_APP_LANGUAGE = "application.language" /* application language if applicable, in standard POSIX format. Ex: "en_GB" */
|
|
||||||
|
|
||||||
PW_KEY_APP_PROCESS_ID = "application.process.id" /* process id (pid)*/
|
|
||||||
PW_KEY_APP_PROCESS_BINARY = "application.process.binary" /* binary name */
|
|
||||||
PW_KEY_APP_PROCESS_USER = "application.process.user" /* user name */
|
|
||||||
PW_KEY_APP_PROCESS_HOST = "application.process.host" /* host name */
|
|
||||||
PW_KEY_APP_PROCESS_MACHINE_ID = "application.process.machine-id" /* the D-Bus host id the application runs on */
|
|
||||||
PW_KEY_APP_PROCESS_SESSION_ID = "application.process.session-id" /* login session of the application, on Unix the value of $XDG_SESSION_ID. */
|
|
||||||
|
|
||||||
/** window system */
|
|
||||||
|
|
||||||
PW_KEY_WINDOW_X11_DISPLAY = "window.x11.display" /* the X11 display string. Ex. ":0.0" */
|
|
||||||
|
|
||||||
/** Client properties */
|
|
||||||
|
|
||||||
PW_KEY_CLIENT_ID = "client.id" /* a client id */
|
|
||||||
PW_KEY_CLIENT_NAME = "client.name" /* the client name */
|
|
||||||
PW_KEY_CLIENT_API = "client.api" /* the client api used to access PipeWire */
|
|
||||||
|
|
||||||
/** Node keys */
|
|
||||||
|
|
||||||
PW_KEY_NODE_ID = "node.id" /* node id */
|
|
||||||
PW_KEY_NODE_NAME = "node.name" /* node name */
|
|
||||||
PW_KEY_NODE_NICK = "node.nick" /* short node name */
|
|
||||||
PW_KEY_NODE_DESCRIPTION = "node.description" /* localized human readable node one-line description. Ex. "Foobar USB Headset" */
|
|
||||||
PW_KEY_NODE_PLUGGED = "node.plugged" /* when the node was created. As a uint64 in nanoseconds. */
|
|
||||||
|
|
||||||
PW_KEY_NODE_SESSION = "node.session" /* the session id this node is part of */
|
|
||||||
PW_KEY_NODE_GROUP = "node.group" /* the group id this node is part of. Nodes in the same group are always scheduled with the same driver. Can be an array of group names. */
|
|
||||||
PW_KEY_NODE_SYNC_GROUP = "node.sync-group" /* the sync group this node is part of. Nodes in the same sync group are always scheduled together with the same driver when the sync is active. Can be an array of sync names. */
|
|
||||||
PW_KEY_NODE_SYNC = "node.sync" /* if the sync-group is active or not */
|
|
||||||
PW_KEY_NODE_TRANSPORT = "node.transport" /* if the transport is active or not */
|
|
||||||
PW_KEY_NODE_EXCLUSIVE = "node.exclusive" /* node wants exclusive access to resources */
|
|
||||||
PW_KEY_NODE_AUTOCONNECT = "node.autoconnect" /* node wants to be automatically connected to a compatible node */
|
|
||||||
PW_KEY_NODE_LATENCY = "node.latency" /* the requested latency of the node as a fraction. Ex: 128/48000 */
|
|
||||||
PW_KEY_NODE_MAX_LATENCY = "node.max-latency" /* the maximum supported latency of the node as a fraction. Ex: 1024/48000 */
|
|
||||||
PW_KEY_NODE_LOCK_QUANTUM = "node.lock-quantum" /* don't change quantum when this node is active */
|
|
||||||
PW_KEY_NODE_FORCE_QUANTUM = "node.force-quantum" /* force a quantum while the node is active */
|
|
||||||
PW_KEY_NODE_RATE = "node.rate" /* the requested rate of the graph as a fraction. Ex: 1/48000 */
|
|
||||||
PW_KEY_NODE_LOCK_RATE = "node.lock-rate" /* don't change rate when this node is active */
|
|
||||||
PW_KEY_NODE_FORCE_RATE = "node.force-rate" /* force a rate while the node is active. A value of 0 takes the denominator of node.rate */
|
|
||||||
|
|
||||||
PW_KEY_NODE_DONT_RECONNECT = "node.dont-reconnect" /* don't reconnect this node. The node is initially linked to target.object or the default node. If the target is removed, the node is destroyed */
|
|
||||||
PW_KEY_NODE_ALWAYS_PROCESS = "node.always-process" /* process even when unlinked */
|
|
||||||
PW_KEY_NODE_WANT_DRIVER = "node.want-driver" /* the node wants to be grouped with a driver node in order to schedule the graph. */
|
|
||||||
PW_KEY_NODE_PAUSE_ON_IDLE = "node.pause-on-idle" /* pause the node when idle */
|
|
||||||
PW_KEY_NODE_SUSPEND_ON_IDLE = "node.suspend-on-idle" /* suspend the node when idle */
|
|
||||||
PW_KEY_NODE_CACHE_PARAMS = "node.cache-params" /* cache the node params */
|
|
||||||
PW_KEY_NODE_TRANSPORT_SYNC = "node.transport.sync" /* the node handles transport sync */
|
|
||||||
PW_KEY_NODE_DRIVER = "node.driver" /* node can drive the graph. When the node is selected as the driver, it needs to start the graph periodically. */
|
|
||||||
PW_KEY_NODE_SUPPORTS_LAZY = "node.supports-lazy" /* the node can be a lazy driver. It will listen to RequestProcess commands and take them into account when deciding to start the graph. A value of 0 disables support, a value of > 0 enables with increasing preference. */
|
|
||||||
PW_KEY_NODE_SUPPORTS_REQUEST = "node.supports-request" /* The node supports emiting RequestProcess events when it wants the graph to be scheduled. A value of 0 disables support, a value of > 0 enables with increasing preference. */
|
|
||||||
PW_KEY_NODE_DRIVER_ID = "node.driver-id" /* the node id of the node assigned as driver for this node */
|
|
||||||
PW_KEY_NODE_ASYNC = "node.async" /* the node wants async scheduling */
|
|
||||||
PW_KEY_NODE_LOOP_NAME = "node.loop.name" /* the loop name fnmatch pattern to run in */
|
|
||||||
PW_KEY_NODE_LOOP_CLASS = "node.loop.class" /* the loop class fnmatch pattern to run in */
|
|
||||||
PW_KEY_NODE_STREAM = "node.stream" /* node is a stream, the server side should add a converter */
|
|
||||||
PW_KEY_NODE_VIRTUAL = "node.virtual" /* the node is some sort of virtual object */
|
|
||||||
PW_KEY_NODE_PASSIVE = "node.passive" /* indicate that a node wants passive links on output/input/all ports when the value is "out"/"in"/"true" respectively */
|
|
||||||
PW_KEY_NODE_LINK_GROUP = "node.link-group" /* the node is internally linked to nodes with the same link-group. Can be an array of group names. */
|
|
||||||
PW_KEY_NODE_NETWORK = "node.network" /* the node is on a network */
|
|
||||||
PW_KEY_NODE_TRIGGER = "node.trigger" /* the node is not scheduled automatically based on the dependencies in the graph but it will be triggered explicitly. */
|
|
||||||
PW_KEY_NODE_CHANNELNAMES = "node.channel-names" /* names of node's channels (unrelated to positions) */
|
|
||||||
PW_KEY_NODE_DEVICE_PORT_NAME_PREFIX = "node.device-port-name-prefix" /* override port name prefix for device ports, like capture and playback or disable the prefix completely if an empty string is provided */
|
|
||||||
PW_KEY_NODE_PHYSICAL = "node.physical" /* ports from the node are physical */
|
|
||||||
PW_KEY_NODE_TERMINAL = "node.terminal" /* ports from the node are terminal */
|
|
||||||
|
|
||||||
PW_KEY_NODE_RELIABLE = "node.reliable" /* node uses reliable transport 1.6.0 */
|
|
||||||
|
|
||||||
/** Port keys */
|
|
||||||
|
|
||||||
PW_KEY_PORT_ID = "port.id" /* port id */
|
|
||||||
PW_KEY_PORT_NAME = "port.name" /* port name */
|
|
||||||
PW_KEY_PORT_DIRECTION = "port.direction" /* the port direction, one of "in" or "out" or "control" and "notify" for control ports */
|
|
||||||
PW_KEY_PORT_ALIAS = "port.alias" /* port alias */
|
|
||||||
PW_KEY_PORT_PHYSICAL = "port.physical" /* if this is a physical port */
|
|
||||||
PW_KEY_PORT_TERMINAL = "port.terminal" /* if this port consumes the data */
|
|
||||||
PW_KEY_PORT_CONTROL = "port.control" /* if this port is a control port */
|
|
||||||
PW_KEY_PORT_MONITOR = "port.monitor" /* if this port is a monitor port */
|
|
||||||
PW_KEY_PORT_CACHE_PARAMS = "port.cache-params" /* cache the node port params */
|
|
||||||
PW_KEY_PORT_EXTRA = "port.extra" /* api specific extra port info, API name should be prefixed. "jack:flags:56" */
|
|
||||||
PW_KEY_PORT_PASSIVE = "port.passive" /* the ports wants passive links, since 0.3.67 */
|
|
||||||
PW_KEY_PORT_IGNORE_LATENCY = "port.ignore-latency" /* latency ignored by peers, since 0.3.71 */
|
|
||||||
PW_KEY_PORT_GROUP = "port.group" /* the port group of the port 1.2.0 */
|
|
||||||
PW_KEY_PORT_EXCLUSIVE = "port.exclusive" /* link port only once 1.6.0 */
|
|
||||||
PW_KEY_PORT_RELIABLE = "port.reliable" /* port uses reliable transport 1.6.0 */
|
|
||||||
|
|
||||||
/** link properties */
|
|
||||||
|
|
||||||
PW_KEY_LINK_ID = "link.id" /* a link id */
|
|
||||||
PW_KEY_LINK_INPUT_NODE = "link.input.node" /* input node id of a link */
|
|
||||||
PW_KEY_LINK_INPUT_PORT = "link.input.port" /* input port id of a link */
|
|
||||||
PW_KEY_LINK_OUTPUT_NODE = "link.output.node" /* output node id of a link */
|
|
||||||
PW_KEY_LINK_OUTPUT_PORT = "link.output.port" /* output port id of a link */
|
|
||||||
PW_KEY_LINK_PASSIVE = "link.passive" /* indicate that a link is passive and does not cause the graph to be runnable. */
|
|
||||||
PW_KEY_LINK_FEEDBACK = "link.feedback" /* indicate that a link is a feedback link and the target will receive data in the next cycle */
|
|
||||||
PW_KEY_LINK_ASYNC = "link.async" /* the link is using async io */
|
|
||||||
|
|
||||||
/** device properties */
|
|
||||||
|
|
||||||
PW_KEY_DEVICE_ID = "device.id" /* device id */
|
|
||||||
PW_KEY_DEVICE_NAME = "device.name" /* device name */
|
|
||||||
PW_KEY_DEVICE_PLUGGED = "device.plugged" /* when the device was created. As a uint64 in nanoseconds. */
|
|
||||||
PW_KEY_DEVICE_NICK = "device.nick" /* a short device nickname */
|
|
||||||
PW_KEY_DEVICE_STRING = "device.string" /* device string in the underlying layer's format. Ex. "surround51:0" */
|
|
||||||
PW_KEY_DEVICE_API = "device.api" /* API this device is accessed with. Ex. "alsa", "v4l2" */
|
|
||||||
PW_KEY_DEVICE_DESCRIPTION = "device.description" /* localized human readable device one-line description. Ex. "Foobar USB Headset" */
|
|
||||||
PW_KEY_DEVICE_BUS_PATH = "device.bus-path" /* bus path to the device in the OS' format. Ex. "pci-0000:00:14.0-usb-0:3.2:1.0" */
|
|
||||||
PW_KEY_DEVICE_SERIAL = "device.serial" /* Serial number if applicable */
|
|
||||||
PW_KEY_DEVICE_VENDOR_ID = "device.vendor.id" /* vendor ID if applicable */
|
|
||||||
PW_KEY_DEVICE_VENDOR_NAME = "device.vendor.name" /* vendor name if applicable */
|
|
||||||
PW_KEY_DEVICE_PRODUCT_ID = "device.product.id" /* product ID if applicable */
|
|
||||||
PW_KEY_DEVICE_PRODUCT_NAME = "device.product.name" /* product name if applicable */
|
|
||||||
PW_KEY_DEVICE_CLASS = "device.class" /* device class */
|
|
||||||
PW_KEY_DEVICE_FORM_FACTOR = "device.form-factor" /* form factor if applicable. One of "internal", "speaker", "handset", "tv", "webcam", "microphone", "headset", "headphone", "hands-free", "car", "hifi", "computer", "portable" */
|
|
||||||
PW_KEY_DEVICE_BUS = "device.bus" /* bus of the device if applicable. One of "isa", "pci", "usb", "firewire", "bluetooth" */
|
|
||||||
PW_KEY_DEVICE_SUBSYSTEM = "device.subsystem" /* device subsystem */
|
|
||||||
PW_KEY_DEVICE_SYSFS_PATH = "device.sysfs.path" /* device sysfs path */
|
|
||||||
PW_KEY_DEVICE_ICON = "device.icon" /* icon for the device. A base64 blob containing PNG image data */
|
|
||||||
PW_KEY_DEVICE_ICON_NAME = "device.icon-name" /* an XDG icon name for the device. Ex. "sound-card-speakers-usb" */
|
|
||||||
PW_KEY_DEVICE_INTENDED_ROLES = "device.intended-roles" /* intended use. A space separated list of roles (see PW_KEY_MEDIA_ROLE) this device is particularly well suited for, due to latency, quality or form factor. */
|
|
||||||
PW_KEY_DEVICE_CACHE_PARAMS = "device.cache-params" /* cache the device spa params */
|
|
||||||
|
|
||||||
/** module properties */
|
|
||||||
|
|
||||||
PW_KEY_MODULE_ID = "module.id" /* the module id */
|
|
||||||
PW_KEY_MODULE_NAME = "module.name" /* the name of the module */
|
|
||||||
PW_KEY_MODULE_AUTHOR = "module.author" /* the author's name */
|
|
||||||
PW_KEY_MODULE_DESCRIPTION = "module.description" /* a human readable one-line description of the module's purpose.*/
|
|
||||||
PW_KEY_MODULE_USAGE = "module.usage" /* a human readable usage description of the module's arguments. */
|
|
||||||
PW_KEY_MODULE_VERSION = "module.version" /* a version string for the module. */
|
|
||||||
PW_KEY_MODULE_DEPRECATED = "module.deprecated" /* the module is deprecated with this message */
|
|
||||||
|
|
||||||
/** Factory properties */
|
|
||||||
|
|
||||||
PW_KEY_FACTORY_ID = "factory.id" /* the factory id */
|
|
||||||
PW_KEY_FACTORY_NAME = "factory.name" /* the name of the factory */
|
|
||||||
PW_KEY_FACTORY_USAGE = "factory.usage" /* the usage of the factory */
|
|
||||||
PW_KEY_FACTORY_TYPE_NAME = "factory.type.name" /* the name of the type created by a factory */
|
|
||||||
PW_KEY_FACTORY_TYPE_VERSION = "factory.type.version" /* the version of the type created by a factory */
|
|
||||||
|
|
||||||
/** Stream properties */
|
|
||||||
|
|
||||||
PW_KEY_STREAM_IS_LIVE = "stream.is-live" /* Indicates that the stream is live. */
|
|
||||||
PW_KEY_STREAM_LATENCY_MIN = "stream.latency.min" /* The minimum latency of the stream. */
|
|
||||||
PW_KEY_STREAM_LATENCY_MAX = "stream.latency.max" /* The maximum latency of the stream */
|
|
||||||
PW_KEY_STREAM_MONITOR = "stream.monitor" /* Indicates that the stream is monitoring and might select a less accurate but faster conversion algorithm. Monitor streams are also ignored when calculating the latency of their peer ports (since 0.3.71).
|
|
||||||
*/
|
|
||||||
PW_KEY_STREAM_DONT_REMIX = "stream.dont-remix" /* don't remix channels */
|
|
||||||
PW_KEY_STREAM_CAPTURE_SINK = "stream.capture.sink" /* Try to capture the sink output instead of source output */
|
|
||||||
|
|
||||||
/** Media */
|
|
||||||
|
|
||||||
PW_KEY_MEDIA_TYPE = "media.type" /* Media type, one of Audio, Video, Midi */
|
|
||||||
PW_KEY_MEDIA_CATEGORY = "media.category" /* Media Category: Playback, Capture, Duplex, Monitor, Manager */
|
|
||||||
PW_KEY_MEDIA_ROLE = "media.role" /* Role: Movie, Music, Camera, Screen, Communication, Game, Notification, DSP, Production, Accessibility, Test */
|
|
||||||
PW_KEY_MEDIA_CLASS = "media.class" /* class Ex: "Video/Source" */
|
|
||||||
PW_KEY_MEDIA_NAME = "media.name" /* media name. Ex: "Pink Floyd: Time" */
|
|
||||||
PW_KEY_MEDIA_TITLE = "media.title" /* title. Ex: "Time" */
|
|
||||||
PW_KEY_MEDIA_ARTIST = "media.artist" /* artist. Ex: "Pink Floyd" */
|
|
||||||
PW_KEY_MEDIA_ALBUM = "media.album" /* album. Ex: "Dark Side of the Moon" */
|
|
||||||
PW_KEY_MEDIA_COPYRIGHT = "media.copyright" /* copyright string */
|
|
||||||
PW_KEY_MEDIA_SOFTWARE = "media.software" /* generator software */
|
|
||||||
PW_KEY_MEDIA_LANGUAGE = "media.language" /* language in POSIX format. Ex: en_GB */
|
|
||||||
PW_KEY_MEDIA_FILENAME = "media.filename" /* filename */
|
|
||||||
PW_KEY_MEDIA_ICON = "media.icon" /* icon for the media, a base64 blob with PNG image data */
|
|
||||||
PW_KEY_MEDIA_ICON_NAME = "media.icon-name" /* an XDG icon name for the media. Ex: "audio-x-mp3" */
|
|
||||||
PW_KEY_MEDIA_COMMENT = "media.comment" /* extra comment */
|
|
||||||
PW_KEY_MEDIA_DATE = "media.date" /* date of the media */
|
|
||||||
PW_KEY_MEDIA_FORMAT = "media.format" /* format of the media */
|
|
||||||
|
|
||||||
/** format related properties */
|
|
||||||
|
|
||||||
PW_KEY_FORMAT_DSP = "format.dsp" /* a dsp format. Ex: "32 bit float mono audio" */
|
|
||||||
|
|
||||||
/** audio related properties */
|
|
||||||
|
|
||||||
PW_KEY_AUDIO_CHANNEL = "audio.channel" /* an audio channel. Ex: "FL" */
|
|
||||||
PW_KEY_AUDIO_RATE = "audio.rate" /* an audio samplerate */
|
|
||||||
PW_KEY_AUDIO_CHANNELS = "audio.channels" /* number of audio channels */
|
|
||||||
PW_KEY_AUDIO_FORMAT = "audio.format" /* an audio format. Ex: "S16LE" */
|
|
||||||
PW_KEY_AUDIO_ALLOWED_RATES = "audio.allowed-rates" /* a list of allowed samplerates ex. "[ 44100 48000 ]" */
|
|
||||||
|
|
||||||
/** video related properties */
|
|
||||||
|
|
||||||
PW_KEY_VIDEO_RATE = "video.framerate" /* a video framerate */
|
|
||||||
PW_KEY_VIDEO_FORMAT = "video.format" /* a video format */
|
|
||||||
PW_KEY_VIDEO_SIZE = "video.size" /* a video size as "<width>x<height" */
|
|
||||||
|
|
||||||
PW_KEY_TARGET_OBJECT = "target.object" /* a target object to link to. This can be and object name or object.serial */
|
|
||||||
)
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user