aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/app/hsu.go
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-09-25 13:46:21 +0900
committerOphestra <cat@gensokyo.uk>2025-09-25 13:51:54 +0900
commitae2df2c450dd655b93a950124b6ab16e2b4d967b (patch)
tree849ab40049228e17891351d97097f8c9d0e18c10 /internal/app/hsu.go
parent6e3f34f2ecfe0f3cd024a6e73bf6560080feac53 (diff)
internal: remove sys package
This package is replaced by container/stub. Remove and replace it with unexported implementation for the upcoming test suite rewrite. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/app/hsu.go')
-rw-r--r--internal/app/hsu.go96
1 files changed, 96 insertions, 0 deletions
diff --git a/internal/app/hsu.go b/internal/app/hsu.go
new file mode 100644
index 00000000..0078ef0d
--- /dev/null
+++ b/internal/app/hsu.go
@@ -0,0 +1,96 @@
+package app
+
+import (
+ "errors"
+ "fmt"
+ "log"
+ "os"
+ "os/exec"
+ "strconv"
+ "sync"
+
+ "hakurei.app/container"
+ "hakurei.app/hst"
+ "hakurei.app/internal/hlog"
+)
+
+// Hsu caches responses from cmd/hsu.
+type Hsu struct {
+ idOnce sync.Once
+ idErr error
+ id int
+
+ kOnce sync.Once
+ k syscallDispatcher
+}
+
+var ErrHsuAccess = errors.New("current user is not in the hsurc file")
+
+// ensureDispatcher ensures Hsu.k is not nil.
+func (h *Hsu) ensureDispatcher() {
+ h.kOnce.Do(func() {
+ if h.k == nil {
+ h.k = direct{}
+ }
+ })
+}
+
+// ID returns the current user hsurc identifier. ErrHsuAccess is returned if the current user is not in hsurc.
+func (h *Hsu) ID() (int, error) {
+ h.ensureDispatcher()
+ h.idOnce.Do(func() {
+ h.id = -1
+ hsuPath := h.k.mustHsuPath()
+
+ cmd := exec.Command(hsuPath)
+ cmd.Path = hsuPath
+ cmd.Stderr = os.Stderr // pass through fatal messages
+ cmd.Env = make([]string, 0)
+ cmd.Dir = container.FHSRoot
+ var (
+ p []byte
+ exitError *exec.ExitError
+ )
+
+ const step = "obtain uid from hsu"
+ if p, h.idErr = h.k.cmdOutput(cmd); h.idErr == nil {
+ h.id, h.idErr = strconv.Atoi(string(p))
+ if h.idErr != nil {
+ h.idErr = &hst.AppError{Step: step, Err: h.idErr, Msg: "invalid uid string from hsu"}
+ }
+ } else if errors.As(h.idErr, &exitError) && exitError != nil && exitError.ExitCode() == 1 {
+ // hsu prints an error message in this case
+ h.idErr = &hst.AppError{Step: step, Err: ErrHsuAccess}
+ } else if os.IsNotExist(h.idErr) {
+ h.idErr = &hst.AppError{Step: step, Err: os.ErrNotExist,
+ Msg: fmt.Sprintf("the setuid helper is missing: %s", hsuPath)}
+ }
+ })
+
+ return h.id, h.idErr
+}
+
+// MustID calls [Hsu.ID] and terminates on error.
+func (h *Hsu) MustID() int {
+ id, err := h.ID()
+ if err == nil {
+ return id
+ }
+
+ const fallback = "cannot retrieve user id from setuid wrapper:"
+ if errors.Is(err, ErrHsuAccess) {
+ hlog.Verbose("*"+fallback, err)
+ os.Exit(1)
+ return -0xdeadbeef
+ } else if m, ok := container.GetErrorMessage(err); ok {
+ log.Fatal(m)
+ return -0xdeadbeef
+ } else {
+ log.Fatalln(fallback, err)
+ return -0xdeadbeef
+ }
+}
+
+// HsuUid returns target uid for the stable hsu uid format.
+// No bounds check is performed, a value retrieved from hsu is expected.
+func HsuUid(id, identity int) int { return 1000000 + id*10000 + identity }