internal/app: do not return from shim start
All checks were successful
Test / Create distribution (push) Successful in 49s
Test / Sandbox (push) Successful in 2m37s
Test / Hakurei (push) Successful in 3m32s
Test / Hpkg (push) Successful in 4m21s
Test / Hakurei (race detector) (push) Successful in 5m37s
Test / Sandbox (race detector) (push) Successful in 2m7s
Test / Flake checks (push) Successful in 1m20s
All checks were successful
Test / Create distribution (push) Successful in 49s
Test / Sandbox (push) Successful in 2m37s
Test / Hakurei (push) Successful in 3m32s
Test / Hpkg (push) Successful in 4m21s
Test / Hakurei (race detector) (push) Successful in 5m37s
Test / Sandbox (race detector) (push) Successful in 2m7s
Test / Flake checks (push) Successful in 1m20s
The whole RunState ugliness and the other horrendous error handling conditions for internal/app come from an old design proposal for maintaining all app containers under the same daemon process for a user. The proposal was ultimately rejected but the implementation remained. It is removed here to alleviate internal/app from much of its ugliness and unreadability. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
parent
f09133a224
commit
b99c63337d
@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
@ -52,29 +51,29 @@ func buildCommand(out io.Writer) command.Command {
|
|||||||
|
|
||||||
{
|
{
|
||||||
var (
|
var (
|
||||||
dbusConfigSession string
|
flagDBusConfigSession string
|
||||||
dbusConfigSystem string
|
flagDBusConfigSystem string
|
||||||
mpris bool
|
flagDBusMpris bool
|
||||||
dbusVerbose bool
|
flagDBusVerbose bool
|
||||||
|
|
||||||
fid string
|
flagID string
|
||||||
aid int
|
flagIdentity int
|
||||||
groups command.RepeatableFlag
|
flagGroups command.RepeatableFlag
|
||||||
homeDir string
|
flagHomeDir string
|
||||||
userName string
|
flagUserName string
|
||||||
|
|
||||||
wayland, x11, dBus, pulse bool
|
flagWayland, flagX11, flagDBus, flagPulse bool
|
||||||
)
|
)
|
||||||
|
|
||||||
c.NewCommand("run", "Configure and start a permissive default sandbox", func(args []string) error {
|
c.NewCommand("run", "Configure and start a permissive default sandbox", func(args []string) error {
|
||||||
// initialise config from flags
|
// initialise config from flags
|
||||||
config := &hst.Config{
|
config := &hst.Config{
|
||||||
ID: fid,
|
ID: flagID,
|
||||||
Args: args,
|
Args: args,
|
||||||
}
|
}
|
||||||
|
|
||||||
if aid < 0 || aid > 9999 {
|
if flagIdentity < 0 || flagIdentity > 9999 {
|
||||||
log.Fatalf("aid %d out of range", aid)
|
log.Fatalf("identity %d out of range", flagIdentity)
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolve home/username from os when flag is unset
|
// resolve home/username from os when flag is unset
|
||||||
@ -82,13 +81,7 @@ func buildCommand(out io.Writer) command.Command {
|
|||||||
passwd *user.User
|
passwd *user.User
|
||||||
passwdOnce sync.Once
|
passwdOnce sync.Once
|
||||||
passwdFunc = func() {
|
passwdFunc = func() {
|
||||||
var us string
|
us := strconv.Itoa(sys.MustUid(std, flagIdentity))
|
||||||
if uid, err := std.Uid(aid); err != nil {
|
|
||||||
fatal("cannot obtain uid from setuid wrapper:", err)
|
|
||||||
} else {
|
|
||||||
us = strconv.Itoa(uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
if u, err := user.LookupId(us); err != nil {
|
if u, err := user.LookupId(us); err != nil {
|
||||||
hlog.Verbosef("cannot look up uid %s", us)
|
hlog.Verbosef("cannot look up uid %s", us)
|
||||||
passwd = &user.User{
|
passwd = &user.User{
|
||||||
@ -104,21 +97,21 @@ func buildCommand(out io.Writer) command.Command {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if homeDir == "os" {
|
if flagHomeDir == "os" {
|
||||||
passwdOnce.Do(passwdFunc)
|
passwdOnce.Do(passwdFunc)
|
||||||
homeDir = passwd.HomeDir
|
flagHomeDir = passwd.HomeDir
|
||||||
}
|
}
|
||||||
|
|
||||||
if userName == "chronos" {
|
if flagUserName == "chronos" {
|
||||||
passwdOnce.Do(passwdFunc)
|
passwdOnce.Do(passwdFunc)
|
||||||
userName = passwd.Username
|
flagUserName = passwd.Username
|
||||||
}
|
}
|
||||||
|
|
||||||
config.Identity = aid
|
config.Identity = flagIdentity
|
||||||
config.Groups = groups
|
config.Groups = flagGroups
|
||||||
config.Username = userName
|
config.Username = flagUserName
|
||||||
|
|
||||||
if a, err := container.NewAbs(homeDir); err != nil {
|
if a, err := container.NewAbs(flagHomeDir); err != nil {
|
||||||
log.Fatal(err.Error())
|
log.Fatal(err.Error())
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
@ -126,43 +119,43 @@ func buildCommand(out io.Writer) command.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var e system.Enablement
|
var e system.Enablement
|
||||||
if wayland {
|
if flagWayland {
|
||||||
e |= system.EWayland
|
e |= system.EWayland
|
||||||
}
|
}
|
||||||
if x11 {
|
if flagX11 {
|
||||||
e |= system.EX11
|
e |= system.EX11
|
||||||
}
|
}
|
||||||
if dBus {
|
if flagDBus {
|
||||||
e |= system.EDBus
|
e |= system.EDBus
|
||||||
}
|
}
|
||||||
if pulse {
|
if flagPulse {
|
||||||
e |= system.EPulse
|
e |= system.EPulse
|
||||||
}
|
}
|
||||||
config.Enablements = hst.NewEnablements(e)
|
config.Enablements = hst.NewEnablements(e)
|
||||||
|
|
||||||
// parse D-Bus config file from flags if applicable
|
// parse D-Bus config file from flags if applicable
|
||||||
if dBus {
|
if flagDBus {
|
||||||
if dbusConfigSession == "builtin" {
|
if flagDBusConfigSession == "builtin" {
|
||||||
config.SessionBus = dbus.NewConfig(fid, true, mpris)
|
config.SessionBus = dbus.NewConfig(flagID, true, flagDBusMpris)
|
||||||
} else {
|
} else {
|
||||||
if conf, err := dbus.NewConfigFromFile(dbusConfigSession); err != nil {
|
if conf, err := dbus.NewConfigFromFile(flagDBusConfigSession); err != nil {
|
||||||
log.Fatalf("cannot load session bus proxy config from %q: %s", dbusConfigSession, err)
|
log.Fatalf("cannot load session bus proxy config from %q: %s", flagDBusConfigSession, err)
|
||||||
} else {
|
} else {
|
||||||
config.SessionBus = conf
|
config.SessionBus = conf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// system bus proxy is optional
|
// system bus proxy is optional
|
||||||
if dbusConfigSystem != "nil" {
|
if flagDBusConfigSystem != "nil" {
|
||||||
if conf, err := dbus.NewConfigFromFile(dbusConfigSystem); err != nil {
|
if conf, err := dbus.NewConfigFromFile(flagDBusConfigSystem); err != nil {
|
||||||
log.Fatalf("cannot load system bus proxy config from %q: %s", dbusConfigSystem, err)
|
log.Fatalf("cannot load system bus proxy config from %q: %s", flagDBusConfigSystem, err)
|
||||||
} else {
|
} else {
|
||||||
config.SystemBus = conf
|
config.SystemBus = conf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// override log from configuration
|
// override log from configuration
|
||||||
if dbusVerbose {
|
if flagDBusVerbose {
|
||||||
if config.SessionBus != nil {
|
if config.SessionBus != nil {
|
||||||
config.SessionBus.Log = true
|
config.SessionBus.Log = true
|
||||||
}
|
}
|
||||||
@ -176,59 +169,63 @@ func buildCommand(out io.Writer) command.Command {
|
|||||||
runApp(config)
|
runApp(config)
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}).
|
}).
|
||||||
Flag(&dbusConfigSession, "dbus-config", command.StringFlag("builtin"),
|
Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"),
|
||||||
"Path to session bus proxy config file, or \"builtin\" for defaults").
|
"Path to session bus proxy config file, or \"builtin\" for defaults").
|
||||||
Flag(&dbusConfigSystem, "dbus-system", command.StringFlag("nil"),
|
Flag(&flagDBusConfigSystem, "dbus-system", command.StringFlag("nil"),
|
||||||
"Path to system bus proxy config file, or \"nil\" to disable").
|
"Path to system bus proxy config file, or \"nil\" to disable").
|
||||||
Flag(&mpris, "mpris", command.BoolFlag(false),
|
Flag(&flagDBusMpris, "mpris", command.BoolFlag(false),
|
||||||
"Allow owning MPRIS D-Bus path, has no effect if custom config is available").
|
"Allow owning MPRIS D-Bus path, has no effect if custom config is available").
|
||||||
Flag(&dbusVerbose, "dbus-log", command.BoolFlag(false),
|
Flag(&flagDBusVerbose, "dbus-log", command.BoolFlag(false),
|
||||||
"Force buffered logging in the D-Bus proxy").
|
"Force buffered logging in the D-Bus proxy").
|
||||||
Flag(&fid, "id", command.StringFlag(""),
|
Flag(&flagID, "id", command.StringFlag(""),
|
||||||
"Reverse-DNS style Application identifier, leave empty to inherit instance identifier").
|
"Reverse-DNS style Application identifier, leave empty to inherit instance identifier").
|
||||||
Flag(&aid, "a", command.IntFlag(0),
|
Flag(&flagIdentity, "a", command.IntFlag(0),
|
||||||
"Application identity").
|
"Application identity").
|
||||||
Flag(nil, "g", &groups,
|
Flag(nil, "g", &flagGroups,
|
||||||
"Groups inherited by all container processes").
|
"Groups inherited by all container processes").
|
||||||
Flag(&homeDir, "d", command.StringFlag("os"),
|
Flag(&flagHomeDir, "d", command.StringFlag("os"),
|
||||||
"Container home directory").
|
"Container home directory").
|
||||||
Flag(&userName, "u", command.StringFlag("chronos"),
|
Flag(&flagUserName, "u", command.StringFlag("chronos"),
|
||||||
"Passwd user name within sandbox").
|
"Passwd user name within sandbox").
|
||||||
Flag(&wayland, "wayland", command.BoolFlag(false),
|
Flag(&flagWayland, "wayland", command.BoolFlag(false),
|
||||||
"Enable connection to Wayland via security-context-v1").
|
"Enable connection to Wayland via security-context-v1").
|
||||||
Flag(&x11, "X", command.BoolFlag(false),
|
Flag(&flagX11, "X", command.BoolFlag(false),
|
||||||
"Enable direct connection to X11").
|
"Enable direct connection to X11").
|
||||||
Flag(&dBus, "dbus", command.BoolFlag(false),
|
Flag(&flagDBus, "dbus", command.BoolFlag(false),
|
||||||
"Enable proxied connection to D-Bus").
|
"Enable proxied connection to D-Bus").
|
||||||
Flag(&pulse, "pulse", command.BoolFlag(false),
|
Flag(&flagPulse, "pulse", command.BoolFlag(false),
|
||||||
"Enable direct connection to PulseAudio")
|
"Enable direct connection to PulseAudio")
|
||||||
}
|
}
|
||||||
|
|
||||||
var showFlagShort bool
|
{
|
||||||
c.NewCommand("show", "Show live or local app configuration", func(args []string) error {
|
var flagShort bool
|
||||||
switch len(args) {
|
c.NewCommand("show", "Show live or local app configuration", func(args []string) error {
|
||||||
case 0: // system
|
switch len(args) {
|
||||||
printShowSystem(os.Stdout, showFlagShort, flagJSON)
|
case 0: // system
|
||||||
|
printShowSystem(os.Stdout, flagShort, flagJSON)
|
||||||
|
|
||||||
case 1: // instance
|
case 1: // instance
|
||||||
name := args[0]
|
name := args[0]
|
||||||
config, entry := tryShort(name)
|
config, entry := tryShort(name)
|
||||||
if config == nil {
|
if config == nil {
|
||||||
config = tryPath(name)
|
config = tryPath(name)
|
||||||
|
}
|
||||||
|
printShowInstance(os.Stdout, time.Now().UTC(), entry, config, flagShort, flagJSON)
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Fatal("show requires 1 argument")
|
||||||
}
|
}
|
||||||
printShowInstance(os.Stdout, time.Now().UTC(), entry, config, showFlagShort, flagJSON)
|
return errSuccess
|
||||||
|
}).Flag(&flagShort, "short", command.BoolFlag(false), "Omit filesystem information")
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
{
|
||||||
log.Fatal("show requires 1 argument")
|
var flagShort bool
|
||||||
}
|
c.NewCommand("ps", "List active instances", func(args []string) error {
|
||||||
return errSuccess
|
printPs(os.Stdout, time.Now().UTC(), state.NewMulti(std.Paths().RunDirPath.String()), flagShort, flagJSON)
|
||||||
}).Flag(&showFlagShort, "short", command.BoolFlag(false), "Omit filesystem information")
|
return errSuccess
|
||||||
|
}).Flag(&flagShort, "short", command.BoolFlag(false), "Print instance id")
|
||||||
var psFlagShort bool
|
}
|
||||||
c.NewCommand("ps", "List active instances", func(args []string) error {
|
|
||||||
printPs(os.Stdout, time.Now().UTC(), state.NewMulti(std.Paths().RunDirPath.String()), psFlagShort, flagJSON)
|
|
||||||
return errSuccess
|
|
||||||
}).Flag(&psFlagShort, "short", command.BoolFlag(false), "Print instance id")
|
|
||||||
|
|
||||||
c.Command("version", "Display version information", func(args []string) error {
|
c.Command("version", "Display version information", func(args []string) error {
|
||||||
fmt.Println(internal.Version())
|
fmt.Println(internal.Version())
|
||||||
@ -259,34 +256,15 @@ func runApp(config *hst.Config) {
|
|||||||
defer stop() // unreachable
|
defer stop() // unreachable
|
||||||
a := app.MustNew(ctx, std)
|
a := app.MustNew(ctx, std)
|
||||||
|
|
||||||
rs := new(app.RunState)
|
|
||||||
if sa, err := a.Seal(config); err != nil {
|
if sa, err := a.Seal(config); err != nil {
|
||||||
hlog.BeforeExit()
|
hlog.BeforeExit()
|
||||||
fatal("cannot seal app:", err)
|
if m, ok := container.GetErrorMessage(err); ok {
|
||||||
|
log.Fatal(m)
|
||||||
|
} else {
|
||||||
|
log.Fatalln("cannot seal app:", err)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
hlog.BeforeExit()
|
sa.Main()
|
||||||
os.Exit(app.PrintRunStateErr(rs, sa.Run(rs)))
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
|
|
||||||
*(*int)(nil) = 0 // not reached
|
|
||||||
}
|
|
||||||
|
|
||||||
// fatal prints the error message according to [container.GetErrorMessage], or fallback
|
|
||||||
// prepended to err if an error message is not available, followed by a call to [os.Exit](1).
|
|
||||||
func fatal(fallback string, err error) {
|
|
||||||
// this indicates the error message has already reached stderr, outside the current process's control;
|
|
||||||
// this is only reached when hsu fails for any reason, as a second error message following hsu is confusing
|
|
||||||
if errors.Is(err, sys.ErrHsuAccess) {
|
|
||||||
hlog.Verbose("*"+fallback, err)
|
|
||||||
os.Exit(1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
m, ok := container.GetErrorMessage(err)
|
|
||||||
if !ok {
|
|
||||||
log.Fatalln(fallback, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Fatal(m)
|
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/app/state"
|
"hakurei.app/internal/app/state"
|
||||||
|
"hakurei.app/internal/sys"
|
||||||
"hakurei.app/system/dbus"
|
"hakurei.app/system/dbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,13 +21,9 @@ func printShowSystem(output io.Writer, short, flagJSON bool) {
|
|||||||
t := newPrinter(output)
|
t := newPrinter(output)
|
||||||
defer t.MustFlush()
|
defer t.MustFlush()
|
||||||
|
|
||||||
info := &hst.Info{Paths: std.Paths()}
|
info := &hst.Info{
|
||||||
|
Paths: std.Paths(),
|
||||||
// get hid by querying uid of identity 0
|
User: sys.MustGetUserID(std),
|
||||||
if uid, err := std.Uid(0); err != nil {
|
|
||||||
fatal("cannot obtain uid from setuid wrapper:", err)
|
|
||||||
} else {
|
|
||||||
info.User = (uid / 10000) - 100
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if flagJSON {
|
if flagJSON {
|
||||||
|
@ -1,184 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"hakurei.app/container"
|
|
||||||
"hakurei.app/hst"
|
|
||||||
"hakurei.app/internal/hlog"
|
|
||||||
"hakurei.app/internal/sys"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PrintRunStateErr prints an error message via [log] if runErr is not nil, and returns an appropriate exit code.
|
|
||||||
//
|
|
||||||
// TODO(ophestra): remove this function once RunState has been replaced
|
|
||||||
func PrintRunStateErr(rs *RunState, runErr error) (code int) {
|
|
||||||
code = rs.ExitStatus()
|
|
||||||
|
|
||||||
if runErr != nil {
|
|
||||||
if rs.Time == nil {
|
|
||||||
// no process has been created
|
|
||||||
printMessageError("cannot start app:", runErr)
|
|
||||||
} else {
|
|
||||||
if m, ok := container.GetErrorMessage(runErr); !ok {
|
|
||||||
// catch-all for unexpected errors
|
|
||||||
log.Println("run returned error:", runErr)
|
|
||||||
} else {
|
|
||||||
var se *StateStoreError
|
|
||||||
if !errors.As(runErr, &se) {
|
|
||||||
// this could only be returned from a shim setup failure path
|
|
||||||
log.Print(m)
|
|
||||||
} else {
|
|
||||||
// InnerErr is returned by c.Save(&sd, seal.ct), and are always unwrapped
|
|
||||||
printMessageError("error returned during revert:",
|
|
||||||
&hst.AppError{Step: "save process state", Err: se.InnerErr})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if code == 0 {
|
|
||||||
code = 126
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if rs.RevertErr != nil {
|
|
||||||
var stateStoreError *StateStoreError
|
|
||||||
if !errors.As(rs.RevertErr, &stateStoreError) || stateStoreError == nil {
|
|
||||||
printMessageError("cannot clean up:", rs.RevertErr)
|
|
||||||
goto out
|
|
||||||
}
|
|
||||||
|
|
||||||
if stateStoreError.Errs != nil {
|
|
||||||
if len(stateStoreError.Errs) == 2 { // storeErr.save(revertErr, store.Close())
|
|
||||||
if stateStoreError.Errs[0] != nil { // revertErr is MessageError joined by errors.Join
|
|
||||||
var joinedErrors interface {
|
|
||||||
Unwrap() []error
|
|
||||||
error
|
|
||||||
}
|
|
||||||
if !errors.As(stateStoreError.Errs[0], &joinedErrors) {
|
|
||||||
printMessageError("cannot revert:", stateStoreError.Errs[0])
|
|
||||||
} else {
|
|
||||||
for _, err := range joinedErrors.Unwrap() {
|
|
||||||
if err != nil {
|
|
||||||
printMessageError("cannot revert:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if stateStoreError.Errs[1] != nil { // store.Close() is joined by errors.Join
|
|
||||||
log.Printf("cannot close store: %v", stateStoreError.Errs[1])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Printf("fault during cleanup: %v", errors.Join(stateStoreError.Errs...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if stateStoreError.OpErr != nil {
|
|
||||||
log.Printf("blind revert due to store fault: %v", stateStoreError.OpErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if stateStoreError.DoErr != nil {
|
|
||||||
printMessageError("state store operation unsuccessful:", stateStoreError.DoErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if stateStoreError.Inner && stateStoreError.InnerErr != nil {
|
|
||||||
printMessageError("cannot destroy state entry:", stateStoreError.InnerErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
out:
|
|
||||||
if code == 0 {
|
|
||||||
code = 128
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if rs.WaitErr != nil {
|
|
||||||
hlog.Verbosef("wait: %v", rs.WaitErr)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(ophestra): this duplicates code in cmd/hakurei/command.go, keep this up to date until removal
|
|
||||||
func printMessageError(fallback string, err error) {
|
|
||||||
// this indicates the error message has already reached stderr, outside the current process's control;
|
|
||||||
// this is only reached when hsu fails for any reason, as a second error message following hsu is confusing
|
|
||||||
if errors.Is(err, sys.ErrHsuAccess) {
|
|
||||||
hlog.Verbose("*"+fallback, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
m, ok := container.GetErrorMessage(err)
|
|
||||||
if !ok {
|
|
||||||
log.Println(fallback, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Print(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StateStoreError is returned for a failed state save.
|
|
||||||
type StateStoreError struct {
|
|
||||||
// whether inner function was called
|
|
||||||
Inner bool
|
|
||||||
// returned by the Save/Destroy method of [state.Cursor]
|
|
||||||
InnerErr error
|
|
||||||
// returned by the Do method of [state.Store]
|
|
||||||
DoErr error
|
|
||||||
// stores an arbitrary store operation error
|
|
||||||
OpErr error
|
|
||||||
// stores arbitrary errors
|
|
||||||
Errs []error
|
|
||||||
}
|
|
||||||
|
|
||||||
// save saves arbitrary errors in [StateStoreError.Errs] once.
|
|
||||||
func (e *StateStoreError) save(errs ...error) {
|
|
||||||
if len(errs) == 0 || e.Errs != nil {
|
|
||||||
panic("invalid call to save")
|
|
||||||
}
|
|
||||||
e.Errs = errs
|
|
||||||
}
|
|
||||||
|
|
||||||
// equiv returns an error that [StateStoreError] is equivalent to, including nil.
|
|
||||||
func (e *StateStoreError) equiv(step string) error {
|
|
||||||
if e.Inner && e.InnerErr == nil && e.DoErr == nil && e.OpErr == nil && errors.Join(e.Errs...) == nil {
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return &hst.AppError{Step: step, Err: e}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *StateStoreError) Error() string {
|
|
||||||
if e.Inner && e.InnerErr != nil {
|
|
||||||
return e.InnerErr.Error()
|
|
||||||
}
|
|
||||||
if e.DoErr != nil {
|
|
||||||
return e.DoErr.Error()
|
|
||||||
}
|
|
||||||
if e.OpErr != nil {
|
|
||||||
return e.OpErr.Error()
|
|
||||||
}
|
|
||||||
if err := errors.Join(e.Errs...); err != nil {
|
|
||||||
return err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
// equiv nullifies e for values where this is reached
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *StateStoreError) Unwrap() (errs []error) {
|
|
||||||
errs = make([]error, 0, 3+len(e.Errs))
|
|
||||||
if e.InnerErr != nil {
|
|
||||||
errs = append(errs, e.InnerErr)
|
|
||||||
}
|
|
||||||
if e.DoErr != nil {
|
|
||||||
errs = append(errs, e.DoErr)
|
|
||||||
}
|
|
||||||
if e.OpErr != nil {
|
|
||||||
errs = append(errs, e.OpErr)
|
|
||||||
}
|
|
||||||
for _, err := range e.Errs {
|
|
||||||
if err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
@ -13,7 +13,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/hst"
|
|
||||||
"hakurei.app/internal"
|
"hakurei.app/internal"
|
||||||
"hakurei.app/internal/app/state"
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/internal/hlog"
|
"hakurei.app/internal/hlog"
|
||||||
@ -23,85 +22,166 @@ import (
|
|||||||
// duration to wait for shim to exit, after container WaitDelay has elapsed.
|
// duration to wait for shim to exit, after container WaitDelay has elapsed.
|
||||||
const shimWaitTimeout = 5 * time.Second
|
const shimWaitTimeout = 5 * time.Second
|
||||||
|
|
||||||
// ErrShimTimeout is returned when shim did not exit within shimWaitTimeout, after its WaitDelay has elapsed.
|
// mainState holds persistent state bound to [Outcome.Main].
|
||||||
// This is different from the container failing to terminate within its timeout period, as that is enforced
|
type mainState struct {
|
||||||
// by the shim. This error is instead returned when there is a lockup in shim preventing it from completing.
|
// done is whether beforeExit has been called already.
|
||||||
var ErrShimTimeout = errors.New("shim did not exit")
|
done bool
|
||||||
|
|
||||||
// RunState stores the outcome of a call to [Outcome.Run].
|
|
||||||
type RunState struct {
|
|
||||||
// Time is the exact point in time where the process was created.
|
// Time is the exact point in time where the process was created.
|
||||||
// Location must be set to UTC.
|
// Location must be set to UTC.
|
||||||
//
|
//
|
||||||
// Time is nil if no process was ever created.
|
// Time is nil if no process was ever created.
|
||||||
Time *time.Time
|
Time *time.Time
|
||||||
// RevertErr is stored by the deferred revert call.
|
|
||||||
RevertErr error
|
|
||||||
// WaitErr is the generic error value created by the standard library.
|
|
||||||
WaitErr error
|
|
||||||
|
|
||||||
syscall.WaitStatus
|
seal *Outcome
|
||||||
|
store state.Store
|
||||||
|
cancel context.CancelFunc
|
||||||
|
cmd *exec.Cmd
|
||||||
|
cmdWait chan error
|
||||||
|
|
||||||
|
uintptr
|
||||||
}
|
}
|
||||||
|
|
||||||
// setStart stores the current time in [RunState] once.
|
const (
|
||||||
func (rs *RunState) setStart() {
|
// mainNeedsRevert indicates the call to Commit has succeeded.
|
||||||
if rs.Time != nil {
|
mainNeedsRevert uintptr = 1 << iota
|
||||||
panic("attempted to store time twice")
|
// mainNeedsDestroy indicates the instance state entry is present in the store.
|
||||||
}
|
mainNeedsDestroy
|
||||||
now := time.Now().UTC()
|
)
|
||||||
rs.Time = &now
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run commits deferred system setup and starts the container.
|
// beforeExit must be called immediately before a call to [os.Exit].
|
||||||
func (seal *Outcome) Run(rs *RunState) error {
|
func (ms mainState) beforeExit(isFault bool) {
|
||||||
if !seal.f.CompareAndSwap(false, true) {
|
if ms.done {
|
||||||
// Run does much more than just starting a process; calling it twice, even if the first call fails, will result
|
panic("attempting to call beforeExit twice")
|
||||||
// in inconsistent state that is impossible to clean up; return here to limit damage and hopefully give the
|
}
|
||||||
// other Run a chance to return
|
ms.done = true
|
||||||
return errors.New("outcome: attempted to run twice")
|
defer hlog.BeforeExit()
|
||||||
|
|
||||||
|
if isFault && ms.cancel != nil {
|
||||||
|
ms.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
if rs == nil {
|
var hasErr bool
|
||||||
panic("invalid state")
|
// updates hasErr but does not terminate
|
||||||
|
perror := func(err error, message string) {
|
||||||
|
hasErr = true
|
||||||
|
printMessageError("cannot "+message+":", err)
|
||||||
}
|
}
|
||||||
|
exitCode := 1
|
||||||
// read comp value early to allow for early failure
|
|
||||||
hsuPath := internal.MustHsuPath()
|
|
||||||
|
|
||||||
if err := seal.sys.Commit(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store := state.NewMulti(seal.runDirPath.String())
|
|
||||||
deferredStoreFunc := func(c state.Cursor) error { return nil } // noop until state in store
|
|
||||||
defer func() {
|
defer func() {
|
||||||
var revertErr error
|
if hasErr {
|
||||||
storeErr := new(StateStoreError)
|
os.Exit(exitCode)
|
||||||
storeErr.Inner, storeErr.DoErr = store.Do(seal.user.identity.unwrap(), func(c state.Cursor) {
|
}
|
||||||
revertErr = func() error {
|
}()
|
||||||
storeErr.InnerErr = deferredStoreFunc(c)
|
|
||||||
|
|
||||||
var rt system.Enablement
|
// this also handles wait for a non-fault termination
|
||||||
ec := system.Process
|
if ms.cmd != nil && ms.cmdWait != nil {
|
||||||
if states, err := c.Load(); err != nil {
|
waitDone := make(chan struct{})
|
||||||
// revert per-process state here to limit damage
|
// TODO(ophestra): enforce this limit early so it does not have to be done twice
|
||||||
storeErr.OpErr = err
|
shimTimeoutCompensated := shimWaitTimeout
|
||||||
return seal.sys.Revert((*system.Criteria)(&ec))
|
if ms.seal.waitDelay > MaxShimWaitDelay {
|
||||||
} else {
|
shimTimeoutCompensated += MaxShimWaitDelay
|
||||||
if l := len(states); l == 0 {
|
} else {
|
||||||
ec |= system.User
|
shimTimeoutCompensated += ms.seal.waitDelay
|
||||||
} else {
|
}
|
||||||
hlog.Verbosef("found %d instances, cleaning up without user-scoped operations", l)
|
// this ties waitDone to ctx with the additional compensated timeout duration
|
||||||
|
go func() { <-ms.seal.ctx.Done(); time.Sleep(shimTimeoutCompensated); close(waitDone) }()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-ms.cmdWait:
|
||||||
|
wstatus, ok := ms.cmd.ProcessState.Sys().(syscall.WaitStatus)
|
||||||
|
if ok {
|
||||||
|
if v := wstatus.ExitStatus(); v != 0 {
|
||||||
|
hasErr = true
|
||||||
|
exitCode = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hlog.Load() {
|
||||||
|
if !ok {
|
||||||
|
if err != nil {
|
||||||
|
hlog.Verbosef("wait: %v", err)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
switch {
|
||||||
|
case wstatus.Exited():
|
||||||
|
hlog.Verbosef("process %d exited with code %d", ms.cmd.Process.Pid, wstatus.ExitStatus())
|
||||||
|
|
||||||
// accumulate enablements of remaining launchers
|
case wstatus.CoreDump():
|
||||||
for i, s := range states {
|
hlog.Verbosef("process %d dumped core", ms.cmd.Process.Pid)
|
||||||
if s.Config != nil {
|
|
||||||
rt |= s.Config.Enablements.Unwrap()
|
case wstatus.Signaled():
|
||||||
} else {
|
hlog.Verbosef("process %d got %s", ms.cmd.Process.Pid, wstatus.Signal())
|
||||||
log.Printf("state entry %d does not contain config", i)
|
|
||||||
|
default:
|
||||||
|
hlog.Verbosef("process %d exited with status %#x", ms.cmd.Process.Pid, wstatus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-waitDone:
|
||||||
|
hlog.Resume()
|
||||||
|
// this is only reachable when shim did not exit within shimWaitTimeout, after its WaitDelay has elapsed.
|
||||||
|
// This is different from the container failing to terminate within its timeout period, as that is enforced
|
||||||
|
// by the shim. This path is instead reached when there is a lockup in shim preventing it from completing.
|
||||||
|
log.Printf("process %d did not terminate", ms.cmd.Process.Pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
hlog.Resume()
|
||||||
|
if ms.seal.sync != nil {
|
||||||
|
if err := ms.seal.sync.Close(); err != nil {
|
||||||
|
perror(err, "close wayland security context")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ms.seal.dbusMsg != nil {
|
||||||
|
ms.seal.dbusMsg()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ms.uintptr&mainNeedsRevert != 0 {
|
||||||
|
if ok, err := ms.store.Do(ms.seal.user.identity.unwrap(), func(c state.Cursor) {
|
||||||
|
if ms.uintptr&mainNeedsDestroy != 0 {
|
||||||
|
if err := c.Destroy(ms.seal.id.unwrap()); err != nil {
|
||||||
|
perror(err, "destroy state entry")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var rt system.Enablement
|
||||||
|
if states, err := c.Load(); err != nil {
|
||||||
|
// it is impossible to continue from this point;
|
||||||
|
// revert per-process state here to limit damage
|
||||||
|
ec := system.Process
|
||||||
|
if revertErr := ms.seal.sys.Revert((*system.Criteria)(&ec)); revertErr != nil {
|
||||||
|
var joinError interface {
|
||||||
|
Unwrap() []error
|
||||||
|
error
|
||||||
|
}
|
||||||
|
if !errors.As(revertErr, &joinError) || joinError == nil {
|
||||||
|
perror(revertErr, "revert system setup")
|
||||||
|
} else {
|
||||||
|
for _, v := range joinError.Unwrap() {
|
||||||
|
perror(v, "revert system setup step")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
perror(err, "load instance states")
|
||||||
|
} else {
|
||||||
|
ec := system.Process
|
||||||
|
if l := len(states); l == 0 {
|
||||||
|
ec |= system.User
|
||||||
|
} else {
|
||||||
|
hlog.Verbosef("found %d instances, cleaning up without user-scoped operations", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// accumulate enablements of remaining launchers
|
||||||
|
for i, s := range states {
|
||||||
|
if s.Config != nil {
|
||||||
|
rt |= s.Config.Enablements.Unwrap()
|
||||||
|
} else {
|
||||||
|
log.Printf("state entry %d does not contain config", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ec |= rt ^ (system.EWayland | system.EX11 | system.EDBus | system.EPulse)
|
ec |= rt ^ (system.EWayland | system.EX11 | system.EDBus | system.EPulse)
|
||||||
if hlog.Load() {
|
if hlog.Load() {
|
||||||
if ec > 0 {
|
if ec > 0 {
|
||||||
@ -109,27 +189,70 @@ func (seal *Outcome) Run(rs *RunState) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return seal.sys.Revert((*system.Criteria)(&ec))
|
if err = ms.seal.sys.Revert((*system.Criteria)(&ec)); err != nil {
|
||||||
}()
|
perror(err, "revert system setup")
|
||||||
})
|
}
|
||||||
storeErr.save(revertErr, store.Close())
|
}
|
||||||
rs.RevertErr = storeErr.equiv("clean up")
|
}); err != nil {
|
||||||
}()
|
if ok {
|
||||||
|
perror(err, "unlock state store")
|
||||||
|
} else {
|
||||||
|
perror(err, "open state store")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ms.uintptr&mainNeedsDestroy != 0 {
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ms.store != nil {
|
||||||
|
if err := ms.store.Close(); err != nil {
|
||||||
|
perror(err, "close state store")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fatal calls printMessageError, performs necessary cleanup, followed by a call to [os.Exit](1).
|
||||||
|
func (ms mainState) fatal(fallback string, ferr error) {
|
||||||
|
printMessageError(fallback, ferr)
|
||||||
|
ms.beforeExit(true)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main commits deferred system setup, runs the container, reverts changes to the system, and terminates the program.
|
||||||
|
// Main does not return.
|
||||||
|
func (seal *Outcome) Main() {
|
||||||
|
if !seal.f.CompareAndSwap(false, true) {
|
||||||
|
panic("outcome: attempted to run twice")
|
||||||
|
}
|
||||||
|
|
||||||
|
// read comp value early for early failure
|
||||||
|
hsuPath := internal.MustHsuPath()
|
||||||
|
|
||||||
|
// ms.beforeExit required beyond this point
|
||||||
|
ms := &mainState{seal: seal}
|
||||||
|
|
||||||
|
if err := seal.sys.Commit(); err != nil {
|
||||||
|
ms.fatal("cannot commit system setup:", err)
|
||||||
|
}
|
||||||
|
ms.uintptr |= mainNeedsRevert
|
||||||
|
ms.store = state.NewMulti(seal.runDirPath.String())
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(seal.ctx)
|
ctx, cancel := context.WithCancel(seal.ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
cmd := exec.CommandContext(ctx, hsuPath)
|
ms.cancel = cancel
|
||||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
|
||||||
cmd.Dir = container.FHSRoot // container init enters final working directory
|
ms.cmd = exec.CommandContext(ctx, hsuPath)
|
||||||
|
ms.cmd.Stdin, ms.cmd.Stdout, ms.cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||||
|
ms.cmd.Dir = container.FHSRoot // container init enters final working directory
|
||||||
// shim runs in the same session as monitor; see shim.go for behaviour
|
// shim runs in the same session as monitor; see shim.go for behaviour
|
||||||
cmd.Cancel = func() error { return cmd.Process.Signal(syscall.SIGCONT) }
|
ms.cmd.Cancel = func() error { return ms.cmd.Process.Signal(syscall.SIGCONT) }
|
||||||
|
|
||||||
var e *gob.Encoder
|
var e *gob.Encoder
|
||||||
if fd, encoder, err := container.Setup(&cmd.ExtraFiles); err != nil {
|
if fd, encoder, err := container.Setup(&ms.cmd.ExtraFiles); err != nil {
|
||||||
return &hst.AppError{Step: "create shim setup pipe", Err: err}
|
ms.fatal("cannot create shim setup pipe:", err)
|
||||||
} else {
|
} else {
|
||||||
e = encoder
|
e = encoder
|
||||||
cmd.Env = []string{
|
ms.cmd.Env = []string{
|
||||||
// passed through to shim by hsu
|
// passed through to shim by hsu
|
||||||
shimEnv + "=" + strconv.Itoa(fd),
|
shimEnv + "=" + strconv.Itoa(fd),
|
||||||
// interpreted by hsu
|
// interpreted by hsu
|
||||||
@ -140,102 +263,78 @@ func (seal *Outcome) Run(rs *RunState) error {
|
|||||||
if len(seal.user.supp) > 0 {
|
if len(seal.user.supp) > 0 {
|
||||||
hlog.Verbosef("attaching supplementary group ids %s", seal.user.supp)
|
hlog.Verbosef("attaching supplementary group ids %s", seal.user.supp)
|
||||||
// interpreted by hsu
|
// interpreted by hsu
|
||||||
cmd.Env = append(cmd.Env, "HAKUREI_GROUPS="+strings.Join(seal.user.supp, " "))
|
ms.cmd.Env = append(ms.cmd.Env, "HAKUREI_GROUPS="+strings.Join(seal.user.supp, " "))
|
||||||
}
|
}
|
||||||
|
|
||||||
hlog.Verbosef("setuid helper at %s", hsuPath)
|
hlog.Verbosef("setuid helper at %s", hsuPath)
|
||||||
hlog.Suspend()
|
hlog.Suspend()
|
||||||
if err := cmd.Start(); err != nil {
|
if err := ms.cmd.Start(); err != nil {
|
||||||
return &hst.AppError{Step: "start setuid wrapper", Err: err}
|
ms.fatal("cannot start setuid wrapper:", err)
|
||||||
}
|
}
|
||||||
rs.setStart()
|
startTime := time.Now().UTC()
|
||||||
|
ms.cmdWait = make(chan error, 1)
|
||||||
// this prevents blocking forever on an early failure
|
// this ties context back to the life of the process
|
||||||
waitErr, setupErr := make(chan error, 1), make(chan error, 1)
|
go func() { ms.cmdWait <- ms.cmd.Wait(); cancel() }()
|
||||||
go func() { waitErr <- cmd.Wait(); cancel() }()
|
ms.Time = &startTime
|
||||||
go func() {
|
|
||||||
setupErr <- e.Encode(&shimParams{
|
|
||||||
os.Getpid(),
|
|
||||||
seal.waitDelay,
|
|
||||||
seal.container,
|
|
||||||
hlog.Load(),
|
|
||||||
})
|
|
||||||
}()
|
|
||||||
|
|
||||||
|
// unfortunately the I/O here cannot be directly canceled;
|
||||||
|
// the cancellation path leads to fatal in this case so that is fine
|
||||||
select {
|
select {
|
||||||
case err := <-setupErr:
|
case err := <-func() (setupErr chan error) {
|
||||||
|
setupErr = make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
setupErr <- e.Encode(&shimParams{
|
||||||
|
os.Getpid(),
|
||||||
|
seal.waitDelay,
|
||||||
|
seal.container,
|
||||||
|
hlog.Load(),
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
return
|
||||||
|
}():
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hlog.Resume()
|
hlog.Resume()
|
||||||
return &hst.AppError{Step: "transmit shim config", Err: err}
|
ms.fatal("cannot transmit shim config:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
hlog.Resume()
|
hlog.Resume()
|
||||||
return newWithMessageError("shim setup canceled", syscall.ECANCELED)
|
ms.fatal("shim context canceled:", newWithMessageError("shim setup canceled", ctx.Err()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// returned after blocking on waitErr
|
// shim accepted setup payload, create process state
|
||||||
var earlyStoreErr = new(StateStoreError)
|
if ok, err := ms.store.Do(seal.user.identity.unwrap(), func(c state.Cursor) {
|
||||||
{
|
if err := c.Save(&state.State{
|
||||||
// shim accepted setup payload, create process state
|
|
||||||
sd := state.State{
|
|
||||||
ID: seal.id.unwrap(),
|
ID: seal.id.unwrap(),
|
||||||
PID: cmd.Process.Pid,
|
PID: ms.cmd.Process.Pid,
|
||||||
Time: *rs.Time,
|
Time: *ms.Time,
|
||||||
|
}, seal.ct); err != nil {
|
||||||
|
ms.fatal("cannot save state entry:", err)
|
||||||
}
|
}
|
||||||
earlyStoreErr.Inner, earlyStoreErr.DoErr = store.Do(seal.user.identity.unwrap(), func(c state.Cursor) {
|
}); err != nil {
|
||||||
earlyStoreErr.InnerErr = c.Save(&sd, seal.ct)
|
if ok {
|
||||||
})
|
ms.uintptr |= mainNeedsDestroy
|
||||||
}
|
ms.fatal("cannot unlock state store:", err)
|
||||||
|
} else {
|
||||||
// state in store at this point, destroy defunct state entry on return
|
ms.fatal("cannot open state store:", err)
|
||||||
deferredStoreFunc = func(c state.Cursor) error { return c.Destroy(seal.id.unwrap()) }
|
|
||||||
|
|
||||||
waitTimeout := make(chan struct{})
|
|
||||||
// TODO(ophestra): enforce this limit early so it does not have to be done twice
|
|
||||||
shimTimeoutCompensated := shimWaitTimeout
|
|
||||||
if seal.waitDelay > MaxShimWaitDelay {
|
|
||||||
shimTimeoutCompensated += MaxShimWaitDelay
|
|
||||||
} else {
|
|
||||||
shimTimeoutCompensated += seal.waitDelay
|
|
||||||
}
|
|
||||||
go func() { <-seal.ctx.Done(); time.Sleep(shimTimeoutCompensated); close(waitTimeout) }()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case rs.WaitErr = <-waitErr:
|
|
||||||
rs.WaitStatus = cmd.ProcessState.Sys().(syscall.WaitStatus)
|
|
||||||
if hlog.Load() {
|
|
||||||
switch {
|
|
||||||
case rs.Exited():
|
|
||||||
hlog.Verbosef("process %d exited with code %d", cmd.Process.Pid, rs.ExitStatus())
|
|
||||||
|
|
||||||
case rs.CoreDump():
|
|
||||||
hlog.Verbosef("process %d dumped core", cmd.Process.Pid)
|
|
||||||
|
|
||||||
case rs.Signaled():
|
|
||||||
hlog.Verbosef("process %d got %s", cmd.Process.Pid, rs.Signal())
|
|
||||||
|
|
||||||
default:
|
|
||||||
hlog.Verbosef("process %d exited with status %#x", cmd.Process.Pid, rs.WaitStatus)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case <-waitTimeout:
|
|
||||||
rs.WaitErr = ErrShimTimeout
|
|
||||||
hlog.Resume()
|
|
||||||
// TODO(ophestra): verify this behaviour in vm tests
|
|
||||||
log.Printf("process %d did not terminate", cmd.Process.Pid)
|
|
||||||
}
|
|
||||||
|
|
||||||
hlog.Resume()
|
|
||||||
if seal.sync != nil {
|
|
||||||
if err := seal.sync.Close(); err != nil {
|
|
||||||
log.Printf("cannot close wayland security context: %v", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if seal.dbusMsg != nil {
|
// state in store at this point, destroy defunct state entry on termination
|
||||||
seal.dbusMsg()
|
ms.uintptr |= mainNeedsDestroy
|
||||||
}
|
|
||||||
|
|
||||||
return earlyStoreErr.equiv("save process state")
|
// beforeExit ties shim process to context
|
||||||
|
ms.beforeExit(false)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// printMessageError prints the error message according to [container.GetErrorMessage],
|
||||||
|
// or fallback prepended to err if an error message is not available.
|
||||||
|
func printMessageError(fallback string, err error) {
|
||||||
|
m, ok := container.GetErrorMessage(err)
|
||||||
|
if !ok {
|
||||||
|
log.Println(fallback, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Print(m)
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,7 @@ type hsuUser struct {
|
|||||||
username string
|
username string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Config) error {
|
func (seal *Outcome) finalise(ctx context.Context, k sys.State, config *hst.Config) error {
|
||||||
const (
|
const (
|
||||||
home = "HOME"
|
home = "HOME"
|
||||||
shell = "SHELL"
|
shell = "SHELL"
|
||||||
@ -176,19 +176,17 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
home: config.Home,
|
home: config.Home,
|
||||||
username: config.Username,
|
username: config.Username,
|
||||||
}
|
}
|
||||||
|
|
||||||
if seal.user.username == "" {
|
if seal.user.username == "" {
|
||||||
seal.user.username = "chronos"
|
seal.user.username = "chronos"
|
||||||
} else if !isValidUsername(seal.user.username) {
|
} else if !isValidUsername(seal.user.username) {
|
||||||
return newWithMessage(fmt.Sprintf("invalid user name %q", seal.user.username))
|
return newWithMessage(fmt.Sprintf("invalid user name %q", seal.user.username))
|
||||||
}
|
}
|
||||||
if u, err := sys.Uid(seal.user.identity.unwrap()); err != nil {
|
seal.user.uid = newInt(sys.MustUid(k, seal.user.identity.unwrap()))
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
seal.user.uid = newInt(u)
|
|
||||||
}
|
|
||||||
seal.user.supp = make([]string, len(config.Groups))
|
seal.user.supp = make([]string, len(config.Groups))
|
||||||
for i, name := range config.Groups {
|
for i, name := range config.Groups {
|
||||||
if g, err := sys.LookupGroup(name); err != nil {
|
if g, err := k.LookupGroup(name); err != nil {
|
||||||
return newWithMessageError(fmt.Sprintf("unknown group %q", name), err)
|
return newWithMessageError(fmt.Sprintf("unknown group %q", name), err)
|
||||||
} else {
|
} else {
|
||||||
seal.user.supp[i] = g.Gid
|
seal.user.supp[i] = g.Gid
|
||||||
@ -201,7 +199,7 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
|
|
||||||
if config.Shell == nil {
|
if config.Shell == nil {
|
||||||
config.Shell = container.AbsFHSRoot.Append("bin", "sh")
|
config.Shell = container.AbsFHSRoot.Append("bin", "sh")
|
||||||
s, _ := sys.LookupEnv(shell)
|
s, _ := k.LookupEnv(shell)
|
||||||
if a, err := container.NewAbs(s); err == nil {
|
if a, err := container.NewAbs(s); err == nil {
|
||||||
config.Shell = a
|
config.Shell = a
|
||||||
}
|
}
|
||||||
@ -210,7 +208,7 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
// hsu clears the environment so resolve paths early
|
// hsu clears the environment so resolve paths early
|
||||||
if config.Path == nil {
|
if config.Path == nil {
|
||||||
if len(config.Args) > 0 {
|
if len(config.Args) > 0 {
|
||||||
if p, err := sys.LookPath(config.Args[0]); err != nil {
|
if p, err := k.LookPath(config.Args[0]); err != nil {
|
||||||
return &hst.AppError{Step: "look up executable file", Err: err}
|
return &hst.AppError{Step: "look up executable file", Err: err}
|
||||||
} else if config.Path, err = container.NewAbs(p); err != nil {
|
} else if config.Path, err = container.NewAbs(p); err != nil {
|
||||||
return newWithMessageError(err.Error(), err)
|
return newWithMessageError(err.Error(), err)
|
||||||
@ -246,7 +244,7 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
|
|
||||||
// hide nscd from container if present
|
// hide nscd from container if present
|
||||||
nscd := container.AbsFHSVar.Append("run/nscd")
|
nscd := container.AbsFHSVar.Append("run/nscd")
|
||||||
if _, err := sys.Stat(nscd.String()); !errors.Is(err, fs.ErrNotExist) {
|
if _, err := k.Stat(nscd.String()); !errors.Is(err, fs.ErrNotExist) {
|
||||||
conf.Filesystem = append(conf.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSEphemeral{Target: nscd}})
|
conf.Filesystem = append(conf.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSEphemeral{Target: nscd}})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,7 +272,7 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
{
|
{
|
||||||
var uid, gid int
|
var uid, gid int
|
||||||
var err error
|
var err error
|
||||||
seal.container, seal.env, err = newContainer(config.Container, sys, seal.id.String(), &uid, &gid)
|
seal.container, seal.env, err = newContainer(config.Container, k, seal.id.String(), &uid, &gid)
|
||||||
seal.waitDelay = config.Container.WaitDelay
|
seal.waitDelay = config.Container.WaitDelay
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &hst.AppError{Step: "initialise container configuration", Err: err}
|
return &hst.AppError{Step: "initialise container configuration", Err: err}
|
||||||
@ -298,7 +296,7 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
seal.env[xdgSessionClass] = "user"
|
seal.env[xdgSessionClass] = "user"
|
||||||
seal.env[xdgSessionType] = "tty"
|
seal.env[xdgSessionType] = "tty"
|
||||||
|
|
||||||
share := &shareHost{seal: seal, sc: sys.Paths()}
|
share := &shareHost{seal: seal, sc: k.Paths()}
|
||||||
seal.runDirPath = share.sc.RunDirPath
|
seal.runDirPath = share.sc.RunDirPath
|
||||||
seal.sys = system.New(seal.ctx, seal.user.uid.unwrap())
|
seal.sys = system.New(seal.ctx, seal.user.uid.unwrap())
|
||||||
seal.sys.Ensure(share.sc.SharePath.String(), 0711)
|
seal.sys.Ensure(share.sc.SharePath.String(), 0711)
|
||||||
@ -342,14 +340,14 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
}
|
}
|
||||||
|
|
||||||
// pass TERM for proper terminal I/O in initial process
|
// pass TERM for proper terminal I/O in initial process
|
||||||
if t, ok := sys.LookupEnv(term); ok {
|
if t, ok := k.LookupEnv(term); ok {
|
||||||
seal.env[term] = t
|
seal.env[term] = t
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Enablements.Unwrap()&system.EWayland != 0 {
|
if config.Enablements.Unwrap()&system.EWayland != 0 {
|
||||||
// outer wayland socket (usually `/run/user/%d/wayland-%d`)
|
// outer wayland socket (usually `/run/user/%d/wayland-%d`)
|
||||||
var socketPath *container.Absolute
|
var socketPath *container.Absolute
|
||||||
if name, ok := sys.LookupEnv(wayland.WaylandDisplay); !ok {
|
if name, ok := k.LookupEnv(wayland.WaylandDisplay); !ok {
|
||||||
hlog.Verbose(wayland.WaylandDisplay + " is not set, assuming " + wayland.FallbackName)
|
hlog.Verbose(wayland.WaylandDisplay + " is not set, assuming " + wayland.FallbackName)
|
||||||
socketPath = share.sc.RuntimePath.Append(wayland.FallbackName)
|
socketPath = share.sc.RuntimePath.Append(wayland.FallbackName)
|
||||||
} else if a, err := container.NewAbs(name); err != nil {
|
} else if a, err := container.NewAbs(name); err != nil {
|
||||||
@ -380,7 +378,7 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
}
|
}
|
||||||
|
|
||||||
if config.Enablements.Unwrap()&system.EX11 != 0 {
|
if config.Enablements.Unwrap()&system.EX11 != 0 {
|
||||||
if d, ok := sys.LookupEnv(display); !ok {
|
if d, ok := k.LookupEnv(display); !ok {
|
||||||
return newWithMessage("DISPLAY is not set")
|
return newWithMessage("DISPLAY is not set")
|
||||||
} else {
|
} else {
|
||||||
socketDir := container.AbsFHSTmp.Append(".X11-unix")
|
socketDir := container.AbsFHSTmp.Append(".X11-unix")
|
||||||
@ -398,7 +396,7 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if socketPath != nil {
|
if socketPath != nil {
|
||||||
if _, err := sys.Stat(socketPath.String()); err != nil {
|
if _, err := k.Stat(socketPath.String()); err != nil {
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
return &hst.AppError{Step: fmt.Sprintf("access X11 socket %q", socketPath), Err: err}
|
return &hst.AppError{Step: fmt.Sprintf("access X11 socket %q", socketPath), Err: err}
|
||||||
}
|
}
|
||||||
@ -422,14 +420,14 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
// PulseAudio socket (usually `/run/user/%d/pulse/native`)
|
// PulseAudio socket (usually `/run/user/%d/pulse/native`)
|
||||||
pulseSocket := pulseRuntimeDir.Append("native")
|
pulseSocket := pulseRuntimeDir.Append("native")
|
||||||
|
|
||||||
if _, err := sys.Stat(pulseRuntimeDir.String()); err != nil {
|
if _, err := k.Stat(pulseRuntimeDir.String()); err != nil {
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
return &hst.AppError{Step: fmt.Sprintf("access PulseAudio directory %q", pulseRuntimeDir), Err: err}
|
return &hst.AppError{Step: fmt.Sprintf("access PulseAudio directory %q", pulseRuntimeDir), Err: err}
|
||||||
}
|
}
|
||||||
return newWithMessage(fmt.Sprintf("PulseAudio directory %q not found", pulseRuntimeDir))
|
return newWithMessage(fmt.Sprintf("PulseAudio directory %q not found", pulseRuntimeDir))
|
||||||
}
|
}
|
||||||
|
|
||||||
if s, err := sys.Stat(pulseSocket.String()); err != nil {
|
if s, err := k.Stat(pulseSocket.String()); err != nil {
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
return &hst.AppError{Step: fmt.Sprintf("access PulseAudio socket %q", pulseSocket), Err: err}
|
return &hst.AppError{Step: fmt.Sprintf("access PulseAudio socket %q", pulseSocket), Err: err}
|
||||||
}
|
}
|
||||||
@ -453,7 +451,7 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
const paLocateStep = "locate PulseAudio cookie"
|
const paLocateStep = "locate PulseAudio cookie"
|
||||||
|
|
||||||
// from environment
|
// from environment
|
||||||
if p, ok := sys.LookupEnv(pulseCookie); ok {
|
if p, ok := k.LookupEnv(pulseCookie); ok {
|
||||||
if a, err := container.NewAbs(p); err != nil {
|
if a, err := container.NewAbs(p); err != nil {
|
||||||
return &hst.AppError{Step: paLocateStep, Err: err}
|
return &hst.AppError{Step: paLocateStep, Err: err}
|
||||||
} else {
|
} else {
|
||||||
@ -464,14 +462,14 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
}
|
}
|
||||||
|
|
||||||
// $HOME/.pulse-cookie
|
// $HOME/.pulse-cookie
|
||||||
if p, ok := sys.LookupEnv(home); ok {
|
if p, ok := k.LookupEnv(home); ok {
|
||||||
if a, err := container.NewAbs(p); err != nil {
|
if a, err := container.NewAbs(p); err != nil {
|
||||||
return &hst.AppError{Step: paLocateStep, Err: err}
|
return &hst.AppError{Step: paLocateStep, Err: err}
|
||||||
} else {
|
} else {
|
||||||
paCookiePath = a.Append(".pulse-cookie")
|
paCookiePath = a.Append(".pulse-cookie")
|
||||||
}
|
}
|
||||||
|
|
||||||
if s, err := sys.Stat(paCookiePath.String()); err != nil {
|
if s, err := k.Stat(paCookiePath.String()); err != nil {
|
||||||
paCookiePath = nil
|
paCookiePath = nil
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
return &hst.AppError{Step: "access PulseAudio cookie", Err: err}
|
return &hst.AppError{Step: "access PulseAudio cookie", Err: err}
|
||||||
@ -485,13 +483,13 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
}
|
}
|
||||||
|
|
||||||
// $XDG_CONFIG_HOME/pulse/cookie
|
// $XDG_CONFIG_HOME/pulse/cookie
|
||||||
if p, ok := sys.LookupEnv(xdgConfigHome); ok {
|
if p, ok := k.LookupEnv(xdgConfigHome); ok {
|
||||||
if a, err := container.NewAbs(p); err != nil {
|
if a, err := container.NewAbs(p); err != nil {
|
||||||
return &hst.AppError{Step: paLocateStep, Err: err}
|
return &hst.AppError{Step: paLocateStep, Err: err}
|
||||||
} else {
|
} else {
|
||||||
paCookiePath = a.Append("pulse", "cookie")
|
paCookiePath = a.Append("pulse", "cookie")
|
||||||
}
|
}
|
||||||
if s, err := sys.Stat(paCookiePath.String()); err != nil {
|
if s, err := k.Stat(paCookiePath.String()); err != nil {
|
||||||
paCookiePath = nil
|
paCookiePath = nil
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
return &hst.AppError{Step: "access PulseAudio cookie", Err: err}
|
return &hst.AppError{Step: "access PulseAudio cookie", Err: err}
|
||||||
|
@ -27,27 +27,27 @@ type multiStore struct {
|
|||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *multiStore) Do(aid int, f func(c Cursor)) (bool, error) {
|
func (s *multiStore) Do(identity int, f func(c Cursor)) (bool, error) {
|
||||||
s.lock.RLock()
|
s.lock.RLock()
|
||||||
defer s.lock.RUnlock()
|
defer s.lock.RUnlock()
|
||||||
|
|
||||||
// load or initialise new backend
|
// load or initialise new backend
|
||||||
b := new(multiBackend)
|
b := new(multiBackend)
|
||||||
b.lock.Lock()
|
b.lock.Lock()
|
||||||
if v, ok := s.backends.LoadOrStore(aid, b); ok {
|
if v, ok := s.backends.LoadOrStore(identity, b); ok {
|
||||||
b = v.(*multiBackend)
|
b = v.(*multiBackend)
|
||||||
} else {
|
} else {
|
||||||
b.path = path.Join(s.base, strconv.Itoa(aid))
|
b.path = path.Join(s.base, strconv.Itoa(identity))
|
||||||
|
|
||||||
// ensure directory
|
// ensure directory
|
||||||
if err := os.MkdirAll(b.path, 0700); err != nil && !errors.Is(err, fs.ErrExist) {
|
if err := os.MkdirAll(b.path, 0700); err != nil && !errors.Is(err, fs.ErrExist) {
|
||||||
s.backends.CompareAndDelete(aid, b)
|
s.backends.CompareAndDelete(identity, b)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// open locker file
|
// open locker file
|
||||||
if l, err := os.OpenFile(b.path+".lock", os.O_RDWR|os.O_CREATE, 0600); err != nil {
|
if l, err := os.OpenFile(b.path+".lock", os.O_RDWR|os.O_CREATE, 0600); err != nil {
|
||||||
s.backends.CompareAndDelete(aid, b)
|
s.backends.CompareAndDelete(identity, b)
|
||||||
return false, err
|
return false, err
|
||||||
} else {
|
} else {
|
||||||
b.lockfile = l
|
b.lockfile = l
|
||||||
|
@ -17,7 +17,7 @@ type Store interface {
|
|||||||
// Do calls f exactly once and ensures store exclusivity until f returns.
|
// Do calls f exactly once and ensures store exclusivity until f returns.
|
||||||
// Returns whether f is called and any errors during the locking process.
|
// Returns whether f is called and any errors during the locking process.
|
||||||
// Cursor provided to f becomes invalid as soon as f returns.
|
// Cursor provided to f becomes invalid as soon as f returns.
|
||||||
Do(aid int, f func(c Cursor)) (ok bool, err error)
|
Do(identity int, f func(c Cursor)) (ok bool, err error)
|
||||||
|
|
||||||
// List queries the store and returns a list of aids known to the store.
|
// List queries the store and returns a list of aids known to the store.
|
||||||
// Note that some or all returned aids might not have any active apps.
|
// Note that some or all returned aids might not have any active apps.
|
||||||
|
@ -3,6 +3,7 @@ package sys
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -11,6 +12,7 @@ import (
|
|||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal"
|
"hakurei.app/internal"
|
||||||
|
"hakurei.app/internal/hlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Hsu caches responses from cmd/hsu.
|
// Hsu caches responses from cmd/hsu.
|
||||||
@ -79,3 +81,24 @@ func (h *Hsu) Uid(identity int) (int, error) {
|
|||||||
}
|
}
|
||||||
return u.uid, u.err
|
return u.uid, u.err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MustUid calls [State.Uid] and terminates on error.
|
||||||
|
func MustUid(s State, identity int) int {
|
||||||
|
uid, err := s.Uid(identity)
|
||||||
|
if err == nil {
|
||||||
|
return uid
|
||||||
|
}
|
||||||
|
|
||||||
|
const fallback = "cannot obtain uid 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -49,14 +49,8 @@ type State interface {
|
|||||||
Uid(identity int) (int, error)
|
Uid(identity int) (int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserID obtains user id from hsu by querying uid of identity 0.
|
// MustGetUserID obtains user id from hsu by querying uid of identity 0.
|
||||||
func GetUserID(os State) (int, error) {
|
func MustGetUserID(os State) int { return (MustUid(os, 0) / 10000) - 100 }
|
||||||
if uid, err := os.Uid(0); err != nil {
|
|
||||||
return -1, err
|
|
||||||
} else {
|
|
||||||
return (uid / 10000) - 100, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyPaths is a generic implementation of [hst.Paths].
|
// CopyPaths is a generic implementation of [hst.Paths].
|
||||||
func CopyPaths(os State, v *hst.Paths, userid int) {
|
func CopyPaths(os State, v *hst.Paths, userid int) {
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
package sys
|
package sys
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
@ -41,30 +39,6 @@ func (s *Std) Printf(format string, v ...any) { hlog.Verbosef(form
|
|||||||
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
||||||
|
|
||||||
func (s *Std) Paths() hst.Paths {
|
func (s *Std) Paths() hst.Paths {
|
||||||
s.pathsOnce.Do(func() {
|
s.pathsOnce.Do(func() { CopyPaths(s, &s.paths, MustGetUserID(s)) })
|
||||||
if userid, err := GetUserID(s); err != nil {
|
|
||||||
// TODO(ophestra): this duplicates code in cmd/hakurei/command.go, keep this up to date until removal
|
|
||||||
hlog.BeforeExit()
|
|
||||||
const fallback = "cannot obtain user id from hsu:"
|
|
||||||
|
|
||||||
// this indicates the error message has already reached stderr, outside the current process's control;
|
|
||||||
// this is only reached when hsu fails for any reason, as a second error message following hsu is confusing
|
|
||||||
if errors.Is(err, ErrHsuAccess) {
|
|
||||||
hlog.Verbose("*"+fallback, err)
|
|
||||||
os.Exit(1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
m, ok := container.GetErrorMessage(err)
|
|
||||||
if !ok {
|
|
||||||
log.Fatalln(fallback, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Fatal(m)
|
|
||||||
} else {
|
|
||||||
CopyPaths(s, &s.paths, userid)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return s.paths
|
return s.paths
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user