5 Commits

Author SHA1 Message Date
d42067df7c cmd/hakurei/json: friendly error messages
All checks were successful
Test / Create distribution (push) Successful in 25s
Test / Sandbox (push) Successful in 39s
Test / Sandbox (race detector) (push) Successful in 39s
Test / Hakurei (push) Successful in 44s
Test / Hakurei (race detector) (push) Successful in 43s
Test / Hpkg (push) Successful in 41s
Test / Flake checks (push) Successful in 1m23s
This change handles errors returned by encoding/json and prints significantly cleaner messages.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-10-21 05:17:25 +09:00
b9459a80c7 container/init: check use constants for open flags
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Sandbox (push) Successful in 2m11s
Test / Hakurei (push) Successful in 3m8s
Test / Sandbox (race detector) (push) Successful in 3m58s
Test / Hpkg (push) Successful in 4m6s
Test / Hakurei (race detector) (push) Successful in 4m45s
Test / Flake checks (push) Successful in 1m28s
These bits are arch-specific.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-10-21 03:13:58 +09:00
f8189d1488 container/syscall: dot-import syscall
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Sandbox (push) Successful in 2m12s
Test / Hakurei (push) Successful in 3m7s
Test / Hpkg (push) Successful in 3m57s
Test / Sandbox (race detector) (push) Successful in 4m2s
Test / Hakurei (race detector) (push) Successful in 4m44s
Test / Flake checks (push) Successful in 1m38s
This avoids having arch-specific constants for arm64.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-10-21 03:09:14 +09:00
5063b774c1 hst: expose version string
All checks were successful
Test / Create distribution (push) Successful in 36s
Test / Sandbox (push) Successful in 2m6s
Test / Hakurei (push) Successful in 3m0s
Test / Hpkg (push) Successful in 3m56s
Test / Sandbox (race detector) (push) Successful in 4m0s
Test / Hakurei (race detector) (push) Successful in 4m44s
Test / Flake checks (push) Successful in 1m20s
The hst API is tied to this version string.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-10-21 01:56:44 +09:00
766dd89ffa internal: clean up build strings
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Sandbox (push) Successful in 2m9s
Test / Hakurei (push) Successful in 3m5s
Test / Hpkg (push) Successful in 4m4s
Test / Sandbox (race detector) (push) Successful in 4m9s
Test / Hakurei (race detector) (push) Successful in 4m46s
Test / Flake checks (push) Successful in 1m30s
These names are less ambiguous and should be understandable without reading the source code.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-10-21 01:49:36 +09:00
18 changed files with 299 additions and 134 deletions

View File

@@ -2,7 +2,6 @@ package main
import (
"context"
"encoding/json"
"fmt"
"io"
"log"
@@ -227,8 +226,11 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
} else {
if f, err := os.Open(flagDBusConfigSession); err != nil {
log.Fatal(err.Error())
} else if err = json.NewDecoder(f).Decode(&config.SessionBus); err != nil {
log.Fatalf("cannot load session bus proxy config from %q: %s", flagDBusConfigSession, err)
} else {
decodeJSON(log.Fatal, "load session bus proxy config", f, &config.SessionBus)
if err = f.Close(); err != nil {
log.Fatal(err.Error())
}
}
}
@@ -236,8 +238,11 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
if flagDBusConfigSystem != "nil" {
if f, err := os.Open(flagDBusConfigSystem); err != nil {
log.Fatal(err.Error())
} else if err = json.NewDecoder(f).Decode(&config.SystemBus); err != nil {
log.Fatalf("cannot load system bus proxy config from %q: %s", flagDBusConfigSystem, err)
} else {
decodeJSON(log.Fatal, "load system bus proxy config", f, &config.SystemBus)
if err = f.Close(); err != nil {
log.Fatal(err.Error())
}
}
}
@@ -323,7 +328,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
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 { printJSON(os.Stdout, false, hst.Template()); return errSuccess })
c.Command("template", "Produce a config template", func(args []string) error { encodeJSON(log.Fatal, os.Stdout, false, hst.Template()); return errSuccess })
c.Command("help", "Show this help message", func([]string) error { c.PrintHelp(); return errSuccess })
return c

60
cmd/hakurei/json.go Normal file
View File

