internal/app: unexport outcome, remove app struct
All checks were successful
Test / Create distribution (push) Successful in 34s
Test / Sandbox (push) Successful in 2m14s
Test / Hakurei (race detector) (push) Successful in 5m20s
Test / Hpkg (push) Successful in 41s
Test / Hakurei (push) Successful in 2m20s
Test / Sandbox (race detector) (push) Successful in 2m9s
Test / Flake checks (push) Successful in 1m30s
All checks were successful
Test / Create distribution (push) Successful in 34s
Test / Sandbox (push) Successful in 2m14s
Test / Hakurei (race detector) (push) Successful in 5m20s
Test / Hpkg (push) Successful in 41s
Test / Hakurei (push) Successful in 2m20s
Test / Sandbox (race detector) (push) Successful in 2m9s
Test / Flake checks (push) Successful in 1m30s
The App struct no longer does anything, and the outcome struct is entirely opaque. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
parent
b99c63337d
commit
1c4f593566
@ -6,11 +6,9 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
|
||||||
"os/user"
|
"os/user"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"hakurei.app/command"
|
"hakurei.app/command"
|
||||||
@ -25,7 +23,7 @@ import (
|
|||||||
"hakurei.app/system/dbus"
|
"hakurei.app/system/dbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func buildCommand(out io.Writer) command.Command {
|
func buildCommand(ctx context.Context, out io.Writer) command.Command {
|
||||||
var (
|
var (
|
||||||
flagVerbose bool
|
flagVerbose bool
|
||||||
flagJSON bool
|
flagJSON bool
|
||||||
@ -45,7 +43,7 @@ func buildCommand(out io.Writer) command.Command {
|
|||||||
config := tryPath(args[0])
|
config := tryPath(args[0])
|
||||||
config.Args = append(config.Args, args[1:]...)
|
config.Args = append(config.Args, args[1:]...)
|
||||||
|
|
||||||
runApp(config)
|
app.Main(ctx, std, config)
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -165,8 +163,7 @@ func buildCommand(out io.Writer) command.Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// invoke app
|
app.Main(ctx, std, config)
|
||||||
runApp(config)
|
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}).
|
}).
|
||||||
Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"),
|
Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"),
|
||||||
@ -249,22 +246,3 @@ func buildCommand(out io.Writer) command.Command {
|
|||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func runApp(config *hst.Config) {
|
|
||||||
ctx, stop := signal.NotifyContext(context.Background(),
|
|
||||||
syscall.SIGINT, syscall.SIGTERM)
|
|
||||||
defer stop() // unreachable
|
|
||||||
a := app.MustNew(ctx, std)
|
|
||||||
|
|
||||||
if sa, err := a.Seal(config); err != nil {
|
|
||||||
hlog.BeforeExit()
|
|
||||||
if m, ok := container.GetErrorMessage(err); ok {
|
|
||||||
log.Fatal(m)
|
|
||||||
} else {
|
|
||||||
log.Fatalln("cannot seal app:", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sa.Main()
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -68,7 +68,7 @@ Flags:
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
out := new(bytes.Buffer)
|
out := new(bytes.Buffer)
|
||||||
c := buildCommand(out)
|
c := buildCommand(t.Context(), out)
|
||||||
if err := c.Parse(tc.args); !errors.Is(err, command.ErrHelp) && !errors.Is(err, flag.ErrHelp) {
|
if err := c.Parse(tc.args); !errors.Is(err, command.ErrHelp) && !errors.Is(err, flag.ErrHelp) {
|
||||||
t.Errorf("Parse: error = %v; want %v",
|
t.Errorf("Parse: error = %v; want %v",
|
||||||
err, command.ErrHelp)
|
err, command.ErrHelp)
|
||||||
|
@ -4,10 +4,13 @@ package main
|
|||||||
//go:generate cp ../../LICENSE .
|
//go:generate cp ../../LICENSE .
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/internal"
|
"hakurei.app/internal"
|
||||||
@ -44,7 +47,11 @@ func main() {
|
|||||||
log.Fatal("this program must not run as root")
|
log.Fatal("this program must not run as root")
|
||||||
}
|
}
|
||||||
|
|
||||||
buildCommand(os.Stderr).MustParse(os.Args[1:], func(err error) {
|
ctx, stop := signal.NotifyContext(context.Background(),
|
||||||
|
syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
defer stop() // unreachable
|
||||||
|
|
||||||
|
buildCommand(ctx, os.Stderr).MustParse(os.Args[1:], func(err error) {
|
||||||
hlog.Verbosef("command returned %v", err)
|
hlog.Verbosef("command returned %v", err)
|
||||||
if errors.Is(err, errSuccess) {
|
if errors.Is(err, errSuccess) {
|
||||||
hlog.BeforeExit()
|
hlog.BeforeExit()
|
||||||
|
@ -3,83 +3,28 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"sync"
|
"os"
|
||||||
|
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/app/state"
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/internal/sys"
|
"hakurei.app/internal/sys"
|
||||||
)
|
)
|
||||||
|
|
||||||
// New returns the address of a newly initialised [App] struct.
|
// Main runs an app according to [hst.Config] and terminates. Main does not return.
|
||||||
func New(ctx context.Context, os sys.State) (*App, error) {
|
func Main(ctx context.Context, k sys.State, config *hst.Config) {
|
||||||
a := new(App)
|
var id state.ID
|
||||||
a.sys = os
|
if err := state.NewAppID(&id); err != nil {
|
||||||
a.ctx = ctx
|
log.Fatal(err)
|
||||||
|
|
||||||
id := new(state.ID)
|
|
||||||
err := state.NewAppID(id)
|
|
||||||
a.id = newID(id)
|
|
||||||
|
|
||||||
return a, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustNew calls [New] and panics if an error is returned.
|
var seal outcome
|
||||||
func MustNew(ctx context.Context, os sys.State) *App {
|
seal.id = &stringPair[state.ID]{id, id.String()}
|
||||||
a, err := New(ctx, os)
|
if err := seal.finalise(ctx, k, config); err != nil {
|
||||||
if err != nil {
|
printMessageError("cannot seal app:", err)
|
||||||
log.Fatalf("cannot create app: %v", err)
|
os.Exit(1)
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// An App keeps track of the hakurei container lifecycle.
|
seal.main()
|
||||||
type App struct {
|
panic("unreachable")
|
||||||
outcome *Outcome
|
|
||||||
|
|
||||||
id *stringPair[state.ID]
|
|
||||||
sys sys.State
|
|
||||||
ctx context.Context
|
|
||||||
mu sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID returns a copy of [state.ID] held by App.
|
|
||||||
func (a *App) ID() state.ID { a.mu.RLock(); defer a.mu.RUnlock(); return a.id.unwrap() }
|
|
||||||
|
|
||||||
func (a *App) String() string {
|
|
||||||
if a == nil {
|
|
||||||
return "<nil>"
|
|
||||||
}
|
|
||||||
|
|
||||||
a.mu.RLock()
|
|
||||||
defer a.mu.RUnlock()
|
|
||||||
|
|
||||||
if a.outcome != nil {
|
|
||||||
if a.outcome.user.uid == nil {
|
|
||||||
return "<invalid>"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("sealed app %s as uid %s", a.id, a.outcome.user.uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("unsealed app %s", a.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seal determines the [Outcome] of [hst.Config].
|
|
||||||
// Values stored in and referred to by [hst.Config] might be overwritten and must not be used again.
|
|
||||||
func (a *App) Seal(config *hst.Config) (*Outcome, error) {
|
|
||||||
a.mu.Lock()
|
|
||||||
defer a.mu.Unlock()
|
|
||||||
|
|
||||||
if a.outcome != nil {
|
|
||||||
panic("attempting to seal app twice")
|
|
||||||
}
|
|
||||||
|
|
||||||
seal := new(Outcome)
|
|
||||||
seal.id = a.id
|
|
||||||
err := seal.finalise(a.ctx, a.sys, config)
|
|
||||||
if err == nil {
|
|
||||||
a.outcome = seal
|
|
||||||
}
|
|
||||||
return seal, err
|
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ type sealTestCase struct {
|
|||||||
config *hst.Config
|
config *hst.Config
|
||||||
id state.ID
|
id state.ID
|
||||||
wantSys *system.I
|
wantSys *system.I
|
||||||
wantContainer *container.Params
|
wantParams *container.Params
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApp(t *testing.T) {
|
func TestApp(t *testing.T) {
|
||||||
@ -29,40 +29,29 @@ func TestApp(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
a := app.NewWithID(t.Context(), tc.id, tc.os)
|
t.Run("finalise", func(t *testing.T) {
|
||||||
var (
|
sys, params, err := app.FinaliseIParams(t.Context(), tc.os, tc.config, &tc.id)
|
||||||
gotSys *system.I
|
if err != nil {
|
||||||
gotContainer *container.Params
|
|
||||||
)
|
|
||||||
if !t.Run("seal", func(t *testing.T) {
|
|
||||||
if sa, err := a.Seal(tc.config); err != nil {
|
|
||||||
if s, ok := container.GetErrorMessage(err); !ok {
|
if s, ok := container.GetErrorMessage(err); !ok {
|
||||||
t.Errorf("Seal: error = %v", err)
|
t.Fatalf("Seal: error = %v", err)
|
||||||
} else {
|
} else {
|
||||||
t.Errorf("Seal: %s", s)
|
t.Fatalf("Seal: %s", s)
|
||||||
}
|
}
|
||||||
return
|
|
||||||
} else {
|
|
||||||
gotSys, gotContainer = app.AppIParams(a, sa)
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("compare sys", func(t *testing.T) {
|
t.Run("sys", func(t *testing.T) {
|
||||||
if !gotSys.Equal(tc.wantSys) {
|
if !sys.Equal(tc.wantSys) {
|
||||||
t.Errorf("Seal: sys = %#v, want %#v",
|
t.Errorf("Seal: sys = %#v, want %#v", sys, tc.wantSys)
|
||||||
gotSys, tc.wantSys)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("compare params", func(t *testing.T) {
|
t.Run("params", func(t *testing.T) {
|
||||||
if !reflect.DeepEqual(gotContainer, tc.wantContainer) {
|
if !reflect.DeepEqual(params, tc.wantParams) {
|
||||||
t.Errorf("seal: params =\n%s\n, want\n%s",
|
t.Errorf("seal: params =\n%s\n, want\n%s", mustMarshal(params), mustMarshal(tc.wantParams))
|
||||||
mustMarshal(gotContainer), mustMarshal(tc.wantContainer))
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,18 +4,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/app/state"
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/internal/sys"
|
"hakurei.app/internal/sys"
|
||||||
"hakurei.app/system"
|
"hakurei.app/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewWithID(ctx context.Context, id state.ID, os sys.State) *App {
|
func FinaliseIParams(ctx context.Context, k sys.State, config *hst.Config, id *state.ID) (*system.I, *container.Params, error) {
|
||||||
return &App{id: newID(&id), sys: os, ctx: ctx}
|
seal := outcome{id: &stringPair[state.ID]{*id, id.String()}}
|
||||||
}
|
return seal.sys, seal.container, seal.finalise(ctx, k, config)
|
||||||
|
|
||||||
func AppIParams(a *App, seal *Outcome) (*system.I, *container.Params) {
|
|
||||||
if a.outcome != seal || a.id != seal.id {
|
|
||||||
panic("broken app/outcome link")
|
|
||||||
}
|
|
||||||
return seal.sys, seal.container
|
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ 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
|
||||||
|
|
||||||
// mainState holds persistent state bound to [Outcome.Main].
|
// mainState holds persistent state bound to outcome.main.
|
||||||
type mainState struct {
|
type mainState struct {
|
||||||
// done is whether beforeExit has been called already.
|
// done is whether beforeExit has been called already.
|
||||||
done bool
|
done bool
|
||||||
@ -33,7 +33,7 @@ type mainState struct {
|
|||||||
// Time is nil if no process was ever created.
|
// Time is nil if no process was ever created.
|
||||||
Time *time.Time
|
Time *time.Time
|
||||||
|
|
||||||
seal *Outcome
|
seal *outcome
|
||||||
store state.Store
|
store state.Store
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
cmd *exec.Cmd
|
cmd *exec.Cmd
|
||||||
@ -218,9 +218,8 @@ func (ms mainState) fatal(fallback string, ferr error) {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main commits deferred system setup, runs the container, reverts changes to the system, and terminates the program.
|
// main carries out outcome and terminates. main does not return.
|
||||||
// Main does not return.
|
func (seal *outcome) main() {
|
||||||
func (seal *Outcome) Main() {
|
|
||||||
if !seal.f.CompareAndSwap(false, true) {
|
if !seal.f.CompareAndSwap(false, true) {
|
||||||
panic("outcome: attempted to run twice")
|
panic("outcome: attempted to run twice")
|
||||||
}
|
}
|
||||||
|
@ -32,8 +32,8 @@ func newWithMessageError(msg string, err error) error {
|
|||||||
return &hst.AppError{Step: "finalise", Err: err, Msg: msg}
|
return &hst.AppError{Step: "finalise", Err: err, Msg: msg}
|
||||||
}
|
}
|
||||||
|
|
||||||
// An Outcome is the runnable state of a hakurei container via [hst.Config].
|
// An outcome is the runnable state of a hakurei container via [hst.Config].
|
||||||
type Outcome struct {
|
type outcome struct {
|
||||||
// copied from initialising [app]
|
// copied from initialising [app]
|
||||||
id *stringPair[state.ID]
|
id *stringPair[state.ID]
|
||||||
// copied from [sys.State]
|
// copied from [sys.State]
|
||||||
@ -66,7 +66,7 @@ type shareHost struct {
|
|||||||
// process-specific directory in XDG_RUNTIME_DIR, empty if unused
|
// process-specific directory in XDG_RUNTIME_DIR, empty if unused
|
||||||
runtimeSharePath *container.Absolute
|
runtimeSharePath *container.Absolute
|
||||||
|
|
||||||
seal *Outcome
|
seal *outcome
|
||||||
sc hst.Paths
|
sc hst.Paths
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,7 +119,7 @@ type hsuUser struct {
|
|||||||
username string
|
username string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (seal *Outcome) finalise(ctx context.Context, k 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"
|
||||||
|
@ -2,12 +2,9 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"hakurei.app/internal/app/state"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func newInt(v int) *stringPair[int] { return &stringPair[int]{v, strconv.Itoa(v)} }
|
func newInt(v int) *stringPair[int] { return &stringPair[int]{v, strconv.Itoa(v)} }
|
||||||
func newID(id *state.ID) *stringPair[state.ID] { return &stringPair[state.ID]{*id, id.String()} }
|
|
||||||
|
|
||||||
// stringPair stores a value and its string representation.
|
// stringPair stores a value and its string representation.
|
||||||
type stringPair[T comparable] struct {
|
type stringPair[T comparable] struct {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user