From e33294db9c334530582215b2c5dd23c0730bc162 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Sat, 28 Mar 2026 17:26:45 +0900 Subject: [PATCH] cmd/hakurei: document stable behaviour These are undocumented anywhere else and is required by tools invoking hakurei. Signed-off-by: Ophestra --- cmd/hakurei/main.go | 39 ++++++++++++++++++++++++++++++++++++++- cmd/hakurei/parse.go | 27 +++++++++++++++++++-------- 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/cmd/hakurei/main.go b/cmd/hakurei/main.go index a5b14d9c..19ffdb68 100644 --- a/cmd/hakurei/main.go +++ b/cmd/hakurei/main.go @@ -1,3 +1,40 @@ +// Hakurei runs user-specified containers as subordinate users. +// +// This program is generally invoked by another, higher level program, which +// creates container configuration via package [hst] or an implementation of it. +// +// The parent may leave files open and specify their file descriptor for various +// uses. In these cases, standard streams and netpoll files are treated as +// invalid file descriptors and rejected. All string representations must be in +// decimal. +// +// When specifying a [hst.Config] JSON stream or file to the run subcommand, the +// argument "-" is equivalent to stdin. Otherwise, file descriptor rules +// described above applies. Invalid file descriptors are treated as file names +// in their string representation, with the exception that if a netpoll file +// descriptor is attempted, the program fails. +// +// The flag --identifier-fd can be optionally specified to the run subcommand to +// receive the identifier of the newly started instance. File descriptor rules +// described above applies, and the file must be writable. This is sent after +// its state is made available, so the client must not attempt to poll for it. +// This uses the internal binary format of [hst.ID]. +// +// For the show and ps subcommands, the flag --json can be applied to the main +// hakurei command to serialise output in JSON when applicable. Additionally, +// the flag --short targeting each subcommand is used to omit some information +// in both JSON and user-facing output. Only JSON-encoded output is covered +// under the compatibility promise. +// +// A template for [hst.Config] demonstrating all available configuration fields +// is returned by [hst.Template]. The JSON-encoded equivalent of this can be +// obtained via the template subcommand. Fields left unpopulated in the template +// (the direct_* family of fields, which are insecure under any configuration if +// enabled) are unsupported. +// +// For simple (but insecure) testing scenarios, the exec subcommand can be used +// to generate a simple, permissive configuration in-memory. See its help +// message for all available options. package main import ( @@ -25,8 +62,8 @@ func main() { // early init path, skips root check and duplicate PR_SET_DUMPABLE container.TryArgv0(nil) - log.SetPrefix("hakurei: ") log.SetFlags(0) + log.SetPrefix("hakurei: ") msg := message.New(log.Default()) early := earlyHardeningErrs{ diff --git a/cmd/hakurei/parse.go b/cmd/hakurei/parse.go index 266eb47f..31d0dfa6 100644 --- a/cmd/hakurei/parse.go +++ b/cmd/hakurei/parse.go @@ -17,8 +17,9 @@ import ( ) // tryPath attempts to read [hst.Config] from multiple sources. -// tryPath reads from [os.Stdin] if name has value "-". -// Otherwise, name is passed to tryFd, and if that returns nil, name is passed to [os.Open]. +// +// tryPath reads from [os.Stdin] if name has value "-". Otherwise, name is +// passed to tryFd, and if that returns nil, name is passed to [os.Open]. func tryPath(msg message.Msg, name string) (config *hst.Config) { var r io.ReadCloser config = new(hst.Config) @@ -46,7 +47,8 @@ func tryPath(msg message.Msg, name string) (config *hst.Config) { return } -// tryFd returns a [io.ReadCloser] if name represents an integer corresponding to a valid file descriptor. +// tryFd returns a [io.ReadCloser] if name represents an integer corresponding +// to a valid file descriptor. func tryFd(msg message.Msg, name string) io.ReadCloser { if v, err := strconv.Atoi(name); err != nil { if !errors.Is(err, strconv.ErrSyntax) { @@ -60,7 +62,12 @@ func tryFd(msg message.Msg, name string) io.ReadCloser { msg.Verbosef("trying config stream from %d", v) fd := uintptr(v) - if _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_GETFD, 0); errno != 0 { + if _, _, errno := syscall.Syscall( + syscall.SYS_FCNTL, + fd, + syscall.F_GETFD, + 0, + ); errno != 0 { if errors.Is(errno, syscall.EBADF) { // reject bad fd return nil } @@ -75,10 +82,12 @@ func tryFd(msg message.Msg, name string) io.ReadCloser { } } -// shortLengthMin is the minimum length a short form identifier can have and still be interpreted as an identifier. +// shortLengthMin is the minimum length a short form identifier can have and +// still be interpreted as an identifier. const shortLengthMin = 1 << 3 -// shortIdentifier returns an eight character short representation of [hst.ID] from its random bytes. +// shortIdentifier returns an eight character short representation of [hst.ID] +// from its random bytes. func shortIdentifier(id *hst.ID) string { return shortIdentifierString(id.String()) } @@ -88,7 +97,8 @@ func shortIdentifierString(s string) string { return s[len(hst.ID{}) : len(hst.ID{})+shortLengthMin] } -// tryIdentifier attempts to match [hst.State] from a [hex] representation of [hst.ID] or a prefix of its lower half. +// tryIdentifier attempts to match [hst.State] from a [hex] representation of +// [hst.ID] or a prefix of its lower half. func tryIdentifier(msg message.Msg, name string, s *store.Store) *hst.State { const ( likeShort = 1 << iota @@ -96,7 +106,8 @@ func tryIdentifier(msg message.Msg, name string, s *store.Store) *hst.State { ) var likely uintptr - if len(name) >= shortLengthMin && len(name) <= len(hst.ID{}) { // half the hex representation + // half the hex representation + if len(name) >= shortLengthMin && len(name) <= len(hst.ID{}) { // cannot safely decode here due to unknown alignment for _, c := range name { if c >= '0' && c <= '9' {