Compare commits

..

No commits in common. "16e674782abbda8c85eac2e7d0c5c22e8cfbf5de" and "17ffdb2dcf3bb5c8edf277a017cecd8e71a2e26b" have entirely different histories.

125 changed files with 317 additions and 1066 deletions

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"
"hakurei.app/internal/env"
"hakurei.app/internal/info"
"hakurei.app/internal/outcome"
"hakurei.app/internal/system/dbus"
"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

@ -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

@ -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

@ -14,9 +14,9 @@ import (
"hakurei.app/container/check"
"hakurei.app/container/seccomp"
"hakurei.app/container/std"
"hakurei.app/internal/info"
"hakurei.app/internal/system/dbus"
"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/system"
"hakurei.app/internal/system/acl"
"hakurei.app/internal/system/dbus"
"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

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

@ -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/system"
"hakurei.app/internal/system/acl"
"hakurei.app/internal/system/dbus"
"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/system"
"hakurei.app/internal/system/acl"
"hakurei.app/internal/system/dbus"
"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/system/acl"
"hakurei.app/internal/system/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/helper"
"hakurei.app/internal/system"
"hakurei.app/internal/system/acl"
"hakurei.app/internal/system/dbus"
"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/system"
"hakurei.app/internal/system/acl"
"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/system"
"hakurei.app/internal/system/acl"
"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/system"
"hakurei.app/internal/system/acl"
"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/system"
"hakurei.app/internal/system/acl"
"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/system"
"hakurei.app/internal/system/acl"
"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/system/acl"
"hakurei.app/internal/system/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/system"
"hakurei.app/internal/system/acl"
"hakurei.app/internal/system/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/system/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/system/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,4 +1,4 @@
package info
package internal
// FallbackVersion is returned when a version string was not set by the linker.
const FallbackVersion = "dirty"

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"
// lddName is the file name of ldd(1) passed to exec.LookPath.
lddName = "ldd"
// lddTimeout is the maximum duration ldd(1) is allowed to ran for before it is terminated.
lddTimeout = 4 * time.Second
var (
msgStatic = []byte("Not a valid dynamic program")
msgStaticGlibc = []byte("not a dynamic executable")
)
// 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) {
func Exec(ctx context.Context, msg message.Msg, p string) ([]*Entry, error) {
const (
lddName = "ldd"
lddTimeout = 4 * time.Second
)
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
}
e.Location, err = strconv.ParseUint(string(segment[3:len(segment)-1]), 16, 64)
return
}
// 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
)
switch len(segments) {
case 2: // /lib/ld-musl-x86_64.so.1 (0x7f04d14ef000)
iL = entrySegmentIndexLocationShort
e.Name = string(bytes.TrimSpace(segments[entrySegmentIndexName]))
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
for i, ent := range payload {
if len(ent) == 0 {
return nil, ErrUnexpectedNewline
}
if a, err := check.NewAbs(string(segments[entrySegmentIndexPath])); err != nil {
return err
} else {
e.Path = a
}
e.Name = string(bytes.TrimSpace(segments[entrySegmentIndexName]))
default:
return EntryUnexpectedSegmentsError(data)
}
segment := strings.SplitN(ent, " ", 5)
return e.decodeLocationSegment(segments[iL])
}
// location index
var iL int
// 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 + " ")
} else {
buf.WriteString(nameInvalid + " ")
}
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)
}
// 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
}
// 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
}
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
switch len(segment) {
case 2: // /lib/ld-musl-x86_64.so.1 (0x7f04d14ef000)
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 = 3
if segment[1] != "=>" {
return nil, ErrUnexpectedSeparator
}
// trailing newline is allowed (glibc)
return false
if !path.IsAbs(segment[2]) {
return nil, ErrPathNotAbsolute
}
result[i] = &Entry{
Name: strings.TrimSpace(segment[0]),
Path: segment[2],
}
default:
return nil, EntryUnexpectedSegmentsError(ent)
}
// leading newline is allowed (musl)
d.notFirst = true
return d.Scan(v)
if loc, err := parseLocation(segment[iL]); err != nil {
return nil, err
} else {
result[i].Location = loc
}
}
d.notFirst = true
d.err = v.UnmarshalText(data)
return d.err == nil
return result, 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
func parseLocation(s string) (uint64, error) {
if len(s) < 4 || s[len(s)-1] != ')' || s[:3] != "(0x" {
return math.MaxUint64, ErrBadLocationFormat
}
return d.s.Err()
return strconv.ParseUint(s[3:len(s)-1], 16, 64)
}
// 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

