Compare commits

..

No commits in common. "master" and "v0.3.1" have entirely different histories.

141 changed files with 1132 additions and 2205 deletions

View File

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

View File

@ -11,24 +11,21 @@ import (
"strconv"
"sync"
"time"
_ "unsafe" // for go:linkname
_ "unsafe"
"hakurei.app/command"
"hakurei.app/container/check"
"hakurei.app/container/fhs"
"hakurei.app/hst"
"hakurei.app/internal/dbus"
"hakurei.app/internal"
"hakurei.app/internal/env"
"hakurei.app/internal/info"
"hakurei.app/internal/outcome"
"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
func optionalErrorUnwrap(err error) error
func optionalErrorUnwrap(_ error) error
func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErrs, out io.Writer) command.Command {
var (
@ -353,7 +350,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
}).Flag(&flagShort, "short", command.BoolFlag(false), "Print instance id")
}
c.Command("version", "Display version information", func(args []string) error { fmt.Println(info.Version()); return errSuccess })
c.Command("version", "Display version information", func(args []string) error { fmt.Println(internal.Version()); 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("help", "Show this help message", func([]string) error { c.PrintHelp(); return errSuccess })

View File

@ -1,13 +1,18 @@
package main
package main_test
import (
"io"
"reflect"
"strings"
"testing"
_ "unsafe"
"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) {
t.Parallel()
@ -57,6 +62,9 @@ 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) {
t.Parallel()
@ -66,7 +74,7 @@ func TestEncodeJSON(t *testing.T) {
want string
}{
{"marshaler", errorJSONMarshaler{},
`cannot encode json for main.errorJSONMarshaler: unique error 3735928559 injected by the test suite`},
`cannot encode json for main_test.errorJSONMarshaler: unique error 3735928559 injected by the test suite`},
{"default", func() {},
`cannot write json: json: unsupported type: func()`},
}

View File

@ -12,8 +12,8 @@ import (
"time"
"hakurei.app/hst"
"hakurei.app/internal"
"hakurei.app/internal/env"
"hakurei.app/internal/info"
"hakurei.app/internal/outcome"
"hakurei.app/internal/store"
"hakurei.app/message"
@ -24,20 +24,20 @@ func printShowSystem(output io.Writer, short, flagJSON bool) {
t := newPrinter(output)
defer t.MustFlush()
hi := &hst.Info{Version: info.Version(), User: new(outcome.Hsu).MustID(nil)}
env.CopyPaths().Copy(&hi.Paths, hi.User)
info := &hst.Info{Version: internal.Version(), User: new(outcome.Hsu).MustID(nil)}
env.CopyPaths().Copy(&info.Paths, info.User)
if flagJSON {
encodeJSON(log.Fatal, output, short, hi)
encodeJSON(log.Fatal, output, short, info)
return
}
t.Printf("Version:\t%s\n", hi.Version)
t.Printf("User:\t%d\n", hi.User)
t.Printf("TempDir:\t%s\n", hi.TempDir)
t.Printf("SharePath:\t%s\n", hi.SharePath)
t.Printf("RuntimePath:\t%s\n", hi.RuntimePath)
t.Printf("RunDirPath:\t%s\n", hi.RunDirPath)
t.Printf("Version:\t%s\n", info.Version)
t.Printf("User:\t%d\n", info.User)
t.Printf("TempDir:\t%s\n", info.TempDir)
t.Printf("SharePath:\t%s\n", info.SharePath)
t.Printf("RuntimePath:\t%s\n", info.RuntimePath)
t.Printf("RunDirPath:\t%s\n", info.RunDirPath)
}
// printShowInstance writes a representation of [hst.State] or [hst.Config] to output.
@ -90,6 +90,12 @@ func printShowInstance(
t.Printf(" Groups:\t%s\n", strings.Join(config.Groups, ", "))
}
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()
// this is included in the upper hst.Config struct but is relevant here
@ -104,12 +110,6 @@ func printShowInstance(
}
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 {
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)
Enablements: wayland, dbus, pulseaudio
Groups: video, dialout, plugdev
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir
Home: /data/data/org.chromium.Chromium
Hostname: localhost
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir
Path: /run/current-system/sw/bin/chromium
Arguments: chromium --ignore-gpu-blocklist --disable-smooth-scrolling --enable-features=UseOzonePlatform --ozone-platform=wayland
@ -161,9 +161,9 @@ App
Identity: 9 (org.chromium.Chromium)
Enablements: wayland, dbus, pulseaudio
Groups: video, dialout, plugdev
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir
Home: /data/data/org.chromium.Chromium
Hostname: localhost
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir
Path: /run/current-system/sw/bin/chromium
Arguments: chromium --ignore-gpu-blocklist --disable-smooth-scrolling --enable-features=UseOzonePlatform --ozone-platform=wayland

View File

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

View File

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

View File

@ -14,10 +14,8 @@ import (
. "hakurei.app/container/check"
)
// unsafeAbs returns check.Absolute on any string value.
//
//go:linkname unsafeAbs hakurei.app/container/check.unsafeAbs
func unsafeAbs(pathname string) *Absolute
func unsafeAbs(_ string) *Absolute
func TestAbsoluteError(t *testing.T) {
t.Parallel()
@ -84,9 +82,9 @@ func TestNewAbs(t *testing.T) {
t.Parallel()
defer func() {
wantPanic := &AbsoluteError{Pathname: "etc"}
wantPanic := `path "etc" is not absolute`
if r := recover(); !reflect.DeepEqual(r, wantPanic) {
if r := recover(); r != wantPanic {
t.Errorf("MustAbs: panic = %v; want %v", r, wantPanic)
}
}()

View File

@ -21,7 +21,6 @@ import (
"hakurei.app/command"
"hakurei.app/container"
"hakurei.app/container/check"
"hakurei.app/container/fhs"
"hakurei.app/container/seccomp"
"hakurei.app/container/std"
"hakurei.app/container/vfs"
@ -30,45 +29,6 @@ import (
"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) {
t.Parallel()
@ -762,14 +722,12 @@ func TestMain(m *testing.M) {
func helperNewContainerLibPaths(ctx context.Context, libPaths *[]*check.Absolute, args ...string) (c *container.Container) {
msg := message.New(nil)
msg.SwapVerbose(testing.Verbose())
executable := check.MustAbs(container.MustExecutable(msg))
c = container.NewCommand(ctx, msg, absHelperInnerPath, "helper", args...)
c.Env = append(c.Env, envDoCheck+"=1")
c.Bind(executable, absHelperInnerPath, 0)
c.Bind(check.MustAbs(os.Args[0]), absHelperInnerPath, 0)
// in case test has cgo enabled
if entries, err := ldd.Resolve(ctx, msg, executable); err != nil {
if entries, err := ldd.Exec(ctx, msg, os.Args[0]); err != nil {
log.Fatalf("ldd: %v", err)
} else {
*libPaths = ldd.Path(entries)

View File

@ -8,10 +8,8 @@ import (
/* 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
func unsafeAbs(pathname string) *check.Absolute
func unsafeAbs(_ string) *check.Absolute
var (
// AbsRoot is [Root] as [check.Absolute].
@ -36,8 +34,6 @@ var (
// AbsDev is [Dev] as [check.Absolute].
AbsDev = unsafeAbs(Dev)
// AbsDevShm is [DevShm] as [check.Absolute].
AbsDevShm = unsafeAbs(DevShm)
// AbsProc is [Proc] as [check.Absolute].
AbsProc = unsafeAbs(Proc)
// AbsSys is [Sys] as [check.Absolute].

View File

@ -29,8 +29,6 @@ const (
// Dev points to the root directory for device nodes.
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 = "/proc/"
// ProcSys points to a hierarchy below /proc/ that exposes a number of kernel tunables.

View File

@ -9,8 +9,7 @@
#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(int *ret_p, uintptr_t allocate_p,
uint32_t arch, uint32_t multiarch,
struct hakurei_syscall_rule *rules,
size_t rules_sz, hakurei_export_flag flags) {
@ -73,9 +72,11 @@ int32_t hakurei_scmp_make_filter(
assert(rule->m_errno == EPERM || rule->m_errno == ENOSYS);
if (rule->arg)
*ret_p = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(rule->m_errno), rule->syscall, 1, *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);
*ret_p = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(rule->m_errno),
rule->syscall, 0);
if (*ret_p == -EFAULT) {
res = 4;
@ -92,17 +93,22 @@ int32_t hakurei_scmp_make_filter(
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)
(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++) {
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));
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));
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);

View File

@ -19,8 +19,7 @@ struct hakurei_syscall_rule {
};
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(int *ret_p, uintptr_t allocate_p,
uint32_t arch, uint32_t multiarch,
struct hakurei_syscall_rule *rules,
size_t rules_sz, hakurei_export_flag flags);

View File

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

6
dist/release.sh vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,73 +0,0 @@
// 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() }

View File

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

View File

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

View File

@ -1,63 +0,0 @@
// 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

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

View File

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

View File

@ -1,90 +0,0 @@
#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

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

View File

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

View File

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

View File

@ -12,8 +12,6 @@ import (
// 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
func IsPollDescriptor(fd uintptr) bool

View File

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

View File

@ -9,10 +9,10 @@ import (
"hakurei.app/container"
"hakurei.app/container/check"
"hakurei.app/hst"
"hakurei.app/internal/acl"
"hakurei.app/internal/env"
"hakurei.app/internal/system"
"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.

View File

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

View File

@ -38,7 +38,7 @@ static void hakurei_shim_sigaction(int sig, siginfo_t *si, void *ucontext) {
void hakurei_shim_setup_cont_signal(pid_t ppid, int fd) {
if (hakurei_shim_param_ppid != -1 || hakurei_shim_fd != -1)
*(volatile int *)NULL = 0; /* unreachable */
*(int *)NULL = 0; /* unreachable */
struct sigaction new_action = {0}, old_action = {0};
if (sigaction(SIGCONT, NULL, &old_action) != 0)

View File

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

View File

@ -16,11 +16,11 @@ import (
"hakurei.app/container/seccomp"
"hakurei.app/container/std"
"hakurei.app/hst"
"hakurei.app/internal/acl"
"hakurei.app/internal/dbus"
"hakurei.app/internal/system"
"hakurei.app/internal/validate"
"hakurei.app/message"
"hakurei.app/system"
"hakurei.app/system/acl"
"hakurei.app/system/dbus"
)
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)
}
// /dev is mounted readonly later on, this prevents /dev/shm from going readonly with it
state.params.Tmpfs(fhs.AbsDevShm, 0, 01777)
state.params.Tmpfs(fhs.AbsDev.Append("shm"), 0, 01777)
return nil
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -27,7 +27,7 @@ func TestEntryData(t *testing.T) {
return buf.String()
}
}
templateStateGob := mustEncodeGob(NewTemplateState())
templateStateGob := mustEncodeGob(newTemplateState())
testCases := []struct {
name string
@ -45,11 +45,11 @@ func TestEntryData(t *testing.T) {
Step: "validate configuration", Err: hst.ErrConfigNull,
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,
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 {
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.Parallel()
s := NewTemplateState()
s := newTemplateState()
t.Run("gob", func(t *testing.T) {
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.
func NewTemplateState() *hst.State {
// newTemplateState returns the address of a new template [hst.State] struct.
func newTemplateState() *hst.State {
return &hst.State{
ID: hst.ID(bytes.Repeat([]byte{0xaa}, len(hst.ID{}))),
PID: 0xcafe,

View File

@ -10,7 +10,7 @@ import (
"strings"
"syscall"
"testing"
_ "unsafe" // for go:linkname
_ "unsafe"
"hakurei.app/container/check"
"hakurei.app/container/stub"
@ -18,23 +18,18 @@ import (
"hakurei.app/internal/store"
)
// Made available here for direct validation of state entry files.
//
//go:linkname newTemplateState hakurei.app/internal/store.newTemplateState
func newTemplateState() *hst.State
//go:linkname entryDecode hakurei.app/internal/store.entryDecode
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
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
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
func save(eh *store.EntryHandle, state *hst.State) error
@ -96,9 +91,9 @@ func TestStateEntryHandle(t *testing.T) {
t.Run("saveload", func(t *testing.T) {
t.Parallel()
eh := store.EntryHandle{Pathname: check.MustAbs(t.TempDir()).Append("entry"),
ID: store.NewTemplateState().ID}
ID: newTemplateState().ID}
if err := save(&eh, store.NewTemplateState()); err != nil {
if err := save(&eh, newTemplateState()); err != nil {
t.Fatalf("save: error = %v", err)
}
@ -117,7 +112,7 @@ func TestStateEntryHandle(t *testing.T) {
t.Fatal(f.Close())
}
if want := store.NewTemplateState(); !reflect.DeepEqual(&got, want) {
if want := newTemplateState(); !reflect.DeepEqual(&got, want) {
t.Errorf("entryDecode: %#v, want %#v", &got, want)
}
})
@ -127,7 +122,7 @@ func TestStateEntryHandle(t *testing.T) {
if et, err := eh.Load(nil); err != nil {
t.Fatalf("load: error = %v", err)
} else if want := store.NewTemplateState().Enablements.Unwrap(); et != want {
} else if want := newTemplateState().Enablements.Unwrap(); et != want {
t.Errorf("load: et = %x, want %x", et, want)
}
})
@ -138,7 +133,7 @@ func TestStateEntryHandle(t *testing.T) {
var got hst.State
if _, err := eh.Load(&got); err != nil {
t.Fatalf("load: error = %v", err)
} else if want := store.NewTemplateState(); !reflect.DeepEqual(&got, want) {
} else if want := newTemplateState(); !reflect.DeepEqual(&got, want) {
t.Errorf("load: %#v, want %#v", &got, want)
}
})

View File

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

View File

@ -1,81 +0,0 @@
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

@ -1,157 +0,0 @@
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"`},
})
}

View File

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

View File

@ -1,94 +0,0 @@
package wayland
import (
"errors"
"os"
"syscall"
"hakurei.app/container/check"
)
// SecurityContext holds resources associated with a Wayland security_context.
type SecurityContext struct {
// Pipe with its write end passed to security-context-v1.
closeFds [2]int
}
// Close releases any resources held by [SecurityContext], and prevents further
// connections to its associated socket.
func (sc *SecurityContext) Close() error {
if sc == nil {
return os.ErrInvalid
}
return errors.Join(
syscall.Close(sc.closeFds[1]),
syscall.Close(sc.closeFds[0]),
)
}
// New creates a new security context on the Wayland display at displayPath
// and associates it with a new socket bound to bindPath.
//
// New does not attach a finalizer to the resulting [SecurityContext] struct.
// The caller is responsible for calling [SecurityContext.Close].
//
// A non-nil error unwraps to concrete type [Error].
func New(displayPath, bindPath *check.Absolute, appID, instanceID string) (*SecurityContext, error) {
// ensure bindPath is available
if f, err := os.Create(bindPath.String()); err != nil {
return nil, &Error{RCreate, bindPath.String(), displayPath.String(), err}
} else if err = f.Close(); err != nil {
return nil, &Error{RCreate, bindPath.String(), displayPath.String(), err}
} else if err = os.Remove(bindPath.String()); err != nil {
return nil, &Error{RCreate, bindPath.String(), displayPath.String(), err}
}
if fd, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0); err != nil {
return nil, &Error{RHostSocket, bindPath.String(), displayPath.String(), err}
} else if err = syscall.Connect(fd, &syscall.SockaddrUnix{Name: displayPath.String()}); err != nil {
_ = syscall.Close(fd)
return nil, &Error{RHostConnect, bindPath.String(), displayPath.String(), err}
} else {
closeFds, bindErr := securityContextBindPipe(fd, bindPath, appID, instanceID)
if bindErr != nil {
// do not leak the pipe and socket
err = errors.Join(bindErr, // already wrapped
syscall.Close(closeFds[1]),
syscall.Close(closeFds[0]),
syscall.Close(fd),
)
}
return &SecurityContext{closeFds}, err
}
}
// securityContextBindPipe binds a socket associated to a security context created on serverFd,
// returning the pipe file descriptors used for security-context-v1 close_fd.
//
// A non-nil error unwraps to concrete type [Error].
func securityContextBindPipe(
serverFd int,
bindPath *check.Absolute,
appID, instanceID string,
) ([2]int, error) {
// write end passed to security-context-v1 close_fd
var closeFds [2]int
if err := syscall.Pipe2(closeFds[0:], syscall.O_CLOEXEC); err != nil {
return closeFds, err
}
// returned error is already wrapped
if err := securityContextBind(
bindPath.String(),
serverFd,
appID, instanceID,
closeFds[1],
); err != nil {
return closeFds, errors.Join(err,
syscall.Close(closeFds[1]),
syscall.Close(closeFds[0]),
)
} else {
return closeFds, nil
}
}

View File

@ -1,54 +0,0 @@
package wayland
import (
"errors"
"os"
"reflect"
"syscall"
"testing"
"hakurei.app/container/check"
)
func TestSecurityContextClose(t *testing.T) {
t.Parallel()
if err := (*SecurityContext)(nil).Close(); !reflect.DeepEqual(err, os.ErrInvalid) {
t.Fatalf("Close: error = %v", err)
}
var ctx SecurityContext
if err := syscall.Pipe2(ctx.closeFds[0:], syscall.O_CLOEXEC); err != nil {
t.Fatalf("Pipe: error = %v", err)
}
t.Cleanup(func() { _ = syscall.Close(ctx.closeFds[0]); _ = syscall.Close(ctx.closeFds[1]) })
if err := ctx.Close(); err != nil {
t.Fatalf("Close: error = %v", err)
}
wantErr := errors.Join(syscall.EBADF, syscall.EBADF)
if err := ctx.Close(); !reflect.DeepEqual(err, wantErr) {
t.Fatalf("Close: error = %#v, want %#v", err, wantErr)
}
}
func TestNewEnsure(t *testing.T) {
existingDirPath := check.MustAbs(t.TempDir()).Append("dir")
if err := os.MkdirAll(existingDirPath.String(), 0700); err != nil {
t.Fatal(err)
}
nonexistent := check.MustAbs("/proc/nonexistent")
wantErr := &Error{RCreate, existingDirPath.String(), nonexistent.String(), &os.PathError{
Op: "open",
Path: existingDirPath.String(),
Err: syscall.EISDIR,
}}
if _, err := New(
nonexistent,
existingDirPath, "", "",
); !reflect.DeepEqual(err, wantErr) {
t.Fatalf("New: error = %#v, want %#v", err, wantErr)
}
}

View File

@ -1,117 +0,0 @@
#include "wayland-client-helper.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include "security-context-v1-protocol.h"
#include <wayland-client.h>
static void registry_handle_global(
void *data,
struct wl_registry *registry,
uint32_t name,
const char *interface,
uint32_t version) {
struct wp_security_context_manager_v1 **out = data;
if (strcmp(interface, wp_security_context_manager_v1_interface.name) == 0)
*out = wl_registry_bind(registry, name, &wp_security_context_manager_v1_interface, 1);
}
static void registry_handle_global_remove(
void *data,
struct wl_registry *registry,
uint32_t name) {} /* no-op */
static const struct wl_registry_listener registry_listener = {
.global = registry_handle_global,
.global_remove = registry_handle_global_remove,
};
hakurei_wayland_res hakurei_security_context_bind(
char *socket_path,
int server_fd,
const char *app_id,
const char *instance_id,
int close_fd) {
hakurei_wayland_res res = HAKUREI_WAYLAND_SUCCESS; /* see wayland.go for handling */
struct wl_display *display = NULL;
struct wl_registry *registry;
struct wp_security_context_manager_v1 *security_context_manager = NULL;
int event_cnt;
int listen_fd = -1;
struct sockaddr_un sockaddr = {0};
struct wp_security_context_v1 *security_context;
display = wl_display_connect_to_fd(server_fd);
if (display == NULL) {
res = HAKUREI_WAYLAND_CONNECT;
goto out;
};
registry = wl_display_get_registry(display);
if (wl_registry_add_listener(registry, &registry_listener, &security_context_manager) < 0) {
res = HAKUREI_WAYLAND_LISTENER;
goto out;
}
event_cnt = wl_display_roundtrip(display);
wl_registry_destroy(registry);
if (event_cnt < 0) {
res = HAKUREI_WAYLAND_ROUNDTRIP;
goto out;
}
if (security_context_manager == NULL) {
res = HAKUREI_WAYLAND_NOT_AVAIL;
goto out;
}
listen_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (listen_fd < 0) {
res = HAKUREI_WAYLAND_SOCKET;
goto out;
}
sockaddr.sun_family = AF_UNIX;
snprintf(sockaddr.sun_path, sizeof(sockaddr.sun_path), "%s", socket_path);
if (bind(listen_fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) != 0) {
res = HAKUREI_WAYLAND_BIND;
goto out;
}
if (listen(listen_fd, 0) != 0) {
res = HAKUREI_WAYLAND_LISTEN;
goto out;
}
security_context = wp_security_context_manager_v1_create_listener(security_context_manager, listen_fd, close_fd);
if (security_context == NULL) { /* not reached */
res = HAKUREI_WAYLAND_NOT_AVAIL;
goto out;
}
wp_security_context_v1_set_sandbox_engine(security_context, "app.hakurei");
wp_security_context_v1_set_app_id(security_context, app_id);
wp_security_context_v1_set_instance_id(security_context, instance_id);
wp_security_context_v1_commit(security_context);
wp_security_context_v1_destroy(security_context);
if (wl_display_roundtrip(display) < 0) {
res = HAKUREI_WAYLAND_ROUNDTRIP;
goto out;
}
out:
if (listen_fd >= 0)
close(listen_fd);
if (security_context_manager != NULL)
wp_security_context_manager_v1_destroy(security_context_manager);
if (display != NULL)
wl_display_disconnect(display);
free((void *)socket_path);
free((void *)app_id);
free((void *)instance_id);
return res;
}

View File

@ -1,40 +0,0 @@
#include <stdbool.h>
#include <sys/un.h>
typedef enum {
HAKUREI_WAYLAND_SUCCESS,
/* wl_display_connect_to_fd failed, errno */
HAKUREI_WAYLAND_CONNECT,
/* wl_registry_add_listener failed, errno */
HAKUREI_WAYLAND_LISTENER,
/* wl_display_roundtrip failed, errno */
HAKUREI_WAYLAND_ROUNDTRIP,
/* compositor does not implement wp_security_context_v1 */
HAKUREI_WAYLAND_NOT_AVAIL,
/* socket failed, errno */
HAKUREI_WAYLAND_SOCKET,
/* bind failed, errno */
HAKUREI_WAYLAND_BIND,
/* listen failed, errno */
HAKUREI_WAYLAND_LISTEN,
/* ensure pathname failed, implemented in conn.go */
HAKUREI_WAYLAND_CREAT,
/* socket for host server failed, implemented in conn.go */
HAKUREI_WAYLAND_HOST_SOCKET,
/* connect for host server failed, implemented in conn.go */
HAKUREI_WAYLAND_HOST_CONNECT,
} hakurei_wayland_res;
hakurei_wayland_res hakurei_security_context_bind(
char *socket_path,
int server_fd,
const char *app_id,
const char *instance_id,
int close_fd);
/* returns whether the specified size fits in the sun_path field of sockaddr_un */
static inline bool hakurei_is_valid_size_sun_path(size_t sz) {
struct sockaddr_un sockaddr;
return sz <= sizeof(sockaddr.sun_path);
};

View File

@ -1,161 +0,0 @@
// Package wayland implements Wayland security_context_v1 protocol.
package wayland
//go:generate sh -c "wayland-scanner client-header `pkg-config --variable=datarootdir wayland-protocols`/wayland-protocols/staging/security-context/security-context-v1.xml security-context-v1-protocol.h"
//go:generate sh -c "wayland-scanner private-code `pkg-config --variable=datarootdir wayland-protocols`/wayland-protocols/staging/security-context/security-context-v1.xml security-context-v1-protocol.c"
/*
#cgo linux pkg-config: --static wayland-client
#cgo freebsd openbsd LDFLAGS: -lwayland-client
#include "wayland-client-helper.h"
*/
import "C"
import (
"errors"
"strings"
)
const (
// Display contains the name of the server socket
// (https://gitlab.freedesktop.org/wayland/wayland/-/blob/1.23.1/src/wayland-client.c#L1147)
// which is concatenated with XDG_RUNTIME_DIR
// (https://gitlab.freedesktop.org/wayland/wayland/-/blob/1.23.1/src/wayland-client.c#L1171)
// or used as-is if absolute
// (https://gitlab.freedesktop.org/wayland/wayland/-/blob/1.23.1/src/wayland-client.c#L1176).
Display = "WAYLAND_DISPLAY"
// FallbackName is used as the wayland socket name if WAYLAND_DISPLAY is unset
// (https://gitlab.freedesktop.org/wayland/wayland/-/blob/1.23.1/src/wayland-client.c#L1149).
FallbackName = "wayland-0"
)
type (
// Res is the outcome of a call to [New].
Res = C.hakurei_wayland_res
// An Error represents a failure during [New].
Error struct {
// Where the failure occurred.
Cause Res
// Attempted pathname socket.
Path string
// Pathname socket to host server. Omitted for libwayland errors.
Host string
// Global errno value set during the fault.
Errno error
}
)
// withPrefix returns prefix suffixed with errno description if available.
func (e *Error) withPrefix(prefix string) string {
if e.Errno == nil {
return prefix
}
return prefix + ": " + e.Errno.Error()
}
const (
// RSuccess is returned on a successful call.
RSuccess Res = C.HAKUREI_WAYLAND_SUCCESS
// RConnect is returned if wl_display_connect_to_fd failed. The global errno is set.
RConnect Res = C.HAKUREI_WAYLAND_CONNECT
// RListener is returned if wl_registry_add_listener failed. The global errno is set.
RListener Res = C.HAKUREI_WAYLAND_LISTENER
// RRoundtrip is returned if wl_display_roundtrip failed. The global errno is set.
RRoundtrip Res = C.HAKUREI_WAYLAND_ROUNDTRIP
// RNotAvail is returned if compositor does not implement wp_security_context_v1.
RNotAvail Res = C.HAKUREI_WAYLAND_NOT_AVAIL
// RSocket is returned if socket failed. The global errno is set.
RSocket Res = C.HAKUREI_WAYLAND_SOCKET
// RBind is returned if bind failed. The global errno is set.
RBind Res = C.HAKUREI_WAYLAND_BIND
// RListen is returned if listen failed. The global errno is set.
RListen Res = C.HAKUREI_WAYLAND_LISTEN
// RCreate is returned if ensuring pathname availability failed. Returned by [New].
RCreate Res = C.HAKUREI_WAYLAND_CREAT
// RHostSocket is returned if socket failed for host server. Returned by [New].
RHostSocket Res = C.HAKUREI_WAYLAND_HOST_SOCKET
// RHostConnect is returned if connect failed for host server. Returned by [New].
RHostConnect Res = C.HAKUREI_WAYLAND_HOST_CONNECT
)
func (e *Error) Unwrap() error { return e.Errno }
func (e *Error) Message() string { return e.Error() }
func (e *Error) Error() string {
switch e.Cause {
case RSuccess:
if e.Errno == nil {
return "success"
}
return e.Errno.Error()
case RConnect:
return e.withPrefix("wl_display_connect_to_fd failed")
case RListener:
return e.withPrefix("wl_registry_add_listener failed")
case RRoundtrip:
return e.withPrefix("wl_display_roundtrip failed")
case RNotAvail:
return "compositor does not implement security_context_v1"
case RSocket:
if e.Errno == nil {
return "socket operation failed"
}
return "socket: " + e.Errno.Error()
case RBind:
return e.withPrefix("cannot bind " + e.Path)
case RListen:
return e.withPrefix("cannot listen on " + e.Path)
case RCreate:
if e.Errno == nil {
return "cannot ensure wayland pathname socket"
}
return e.Errno.Error()
case RHostSocket:
return e.withPrefix("socket")
case RHostConnect:
return e.withPrefix("cannot connect to " + e.Host)
default:
return e.withPrefix("impossible outcome") /* not reached */
}
}
// securityContextBind calls hakurei_security_context_bind.
//
// A non-nil error has concrete type [Error].
func securityContextBind(
socketPath string,
serverFd int,
appID, instanceID string,
closeFd int,
) error {
if hasNull(socketPath) || hasNull(appID) || hasNull(instanceID) {
return &Error{Cause: RBind, Path: socketPath, Errno: errors.New("argument contains NUL character")}
}
if !C.hakurei_is_valid_size_sun_path(C.size_t(len(socketPath))) {
return &Error{Cause: RBind, Path: socketPath, Errno: errors.New("socket pathname too long")}
}
var e Error
e.Cause, e.Errno = C.hakurei_security_context_bind(
C.CString(socketPath),
C.int(serverFd),
C.CString(appID),
C.CString(instanceID),
C.int(closeFd),
)
if e.Cause == RSuccess {
return nil
}
e.Path = socketPath
return &e
}
// hasNull returns whether s contains the NUL character.
func hasNull(s string) bool { return strings.IndexByte(s, 0) > -1 }

View File

@ -1,137 +0,0 @@
package wayland
import (
"errors"
"os"
"reflect"
"syscall"
"testing"
"hakurei.app/container/stub"
)
func TestError(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
err Error
want string
}{
{"success", Error{
Cause: RSuccess,
}, "success"},
{"success errno", Error{
Cause: RSuccess,
Errno: stub.UniqueError(0),
}, "unique error 0 injected by the test suite"},
{"wl_display_connect_to_fd", Error{
Cause: RConnect,
Errno: stub.UniqueError(1),
}, "wl_display_connect_to_fd failed: unique error 1 injected by the test suite"},
{"wl_registry_add_listener", Error{
Cause: RListener,
Errno: stub.UniqueError(2),
}, "wl_registry_add_listener failed: unique error 2 injected by the test suite"},
{"wl_display_roundtrip", Error{
Cause: RRoundtrip,
Errno: stub.UniqueError(3),
}, "wl_display_roundtrip failed: unique error 3 injected by the test suite"},
{"not available", Error{
Cause: RNotAvail,
}, "compositor does not implement security_context_v1"},
{"not available errno", Error{
Cause: RNotAvail,
Errno: syscall.EAGAIN,
}, "compositor does not implement security_context_v1"},
{"socket", Error{
Cause: RSocket,
Errno: stub.UniqueError(4),
}, "socket: unique error 4 injected by the test suite"},
{"bind", Error{
Cause: RBind,
Path: "/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/wayland",
Errno: stub.UniqueError(5),
}, "cannot bind /hakurei.0/18783d07791f2460dbbcffb76c24c9e6/wayland: unique error 5 injected by the test suite"},
{"listen", Error{
Cause: RListen,
Path: "/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/wayland",
Errno: stub.UniqueError(6),
}, "cannot listen on /hakurei.0/18783d07791f2460dbbcffb76c24c9e6/wayland: unique error 6 injected by the test suite"},
{"socket invalid", Error{
Cause: RSocket,
}, "socket operation failed"},
{"create", Error{
Cause: RCreate,
}, "cannot ensure wayland pathname socket"},
{"create path", Error{
Cause: RCreate,
Errno: &os.PathError{Op: "create", Path: "/proc/nonexistent", Err: syscall.EEXIST},
}, "create /proc/nonexistent: file exists"},
{"host socket", Error{
Cause: RHostSocket,
Errno: stub.UniqueError(7),
}, "socket: unique error 7 injected by the test suite"},
{"host connect", Error{
Cause: RHostConnect,
Host: "/run/user/1971/wayland-1",
Errno: stub.UniqueError(8),
}, "cannot connect to /run/user/1971/wayland-1: unique error 8 injected by the test suite"},
{"invalid", Error{
Cause: 0xbad,
}, "impossible outcome"},
{"invalid errno", Error{
Cause: 0xbad,
Errno: stub.UniqueError(9),
}, "impossible outcome: unique error 9 injected by the test suite"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
if got := tc.err.Message(); got != tc.want {
t.Errorf("Message: %q, want %q", got, tc.want)
}
})
}
}
func TestSecurityContextBindValidate(t *testing.T) {
t.Parallel()
t.Run("NUL", func(t *testing.T) {
t.Parallel()
want := &Error{Cause: RBind, Path: "\x00", Errno: errors.New("argument contains NUL character")}
if got := securityContextBind("\x00", -1, "\x00", "\x00", -1); !reflect.DeepEqual(got, want) {
t.Fatalf("securityContextBind: error = %#v, want %#v", got, want)
}
})
t.Run("long", func(t *testing.T) {
t.Parallel()
// 256 bytes
const oversizedPath = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
want := &Error{Cause: RBind, Path: oversizedPath, Errno: errors.New("socket pathname too long")}
if got := securityContextBind(oversizedPath, -1, "", "", -1); !reflect.DeepEqual(got, want) {
t.Fatalf("securityContextBind: error = %#v, want %#v", got, want)
}
})
}

27
ldd/error.go Normal file
View File

@ -0,0 +1,27 @@
package ldd
import (
"errors"
"fmt"
)
var (
ErrUnexpectedSeparator = errors.New("unexpected separator")
ErrPathNotAbsolute = errors.New("path not absolute")
ErrBadLocationFormat = errors.New("bad location format")
ErrUnexpectedNewline = errors.New("unexpected newline")
)
type EntryUnexpectedSegmentsError string
func (e EntryUnexpectedSegmentsError) Is(err error) bool {
var eq EntryUnexpectedSegmentsError
if !errors.As(err, &eq) {
return false
}
return e == eq
}
func (e EntryUnexpectedSegmentsError) Error() string {
return fmt.Sprintf("unexpected segments in entry %q", string(e))
}

View File

@ -3,7 +3,6 @@ package ldd
import (
"bytes"
"context"
"errors"
"io"
"os"
"os/exec"
@ -17,26 +16,17 @@ import (
"hakurei.app/message"
)
const (
// msgStaticSuffix is the suffix of message printed to stderr by musl on a statically linked program.
msgStaticSuffix = ": Not a valid dynamic program"
// msgStaticGlibc is a substring of the message printed to stderr by glibc on a statically linked program.
msgStaticGlibc = "not a dynamic executable"
var (
msgStatic = []byte("Not a valid dynamic program")
msgStaticGlibc = []byte("not a dynamic executable")
)
// lddName is the file name of ldd(1) passed to exec.LookPath.
func Exec(ctx context.Context, msg message.Msg, p string) ([]*Entry, error) {
const (
lddName = "ldd"
// lddTimeout is the maximum duration ldd(1) is allowed to ran for before it is terminated.
lddTimeout = 4 * time.Second
)
// Resolve runs ldd(1) in a strict sandbox and connects its stdout to a [Decoder].
//
// The returned error has concrete type
// [exec.Error] or [check.AbsoluteError] for fault during lookup of ldd(1),
// [os.SyscallError] for fault creating the stdout pipe,
// [container.StartError] for fault during either stage of container setup.
// Otherwise, it passes through the return values of [Decoder.Decode].
func Resolve(ctx context.Context, msg message.Msg, pathname *check.Absolute) ([]*Entry, error) {
c, cancel := context.WithTimeout(ctx, lddTimeout)
defer cancel()
@ -47,24 +37,18 @@ func Resolve(ctx context.Context, msg message.Msg, pathname *check.Absolute) ([]
return nil, err
}
z := container.NewCommand(c, msg, toolPath, lddName, pathname.String())
z := container.NewCommand(c, msg, toolPath, lddName, p)
z.Hostname = "hakurei-" + lddName
z.SeccompFlags |= seccomp.AllowMultiarch
z.SeccompPresets |= std.PresetStrict
stderr := new(bytes.Buffer)
stdout, stderr := new(bytes.Buffer), new(bytes.Buffer)
z.Stdout = stdout
z.Stderr = stderr
z.
Bind(fhs.AbsRoot, fhs.AbsRoot, 0).
Proc(fhs.AbsProc).
Dev(fhs.AbsDev, false)
var d *Decoder
if r, err := z.StdoutPipe(); err != nil {
return nil, err
} else {
d = NewDecoder(r)
}
if err := z.Start(); err != nil {
return nil, err
}
@ -72,35 +56,15 @@ func Resolve(ctx context.Context, msg message.Msg, pathname *check.Absolute) ([]
if err := z.Serve(); err != nil {
return nil, err
}
entries, decodeErr := d.Decode()
if decodeErr != nil {
// do not cancel on successful decode to avoid racing with ldd(1) termination
cancel()
}
if err := z.Wait(); err != nil {
m := stderr.Bytes()
if bytes.Contains(m, []byte(msgStaticSuffix)) || bytes.Contains(m, []byte(msgStaticGlibc)) {
if bytes.Contains(m, append([]byte(p+": "), msgStatic...)) ||
bytes.Contains(m, msgStaticGlibc) {
return nil, nil
}
if decodeErr != nil {
return nil, errors.Join(decodeErr, err)
}
return nil, err
}
return entries, decodeErr
}
// Exec runs ldd(1) in a restrictive [container] and connects it to a [Decoder], returning resulting entries.
//
// Deprecated: this function takes an unchecked pathname string.
// Relative pathnames do not work in the container as working directory information is not sent.
func Exec(ctx context.Context, msg message.Msg, pathname string) ([]*Entry, error) {
if a, err := check.NewAbs(pathname); err != nil {
return nil, err
} else {
return Resolve(ctx, msg, a)
}
v := stdout.Bytes()
return Parse(v)
}

View File

@ -1,46 +0,0 @@
package ldd_test
import (
"errors"
"os"
"os/exec"
"testing"
"hakurei.app/container"
"hakurei.app/container/check"
"hakurei.app/ldd"
"hakurei.app/message"
)
func TestExec(t *testing.T) {
t.Parallel()
t.Run("failure", func(t *testing.T) {
t.Parallel()
_, err := ldd.Resolve(t.Context(), nil, check.MustAbs("/proc/nonexistent"))
var exitError *exec.ExitError
if !errors.As(err, &exitError) {
t.Fatalf("Exec: error has incorrect concrete type: %#v", err)
}
const want = 1
if got := exitError.ExitCode(); got != want {
t.Fatalf("Exec: ExitCode = %d, want %d", got, want)
}
})
t.Run("success", func(t *testing.T) {
msg := message.New(nil)
msg.GetLogger().SetPrefix("check: ")
if entries, err := ldd.Resolve(t.Context(), nil, check.MustAbs(container.MustExecutable(msg))); err != nil {
t.Fatalf("Exec: error = %v", err)
} else if testing.Verbose() {
// result cannot be measured here as build information is not known
t.Logf("Exec: %q", entries)
}
})
}
func TestMain(m *testing.M) { container.TryArgv0(nil); os.Exit(m.Run()) }

View File

@ -1,241 +1,66 @@
// Package ldd provides a robust parser for ldd(1) output, and a convenience function
// for running ldd(1) in a strict sandbox.
//
// Note: despite the additional hardening, great care must be taken when using ldd(1).
// As a general rule, you must never run ldd(1) against a file that you do not wish to
// execute within the same context.
// Package ldd retrieves linker information by invoking ldd from glibc or musl and parsing its output.
package ldd
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"math"
"path"
"strconv"
"strings"
"hakurei.app/container/check"
)
var (
// ErrUnexpectedNewline is returned when encountering an unexpected empty line.
ErrUnexpectedNewline = errors.New("unexpected newline")
// ErrUnexpectedSeparator is returned when encountering an unexpected separator segment.
ErrUnexpectedSeparator = errors.New("unexpected separator")
// ErrBadLocationFormat is returned for an incorrectly formatted [Entry.Location] segment.
ErrBadLocationFormat = errors.New("bad location format")
)
// EntryUnexpectedSegmentsError is returned when encountering
// a line containing unexpected number of segments.
type EntryUnexpectedSegmentsError string
func (e EntryUnexpectedSegmentsError) Error() string {
return fmt.Sprintf("unexpected segments in entry %q", string(e))
}
// An Entry represents one line of ldd(1) output.
type Entry struct {
// File name of required object.
Name string `json:"name"`
// Absolute pathname of matched object. Only populated for the long variant.
Path *check.Absolute `json:"path,omitempty"`
// Address at which the object is loaded.
Name string `json:"name,omitempty"`
Path string `json:"path,omitempty"`
Location uint64 `json:"location"`
}
const (
// entrySegmentIndexName is the index of the segment holding [Entry.Name].
entrySegmentIndexName = 0
// entrySegmentIndexPath is the index of the segment holding [Entry.Path],
// present only for a line describing a fully populated [Entry].
entrySegmentIndexPath = 2
// entrySegmentIndexSeparator is the index of the segment containing the magic bytes entrySegmentFullSeparator,
// present only for a line describing a fully populated [Entry].
entrySegmentIndexSeparator = 1
// entrySegmentIndexLocation is the index of the segment holding [Entry.Location]
// for a line describing a fully populated [Entry].
entrySegmentIndexLocation = 3
// entrySegmentIndexLocationShort is the index of the segment holding [Entry.Location]
// for a line describing only [Entry.Name].
entrySegmentIndexLocationShort = 1
func Parse(p []byte) ([]*Entry, error) {
payload := strings.Split(strings.TrimSpace(string(p)), "\n")
result := make([]*Entry, len(payload))
// entrySegmentSep is the byte separating segments in an [Entry] line.
entrySegmentSep = ' '
// entrySegmentFullSeparator is the exact contents of the segment at index entrySegmentIndexSeparator.
entrySegmentFullSeparator = "=>"
// entrySegmentLocationLengthMin is the minimum possible length of a segment corresponding to [Entry.Location].
entrySegmentLocationLengthMin = 4
// entrySegmentLocationPrefix are magic bytes prefixing a segment corresponding to [Entry.Location].
entrySegmentLocationPrefix = "(0x"
// entrySegmentLocationSuffix is the magic byte suffixing a segment corresponding to [Entry.Location].
entrySegmentLocationSuffix = ')'
)
// decodeLocationSegment decodes and saves the segment corresponding to [Entry.Location].
func (e *Entry) decodeLocationSegment(segment []byte) (err error) {
if len(segment) < entrySegmentLocationLengthMin ||
segment[len(segment)-1] != entrySegmentLocationSuffix ||
string(segment[:len(entrySegmentLocationPrefix)]) != entrySegmentLocationPrefix {
return ErrBadLocationFormat
for i, ent := range payload {
if len(ent) == 0 {
return nil, ErrUnexpectedNewline
}
e.Location, err = strconv.ParseUint(string(segment[3:len(segment)-1]), 16, 64)
return
}
segment := strings.SplitN(ent, " ", 5)
// UnmarshalText parses a line of ldd(1) output and saves it to [Entry].
func (e *Entry) UnmarshalText(data []byte) error {
var (
segments = bytes.SplitN(data, []byte{entrySegmentSep}, 5)
// segment to pass to decodeLocationSegment
iL int
)
// location index
var iL int
switch len(segments) {
switch len(segment) {
case 2: // /lib/ld-musl-x86_64.so.1 (0x7f04d14ef000)
iL = entrySegmentIndexLocationShort
e.Name = string(bytes.TrimSpace(segments[entrySegmentIndexName]))
iL = 1
result[i] = &Entry{Name: strings.TrimSpace(segment[0])}
case 4: // libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7f04d14ef000)
iL = entrySegmentIndexLocation
if string(segments[entrySegmentIndexSeparator]) != entrySegmentFullSeparator {
return ErrUnexpectedSeparator
iL = 3
if segment[1] != "=>" {
return nil, ErrUnexpectedSeparator
}
if a, err := check.NewAbs(string(segments[entrySegmentIndexPath])); err != nil {
return err
} else {
e.Path = a
if !path.IsAbs(segment[2]) {
return nil, ErrPathNotAbsolute
}
result[i] = &Entry{
Name: strings.TrimSpace(segment[0]),
Path: segment[2],
}
e.Name = string(bytes.TrimSpace(segments[entrySegmentIndexName]))
default:
return EntryUnexpectedSegmentsError(data)
return nil, EntryUnexpectedSegmentsError(ent)
}
return e.decodeLocationSegment(segments[iL])
}
// String returns the musl ldd(1) representation of [Entry].
func (e *Entry) String() string {
// nameInvalid is used for a zero-length e.Name
const nameInvalid = "invalid"
var buf strings.Builder
// libzstd.so.1 => /usr/lib/libzstd.so.1 (0x7ff71bfd2000)
l := len(e.Name) + 1
if e.Name == "" {
l += len(nameInvalid)
}
if e.Path != nil {
l += len(entrySegmentFullSeparator) + 1 + len(e.Path.String()) + 1
}
l += len(entrySegmentLocationPrefix) + 1<<4 + 1
buf.Grow(l)
if e.Name != "" {
buf.WriteString(e.Name + " ")
if loc, err := parseLocation(segment[iL]); err != nil {
return nil, err
} else {
buf.WriteString(nameInvalid + " ")
result[i].Location = loc
}
if e.Path != nil {
buf.WriteString(entrySegmentFullSeparator + " " + e.Path.String() + " ")
}
buf.WriteString(entrySegmentLocationPrefix + strconv.FormatUint(e.Location, 16) + string(entrySegmentLocationSuffix))
return buf.String()
}
// Path returns a deduplicated slice of absolute directory paths in entries.
func Path(entries []*Entry) []*check.Absolute {
p := make([]*check.Absolute, 0, len(entries)*2)
for _, entry := range entries {
if entry.Path != nil {
p = append(p, entry.Path.Dir())
}
if a, err := check.NewAbs(entry.Name); err == nil {
p = append(p, a.Dir())
}
}
check.SortAbs(p)
return check.CompactAbs(p)
return result, nil
}
// A Decoder reads and decodes [Entry] values from an input stream.
//
// The zero value is not safe for use.
type Decoder struct {
s *bufio.Scanner
// Whether the current line is not the first line.
notFirst bool
// Whether s has no more tokens.
depleted bool
// Holds onto the first error encountered while parsing.
err error
func parseLocation(s string) (uint64, error) {
if len(s) < 4 || s[len(s)-1] != ')' || s[:3] != "(0x" {
return math.MaxUint64, ErrBadLocationFormat
}
// NewDecoder returns a new decoder that reads from r.
//
// The decoder introduces its own buffering and may read
// data from r beyond the [Entry] values requested.
func NewDecoder(r io.Reader) *Decoder { return &Decoder{s: bufio.NewScanner(r)} }
// Scan advances the [Decoder] to the next [Entry] and
// stores the result in the value pointed to by v.
func (d *Decoder) Scan(v *Entry) bool {
if d.s == nil || d.err != nil || d.depleted {
return false
return strconv.ParseUint(s[3:len(s)-1], 16, 64)
}
if !d.s.Scan() {
d.depleted = true
return false
}
data := d.s.Bytes()
if len(data) == 0 {
if d.notFirst {
if d.s.Scan() && d.err == nil {
d.err = ErrUnexpectedNewline
}
// trailing newline is allowed (glibc)
return false
}
// leading newline is allowed (musl)
d.notFirst = true
return d.Scan(v)
}
d.notFirst = true
d.err = v.UnmarshalText(data)
return d.err == nil
}
// Err returns the first non-EOF error that was encountered
// by the underlying [bufio.Scanner] or [Entry].
func (d *Decoder) Err() error {
if d.err != nil || d.s == nil {
return d.err
}
return d.s.Err()
}
// Decode reads from the input stream until there are no more entries
// and returns the results in a slice.
func (d *Decoder) Decode() ([]*Entry, error) {
var entries []*Entry
e := new(Entry)
for d.Scan(e) {
entries = append(entries, e)
e = new(Entry)
}
return entries, d.Err()
}
// Parse returns a slice of addresses to [Entry] decoded from p.
func Parse(p []byte) ([]*Entry, error) { return NewDecoder(bytes.NewReader(p)).Decode() }

View File

@ -1,23 +1,14 @@
package ldd_test
import (
"encoding/json"
"errors"
"reflect"
"strings"
"testing"
"hakurei.app/container/check"
"hakurei.app/ldd"
)
func TestEntryUnexpectedSegmentsError(t *testing.T) {
const want = `unexpected segments in entry "\x00"`
if got := ldd.EntryUnexpectedSegmentsError("\x00").Error(); got != want {
t.Fatalf("Error: %s, want %s", got, want)
}
}
func TestDecodeError(t *testing.T) {
func TestParseError(t *testing.T) {
t.Parallel()
testCases := []struct {
@ -29,36 +20,24 @@ func TestDecodeError(t *testing.T) {
libzstd.so.1 => /usr/lib/libzstd.so.1 (0x7ff71bfd2000)
`, ldd.ErrUnexpectedNewline},
{"unexpected separator", `
libzstd.so.1 = /usr/lib/libzstd.so.1 (0x7ff71bfd2000)
`, ldd.ErrUnexpectedSeparator},
{"path not absolute", `
libzstd.so.1 => usr/lib/libzstd.so.1 (0x7ff71bfd2000)
`, &check.AbsoluteError{Pathname: "usr/lib/libzstd.so.1"}},
`, ldd.ErrPathNotAbsolute},
{"unexpected segments", `
meow libzstd.so.1 => /usr/lib/libzstd.so.1 (0x7ff71bfd2000)
`, ldd.EntryUnexpectedSegmentsError("meow libzstd.so.1 => /usr/lib/libzstd.so.1 (0x7ff71bfd2000)")},
{"bad location format", `
libzstd.so.1 => /usr/lib/libzstd.so.1 7ff71bfd2000
`, ldd.ErrBadLocationFormat},
{"valid", ``, nil},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
d := ldd.NewDecoder(strings.NewReader(tc.out))
if _, err := d.Decode(); !reflect.DeepEqual(err, tc.wantErr) {
t.Errorf("Decode: error = %v, wantErr %v", err, tc.wantErr)
}
if d.Scan(new(ldd.Entry)) {
t.Fatalf("Scan: unexpected true")
if _, err := ldd.Parse([]byte(tc.out)); !errors.Is(err, tc.wantErr) {
t.Errorf("Parse() error = %v, wantErr %v", err, tc.wantErr)
}
})
}
@ -70,7 +49,6 @@ func TestParse(t *testing.T) {
testCases := []struct {
file, out string
want []*ldd.Entry
paths []*check.Absolute
}{
{"musl /bin/kmod", `
/lib/ld-musl-x86_64.so.1 (0x7ff71c0a4000)
@ -78,38 +56,30 @@ libzstd.so.1 => /usr/lib/libzstd.so.1 (0x7ff71bfd2000)
liblzma.so.5 => /usr/lib/liblzma.so.5 (0x7ff71bf9a000)
libz.so.1 => /lib/libz.so.1 (0x7ff71bf80000)
libcrypto.so.3 => /lib/libcrypto.so.3 (0x7ff71ba00000)
libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7ff71c0a4000)`, []*ldd.Entry{
{"/lib/ld-musl-x86_64.so.1", nil, 0x7ff71c0a4000},
{"libzstd.so.1", check.MustAbs("/usr/lib/libzstd.so.1"), 0x7ff71bfd2000},
{"liblzma.so.5", check.MustAbs("/usr/lib/liblzma.so.5"), 0x7ff71bf9a000},
{"libz.so.1", check.MustAbs("/lib/libz.so.1"), 0x7ff71bf80000},
{"libcrypto.so.3", check.MustAbs("/lib/libcrypto.so.3"), 0x7ff71ba00000},
{"libc.musl-x86_64.so.1", check.MustAbs("/lib/ld-musl-x86_64.so.1"), 0x7ff71c0a4000},
}, []*check.Absolute{
check.MustAbs("/lib"),
check.MustAbs("/usr/lib"),
libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7ff71c0a4000)`,
[]*ldd.Entry{
{"/lib/ld-musl-x86_64.so.1", "", 0x7ff71c0a4000},
{"libzstd.so.1", "/usr/lib/libzstd.so.1", 0x7ff71bfd2000},
{"liblzma.so.5", "/usr/lib/liblzma.so.5", 0x7ff71bf9a000},
{"libz.so.1", "/lib/libz.so.1", 0x7ff71bf80000},
{"libcrypto.so.3", "/lib/libcrypto.so.3", 0x7ff71ba00000},
{"libc.musl-x86_64.so.1", "/lib/ld-musl-x86_64.so.1", 0x7ff71c0a4000},
}},
{"glibc /nix/store/rc3n2r3nffpib2gqpxlkjx36frw6n34z-kmod-31/bin/kmod", `
linux-vdso.so.1 (0x00007ffed65be000)
libzstd.so.1 => /nix/store/80pxmvb9q43kh9rkjagc4h41vf6dh1y6-zstd-1.5.6/lib/libzstd.so.1 (0x00007f3199cd1000)
liblzma.so.5 => /nix/store/g78jna1i5qhh8gqs4mr64648f0szqgw4-xz-5.4.7/lib/liblzma.so.5 (0x00007f3199ca2000)
libc.so.6 => /nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/libc.so.6 (0x00007f3199ab5000)
libpthread.so.0 => /nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/libpthread.so.0 (0x00007f3199ab0000)
/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/ld-linux-x86-64.so.2 => /nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib64/ld-linux-x86-64.so.2 (0x00007f3199da5000)`, []*ldd.Entry{
{"linux-vdso.so.1", nil, 0x00007ffed65be000},
{"libzstd.so.1", check.MustAbs("/nix/store/80pxmvb9q43kh9rkjagc4h41vf6dh1y6-zstd-1.5.6/lib/libzstd.so.1"), 0x00007f3199cd1000},
{"liblzma.so.5", check.MustAbs("/nix/store/g78jna1i5qhh8gqs4mr64648f0szqgw4-xz-5.4.7/lib/liblzma.so.5"), 0x00007f3199ca2000},
{"libc.so.6", check.MustAbs("/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/libc.so.6"), 0x00007f3199ab5000},
{"libpthread.so.0", check.MustAbs("/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/libpthread.so.0"), 0x00007f3199ab0000},
{"/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/ld-linux-x86-64.so.2", check.MustAbs("/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib64/ld-linux-x86-64.so.2"), 0x00007f3199da5000},
}, []*check.Absolute{
check.MustAbs("/nix/store/80pxmvb9q43kh9rkjagc4h41vf6dh1y6-zstd-1.5.6/lib"),
check.MustAbs("/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib"),
check.MustAbs("/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib64"),
check.MustAbs("/nix/store/g78jna1i5qhh8gqs4mr64648f0szqgw4-xz-5.4.7/lib"),
/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/ld-linux-x86-64.so.2 => /nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib64/ld-linux-x86-64.so.2 (0x00007f3199da5000)`,
[]*ldd.Entry{
{"linux-vdso.so.1", "", 0x00007ffed65be000},
{"libzstd.so.1", "/nix/store/80pxmvb9q43kh9rkjagc4h41vf6dh1y6-zstd-1.5.6/lib/libzstd.so.1", 0x00007f3199cd1000},
{"liblzma.so.5", "/nix/store/g78jna1i5qhh8gqs4mr64648f0szqgw4-xz-5.4.7/lib/liblzma.so.5", 0x00007f3199ca2000},
{"libc.so.6", "/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/libc.so.6", 0x00007f3199ab5000},
{"libpthread.so.0", "/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/libpthread.so.0", 0x00007f3199ab0000},
{"/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/ld-linux-x86-64.so.2", "/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib64/ld-linux-x86-64.so.2", 0x00007f3199da5000},
}},
{"glibc /usr/bin/xdg-dbus-proxy", `
linux-vdso.so.1 (0x00007725f5772000)
libglib-2.0.so.0 => /usr/lib/libglib-2.0.so.0 (0x00007725f55d5000)
@ -123,129 +93,31 @@ libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7ff71c0a4000)`, []*ldd.Entr
libmount.so.1 => /usr/lib/libmount.so.1 (0x00007725f5076000)
libffi.so.8 => /usr/lib/libffi.so.8 (0x00007725f506b000)
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007725f5774000)
libblkid.so.1 => /usr/lib/libblkid.so.1 (0x00007725f5032000)`, []*ldd.Entry{
{"linux-vdso.so.1", nil, 0x00007725f5772000},
{"libglib-2.0.so.0", check.MustAbs("/usr/lib/libglib-2.0.so.0"), 0x00007725f55d5000},
{"libgio-2.0.so.0", check.MustAbs("/usr/lib/libgio-2.0.so.0"), 0x00007725f5406000},
{"libgobject-2.0.so.0", check.MustAbs("/usr/lib/libgobject-2.0.so.0"), 0x00007725f53a6000},
{"libgcc_s.so.1", check.MustAbs("/usr/lib/libgcc_s.so.1"), 0x00007725f5378000},
{"libc.so.6", check.MustAbs("/usr/lib/libc.so.6"), 0x00007725f5187000},
{"libpcre2-8.so.0", check.MustAbs("/usr/lib/libpcre2-8.so.0"), 0x00007725f50e8000},
{"libgmodule-2.0.so.0", check.MustAbs("/usr/lib/libgmodule-2.0.so.0"), 0x00007725f50df000},
{"libz.so.1", check.MustAbs("/usr/lib/libz.so.1"), 0x00007725f50c6000},
{"libmount.so.1", check.MustAbs("/usr/lib/libmount.so.1"), 0x00007725f5076000},
{"libffi.so.8", check.MustAbs("/usr/lib/libffi.so.8"), 0x00007725f506b000},
{"/lib64/ld-linux-x86-64.so.2", check.MustAbs("/usr/lib64/ld-linux-x86-64.so.2"), 0x00007725f5774000},
{"libblkid.so.1", check.MustAbs("/usr/lib/libblkid.so.1"), 0x00007725f5032000},
}, []*check.Absolute{
check.MustAbs("/lib64"),
check.MustAbs("/usr/lib"),
check.MustAbs("/usr/lib64"),
libblkid.so.1 => /usr/lib/libblkid.so.1 (0x00007725f5032000)`,
[]*ldd.Entry{
{"linux-vdso.so.1", "", 0x00007725f5772000},
{"libglib-2.0.so.0", "/usr/lib/libglib-2.0.so.0", 0x00007725f55d5000},
{"libgio-2.0.so.0", "/usr/lib/libgio-2.0.so.0", 0x00007725f5406000},
{"libgobject-2.0.so.0", "/usr/lib/libgobject-2.0.so.0", 0x00007725f53a6000},
{"libgcc_s.so.1", "/usr/lib/libgcc_s.so.1", 0x00007725f5378000},
{"libc.so.6", "/usr/lib/libc.so.6", 0x00007725f5187000},
{"libpcre2-8.so.0", "/usr/lib/libpcre2-8.so.0", 0x00007725f50e8000},
{"libgmodule-2.0.so.0", "/usr/lib/libgmodule-2.0.so.0", 0x00007725f50df000},
{"libz.so.1", "/usr/lib/libz.so.1", 0x00007725f50c6000},
{"libmount.so.1", "/usr/lib/libmount.so.1", 0x00007725f5076000},
{"libffi.so.8", "/usr/lib/libffi.so.8", 0x00007725f506b000},
{"/lib64/ld-linux-x86-64.so.2", "/usr/lib64/ld-linux-x86-64.so.2", 0x00007725f5774000},
{"libblkid.so.1", "/usr/lib/libblkid.so.1", 0x00007725f5032000},
}},
}
for _, tc := range testCases {
t.Run(tc.file, func(t *testing.T) {
t.Parallel()
if got, err := ldd.Parse([]byte(tc.out)); err != nil {
t.Errorf("Parse: error = %v", err)
t.Errorf("Parse() error = %v", err)
} else if !reflect.DeepEqual(got, tc.want) {
t.Errorf("Parse: \n%s\nwant\n%s", mustMarshalJSON(got), mustMarshalJSON(tc.want))
} else if paths := ldd.Path(got); !reflect.DeepEqual(paths, tc.paths) {
t.Errorf("Paths: %v, want %v", paths, tc.paths)
t.Errorf("Parse() got = %#v, want %#v", got, tc.want)
}
})
}
}
func TestString(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
e ldd.Entry
want string
}{
{"ld", ldd.Entry{
Name: "/lib/ld-musl-x86_64.so.1",
Location: 0x7ff71c0a4000,
}, `/lib/ld-musl-x86_64.so.1 (0x7ff71c0a4000)`},
{"libzstd", ldd.Entry{
Name: "libzstd.so.1",
Path: check.MustAbs("/usr/lib/libzstd.so.1"),
Location: 0x7ff71bfd2000,
}, `libzstd.so.1 => /usr/lib/libzstd.so.1 (0x7ff71bfd2000)`},
{"liblzma", ldd.Entry{
Name: "liblzma.so.5",
Path: check.MustAbs("/usr/lib/liblzma.so.5"),
Location: 0x7ff71bf9a000,
}, `liblzma.so.5 => /usr/lib/liblzma.so.5 (0x7ff71bf9a000)`},
{"libz", ldd.Entry{
Name: "libz.so.1",
Path: check.MustAbs("/lib/libz.so.1"),
Location: 0x7ff71bf80000,
}, `libz.so.1 => /lib/libz.so.1 (0x7ff71bf80000)`},
{"libcrypto", ldd.Entry{
Name: "libcrypto.so.3",
Path: check.MustAbs("/lib/libcrypto.so.3"),
Location: 0x7ff71ba00000,
}, `libcrypto.so.3 => /lib/libcrypto.so.3 (0x7ff71ba00000)`},
{"libc", ldd.Entry{
Name: "libc.musl-x86_64.so.1",
Path: check.MustAbs("/lib/ld-musl-x86_64.so.1"),
Location: 0x7ff71c0a4000,
}, `libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7ff71c0a4000)`},
{"invalid", ldd.Entry{
Location: 0x7ff71c0a4000,
}, `invalid (0x7ff71c0a4000)`},
{"invalid long", ldd.Entry{
Path: check.MustAbs("/lib/ld-musl-x86_64.so.1"),
Location: 0x7ff71c0a4000,
}, `invalid => /lib/ld-musl-x86_64.so.1 (0x7ff71c0a4000)`},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
t.Run("decode", func(t *testing.T) {
if tc.e.Name == "" {
return
}
t.Parallel()
var got ldd.Entry
if err := got.UnmarshalText([]byte(tc.want)); err != nil {
t.Fatalf("UnmarshalText: error = %v", err)
}
if !reflect.DeepEqual(&got, &tc.e) {
t.Errorf("UnmarshalText: %#v, want %#v", got, tc.e)
}
})
t.Run("encode", func(t *testing.T) {
t.Parallel()
if got := tc.e.String(); got != tc.want {
t.Errorf("String: %s, want %s", got, tc.want)
}
})
})
}
}
// mustMarshalJSON calls [json.Marshal] and returns the resulting data.
func mustMarshalJSON(v any) []byte {
if data, err := json.Marshal(v); err != nil {
panic(err)
} else {
return data
}
}

20
ldd/path.go Normal file
View File

@ -0,0 +1,20 @@
package ldd
import (
"hakurei.app/container/check"
)
// Path returns a deterministic, deduplicated slice of absolute directory paths in entries.
func Path(entries []*Entry) []*check.Absolute {
p := make([]*check.Absolute, 0, len(entries)*2)
for _, entry := range entries {
if a, err := check.NewAbs(entry.Path); err == nil {
p = append(p, a.Dir())
}
if a, err := check.NewAbs(entry.Name); err == nil {
p = append(p, a.Dir())
}
}
check.SortAbs(p)
return check.CompactAbs(p)
}

View File

@ -20,11 +20,10 @@
# for passthru.buildInputs
go,
clang,
gcc,
# for check
util-linux,
nettools,
glibc, # for ldd
withStatic ? stdenv.hostPlatform.isStatic,
@ -66,7 +65,7 @@ buildGoModule rec {
lib.attrsets.foldlAttrs
(
ldflags: name: value:
ldflags ++ [ "-X hakurei.app/internal/info.${name}=${value}" ]
ldflags ++ [ "-X hakurei.app/internal.${name}=${value}" ]
)
(
[ "-s -w" ]
@ -81,13 +80,8 @@ buildGoModule rec {
hsuPath = "/run/wrappers/bin/hsu";
};
env = {
# use clang instead of gcc
CC = "clang -O3 -Werror";
# nix build environment does not allow acls
GO_TEST_SKIP_ACL = 1;
};
env.GO_TEST_SKIP_ACL = 1;
buildInputs = [
libffi
@ -104,9 +98,6 @@ buildGoModule rec {
nativeBuildInputs = [
pkg-config
makeBinaryWrapper
# for container example
nettools
];
postInstall =
@ -140,7 +131,7 @@ buildGoModule rec {
passthru.targetPkgs = [
go
clang
gcc
xorg.xorgproto
util-linux

View File

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

View File

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

View File

@ -1,25 +0,0 @@
// Package acl exposes the internal/acl package.
//
// Deprecated: This package will be removed in 0.4.
package acl
import (
_ "unsafe" // for go:linkname
"hakurei.app/internal/acl"
)
type Perm = acl.Perm
const (
Read = acl.Read
Write = acl.Write
Execute = acl.Execute
)
// Update replaces ACL_USER entry with qualifier uid.
//
//go:linkname Update hakurei.app/internal/acl.Update
func Update(name string, uid int, perms ...Perm) error
type Perms = acl.Perms

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 (
"testing"
"hakurei.app/internal/acl"
"hakurei.app/system/acl"
)
func TestPerms(t *testing.T) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,115 +0,0 @@
// Package dbus exposes the internal/dbus package.
//
// Deprecated: This package will be removed in 0.4.
package dbus
import (
"context"
"io"
_ "unsafe" // for go:linkname
"hakurei.app/hst"
"hakurei.app/internal/dbus"
"hakurei.app/message"
)
type AddrEntry = dbus.AddrEntry
// EqualAddrEntries returns whether two slices of [AddrEntry] are equal.
//
//go:linkname EqualAddrEntries hakurei.app/internal/dbus.EqualAddrEntries
func EqualAddrEntries(entries, target []AddrEntry) bool
// Parse parses D-Bus address according to
// https://dbus.freedesktop.org/doc/dbus-specification.html#addresses
//
//go:linkname Parse hakurei.app/internal/dbus.Parse
func Parse(addr []byte) ([]AddrEntry, error)
type ParseError = dbus.ParseError
const (
ErrNoColon = dbus.ErrNoColon
ErrBadPairSep = dbus.ErrBadPairSep
ErrBadPairKey = dbus.ErrBadPairKey
ErrBadPairVal = dbus.ErrBadPairVal
ErrBadValLength = dbus.ErrBadValLength
ErrBadValByte = dbus.ErrBadValByte
ErrBadValHexLength = dbus.ErrBadValHexLength
ErrBadValHexByte = dbus.ErrBadValHexByte
)
type BadAddressError = dbus.BadAddressError
// ProxyPair is an upstream dbus address and a downstream socket path.
type ProxyPair = dbus.ProxyPair
// Args returns the xdg-dbus-proxy arguments equivalent of [hst.BusConfig].
//
//go:linkname Args hakurei.app/internal/dbus.Args
func Args(c *hst.BusConfig, bus ProxyPair) (args []string)
// NewConfig returns the address of a new [hst.BusConfig] with optional defaults.
//
//go:linkname NewConfig hakurei.app/internal/dbus.NewConfig
func NewConfig(id string, defaults, mpris bool) *hst.BusConfig
const (
/*
SessionBusAddress is the name of the environment variable where the address of the login session message bus is given in.
If that variable is not set, applications may also try to read the address from the X Window System root window property _DBUS_SESSION_BUS_ADDRESS.
The root window property must have type STRING. The environment variable should have precedence over the root window property.
The address of the login session message bus is given in the DBUS_SESSION_BUS_ADDRESS environment variable.
If DBUS_SESSION_BUS_ADDRESS is not set, or if it's set to the string "autolaunch:",
the system should use platform-specific methods of locating a running D-Bus session server,
or starting one if a running instance cannot be found.
Note that this mechanism is not recommended for attempting to determine if a daemon is running.
It is inherently racy to attempt to make this determination, since the bus daemon may be started just before or just after the determination is made.
Therefore, it is recommended that applications do not try to make this determination for their functionality purposes, and instead they should attempt to start the server.
This package diverges from the specification, as the caller is unlikely to be an X client, or be in a position to autolaunch a dbus server.
So a fallback address with a socket located in the well-known default XDG_RUNTIME_DIR formatting is used.
*/
SessionBusAddress = dbus.SessionBusAddress
/*
SystemBusAddress is the name of the environment variable where the address of the system message bus is given in.
If that variable is not set, applications should try to connect to the well-known address unix:path=/var/run/dbus/system_bus_socket.
Implementations of the well-known system bus should listen on an address that will result in that connection being successful.
*/
SystemBusAddress = dbus.SystemBusAddress
// FallbackSystemBusAddress is used when [SystemBusAddress] is not set.
FallbackSystemBusAddress = dbus.FallbackSystemBusAddress
)
// Address returns the session and system bus addresses copied from environment,
// or appropriate fallback values if they are not set.
//
//go:linkname Address hakurei.app/internal/dbus.Address
func Address() (session, system string)
// ProxyName is the file name or path to the proxy program.
// Overriding ProxyName will only affect Proxy instance created after the change.
//
//go:linkname ProxyName hakurei.app/internal/dbus.ProxyName
var ProxyName string
// Proxy holds the state of a xdg-dbus-proxy process, and should never be copied.
type Proxy = dbus.Proxy
// Final describes the outcome of a proxy configuration.
type Final = dbus.Final
// Finalise creates a checked argument writer for [Proxy].
//
//go:linkname Finalise hakurei.app/internal/dbus.Finalise
func Finalise(sessionBus, systemBus ProxyPair, session, system *hst.BusConfig) (final *Final, err error)
// New returns a new instance of [Proxy].
//
//go:linkname New hakurei.app/internal/dbus.New
func New(ctx context.Context, msg message.Msg, final *Final, output io.Writer) *Proxy

View File

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

View File

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

View File

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

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