@@ -0,0 +1,60 @@
package main
import (
"encoding/json"
"errors"
"io"
"strconv"
)
// decodeJSON decodes json from r and stores it in v. A non-nil error results in a call to fatal.
func decodeJSON(fatal func(v ...any), op string, r io.Reader, v any) {
err := json.NewDecoder(r).Decode(v)
if err == nil {
return
}
var (
syntaxError *json.SyntaxError
unmarshalTypeError *json.UnmarshalTypeError
msg string
)
switch {
case errors.As(err, &syntaxError) && syntaxError != nil:
msg = syntaxError.Error() +
" at byte " + strconv.FormatInt(syntaxError.Offset, 10)
case errors.As(err, &unmarshalTypeError) && unmarshalTypeError != nil:
msg = "inappropriate " + unmarshalTypeError.Value +
" at byte " + strconv.FormatInt(unmarshalTypeError.Offset, 10)
default:
// InvalidUnmarshalError: incorrect usage, does not need to be handled
// io.ErrUnexpectedEOF: no additional error information available
msg = err.Error()
}
fatal("cannot " + op + ": " + msg)
}
// encodeJSON encodes v to output. A non-nil error results in a call to fatal.
func encodeJSON(fatal func(v ...any), output io.Writer, short bool, v any) {
encoder := json.NewEncoder(output)
if !short {
encoder.SetIndent("", " ")
}
if err := encoder.Encode(v); err != nil {
var marshalerError *json.MarshalerError
if errors.As(err, &marshalerError) && marshalerError != nil {
// this likely indicates an implementation error in hst
fatal("cannot encode json for " + marshalerError.Type.String() + ": " + marshalerError.Err.Error())
return
}
// UnsupportedTypeError, UnsupportedValueError: incorrect usage, does not need to be handled
fatal("cannot write json: " + err.Error())
}
}

107
cmd/hakurei/json_test.go Normal file
View File

@@ -0,0 +1,107 @@
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()
testCases := []struct {
name string
t reflect.Type
data string
want any
msg string
}{
{"success", reflect.TypeFor[uintptr](), "3735928559\n", uintptr(0xdeadbeef), ""},
{"syntax", reflect.TypeFor[*int](), "\x00", nil,
`cannot load sample: invalid character '\x00' looking for beginning of value at byte 1`},
{"type", reflect.TypeFor[uintptr](), "-1", nil,
`cannot load sample: inappropriate number -1 at byte 2`},
{"default", reflect.TypeFor[*int](), "{", nil,
"cannot load sample: unexpected EOF"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
var (
gotP = reflect.New(tc.t)
gotMsg *string
)
decodeJSON(func(v ...any) {
if gotMsg != nil {
t.Fatal("fatal called twice")
}
msg := v[0].(string)
gotMsg = &msg
}, "load sample", strings.NewReader(tc.data), gotP.Interface())
if tc.msg != "" {
if gotMsg == nil {
t.Errorf("decodeJSON: success, want fatal %q", tc.msg)
} else if *gotMsg != tc.msg {
t.Errorf("decodeJSON: fatal = %q, want %q", *gotMsg, tc.msg)
}
} else if gotMsg != nil {
t.Errorf("decodeJSON: fatal = %q", *gotMsg)
} else if !reflect.DeepEqual(gotP.Elem().Interface(), tc.want) {
t.Errorf("decodeJSON: %#v, want %#v", gotP.Elem().Interface(), tc.want)
}
})
}
}
//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()
testCases := []struct {
name string
v any
want string
}{
{"marshaler", errorJSONMarshaler{},
`cannot encode json for main_test.errorJSONMarshaler: unique error 3735928559 injected by the test suite`},
{"default", func() {},
`cannot write json: json: unsupported type: func()`},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
var called bool
encodeJSON(func(v ...any) {
if called {
t.Fatal("fatal called twice")
}
called = true
if v[0].(string) != tc.want {
t.Errorf("encodeJSON: fatal = %q, want %q", v[0].(string), tc.want)
}
}, nil, false, tc.v)
if !called {
t.Errorf("encodeJSON: success, want fatal %q", tc.want)
}
})
}
}
// errorJSONMarshaler implements json.Marshaler.
type errorJSONMarshaler struct{}
func (errorJSONMarshaler) MarshalJSON() ([]byte, error) { return nil, stub.UniqueError(0xdeadbeef) }

