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() { | ||||
| 					var us string | ||||
| 					if uid, err := std.Uid(aid); err != nil { | ||||
| 						hlog.PrintBaseError(err, "cannot obtain uid from setuid wrapper:") | ||||
| 						os.Exit(1) | ||||
| 						fatal("cannot obtain uid from setuid wrapper:", err) | ||||
| 					} else { | ||||
| 						us = strconv.Itoa(uid) | ||||
| 					} | ||||
| @ -260,11 +259,33 @@ func runApp(config *hst.Config) { | ||||
| 
 | ||||
| 	rs := new(app.RunState) | ||||
| 	if sa, err := a.Seal(config); err != nil { | ||||
| 		hlog.PrintBaseError(err, "cannot seal app:") | ||||
| 		internal.Exit(1) | ||||
| 		hlog.BeforeExit() | ||||
| 		fatal("cannot seal app:", err) | ||||
| 	} else { | ||||
| 		internal.Exit(app.PrintRunStateErr(rs, sa.Run(rs))) | ||||
| 		hlog.BeforeExit() | ||||
| 		os.Exit(app.PrintRunStateErr(rs, sa.Run(rs))) | ||||
| 	} | ||||
| 
 | ||||
| 	*(*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" | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"slices" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| @ -14,7 +13,6 @@ import ( | ||||
| 
 | ||||
| 	"hakurei.app/hst" | ||||
| 	"hakurei.app/internal/app/state" | ||||
| 	"hakurei.app/internal/hlog" | ||||
| 	"hakurei.app/system/dbus" | ||||
| ) | ||||
| 
 | ||||
| @ -26,8 +24,7 @@ func printShowSystem(output io.Writer, short, flagJSON bool) { | ||||
| 
 | ||||
| 	// get hid by querying uid of identity 0 | ||||
| 	if uid, err := std.Uid(0); err != nil { | ||||
| 		hlog.PrintBaseError(err, "cannot obtain uid from setuid wrapper:") | ||||
| 		os.Exit(1) | ||||
| 		fatal("cannot obtain uid from setuid wrapper:", err) | ||||
| 	} else { | ||||
| 		info.User = (uid / 10000) - 100 | ||||
| 	} | ||||
|  | ||||
| @ -12,6 +12,7 @@ import ( | ||||
| 	"hakurei.app/internal/sys" | ||||
| ) | ||||
| 
 | ||||
| // New returns the address of a newly initialised [App] struct. | ||||
| func New(ctx context.Context, os sys.State) (*App, error) { | ||||
| 	a := new(App) | ||||
| 	a.sys = os | ||||
| @ -24,6 +25,7 @@ func New(ctx context.Context, os sys.State) (*App, error) { | ||||
| 	return a, err | ||||
| } | ||||
| 
 | ||||
| // MustNew calls [New] and panics if an error is returned. | ||||
| func MustNew(ctx context.Context, os sys.State) *App { | ||||
| 	a, err := New(ctx, os) | ||||
| 	if err != nil { | ||||
| @ -32,6 +34,7 @@ func MustNew(ctx context.Context, os sys.State) *App { | ||||
| 	return a | ||||
| } | ||||
| 
 | ||||
| // An App keeps track of the hakurei container lifecycle. | ||||
| type App struct { | ||||
| 	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 { | ||||
| 	if a == nil { | ||||
| 		return "(invalid app)" | ||||
| 		return "<nil>" | ||||
| 	} | ||||
| 
 | ||||
| 	a.mu.RLock() | ||||
| @ -54,12 +57,12 @@ func (a *App) String() string { | ||||
| 
 | ||||
| 	if a.outcome != 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]. | ||||
| @ -69,7 +72,7 @@ func (a *App) Seal(config *hst.Config) (*Outcome, error) { | ||||
| 	defer a.mu.Unlock() | ||||
| 
 | ||||
| 	if a.outcome != nil { | ||||
| 		panic("app sealed twice") | ||||
| 		panic("attempting to seal app twice") | ||||
| 	} | ||||
| 
 | ||||
| 	seal := new(Outcome) | ||||
|  | ||||
| @ -11,7 +11,6 @@ import ( | ||||
| 	"hakurei.app/hst" | ||||
| 	"hakurei.app/internal/app" | ||||
| 	"hakurei.app/internal/app/state" | ||||
| 	"hakurei.app/internal/hlog" | ||||
| 	"hakurei.app/internal/sys" | ||||
| 	"hakurei.app/system" | ||||
| ) | ||||
| @ -37,8 +36,11 @@ func TestApp(t *testing.T) { | ||||
| 			) | ||||
| 			if !t.Run("seal", func(t *testing.T) { | ||||
| 				if sa, err := a.Seal(tc.config); err != nil { | ||||
| 					hlog.PrintBaseError(err, "got generic error:") | ||||
| 					t.Errorf("Seal: error = %v", err) | ||||
| 					if s, ok := container.GetErrorMessage(err); !ok { | ||||
| 						t.Errorf("Seal: error = %v", err) | ||||
| 					} else { | ||||
| 						t.Errorf("Seal: %s", s) | ||||
| 					} | ||||
| 					return | ||||
| 				} else { | ||||
| 					gotSys, gotContainer = app.AppIParams(a, sa) | ||||
|  | ||||
| @ -11,7 +11,6 @@ import ( | ||||
| 	"hakurei.app/container" | ||||
| 	"hakurei.app/container/seccomp" | ||||
| 	"hakurei.app/hst" | ||||
| 	"hakurei.app/internal/hlog" | ||||
| 	"hakurei.app/internal/sys" | ||||
| 	"hakurei.app/system/dbus" | ||||
| ) | ||||
| @ -23,7 +22,7 @@ const preallocateOpsCount = 1 << 5 | ||||
| // 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) { | ||||
| 	if s == nil { | ||||
| 		return nil, nil, hlog.WrapErr(syscall.EBADE, "invalid container configuration") | ||||
| 		return nil, nil, newWithMessage("invalid container configuration") | ||||
| 	} | ||||
| 
 | ||||
| 	params := &container.Params{ | ||||
|  | ||||
| @ -4,48 +4,33 @@ import ( | ||||
| 	"errors" | ||||
| 	"log" | ||||
| 
 | ||||
| 	"hakurei.app/container" | ||||
| 	"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) { | ||||
| 	code = rs.ExitStatus() | ||||
| 
 | ||||
| 	if runErr != nil { | ||||
| 		if rs.Time == nil { | ||||
| 			hlog.PrintBaseError(runErr, "cannot start app:") | ||||
| 			// no process has been created | ||||
| 			printMessageError("cannot start app:", runErr) | ||||
| 		} else { | ||||
| 			var e *hlog.BaseError | ||||
| 			if !hlog.AsBaseError(runErr, &e) { | ||||
| 				log.Println("wait failed:", runErr) | ||||
| 			if m, ok := container.GetErrorMessage(runErr); !ok { | ||||
| 				// catch-all for unexpected errors | ||||
| 				log.Println("run returned error:", runErr) | ||||
| 			} else { | ||||
| 				// Wait only returns either *app.ProcessError or *app.StateStoreError wrapped in a *app.BaseError | ||||
| 				var se *StateStoreError | ||||
| 				if !errors.As(runErr, &se) { | ||||
| 					// does not need special handling | ||||
| 					log.Print(e.Message()) | ||||
| 					// this could only be returned from a shim setup failure path | ||||
| 					log.Print(m) | ||||
| 				} else { | ||||
| 					// inner error are either unwrapped store errors | ||||
| 					// or joined errors returned by *appSealTx revert | ||||
| 					// wrapped in *app.BaseError | ||||
| 					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()) | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
| 					// InnerErr is returned by c.Save(&sd, seal.ct), and are always unwrapped | ||||
| 					printMessageError("error returned during revert:", | ||||
| 						&FinaliseError{Step: "save process state", Err: se.InnerErr}) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| @ -58,43 +43,45 @@ func PrintRunStateErr(rs *RunState, runErr error) (code int) { | ||||
| 	if rs.RevertErr != nil { | ||||
| 		var stateStoreError *StateStoreError | ||||
| 		if !errors.As(rs.RevertErr, &stateStoreError) || stateStoreError == nil { | ||||
| 			hlog.PrintBaseError(rs.RevertErr, "generic fault during cleanup:") | ||||
| 			printMessageError("cannot clean up:", rs.RevertErr) | ||||
| 			goto out | ||||
| 		} | ||||
| 
 | ||||
| 		if stateStoreError.Err != nil { | ||||
| 			if len(stateStoreError.Err) == 2 { | ||||
| 				if stateStoreError.Err[0] != nil { | ||||
| 					if joinedErrs, ok := stateStoreError.Err[0].(interface{ Unwrap() []error }); !ok { | ||||
| 						hlog.PrintBaseError(stateStoreError.Err[0], "generic fault during revert:") | ||||
| 		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 joinedErrs.Unwrap() { | ||||
| 						for _, err := range joinedErrors.Unwrap() { | ||||
| 							if err != nil { | ||||
| 								hlog.PrintBaseError(err, "fault during revert:") | ||||
| 								printMessageError("cannot revert:", err) | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 				if stateStoreError.Err[1] != nil { | ||||
| 					log.Printf("cannot close store: %v", stateStoreError.Err[1]) | ||||
| 				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.Err...)) | ||||
| 				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) | ||||
| 			log.Printf("blind revert due to store fault: %v", stateStoreError.OpErr) | ||||
| 		} | ||||
| 
 | ||||
| 		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 { | ||||
| 			hlog.PrintBaseError(stateStoreError.InnerErr, "cannot destroy state entry:") | ||||
| 			printMessageError("cannot destroy state entry:", stateStoreError.InnerErr) | ||||
| 		} | ||||
| 
 | ||||
| 	out: | ||||
| @ -108,7 +95,18 @@ func PrintRunStateErr(rs *RunState, runErr error) (code int) { | ||||
| 	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 { | ||||
| 	// whether inner function was called | ||||
| 	Inner bool | ||||
| @ -119,22 +117,23 @@ type StateStoreError struct { | ||||
| 	// stores an arbitrary store operation error | ||||
| 	OpErr error | ||||
| 	// 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) { | ||||
| 	if len(errs) == 0 || e.Err != nil { | ||||
| 	if len(errs) == 0 || e.Errs != nil { | ||||
| 		panic("invalid call to save") | ||||
| 	} | ||||
| 	e.Err = errs | ||||
| 	e.Errs = errs | ||||
| } | ||||
| 
 | ||||
| func (e *StateStoreError) equiv(a ...any) error { | ||||
| 	if e.Inner && e.InnerErr == nil && e.DoErr == nil && e.OpErr == nil && errors.Join(e.Err...) == nil { | ||||
| // 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 hlog.WrapErrSuffix(e, a...) | ||||
| 		return &FinaliseError{Step: step, Err: e} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @ -148,7 +147,7 @@ func (e *StateStoreError) Error() string { | ||||
| 	if e.OpErr != nil { | ||||
| 		return e.OpErr.Error() | ||||
| 	} | ||||
| 	if err := errors.Join(e.Err...); err != nil { | ||||
| 	if err := errors.Join(e.Errs...); err != nil { | ||||
| 		return err.Error() | ||||
| 	} | ||||
| 
 | ||||
| @ -157,7 +156,7 @@ func (e *StateStoreError) Error() string { | ||||
| } | ||||
| 
 | ||||
| func (e *StateStoreError) Unwrap() (errs []error) { | ||||
| 	errs = make([]error, 0, 3) | ||||
| 	errs = make([]error, 0, 3+len(e.Errs)) | ||||
| 	if e.InnerErr != nil { | ||||
| 		errs = append(errs, e.InnerErr) | ||||
| 	} | ||||
| @ -167,15 +166,10 @@ func (e *StateStoreError) Unwrap() (errs []error) { | ||||
| 	if e.OpErr != nil { | ||||
| 		errs = append(errs, e.OpErr) | ||||
| 	} | ||||
| 	if err := errors.Join(e.Err...); err != nil { | ||||
| 		errs = append(errs, err) | ||||
| 	for _, err := range e.Errs { | ||||
| 		if err != nil { | ||||
| 			errs = append(errs, err) | ||||
| 		} | ||||
| 	} | ||||
| 	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()) | ||||
| 		rs.RevertErr = storeErr.equiv("error during cleanup:") | ||||
| 		rs.RevertErr = storeErr.equiv("clean up") | ||||
| 	}() | ||||
| 
 | ||||
| 	ctx, cancel := context.WithCancel(seal.ctx) | ||||
| @ -119,8 +119,7 @@ func (seal *Outcome) Run(rs *RunState) error { | ||||
| 
 | ||||
| 	var e *gob.Encoder | ||||
| 	if fd, encoder, err := container.Setup(&cmd.ExtraFiles); err != nil { | ||||
| 		return hlog.WrapErrSuffix(err, | ||||
| 			"cannot create shim setup pipe:") | ||||
| 		return &FinaliseError{Step: "create shim setup pipe", Err: err} | ||||
| 	} else { | ||||
| 		e = encoder | ||||
| 		cmd.Env = []string{ | ||||
| @ -140,8 +139,7 @@ func (seal *Outcome) Run(rs *RunState) error { | ||||
| 	hlog.Verbosef("setuid helper at %s", hsuPath) | ||||
| 	hlog.Suspend() | ||||
| 	if err := cmd.Start(); err != nil { | ||||
| 		return hlog.WrapErrSuffix(err, | ||||
| 			"cannot start setuid wrapper:") | ||||
| 		return &FinaliseError{Step: "start setuid wrapper", Err: err} | ||||
| 	} | ||||
| 	rs.setStart() | ||||
| 
 | ||||
| @ -161,14 +159,12 @@ func (seal *Outcome) Run(rs *RunState) error { | ||||
| 	case err := <-setupErr: | ||||
| 		if err != nil { | ||||
| 			hlog.Resume() | ||||
| 			return hlog.WrapErrSuffix(err, | ||||
| 				"cannot transmit shim config:") | ||||
| 			return &FinaliseError{Step: "transmit shim config", Err: err} | ||||
| 		} | ||||
| 
 | ||||
| 	case <-ctx.Done(): | ||||
| 		hlog.Resume() | ||||
| 		return hlog.WrapErr(syscall.ECANCELED, | ||||
| 			"shim setup canceled") | ||||
| 		return newWithMessageError("shim setup canceled", syscall.ECANCELED) | ||||
| 	} | ||||
| 
 | ||||
| 	// returned after blocking on waitErr | ||||
| @ -225,5 +221,5 @@ func (seal *Outcome) Run(rs *RunState) error { | ||||
| 		seal.dbusMsg() | ||||
| 	} | ||||
| 
 | ||||
| 	return earlyStoreErr.equiv("cannot save process state:") | ||||
| 	return earlyStoreErr.equiv("save process state") | ||||
| } | ||||
|  | ||||
| @ -8,8 +8,8 @@ import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/fs" | ||||
| 	"net" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"slices" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| @ -28,35 +28,36 @@ import ( | ||||
| 	"hakurei.app/system/wayland" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	home  = "HOME" | ||||
| 	shell = "SHELL" | ||||
| // A FinaliseError is returned while finalising a [hst.Config] outcome. | ||||
| type FinaliseError struct { | ||||
| 	Step string | ||||
| 	Err  error | ||||
| 	Msg  string | ||||
| } | ||||
| 
 | ||||
| 	xdgConfigHome   = "XDG_CONFIG_HOME" | ||||
| 	xdgRuntimeDir   = "XDG_RUNTIME_DIR" | ||||
| 	xdgSessionClass = "XDG_SESSION_CLASS" | ||||
| 	xdgSessionType  = "XDG_SESSION_TYPE" | ||||
| func (e *FinaliseError) Error() string { return e.Err.Error() } | ||||
| func (e *FinaliseError) Unwrap() error { return e.Err } | ||||
| func (e *FinaliseError) Message() string { | ||||
| 	if e.Msg != "" { | ||||
| 		return e.Msg | ||||
| 	} | ||||
| 
 | ||||
| 	term    = "TERM" | ||||
| 	display = "DISPLAY" | ||||
| 	switch { | ||||
| 	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" | ||||
| 	pulseCookie = "PULSE_COOKIE" | ||||
| 	default: | ||||
| 		return "cannot " + e.Step + ": " + e.Error() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 	dbusSessionBusAddress = "DBUS_SESSION_BUS_ADDRESS" | ||||
| 	dbusSystemBusAddress  = "DBUS_SYSTEM_BUS_ADDRESS" | ||||
| ) | ||||
| 
 | ||||
| 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") | ||||
| ) | ||||
| func newWithMessage(msg string) error { return newWithMessageError(msg, os.ErrInvalid) } | ||||
| func newWithMessageError(msg string, err error) error { | ||||
| 	return &FinaliseError{Step: "finalise", Err: err, Msg: msg} | ||||
| } | ||||
| 
 | ||||
| // An Outcome is the runnable state of a hakurei container via [hst.Config]. | ||||
| type Outcome struct { | ||||
| @ -146,35 +147,55 @@ type hsuUser struct { | ||||
| } | ||||
| 
 | ||||
| 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 { | ||||
| 		// unreachable | ||||
| 		panic("invalid call to finalise") | ||||
| 	} | ||||
| 	if seal.ctx != nil { | ||||
| 		// unreachable | ||||
| 		panic("attempting to finalise twice") | ||||
| 	} | ||||
| 	seal.ctx = ctx | ||||
| 
 | ||||
| 	if config == nil { | ||||
| 		return hlog.WrapErr(syscall.EINVAL, syscall.EINVAL.Error()) | ||||
| 		// unreachable | ||||
| 		return newWithMessage("invalid configuration") | ||||
| 	} | ||||
| 	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 | ||||
| 		ct := new(bytes.Buffer) | ||||
| 		if err := gob.NewEncoder(ct).Encode(config); err != nil { | ||||
| 			return hlog.WrapErrSuffix(err, | ||||
| 				"cannot encode initial config:") | ||||
| 			return &FinaliseError{Step: "encode initial config", Err: err} | ||||
| 		} | ||||
| 		seal.ct = ct | ||||
| 	} | ||||
| 
 | ||||
| 	// allowed identity range 0 to 9999, this is checked again in hsu | ||||
| 	if config.Identity < 0 || config.Identity > 9999 { | ||||
| 		return hlog.WrapErr(ErrIdent, | ||||
| 			fmt.Sprintf("identity %d out of range", config.Identity)) | ||||
| 		return newWithMessage(fmt.Sprintf("identity %d out of range", config.Identity)) | ||||
| 	} | ||||
| 
 | ||||
| 	seal.user = hsuUser{ | ||||
| @ -185,8 +206,7 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co | ||||
| 	if seal.user.username == "" { | ||||
| 		seal.user.username = "chronos" | ||||
| 	} else if !isValidUsername(seal.user.username) { | ||||
| 		return hlog.WrapErr(ErrName, | ||||
| 			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 { | ||||
| 		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)) | ||||
| 	for i, name := range config.Groups { | ||||
| 		if g, err := sys.LookupGroup(name); err != nil { | ||||
| 			return hlog.WrapErr(err, | ||||
| 				fmt.Sprintf("unknown group %q", name)) | ||||
| 			return newWithMessageError(fmt.Sprintf("unknown group %q", name), err) | ||||
| 		} else { | ||||
| 			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 len(config.Args) > 0 { | ||||
| 				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 { | ||||
| 					return hlog.WrapErr(err, err.Error()) | ||||
| 					return newWithMessageError(err.Error(), err) | ||||
| 				} | ||||
| 			} else { | ||||
| 				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 | ||||
| 	if config.Shell == nil { | ||||
| 		return hlog.WrapErr(syscall.EINVAL, "invalid shell path") | ||||
| 		return newWithMessage("invalid shell path") | ||||
| 	} | ||||
| 	if config.Path == nil { | ||||
| 		return hlog.WrapErr(syscall.EINVAL, "invalid program path") | ||||
| 		return newWithMessage("invalid program path") | ||||
| 	} | ||||
| 
 | ||||
| 	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.waitDelay = config.Container.WaitDelay | ||||
| 		if err != nil { | ||||
| 			return hlog.WrapErrSuffix(err, | ||||
| 				"cannot initialise container configuration:") | ||||
| 			return &FinaliseError{Step: "initialise container configuration", Err: err} | ||||
| 		} | ||||
| 		if len(config.Args) == 0 { | ||||
| 			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 d, ok := sys.LookupEnv(display); !ok { | ||||
| 			return hlog.WrapErr(ErrXDisplay, | ||||
| 				"DISPLAY is not set") | ||||
| 			return newWithMessage("DISPLAY is not set") | ||||
| 		} else { | ||||
| 			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 _, err := sys.Stat(socketPath.String()); err != nil { | ||||
| 					if !errors.Is(err, fs.ErrNotExist) { | ||||
| 						return hlog.WrapErrSuffix(err, | ||||
| 							fmt.Sprintf("cannot access X11 socket %q:", socketPath)) | ||||
| 						return &FinaliseError{Step: fmt.Sprintf("access X11 socket %q", socketPath), Err: err} | ||||
| 					} | ||||
| 				} else { | ||||
| 					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 !errors.Is(err, fs.ErrNotExist) { | ||||
| 				return hlog.WrapErrSuffix(err, | ||||
| 					fmt.Sprintf("cannot access PulseAudio directory %q:", pulseRuntimeDir)) | ||||
| 				return &FinaliseError{Step: fmt.Sprintf("access PulseAudio directory %q", pulseRuntimeDir), Err: err} | ||||
| 			} | ||||
| 			return hlog.WrapErr(ErrPulseSocket, | ||||
| 				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 !errors.Is(err, fs.ErrNotExist) { | ||||
| 				return hlog.WrapErrSuffix(err, | ||||
| 					fmt.Sprintf("cannot access PulseAudio socket %q:", pulseSocket)) | ||||
| 				return &FinaliseError{Step: fmt.Sprintf("access PulseAudio socket %q", pulseSocket), Err: err} | ||||
| 			} | ||||
| 			return hlog.WrapErr(ErrPulseSocket, | ||||
| 				fmt.Sprintf("PulseAudio directory %q found but socket does not exist", pulseRuntimeDir)) | ||||
| 			return newWithMessage(fmt.Sprintf("PulseAudio directory %q found but socket does not exist", pulseRuntimeDir)) | ||||
| 		} else { | ||||
| 			if m := s.Mode(); m&0o006 != 0o006 { | ||||
| 				return hlog.WrapErr(ErrPulseMode, | ||||
| 					fmt.Sprintf("unexpected permissions on %q:", pulseSocket), m) | ||||
| 				return newWithMessage(fmt.Sprintf("unexpected permissions on %q: %s", 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() | ||||
| 
 | ||||
| 		// publish current user's pulse cookie for target user | ||||
| 		if src, err := discoverPulseCookie(sys); err != nil { | ||||
| 			// not fatal | ||||
| 			hlog.Verbose(strings.TrimSpace(err.(*hlog.BaseError).Message())) | ||||
| 		} else { | ||||
| 		var paCookiePath *container.Absolute | ||||
| 		{ | ||||
| 			const paLocateStep = "locate PulseAudio cookie" | ||||
| 
 | ||||
| 			// 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") | ||||
| 			seal.env[pulseCookie] = innerDst.String() | ||||
| 			var payload *[]byte | ||||
| 			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)) | ||||
| 	for k, v := range seal.env { | ||||
| 		if strings.IndexByte(k, '=') != -1 { | ||||
| 			return hlog.WrapErr(syscall.EINVAL, | ||||
| 				fmt.Sprintf("invalid environment variable %s", k)) | ||||
| 			return &FinaliseError{Step: "flatten environment", Err: syscall.EINVAL, | ||||
| 				Msg: fmt.Sprintf("invalid environment variable %s", k)} | ||||
| 		} | ||||
| 		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 | ||||
| } | ||||
| 
 | ||||
| // 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 { | ||||
| 		hlog.PrintBaseError(err, "cannot start container:") | ||||
| 		printMessageError("cannot start container:", err) | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
| 	if err := z.Serve(); err != nil { | ||||
| 		hlog.PrintBaseError(err, "cannot configure container:") | ||||
| 		printMessageError("cannot configure container:", err) | ||||
| 	} | ||||
| 
 | ||||
| 	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{} | ||||
| 
 | ||||
| func (Output) IsVerbose() bool                         { return Load() } | ||||
| func (Output) Verbose(v ...any)                        { Verbose(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) PrintBaseErr(err error, fallback string) { PrintBaseError(err, fallback) } | ||||
| func (Output) Suspend()                                { Suspend() } | ||||
| func (Output) Resume() bool                            { return Resume() } | ||||
| func (Output) BeforeExit()                             { BeforeExit() } | ||||
| func (Output) IsVerbose() bool                  { return Load() } | ||||
| func (Output) Verbose(v ...any)                 { Verbose(v...) } | ||||
| func (Output) Verbosef(format string, v ...any) { Verbosef(format, v...) } | ||||
| func (Output) Suspend()                         { Suspend() } | ||||
| func (Output) Resume() bool                     { return Resume() } | ||||
| func (Output) BeforeExit()                      { BeforeExit() } | ||||
|  | ||||
| @ -4,6 +4,7 @@ import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/fs" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"os/user" | ||||
| @ -51,7 +52,14 @@ const xdgRuntimeDir = "XDG_RUNTIME_DIR" | ||||
| func (s *Std) Paths() hst.Paths { | ||||
| 	s.pathsOnce.Do(func() { | ||||
| 		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() | ||||
| 			s.Exit(1) | ||||
| 		} else { | ||||
| @ -61,6 +69,16 @@ func (s *Std) Paths() hst.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) { | ||||
| 	s.uidOnce.Do(func() { | ||||
| 		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 { | ||||
| 		u.uid, u.err = strconv.Atoi(string(p)) | ||||
| 		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 { | ||||
| 		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) { | ||||
| 		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 | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user