Compare commits

..

28 Commits

Author SHA1 Message Date
b73a789dfe
.clang-format: increase indent width
All checks were successful
Test / Create distribution (push) Successful in 26s
Test / Sandbox (push) Successful in 2m27s
Test / Hakurei (push) Successful in 3m17s
Test / Hpkg (push) Successful in 3m27s
Test / Sandbox (race detector) (push) Successful in 4m21s
Test / Hakurei (race detector) (push) Successful in 4m59s
Test / Flake checks (push) Successful in 1m31s
This significantly increases readability. This patch is pretty big so it is being done after mostly everything has settled.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-16 20:57:29 +09:00
38b5ff0cec
internal/wayland: check pathname size
All checks were successful
Test / Create distribution (push) Successful in 26s
Test / Sandbox (push) Successful in 39s
Test / Sandbox (race detector) (push) Successful in 39s
Test / Hakurei (push) Successful in 43s
Test / Hakurei (race detector) (push) Successful in 43s
Test / Hpkg (push) Successful in 41s
Test / Flake checks (push) Successful in 1m22s
This avoids passing a truncated pathname to the kernel.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-16 03:34:05 +09:00
3c204b9b40
internal/wayland: increase error detail
All checks were successful
Test / Create distribution (push) Successful in 35s
Test / Sandbox (push) Successful in 2m14s
Test / Hakurei (push) Successful in 3m16s
Test / Hpkg (push) Successful in 4m5s
Test / Sandbox (race detector) (push) Successful in 4m18s
Test / Hakurei (race detector) (push) Successful in 5m8s
Test / Flake checks (push) Successful in 1m21s
This includes targeted paths in the returned errors.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-16 02:09:50 +09:00
00771efeb4
internal/wayland: remove fd typecasts
All checks were successful
Test / Create distribution (push) Successful in 35s
Test / Sandbox (push) Successful in 2m21s
Test / Hakurei (push) Successful in 3m14s
Test / Hpkg (push) Successful in 4m2s
Test / Sandbox (race detector) (push) Successful in 4m18s
Test / Hakurei (race detector) (push) Successful in 5m5s
Test / Flake checks (push) Successful in 1m24s
These are no longer necessary since RawConn is no longer used.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-16 01:45:37 +09:00
61972d61f6
internal/wayland: reimplement connect/bind code
All checks were successful
Test / Create distribution (push) Successful in 36s
Test / Sandbox (push) Successful in 2m13s
Test / Hakurei (push) Successful in 3m18s
Test / Hpkg (push) Successful in 4m9s
Test / Sandbox (race detector) (push) Successful in 4m14s
Test / Hakurei (race detector) (push) Successful in 5m7s
Test / Flake checks (push) Successful in 1m26s
The old implementation is relocated to system/wayland/deprecated.go.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-16 01:23:16 +09:00
fe40af7b7e
internal/wayland: relocate connection struct
All checks were successful
Test / Create distribution (push) Successful in 44s
Test / Sandbox (push) Successful in 2m24s
Test / Hakurei (push) Successful in 3m23s
Test / Hpkg (push) Successful in 4m14s
Test / Sandbox (race detector) (push) Successful in 4m24s
Test / Hakurei (race detector) (push) Successful in 5m20s
Test / Flake checks (push) Successful in 1m33s
This interface is getting replaced, so relocating it to the deprecated wrapper package before working on its replacement.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-15 23:25:46 +09:00
12751932d1
internal/wayland: improve error handling
All checks were successful
Test / Create distribution (push) Successful in 36s
Test / Sandbox (push) Successful in 2m21s
Test / Hakurei (push) Successful in 3m20s
Test / Hpkg (push) Successful in 4m9s
Test / Sandbox (race detector) (push) Successful in 4m24s
Test / Hakurei (race detector) (push) Successful in 5m16s
Test / Flake checks (push) Successful in 1m32s
Note: wl_registry_add_listener is undocumented everywhere. Its implementation calls wl_proxy_add_listener which returns 0 on success or -1 on failure.
Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-15 21:26:31 +09:00
41b49137a8
.clang-format: do not limit line length
All checks were successful
Test / Create distribution (push) Successful in 38s
Test / Sandbox (push) Successful in 2m18s
Test / Hakurei (push) Successful in 3m15s
Test / Hpkg (push) Successful in 4m5s
Test / Sandbox (race detector) (push) Successful in 4m26s
Test / Hakurei (race detector) (push) Successful in 5m10s
Test / Flake checks (push) Successful in 1m27s
This hard limit destroys readability in some places.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-15 17:06:43 +09:00
c761e1de4d
nix: build with clang
All checks were successful
Test / Create distribution (push) Successful in 36s
Test / Sandbox (push) Successful in 41s
Test / Sandbox (race detector) (push) Successful in 41s
Test / Hakurei (push) Successful in 44s
Test / Hakurei (race detector) (push) Successful in 45s
Test / Hpkg (push) Successful in 42s
Test / Flake checks (push) Successful in 1m29s
Clang is better than gcc in various ways. This also pulls in clang-format which is very helpful.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-15 16:36:36 +09:00
a91920310d
internal: relocate packages
All checks were successful
Test / Create distribution (push) Successful in 36s
Test / Sandbox (push) Successful in 2m20s
Test / Hakurei (push) Successful in 3m19s
Test / Hpkg (push) Successful in 4m12s
Test / Sandbox (race detector) (push) Successful in 4m31s
Test / Hakurei (race detector) (push) Successful in 5m12s
Test / Flake checks (push) Successful in 1m32s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-15 13:58:34 +09:00
16e674782a
cmd/hakurei: reorder show entries
All checks were successful
Test / Create distribution (push) Successful in 25s
Test / Sandbox (race detector) (push) Successful in 39s
Test / Sandbox (push) Successful in 40s
Test / Hakurei (push) Successful in 44s
Test / Hakurei (race detector) (push) Successful in 43s
Test / Hpkg (push) Successful in 41s
Test / Flake checks (push) Successful in 1m20s
This order semantically makes more sense and generally looks tidier.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-14 22:14:49 +09:00
47244daefb
treewide: migrate ldd callers
All checks were successful
Test / Create distribution (push) Successful in 34s
Test / Sandbox (push) Successful in 2m22s
Test / Hakurei (push) Successful in 3m17s
Test / Hpkg (push) Successful in 4m5s
Test / Sandbox (race detector) (push) Successful in 4m15s
Test / Hakurei (race detector) (push) Successful in 5m6s
Test / Flake checks (push) Successful in 1m22s
This discontinues use of the deprecated ldd.Exec function for #25.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-14 21:59:59 +09:00
46fa104419
ldd: require absolute pathname
All checks were successful
Test / Create distribution (push) Successful in 36s
Test / Sandbox (push) Successful in 2m22s
Test / Hakurei (push) Successful in 3m18s
Test / Hpkg (push) Successful in 4m1s
Test / Sandbox (race detector) (push) Successful in 4m12s
Test / Hakurei (race detector) (push) Successful in 5m7s
Test / Flake checks (push) Successful in 1m23s
The sandbox which ldd(1) runs in does not inherit parent work directory, so relative pathnames will not work correctly. While it is trivial to support such a use case, the use of relative pathnames is highly error-prone and generally frowned against in this project. The Exec function remains available under the same signature until v0.4.0 where it will be removed.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-14 21:53:10 +09:00
45953b3d9c
ldd: cancel on decoder error
All checks were successful
Test / Create distribution (push) Successful in 34s
Test / Sandbox (push) Successful in 2m20s
Test / Hakurei (push) Successful in 3m14s
Test / Hpkg (push) Successful in 4m8s
Test / Sandbox (race detector) (push) Successful in 4m22s
Test / Hakurei (race detector) (push) Successful in 5m7s
Test / Flake checks (push) Successful in 1m28s
This prevents blocking from failures caused by ldd(1) emitting output that is not anticipated by the decoder.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-14 21:43:34 +09:00
42759e7a9f
ldd: create musl entry representation
All checks were successful
Test / Create distribution (push) Successful in 35s
Test / Sandbox (push) Successful in 2m16s
Test / Hakurei (push) Successful in 3m15s
Test / Hpkg (push) Successful in 3m59s
Test / Sandbox (race detector) (push) Successful in 4m15s
Test / Hakurei (race detector) (push) Successful in 5m4s
Test / Flake checks (push) Successful in 1m39s
This mostly helps with debugging.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-14 21:38:35 +09:00
8e2d2c8246
ldd: check decoder scan guard
All checks were successful
Test / Create distribution (push) Successful in 38s
Test / Sandbox (push) Successful in 2m19s
Test / Hakurei (push) Successful in 3m23s
Test / Hpkg (push) Successful in 4m8s
Test / Sandbox (race detector) (push) Successful in 4m19s
Test / Hakurei (race detector) (push) Successful in 5m12s
Test / Flake checks (push) Successful in 1m27s
This was unreachable via the Parse wrapper.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-14 18:32:47 +09:00
299685775a
container: provide usage example
All checks were successful
Test / Create distribution (push) Successful in 39s
Test / Sandbox (push) Successful in 2m17s
Test / Hakurei (push) Successful in 3m15s
Test / Sandbox (race detector) (push) Successful in 4m12s
Test / Hpkg (push) Successful in 4m16s
Test / Hakurei (race detector) (push) Successful in 5m7s
Test / Flake checks (push) Successful in 1m29s
This requires cgo so unfortunately will not run in the playground.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-14 18:25:22 +09:00
b7406cc4c4
ldd: update package doc comment
All checks were successful
Test / Create distribution (push) Successful in 36s
Test / Sandbox (push) Successful in 2m19s
Test / Hakurei (push) Successful in 3m18s
Test / Sandbox (race detector) (push) Successful in 4m16s
Test / Hpkg (push) Successful in 4m14s
Test / Hakurei (race detector) (push) Successful in 5m7s
Test / Flake checks (push) Successful in 1m34s
This should hopefully deter misuse of this package.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-14 17:49:01 +09:00
690a0ed0d6
ldd: decode from reader
All checks were successful
Test / Create distribution (push) Successful in 34s
Test / Sandbox (push) Successful in 2m27s
Test / Hakurei (push) Successful in 3m20s
Test / Hpkg (push) Successful in 4m10s
Test / Sandbox (race detector) (push) Successful in 4m18s
Test / Hakurei (race detector) (push) Successful in 5m5s
Test / Flake checks (push) Successful in 1m31s
This should reduce memory footprint of the parsing process and allow decoding part of the stream.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-14 08:33:19 +09:00
a9d72a5eb1
internal/outcome: rename run from main
All checks were successful
Test / Create distribution (push) Successful in 34s
Test / Sandbox (push) Successful in 2m31s
Test / Hakurei (push) Successful in 3m23s
Test / Sandbox (race detector) (push) Successful in 4m14s
Test / Hpkg (push) Successful in 4m16s
Test / Hakurei (race detector) (push) Successful in 5m4s
Test / Flake checks (push) Successful in 1m30s
The "main.go" name is quite confusing as this is often only present in main packages.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-14 01:06:14 +09:00
6d14bb814f
container/fhs: add constant for /dev/shm/
All checks were successful
Test / Create distribution (push) Successful in 36s
Test / Sandbox (push) Successful in 2m21s
Test / Hakurei (push) Successful in 3m16s
Test / Hpkg (push) Successful in 4m11s
Test / Sandbox (race detector) (push) Successful in 4m12s
Test / Hakurei (race detector) (push) Successful in 5m6s
Test / Flake checks (push) Successful in 1m32s
This is mounted for the default read-only /dev/ when programs want to use shm_open(3). Defining it here is less error-prone and saves the extra append at runtime.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-14 01:03:26 +09:00
be0e387ab0
internal/info: relocate from internal
All checks were successful
Test / Create distribution (push) Successful in 35s
Test / Sandbox (push) Successful in 2m27s
Test / Hakurei (push) Successful in 3m15s
Test / Hpkg (push) Successful in 4m11s
Test / Sandbox (race detector) (push) Successful in 4m18s
Test / Hakurei (race detector) (push) Successful in 5m2s
Test / Flake checks (push) Successful in 1m30s
This is cleaner and makes more sense. The longer LDFLAGS was never a valid concern since it is always inserted by a script.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-13 07:29:46 +09:00
abeb67964f
treewide: document linkname uses
All checks were successful
Test / Create distribution (push) Successful in 34s
Test / Sandbox (push) Successful in 2m17s
Test / Hakurei (push) Successful in 3m15s
Test / Hpkg (push) Successful in 4m6s
Test / Sandbox (race detector) (push) Successful in 4m12s
Test / Hakurei (race detector) (push) Successful in 5m2s
Test / Flake checks (push) Successful in 1m26s
These provide justification for each use of linkname. Poorly thought out uses of linkname are removed.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-13 07:14:16 +09:00
bf5d10743f
treewide: import internal/system
All checks were successful
Test / Create distribution (push) Successful in 34s
Test / Sandbox (push) Successful in 2m18s
Test / Hakurei (push) Successful in 3m15s
Test / Hpkg (push) Successful in 4m12s
Test / Sandbox (race detector) (push) Successful in 4m16s
Test / Hakurei (race detector) (push) Successful in 5m8s
Test / Flake checks (push) Successful in 1m31s
For #24.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-13 01:22:47 +09:00
4e7aab07d5
internal/system: relocate from system
All checks were successful
Test / Create distribution (push) Successful in 35s
Test / Sandbox (push) Successful in 2m18s
Test / Hakurei (push) Successful in 3m17s
Test / Sandbox (race detector) (push) Successful in 4m7s
Test / Hpkg (push) Successful in 4m13s
Test / Hakurei (race detector) (push) Successful in 5m3s
Test / Flake checks (push) Successful in 1m40s
These packages are highly specific to hakurei and are difficult to use safely from other pieces of code.