View File

@@ -1,7 +1,6 @@
package main
import (
"encoding/json"
"errors"
"io"
"log"
@@ -42,10 +41,7 @@ func tryPath(msg message.Msg, name string) (config *hst.Config) {
r = os.Stdin
}
if err := json.NewDecoder(r).Decode(&config); err != nil {
log.Fatalf("cannot load configuration: %v", err)
}
decodeJSON(log.Fatal, "load configuration", r, &config)
return
}

View File

@@ -1,7 +1,6 @@
package main
import (
"encoding/json"
"fmt"
"io"
"log"
@@ -12,23 +11,26 @@ import (
"time"
"hakurei.app/hst"
"hakurei.app/internal"
"hakurei.app/internal/app"
"hakurei.app/internal/app/state"
"hakurei.app/message"
)
// printShowSystem populates and writes a representation of [hst.Info] to output.
func printShowSystem(output io.Writer, short, flagJSON bool) {
t := newPrinter(output)
defer t.MustFlush()
info := &hst.Info{User: new(app.Hsu).MustID(nil)}
info := &hst.Info{Version: internal.Version(), User: new(app.Hsu).MustID(nil)}
app.CopyPaths().Copy(&info.Paths, info.User)
if flagJSON {
printJSON(output, short, info)
encodeJSON(log.Fatal, output, short, info)
return
}
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)
@@ -36,6 +38,7 @@ func printShowSystem(output io.Writer, short, flagJSON bool) {
t.Printf("RunDirPath:\t%s\n", info.RunDirPath)
}
// printShowInstance writes a representation of [state.State] or [hst.Config] to output.
func printShowInstance(
output io.Writer, now time.Time,
instance *state.State, config *hst.Config,
@@ -44,9 +47,9 @@ func printShowInstance(
if flagJSON {
if instance != nil {
printJSON(output, short, instance)
encodeJSON(log.Fatal, output, short, instance)
} else {
printJSON(output, short, config)
encodeJSON(log.Fatal, output, short, config)
}
return
}
@@ -168,6 +171,7 @@ func printShowInstance(
return
}
// printPs writes a representation of active instances to output.
func printPs(output io.Writer, now time.Time, s state.Store, short, flagJSON bool) {
var entries state.Entries
if e, err := state.Join(s); err != nil {
@@ -184,7 +188,7 @@ func printPs(output io.Writer, now time.Time, s state.Store, short, flagJSON boo
for id, instance := range entries {
es[id.String()] = instance
}
printJSON(output, short, es)
encodeJSON(log.Fatal, output, short, es)
return
}
@@ -213,7 +217,7 @@ func printPs(output io.Writer, now time.Time, s state.Store, short, flagJSON boo
for i, e := range exp {
v[i] = e.s
}
printJSON(output, short, v)
encodeJSON(log.Fatal, output, short, v)
} else {
for _, e := range exp {
mustPrintln(output, e.s[:8])
@@ -247,40 +251,39 @@ func printPs(output io.Writer, now time.Time, s state.Store, short, flagJSON boo
}
}
// expandedStateEntry stores [state.State] alongside a string representation of its [state.ID].
type expandedStateEntry struct {
s string
*state.State
}
func printJSON(output io.Writer, short bool, v any) {
encoder := json.NewEncoder(output)
if !short {
encoder.SetIndent("", " ")
}
if err := encoder.Encode(v); err != nil {
log.Fatalf("cannot serialise: %v", err)
}
}
// newPrinter returns a configured, wrapped [tabwriter.Writer].
func newPrinter(output io.Writer) *tp { return &tp{tabwriter.NewWriter(output, 0, 1, 4, ' ', 0)} }
// tp wraps [tabwriter.Writer] to provide additional formatting methods.
type tp struct{ *tabwriter.Writer }
// Printf calls [fmt.Fprintf] on the underlying [tabwriter.Writer].
func (p *tp) Printf(format string, a ...any) {
if _, err := fmt.Fprintf(p, format, a...); err != nil {
log.Fatalf("cannot write to tabwriter: %v", err)
}
}
// Println calls [fmt.Fprintln] on the underlying [tabwriter.Writer].
func (p *tp) Println(a ...any) {
if _, err := fmt.Fprintln(p, a...); err != nil {
log.Fatalf("cannot write to tabwriter: %v", err)
}
}
// MustFlush calls the Flush method of [tabwriter.Writer] and calls [log.Fatalf] on a non-nil error.
func (p *tp) MustFlush() {
if err := p.Writer.Flush(); err != nil {
log.Fatalf("cannot flush tabwriter: %v", err)
}
}
func mustPrint(output io.Writer, a ...any) {
if _, err := fmt.Fprint(output, a...); err != nil {
log.Fatalf("cannot print: %v", err)

View File

@@ -14,7 +14,7 @@ import (
"hakurei.app/message"
)
var hakureiPath = internal.MustHakureiPath()
var hakureiPathVal = internal.MustHakureiPath().String()
func mustRunApp(ctx context.Context, msg message.Msg, config *hst.Config, beforeFail func()) {
var (
@@ -27,9 +27,9 @@ func mustRunApp(ctx context.Context, msg message.Msg, config *hst.Config, before
log.Fatalf("cannot pipe: %v", err)
} else {
if msg.IsVerbose() {
cmd = exec.CommandContext(ctx, hakureiPath.String(), "-v", "app", "3")
cmd = exec.CommandContext(ctx, hakureiPathVal, "-v", "app", "3")
} else {
cmd = exec.CommandContext(ctx, hakureiPath.String(), "app", "3")
cmd = exec.CommandContext(ctx, hakureiPathVal, "app", "3")
}
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
cmd.ExtraFiles = []*os.File{r}

View File

@@ -26,6 +26,11 @@ const (
identityMax = 9999
)
// hakureiPath is the absolute path to Hakurei.
//
// This is set by the linker.
var hakureiPath string
func main() {
log.SetFlags(0)
log.SetPrefix("hsu: ")
@@ -43,13 +48,18 @@ func main() {
log.Fatal("this program must not be started by root")
}
if !path.IsAbs(hakureiPath) {
log.Fatal("this program is compiled incorrectly")
return
}
var toolPath string
pexe := path.Join("/proc", strconv.Itoa(os.Getppid()), "exe")
if p, err := os.Readlink(pexe); err != nil {
log.Fatalf("cannot read parent executable path: %v", err)
} else if strings.HasSuffix(p, " (deleted)") {
log.Fatal("hakurei executable has been deleted")
} else if p != mustCheckPath(hmain) {
} else if p != hakureiPath {
log.Fatal("this program must be started by hakurei")
} else {
toolPath = p

View File

@@ -19,5 +19,5 @@ buildGoModule {
ldflags = lib.attrsets.foldlAttrs (
ldflags: name: value:
ldflags ++ [ "-X main.${name}=${value}" ]
) [ "-s -w" ] { hmain = "${hakurei}/libexec/hakurei"; };
) [ "-s -w" ] { hakureiPath = "${hakurei}/libexec/hakurei"; };
}

View File

@@ -1,20 +0,0 @@
package main
import (
"log"
"path"
)
const compPoison = "INVALIDINVALIDINVALIDINVALIDINVALID"
var (
hmain = compPoison
)
func mustCheckPath(p string) string {
if p != compPoison && p != "" && path.IsAbs(p) {
return p
}
log.Fatal("this program is compiled incorrectly")
return compPoison
}

View File

@@ -1029,8 +1029,8 @@ func TestInitEntrypoint(t *testing.T) {
/* end apply */
call("mount", stub.ExpectArgs{"host", "host", "", uintptr(0x4c000), ""}, nil, nil),
call("unmount", stub.ExpectArgs{"host", 2}, nil, nil),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, stub.UniqueError(37)),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, stub.UniqueError(37)),
call("fatalf", stub.ExpectArgs{"cannot open intermediate root: %v", []any{stub.UniqueError(37)}}, nil, nil),
},
}, nil},
@@ -1088,8 +1088,8 @@ func TestInitEntrypoint(t *testing.T) {
/* end apply */
call("mount", stub.ExpectArgs{"host", "host", "", uintptr(0x4c000), ""}, nil, nil),
call("unmount", stub.ExpectArgs{"host", 2}, nil, nil),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, nil),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, nil),
call("chdir", stub.ExpectArgs{"/sysroot"}, nil, stub.UniqueError(35)),
call("fatalf", stub.ExpectArgs{"cannot enter sysroot: %v", []any{stub.UniqueError(35)}}, nil, nil),
},
@@ -1148,8 +1148,8 @@ func TestInitEntrypoint(t *testing.T) {
/* end apply */
call("mount", stub.ExpectArgs{"host", "host", "", uintptr(0x4c000), ""}, nil, nil),
call("unmount", stub.ExpectArgs{"host", 2}, nil, nil),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, nil),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, nil),
call("chdir", stub.ExpectArgs{"/sysroot"}, nil, nil),
call("pivotRoot", stub.ExpectArgs{".", "."}, nil, stub.UniqueError(33)),
call("fatalf", stub.ExpectArgs{"cannot pivot into sysroot: %v", []any{stub.UniqueError(33)}}, nil, nil),
@@ -1209,8 +1209,8 @@ func TestInitEntrypoint(t *testing.T) {
/* end apply */
call("mount", stub.ExpectArgs{"host", "host", "", uintptr(0x4c000), ""}, nil, nil),
call("unmount", stub.ExpectArgs{"host", 2}, nil, nil),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, nil),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, nil),
call("chdir", stub.ExpectArgs{"/sysroot"}, nil, nil),
call("pivotRoot", stub.ExpectArgs{".", "."}, nil, nil),
call("fchdir", stub.ExpectArgs{1 << 35}, nil, stub.UniqueError(31)),
@@ -1271,8 +1271,8 @@ func TestInitEntrypoint(t *testing.T) {
/* end apply */
call("mount", stub.ExpectArgs{"host", "host", "", uintptr(0x4c000), ""}, nil, nil),
call("unmount", stub.ExpectArgs{"host", 2}, nil, nil),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, nil),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, nil),
call("chdir", stub.ExpectArgs{"/sysroot"}, nil, nil),
call("pivotRoot", stub.ExpectArgs{".", "."}, nil, nil),
call("fchdir", stub.ExpectArgs{1 << 35}, nil, nil),
@@ -1334,8 +1334,8 @@ func TestInitEntrypoint(t *testing.T) {
/* end apply */
call("mount", stub.ExpectArgs{"host", "host", "", uintptr(0x4c000), ""}, nil, nil),
call("unmount", stub.ExpectArgs{"host", 2}, nil, nil),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, nil),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, nil),
call("chdir", stub.ExpectArgs{"/sysroot"}, nil, nil),
call("pivotRoot", stub.ExpectArgs{".", "."}, nil, nil),
call("fchdir", stub.ExpectArgs{1 << 35}, nil, nil),
@@ -1398,8 +1398,8 @@ func TestInitEntrypoint(t *testing.T) {
/* end apply */
call("mount", stub.ExpectArgs{"host", "host", "", uintptr(0x4c000), ""}, nil, nil),
call("unmount", stub.ExpectArgs{"host", 2}, nil, nil),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, nil),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, nil),
call("chdir", stub.ExpectArgs{"/sysroot"}, nil, nil),
call("pivotRoot", stub.ExpectArgs{".", "."}, nil, nil),
call("fchdir", stub.ExpectArgs{1 << 35}, nil, nil),
@@ -1463,8 +1463,8 @@ func TestInitEntrypoint(t *testing.T) {
/* end apply */
call("mount", stub.ExpectArgs{"host", "host", "", uintptr(0x4c000), ""}, nil, nil),
call("unmount", stub.ExpectArgs{"host", 2}, nil, nil),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, nil),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, nil),
call("chdir", stub.ExpectArgs{"/sysroot"}, nil, nil),
call("pivotRoot", stub.ExpectArgs{".", "."}, nil, nil),
call("fchdir", stub.ExpectArgs{1 << 35}, nil, nil),
@@ -1529,8 +1529,8 @@ func TestInitEntrypoint(t *testing.T) {
/* end apply */
call("mount", stub.ExpectArgs{"host", "host", "", uintptr(0x4c000), ""}, nil, nil),
call("unmount", stub.ExpectArgs{"host", 2}, nil, nil),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, nil),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, nil),
call("chdir", stub.ExpectArgs{"/sysroot"}, nil, nil),
call("pivotRoot", stub.ExpectArgs{".", "."}, nil, nil),
call("fchdir", stub.ExpectArgs{1 << 35}, nil, nil),
@@ -1603,8 +1603,8 @@ func TestInitEntrypoint(t *testing.T) {
/* end apply */
call("mount", stub.ExpectArgs{"host", "host", "", uintptr(0x4c000), ""}, nil, nil),
call("unmount", stub.ExpectArgs{"host", 2}, nil, nil),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, nil),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, nil),
call("chdir", stub.ExpectArgs{"/sysroot"}, nil, nil),
call("pivotRoot", stub.ExpectArgs{".", "."}, nil, nil),
call("fchdir", stub.ExpectArgs{1 << 35}, nil, nil),
@@ -1710,8 +1710,8 @@ func TestInitEntrypoint(t *testing.T) {
/* end apply */
call("mount", stub.ExpectArgs{"host", "host", "", uintptr(0x4c000), ""}, nil, nil),
call("unmount", stub.ExpectArgs{"host", 2}, nil, nil),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, nil),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, nil),
call("chdir", stub.ExpectArgs{"/sysroot"}, nil, nil),
call("pivotRoot", stub.ExpectArgs{".", "."}, nil, nil),
call("fchdir", stub.ExpectArgs{1 << 35}, nil, nil),
@@ -1818,8 +1818,8 @@ func TestInitEntrypoint(t *testing.T) {
/* end apply */
call("mount", stub.ExpectArgs{"host", "host", "", uintptr(0x4c000), ""}, nil, nil),
call("unmount", stub.ExpectArgs{"host", 2}, nil, nil),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, nil),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, nil),
call("chdir", stub.ExpectArgs{"/sysroot"}, nil, nil),
call("pivotRoot", stub.ExpectArgs{".", "."}, nil, nil),
call("fchdir", stub.ExpectArgs{1 << 35}, nil, nil),
@@ -1927,8 +1927,8 @@ func TestInitEntrypoint(t *testing.T) {
/* end apply */
call("mount", stub.ExpectArgs{"host", "host", "", uintptr(0x4c000), ""}, nil, nil),
call("unmount", stub.ExpectArgs{"host", 2}, nil, nil),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, nil),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, nil),
call("chdir", stub.ExpectArgs{"/sysroot"}, nil, nil),
call("pivotRoot", stub.ExpectArgs{".", "."}, nil, nil),
call("fchdir", stub.ExpectArgs{1 << 35}, nil, nil),
@@ -2041,8 +2041,8 @@ func TestInitEntrypoint(t *testing.T) {
/* end apply */
call("mount", stub.ExpectArgs{"host", "host", "", uintptr(0x4c000), ""}, nil, nil),
call("unmount", stub.ExpectArgs{"host", 2}, nil, nil),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, nil),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, nil),
call("chdir", stub.ExpectArgs{"/sysroot"}, nil, nil),
call("pivotRoot", stub.ExpectArgs{".", "."}, nil, nil),
call("fchdir", stub.ExpectArgs{1 << 35}, nil, nil),
@@ -2143,8 +2143,8 @@ func TestInitEntrypoint(t *testing.T) {
/* end apply */
call("mount", stub.ExpectArgs{"host", "host", "", uintptr(0x4c000), ""}, nil, nil),
call("unmount", stub.ExpectArgs{"host", 2}, nil, nil),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, nil),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, nil),
call("chdir", stub.ExpectArgs{"/sysroot"}, nil, nil),
call("pivotRoot", stub.ExpectArgs{".", "."}, nil, nil),
call("fchdir", stub.ExpectArgs{1 << 35}, nil, nil),
@@ -2236,8 +2236,8 @@ func TestInitEntrypoint(t *testing.T) {
/* end apply */
call("mount", stub.ExpectArgs{"host", "host", "", uintptr(0x4c000), ""}, nil, nil),
call("unmount", stub.ExpectArgs{"host", 2}, nil, nil),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, nil),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, nil),
call("chdir", stub.ExpectArgs{"/sysroot"}, nil, nil),
call("pivotRoot", stub.ExpectArgs{".", "."}, nil, nil),
call("fchdir", stub.ExpectArgs{1 << 35}, nil, nil),
@@ -2331,8 +2331,8 @@ func TestInitEntrypoint(t *testing.T) {
/* end apply */
call("mount", stub.ExpectArgs{"host", "host", "", uintptr(0x4c000), ""}, nil, nil),
call("unmount", stub.ExpectArgs{"host", 2}, nil, nil),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, nil),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, nil),
call("chdir", stub.ExpectArgs{"/sysroot"}, nil, nil),
call("pivotRoot", stub.ExpectArgs{".", "."}, nil, nil),
call("fchdir", stub.ExpectArgs{1 << 35}, nil, nil),
@@ -2433,8 +2433,8 @@ func TestInitEntrypoint(t *testing.T) {
/* end apply */
call("mount", stub.ExpectArgs{"host", "host", "", uintptr(0x4c000), ""}, nil, nil),
call("unmount", stub.ExpectArgs{"host", 2}, nil, nil),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, nil),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, nil),
call("chdir", stub.ExpectArgs{"/sysroot"}, nil, nil),
call("pivotRoot", stub.ExpectArgs{".", "."}, nil, nil),
call("fchdir", stub.ExpectArgs{1 << 35}, nil, nil),
@@ -2572,8 +2572,8 @@ func TestInitEntrypoint(t *testing.T) {
/* end apply */
call("mount", stub.ExpectArgs{"host", "host", "", uintptr(0x4c000), ""}, nil, nil),
call("unmount", stub.ExpectArgs{"host", 2}, nil, nil),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", 0x10000, uint32(0)}, 1<<35, nil),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, syscall.EINTR),
call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, nil),
call("chdir", stub.ExpectArgs{"/sysroot"}, nil, nil),
call("pivotRoot", stub.ExpectArgs{".", "."}, nil, nil),
call("fchdir", stub.ExpectArgs{1 << 35}, nil, nil),

