diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-09-25 13:46:21 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-09-25 13:51:54 +0900 |
| commit | ae2df2c450dd655b93a950124b6ab16e2b4d967b (patch) | |
| tree | 849ab40049228e17891351d97097f8c9d0e18c10 /internal/app/hsu.go | |
| parent | 6e3f34f2ecfe0f3cd024a6e73bf6560080feac53 (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.go | 96 |
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 } |