Their exported symbols are made available until v0.4.0 where they will be removed for #24.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-13 01:17:47 +09:00
15a66a2b31
treewide: import internal/helper
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Sandbox (push) Successful in 2m15s
Test / Hakurei (push) Successful in 3m15s
Test / Hpkg (push) Successful in 4m4s
Test / Sandbox (race detector) (push) Successful in 4m8s
Test / Hakurei (race detector) (push) Successful in 5m3s
Test / Flake checks (push) Successful in 1m27s
For #24.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-12 23:19:34 +09:00
f347d44c22
internal/helper: relocate from helper
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Sandbox (push) Successful in 2m26s
Test / Hakurei (push) Successful in 3m15s
Test / Hpkg (push) Successful in 4m8s
Test / Sandbox (race detector) (push) Successful in 4m16s
Test / Hakurei (race detector) (push) Successful in 5m5s
Test / Flake checks (push) Successful in 1m23s
This package is ugly and is pending removal only kept alive by xdg-dbus-proxy.

Its exported symbols are made available until v0.4.0 where it will be removed for #24.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-12 23:16:13 +09:00
b5630f6883
test: move package sandbox internal
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Hakurei (push) Successful in 43s
Test / Hpkg (push) Successful in 40s
Test / Hakurei (race detector) (push) Successful in 43s
Test / Sandbox (push) Successful in 1m56s
Test / Sandbox (race detector) (push) Successful in 2m39s
Test / Flake checks (push) Successful in 1m24s
This should never be used outside vm tests.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-12 23:03:22 +09:00
141 changed files with 2211 additions and 1138 deletions

2
.clang-format Normal file
View File

@ -0,0 +1,2 @@
ColumnLimit: 0
IndentWidth: 4

View File

@ -11,21 +11,24 @@ import (
"strconv" "strconv"
"sync" "sync"
"time" "time"
_ "unsafe" _ "unsafe" // for go:linkname
"hakurei.app/command" "hakurei.app/command"
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/container/fhs" "hakurei.app/container/fhs"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal" "hakurei.app/internal/dbus"
"hakurei.app/internal/env" "hakurei.app/internal/env"
"hakurei.app/internal/info"
"hakurei.app/internal/outcome" "hakurei.app/internal/outcome"
"hakurei.app/message" "hakurei.app/message"
"hakurei.app/system/dbus"
) )
// optionalErrorUnwrap calls [errors.Unwrap] and returns the resulting value
// if it is not nil, or the original value if it is.
//
//go:linkname optionalErrorUnwrap hakurei.app/container.optionalErrorUnwrap //go:linkname optionalErrorUnwrap hakurei.app/container.optionalErrorUnwrap
func optionalErrorUnwrap(_ error) error func optionalErrorUnwrap(err error) error
func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErrs, out io.Writer) command.Command { func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErrs, out io.Writer) command.Command {
var ( var (
@ -350,7 +353,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
}).Flag(&flagShort, "short", command.BoolFlag(false), "Print instance id") }).Flag(&flagShort, "short", command.BoolFlag(false), "Print instance id")
} }
c.Command("version", "Display version information", func(args []string) error { fmt.Println(internal.Version()); return errSuccess }) c.Command("version", "Display version information", func(args []string) error { fmt.Println(info.Version()); return errSuccess })
c.Command("license", "Show full license text", func(args []string) error { fmt.Println(license); return errSuccess }) c.Command("license", "Show full license text", func(args []string) error { fmt.Println(license); return errSuccess })
c.Command("template", "Produce a config template", func(args []string) error { encodeJSON(log.Fatal, os.Stdout, false, hst.Template()); return errSuccess }) c.Command("template", "Produce a config template", func(args []string) error { encodeJSON(log.Fatal, os.Stdout, false, hst.Template()); return errSuccess })
c.Command("help", "Show this help message", func([]string) error { c.PrintHelp(); return errSuccess }) c.Command("help", "Show this help message", func([]string) error { c.PrintHelp(); return errSuccess })

View File

