internal/hlog: remove error wrapping
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Test / Create distribution (push) Successful in 32s
				
			
		
			
				
	
				Test / Sandbox (push) Successful in 2m29s
				
			
		
			
				
	
				Test / Hakurei (push) Successful in 4m6s
				
			
		
			
				
	
				Test / Hpkg (push) Successful in 4m45s
				
			
		
			
				
	
				Test / Sandbox (race detector) (push) Successful in 4m48s
				
			
		
			
				
	
				Test / Hakurei (race detector) (push) Successful in 6m4s
				
			
		
			
				
	
				Test / Flake checks (push) Successful in 1m26s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Test / Create distribution (push) Successful in 32s
				
			Test / Sandbox (push) Successful in 2m29s
				
			Test / Hakurei (push) Successful in 4m6s
				
			Test / Hpkg (push) Successful in 4m45s
				
			Test / Sandbox (race detector) (push) Successful in 4m48s
				
			Test / Hakurei (race detector) (push) Successful in 6m4s
				
			Test / Flake checks (push) Successful in 1m26s
				
			This was a stopgap solution that lasted for way too long. This finally removes it and prepares internal/app for some major changes. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
		
							parent
							
								
									6265aea73a
								
							
						
					
					
						commit
						f876043844
					
				| @ -82,8 +82,7 @@ func buildCommand(out io.Writer) command.Command { | |||||||
| 				passwdFunc = func() { | 				passwdFunc = func() { | ||||||
| 					var us string | 					var us string | ||||||
| 					if uid, err := std.Uid(aid); err != nil { | 					if uid, err := std.Uid(aid); err != nil { | ||||||
| 						hlog.PrintBaseError(err, "cannot obtain uid from setuid wrapper:") | 						fatal("cannot obtain uid from setuid wrapper:", err) | ||||||
| 						os.Exit(1) |  | ||||||
| 					} else { | 					} else { | ||||||
| 						us = strconv.Itoa(uid) | 						us = strconv.Itoa(uid) | ||||||
| 					} | 					} | ||||||
| @ -260,11 +259,33 @@ func runApp(config *hst.Config) { | |||||||
| 
 | 
 | ||||||
| 	rs := new(app.RunState) | 	rs := new(app.RunState) | ||||||
| 	if sa, err := a.Seal(config); err != nil { | 	if sa, err := a.Seal(config); err != nil { | ||||||
| 		hlog.PrintBaseError(err, "cannot seal app:") | 		hlog.BeforeExit() | ||||||
| 		internal.Exit(1) | 		fatal("cannot seal app:", err) | ||||||
| 	} else { | 	} else { | ||||||
| 		internal.Exit(app.PrintRunStateErr(rs, sa.Run(rs))) | 		hlog.BeforeExit() | ||||||
|  | 		os.Exit(app.PrintRunStateErr(rs, sa.Run(rs))) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	*(*int)(nil) = 0 // not reached | 	*(*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) { | ||||||
|  | 	m, ok := container.GetErrorMessage(err) | ||||||
|  | 	if !ok { | ||||||
|  | 		log.Fatal(fallback, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// 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 we do not want a second error message following hsu | ||||||
|  | 	// TODO(ophestra): handle the hsu error here instead of relying on a magic string | ||||||
|  | 	if m == "\x00" { | ||||||
|  | 		hlog.Verbose("*"+fallback, err) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	log.Fatal(m) | ||||||
|  | } | ||||||
|  | |||||||
| @ -5,7 +5,6 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"log" | 	"log" | ||||||
| 	"os" |  | ||||||
| 	"slices" | 	"slices" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| @ -14,7 +13,6 @@ import ( | |||||||
| 
 | 
 | ||||||
| 	"hakurei.app/hst" | 	"hakurei.app/hst" | ||||||
| 	"hakurei.app/internal/app/state" | 	"hakurei.app/internal/app/state" | ||||||
| 	"hakurei.app/internal/hlog" |  | ||||||
| 	"hakurei.app/system/dbus" | 	"hakurei.app/system/dbus" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| @ -26,8 +24,7 @@ func printShowSystem(output io.Writer, short, flagJSON bool) { | |||||||
| 
 | 
 | ||||||
| 	// get hid by querying uid of identity 0 | 	// get hid by querying uid of identity 0 | ||||||
| 	if uid, err := std.Uid(0); err != nil { | 	if uid, err := std.Uid(0); err != nil { | ||||||
| 		hlog.PrintBaseError(err, "cannot obtain uid from setuid wrapper:") | 		fatal("cannot obtain uid from setuid wrapper:", err) | ||||||
| 		os.Exit(1) |  | ||||||
| 	} else { | 	} else { | ||||||
| 		info.User = (uid / 10000) - 100 | 		info.User = (uid / 10000) - 100 | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ import ( | |||||||
| 	"hakurei.app/internal/sys" | 	"hakurei.app/internal/sys" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | // New returns the address of a newly initialised [App] struct. | ||||||
| func New(ctx context.Context, os sys.State) (*App, error) { | func New(ctx context.Context, os sys.State) (*App, error) { | ||||||
| 	a := new(App) | 	a := new(App) | ||||||
| 	a.sys = os | 	a.sys = os | ||||||
| @ -24,6 +25,7 @@ func New(ctx context.Context, os sys.State) (*App, error) { | |||||||
| 	return a, err | 	return a, err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // MustNew calls [New] and panics if an error is returned. | ||||||
| func MustNew(ctx context.Context, os sys.State) *App { | func MustNew(ctx context.Context, os sys.State) *App { | ||||||
| 	a, err := New(ctx, os) | 	a, err := New(ctx, os) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @ -32,6 +34,7 @@ func MustNew(ctx context.Context, os sys.State) *App { | |||||||
| 	return a | 	return a | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // An App keeps track of the hakurei container lifecycle. | ||||||
| type App struct { | type App struct { | ||||||
| 	outcome *Outcome | 	outcome *Outcome | ||||||
| 
 | 
 | ||||||
| @ -46,7 +49,7 @@ func (a *App) ID() state.ID { a.mu.RLock(); defer a.mu.RUnlock(); return a.id.un | |||||||
| 
 | 
 | ||||||
| func (a *App) String() string { | func (a *App) String() string { | ||||||
| 	if a == nil { | 	if a == nil { | ||||||
| 		return "(invalid app)" | 		return "<nil>" | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	a.mu.RLock() | 	a.mu.RLock() | ||||||
| @ -54,12 +57,12 @@ func (a *App) String() string { | |||||||
| 
 | 
 | ||||||
| 	if a.outcome != nil { | 	if a.outcome != nil { | ||||||
| 		if a.outcome.user.uid == nil { | 		if a.outcome.user.uid == nil { | ||||||
| 			return fmt.Sprintf("(sealed app %s with invalid uid)", a.id) | 			return "<invalid>" | ||||||
| 		} | 		} | ||||||
| 		return fmt.Sprintf("(sealed app %s as uid %s)", a.id, a.outcome.user.uid) | 		return fmt.Sprintf("sealed app %s as uid %s", a.id, a.outcome.user.uid) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return fmt.Sprintf("(unsealed app %s)", a.id) | 	return fmt.Sprintf("unsealed app %s", a.id) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Seal determines the [Outcome] of [hst.Config]. | // Seal determines the [Outcome] of [hst.Config]. | ||||||
| @ -69,7 +72,7 @@ func (a *App) Seal(config *hst.Config) (*Outcome, error) { | |||||||
| 	defer a.mu.Unlock() | 	defer a.mu.Unlock() | ||||||
| 
 | 
 | ||||||
| 	if a.outcome != nil { | 	if a.outcome != nil { | ||||||
| 		panic("app sealed twice") | 		panic("attempting to seal app twice") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	seal := new(Outcome) | 	seal := new(Outcome) | ||||||
|  | |||||||
| @ -11,7 +11,6 @@ import ( | |||||||
| 	"hakurei.app/hst" | 	"hakurei.app/hst" | ||||||
| 	"hakurei.app/internal/app" | 	"hakurei.app/internal/app" | ||||||
| 	"hakurei.app/internal/app/state" | 	"hakurei.app/internal/app/state" | ||||||
| 	"hakurei.app/internal/hlog" |  | ||||||
| 	"hakurei.app/internal/sys" | 	"hakurei.app/internal/sys" | ||||||
| 	"hakurei.app/system" | 	"hakurei.app/system" | ||||||
| ) | ) | ||||||
| @ -37,8 +36,11 @@ func TestApp(t *testing.T) { | |||||||
| 			) | 			) | ||||||
| 			if !t.Run("seal", func(t *testing.T) { | 			if !t.Run("seal", func(t *testing.T) { | ||||||
| 				if sa, err := a.Seal(tc.config); err != nil { | 				if sa, err := a.Seal(tc.config); err != nil { | ||||||
| 					hlog.PrintBaseError(err, "got generic error:") | 					if s, ok := container.GetErrorMessage(err); !ok { | ||||||
| 					t.Errorf("Seal: error = %v", err) | 						t.Errorf("Seal: error = %v", err) | ||||||
|  | 					} else { | ||||||
|  | 						t.Errorf("Seal: %s", s) | ||||||
|  | 					} | ||||||
| 					return | 					return | ||||||
| 				} else { | 				} else { | ||||||
| 					gotSys, gotContainer = app.AppIParams(a, sa) | 					gotSys, gotContainer = app.AppIParams(a, sa) | ||||||
|  | |||||||
| @ -11,7 +11,6 @@ import ( | |||||||
| 	"hakurei.app/container" | 	"hakurei.app/container" | ||||||
| 	"hakurei.app/container/seccomp" | 	"hakurei.app/container/seccomp" | ||||||
| 	"hakurei.app/hst" | 	"hakurei.app/hst" | ||||||
| 	"hakurei.app/internal/hlog" |  | ||||||
| 	"hakurei.app/internal/sys" | 	"hakurei.app/internal/sys" | ||||||
| 	"hakurei.app/system/dbus" | 	"hakurei.app/system/dbus" | ||||||
| ) | ) | ||||||
| @ -23,7 +22,7 @@ const preallocateOpsCount = 1 << 5 | |||||||
| // Note that remaining container setup must be queued by the caller. | // Note that remaining container setup must be queued by the caller. | ||||||
| func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid *int) (*container.Params, map[string]string, error) { | func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid *int) (*container.Params, map[string]string, error) { | ||||||
| 	if s == nil { | 	if s == nil { | ||||||
| 		return nil, nil, hlog.WrapErr(syscall.EBADE, "invalid container configuration") | 		return nil, nil, newWithMessage("invalid container configuration") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	params := &container.Params{ | 	params := &container.Params{ | ||||||
|  | |||||||
| @ -4,48 +4,33 @@ import ( | |||||||
| 	"errors" | 	"errors" | ||||||
| 	"log" | 	"log" | ||||||
| 
 | 
 | ||||||
|  | 	"hakurei.app/container" | ||||||
| 	"hakurei.app/internal/hlog" | 	"hakurei.app/internal/hlog" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | // 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) { | func PrintRunStateErr(rs *RunState, runErr error) (code int) { | ||||||
| 	code = rs.ExitStatus() | 	code = rs.ExitStatus() | ||||||
| 
 | 
 | ||||||
| 	if runErr != nil { | 	if runErr != nil { | ||||||
| 		if rs.Time == nil { | 		if rs.Time == nil { | ||||||
| 			hlog.PrintBaseError(runErr, "cannot start app:") | 			// no process has been created | ||||||
|  | 			printMessageError("cannot start app:", runErr) | ||||||
| 		} else { | 		} else { | ||||||
| 			var e *hlog.BaseError | 			if m, ok := container.GetErrorMessage(runErr); !ok { | ||||||
| 			if !hlog.AsBaseError(runErr, &e) { | 				// catch-all for unexpected errors | ||||||
| 				log.Println("wait failed:", runErr) | 				log.Println("run returned error:", runErr) | ||||||
| 			} else { | 			} else { | ||||||
| 				// Wait only returns either *app.ProcessError or *app.StateStoreError wrapped in a *app.BaseError |  | ||||||
| 				var se *StateStoreError | 				var se *StateStoreError | ||||||
| 				if !errors.As(runErr, &se) { | 				if !errors.As(runErr, &se) { | ||||||
| 					// does not need special handling | 					// this could only be returned from a shim setup failure path | ||||||
| 					log.Print(e.Message()) | 					log.Print(m) | ||||||
| 				} else { | 				} else { | ||||||
| 					// inner error are either unwrapped store errors | 					// InnerErr is returned by c.Save(&sd, seal.ct), and are always unwrapped | ||||||
| 					// or joined errors returned by *appSealTx revert | 					printMessageError("error returned during revert:", | ||||||
| 					// wrapped in *app.BaseError | 						&FinaliseError{Step: "save process state", Err: se.InnerErr}) | ||||||
| 					var ej RevertCompoundError |  | ||||||
| 					if !errors.As(se.InnerErr, &ej) { |  | ||||||
| 						// does not require special handling |  | ||||||
| 						log.Print(e.Message()) |  | ||||||
| 					} else { |  | ||||||
| 						errs := ej.Unwrap() |  | ||||||
| 
 |  | ||||||
| 						// every error here is wrapped in *app.BaseError |  | ||||||
| 						for _, ei := range errs { |  | ||||||
| 							var eb *hlog.BaseError |  | ||||||
| 							if !errors.As(ei, &eb) { |  | ||||||
| 								// unreachable |  | ||||||
| 								log.Println("invalid error type returned by revert:", ei) |  | ||||||
| 							} else { |  | ||||||
| 								// print inner *app.BaseError message |  | ||||||
| 								log.Print(eb.Message()) |  | ||||||
| 							} |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| @ -58,43 +43,45 @@ func PrintRunStateErr(rs *RunState, runErr error) (code int) { | |||||||
| 	if rs.RevertErr != nil { | 	if rs.RevertErr != nil { | ||||||
| 		var stateStoreError *StateStoreError | 		var stateStoreError *StateStoreError | ||||||
| 		if !errors.As(rs.RevertErr, &stateStoreError) || stateStoreError == nil { | 		if !errors.As(rs.RevertErr, &stateStoreError) || stateStoreError == nil { | ||||||
| 			hlog.PrintBaseError(rs.RevertErr, "generic fault during cleanup:") | 			printMessageError("cannot clean up:", rs.RevertErr) | ||||||
| 			goto out | 			goto out | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if stateStoreError.Err != nil { | 		if stateStoreError.Errs != nil { | ||||||
| 			if len(stateStoreError.Err) == 2 { | 			if len(stateStoreError.Errs) == 2 { // storeErr.save(revertErr, store.Close()) | ||||||
| 				if stateStoreError.Err[0] != nil { | 				if stateStoreError.Errs[0] != nil { // revertErr is MessageError joined by errors.Join | ||||||
| 					if joinedErrs, ok := stateStoreError.Err[0].(interface{ Unwrap() []error }); !ok { | 					var joinedErrors interface { | ||||||
| 						hlog.PrintBaseError(stateStoreError.Err[0], "generic fault during revert:") | 						Unwrap() []error | ||||||
|  | 						error | ||||||
|  | 					} | ||||||
|  | 					if !errors.As(stateStoreError.Errs[0], &joinedErrors) { | ||||||
|  | 						printMessageError("cannot revert:", stateStoreError.Errs[0]) | ||||||
| 					} else { | 					} else { | ||||||
| 						for _, err := range joinedErrs.Unwrap() { | 						for _, err := range joinedErrors.Unwrap() { | ||||||
| 							if err != nil { | 							if err != nil { | ||||||
| 								hlog.PrintBaseError(err, "fault during revert:") | 								printMessageError("cannot revert:", err) | ||||||
| 							} | 							} | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 				if stateStoreError.Err[1] != nil { | 				if stateStoreError.Errs[1] != nil { // store.Close() is joined by errors.Join | ||||||
| 					log.Printf("cannot close store: %v", stateStoreError.Err[1]) | 					log.Printf("cannot close store: %v", stateStoreError.Errs[1]) | ||||||
| 				} | 				} | ||||||
| 			} else { | 			} else { | ||||||
| 				log.Printf("fault during cleanup: %v", | 				log.Printf("fault during cleanup: %v", errors.Join(stateStoreError.Errs...)) | ||||||
| 					errors.Join(stateStoreError.Err...)) |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if stateStoreError.OpErr != nil { | 		if stateStoreError.OpErr != nil { | ||||||
| 			log.Printf("blind revert due to store fault: %v", | 			log.Printf("blind revert due to store fault: %v", stateStoreError.OpErr) | ||||||
| 				stateStoreError.OpErr) |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if stateStoreError.DoErr != nil { | 		if stateStoreError.DoErr != nil { | ||||||
| 			hlog.PrintBaseError(stateStoreError.DoErr, "state store operation unsuccessful:") | 			printMessageError("state store operation unsuccessful:", stateStoreError.DoErr) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if stateStoreError.Inner && stateStoreError.InnerErr != nil { | 		if stateStoreError.Inner && stateStoreError.InnerErr != nil { | ||||||
| 			hlog.PrintBaseError(stateStoreError.InnerErr, "cannot destroy state entry:") | 			printMessageError("cannot destroy state entry:", stateStoreError.InnerErr) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	out: | 	out: | ||||||
| @ -108,7 +95,18 @@ func PrintRunStateErr(rs *RunState, runErr error) (code int) { | |||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // StateStoreError is returned for a failed state save | // TODO(ophestra): this duplicates code in cmd/hakurei/command.go, keep this up to date until removal | ||||||
|  | func printMessageError(fallback string, err error) { | ||||||
|  | 	if m, ok := container.GetErrorMessage(err); ok { | ||||||
|  | 		if m != "\x00" { | ||||||
|  | 			log.Print(m) | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		log.Println(fallback, err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // StateStoreError is returned for a failed state save. | ||||||
| type StateStoreError struct { | type StateStoreError struct { | ||||||
| 	// whether inner function was called | 	// whether inner function was called | ||||||
| 	Inner bool | 	Inner bool | ||||||
| @ -119,22 +117,23 @@ type StateStoreError struct { | |||||||
| 	// stores an arbitrary store operation error | 	// stores an arbitrary store operation error | ||||||
| 	OpErr error | 	OpErr error | ||||||
| 	// stores arbitrary errors | 	// stores arbitrary errors | ||||||
| 	Err []error | 	Errs []error | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // save saves arbitrary errors in [StateStoreError] once. | // save saves arbitrary errors in [StateStoreError.Errs] once. | ||||||
| func (e *StateStoreError) save(errs ...error) { | func (e *StateStoreError) save(errs ...error) { | ||||||
| 	if len(errs) == 0 || e.Err != nil { | 	if len(errs) == 0 || e.Errs != nil { | ||||||
| 		panic("invalid call to save") | 		panic("invalid call to save") | ||||||
| 	} | 	} | ||||||
| 	e.Err = errs | 	e.Errs = errs | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (e *StateStoreError) equiv(a ...any) error { | // equiv returns an error that [StateStoreError] is equivalent to, including nil. | ||||||
| 	if e.Inner && e.InnerErr == nil && e.DoErr == nil && e.OpErr == nil && errors.Join(e.Err...) == 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 | 		return nil | ||||||
| 	} else { | 	} else { | ||||||
| 		return hlog.WrapErrSuffix(e, a...) | 		return &FinaliseError{Step: step, Err: e} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -148,7 +147,7 @@ func (e *StateStoreError) Error() string { | |||||||
| 	if e.OpErr != nil { | 	if e.OpErr != nil { | ||||||
| 		return e.OpErr.Error() | 		return e.OpErr.Error() | ||||||
| 	} | 	} | ||||||
| 	if err := errors.Join(e.Err...); err != nil { | 	if err := errors.Join(e.Errs...); err != nil { | ||||||
| 		return err.Error() | 		return err.Error() | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @ -157,7 +156,7 @@ func (e *StateStoreError) Error() string { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (e *StateStoreError) Unwrap() (errs []error) { | func (e *StateStoreError) Unwrap() (errs []error) { | ||||||
| 	errs = make([]error, 0, 3) | 	errs = make([]error, 0, 3+len(e.Errs)) | ||||||
| 	if e.InnerErr != nil { | 	if e.InnerErr != nil { | ||||||
| 		errs = append(errs, e.InnerErr) | 		errs = append(errs, e.InnerErr) | ||||||
| 	} | 	} | ||||||
| @ -167,15 +166,10 @@ func (e *StateStoreError) Unwrap() (errs []error) { | |||||||
| 	if e.OpErr != nil { | 	if e.OpErr != nil { | ||||||
| 		errs = append(errs, e.OpErr) | 		errs = append(errs, e.OpErr) | ||||||
| 	} | 	} | ||||||
| 	if err := errors.Join(e.Err...); err != nil { | 	for _, err := range e.Errs { | ||||||
| 		errs = append(errs, err) | 		if err != nil { | ||||||
|  | 			errs = append(errs, err) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 |  | ||||||
| // A RevertCompoundError encapsulates errors returned by |  | ||||||
| // the Revert method of [system.I]. |  | ||||||
| type RevertCompoundError interface { |  | ||||||
| 	Error() string |  | ||||||
| 	Unwrap() []error |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -106,7 +106,7 @@ func (seal *Outcome) Run(rs *RunState) error { | |||||||
| 			}() | 			}() | ||||||
| 		}) | 		}) | ||||||
| 		storeErr.save(revertErr, store.Close()) | 		storeErr.save(revertErr, store.Close()) | ||||||
| 		rs.RevertErr = storeErr.equiv("error during cleanup:") | 		rs.RevertErr = storeErr.equiv("clean up") | ||||||
| 	}() | 	}() | ||||||
| 
 | 
 | ||||||
| 	ctx, cancel := context.WithCancel(seal.ctx) | 	ctx, cancel := context.WithCancel(seal.ctx) | ||||||
| @ -119,8 +119,7 @@ func (seal *Outcome) Run(rs *RunState) error { | |||||||
| 
 | 
 | ||||||
| 	var e *gob.Encoder | 	var e *gob.Encoder | ||||||
| 	if fd, encoder, err := container.Setup(&cmd.ExtraFiles); err != nil { | 	if fd, encoder, err := container.Setup(&cmd.ExtraFiles); err != nil { | ||||||
| 		return hlog.WrapErrSuffix(err, | 		return &FinaliseError{Step: "create shim setup pipe", Err: err} | ||||||
| 			"cannot create shim setup pipe:") |  | ||||||
| 	} else { | 	} else { | ||||||
| 		e = encoder | 		e = encoder | ||||||
| 		cmd.Env = []string{ | 		cmd.Env = []string{ | ||||||
| @ -140,8 +139,7 @@ func (seal *Outcome) Run(rs *RunState) error { | |||||||
| 	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 := cmd.Start(); err != nil { | ||||||
| 		return hlog.WrapErrSuffix(err, | 		return &FinaliseError{Step: "start setuid wrapper", Err: err} | ||||||
| 			"cannot start setuid wrapper:") |  | ||||||
| 	} | 	} | ||||||
| 	rs.setStart() | 	rs.setStart() | ||||||
| 
 | 
 | ||||||
| @ -161,14 +159,12 @@ func (seal *Outcome) Run(rs *RunState) error { | |||||||
| 	case err := <-setupErr: | 	case err := <-setupErr: | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			hlog.Resume() | 			hlog.Resume() | ||||||
| 			return hlog.WrapErrSuffix(err, | 			return &FinaliseError{Step: "transmit shim config", Err: err} | ||||||
| 				"cannot transmit shim config:") |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	case <-ctx.Done(): | 	case <-ctx.Done(): | ||||||
| 		hlog.Resume() | 		hlog.Resume() | ||||||
| 		return hlog.WrapErr(syscall.ECANCELED, | 		return newWithMessageError("shim setup canceled", syscall.ECANCELED) | ||||||
| 			"shim setup canceled") |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// returned after blocking on waitErr | 	// returned after blocking on waitErr | ||||||
| @ -225,5 +221,5 @@ func (seal *Outcome) Run(rs *RunState) error { | |||||||
| 		seal.dbusMsg() | 		seal.dbusMsg() | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return earlyStoreErr.equiv("cannot save process state:") | 	return earlyStoreErr.equiv("save process state") | ||||||
| } | } | ||||||
|  | |||||||
| @ -8,8 +8,8 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"io/fs" | 	"io/fs" | ||||||
|  | 	"net" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path" |  | ||||||
| 	"slices" | 	"slices" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| @ -28,35 +28,36 @@ import ( | |||||||
| 	"hakurei.app/system/wayland" | 	"hakurei.app/system/wayland" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const ( | // A FinaliseError is returned while finalising a [hst.Config] outcome. | ||||||
| 	home  = "HOME" | type FinaliseError struct { | ||||||
| 	shell = "SHELL" | 	Step string | ||||||
|  | 	Err  error | ||||||
|  | 	Msg  string | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| 	xdgConfigHome   = "XDG_CONFIG_HOME" | func (e *FinaliseError) Error() string { return e.Err.Error() } | ||||||
| 	xdgRuntimeDir   = "XDG_RUNTIME_DIR" | func (e *FinaliseError) Unwrap() error { return e.Err } | ||||||
| 	xdgSessionClass = "XDG_SESSION_CLASS" | func (e *FinaliseError) Message() string { | ||||||
| 	xdgSessionType  = "XDG_SESSION_TYPE" | 	if e.Msg != "" { | ||||||
|  | 		return e.Msg | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	term    = "TERM" | 	switch { | ||||||
| 	display = "DISPLAY" | 	case errors.As(e.Err, new(*os.PathError)), | ||||||
|  | 		errors.As(e.Err, new(*os.LinkError)), | ||||||
|  | 		errors.As(e.Err, new(*os.SyscallError)), | ||||||
|  | 		errors.As(e.Err, new(*net.OpError)): | ||||||
|  | 		return "cannot " + e.Error() | ||||||
| 
 | 
 | ||||||
| 	pulseServer = "PULSE_SERVER" | 	default: | ||||||
| 	pulseCookie = "PULSE_COOKIE" | 		return "cannot " + e.Step + ": " + e.Error() | ||||||
|  | 	} | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| 	dbusSessionBusAddress = "DBUS_SESSION_BUS_ADDRESS" | func newWithMessage(msg string) error { return newWithMessageError(msg, os.ErrInvalid) } | ||||||
| 	dbusSystemBusAddress  = "DBUS_SYSTEM_BUS_ADDRESS" | func newWithMessageError(msg string, err error) error { | ||||||
| ) | 	return &FinaliseError{Step: "finalise", Err: err, Msg: msg} | ||||||
| 
 | } | ||||||
| var ( |  | ||||||
| 	ErrIdent = errors.New("invalid identity") |  | ||||||
| 	ErrName  = errors.New("invalid username") |  | ||||||
| 
 |  | ||||||
| 	ErrXDisplay = errors.New(display + " unset") |  | ||||||
| 
 |  | ||||||
| 	ErrPulseCookie = errors.New("pulse cookie not present") |  | ||||||
| 	ErrPulseSocket = errors.New("pulse socket not present") |  | ||||||
| 	ErrPulseMode   = errors.New("unexpected pulse socket mode") |  | ||||||
| ) |  | ||||||
| 
 | 
 | ||||||
| // 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 { | ||||||
| @ -146,35 +147,55 @@ type hsuUser struct { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Config) error { | func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Config) error { | ||||||
|  | 	const ( | ||||||
|  | 		home  = "HOME" | ||||||
|  | 		shell = "SHELL" | ||||||
|  | 
 | ||||||
|  | 		xdgConfigHome   = "XDG_CONFIG_HOME" | ||||||
|  | 		xdgRuntimeDir   = "XDG_RUNTIME_DIR" | ||||||
|  | 		xdgSessionClass = "XDG_SESSION_CLASS" | ||||||
|  | 		xdgSessionType  = "XDG_SESSION_TYPE" | ||||||
|  | 
 | ||||||
|  | 		term    = "TERM" | ||||||
|  | 		display = "DISPLAY" | ||||||
|  | 
 | ||||||
|  | 		pulseServer = "PULSE_SERVER" | ||||||
|  | 		pulseCookie = "PULSE_COOKIE" | ||||||
|  | 
 | ||||||
|  | 		dbusSessionBusAddress = "DBUS_SESSION_BUS_ADDRESS" | ||||||
|  | 		dbusSystemBusAddress  = "DBUS_SYSTEM_BUS_ADDRESS" | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
| 	if ctx == nil { | 	if ctx == nil { | ||||||
|  | 		// unreachable | ||||||
| 		panic("invalid call to finalise") | 		panic("invalid call to finalise") | ||||||
| 	} | 	} | ||||||
| 	if seal.ctx != nil { | 	if seal.ctx != nil { | ||||||
|  | 		// unreachable | ||||||
| 		panic("attempting to finalise twice") | 		panic("attempting to finalise twice") | ||||||
| 	} | 	} | ||||||
| 	seal.ctx = ctx | 	seal.ctx = ctx | ||||||
| 
 | 
 | ||||||
| 	if config == nil { | 	if config == nil { | ||||||
| 		return hlog.WrapErr(syscall.EINVAL, syscall.EINVAL.Error()) | 		// unreachable | ||||||
|  | 		return newWithMessage("invalid configuration") | ||||||
| 	} | 	} | ||||||
| 	if config.Home == nil { | 	if config.Home == nil { | ||||||
| 		return hlog.WrapErr(os.ErrInvalid, "invalid path to home directory") | 		return newWithMessage("invalid path to home directory") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	{ | 	{ | ||||||
| 		// encode initial configuration for state tracking | 		// encode initial configuration for state tracking | ||||||
| 		ct := new(bytes.Buffer) | 		ct := new(bytes.Buffer) | ||||||
| 		if err := gob.NewEncoder(ct).Encode(config); err != nil { | 		if err := gob.NewEncoder(ct).Encode(config); err != nil { | ||||||
| 			return hlog.WrapErrSuffix(err, | 			return &FinaliseError{Step: "encode initial config", Err: err} | ||||||
| 				"cannot encode initial config:") |  | ||||||
| 		} | 		} | ||||||
| 		seal.ct = ct | 		seal.ct = ct | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// allowed identity range 0 to 9999, this is checked again in hsu | 	// allowed identity range 0 to 9999, this is checked again in hsu | ||||||
| 	if config.Identity < 0 || config.Identity > 9999 { | 	if config.Identity < 0 || config.Identity > 9999 { | ||||||
| 		return hlog.WrapErr(ErrIdent, | 		return newWithMessage(fmt.Sprintf("identity %d out of range", config.Identity)) | ||||||
| 			fmt.Sprintf("identity %d out of range", config.Identity)) |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	seal.user = hsuUser{ | 	seal.user = hsuUser{ | ||||||
| @ -185,8 +206,7 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co | |||||||
| 	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 hlog.WrapErr(ErrName, | 		return newWithMessage(fmt.Sprintf("invalid user name %q", seal.user.username)) | ||||||
| 			fmt.Sprintf("invalid user name %q", seal.user.username)) |  | ||||||
| 	} | 	} | ||||||
| 	if u, err := sys.Uid(seal.user.identity.unwrap()); err != nil { | 	if u, err := sys.Uid(seal.user.identity.unwrap()); err != nil { | ||||||
| 		return err | 		return err | ||||||
| @ -196,8 +216,7 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co | |||||||
| 	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 := sys.LookupGroup(name); err != nil { | ||||||
| 			return hlog.WrapErr(err, | 			return newWithMessageError(fmt.Sprintf("unknown group %q", name), err) | ||||||
| 				fmt.Sprintf("unknown group %q", name)) |  | ||||||
| 		} else { | 		} else { | ||||||
| 			seal.user.supp[i] = g.Gid | 			seal.user.supp[i] = g.Gid | ||||||
| 		} | 		} | ||||||
| @ -219,9 +238,9 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co | |||||||
| 		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 := sys.LookPath(config.Args[0]); err != nil { | ||||||
| 					return hlog.WrapErr(err, err.Error()) | 					return &FinaliseError{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 hlog.WrapErr(err, err.Error()) | 					return newWithMessageError(err.Error(), err) | ||||||
| 				} | 				} | ||||||
| 			} else { | 			} else { | ||||||
| 				config.Path = config.Shell | 				config.Path = config.Shell | ||||||
| @ -272,10 +291,10 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co | |||||||
| 
 | 
 | ||||||
| 	// late nil checks for pd behaviour | 	// late nil checks for pd behaviour | ||||||
| 	if config.Shell == nil { | 	if config.Shell == nil { | ||||||
| 		return hlog.WrapErr(syscall.EINVAL, "invalid shell path") | 		return newWithMessage("invalid shell path") | ||||||
| 	} | 	} | ||||||
| 	if config.Path == nil { | 	if config.Path == nil { | ||||||
| 		return hlog.WrapErr(syscall.EINVAL, "invalid program path") | 		return newWithMessage("invalid program path") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var mapuid, mapgid *stringPair[int] | 	var mapuid, mapgid *stringPair[int] | ||||||
| @ -285,8 +304,7 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co | |||||||
| 		seal.container, seal.env, err = newContainer(config.Container, sys, seal.id.String(), &uid, &gid) | 		seal.container, seal.env, err = newContainer(config.Container, sys, seal.id.String(), &uid, &gid) | ||||||
| 		seal.waitDelay = config.Container.WaitDelay | 		seal.waitDelay = config.Container.WaitDelay | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return hlog.WrapErrSuffix(err, | 			return &FinaliseError{Step: "initialise container configuration", Err: err} | ||||||
| 				"cannot initialise container configuration:") |  | ||||||
| 		} | 		} | ||||||
| 		if len(config.Args) == 0 { | 		if len(config.Args) == 0 { | ||||||
| 			config.Args = []string{config.Path.String()} | 			config.Args = []string{config.Path.String()} | ||||||
| @ -390,8 +408,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 := sys.LookupEnv(display); !ok { | ||||||
| 			return hlog.WrapErr(ErrXDisplay, | 			return newWithMessage("DISPLAY is not set") | ||||||
| 				"DISPLAY is not set") |  | ||||||
| 		} else { | 		} else { | ||||||
| 			socketDir := container.AbsFHSTmp.Append(".X11-unix") | 			socketDir := container.AbsFHSTmp.Append(".X11-unix") | ||||||
| 
 | 
 | ||||||
| @ -410,8 +427,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 := sys.Stat(socketPath.String()); err != nil { | ||||||
| 					if !errors.Is(err, fs.ErrNotExist) { | 					if !errors.Is(err, fs.ErrNotExist) { | ||||||
| 						return hlog.WrapErrSuffix(err, | 						return &FinaliseError{Step: fmt.Sprintf("access X11 socket %q", socketPath), Err: err} | ||||||
| 							fmt.Sprintf("cannot access X11 socket %q:", socketPath)) |  | ||||||
| 					} | 					} | ||||||
| 				} else { | 				} else { | ||||||
| 					seal.sys.UpdatePermType(system.EX11, socketPath.String(), acl.Read, acl.Write, acl.Execute) | 					seal.sys.UpdatePermType(system.EX11, socketPath.String(), acl.Read, acl.Write, acl.Execute) | ||||||
| @ -435,24 +451,19 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co | |||||||
| 
 | 
 | ||||||
| 		if _, err := sys.Stat(pulseRuntimeDir.String()); err != nil { | 		if _, err := sys.Stat(pulseRuntimeDir.String()); err != nil { | ||||||
| 			if !errors.Is(err, fs.ErrNotExist) { | 			if !errors.Is(err, fs.ErrNotExist) { | ||||||
| 				return hlog.WrapErrSuffix(err, | 				return &FinaliseError{Step: fmt.Sprintf("access PulseAudio directory %q", pulseRuntimeDir), Err: err} | ||||||
| 					fmt.Sprintf("cannot access PulseAudio directory %q:", pulseRuntimeDir)) |  | ||||||
| 			} | 			} | ||||||
| 			return hlog.WrapErr(ErrPulseSocket, | 			return newWithMessage(fmt.Sprintf("PulseAudio directory %q not found", pulseRuntimeDir)) | ||||||
| 				fmt.Sprintf("PulseAudio directory %q not found", pulseRuntimeDir)) |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if s, err := sys.Stat(pulseSocket.String()); err != nil { | 		if s, err := sys.Stat(pulseSocket.String()); err != nil { | ||||||
| 			if !errors.Is(err, fs.ErrNotExist) { | 			if !errors.Is(err, fs.ErrNotExist) { | ||||||
| 				return hlog.WrapErrSuffix(err, | 				return &FinaliseError{Step: fmt.Sprintf("access PulseAudio socket %q", pulseSocket), Err: err} | ||||||
| 					fmt.Sprintf("cannot access PulseAudio socket %q:", pulseSocket)) |  | ||||||
| 			} | 			} | ||||||
| 			return hlog.WrapErr(ErrPulseSocket, | 			return newWithMessage(fmt.Sprintf("PulseAudio directory %q found but socket does not exist", pulseRuntimeDir)) | ||||||
| 				fmt.Sprintf("PulseAudio directory %q found but socket does not exist", pulseRuntimeDir)) |  | ||||||
| 		} else { | 		} else { | ||||||
| 			if m := s.Mode(); m&0o006 != 0o006 { | 			if m := s.Mode(); m&0o006 != 0o006 { | ||||||
| 				return hlog.WrapErr(ErrPulseMode, | 				return newWithMessage(fmt.Sprintf("unexpected permissions on %q: %s", pulseSocket, m)) | ||||||
| 					fmt.Sprintf("unexpected permissions on %q:", pulseSocket), m) |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| @ -464,15 +475,75 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co | |||||||
| 		seal.env[pulseServer] = "unix:" + innerPulseSocket.String() | 		seal.env[pulseServer] = "unix:" + innerPulseSocket.String() | ||||||
| 
 | 
 | ||||||
| 		// publish current user's pulse cookie for target user | 		// publish current user's pulse cookie for target user | ||||||
| 		if src, err := discoverPulseCookie(sys); err != nil { | 		var paCookiePath *container.Absolute | ||||||
| 			// not fatal | 		{ | ||||||
| 			hlog.Verbose(strings.TrimSpace(err.(*hlog.BaseError).Message())) | 			const paLocateStep = "locate PulseAudio cookie" | ||||||
| 		} else { | 
 | ||||||
|  | 			// from environment | ||||||
|  | 			if p, ok := sys.LookupEnv(pulseCookie); ok { | ||||||
|  | 				if a, err := container.NewAbs(p); err != nil { | ||||||
|  | 					return &FinaliseError{Step: paLocateStep, Err: err} | ||||||
|  | 				} else { | ||||||
|  | 					// this takes precedence, do not verify whether the file is accessible | ||||||
|  | 					paCookiePath = a | ||||||
|  | 					goto out | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// $HOME/.pulse-cookie | ||||||
|  | 			if p, ok := sys.LookupEnv(home); ok { | ||||||
|  | 				if a, err := container.NewAbs(p); err != nil { | ||||||
|  | 					return &FinaliseError{Step: paLocateStep, Err: err} | ||||||
|  | 				} else { | ||||||
|  | 					paCookiePath = a.Append(".pulse-cookie") | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				if s, err := sys.Stat(paCookiePath.String()); err != nil { | ||||||
|  | 					paCookiePath = nil | ||||||
|  | 					if !errors.Is(err, fs.ErrNotExist) { | ||||||
|  | 						return &FinaliseError{Step: "access PulseAudio cookie", Err: err} | ||||||
|  | 					} | ||||||
|  | 					// fallthrough | ||||||
|  | 				} else if s.IsDir() { | ||||||
|  | 					paCookiePath = nil | ||||||
|  | 				} else { | ||||||
|  | 					goto out | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// $XDG_CONFIG_HOME/pulse/cookie | ||||||
|  | 			if p, ok := sys.LookupEnv(xdgConfigHome); ok { | ||||||
|  | 				if a, err := container.NewAbs(p); err != nil { | ||||||
|  | 					return &FinaliseError{Step: paLocateStep, Err: err} | ||||||
|  | 				} else { | ||||||
|  | 					paCookiePath = a.Append("pulse", "cookie") | ||||||
|  | 				} | ||||||
|  | 				if s, err := sys.Stat(paCookiePath.String()); err != nil { | ||||||
|  | 					paCookiePath = nil | ||||||
|  | 					if !errors.Is(err, fs.ErrNotExist) { | ||||||
|  | 						return &FinaliseError{Step: "access PulseAudio cookie", Err: err} | ||||||
|  | 					} | ||||||
|  | 					// fallthrough | ||||||
|  | 				} else if s.IsDir() { | ||||||
|  | 					paCookiePath = nil | ||||||
|  | 				} else { | ||||||
|  | 					goto out | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		out: | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if paCookiePath != nil { | ||||||
| 			innerDst := hst.AbsTmp.Append("/pulse-cookie") | 			innerDst := hst.AbsTmp.Append("/pulse-cookie") | ||||||
| 			seal.env[pulseCookie] = innerDst.String() | 			seal.env[pulseCookie] = innerDst.String() | ||||||
| 			var payload *[]byte | 			var payload *[]byte | ||||||
| 			seal.container.PlaceP(innerDst, &payload) | 			seal.container.PlaceP(innerDst, &payload) | ||||||
| 			seal.sys.CopyFile(payload, src, 256, 256) | 			seal.sys.CopyFile(payload, paCookiePath.String(), 256, 256) | ||||||
|  | 		} else { | ||||||
|  | 			hlog.Verbose("cannot locate PulseAudio cookie (tried " + | ||||||
|  | 				"$PULSE_COOKIE, " + | ||||||
|  | 				"$XDG_CONFIG_HOME/pulse/cookie, " + | ||||||
|  | 				"$HOME/.pulse-cookie)") | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @ -538,8 +609,8 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co | |||||||
| 	seal.container.Env = make([]string, 0, len(seal.env)) | 	seal.container.Env = make([]string, 0, len(seal.env)) | ||||||
| 	for k, v := range seal.env { | 	for k, v := range seal.env { | ||||||
| 		if strings.IndexByte(k, '=') != -1 { | 		if strings.IndexByte(k, '=') != -1 { | ||||||
| 			return hlog.WrapErr(syscall.EINVAL, | 			return &FinaliseError{Step: "flatten environment", Err: syscall.EINVAL, | ||||||
| 				fmt.Sprintf("invalid environment variable %s", k)) | 				Msg: fmt.Sprintf("invalid environment variable %s", k)} | ||||||
| 		} | 		} | ||||||
| 		seal.container.Env = append(seal.container.Env, k+"="+v) | 		seal.container.Env = append(seal.container.Env, k+"="+v) | ||||||
| 	} | 	} | ||||||
| @ -552,42 +623,3 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co | |||||||
| 
 | 
 | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 |  | ||||||
| // discoverPulseCookie attempts various standard methods to discover the current user's PulseAudio authentication cookie |  | ||||||
| func discoverPulseCookie(sys sys.State) (string, error) { |  | ||||||
| 	if p, ok := sys.LookupEnv(pulseCookie); ok { |  | ||||||
| 		return p, nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// dotfile $HOME/.pulse-cookie |  | ||||||
| 	if p, ok := sys.LookupEnv(home); ok { |  | ||||||
| 		p = path.Join(p, ".pulse-cookie") |  | ||||||
| 		if s, err := sys.Stat(p); err != nil { |  | ||||||
| 			if !errors.Is(err, fs.ErrNotExist) { |  | ||||||
| 				return p, hlog.WrapErrSuffix(err, |  | ||||||
| 					fmt.Sprintf("cannot access PulseAudio cookie %q:", p)) |  | ||||||
| 			} |  | ||||||
| 			// not found, try next method |  | ||||||
| 		} else if !s.IsDir() { |  | ||||||
| 			return p, nil |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// $XDG_CONFIG_HOME/pulse/cookie |  | ||||||
| 	if p, ok := sys.LookupEnv(xdgConfigHome); ok { |  | ||||||
| 		p = path.Join(p, "pulse", "cookie") |  | ||||||
| 		if s, err := sys.Stat(p); err != nil { |  | ||||||
| 			if !errors.Is(err, fs.ErrNotExist) { |  | ||||||
| 				return p, hlog.WrapErrSuffix(err, |  | ||||||
| 					fmt.Sprintf("cannot access PulseAudio cookie %q:", p)) |  | ||||||
| 			} |  | ||||||
| 			// not found, try next method |  | ||||||
| 		} else if !s.IsDir() { |  | ||||||
| 			return p, nil |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return "", hlog.WrapErr(ErrPulseCookie, |  | ||||||
| 		fmt.Sprintf("cannot locate PulseAudio cookie (tried $%s, $%s/pulse/cookie, $%s/.pulse-cookie)", |  | ||||||
| 			pulseCookie, xdgConfigHome, home)) |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -155,11 +155,11 @@ func ShimMain() { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if err := z.Start(); err != nil { | 	if err := z.Start(); err != nil { | ||||||
| 		hlog.PrintBaseError(err, "cannot start container:") | 		printMessageError("cannot start container:", err) | ||||||
| 		os.Exit(1) | 		os.Exit(1) | ||||||
| 	} | 	} | ||||||
| 	if err := z.Serve(); err != nil { | 	if err := z.Serve(); err != nil { | ||||||
| 		hlog.PrintBaseError(err, "cannot configure container:") | 		printMessageError("cannot configure container:", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if err := seccomp.Load( | 	if err := seccomp.Load( | ||||||
|  | |||||||
| @ -1,81 +0,0 @@ | |||||||
| package hlog |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"log" |  | ||||||
| 	"reflect" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // baseError implements a basic error container |  | ||||||
| type baseError struct { |  | ||||||
| 	Err error |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (e *baseError) Error() string { return e.Err.Error() } |  | ||||||
| func (e *baseError) Unwrap() error { return e.Err } |  | ||||||
| 
 |  | ||||||
| // BaseError implements an error container with a user-facing message |  | ||||||
| type BaseError struct { |  | ||||||
| 	message string |  | ||||||
| 	baseError |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Message returns a user-facing error message |  | ||||||
| func (e *BaseError) Message() string { return e.message } |  | ||||||
| 
 |  | ||||||
| // WrapErr wraps an error with a corresponding message. |  | ||||||
| func WrapErr(err error, a ...any) error { |  | ||||||
| 	if err == nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	return wrapErr(err, fmt.Sprintln(a...)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // WrapErrSuffix wraps an error with a corresponding message with err at the end of the message. |  | ||||||
| func WrapErrSuffix(err error, a ...any) error { |  | ||||||
| 	if err == nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	return wrapErr(err, fmt.Sprintln(append(a, err)...)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // WrapErrFunc wraps an error with a corresponding message returned by f. |  | ||||||
| func WrapErrFunc(err error, f func(err error) string) error { |  | ||||||
| 	if err == nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	return wrapErr(err, f(err)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func wrapErr(err error, message string) *BaseError { |  | ||||||
| 	return &BaseError{message, baseError{err}} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var ( |  | ||||||
| 	baseErrorType = reflect.TypeFor[*BaseError]() |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func AsBaseError(err error, target **BaseError) bool { |  | ||||||
| 	v := reflect.ValueOf(err) |  | ||||||
| 	if !v.CanConvert(baseErrorType) { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	*target = v.Convert(baseErrorType).Interface().(*BaseError) |  | ||||||
| 	return true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func PrintBaseError(err error, fallback string) { |  | ||||||
| 	var e *BaseError |  | ||||||
| 
 |  | ||||||
| 	if AsBaseError(err, &e) { |  | ||||||
| 		if msg := e.Message(); strings.TrimSpace(msg) != "" { |  | ||||||
| 			log.Print(msg) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		Verbose("*"+fallback, err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	log.Println(fallback, err) |  | ||||||
| } |  | ||||||
| @ -2,11 +2,9 @@ package hlog | |||||||
| 
 | 
 | ||||||
| type Output struct{} | type Output struct{} | ||||||
| 
 | 
 | ||||||
| func (Output) IsVerbose() bool                         { return Load() } | func (Output) IsVerbose() bool                  { return Load() } | ||||||
| func (Output) Verbose(v ...any)                        { Verbose(v...) } | func (Output) Verbose(v ...any)                 { Verbose(v...) } | ||||||
| func (Output) Verbosef(format string, v ...any)        { Verbosef(format, v...) } | func (Output) Verbosef(format string, v ...any) { Verbosef(format, v...) } | ||||||
| func (Output) WrapErr(err error, a ...any) error       { return WrapErr(err, a...) } | func (Output) Suspend()                         { Suspend() } | ||||||
| func (Output) PrintBaseErr(err error, fallback string) { PrintBaseError(err, fallback) } | func (Output) Resume() bool                     { return Resume() } | ||||||
| func (Output) Suspend()                                { Suspend() } | func (Output) BeforeExit()                      { BeforeExit() } | ||||||
| func (Output) Resume() bool                            { return Resume() } |  | ||||||
| func (Output) BeforeExit()                             { BeforeExit() } |  | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ import ( | |||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io/fs" | 	"io/fs" | ||||||
|  | 	"log" | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 	"os/user" | 	"os/user" | ||||||
| @ -51,7 +52,14 @@ 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() { | ||||||
| 		if userid, err := GetUserID(s); err != nil { | 		if userid, err := GetUserID(s); err != nil { | ||||||
| 			hlog.PrintBaseError(err, "cannot obtain user id from hsu:") | 			// TODO(ophestra): this duplicates code in cmd/hakurei/command.go, keep this up to date until removal | ||||||
|  | 			if m, ok := container.GetErrorMessage(err); ok { | ||||||
|  | 				if m != "\x00" { | ||||||
|  | 					log.Print(m) | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				log.Println("cannot obtain user id from hsu:", err) | ||||||
|  | 			} | ||||||
| 			hlog.BeforeExit() | 			hlog.BeforeExit() | ||||||
| 			s.Exit(1) | 			s.Exit(1) | ||||||
| 		} else { | 		} else { | ||||||
| @ -61,6 +69,16 @@ func (s *Std) Paths() hst.Paths { | |||||||
| 	return s.paths | 	return s.paths | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // this is a temporary placeholder until this package is removed | ||||||
|  | type wrappedError struct { | ||||||
|  | 	Err error | ||||||
|  | 	Msg string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *wrappedError) Error() string   { return e.Err.Error() } | ||||||
|  | func (e *wrappedError) Unwrap() error   { return e.Err } | ||||||
|  | func (e *wrappedError) Message() string { return e.Msg } | ||||||
|  | 
 | ||||||
| func (s *Std) Uid(identity int) (int, error) { | func (s *Std) Uid(identity int) (int, error) { | ||||||
| 	s.uidOnce.Do(func() { | 	s.uidOnce.Do(func() { | ||||||
| 		s.uidCopy = make(map[int]struct { | 		s.uidCopy = make(map[int]struct { | ||||||
| @ -103,12 +121,13 @@ func (s *Std) Uid(identity int) (int, error) { | |||||||
| 	if p, u.err = cmd.Output(); u.err == nil { | 	if p, u.err = cmd.Output(); u.err == nil { | ||||||
| 		u.uid, u.err = strconv.Atoi(string(p)) | 		u.uid, u.err = strconv.Atoi(string(p)) | ||||||
| 		if u.err != nil { | 		if u.err != nil { | ||||||
| 			u.err = hlog.WrapErr(u.err, "invalid uid string from hsu") | 			u.err = &wrappedError{u.err, "invalid uid string from hsu"} | ||||||
| 		} | 		} | ||||||
| 	} else if errors.As(u.err, &exitError) && exitError != nil && exitError.ExitCode() == 1 { | 	} else if errors.As(u.err, &exitError) && exitError != nil && exitError.ExitCode() == 1 { | ||||||
| 		u.err = hlog.WrapErr(syscall.EACCES, "") // hsu prints to stderr in this case | 		// hsu prints an error message in this case | ||||||
|  | 		u.err = &wrappedError{syscall.EACCES, "\x00"} // this drops the message, handled in cmd/hakurei/command.go | ||||||
| 	} else if os.IsNotExist(u.err) { | 	} else if os.IsNotExist(u.err) { | ||||||
| 		u.err = hlog.WrapErr(os.ErrNotExist, fmt.Sprintf("the setuid helper is missing: %s", hsuPath)) | 		u.err = &wrappedError{os.ErrNotExist, fmt.Sprintf("the setuid helper is missing: %s", hsuPath)} | ||||||
| 	} | 	} | ||||||
| 	return u.uid, u.err | 	return u.uid, u.err | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user