View File

@@ -1,13 +1,13 @@
package container
import (
"syscall"
. "syscall"
"unsafe"
)
// SetPtracer allows processes to ptrace(2) the calling process.
func SetPtracer(pid uintptr) error {
_, _, errno := syscall.Syscall(syscall.SYS_PRCTL, syscall.PR_SET_PTRACER, pid, 0)
_, _, errno := Syscall(SYS_PRCTL, PR_SET_PTRACER, pid, 0)
if errno == 0 {
return nil
}
@@ -22,7 +22,7 @@ const (
// SetDumpable sets the "dumpable" attribute of the calling process.
func SetDumpable(dumpable uintptr) error {
// linux/sched/coredump.h
if _, _, errno := syscall.Syscall(syscall.SYS_PRCTL, syscall.PR_SET_DUMPABLE, dumpable, 0); errno != 0 {
if _, _, errno := Syscall(SYS_PRCTL, PR_SET_DUMPABLE, dumpable, 0); errno != 0 {
return errno
}
@@ -31,7 +31,7 @@ func SetDumpable(dumpable uintptr) error {
// SetNoNewPrivs sets the calling thread's no_new_privs attribute.
func SetNoNewPrivs() error {
_, _, errno := syscall.Syscall(syscall.SYS_PRCTL, PR_SET_NO_NEW_PRIVS, 1, 0)
_, _, errno := Syscall(SYS_PRCTL, PR_SET_NO_NEW_PRIVS, 1, 0)
if errno == 0 {
return nil
}
@@ -41,10 +41,10 @@ func SetNoNewPrivs() error {
// Isatty tests whether a file descriptor refers to a terminal.
func Isatty(fd int) bool {
var buf [8]byte
r, _, _ := syscall.Syscall(
syscall.SYS_IOCTL,
r, _, _ := Syscall(
SYS_IOCTL,
uintptr(fd),
syscall.TIOCGWINSZ,
TIOCGWINSZ,
uintptr(unsafe.Pointer(&buf[0])),
)
return r == 0
@@ -60,7 +60,7 @@ func Isatty(fd int) bool {
func IgnoringEINTR(fn func() error) error {
for {
err := fn()
if err != syscall.EINTR {
if err != EINTR {
return err
}
}

8
dist/release.sh vendored
View File

@@ -10,10 +10,10 @@ cp -rv "dist/comp" "${out}"
go generate ./...
go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w -buildid= -extldflags '-static'
-X hakurei.app/internal.version=${VERSION}
-X hakurei.app/internal.hmain=/usr/bin/hakurei
-X hakurei.app/internal.hsu=/usr/bin/hsu
-X main.hmain=/usr/bin/hakurei" ./...
-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}"
rm -rf "./${out}"

View File

@@ -52,7 +52,10 @@ type Paths struct {
RunDirPath *check.Absolute `json:"run_dir_path"`
}
// Info holds basic system information collected from the implementation.
type Info struct {
// Version is a hardcoded version string.
Version string `json:"version"`
// User is the userid according to hsu.
User int `json:"user"`

View File

@@ -1,17 +0,0 @@
package internal
const compPoison = "INVALIDINVALIDINVALIDINVALIDINVALID"
var (
version = compPoison
)
// checkComp validates string value set at compile time.
func checkComp(s string) (string, bool) { return s, s != compPoison && s != "" }
func Version() string {
if v, ok := checkComp(version); ok {
return v
}
return "impure"
}

View File

@@ -6,20 +6,20 @@ import (
"hakurei.app/container/check"
)
var (
hmain = compPoison
hsu = compPoison
)
// Absolute paths to the Hakurei installation.
//
// These are set by the linker.
var hakureiPath, hsuPath string
// MustHakureiPath returns the absolute path to hakurei, configured at compile time.
func MustHakureiPath() *check.Absolute { return mustCheckPath(log.Fatal, "hakurei", hmain) }
// MustHakureiPath returns the [check.Absolute] path to hakurei.
func MustHakureiPath() *check.Absolute { return mustCheckPath(log.Fatal, "hakurei", hakureiPath) }
// MustHsuPath returns the absolute path to hakurei, configured at compile time.
func MustHsuPath() *check.Absolute { return mustCheckPath(log.Fatal, "hsu", hsu) }
// MustHsuPath returns the [check.Absolute] to hsu.
func MustHsuPath() *check.Absolute { return mustCheckPath(log.Fatal, "hsu", hsuPath) }
// mustCheckPath checks a pathname against compPoison, then [container.NewAbs], calling fatal if either step fails.
// mustCheckPath checks a pathname to not be zero, then [check.NewAbs], calling fatal if either step fails.
func mustCheckPath(fatal func(v ...any), name, pathname string) *check.Absolute {
if pathname != compPoison && pathname != "" {
if pathname != "" {
if a, err := check.NewAbs(pathname); err != nil {
fatal(err.Error())
return nil // unreachable

View File

@@ -15,7 +15,6 @@ func TestMustCheckPath(t *testing.T) {
pathname string
wantFatal string
}{
{"poison", compPoison, "invalid test path, this program is compiled incorrectly"},
{"zero", "", "invalid test path, this program is compiled incorrectly"},
{"not absolute", "\x00", `path "\x00" is not absolute`},
{"success", "/proc/nonexistent", ""},

19
internal/version.go Normal file
View File

@@ -0,0 +1,19 @@
package internal
// FallbackVersion is returned when a version string was not set by the linker.
const FallbackVersion = "dirty"
// buildVersion is the Hakurei tree's version string at build time.
//
// This is set by the linker.
var buildVersion string
// Version returns the Hakurei tree's version string.
// It is either the value of the constant [FallbackVersion] or,
// when possible, a release tag like "v1.0.0".
func Version() string {
if buildVersion != "" {
return buildVersion
}
return FallbackVersion
}

View File

@@ -75,9 +75,9 @@ buildGoModule rec {
]
)
{
version = "v${version}";
hmain = "${placeholder "out"}/libexec/hakurei";
hsu = "/run/wrappers/bin/hsu";
buildVersion = "v${version}";
hakureiPath = "${placeholder "out"}/libexec/hakurei";
hsuPath = "/run/wrappers/bin/hsu";
};
# nix build environment does not allow acls