@ -1,18 +1,13 @@
package main_test package main
import ( import (
"io"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
_ "unsafe"
"hakurei.app/container/stub" "hakurei.app/container/stub"
) )
//go:linkname decodeJSON hakurei.app/cmd/hakurei.decodeJSON
func decodeJSON(fatal func(v ...any), op string, r io.Reader, v any)
func TestDecodeJSON(t *testing.T) { func TestDecodeJSON(t *testing.T) {
t.Parallel() t.Parallel()
@ -62,9 +57,6 @@ func TestDecodeJSON(t *testing.T) {
} }
} }
//go:linkname encodeJSON hakurei.app/cmd/hakurei.encodeJSON
func encodeJSON(fatal func(v ...any), output io.Writer, short bool, v any)
func TestEncodeJSON(t *testing.T) { func TestEncodeJSON(t *testing.T) {
t.Parallel() t.Parallel()
@ -74,7 +66,7 @@ func TestEncodeJSON(t *testing.T) {
want string want string
}{ }{
{"marshaler", errorJSONMarshaler{}, {"marshaler", errorJSONMarshaler{},
`cannot encode json for main_test.errorJSONMarshaler: unique error 3735928559 injected by the test suite`}, `cannot encode json for main.errorJSONMarshaler: unique error 3735928559 injected by the test suite`},
{"default", func() {}, {"default", func() {},
`cannot write json: json: unsupported type: func()`}, `cannot write json: json: unsupported type: func()`},
} }

View File

@ -12,8 +12,8 @@ import (
"time" "time"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal"
"hakurei.app/internal/env" "hakurei.app/internal/env"
"hakurei.app/internal/info"
"hakurei.app/internal/outcome" "hakurei.app/internal/outcome"
"hakurei.app/internal/store" "hakurei.app/internal/store"
"hakurei.app/message" "hakurei.app/message"
@ -24,20 +24,20 @@ func printShowSystem(output io.Writer, short, flagJSON bool) {
t := newPrinter(output) t := newPrinter(output)
defer t.MustFlush() defer t.MustFlush()
info := &hst.Info{Version: internal.Version(), User: new(outcome.Hsu).MustID(nil)} hi := &hst.Info{Version: info.Version(), User: new(outcome.Hsu).MustID(nil)}
env.CopyPaths().Copy(&info.Paths, info.User) env.CopyPaths().Copy(&hi.Paths, hi.User)
if flagJSON { if flagJSON {
encodeJSON(log.Fatal, output, short, info) encodeJSON(log.Fatal, output, short, hi)
return return
} }
t.Printf("Version:\t%s\n", info.Version) t.Printf("Version:\t%s\n", hi.Version)
t.Printf("User:\t%d\n", info.User) t.Printf("User:\t%d\n", hi.User)
t.Printf("TempDir:\t%s\n", info.TempDir) t.Printf("TempDir:\t%s\n", hi.TempDir)
t.Printf("SharePath:\t%s\n", info.SharePath) t.Printf("SharePath:\t%s\n", hi.SharePath)
t.Printf("RuntimePath:\t%s\n", info.RuntimePath) t.Printf("RuntimePath:\t%s\n", hi.RuntimePath)
t.Printf("RunDirPath:\t%s\n", info.RunDirPath) t.Printf("RunDirPath:\t%s\n", hi.RunDirPath)
} }
// printShowInstance writes a representation of [hst.State] or [hst.Config] to output. // printShowInstance writes a representation of [hst.State] or [hst.Config] to output.
@ -90,12 +90,6 @@ func printShowInstance(
t.Printf(" Groups:\t%s\n", strings.Join(config.Groups, ", ")) t.Printf(" Groups:\t%s\n", strings.Join(config.Groups, ", "))
} }
if config.Container != nil { if config.Container != nil {
if config.Container.Home != nil {
t.Printf(" Home:\t%s\n", config.Container.Home)
}
if config.Container.Hostname != "" {
t.Printf(" Hostname:\t%s\n", config.Container.Hostname)
}
flags := config.Container.Flags.String() flags := config.Container.Flags.String()
// this is included in the upper hst.Config struct but is relevant here // this is included in the upper hst.Config struct but is relevant here
@ -110,6 +104,12 @@ func printShowInstance(
} }
t.Printf(" Flags:\t%s\n", flags) t.Printf(" Flags:\t%s\n", flags)
if config.Container.Home != nil {
t.Printf(" Home:\t%s\n", config.Container.Home)
}
if config.Container.Hostname != "" {
t.Printf(" Hostname:\t%s\n", config.Container.Hostname)
}
if config.Container.Path != nil { if config.Container.Path != nil {
t.Printf(" Path:\t%s\n", config.Container.Path) t.Printf(" Path:\t%s\n", config.Container.Path)
} }

View File

@ -64,9 +64,9 @@ func TestPrintShowInstance(t *testing.T) {
Identity: 9 (org.chromium.Chromium) Identity: 9 (org.chromium.Chromium)
Enablements: wayland, dbus, pulseaudio Enablements: wayland, dbus, pulseaudio
Groups: video, dialout, plugdev Groups: video, dialout, plugdev
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir
Home: /data/data/org.chromium.Chromium Home: /data/data/org.chromium.Chromium
Hostname: localhost Hostname: localhost
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir
Path: /run/current-system/sw/bin/chromium Path: /run/current-system/sw/bin/chromium
Arguments: chromium --ignore-gpu-blocklist --disable-smooth-scrolling --enable-features=UseOzonePlatform --ozone-platform=wayland Arguments: chromium --ignore-gpu-blocklist --disable-smooth-scrolling --enable-features=UseOzonePlatform --ozone-platform=wayland
@ -161,9 +161,9 @@ App
Identity: 9 (org.chromium.Chromium) Identity: 9 (org.chromium.Chromium)
Enablements: wayland, dbus, pulseaudio Enablements: wayland, dbus, pulseaudio
Groups: video, dialout, plugdev Groups: video, dialout, plugdev
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir
Home: /data/data/org.chromium.Chromium Home: /data/data/org.chromium.Chromium
Hostname: localhost Hostname: localhost
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir
Path: /run/current-system/sw/bin/chromium Path: /run/current-system/sw/bin/chromium
Arguments: chromium --ignore-gpu-blocklist --disable-smooth-scrolling --enable-features=UseOzonePlatform --ozone-platform=wayland Arguments: chromium --ignore-gpu-blocklist --disable-smooth-scrolling --enable-features=UseOzonePlatform --ozone-platform=wayland

View File

@ -10,11 +10,11 @@ import (
"os/exec" "os/exec"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal" "hakurei.app/internal/info"
"hakurei.app/message" "hakurei.app/message"
) )
var hakureiPathVal = internal.MustHakureiPath().String() var hakureiPathVal = info.MustHakureiPath().String()
func mustRunApp(ctx context.Context, msg message.Msg, config *hst.Config, beforeFail func()) { func mustRunApp(ctx context.Context, msg message.Msg, config *hst.Config, beforeFail func()) {
var ( var (

View File

@ -56,7 +56,7 @@ func NewAbs(pathname string) (*Absolute, error) {
// MustAbs calls [NewAbs] and panics on error. // MustAbs calls [NewAbs] and panics on error.
func MustAbs(pathname string) *Absolute { func MustAbs(pathname string) *Absolute {
if a, err := NewAbs(pathname); err != nil { if a, err := NewAbs(pathname); err != nil {
panic(err.Error()) panic(err)
} else { } else {
return a return a
} }

View File

@ -14,8 +14,10 @@ import (
. "hakurei.app/container/check" . "hakurei.app/container/check"
) )
// unsafeAbs returns check.Absolute on any string value.
//
//go:linkname unsafeAbs hakurei.app/container/check.unsafeAbs //go:linkname unsafeAbs hakurei.app/container/check.unsafeAbs
func unsafeAbs(_ string) *Absolute func unsafeAbs(pathname string) *Absolute
func TestAbsoluteError(t *testing.T) { func TestAbsoluteError(t *testing.T) {
t.Parallel() t.Parallel()
@ -82,9 +84,9 @@ func TestNewAbs(t *testing.T) {
t.Parallel() t.Parallel()
defer func() { defer func() {
wantPanic := `path "etc" is not absolute` wantPanic := &AbsoluteError{Pathname: "etc"}
if r := recover(); 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)
} }
}() }()

View File

@ -21,6 +21,7 @@ import (
"hakurei.app/command" "hakurei.app/command"
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/container/check" "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/container/vfs" "hakurei.app/container/vfs"
@ -29,6 +30,45 @@ import (
"hakurei.app/message" "hakurei.app/message"
) )
// Note: this package requires cgo, which is unavailable in the Go playground.
func Example() {
// Must be called early if the current process starts containers.
container.TryArgv0(nil)
// Configure the container.
z := container.New(context.Background(), nil)
z.Hostname = "hakurei-example"
z.Proc(fhs.AbsProc).Dev(fhs.AbsDev, true)
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr
// Bind / for demonstration.
z.Bind(fhs.AbsRoot, fhs.AbsRoot, 0)
if name, err := exec.LookPath("hostname"); err != nil {
panic(err)
} else {
z.Path = check.MustAbs(name)
}
// This completes the first stage of container setup and starts the container init process.
// The new process blocks until the Serve method is called.
if err := z.Start(); err != nil {
panic(err)
}
// This serves the setup payload to the container init process,
// starting the second stage of container setup.
if err := z.Serve(); err != nil {
panic(err)
}
// Must be called if the Start method succeeds.
if err := z.Wait(); err != nil {
panic(err)
}
// Output: hakurei-example
}
func TestStartError(t *testing.T) { func TestStartError(t *testing.T) {
t.Parallel() t.Parallel()
@ -722,12 +762,14 @@ func TestMain(m *testing.M) {
func helperNewContainerLibPaths(ctx context.Context, libPaths *[]*check.Absolute, args ...string) (c *container.Container) { func helperNewContainerLibPaths(ctx context.Context, libPaths *[]*check.Absolute, args ...string) (c *container.Container) {
msg := message.New(nil) msg := message.New(nil)
msg.SwapVerbose(testing.Verbose()) msg.SwapVerbose(testing.Verbose())
executable := check.MustAbs(container.MustExecutable(msg))
c = container.NewCommand(ctx, msg, absHelperInnerPath, "helper", args...) c = container.NewCommand(ctx, msg, absHelperInnerPath, "helper", args...)
c.Env = append(c.Env, envDoCheck+"=1") c.Env = append(c.Env, envDoCheck+"=1")
c.Bind(check.MustAbs(os.Args[0]), absHelperInnerPath, 0) c.Bind(executable, absHelperInnerPath, 0)
// in case test has cgo enabled // in case test has cgo enabled
if entries, err := ldd.Exec(ctx, msg, os.Args[0]); err != nil { if entries, err := ldd.Resolve(ctx, msg, executable); err != nil {
log.Fatalf("ldd: %v", err) log.Fatalf("ldd: %v", err)
} else { } else {
*libPaths = ldd.Path(entries) *libPaths = ldd.Path(entries)

View File

@ -8,8 +8,10 @@ import (
/* constants in this file bypass abs check, be extremely careful when changing them! */ /* constants in this file bypass abs check, be extremely careful when changing them! */
// unsafeAbs returns check.Absolute on any string value.
//
//go:linkname unsafeAbs hakurei.app/container/check.unsafeAbs //go:linkname unsafeAbs hakurei.app/container/check.unsafeAbs
func unsafeAbs(_ string) *check.Absolute func unsafeAbs(pathname string) *check.Absolute
var ( var (
// AbsRoot is [Root] as [check.Absolute]. // AbsRoot is [Root] as [check.Absolute].
@ -34,6 +36,8 @@ var (
// AbsDev is [Dev] as [check.Absolute]. // AbsDev is [Dev] as [check.Absolute].
AbsDev = unsafeAbs(Dev) AbsDev = unsafeAbs(Dev)
// AbsDevShm is [DevShm] as [check.Absolute].
AbsDevShm = unsafeAbs(DevShm)
// AbsProc is [Proc] as [check.Absolute]. // AbsProc is [Proc] as [check.Absolute].
AbsProc = unsafeAbs(Proc) AbsProc = unsafeAbs(Proc)
// AbsSys is [Sys] as [check.Absolute]. // AbsSys is [Sys] as [check.Absolute].

View File

@ -29,6 +29,8 @@ const (
// Dev points to the root directory for device nodes. // Dev points to the root directory for device nodes.
Dev = "/dev/" Dev = "/dev/"
// DevShm is the place for POSIX shared memory segments, as created via shm_open(3).
DevShm = "/dev/shm/"
// Proc points to a virtual kernel file system exposing the process list and other functionality. // Proc points to a virtual kernel file system exposing the process list and other functionality.
Proc = "/proc/" Proc = "/proc/"
// ProcSys points to a hierarchy below /proc/ that exposes a number of kernel tunables. // ProcSys points to a hierarchy below /proc/ that exposes a number of kernel tunables.

View File

@ -9,136 +9,130 @@
#define LEN(arr) (sizeof(arr) / sizeof((arr)[0])) #define LEN(arr) (sizeof(arr) / sizeof((arr)[0]))
int32_t hakurei_scmp_make_filter(int *ret_p, uintptr_t allocate_p, int32_t hakurei_scmp_make_filter(
uint32_t arch, uint32_t multiarch, int *ret_p, uintptr_t allocate_p,
struct hakurei_syscall_rule *rules, uint32_t arch, uint32_t multiarch,
size_t rules_sz, hakurei_export_flag flags) { struct hakurei_syscall_rule *rules,
int i; size_t rules_sz, hakurei_export_flag flags) {
int last_allowed_family; int i;
int disallowed; int last_allowed_family;
struct hakurei_syscall_rule *rule; int disallowed;
void *buf; struct hakurei_syscall_rule *rule;
size_t len = 0; void *buf;
size_t len = 0;
int32_t res = 0; /* refer to resPrefix for message */ int32_t res = 0; /* refer to resPrefix for message */
/* Blocklist all but unix, inet, inet6 and netlink */ /* Blocklist all but unix, inet, inet6 and netlink */
struct { struct {
int family; int family;
hakurei_export_flag flags_mask; hakurei_export_flag flags_mask;
} socket_family_allowlist[] = { } socket_family_allowlist[] = {
/* NOTE: Keep in numerical order */ /* NOTE: Keep in numerical order */
{AF_UNSPEC, 0}, {AF_UNSPEC, 0},
{AF_LOCAL, 0}, {AF_LOCAL, 0},
{AF_INET, 0}, {AF_INET, 0},
{AF_INET6, 0}, {AF_INET6, 0},
{AF_NETLINK, 0}, {AF_NETLINK, 0},
{AF_CAN, HAKUREI_EXPORT_CAN}, {AF_CAN, HAKUREI_EXPORT_CAN},
{AF_BLUETOOTH, HAKUREI_EXPORT_BLUETOOTH}, {AF_BLUETOOTH, HAKUREI_EXPORT_BLUETOOTH},
}; };
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ALLOW); scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ALLOW);
if (ctx == NULL) { if (ctx == NULL) {
res = 1; res = 1;
goto out;
} else
errno = 0;
/* We only really need to handle arches on multiarch systems.
* If only one arch is supported the default is fine */
if (arch != 0) {
/* This *adds* the target arch, instead of replacing the
* native one. This is not ideal, because we'd like to only
* allow the target arch, but we can't really disallow the
* native arch at this point, because then bubblewrap
* couldn't continue running. */
*ret_p = seccomp_arch_add(ctx, arch);
if (*ret_p < 0 && *ret_p != -EEXIST) {
res = 2;
goto out;
}
if (flags & HAKUREI_EXPORT_MULTIARCH && multiarch != 0) {
*ret_p = seccomp_arch_add(ctx, multiarch);
if (*ret_p < 0 && *ret_p != -EEXIST) {
res = 3;
goto out; goto out;
} } else
} errno = 0;
}
for (i = 0; i < rules_sz; i++) { /* We only really need to handle arches on multiarch systems.
rule = &rules[i]; * If only one arch is supported the default is fine */
assert(rule->m_errno == EPERM || rule->m_errno == ENOSYS); if (arch != 0) {
/* This *adds* the target arch, instead of replacing the
* native one. This is not ideal, because we'd like to only
* allow the target arch, but we can't really disallow the
* native arch at this point, because then bubblewrap
* couldn't continue running. */
*ret_p = seccomp_arch_add(ctx, arch);
if (*ret_p < 0 && *ret_p != -EEXIST) {
res = 2;
goto out;
}
if (rule->arg) if (flags & HAKUREI_EXPORT_MULTIARCH && multiarch != 0) {
*ret_p = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(rule->m_errno), *ret_p = seccomp_arch_add(ctx, multiarch);
rule->syscall, 1, *rule->arg); if (*ret_p < 0 && *ret_p != -EEXIST) {
else res = 3;
*ret_p = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(rule->m_errno), goto out;
rule->syscall, 0); }
}
if (*ret_p == -EFAULT) {
res = 4;
goto out;
} else if (*ret_p < 0) {
res = 5;
goto out;
}
}
/* Socket filtering doesn't work on e.g. i386, so ignore failures here
* However, we need to user seccomp_rule_add_exact to avoid libseccomp doing
* something else: https://github.com/seccomp/libseccomp/issues/8 */
last_allowed_family = -1;
for (i = 0; i < LEN(socket_family_allowlist); i++) {
if (socket_family_allowlist[i].flags_mask != 0 &&
(socket_family_allowlist[i].flags_mask & flags) !=
socket_family_allowlist[i].flags_mask)
continue;
for (disallowed = last_allowed_family + 1;
disallowed < socket_family_allowlist[i].family; disallowed++) {
/* Blocklist the in-between valid families */
seccomp_rule_add_exact(ctx, SCMP_ACT_ERRNO(EAFNOSUPPORT),
SCMP_SYS(socket), 1,
SCMP_A0(SCMP_CMP_EQ, disallowed));
}
last_allowed_family = socket_family_allowlist[i].family;
}
/* Blocklist the rest */
seccomp_rule_add_exact(ctx, SCMP_ACT_ERRNO(EAFNOSUPPORT), SCMP_SYS(socket), 1,
SCMP_A0(SCMP_CMP_GE, last_allowed_family + 1));
if (allocate_p == 0) {
*ret_p = seccomp_load(ctx);
if (*ret_p != 0) {
res = 7;
goto out;
}
} else {
*ret_p = seccomp_export_bpf_mem(ctx, NULL, &len);
if (*ret_p != 0) {
res = 6;
goto out;
} }
buf = hakurei_scmp_allocate(allocate_p, len); for (i = 0; i < rules_sz; i++) {
if (buf == NULL) { rule = &rules[i];
res = 4; assert(rule->m_errno == EPERM || rule->m_errno == ENOSYS);
goto out;
if (rule->arg)
*ret_p = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(rule->m_errno), rule->syscall, 1, *rule->arg);
else
*ret_p = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(rule->m_errno), rule->syscall, 0);
if (*ret_p == -EFAULT) {
res = 4;
goto out;
} else if (*ret_p < 0) {
res = 5;
goto out;
}
} }
*ret_p = seccomp_export_bpf_mem(ctx, buf, &len); /* Socket filtering doesn't work on e.g. i386, so ignore failures here
if (*ret_p != 0) { * However, we need to user seccomp_rule_add_exact to avoid libseccomp doing
res = 6; * something else: https://github.com/seccomp/libseccomp/issues/8 */
goto out; last_allowed_family = -1;
for (i = 0; i < LEN(socket_family_allowlist); i++) {
if (socket_family_allowlist[i].flags_mask != 0 &&
(socket_family_allowlist[i].flags_mask & flags) != socket_family_allowlist[i].flags_mask)
continue;
for (disallowed = last_allowed_family + 1; disallowed < socket_family_allowlist[i].family; disallowed++) {
/* Blocklist the in-between valid families */
seccomp_rule_add_exact(ctx, SCMP_ACT_ERRNO(EAFNOSUPPORT), SCMP_SYS(socket), 1, SCMP_A0(SCMP_CMP_EQ, disallowed));
}
last_allowed_family = socket_family_allowlist[i].family;
}
/* Blocklist the rest */
seccomp_rule_add_exact(ctx, SCMP_ACT_ERRNO(EAFNOSUPPORT), SCMP_SYS(socket), 1, SCMP_A0(SCMP_CMP_GE, last_allowed_family + 1));
if (allocate_p == 0) {
*ret_p = seccomp_load(ctx);
if (*ret_p != 0) {
res = 7;
goto out;
}
} else {
*ret_p = seccomp_export_bpf_mem(ctx, NULL, &len);
if (*ret_p != 0) {
res = 6;
goto out;
}
buf = hakurei_scmp_allocate(allocate_p, len);
if (buf == NULL) {
res = 4;
goto out;
}
*ret_p = seccomp_export_bpf_mem(ctx, buf, &len);
if (*ret_p != 0) {
res = 6;
goto out;
}
} }
}
out: out:
if (ctx) if (ctx)
seccomp_release(ctx); seccomp_release(ctx);
return res; return res;
} }

View File

@ -1,25 +1,26 @@
#include <seccomp.h> #include <seccomp.h>
#include <stdint.h> #include <stdint.h>
#if (SCMP_VER_MAJOR < 2) || (SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 5) || \ #if (SCMP_VER_MAJOR < 2) || (SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 5) || \
(SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR == 5 && SCMP_VER_MICRO < 1) (SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR == 5 && SCMP_VER_MICRO < 1)
#error This package requires libseccomp >= v2.5.1 #error This package requires libseccomp >= v2.5.1
#endif #endif
typedef enum { typedef enum {
HAKUREI_EXPORT_MULTIARCH = 1 << 0, HAKUREI_EXPORT_MULTIARCH = 1 << 0,
HAKUREI_EXPORT_CAN = 1 << 1, HAKUREI_EXPORT_CAN = 1 << 1,
HAKUREI_EXPORT_BLUETOOTH = 1 << 2, HAKUREI_EXPORT_BLUETOOTH = 1 << 2,
} hakurei_export_flag; } hakurei_export_flag;
struct hakurei_syscall_rule { struct hakurei_syscall_rule {
int syscall; int syscall;
int m_errno; int m_errno;
struct scmp_arg_cmp *arg; struct scmp_arg_cmp *arg;
}; };
extern void *hakurei_scmp_allocate(uintptr_t f, size_t len); extern void *hakurei_scmp_allocate(uintptr_t f, size_t len);
int32_t hakurei_scmp_make_filter(int *ret_p, uintptr_t allocate_p, int32_t hakurei_scmp_make_filter(
uint32_t arch, uint32_t multiarch, int *ret_p, uintptr_t allocate_p,
struct hakurei_syscall_rule *rules, uint32_t arch, uint32_t multiarch,
size_t rules_sz, hakurei_export_flag flags); struct hakurei_syscall_rule *rules,
size_t rules_sz, hakurei_export_flag flags);

View File

@ -7,8 +7,10 @@ import (
"hakurei.app/container/stub" "hakurei.app/container/stub"
) )
// Made available here to check panic recovery behaviour.
//
//go:linkname handleExitNew hakurei.app/container/stub.handleExitNew //go:linkname handleExitNew hakurei.app/container/stub.handleExitNew
func handleExitNew(_ testing.TB) func handleExitNew(t testing.TB)
// overrideTFailNow overrides the Fail and FailNow method. // overrideTFailNow overrides the Fail and FailNow method.
type overrideTFailNow struct { type overrideTFailNow struct {

6
dist/release.sh vendored
View File

@ -10,9 +10,9 @@ cp -rv "dist/comp" "${out}"
go generate ./... go generate ./...
go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w -buildid= -extldflags '-static' go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w -buildid= -extldflags '-static'
-X hakurei.app/internal.buildVersion=${VERSION} -X hakurei.app/internal/info.buildVersion=${VERSION}
-X hakurei.app/internal.hakureiPath=/usr/bin/hakurei -X hakurei.app/internal/info.hakureiPath=/usr/bin/hakurei
-X hakurei.app/internal.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" ./...
rm -f "./${out}.tar.gz" && tar -C dist -czf "${out}.tar.gz" "${pname}" rm -f "./${out}.tar.gz" && tar -C dist -czf "${out}.tar.gz" "${pname}"

View File

@ -114,7 +114,7 @@
inherit (pkgs) inherit (pkgs)
# passthru.buildInputs # passthru.buildInputs
go go
gcc clang
# nativeBuildInputs # nativeBuildInputs
pkg-config pkg-config
@ -129,6 +129,10 @@
zstd zstd
gnutar gnutar
coreutils coreutils
# for check
util-linux
nettools
; ;
}; };
hsu = pkgs.callPackage ./cmd/hsu/package.nix { inherit (self.packages.${system}) hakurei; }; hsu = pkgs.callPackage ./cmd/hsu/package.nix { inherit (self.packages.${system}) hakurei; };
@ -144,7 +148,7 @@
&& chmod -R +w . && chmod -R +w .
export HAKUREI_VERSION="v${hakurei.version}" export HAKUREI_VERSION="v${hakurei.version}"
./dist/release.sh && mkdir $out && cp -v "dist/hakurei-$HAKUREI_VERSION.tar.gz"* $out CC="clang -O3 -Werror" ./dist/release.sh && mkdir $out && cp -v "dist/hakurei-$HAKUREI_VERSION.tar.gz"* $out
''; '';
} }
); );

73
helper/deprecated.go Normal file
View File

@ -0,0 +1,73 @@
// Package helper exposes the internal/helper package.
//
// Deprecated: This package will be removed in 0.4.
package helper
import (
"context"
"io"
"os"
"os/exec"
"time"
_ "unsafe" // for go:linkname
"hakurei.app/container"
"hakurei.app/container/check"
"hakurei.app/internal/helper"
"hakurei.app/message"
)
//go:linkname WaitDelay hakurei.app/internal/helper.WaitDelay
var WaitDelay time.Duration
const (
// HakureiHelper is set to 1 when args fd is enabled and 0 otherwise.
HakureiHelper = helper.HakureiHelper
// HakureiStatus is set to 1 when stat fd is enabled and 0 otherwise.
HakureiStatus = helper.HakureiStatus
)
type Helper = helper.Helper
// NewCheckedArgs returns a checked null-terminated argument writer for a copy of args.
//
//go:linkname NewCheckedArgs hakurei.app/internal/helper.NewCheckedArgs
func NewCheckedArgs(args ...string) (wt io.WriterTo, err error)
// MustNewCheckedArgs returns a checked null-terminated argument writer for a copy of args.
// If s contains a NUL byte this function panics instead of returning an error.
//
//go:linkname MustNewCheckedArgs hakurei.app/internal/helper.MustNewCheckedArgs
func MustNewCheckedArgs(args ...string) io.WriterTo
// NewDirect initialises a new direct Helper instance with wt as the null-terminated argument writer.
// Function argF returns an array of arguments passed directly to the child process.
//
//go:linkname NewDirect hakurei.app/internal/helper.NewDirect
func NewDirect(
ctx context.Context,
name string,
wt io.WriterTo,
stat bool,
argF func(argsFd, statFd int) []string,
cmdF func(cmd *exec.Cmd),
extraFiles []*os.File,
) Helper
// New initialises a Helper instance with wt as the null-terminated argument writer.
//
//go:linkname New hakurei.app/internal/helper.New
func New(
ctx context.Context,
msg message.Msg,
pathname *check.Absolute, name string,
wt io.WriterTo,
stat bool,
argF func(argsFd, statFd int) []string,
cmdF func(z *container.Container),
extraFiles []*os.File,
) Helper
// InternalHelperStub is an internal function but exported because it is cross-package;
// it is part of the implementation of the helper stub.
func InternalHelperStub() { helper.InternalHelperStub() }

63
helper/proc/deprecated.go Normal file
View File

@ -0,0 +1,63 @@
// Deprecated: This package will be removed in 0.4.
package proc
import (
"context"
"io"
"os"
"os/exec"
"time"
_ "unsafe" // for go:linkname
"hakurei.app/internal/helper/proc"
)
//go:linkname FulfillmentTimeout hakurei.app/internal/helper/proc.FulfillmentTimeout
var FulfillmentTimeout time.Duration
// A File is an extra file with deferred initialisation.
type File = proc.File
// ExtraFilesPre is a linked list storing addresses of [os.File].
type ExtraFilesPre = proc.ExtraFilesPre
// Fulfill calls the [File.Fulfill] method on all files, starts cmd and blocks until all fulfillment completes.
//
//go:linkname Fulfill hakurei.app/internal/helper/proc.Fulfill
func Fulfill(ctx context.Context,
v *[]*os.File, start func() error,
files []File, extraFiles *ExtraFilesPre,
) (err error)
// InitFile initialises f as part of the slice extraFiles points to,
// and returns its final fd value.
//
//go:linkname InitFile hakurei.app/internal/helper/proc.InitFile
func InitFile(f File, extraFiles *ExtraFilesPre) (fd uintptr)
// BaseFile implements the Init method of the File interface and provides indirect access to extra file state.
type BaseFile = proc.BaseFile
//go:linkname ExtraFile hakurei.app/internal/helper/proc.ExtraFile
func ExtraFile(cmd *exec.Cmd, f *os.File) (fd uintptr)
//go:linkname ExtraFileSlice hakurei.app/internal/helper/proc.ExtraFileSlice
func ExtraFileSlice(extraFiles *[]*os.File, f *os.File) (fd uintptr)
// NewWriterTo returns a [File] that receives content from wt on fulfillment.
//
//go:linkname NewWriterTo hakurei.app/internal/helper/proc.NewWriterTo
func NewWriterTo(wt io.WriterTo) File
// NewStat returns a [File] implementing the behaviour
// of the receiving end of xdg-dbus-proxy stat fd.
//
//go:linkname NewStat hakurei.app/internal/helper/proc.NewStat
func NewStat(s *io.Closer) File
var (
//go:linkname ErrStatFault hakurei.app/internal/helper/proc.ErrStatFault
ErrStatFault error
//go:linkname ErrStatRead hakurei.app/internal/helper/proc.ErrStatRead
ErrStatRead error
)

View File

@ -6,11 +6,13 @@ import (
"reflect" "reflect"
"testing" "testing"
"time" "time"
_ "unsafe" _ "unsafe" // for go:linkname
"hakurei.app/hst" "hakurei.app/hst"
) )
// Made available here to check time encoding behaviour of [hst.ID].
//
//go:linkname newInstanceID hakurei.app/hst.newInstanceID //go:linkname newInstanceID hakurei.app/hst.newInstanceID
func newInstanceID(id *hst.ID, p uint64) error func newInstanceID(id *hst.ID, p uint64) error

View File

@ -13,7 +13,7 @@ import (
"strconv" "strconv"
"testing" "testing"
"hakurei.app/system/acl" "hakurei.app/internal/acl"
) )
const testFileName = "acl.test" const testFileName = "acl.test"

View File

@ -0,0 +1,90 @@
#include "libacl-helper.h"
#include <acl/libacl.h>
#include <stdbool.h>
#include <stdlib.h>
#include <sys/acl.h>
int hakurei_acl_update_file_by_uid(const char *path_p, uid_t uid,
acl_perm_t *perms, size_t plen) {
int ret;
bool v;
int i;
acl_t acl;
acl_entry_t entry;
acl_tag_t tag_type;
void *qualifier_p;
acl_permset_t permset;
ret = -1; /* acl_get_file */
acl = acl_get_file(path_p, ACL_TYPE_ACCESS);
if (acl == NULL)
goto out;
/* prune entries by uid */
for (i = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); i == 1;
i = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry)) {
ret = -2; /* acl_get_tag_type */
if (acl_get_tag_type(entry, &tag_type) != 0)
goto out;
if (tag_type != ACL_USER)
continue;
ret = -3; /* acl_get_qualifier */
qualifier_p = acl_get_qualifier(entry);
if (qualifier_p == NULL)
goto out;
v = *(uid_t *)qualifier_p == uid;
acl_free(qualifier_p);
if (!v)
continue;
ret = -4; /* acl_delete_entry */
if (acl_delete_entry(acl, entry) != 0)
goto out;
}
if (plen == 0)
goto set;
ret = -5; /* acl_create_entry */
if (acl_create_entry(&acl, &entry) != 0)
goto out;
ret = -6; /* acl_get_permset */
if (acl_get_permset(entry, &permset) != 0)
goto out;
ret = -7; /* acl_add_perm */
for (i = 0; i < plen; i++) {
if (acl_add_perm(permset, perms[i]) != 0)
goto out;
}
ret = -8; /* acl_set_tag_type */
if (acl_set_tag_type(entry, ACL_USER) != 0)
goto out;
ret = -9; /* acl_set_qualifier */
if (acl_set_qualifier(entry, (void *)&uid) != 0)
goto out;
set:
ret = -10; /* acl_calc_mask */
if (acl_calc_mask(&acl) != 0)
goto out;
ret = -11; /* acl_valid */
if (acl_valid(acl) != 0)
goto out;
ret = -12; /* acl_set_file */
if (acl_set_file(path_p, ACL_TYPE_ACCESS, acl) == 0)
ret = 0;
out:
free((void *)path_p);
if (acl != NULL)
acl_free((void *)acl);
return ret;
}

View File

@ -3,7 +3,7 @@ package acl_test
import ( import (
"testing" "testing"
"hakurei.app/system/acl" "hakurei.app/internal/acl"
) )
func TestPerms(t *testing.T) { func TestPerms(t *testing.T) {

View File

@ -5,7 +5,7 @@ import (
"reflect" "reflect"
"testing" "testing"
"hakurei.app/system/dbus" "hakurei.app/internal/dbus"
) )
func TestParse(t *testing.T) { func TestParse(t *testing.T) {

View File

@ -7,7 +7,7 @@ import (
"testing" "testing"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/system/dbus" "hakurei.app/internal/dbus"
) )
func TestConfigArgs(t *testing.T) { func TestConfigArgs(t *testing.T) {

View File

@ -11,9 +11,9 @@ import (
"testing" "testing"
"time" "time"
"hakurei.app/helper" "hakurei.app/internal/dbus"
"hakurei.app/internal/helper"
"hakurei.app/message" "hakurei.app/message"
"hakurei.app/system/dbus"
) )
func TestFinalise(t *testing.T) { func TestFinalise(t *testing.T) {

View File

@ -12,7 +12,7 @@ import (
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/container/seccomp" "hakurei.app/container/seccomp"
"hakurei.app/container/std" "hakurei.app/container/std"
"hakurei.app/helper" "hakurei.app/internal/helper"
"hakurei.app/ldd" "hakurei.app/ldd"
) )
@ -54,7 +54,7 @@ func (p *Proxy) Start() error {
} }
var libPaths []*check.Absolute var libPaths []*check.Absolute
if entries, err := ldd.Exec(ctx, p.msg, toolPath.String()); err != nil { if entries, err := ldd.Resolve(ctx, p.msg, toolPath); err != nil {
return err return err
} else { } else {
libPaths = ldd.Path(entries) libPaths = ldd.Path(entries)

View File

@ -5,7 +5,7 @@ import (
"testing" "testing"
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/helper" "hakurei.app/internal/helper"
) )
func TestMain(m *testing.M) { container.TryArgv0(nil); helper.InternalHelperStub(); os.Exit(m.Run()) } func TestMain(m *testing.M) { container.TryArgv0(nil); helper.InternalHelperStub(); os.Exit(m.Run()) }

View File

@ -6,8 +6,8 @@ import (
"sync" "sync"
"syscall" "syscall"
"hakurei.app/helper"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/helper"
"hakurei.app/message" "hakurei.app/message"
) )

View File

@ -7,7 +7,7 @@ import (
"syscall" "syscall"
"testing" "testing"
"hakurei.app/helper" "hakurei.app/internal/helper"
) )
func TestArgsString(t *testing.T) { func TestArgsString(t *testing.T) {

View File

@ -10,7 +10,7 @@ import (
"sync" "sync"
"syscall" "syscall"
"hakurei.app/helper/proc" "hakurei.app/internal/helper/proc"
) )
// NewDirect initialises a new direct Helper instance with wt as the null-terminated argument writer. // NewDirect initialises a new direct Helper instance with wt as the null-terminated argument writer.

View File

@ -9,7 +9,7 @@ import (
"testing" "testing"
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/helper" "hakurei.app/internal/helper"
) )
func TestCmd(t *testing.T) { func TestCmd(t *testing.T) {

View File

@ -11,7 +11,7 @@ import (
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/helper/proc" "hakurei.app/internal/helper/proc"
"hakurei.app/message" "hakurei.app/message"
) )

View File

@ -9,7 +9,7 @@ import (
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/container/fhs" "hakurei.app/container/fhs"
"hakurei.app/helper" "hakurei.app/internal/helper"
) )
func TestContainer(t *testing.T) { func TestContainer(t *testing.T) {

View File

@ -8,7 +8,7 @@ import (
"os" "os"
"time" "time"
"hakurei.app/helper/proc" "hakurei.app/internal/helper/proc"
) )
var WaitDelay = 2 * time.Second var WaitDelay = 2 * time.Second

View File

@ -13,7 +13,7 @@ import (
"testing" "testing"
"time" "time"
"hakurei.app/helper" "hakurei.app/internal/helper"
) )
var ( var (

View File

@ -5,7 +5,7 @@ import (
"testing" "testing"
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/helper" "hakurei.app/internal/helper"
) )
func TestMain(m *testing.M) { container.TryArgv0(nil); helper.InternalHelperStub(); os.Exit(m.Run()) } func TestMain(m *testing.M) { container.TryArgv0(nil); helper.InternalHelperStub(); os.Exit(m.Run()) }

View File

@ -1,4 +1,4 @@
package internal package info
import ( import (
"log" "log"

View File

@ -1,4 +1,4 @@
package internal package info
import ( import (
"reflect" "reflect"

View File

@ -1,4 +1,4 @@
package internal package info
// FallbackVersion is returned when a version string was not set by the linker. // FallbackVersion is returned when a version string was not set by the linker.
const FallbackVersion = "dirty" const FallbackVersion = "dirty"

View File

@ -14,9 +14,9 @@ import (
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/container/seccomp" "hakurei.app/container/seccomp"
"hakurei.app/container/std" "hakurei.app/container/std"
"hakurei.app/internal" "hakurei.app/internal/dbus"
"hakurei.app/internal/info"
"hakurei.app/message" "hakurei.app/message"
"hakurei.app/system/dbus"
) )
// osFile represents [os.File]. // osFile represents [os.File].
@ -156,7 +156,7 @@ func (direct) seccompLoad(rules []std.NativeRule, flags seccomp.ExportFlag) erro
return seccomp.Load(rules, flags) return seccomp.Load(rules, flags)
} }
func (direct) mustHsuPath() *check.Absolute { return internal.MustHsuPath() } func (direct) mustHsuPath() *check.Absolute { return info.MustHsuPath() }
func (direct) dbusAddress() (session, system string) { return dbus.Address() } func (direct) dbusAddress() (session, system string) { return dbus.Address() }

View File

@ -24,8 +24,8 @@ import (
"hakurei.app/container/std" "hakurei.app/container/std"
"hakurei.app/container/stub" "hakurei.app/container/stub"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/system"
"hakurei.app/message" "hakurei.app/message"
"hakurei.app/system"
) )
// call initialises a [stub.Call]. // call initialises a [stub.Call].

View File

@ -8,8 +8,8 @@ import (
"os/user" "os/user"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/system"
"hakurei.app/message" "hakurei.app/message"
"hakurei.app/system"
) )
func newWithMessage(msg string) error { return newWithMessageError(msg, os.ErrInvalid) } func newWithMessage(msg string) error { return newWithMessageError(msg, os.ErrInvalid) }

View File

@ -9,10 +9,10 @@ import (
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/acl"
"hakurei.app/internal/env" "hakurei.app/internal/env"
"hakurei.app/internal/system"
"hakurei.app/message" "hakurei.app/message"
"hakurei.app/system"
"hakurei.app/system/acl"
) )
// envAllocSize is the initial size of the env map pre-allocated when the configured env map is nil. // envAllocSize is the initial size of the env map pre-allocated when the configured env map is nil.

View File

@ -16,10 +16,10 @@ import (
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/container/fhs" "hakurei.app/container/fhs"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal" "hakurei.app/internal/info"
"hakurei.app/internal/store" "hakurei.app/internal/store"
"hakurei.app/internal/system"
"hakurei.app/message" "hakurei.app/message"
"hakurei.app/system"
) )
const ( const (
@ -39,7 +39,7 @@ func (k *outcome) main(msg message.Msg, identifierFd int) {
} }
// read comp value early for early failure // read comp value early for early failure
hsuPath := internal.MustHsuPath() hsuPath := info.MustHsuPath()
const ( const (
// transitions to processCommit, or processFinal on failure // transitions to processCommit, or processFinal on failure

View File

@ -12,6 +12,8 @@ import (
// IsPollDescriptor reports whether fd is the descriptor being used by the poller. // IsPollDescriptor reports whether fd is the descriptor being used by the poller.
// //
// Made available here to determine and reject impossible fd.
//
//go:linkname IsPollDescriptor internal/poll.IsPollDescriptor //go:linkname IsPollDescriptor internal/poll.IsPollDescriptor
func IsPollDescriptor(fd uintptr) bool func IsPollDescriptor(fd uintptr) bool

View File

@ -21,10 +21,10 @@ import (
"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/acl"
"hakurei.app/internal/dbus"
"hakurei.app/internal/system"
"hakurei.app/message" "hakurei.app/message"
"hakurei.app/system"
"hakurei.app/system/acl"
"hakurei.app/system/dbus"
) )
func TestOutcomeMain(t *testing.T) { func TestOutcomeMain(t *testing.T) {
@ -141,7 +141,7 @@ func TestOutcomeMain(t *testing.T) {
Proc(fhs.AbsProc). Proc(fhs.AbsProc).
Tmpfs(hst.AbsPrivateTmp, 1<<12, 0755). Tmpfs(hst.AbsPrivateTmp, 1<<12, 0755).
Bind(fhs.AbsDev, fhs.AbsDev, std.BindWritable|std.BindDevice). Bind(fhs.AbsDev, fhs.AbsDev, std.BindWritable|std.BindDevice).
Tmpfs(fhs.AbsDev.Append("shm"), 0, 01777). Tmpfs(fhs.AbsDevShm, 0, 01777).
// spRuntimeOp // spRuntimeOp
Tmpfs(fhs.AbsRunUser, 1<<12, 0755). Tmpfs(fhs.AbsRunUser, 1<<12, 0755).
@ -243,7 +243,7 @@ func TestOutcomeMain(t *testing.T) {
Proc(m("/proc/")). Proc(m("/proc/")).
Tmpfs(hst.AbsPrivateTmp, 4096, 0755). Tmpfs(hst.AbsPrivateTmp, 4096, 0755).
DevWritable(m("/dev/"), true). DevWritable(m("/dev/"), true).
Tmpfs(m("/dev/shm"), 0, 01777). Tmpfs(m("/dev/shm/"), 0, 01777).
Tmpfs(m("/run/user/"), 4096, 0755). Tmpfs(m("/run/user/"), 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).
@ -412,7 +412,7 @@ func TestOutcomeMain(t *testing.T) {
Proc(m("/proc/")). Proc(m("/proc/")).
Tmpfs(hst.AbsPrivateTmp, 4096, 0755). Tmpfs(hst.AbsPrivateTmp, 4096, 0755).
DevWritable(m("/dev/"), true). DevWritable(m("/dev/"), true).
Tmpfs(m("/dev/shm"), 0, 01777). Tmpfs(m("/dev/shm/"), 0, 01777).
Tmpfs(m("/run/user/"), 4096, 0755). Tmpfs(m("/run/user/"), 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).
@ -558,7 +558,7 @@ func TestOutcomeMain(t *testing.T) {
Proc(m("/proc/")). Proc(m("/proc/")).
Tmpfs(hst.AbsPrivateTmp, 4096, 0755). Tmpfs(hst.AbsPrivateTmp, 4096, 0755).
DevWritable(m("/dev/"), true). DevWritable(m("/dev/"), true).
Tmpfs(m("/dev/shm"), 0, 01777). Tmpfs(m("/dev/shm/"), 0, 01777).
Tmpfs(m("/run/user/"), 4096, 0755). Tmpfs(m("/run/user/"), 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).

View File

@ -10,53 +10,53 @@ static int hakurei_shim_fd = -1;
/* see shim.go for handling of the message */ /* see shim.go for handling of the message */
static inline ssize_t hakurei_shim_write(hakurei_shim_msg msg) { static inline ssize_t hakurei_shim_write(hakurei_shim_msg msg) {
int savedErrno = errno; int savedErrno = errno;
unsigned char buf = (unsigned char)msg; unsigned char buf = (unsigned char)msg;
ssize_t ret = write(hakurei_shim_fd, &buf, 1); ssize_t ret = write(hakurei_shim_fd, &buf, 1);
if (ret == -1 && errno != EAGAIN) if (ret == -1 && errno != EAGAIN)
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
errno = savedErrno; errno = savedErrno;
return ret; return ret;
} }
static void hakurei_shim_sigaction(int sig, siginfo_t *si, void *ucontext) { static void hakurei_shim_sigaction(int sig, siginfo_t *si, void *ucontext) {
if (sig != SIGCONT || si == NULL) { if (sig != SIGCONT || si == NULL) {
hakurei_shim_write(HAKUREI_SHIM_INVALID); hakurei_shim_write(HAKUREI_SHIM_INVALID);
return; return;
} }
if (si->si_pid == hakurei_shim_param_ppid) { if (si->si_pid == hakurei_shim_param_ppid) {
hakurei_shim_write(HAKUREI_SHIM_EXIT_REQUESTED); hakurei_shim_write(HAKUREI_SHIM_EXIT_REQUESTED);
return; return;
} }
hakurei_shim_write(HAKUREI_SHIM_BAD_PID); hakurei_shim_write(HAKUREI_SHIM_BAD_PID);
if (getppid() != hakurei_shim_param_ppid) if (getppid() != hakurei_shim_param_ppid)
hakurei_shim_write(HAKUREI_SHIM_ORPHAN); hakurei_shim_write(HAKUREI_SHIM_ORPHAN);
} }
void hakurei_shim_setup_cont_signal(pid_t ppid, int fd) { void hakurei_shim_setup_cont_signal(pid_t ppid, int fd) {
if (hakurei_shim_param_ppid != -1 || hakurei_shim_fd != -1) if (hakurei_shim_param_ppid != -1 || hakurei_shim_fd != -1)
*(int *)NULL = 0; /* unreachable */ *(volatile int *)NULL = 0; /* unreachable */
struct sigaction new_action = {0}, old_action = {0}; struct sigaction new_action = {0}, old_action = {0};
if (sigaction(SIGCONT, NULL, &old_action) != 0) if (sigaction(SIGCONT, NULL, &old_action) != 0)
return; return;
if (old_action.sa_handler != SIG_DFL) { if (old_action.sa_handler != SIG_DFL) {
errno = ENOTRECOVERABLE; errno = ENOTRECOVERABLE;
return; return;
} }
new_action.sa_sigaction = hakurei_shim_sigaction; new_action.sa_sigaction = hakurei_shim_sigaction;
if (sigemptyset(&new_action.sa_mask) != 0) if (sigemptyset(&new_action.sa_mask) != 0)
return; return;
new_action.sa_flags = SA_ONSTACK | SA_SIGINFO; new_action.sa_flags = SA_ONSTACK | SA_SIGINFO;
if (sigaction(SIGCONT, &new_action, NULL) != 0) if (sigaction(SIGCONT, &new_action, NULL) != 0)
return; return;
errno = 0; errno = 0;
hakurei_shim_param_ppid = ppid; hakurei_shim_param_ppid = ppid;
hakurei_shim_fd = fd; hakurei_shim_fd = fd;
} }

View File

@ -2,10 +2,10 @@
/* see shim.go for documentation */ /* see shim.go for documentation */
typedef enum { typedef enum {
HAKUREI_SHIM_EXIT_REQUESTED, HAKUREI_SHIM_EXIT_REQUESTED,
HAKUREI_SHIM_ORPHAN, HAKUREI_SHIM_ORPHAN,
HAKUREI_SHIM_INVALID, HAKUREI_SHIM_INVALID,
HAKUREI_SHIM_BAD_PID, HAKUREI_SHIM_BAD_PID,
} hakurei_shim_msg; } hakurei_shim_msg;
void hakurei_shim_setup_cont_signal(pid_t ppid, int fd); void hakurei_shim_setup_cont_signal(pid_t ppid, int fd);

View File

@ -66,7 +66,7 @@ func TestShimEntrypoint(t *testing.T) {
Proc(fhs.AbsProc). Proc(fhs.AbsProc).
Tmpfs(hst.AbsPrivateTmp, 1<<12, 0755). Tmpfs(hst.AbsPrivateTmp, 1<<12, 0755).
Bind(fhs.AbsDev, fhs.AbsDev, std.BindWritable|std.BindDevice). Bind(fhs.AbsDev, fhs.AbsDev, std.BindWritable|std.BindDevice).
Tmpfs(fhs.AbsDev.Append("shm"), 0, 01777). Tmpfs(fhs.AbsDevShm, 0, 01777).
// spRuntimeOp // spRuntimeOp
Tmpfs(fhs.AbsRunUser, 1<<12, 0755). Tmpfs(fhs.AbsRunUser, 1<<12, 0755).

View File

@ -16,11 +16,11 @@ import (
"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/acl"
"hakurei.app/internal/dbus"
"hakurei.app/internal/system"
"hakurei.app/internal/validate" "hakurei.app/internal/validate"
"hakurei.app/message" "hakurei.app/message"
"hakurei.app/system"
"hakurei.app/system/acl"
"hakurei.app/system/dbus"
) )
const varRunNscd = fhs.Var + "run/nscd" const varRunNscd = fhs.Var + "run/nscd"
@ -116,7 +116,7 @@ func (s *spParamsOp) toContainer(state *outcomeStateParams) error {
state.params.Bind(fhs.AbsDev, fhs.AbsDev, std.BindWritable|std.BindDevice) state.params.Bind(fhs.AbsDev, fhs.AbsDev, std.BindWritable|std.BindDevice)
} }
// /dev is mounted readonly later on, this prevents /dev/shm from going readonly with it // /dev is mounted readonly later on, this prevents /dev/shm from going readonly with it
state.params.Tmpfs(fhs.AbsDev.Append("shm"), 0, 01777) state.params.Tmpfs(fhs.AbsDevShm, 0, 01777)
return nil return nil
} }

View File

@ -14,9 +14,9 @@ import (
"hakurei.app/container/std" "hakurei.app/container/std"
"hakurei.app/container/stub" "hakurei.app/container/stub"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/system" "hakurei.app/internal/acl"
"hakurei.app/system/acl" "hakurei.app/internal/dbus"
"hakurei.app/system/dbus" "hakurei.app/internal/system"
) )
func TestSpParamsOp(t *testing.T) { func TestSpParamsOp(t *testing.T) {
@ -72,7 +72,7 @@ func TestSpParamsOp(t *testing.T) {
Root(m("/var/lib/hakurei/base/org.debian"), std.BindWritable). Root(m("/var/lib/hakurei/base/org.debian"), std.BindWritable).
Proc(fhs.AbsProc).Tmpfs(hst.AbsPrivateTmp, 1<<12, 0755). Proc(fhs.AbsProc).Tmpfs(hst.AbsPrivateTmp, 1<<12, 0755).
DevWritable(fhs.AbsDev, true). DevWritable(fhs.AbsDev, true).
Tmpfs(fhs.AbsDev.Append("shm"), 0, 01777), Tmpfs(fhs.AbsDevShm, 0, 01777),
}, paramsWantEnv(config, map[string]string{ }, paramsWantEnv(config, map[string]string{
"TERM": "xterm", "TERM": "xterm",
}, func(t *testing.T, state *outcomeStateParams) { }, func(t *testing.T, state *outcomeStateParams) {
@ -110,7 +110,7 @@ func TestSpParamsOp(t *testing.T) {
Root(m("/var/lib/hakurei/base/org.debian"), std.BindWritable). Root(m("/var/lib/hakurei/base/org.debian"), std.BindWritable).
Proc(fhs.AbsProc).Tmpfs(hst.AbsPrivateTmp, 1<<12, 0755). Proc(fhs.AbsProc).Tmpfs(hst.AbsPrivateTmp, 1<<12, 0755).
Bind(fhs.AbsDev, fhs.AbsDev, std.BindWritable|std.BindDevice). Bind(fhs.AbsDev, fhs.AbsDev, std.BindWritable|std.BindDevice).
Tmpfs(fhs.AbsDev.Append("shm"), 0, 01777), Tmpfs(fhs.AbsDevShm, 0, 01777),
}, paramsWantEnv(config, map[string]string{ }, paramsWantEnv(config, map[string]string{
"TERM": "xterm", "TERM": "xterm",
}, func(t *testing.T, state *outcomeStateParams) { }, func(t *testing.T, state *outcomeStateParams) {

View File

@ -5,8 +5,8 @@ import (
"hakurei.app/container/fhs" "hakurei.app/container/fhs"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/system/acl" "hakurei.app/internal/acl"
"hakurei.app/system/dbus" "hakurei.app/internal/dbus"
) )
func init() { gob.Register(new(spDBusOp)) } func init() { gob.Register(new(spDBusOp)) }

View File

@ -6,12 +6,12 @@ import (
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/container/stub" "hakurei.app/container/stub"
"hakurei.app/helper"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/acl"
"hakurei.app/internal/dbus"
"hakurei.app/internal/helper"
"hakurei.app/internal/system"
"hakurei.app/message" "hakurei.app/message"
"hakurei.app/system"
"hakurei.app/system/acl"
"hakurei.app/system/dbus"
) )
func TestSpDBusOp(t *testing.T) { func TestSpDBusOp(t *testing.T) {

View File

@ -11,8 +11,8 @@ import (
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/container/stub" "hakurei.app/container/stub"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/system" "hakurei.app/internal/acl"
"hakurei.app/system/acl" "hakurei.app/internal/system"
) )
func TestSpPulseOp(t *testing.T) { func TestSpPulseOp(t *testing.T) {

View File

@ -7,8 +7,8 @@ import (
"hakurei.app/container/fhs" "hakurei.app/container/fhs"
"hakurei.app/container/std" "hakurei.app/container/std"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/system" "hakurei.app/internal/acl"
"hakurei.app/system/acl" "hakurei.app/internal/system"
) )
const ( const (

View File

@ -8,8 +8,8 @@ import (
"hakurei.app/container/std" "hakurei.app/container/std"
"hakurei.app/container/stub" "hakurei.app/container/stub"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/system" "hakurei.app/internal/acl"
"hakurei.app/system/acl" "hakurei.app/internal/system"
) )
func TestSpRuntimeOp(t *testing.T) { func TestSpRuntimeOp(t *testing.T) {

View File

@ -7,8 +7,8 @@ import (
"hakurei.app/container/fhs" "hakurei.app/container/fhs"
"hakurei.app/container/std" "hakurei.app/container/std"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/system" "hakurei.app/internal/acl"
"hakurei.app/system/acl" "hakurei.app/internal/system"
) )
func init() { gob.Register(spTmpdirOp{}) } func init() { gob.Register(spTmpdirOp{}) }

View File

@ -8,8 +8,8 @@ import (
"hakurei.app/container/std" "hakurei.app/container/std"
"hakurei.app/container/stub" "hakurei.app/container/stub"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/system" "hakurei.app/internal/acl"
"hakurei.app/system/acl" "hakurei.app/internal/system"
) )
func TestSpTmpdirOp(t *testing.T) { func TestSpTmpdirOp(t *testing.T) {

View File

@ -5,8 +5,8 @@ import (
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/system/acl" "hakurei.app/internal/acl"
"hakurei.app/system/wayland" "hakurei.app/internal/wayland"
) )
func init() { gob.Register(new(spWaylandOp)) } func init() { gob.Register(new(spWaylandOp)) }
@ -25,8 +25,8 @@ func (s *spWaylandOp) toSystem(state *outcomeStateSys) error {
// outer wayland socket (usually `/run/user/%d/wayland-%d`) // outer wayland socket (usually `/run/user/%d/wayland-%d`)
var socketPath *check.Absolute var socketPath *check.Absolute
if name, ok := state.k.lookupEnv(wayland.WaylandDisplay); !ok { if name, ok := state.k.lookupEnv(wayland.Display); !ok {
state.msg.Verbose(wayland.WaylandDisplay + " is not set, assuming " + wayland.FallbackName) state.msg.Verbose(wayland.Display + " is not set, assuming " + wayland.FallbackName)
socketPath = state.sc.RuntimePath.Append(wayland.FallbackName) socketPath = state.sc.RuntimePath.Append(wayland.FallbackName)
} else if a, err := check.NewAbs(name); err != nil { } else if a, err := check.NewAbs(name); err != nil {
socketPath = state.sc.RuntimePath.Append(name) socketPath = state.sc.RuntimePath.Append(name)
@ -53,7 +53,7 @@ func (s *spWaylandOp) toSystem(state *outcomeStateSys) error {
func (s *spWaylandOp) toContainer(state *outcomeStateParams) error { func (s *spWaylandOp) toContainer(state *outcomeStateParams) error {
innerPath := state.runtimeDir.Append(wayland.FallbackName) innerPath := state.runtimeDir.Append(wayland.FallbackName)
state.env[wayland.WaylandDisplay] = wayland.FallbackName state.env[wayland.Display] = wayland.FallbackName
if s.SocketPath == nil { if s.SocketPath == nil {
state.params.Bind(state.instancePath().Append("wayland"), innerPath, 0) state.params.Bind(state.instancePath().Append("wayland"), innerPath, 0)
} else { } else {

View File

@ -6,9 +6,9 @@ import (
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/container/stub" "hakurei.app/container/stub"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/system" "hakurei.app/internal/acl"
"hakurei.app/system/acl" "hakurei.app/internal/system"
"hakurei.app/system/wayland" "hakurei.app/internal/wayland"
) )
func TestSpWaylandOp(t *testing.T) { func TestSpWaylandOp(t *testing.T) {
@ -47,7 +47,7 @@ func TestSpWaylandOp(t *testing.T) {
Ops: new(container.Ops). Ops: new(container.Ops).
Bind(m(wantInstancePrefix+"/wayland"), m("/run/user/1000/wayland-0"), 0), Bind(m(wantInstancePrefix+"/wayland"), m("/run/user/1000/wayland-0"), 0),
}, paramsWantEnv(config, map[string]string{ }, paramsWantEnv(config, map[string]string{
wayland.WaylandDisplay: wayland.FallbackName, wayland.Display: wayland.FallbackName,
}, nil), nil}, }, nil), nil},
{"success direct", func(isShim, _ bool) outcomeOp { {"success direct", func(isShim, _ bool) outcomeOp {
@ -75,7 +75,7 @@ func TestSpWaylandOp(t *testing.T) {
Ops: new(container.Ops). Ops: new(container.Ops).
Bind(m("/proc/nonexistent/wayland"), m("/run/user/1000/wayland-0"), 0), Bind(m("/proc/nonexistent/wayland"), m("/run/user/1000/wayland-0"), 0),
}, paramsWantEnv(config, map[string]string{ }, paramsWantEnv(config, map[string]string{
wayland.WaylandDisplay: wayland.FallbackName, wayland.Display: wayland.FallbackName,
}, nil), nil}, }, nil), nil},
{"success", func(bool, bool) outcomeOp { {"success", func(bool, bool) outcomeOp {
@ -98,7 +98,7 @@ func TestSpWaylandOp(t *testing.T) {
Ops: new(container.Ops). Ops: new(container.Ops).
Bind(m(wantInstancePrefix+"/wayland"), m("/run/user/1000/wayland-0"), 0), Bind(m(wantInstancePrefix+"/wayland"), m("/run/user/1000/wayland-0"), 0),
}, paramsWantEnv(config, map[string]string{ }, paramsWantEnv(config, map[string]string{
wayland.WaylandDisplay: wayland.FallbackName, wayland.Display: wayland.FallbackName,
}, nil), nil}, }, nil), nil},
}) })
} }

View File

@ -11,7 +11,7 @@ import (
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/container/fhs" "hakurei.app/container/fhs"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/system/acl" "hakurei.app/internal/acl"
) )
var absX11SocketDir = fhs.AbsTmp.Append(".X11-unix") var absX11SocketDir = fhs.AbsTmp.Append(".X11-unix")

View File

@ -7,7 +7,7 @@ import (
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/container/stub" "hakurei.app/container/stub"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/system/acl" "hakurei.app/internal/acl"
) )
func TestSpX11Op(t *testing.T) { func TestSpX11Op(t *testing.T) {

View File

@ -27,7 +27,7 @@ func TestEntryData(t *testing.T) {
return buf.String() return buf.String()
} }
} }
templateStateGob := mustEncodeGob(newTemplateState()) templateStateGob := mustEncodeGob(NewTemplateState())
testCases := []struct { testCases := []struct {
name string name string
@ -45,11 +45,11 @@ func TestEntryData(t *testing.T) {
Step: "validate configuration", Err: hst.ErrConfigNull, Step: "validate configuration", Err: hst.ErrConfigNull,
Msg: "invalid configuration"}}, Msg: "invalid configuration"}},
{"inconsistent enablement", "\x00\xff\xca\xfe\x00\x00\xff\x00" + templateStateGob, newTemplateState(), &hst.AppError{ {"inconsistent enablement", "\x00\xff\xca\xfe\x00\x00\xff\x00" + templateStateGob, NewTemplateState(), &hst.AppError{
Step: "validate state enablement", Err: os.ErrInvalid, Step: "validate state enablement", Err: os.ErrInvalid,
Msg: "state entry aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa has unexpected enablement byte 0xd, 0xff"}}, Msg: "state entry aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa has unexpected enablement byte 0xd, 0xff"}},
{"template", "\x00\xff\xca\xfe\x00\x00\x0d\xf2" + templateStateGob, newTemplateState(), nil}, {"template", "\x00\xff\xca\xfe\x00\x00\x0d\xf2" + templateStateGob, NewTemplateState(), nil},
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
@ -105,7 +105,7 @@ func TestEntryData(t *testing.T) {
t.Run("encode fault", func(t *testing.T) { t.Run("encode fault", func(t *testing.T) {
t.Parallel() t.Parallel()
s := newTemplateState() s := NewTemplateState()
t.Run("gob", func(t *testing.T) { t.Run("gob", func(t *testing.T) {
var want = &hst.AppError{Step: "encode state body", Err: stub.UniqueError(0xcafe)} var want = &hst.AppError{Step: "encode state body", Err: stub.UniqueError(0xcafe)}
@ -123,8 +123,8 @@ func TestEntryData(t *testing.T) {
}) })
} }
// newTemplateState returns the address of a new template [hst.State] struct. // NewTemplateState returns the address of a new template [hst.State] struct.
func newTemplateState() *hst.State { func NewTemplateState() *hst.State {
return &hst.State{ return &hst.State{
ID: hst.ID(bytes.Repeat([]byte{0xaa}, len(hst.ID{}))), ID: hst.ID(bytes.Repeat([]byte{0xaa}, len(hst.ID{}))),
PID: 0xcafe, PID: 0xcafe,

View File

@ -10,7 +10,7 @@ import (
"strings" "strings"
"syscall" "syscall"
"testing" "testing"
_ "unsafe" _ "unsafe" // for go:linkname
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/container/stub" "hakurei.app/container/stub"
@ -18,18 +18,23 @@ import (
"hakurei.app/internal/store" "hakurei.app/internal/store"
) )
//go:linkname newTemplateState hakurei.app/internal/store.newTemplateState // Made available here for direct validation of state entry files.
func newTemplateState() *hst.State //
//go:linkname entryDecode hakurei.app/internal/store.entryDecode //go:linkname entryDecode hakurei.app/internal/store.entryDecode
func entryDecode(r io.Reader, p *hst.State) (hst.Enablement, error) func entryDecode(r io.Reader, p *hst.State) (hst.Enablement, error)
// Made available here for direct access to known segment handles.
//
//go:linkname newHandle hakurei.app/internal/store.newHandle //go:linkname newHandle hakurei.app/internal/store.newHandle
func newHandle(base *check.Absolute, identity int) *store.Handle func newHandle(base *check.Absolute, identity int) *store.Handle
// Made available here to check open error handling behaviour.
//
//go:linkname open hakurei.app/internal/store.(*EntryHandle).open //go:linkname open hakurei.app/internal/store.(*EntryHandle).open
func open(eh *store.EntryHandle, flag int, perm os.FileMode) (*os.File, error) func open(eh *store.EntryHandle, flag int, perm os.FileMode) (*os.File, error)
// Made available here to check the saveload cycle.
//
//go:linkname save hakurei.app/internal/store.(*EntryHandle).save //go:linkname save hakurei.app/internal/store.(*EntryHandle).save
func save(eh *store.EntryHandle, state *hst.State) error func save(eh *store.EntryHandle, state *hst.State) error
@ -91,9 +96,9 @@ func TestStateEntryHandle(t *testing.T) {
t.Run("saveload", func(t *testing.T) { t.Run("saveload", func(t *testing.T) {
t.Parallel() t.Parallel()
eh := store.EntryHandle{Pathname: check.MustAbs(t.TempDir()).Append("entry"), eh := store.EntryHandle{Pathname: check.MustAbs(t.TempDir()).Append("entry"),
ID: newTemplateState().ID} ID: store.NewTemplateState().ID}
if err := save(&eh, newTemplateState()); err != nil { if err := save(&eh, store.NewTemplateState()); err != nil {
t.Fatalf("save: error = %v", err) t.Fatalf("save: error = %v", err)
} }
@ -112,7 +117,7 @@ func TestStateEntryHandle(t *testing.T) {
t.Fatal(f.Close()) t.Fatal(f.Close())
} }
if want := newTemplateState(); !reflect.DeepEqual(&got, want) { if want := store.NewTemplateState(); !reflect.DeepEqual(&got, want) {
t.Errorf("entryDecode: %#v, want %#v", &got, want) t.Errorf("entryDecode: %#v, want %#v", &got, want)
} }
}) })
@ -122,7 +127,7 @@ func TestStateEntryHandle(t *testing.T) {
if et, err := eh.Load(nil); err != nil { if et, err := eh.Load(nil); err != nil {
t.Fatalf("load: error = %v", err) t.Fatalf("load: error = %v", err)
} else if want := newTemplateState().Enablements.Unwrap(); et != want { } else if want := store.NewTemplateState().Enablements.Unwrap(); et != want {
t.Errorf("load: et = %x, want %x", et, want) t.Errorf("load: et = %x, want %x", et, want)
} }
}) })
@ -133,7 +138,7 @@ func TestStateEntryHandle(t *testing.T) {
var got hst.State var got hst.State
if _, err := eh.Load(&got); err != nil { if _, err := eh.Load(&got); err != nil {
t.Fatalf("load: error = %v", err) t.Fatalf("load: error = %v", err)
} else if want := newTemplateState(); !reflect.DeepEqual(&got, want) { } else if want := store.NewTemplateState(); !reflect.DeepEqual(&got, want) {
t.Errorf("load: %#v, want %#v", &got, want) t.Errorf("load: %#v, want %#v", &got, want)
} }
}) })

View File

@ -12,13 +12,15 @@ import (
"syscall" "syscall"
"testing" "testing"
"time" "time"
_ "unsafe" _ "unsafe" // for go:linkname
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/store" "hakurei.app/internal/store"
) )
// Made available here to check bigLock error handling behaviour.
//
//go:linkname bigLock hakurei.app/internal/store.(*Store).bigLock //go:linkname bigLock hakurei.app/internal/store.(*Store).bigLock
func bigLock(s *store.Store) (unlock func(), err error) func bigLock(s *store.Store) (unlock func(), err error)

View File

@ -8,7 +8,7 @@ import (
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/system/acl" "hakurei.app/internal/acl"
) )
// UpdatePerm calls UpdatePermType with the [Process] criteria. // UpdatePerm calls UpdatePermType with the [Process] criteria.

View File

@ -7,7 +7,7 @@ import (
"hakurei.app/container/stub" "hakurei.app/container/stub"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/system/acl" "hakurei.app/internal/acl"
) )
func TestACLUpdateOp(t *testing.T) { func TestACLUpdateOp(t *testing.T) {

View File

@ -13,7 +13,7 @@ import (
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/system/dbus" "hakurei.app/internal/dbus"
) )
// ErrDBusConfig is returned when a required [hst.BusConfig] argument is nil. // ErrDBusConfig is returned when a required [hst.BusConfig] argument is nil.

View File

@ -10,9 +10,9 @@ import (
"testing" "testing"
"hakurei.app/container/stub" "hakurei.app/container/stub"
"hakurei.app/helper"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/system/dbus" "hakurei.app/internal/dbus"
"hakurei.app/internal/helper"
) )
func TestDBusProxyOp(t *testing.T) { func TestDBusProxyOp(t *testing.T) {

View File

@ -6,10 +6,12 @@ import (
"log" "log"
"os" "os"
"hakurei.app/container/check"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/system/acl" "hakurei.app/internal/acl"
"hakurei.app/system/dbus" "hakurei.app/internal/dbus"
"hakurei.app/system/internal/xcb" "hakurei.app/internal/wayland"
"hakurei.app/internal/xcb"
) )
type osFile interface { type osFile interface {
@ -45,6 +47,8 @@ type syscallDispatcher interface {
// aclUpdate provides [acl.Update]. // aclUpdate provides [acl.Update].
aclUpdate(name string, uid int, perms ...acl.Perm) error aclUpdate(name string, uid int, perms ...acl.Perm) error
waylandNew(displayPath, bindPath *check.Absolute, appID, instanceID string) (*wayland.SecurityContext, error)
// xcbChangeHosts provides [xcb.ChangeHosts]. // xcbChangeHosts provides [xcb.ChangeHosts].
xcbChangeHosts(mode xcb.HostMode, family xcb.Family, address string) error xcbChangeHosts(mode xcb.HostMode, family xcb.Family, address string) error
@ -76,6 +80,10 @@ func (k direct) aclUpdate(name string, uid int, perms ...acl.Perm) error {
return acl.Update(name, uid, perms...) return acl.Update(name, uid, perms...)
} }
func (k direct) waylandNew(displayPath, bindPath *check.Absolute, appID, instanceID string) (*wayland.SecurityContext, error) {
return wayland.New(displayPath, bindPath, appID, instanceID)
}
func (k direct) xcbChangeHosts(mode xcb.HostMode, family xcb.Family, address string) error { func (k direct) xcbChangeHosts(mode xcb.HostMode, family xcb.Family, address string) error {
return xcb.ChangeHosts(mode, family, address) return xcb.ChangeHosts(mode, family, address)
} }

View File

@ -8,11 +8,13 @@ import (
"testing" "testing"
"unsafe" "unsafe"
"hakurei.app/container/check"
"hakurei.app/container/stub" "hakurei.app/container/stub"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/system/acl" "hakurei.app/internal/acl"
"hakurei.app/system/dbus" "hakurei.app/internal/dbus"
"hakurei.app/system/internal/xcb" "hakurei.app/internal/wayland"
"hakurei.app/internal/xcb"
) )
// call initialises a [stub.Call]. // call initialises a [stub.Call].
@ -268,6 +270,15 @@ func (k *kstub) aclUpdate(name string, uid int, perms ...acl.Perm) error {
stub.CheckArgReflect(k.Stub, "perms", perms, 2)) stub.CheckArgReflect(k.Stub, "perms", perms, 2))
} }
func (k *kstub) waylandNew(displayPath, bindPath *check.Absolute, appID, instanceID string) (*wayland.SecurityContext, error) {
k.Helper()
return nil, k.Expects("waylandNew").Error(
stub.CheckArgReflect(k.Stub, "displayPath", displayPath, 0),
stub.CheckArgReflect(k.Stub, "bindPath", bindPath, 1),
stub.CheckArg(k.Stub, "appID", appID, 2),
stub.CheckArg(k.Stub, "instanceID", instanceID, 3))
}
func (k *kstub) xcbChangeHosts(mode xcb.HostMode, family xcb.Family, address string) error { func (k *kstub) xcbChangeHosts(mode xcb.HostMode, family xcb.Family, address string) error {
k.Helper() k.Helper()
return k.Expects("xcbChangeHosts").Error( return k.Expects("xcbChangeHosts").Error(

View File

@ -40,6 +40,10 @@ func (e *OpError) Error() string {
} }
func (e *OpError) Message() string { func (e *OpError) Message() string {
if m, ok := message.GetMessage(e.Err); ok {
return m
}
switch { switch {
case e.Msg != "": case e.Msg != "":
return e.Error() return e.Error()

View File

@ -44,7 +44,7 @@ type Op interface {
String() string String() string
} }
// TypeString extends [Enablement.String] to support [User] and [Process]. // TypeString extends [hst.Enablement.String] to support [User] and [Process].
func TypeString(e hst.Enablement) string { func TypeString(e hst.Enablement) string {
switch e { switch e {
case User: case User:

View File

@ -11,8 +11,8 @@ import (
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/container/stub" "hakurei.app/container/stub"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/xcb"
"hakurei.app/message" "hakurei.app/message"
"hakurei.app/system/internal/xcb"
) )
func TestCriteria(t *testing.T) { func TestCriteria(t *testing.T) {

View File

@ -0,0 +1,81 @@
package system
import (
"errors"
"fmt"
"os"
"hakurei.app/container/check"
"hakurei.app/hst"
"hakurei.app/internal/acl"
"hakurei.app/internal/wayland"
)
// Wayland maintains a wayland socket with security-context-v1 attached via [wayland].
// The socket stops accepting connections once the pipe referred to by sync is closed.
// The socket is pathname only and is destroyed on revert.
func (sys *I) Wayland(dst, src *check.Absolute, appID, instanceID string) *I {
sys.ops = append(sys.ops, &waylandOp{nil,
dst, src, appID, instanceID})
return sys
}
// waylandOp implements [I.Wayland].
type waylandOp struct {
ctx *wayland.SecurityContext
dst, src *check.Absolute
appID, instanceID string
}
func (w *waylandOp) Type() hst.Enablement { return Process }
func (w *waylandOp) apply(sys *I) (err error) {
if w.ctx, err = sys.waylandNew(w.src, w.dst, w.appID, w.instanceID); err != nil {
return newOpError("wayland", err, false)
} else {
sys.msg.Verbosef("wayland pathname socket on %q via %q", w.dst, w.src)
if err = sys.chmod(w.dst.String(), 0); err != nil {
if closeErr := w.ctx.Close(); closeErr != nil {
return newOpError("wayland", errors.Join(err, closeErr), false)
}
return newOpError("wayland", err, false)
}
if err = sys.aclUpdate(w.dst.String(), sys.uid, acl.Read, acl.Write, acl.Execute); err != nil {
if closeErr := w.ctx.Close(); closeErr != nil {
return newOpError("wayland", errors.Join(err, closeErr), false)
}
return newOpError("wayland", err, false)
}
return nil
}
}
func (w *waylandOp) revert(sys *I, _ *Criteria) error {
var (
hangupErr error
removeErr error
)
sys.msg.Verbosef("hanging up wayland socket on %q", w.dst)
if w.ctx != nil {
hangupErr = w.ctx.Close()
}
if err := sys.remove(w.dst.String()); err != nil && !errors.Is(err, os.ErrNotExist) {
removeErr = err
}
return newOpError("wayland", errors.Join(hangupErr, removeErr), true)
}
func (w *waylandOp) Is(o Op) bool {
target, ok := o.(*waylandOp)
return ok && w != nil && target != nil &&
w.dst.Is(target.dst) && w.src.Is(target.src) &&
w.appID == target.appID && w.instanceID == target.instanceID
}
func (w *waylandOp) Path() string { return w.dst.String() }
func (w *waylandOp) String() string { return fmt.Sprintf("wayland socket at %q", w.dst) }

View File

@ -0,0 +1,157 @@
package system
import (
"errors"
"os"
"testing"
"hakurei.app/container/stub"
"hakurei.app/internal/acl"
)
func TestWaylandOp(t *testing.T) {
t.Parallel()
checkOpBehaviour(t, []opBehaviourTestCase{
{"chmod", 0xbeef, 0xff, &waylandOp{nil,
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
m("/run/user/1971/wayland-0"),
"org.chromium.Chromium",
"ebf083d1b175911782d413369b64ce7c",
}, []stub.Call{
call("waylandNew", stub.ExpectArgs{m("/run/user/1971/wayland-0"), m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"), "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c"}, nil, nil),
call("verbosef", stub.ExpectArgs{"wayland pathname socket on %q via %q", []any{m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"), m("/run/user/1971/wayland-0")}}, nil, nil),
call("chmod", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", os.FileMode(0)}, nil, stub.UniqueError(3)),
}, &OpError{Op: "wayland", Err: errors.Join(stub.UniqueError(3), os.ErrInvalid)}, nil, nil},
{"aclUpdate", 0xbeef, 0xff, &waylandOp{nil,
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
m("/run/user/1971/wayland-0"),
"org.chromium.Chromium",
"ebf083d1b175911782d413369b64ce7c",
}, []stub.Call{
call("waylandNew", stub.ExpectArgs{m("/run/user/1971/wayland-0"), m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"), "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c"}, nil, nil),
call("verbosef", stub.ExpectArgs{"wayland pathname socket on %q via %q", []any{m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"), m("/run/user/1971/wayland-0")}}, nil, nil),
call("chmod", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", os.FileMode(0)}, nil, nil),
call("aclUpdate", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", 0xbeef, []acl.Perm{acl.Read, acl.Write, acl.Execute}}, nil, stub.UniqueError(2)),
}, &OpError{Op: "wayland", Err: errors.Join(stub.UniqueError(2), os.ErrInvalid)}, nil, nil},
{"remove", 0xbeef, 0xff, &waylandOp{nil,
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
m("/run/user/1971/wayland-0"),
"org.chromium.Chromium",
"ebf083d1b175911782d413369b64ce7c",
}, []stub.Call{
call("waylandNew", stub.ExpectArgs{m("/run/user/1971/wayland-0"), m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"), "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c"}, nil, nil),
call("verbosef", stub.ExpectArgs{"wayland pathname socket on %q via %q", []any{m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"), m("/run/user/1971/wayland-0")}}, nil, nil),
call("chmod", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", os.FileMode(0)}, nil, nil),
call("aclUpdate", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", 0xbeef, []acl.Perm{acl.Read, acl.Write, acl.Execute}}, nil, nil),
}, nil, []stub.Call{
call("verbosef", stub.ExpectArgs{"hanging up wayland socket on %q", []any{m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland")}}, nil, nil),
call("remove", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"}, nil, stub.UniqueError(1)),
}, &OpError{Op: "wayland", Err: errors.Join(stub.UniqueError(1)), Revert: true}},
{"success", 0xbeef, 0xff, &waylandOp{nil,
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
m("/run/user/1971/wayland-0"),
"org.chromium.Chromium",
"ebf083d1b175911782d413369b64ce7c",
}, []stub.Call{
call("waylandNew", stub.ExpectArgs{m("/run/user/1971/wayland-0"), m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"), "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c"}, nil, nil),
call("verbosef", stub.ExpectArgs{"wayland pathname socket on %q via %q", []any{m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"), m("/run/user/1971/wayland-0")}}, nil, nil),
call("chmod", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", os.FileMode(0)}, nil, nil),
call("aclUpdate", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", 0xbeef, []acl.Perm{acl.Read, acl.Write, acl.Execute}}, nil, nil),
}, nil, []stub.Call{
call("verbosef", stub.ExpectArgs{"hanging up wayland socket on %q", []any{m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland")}}, nil, nil),
call("remove", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"}, nil, nil),
}, nil},
})
checkOpsBuilder(t, "Wayland", []opsBuilderTestCase{
{"chromium", 0xcafe, func(_ *testing.T, sys *I) {
sys.Wayland(
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
m("/run/user/1971/wayland-0"),
"org.chromium.Chromium",
"ebf083d1b175911782d413369b64ce7c",
)
}, []Op{&waylandOp{nil,
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
m("/run/user/1971/wayland-0"),
"org.chromium.Chromium",
"ebf083d1b175911782d413369b64ce7c",
}}, stub.Expect{}},
})
checkOpIs(t, []opIsTestCase{
{"dst differs", &waylandOp{nil,
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7d/wayland"),
m("/run/user/1971/wayland-0"),
"org.chromium.Chromium",
"ebf083d1b175911782d413369b64ce7c",
}, &waylandOp{nil,
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
m("/run/user/1971/wayland-0"),
"org.chromium.Chromium",
"ebf083d1b175911782d413369b64ce7c",
}, false},
{"src differs", &waylandOp{nil,
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
m("/run/user/1971/wayland-1"),
"org.chromium.Chromium",
"ebf083d1b175911782d413369b64ce7c",
}, &waylandOp{nil,
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
m("/run/user/1971/wayland-0"),
"org.chromium.Chromium",
"ebf083d1b175911782d413369b64ce7c",
}, false},
{"appID differs", &waylandOp{nil,
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
m("/run/user/1971/wayland-0"),
"org.chromium",
"ebf083d1b175911782d413369b64ce7c",
}, &waylandOp{nil,
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
m("/run/user/1971/wayland-0"),
"org.chromium.Chromium",
"ebf083d1b175911782d413369b64ce7c",
}, false},
{"instanceID differs", &waylandOp{nil,
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
m("/run/user/1971/wayland-0"),
"org.chromium.Chromium",
"ebf083d1b175911782d413369b64ce7d",
}, &waylandOp{nil,
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
m("/run/user/1971/wayland-0"),
"org.chromium.Chromium",
"ebf083d1b175911782d413369b64ce7c",
}, false},
{"equals", &waylandOp{nil,
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
m("/run/user/1971/wayland-0"),
"org.chromium.Chromium",
"ebf083d1b175911782d413369b64ce7c",
}, &waylandOp{nil,
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
m("/run/user/1971/wayland-0"),
"org.chromium.Chromium",
"ebf083d1b175911782d413369b64ce7c",
}, true},
})
checkOpMeta(t, []opMetaTestCase{
{"chromium", &waylandOp{nil,
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
m("/run/user/1971/wayland-0"),
"org.chromium.Chromium",
"ebf083d1b175911782d413369b64ce7c",
}, Process, "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
`wayland socket at "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"`},
})
}

Some files were not shown because too many files have changed in this diff Show More