internal/env: relocate from app
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Test / Create distribution (push) Successful in 32s
				
			
		
			
				
	
				Test / Sandbox (push) Successful in 2m8s
				
			
		
			
				
	
				Test / Hakurei (push) Successful in 3m10s
				
			
		
			
				
	
				Test / Hpkg (push) Successful in 4m1s
				
			
		
			
				
	
				Test / Sandbox (race detector) (push) Successful in 4m7s
				
			
		
			
				
	
				Test / Hakurei (race detector) (push) Successful in 4m53s
				
			
		
			
				
	
				Test / Flake checks (push) Successful in 1m27s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Test / Create distribution (push) Successful in 32s
				
			Test / Sandbox (push) Successful in 2m8s
				
			Test / Hakurei (push) Successful in 3m10s
				
			Test / Hpkg (push) Successful in 4m1s
				
			Test / Sandbox (race detector) (push) Successful in 4m7s
				
			Test / Hakurei (race detector) (push) Successful in 4m53s
				
			Test / Flake checks (push) Successful in 1m27s
				
			This package is much cleaner to stub independently, and makes no sense to lump into app. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
		
							parent
							
								
									274686d10d
								
							
						
					
					
						commit
						a52f7038e5
					
				| @ -20,6 +20,7 @@ import ( | ||||
| 	"hakurei.app/internal" | ||||
| 	"hakurei.app/internal/app" | ||||
| 	"hakurei.app/internal/app/state" | ||||
| 	"hakurei.app/internal/env" | ||||
| 	"hakurei.app/message" | ||||
| 	"hakurei.app/system/dbus" | ||||
| ) | ||||
| @ -320,7 +321,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr | ||||
| 		var flagShort bool | ||||
| 		c.NewCommand("ps", "List active instances", func(args []string) error { | ||||
| 			var sc hst.Paths | ||||
| 			app.CopyPaths().Copy(&sc, new(app.Hsu).MustID(nil)) | ||||
| 			env.CopyPaths().Copy(&sc, new(app.Hsu).MustID(nil)) | ||||
| 			printPs(os.Stdout, time.Now().UTC(), state.NewMulti(msg, sc.RunDirPath), flagShort, flagJSON) | ||||
| 			return errSuccess | ||||
| 		}).Flag(&flagShort, "short", command.BoolFlag(false), "Print instance id") | ||||
|  | ||||
| @ -13,6 +13,7 @@ import ( | ||||
| 	"hakurei.app/hst" | ||||
| 	"hakurei.app/internal/app" | ||||
| 	"hakurei.app/internal/app/state" | ||||
| 	"hakurei.app/internal/env" | ||||
| 	"hakurei.app/message" | ||||
| ) | ||||
| 
 | ||||