@ -24,7 +24,6 @@
# 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" ]
@ -99,9 +98,6 @@ buildGoModule rec {
nativeBuildInputs = [
pkg-config
makeBinaryWrapper
# for container example
nettools
];
postInstall =

View File

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

View File

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

View File

@ -1,25 +0,0 @@
// Package acl exposes the internal/system/acl package.
//
// Deprecated: This package will be removed in 0.4.
package acl
import (
_ "unsafe" // for go:linkname
"hakurei.app/internal/system/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/system/acl.Update
func Update(name string, uid int, perms ...Perm) error
type Perms = acl.Perms

View File

@ -3,7 +3,7 @@ package acl_test
import (
"testing"
"hakurei.app/internal/system/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/system/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/system/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/system/dbus"
"hakurei.app/system/dbus"
)
func TestParse(t *testing.T) {

View File

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

View File

@ -11,9 +11,9 @@ import (
"testing"
"time"
"hakurei.app/internal/helper"
"hakurei.app/internal/system/dbus"
"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/system/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/system/dbus"
"hakurei.app/message"
)
type AddrEntry = dbus.AddrEntry
// EqualAddrEntries returns whether two slices of [AddrEntry] are equal.
//
//go:linkname EqualAddrEntries hakurei.app/internal/system/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/system/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/system/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/system/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/system/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/system/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/system/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/system/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"
)

View File

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

View File

@ -1,50 +0,0 @@
// Package system exposes the internal/system package.
//
// Deprecated: This package will be removed in 0.4.
package system
import (
"context"
_ "unsafe" // for go:linkname
"hakurei.app/hst"
"hakurei.app/internal/system"
"hakurei.app/message"
)
// ErrDBusConfig is returned when a required hst.BusConfig argument is nil.
//
//go:linkname ErrDBusConfig hakurei.app/internal/system.ErrDBusConfig
var ErrDBusConfig error
// OpError is returned by [I.Commit] and [I.Revert].
type OpError = system.OpError
const (
// User type is reverted at final instance exit.
User = system.User
// Process type is unconditionally reverted on exit.
Process = system.Process
CM = system.CM
)
// Criteria specifies types of Op to revert.
type Criteria = system.Criteria
// Op is a reversible system operation.
type Op = system.Op
// TypeString extends [Enablement.String] to support [User] and [Process].
//
//go:linkname TypeString hakurei.app/internal/system.TypeString
func TypeString(e hst.Enablement) string
// New returns the address of a new [I] targeting uid.
//
//go:linkname New hakurei.app/internal/system.New
func New(ctx context.Context, msg message.Msg, uid int) (sys *I)
// An I provides deferred operating system interaction. [I] must not be copied.
// Methods of [I] must not be used concurrently.
type I = system.I

View File

@ -7,9 +7,9 @@ import (
"os"
"hakurei.app/hst"
"hakurei.app/internal/system/acl"
"hakurei.app/internal/system/dbus"
"hakurei.app/internal/system/xcb"
"hakurei.app/system/acl"
"hakurei.app/system/dbus"
"hakurei.app/system/internal/xcb"
)
type osFile interface {

View File

@ -10,9 +10,9 @@ import (
"hakurei.app/container/stub"
"hakurei.app/hst"
"hakurei.app/internal/system/acl"
"hakurei.app/internal/system/dbus"
"hakurei.app/internal/system/xcb"
"hakurei.app/system/acl"
"hakurei.app/system/dbus"
"hakurei.app/system/internal/xcb"
)
// call initialises a [stub.Call].

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