| @ -83,7 +84,7 @@ func shortIdentifierString(s string) string { | ||||
| func tryIdentifier(msg message.Msg, name string) (config *hst.Config, entry *hst.State) { | ||||
| 	return tryIdentifierEntries(msg, name, func() map[hst.ID]*hst.State { | ||||
| 		var sc hst.Paths | ||||
| 		app.CopyPaths().Copy(&sc, new(app.Hsu).MustID(nil)) | ||||
| 		env.CopyPaths().Copy(&sc, new(app.Hsu).MustID(nil)) | ||||
| 		s := state.NewMulti(msg, sc.RunDirPath) | ||||
| 		if entries, err := state.Join(s); err != nil { | ||||
| 			msg.GetLogger().Printf("cannot join store: %v", err) // not fatal | ||||
|  | ||||
| @ -14,6 +14,7 @@ import ( | ||||
| 	"hakurei.app/internal" | ||||
| 	"hakurei.app/internal/app" | ||||
| 	"hakurei.app/internal/app/state" | ||||
| 	"hakurei.app/internal/env" | ||||
| 	"hakurei.app/message" | ||||
| ) | ||||
| 
 | ||||
| @ -23,7 +24,7 @@ func printShowSystem(output io.Writer, short, flagJSON bool) { | ||||
| 	defer t.MustFlush() | ||||
| 
 | ||||
| 	info := &hst.Info{Version: internal.Version(), User: new(app.Hsu).MustID(nil)} | ||||
| 	app.CopyPaths().Copy(&info.Paths, info.User) | ||||
| 	env.CopyPaths().Copy(&info.Paths, info.User) | ||||
| 
 | ||||
| 	if flagJSON { | ||||
| 		encodeJSON(log.Fatal, output, short, info) | ||||
|  | ||||
| @ -1,59 +0,0 @@ | ||||
| package app | ||||
| 
 | ||||
| import ( | ||||
| 	"strconv" | ||||
| 
 | ||||
| 	"hakurei.app/container/check" | ||||
| 	"hakurei.app/hst" | ||||
| ) | ||||
| 
 | ||||
| // EnvPaths holds paths copied from the environment and is used to create [hst.Paths]. | ||||
| type EnvPaths struct { | ||||
| 	// TempDir is returned by [os.TempDir]. | ||||
| 	TempDir *check.Absolute | ||||
| 	// RuntimePath is copied from $XDG_RUNTIME_DIR. | ||||
| 	RuntimePath *check.Absolute | ||||
| } | ||||
| 
 | ||||
| // Copy expands [EnvPaths] into [hst.Paths]. | ||||
| func (env *EnvPaths) Copy(v *hst.Paths, userid int) { | ||||
| 	if env == nil || env.TempDir == nil || v == nil { | ||||
| 		panic("attempting to use an invalid EnvPaths") | ||||
| 	} | ||||
| 
 | ||||
| 	v.TempDir = env.TempDir | ||||
| 	v.SharePath = env.TempDir.Append("hakurei." + strconv.Itoa(userid)) | ||||
| 
 | ||||
| 	if env.RuntimePath == nil { | ||||
| 		// fall back to path in share since hakurei has no hard XDG dependency | ||||
| 		v.RunDirPath = v.SharePath.Append("run") | ||||
| 		v.RuntimePath = v.RunDirPath.Append("compat") | ||||
| 	} else { | ||||
| 		v.RuntimePath = env.RuntimePath | ||||
| 		v.RunDirPath = env.RuntimePath.Append("hakurei") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // CopyPaths returns a populated [EnvPaths]. | ||||
| func CopyPaths() *EnvPaths { return copyPaths(direct{}) } | ||||
| 
 | ||||
| // copyPaths returns a populated [EnvPaths]. | ||||
| func copyPaths(k syscallDispatcher) *EnvPaths { | ||||
| 	const xdgRuntimeDir = "XDG_RUNTIME_DIR" | ||||
| 
 | ||||
| 	var env EnvPaths | ||||
| 
 | ||||
| 	if tempDir, err := check.NewAbs(k.tempdir()); err != nil { | ||||
| 		k.fatalf("invalid TMPDIR: %v", err) | ||||
| 		panic("unreachable") | ||||
| 	} else { | ||||
| 		env.TempDir = tempDir | ||||
| 	} | ||||
| 
 | ||||
| 	r, _ := k.lookupEnv(xdgRuntimeDir) | ||||
| 	if a, err := check.NewAbs(r); err == nil { | ||||
| 		env.RuntimePath = a | ||||
| 	} | ||||
| 
 | ||||
| 	return &env | ||||
| } | ||||
| @ -8,6 +8,7 @@ import ( | ||||
| 	"hakurei.app/container" | ||||
| 	"hakurei.app/container/check" | ||||
| 	"hakurei.app/hst" | ||||
| 	"hakurei.app/internal/env" | ||||
| 	"hakurei.app/message" | ||||
| 	"hakurei.app/system" | ||||
| 	"hakurei.app/system/acl" | ||||
| @ -58,7 +59,7 @@ type outcomeState struct { | ||||
| 
 | ||||
| 	// Copied from [EnvPaths] per-process. | ||||
| 	sc hst.Paths | ||||
| 	*EnvPaths | ||||
| 	*env.Paths | ||||
| 
 | ||||
| 	// Copied via populateLocal. | ||||
| 	k syscallDispatcher | ||||
| @ -72,7 +73,7 @@ func (s *outcomeState) valid() bool { | ||||
| 		s.Shim.valid() && | ||||
| 		s.ID != nil && | ||||
| 		s.Container != nil && | ||||
| 		s.EnvPaths != nil | ||||
| 		s.Paths != nil | ||||
| } | ||||
| 
 | ||||
| // newOutcomeState returns the address of a new outcomeState with its exported fields populated via syscallDispatcher. | ||||
| @ -82,7 +83,7 @@ func newOutcomeState(k syscallDispatcher, msg message.Msg, id *hst.ID, config *h | ||||
| 		ID:        id, | ||||
| 		Identity:  config.Identity, | ||||
| 		UserID:    hsu.MustID(msg), | ||||
| 		EnvPaths:  copyPaths(k), | ||||
| 		Paths:     env.CopyPathsFunc(k.fatalf, k.tempdir, func(key string) string { v, _ := k.lookupEnv(key); return v }), | ||||
| 		Container: config.Container, | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -4,6 +4,7 @@ import ( | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"hakurei.app/hst" | ||||
| 	"hakurei.app/internal/env" | ||||
| ) | ||||
| 
 | ||||
| func TestOutcomeStateValid(t *testing.T) { | ||||
| @ -16,11 +17,11 @@ func TestOutcomeStateValid(t *testing.T) { | ||||
| 	}{ | ||||
| 		{"nil", nil, false}, | ||||
| 		{"zero", new(outcomeState), false}, | ||||
| 		{"shim", &outcomeState{Shim: &shimParams{PrivPID: -1, Ops: []outcomeOp{}}, Container: new(hst.ContainerConfig), EnvPaths: new(EnvPaths)}, false}, | ||||
| 		{"id", &outcomeState{Shim: &shimParams{PrivPID: 1, Ops: []outcomeOp{}}, Container: new(hst.ContainerConfig), EnvPaths: new(EnvPaths)}, false}, | ||||
| 		{"container", &outcomeState{Shim: &shimParams{PrivPID: 1, Ops: []outcomeOp{}}, ID: new(hst.ID), EnvPaths: new(EnvPaths)}, false}, | ||||
| 		{"shim", &outcomeState{Shim: &shimParams{PrivPID: -1, Ops: []outcomeOp{}}, Container: new(hst.ContainerConfig), Paths: new(env.Paths)}, false}, | ||||
| 		{"id", &outcomeState{Shim: &shimParams{PrivPID: 1, Ops: []outcomeOp{}}, Container: new(hst.ContainerConfig), Paths: new(env.Paths)}, false}, | ||||
| 		{"container", &outcomeState{Shim: &shimParams{PrivPID: 1, Ops: []outcomeOp{}}, ID: new(hst.ID), Paths: new(env.Paths)}, false}, | ||||
| 		{"envpaths", &outcomeState{Shim: &shimParams{PrivPID: 1, Ops: []outcomeOp{}}, ID: new(hst.ID), Container: new(hst.ContainerConfig)}, false}, | ||||
| 		{"valid", &outcomeState{Shim: &shimParams{PrivPID: 1, Ops: []outcomeOp{}}, ID: new(hst.ID), Container: new(hst.ContainerConfig), EnvPaths: new(EnvPaths)}, true}, | ||||
| 		{"valid", &outcomeState{Shim: &shimParams{PrivPID: 1, Ops: []outcomeOp{}}, ID: new(hst.ID), Container: new(hst.ContainerConfig), Paths: new(env.Paths)}, true}, | ||||
| 	} | ||||
| 	for _, tc := range testCases { | ||||
| 		t.Run(tc.name, func(t *testing.T) { | ||||
|  | ||||
| @ -14,6 +14,7 @@ import ( | ||||
| 	"hakurei.app/container/seccomp" | ||||
| 	"hakurei.app/container/stub" | ||||
| 	"hakurei.app/hst" | ||||
| 	"hakurei.app/internal/env" | ||||
| ) | ||||
| 
 | ||||
| func TestShimEntrypoint(t *testing.T) { | ||||
| @ -128,7 +129,7 @@ func TestShimEntrypoint(t *testing.T) { | ||||
| 				Container: hst.Template().Container, | ||||
| 				Mapuid:    1000, | ||||
| 				Mapgid:    100, | ||||
| 				EnvPaths:  &EnvPaths{TempDir: fhs.AbsTmp, RuntimePath: fhs.AbsRunUser.Append("1000")}, | ||||
| 				Paths:     &env.Paths{TempDir: fhs.AbsTmp, RuntimePath: fhs.AbsRunUser.Append("1000")}, | ||||
| 			}, nil}, nil, nil), | ||||
| 			call("swapVerbose", stub.ExpectArgs{true}, false, nil), | ||||
| 			call("verbosef", stub.ExpectArgs{"process share directory at %q, runtime directory at %q", []any{m("/tmp/hakurei.10"), m("/run/user/1000/hakurei")}}, nil, nil), | ||||
|  | ||||
							
								
								
									
										66
									
								
								internal/env/env.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								internal/env/env.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,66 @@ | ||||
| // Package env provides the [Paths] struct for efficiently building paths from the environment. | ||||
| package env | ||||
| 
 | ||||
| import ( | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"strconv" | ||||
| 
 | ||||
| 	"hakurei.app/container/check" | ||||
| 	"hakurei.app/hst" | ||||
| ) | ||||
| 
 | ||||
| // Paths holds paths copied from the environment and is used to create [hst.Paths]. | ||||
| type Paths struct { | ||||
| 	// TempDir is returned by [os.TempDir]. | ||||
| 	TempDir *check.Absolute | ||||
| 	// RuntimePath is copied from $XDG_RUNTIME_DIR. | ||||
| 	RuntimePath *check.Absolute | ||||
| } | ||||
| 
 | ||||
| // Copy expands [Paths] into [hst.Paths]. | ||||
| func (env *Paths) Copy(v *hst.Paths, userid int) { | ||||
| 	if env == nil || env.TempDir == nil || v == nil { | ||||
| 		panic("attempting to use an invalid Paths") | ||||
| 	} | ||||
| 
 | ||||
| 	v.TempDir = env.TempDir | ||||
| 	v.SharePath = env.TempDir.Append("hakurei." + strconv.Itoa(userid)) | ||||
| 
 | ||||
| 	if env.RuntimePath == nil { | ||||
| 		// fall back to path in share since hakurei has no hard XDG dependency | ||||
| 		v.RunDirPath = v.SharePath.Append("run") | ||||
| 		v.RuntimePath = v.RunDirPath.Append("compat") | ||||
| 	} else { | ||||
| 		v.RuntimePath = env.RuntimePath | ||||
| 		v.RunDirPath = env.RuntimePath.Append("hakurei") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // CopyPaths returns a populated [Paths]. | ||||
| func CopyPaths() *Paths { return CopyPathsFunc(log.Fatalf, os.TempDir, os.Getenv) } | ||||
| 
 | ||||
| // CopyPathsFunc returns a populated [Paths], | ||||
| // using the provided [log.Fatalf], [os.TempDir], [os.Getenv] functions. | ||||
| func CopyPathsFunc( | ||||
| 	fatalf func(format string, v ...any), | ||||
| 	tempdir func() string, | ||||
| 	getenv func(key string) string, | ||||
| ) *Paths { | ||||
| 	const xdgRuntimeDir = "XDG_RUNTIME_DIR" | ||||
| 
 | ||||
| 	var env Paths | ||||
| 
 | ||||
| 	if tempDir, err := check.NewAbs(tempdir()); err != nil { | ||||
| 		fatalf("invalid TMPDIR: %v", err) | ||||
| 		panic("unreachable") | ||||
| 	} else { | ||||
| 		env.TempDir = tempDir | ||||
| 	} | ||||
| 
 | ||||
| 	if a, err := check.NewAbs(getenv(xdgRuntimeDir)); err == nil { | ||||
| 		env.RuntimePath = a | ||||
| 	} | ||||
| 
 | ||||
| 	return &env | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| package app | ||||
| package env_test | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| @ -10,26 +10,27 @@ import ( | ||||
| 	"hakurei.app/container/fhs" | ||||
| 	"hakurei.app/container/stub" | ||||
| 	"hakurei.app/hst" | ||||
| 	"hakurei.app/internal/env" | ||||
| ) | ||||
| 
 | ||||
| func TestEnvPaths(t *testing.T) { | ||||
| func TestPaths(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 
 | ||||
| 	testCases := []struct { | ||||
| 		name string | ||||
| 		env  *EnvPaths | ||||
| 		env  *env.Paths | ||||
| 		want hst.Paths | ||||
| 
 | ||||
| 		wantPanic string | ||||
| 	}{ | ||||
| 		{"nil", nil, hst.Paths{}, "attempting to use an invalid EnvPaths"}, | ||||
| 		{"zero", new(EnvPaths), hst.Paths{}, "attempting to use an invalid EnvPaths"}, | ||||
| 		{"nil", nil, hst.Paths{}, "attempting to use an invalid Paths"}, | ||||
| 		{"zero", new(env.Paths), hst.Paths{}, "attempting to use an invalid Paths"}, | ||||
| 
 | ||||
| 		{"nil tempdir", &EnvPaths{ | ||||
| 		{"nil tempdir", &env.Paths{ | ||||
| 			RuntimePath: fhs.AbsTmp, | ||||
| 		}, hst.Paths{}, "attempting to use an invalid EnvPaths"}, | ||||
| 		}, hst.Paths{}, "attempting to use an invalid Paths"}, | ||||
| 
 | ||||
| 		{"nil runtime", &EnvPaths{ | ||||
| 		{"nil runtime", &env.Paths{ | ||||
| 			TempDir: fhs.AbsTmp, | ||||
| 		}, hst.Paths{ | ||||
| 			TempDir:     fhs.AbsTmp, | ||||
| @ -38,7 +39,7 @@ func TestEnvPaths(t *testing.T) { | ||||
| 			RunDirPath:  fhs.AbsTmp.Append("hakurei.3735928559/run"), | ||||
| 		}, ""}, | ||||
| 
 | ||||
| 		{"full", &EnvPaths{ | ||||
| 		{"full", &env.Paths{ | ||||
| 			TempDir:     fhs.AbsTmp, | ||||
| 			RuntimePath: fhs.AbsRunUser.Append("1000"), | ||||
| 		}, hst.Paths{ | ||||
| @ -76,16 +77,16 @@ func TestCopyPaths(t *testing.T) { | ||||
| 		env   map[string]string | ||||
| 		tmp   string | ||||
| 		fatal string | ||||
| 		want  EnvPaths | ||||
| 		want  env.Paths | ||||
| 	}{ | ||||
| 		{"invalid tempdir", nil, "\x00", | ||||
| 			"invalid TMPDIR: path \"\\x00\" is not absolute", EnvPaths{}}, | ||||
| 			"invalid TMPDIR: path \"\\x00\" is not absolute", env.Paths{}}, | ||||
| 		{"empty environment", make(map[string]string), container.Nonexistent, | ||||
| 			"", EnvPaths{TempDir: check.MustAbs(container.Nonexistent)}}, | ||||
| 			"", env.Paths{TempDir: check.MustAbs(container.Nonexistent)}}, | ||||
| 		{"invalid XDG_RUNTIME_DIR", map[string]string{"XDG_RUNTIME_DIR": "\x00"}, container.Nonexistent, | ||||
| 			"", EnvPaths{TempDir: check.MustAbs(container.Nonexistent)}}, | ||||
| 			"", env.Paths{TempDir: check.MustAbs(container.Nonexistent)}}, | ||||
| 		{"full", map[string]string{"XDG_RUNTIME_DIR": "/\x00"}, container.Nonexistent, | ||||
| 			"", EnvPaths{TempDir: check.MustAbs(container.Nonexistent), RuntimePath: check.MustAbs("/\x00")}}, | ||||
| 			"", env.Paths{TempDir: check.MustAbs(container.Nonexistent), RuntimePath: check.MustAbs("/\x00")}}, | ||||
| 	} | ||||
| 	for _, tc := range testCases { | ||||
| 		t.Run(tc.name, func(t *testing.T) { | ||||
| @ -94,8 +95,16 @@ func TestCopyPaths(t *testing.T) { | ||||
| 				defer stub.HandleExit(t) | ||||
| 			} | ||||
| 
 | ||||
| 			k := copyPathsDispatcher{t: t, env: tc.env, tmp: tc.tmp, expectsFatal: tc.fatal} | ||||
| 			got := copyPaths(k) | ||||
| 			got := env.CopyPathsFunc(func(format string, v ...any) { | ||||
| 				if tc.fatal == "" { | ||||
| 					t.Fatalf("unexpected call to fatalf: format = %q, v = %#v", format, v) | ||||
| 				} | ||||
| 
 | ||||
| 				if got := fmt.Sprintf(format, v...); got != tc.fatal { | ||||
| 					t.Fatalf("fatalf: %q, want %q", got, tc.fatal) | ||||
| 				} | ||||
| 				panic(stub.PanicExit) | ||||
| 			}, func() string { return tc.tmp }, func(key string) string { return tc.env[key] }) | ||||
| 
 | ||||
| 			if tc.fatal != "" { | ||||
| 				t.Fatalf("copyPaths: expected fatal %q", tc.fatal) | ||||
| @ -107,31 +116,3 @@ func TestCopyPaths(t *testing.T) { | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // copyPathsDispatcher implements enough of syscallDispatcher for all copyPaths code paths. | ||||
| type copyPathsDispatcher struct { | ||||
| 	env map[string]string | ||||
| 	tmp string | ||||
| 
 | ||||
| 	// must be checked at the conclusion of the test | ||||
| 	expectsFatal string | ||||
| 
 | ||||
| 	t *testing.T | ||||
| 	panicDispatcher | ||||
| } | ||||
| 
 | ||||
| func (k copyPathsDispatcher) tempdir() string { return k.tmp } | ||||
| func (k copyPathsDispatcher) lookupEnv(key string) (value string, ok bool) { | ||||
| 	value, ok = k.env[key] | ||||
| 	return | ||||
| } | ||||
| func (k copyPathsDispatcher) fatalf(format string, v ...any) { | ||||
| 	if k.expectsFatal == "" { | ||||
| 		k.t.Fatalf("unexpected call to fatalf: format = %q, v = %#v", format, v) | ||||
| 	} | ||||
| 
 | ||||
| 	if got := fmt.Sprintf(format, v...); got != k.expectsFatal { | ||||
| 		k.t.Fatalf("fatalf: %q, want %q", got, k.expectsFatal) | ||||
| 	} | ||||
| 	panic(stub.PanicExit) | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user