container: wrap container init start errors
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Test / Create distribution (push) Successful in 35s
				
			
		
			
				
	
				Test / Sandbox (push) Successful in 1m59s
				
			
		
			
				
	
				Test / Hakurei (push) Successful in 3m20s
				
			
		
			
				
	
				Test / Sandbox (race detector) (push) Successful in 4m26s
				
			
		
			
				
	
				Test / Hpkg (push) Successful in 3m47s
				
			
		
			
				
	
				Test / Hakurei (race detector) (push) Successful in 5m21s
				
			
		
			
				
	
				Test / Flake checks (push) Successful in 1m35s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Test / Create distribution (push) Successful in 35s
				
			Test / Sandbox (push) Successful in 1m59s
				
			Test / Hakurei (push) Successful in 3m20s
				
			Test / Sandbox (race detector) (push) Successful in 4m26s
				
			Test / Hpkg (push) Successful in 3m47s
				
			Test / Hakurei (race detector) (push) Successful in 5m21s
				
			Test / Flake checks (push) Successful in 1m35s
				
			This helps indicate the exact origin and nature of the error. This eliminates generic WrapErr from container. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
		
							parent
							
								
									f5abce9df5
								
							
						
					
					
						commit
						712cfc06d7
					
				| @ -99,6 +99,39 @@ type ( | |||||||
| 	} | 	} | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | // A StartError contains additional information on a container startup failure. | ||||||
|  | type StartError struct { | ||||||
|  | 	// Fatal suggests whether this error should be considered fatal for the entire program. | ||||||
|  | 	Fatal bool | ||||||
|  | 	// Step refers to the part of the setup this error is returned from. | ||||||
|  | 	Step string | ||||||
|  | 	// Err is the underlying error. | ||||||
|  | 	Err error | ||||||
|  | 	// Origin is whether this error originated from the [Container.Start] method. | ||||||
|  | 	Origin bool | ||||||
|  | 	// Passthrough is whether the Error method is passed through to Err. | ||||||
|  | 	Passthrough bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *StartError) Unwrap() error { return e.Err } | ||||||
|  | func (e *StartError) Error() string { | ||||||
|  | 	if e.Passthrough { | ||||||
|  | 		return e.Err.Error() | ||||||
|  | 	} | ||||||
|  | 	if e.Origin { | ||||||
|  | 		return e.Step | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	{ | ||||||
|  | 		var syscallError *os.SyscallError | ||||||
|  | 		if errors.As(e.Err, &syscallError) && syscallError != nil { | ||||||
|  | 			return e.Step + " " + syscallError.Error() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return e.Step + ": " + e.Err.Error() | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Start starts the container init. The init process blocks until Serve is called. | // Start starts the container init. The init process blocks until Serve is called. | ||||||
| func (p *Container) Start() error { | func (p *Container) Start() error { | ||||||
| 	if p.cmd != nil { | 	if p.cmd != nil { | ||||||
| @ -167,8 +200,7 @@ func (p *Container) Start() error { | |||||||
| 
 | 
 | ||||||
| 	// place setup pipe before user supplied extra files, this is later restored by init | 	// place setup pipe before user supplied extra files, this is later restored by init | ||||||
| 	if fd, e, err := Setup(&p.cmd.ExtraFiles); err != nil { | 	if fd, e, err := Setup(&p.cmd.ExtraFiles); err != nil { | ||||||
| 		return wrapErrSuffix(err, | 		return &StartError{true, "set up params stream", err, false, false} | ||||||
| 			"cannot create shim setup pipe:") |  | ||||||
| 	} else { | 	} else { | ||||||
| 		p.setup = e | 		p.setup = e | ||||||
| 		p.cmd.Env = []string{setupEnv + "=" + strconv.Itoa(fd)} | 		p.cmd.Env = []string{setupEnv + "=" + strconv.Itoa(fd)} | ||||||
| @ -183,8 +215,7 @@ func (p *Container) Start() error { | |||||||
| 		done <- func() error { // setup depending on per-thread state must happen here | 		done <- func() error { // setup depending on per-thread state must happen here | ||||||
| 			// PR_SET_NO_NEW_PRIVS: depends on per-thread state but acts on all processes created from that thread | 			// PR_SET_NO_NEW_PRIVS: depends on per-thread state but acts on all processes created from that thread | ||||||
| 			if err := SetNoNewPrivs(); err != nil { | 			if err := SetNoNewPrivs(); err != nil { | ||||||
| 				return wrapErrSuffix(err, | 				return &StartError{true, "prctl(PR_SET_NO_NEW_PRIVS)", err, false, false} | ||||||
| 					"prctl(PR_SET_NO_NEW_PRIVS):") |  | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			// landlock: depends on per-thread state but acts on a process group | 			// landlock: depends on per-thread state but acts on a process group | ||||||
| @ -200,28 +231,24 @@ func (p *Container) Start() error { | |||||||
| 						// already covered by namespaces (pid) | 						// already covered by namespaces (pid) | ||||||
| 						goto landlockOut | 						goto landlockOut | ||||||
| 					} | 					} | ||||||
| 					return wrapErrSuffix(err, | 					return &StartError{false, "get landlock ABI", err, false, false} | ||||||
| 						"landlock does not appear to be enabled:") |  | ||||||
| 				} else if abi < 6 { | 				} else if abi < 6 { | ||||||
| 					if p.HostAbstract { | 					if p.HostAbstract { | ||||||
| 						// see above comment | 						// see above comment | ||||||
| 						goto landlockOut | 						goto landlockOut | ||||||
| 					} | 					} | ||||||
| 					return msg.WrapErr(ENOSYS, | 					return &StartError{false, "kernel version too old for LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET", ENOSYS, true, false} | ||||||
| 						"kernel version too old for LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET") |  | ||||||
| 				} else { | 				} else { | ||||||
| 					msg.Verbosef("landlock abi version %d", abi) | 					msg.Verbosef("landlock abi version %d", abi) | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				if rulesetFd, err := rulesetAttr.Create(0); err != nil { | 				if rulesetFd, err := rulesetAttr.Create(0); err != nil { | ||||||
| 					return wrapErrSuffix(err, | 					return &StartError{true, "create landlock ruleset", err, false, false} | ||||||
| 						"cannot create landlock ruleset:") |  | ||||||
| 				} else { | 				} else { | ||||||
| 					msg.Verbosef("enforcing landlock ruleset %s", rulesetAttr) | 					msg.Verbosef("enforcing landlock ruleset %s", rulesetAttr) | ||||||
| 					if err = LandlockRestrictSelf(rulesetFd, 0); err != nil { | 					if err = LandlockRestrictSelf(rulesetFd, 0); err != nil { | ||||||
| 						_ = Close(rulesetFd) | 						_ = Close(rulesetFd) | ||||||
| 						return wrapErrSuffix(err, | 						return &StartError{true, "enforce landlock ruleset", err, false, false} | ||||||
| 							"cannot enforce landlock ruleset:") |  | ||||||
| 					} | 					} | ||||||
| 					if err = Close(rulesetFd); err != nil { | 					if err = Close(rulesetFd); err != nil { | ||||||
| 						msg.Verbosef("cannot close landlock ruleset: %v", err) | 						msg.Verbosef("cannot close landlock ruleset: %v", err) | ||||||
| @ -234,7 +261,7 @@ func (p *Container) Start() error { | |||||||
| 
 | 
 | ||||||
| 			msg.Verbose("starting container init") | 			msg.Verbose("starting container init") | ||||||
| 			if err := p.cmd.Start(); err != nil { | 			if err := p.cmd.Start(); err != nil { | ||||||
| 				return msg.WrapErr(err, err.Error()) | 				return &StartError{false, "start container init", err, false, true} | ||||||
| 			} | 			} | ||||||
| 			return nil | 			return nil | ||||||
| 		}() | 		}() | ||||||
| @ -257,7 +284,7 @@ func (p *Container) Serve() error { | |||||||
| 
 | 
 | ||||||
| 	if p.Path == nil { | 	if p.Path == nil { | ||||||
| 		p.cancel() | 		p.cancel() | ||||||
| 		return msg.WrapErr(EINVAL, "invalid executable pathname") | 		return &StartError{false, "invalid executable pathname", EINVAL, true, false} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// do not transmit nil | 	// do not transmit nil | ||||||
|  | |||||||
| @ -10,6 +10,7 @@ import ( | |||||||
| 	"os" | 	"os" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 	"os/signal" | 	"os/signal" | ||||||
|  | 	"reflect" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"syscall" | 	"syscall" | ||||||
| @ -26,6 +27,100 @@ import ( | |||||||
| 	"hakurei.app/ldd" | 	"hakurei.app/ldd" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | func TestStartError(t *testing.T) { | ||||||
|  | 	testCases := []struct { | ||||||
|  | 		name string | ||||||
|  | 		err  error | ||||||
|  | 		s    string | ||||||
|  | 		is   error | ||||||
|  | 		isF  error | ||||||
|  | 	}{ | ||||||
|  | 		{"params env", &container.StartError{ | ||||||
|  | 			Fatal: true, | ||||||
|  | 			Step:  "set up params stream", | ||||||
|  | 			Err:   container.ErrReceiveEnv, | ||||||
|  | 		}, | ||||||
|  | 			"set up params stream: environment variable not set", | ||||||
|  | 			container.ErrReceiveEnv, syscall.EBADF}, | ||||||
|  | 
 | ||||||
|  | 		{"params", &container.StartError{ | ||||||
|  | 			Fatal: true, | ||||||
|  | 			Step:  "set up params stream", | ||||||
|  | 			Err:   &os.SyscallError{Syscall: "pipe2", Err: syscall.EBADF}, | ||||||
|  | 		}, | ||||||
|  | 			"set up params stream pipe2: bad file descriptor", | ||||||
|  | 			syscall.EBADF, os.ErrInvalid}, | ||||||
|  | 
 | ||||||
|  | 		{"PR_SET_NO_NEW_PRIVS", &container.StartError{ | ||||||
|  | 			Fatal: true, | ||||||
|  | 			Step:  "prctl(PR_SET_NO_NEW_PRIVS)", | ||||||
|  | 			Err:   syscall.EPERM, | ||||||
|  | 		}, | ||||||
|  | 			"prctl(PR_SET_NO_NEW_PRIVS): operation not permitted", | ||||||
|  | 			syscall.EPERM, syscall.EACCES}, | ||||||
|  | 
 | ||||||
|  | 		{"landlock abi", &container.StartError{ | ||||||
|  | 			Step: "get landlock ABI", | ||||||
|  | 			Err:  syscall.ENOSYS, | ||||||
|  | 		}, | ||||||
|  | 			"get landlock ABI: function not implemented", | ||||||
|  | 			syscall.ENOSYS, syscall.ENOEXEC}, | ||||||
|  | 
 | ||||||
|  | 		{"landlock old", &container.StartError{ | ||||||
|  | 			Step:   "kernel version too old for LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET", | ||||||
|  | 			Err:    syscall.ENOSYS, | ||||||
|  | 			Origin: true, | ||||||
|  | 		}, | ||||||
|  | 			"kernel version too old for LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET", | ||||||
|  | 			syscall.ENOSYS, syscall.ENOSPC}, | ||||||
|  | 
 | ||||||
|  | 		{"landlock create", &container.StartError{ | ||||||
|  | 			Fatal: true, | ||||||
|  | 			Step:  "create landlock ruleset", | ||||||
|  | 			Err:   syscall.EBADFD, | ||||||
|  | 		}, | ||||||
|  | 			"create landlock ruleset: file descriptor in bad state", | ||||||
|  | 			syscall.EBADFD, syscall.EBADF}, | ||||||
|  | 
 | ||||||
|  | 		{"landlock enforce", &container.StartError{ | ||||||
|  | 			Fatal: true, | ||||||
|  | 			Step:  "enforce landlock ruleset", | ||||||
|  | 			Err:   syscall.ENOTRECOVERABLE, | ||||||
|  | 		}, | ||||||
|  | 			"enforce landlock ruleset: state not recoverable", | ||||||
|  | 			syscall.ENOTRECOVERABLE, syscall.ETIMEDOUT}, | ||||||
|  | 
 | ||||||
|  | 		{"start", &container.StartError{ | ||||||
|  | 			Step: "start container init", | ||||||
|  | 			Err: &os.PathError{ | ||||||
|  | 				Op:   "fork/exec", | ||||||
|  | 				Path: "/proc/nonexistent", | ||||||
|  | 				Err:  syscall.ENOENT, | ||||||
|  | 			}, Passthrough: true, | ||||||
|  | 		}, | ||||||
|  | 			"fork/exec /proc/nonexistent: no such file or directory", | ||||||
|  | 			syscall.ENOENT, syscall.ENOSYS}, | ||||||
|  | 	} | ||||||
|  | 	for _, tc := range testCases { | ||||||
|  | 		t.Run(tc.name, func(t *testing.T) { | ||||||
|  | 			t.Run("error", func(t *testing.T) { | ||||||
|  | 				if got := tc.err.Error(); got != tc.s { | ||||||
|  | 					t.Errorf("Error: %q, want %q", got, tc.s) | ||||||
|  | 				} | ||||||
|  | 			}) | ||||||
|  | 
 | ||||||
|  | 			t.Run("is", func(t *testing.T) { | ||||||
|  | 				if !errors.Is(tc.err, tc.is) { | ||||||
|  | 					t.Error("Is: unexpected false") | ||||||
|  | 				} | ||||||
|  | 				if errors.Is(tc.err, tc.isF) { | ||||||
|  | 					t.Errorf("Is: unexpected true") | ||||||
|  | 				} | ||||||
|  | 			}) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| const ( | const ( | ||||||
| 	ignore  = "\x00" | 	ignore  = "\x00" | ||||||
| 	ignoreV = -1 | 	ignoreV = -1 | ||||||
| @ -217,9 +312,11 @@ func TestContainer(t *testing.T) { | |||||||
| 	t.Run("cancel", testContainerCancel(nil, func(t *testing.T, c *container.Container) { | 	t.Run("cancel", testContainerCancel(nil, func(t *testing.T, c *container.Container) { | ||||||
| 		wantErr := context.Canceled | 		wantErr := context.Canceled | ||||||
| 		wantExitCode := 0 | 		wantExitCode := 0 | ||||||
| 		if err := c.Wait(); !errors.Is(err, wantErr) { | 		if err := c.Wait(); !reflect.DeepEqual(err, wantErr) { | ||||||
| 			container.GetOutput().PrintBaseErr(err, "wait:") | 			if m, ok := container.InternalMessageFromError(err); ok { | ||||||
| 			t.Errorf("Wait: error = %v, want %v", err, wantErr) | 				t.Error(m) | ||||||
|  | 			} | ||||||
|  | 			t.Errorf("Wait: error = %#v, want %#v", err, wantErr) | ||||||
| 		} | 		} | ||||||
| 		if ps := c.ProcessState(); ps == nil { | 		if ps := c.ProcessState(); ps == nil { | ||||||
| 			t.Errorf("ProcessState unexpectedly returned nil") | 			t.Errorf("ProcessState unexpectedly returned nil") | ||||||
| @ -233,7 +330,9 @@ func TestContainer(t *testing.T) { | |||||||
| 	}, func(t *testing.T, c *container.Container) { | 	}, func(t *testing.T, c *container.Container) { | ||||||
| 		var exitError *exec.ExitError | 		var exitError *exec.ExitError | ||||||
| 		if err := c.Wait(); !errors.As(err, &exitError) { | 		if err := c.Wait(); !errors.As(err, &exitError) { | ||||||
| 			container.GetOutput().PrintBaseErr(err, "wait:") | 			if m, ok := container.InternalMessageFromError(err); ok { | ||||||
|  | 				t.Error(m) | ||||||
|  | 			} | ||||||
| 			t.Errorf("Wait: error = %v", err) | 			t.Errorf("Wait: error = %v", err) | ||||||
| 		} | 		} | ||||||
| 		if code := exitError.ExitCode(); code != blockExitCodeInterrupt { | 		if code := exitError.ExitCode(); code != blockExitCodeInterrupt { | ||||||
| @ -313,17 +412,26 @@ func TestContainer(t *testing.T) { | |||||||
| 
 | 
 | ||||||
| 			if err := c.Start(); err != nil { | 			if err := c.Start(); err != nil { | ||||||
| 				_, _ = output.WriteTo(os.Stdout) | 				_, _ = output.WriteTo(os.Stdout) | ||||||
| 				container.GetOutput().PrintBaseErr(err, "start:") | 				if m, ok := container.InternalMessageFromError(err); ok { | ||||||
| 				t.Fatalf("cannot start container: %v", err) | 					t.Fatal(m) | ||||||
|  | 				} else { | ||||||
|  | 					t.Fatalf("cannot start container: %v", err) | ||||||
|  | 				} | ||||||
| 			} else if err = c.Serve(); err != nil { | 			} else if err = c.Serve(); err != nil { | ||||||
| 				_, _ = output.WriteTo(os.Stdout) | 				_, _ = output.WriteTo(os.Stdout) | ||||||
| 				container.GetOutput().PrintBaseErr(err, "serve:") | 				if m, ok := container.InternalMessageFromError(err); ok { | ||||||
| 				t.Errorf("cannot serve setup params: %v", err) | 					t.Error(m) | ||||||
|  | 				} else { | ||||||
|  | 					t.Errorf("cannot serve setup params: %v", err) | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 			if err := c.Wait(); err != nil { | 			if err := c.Wait(); err != nil { | ||||||
| 				_, _ = output.WriteTo(os.Stdout) | 				_, _ = output.WriteTo(os.Stdout) | ||||||
| 				container.GetOutput().PrintBaseErr(err, "wait:") | 				if m, ok := container.InternalMessageFromError(err); ok { | ||||||
| 				t.Fatalf("wait: %v", err) | 					t.Fatal(m) | ||||||
|  | 				} else { | ||||||
|  | 					t.Fatalf("wait: %v", err) | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @ -376,11 +484,17 @@ func testContainerCancel( | |||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if err := c.Start(); err != nil { | 		if err := c.Start(); err != nil { | ||||||
| 			container.GetOutput().PrintBaseErr(err, "start:") | 			if m, ok := container.InternalMessageFromError(err); ok { | ||||||
| 			t.Fatalf("cannot start container: %v", err) | 				t.Fatal(m) | ||||||
|  | 			} else { | ||||||
|  | 				t.Fatalf("cannot start container: %v", err) | ||||||
|  | 			} | ||||||
| 		} else if err = c.Serve(); err != nil { | 		} else if err = c.Serve(); err != nil { | ||||||
| 			container.GetOutput().PrintBaseErr(err, "serve:") | 			if m, ok := container.InternalMessageFromError(err); ok { | ||||||
| 			t.Errorf("cannot serve setup params: %v", err) | 				t.Error(m) | ||||||
|  | 			} else { | ||||||
|  | 				t.Errorf("cannot serve setup params: %v", err) | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 		<-ready | 		<-ready | ||||||
| 		cancel() | 		cancel() | ||||||
|  | |||||||
| @ -138,8 +138,6 @@ type syscallDispatcher interface { | |||||||
| 	resume() bool | 	resume() bool | ||||||
| 	// beforeExit provides [Msg.BeforeExit]. | 	// beforeExit provides [Msg.BeforeExit]. | ||||||
| 	beforeExit() | 	beforeExit() | ||||||
| 	// printBaseErr provides [Msg.PrintBaseErr]. |  | ||||||
| 	printBaseErr(err error, fallback string) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // direct implements syscallDispatcher on the current kernel. | // direct implements syscallDispatcher on the current kernel. | ||||||
| @ -234,12 +232,11 @@ func (direct) wait4(pid int, wstatus *syscall.WaitStatus, options int, rusage *s | |||||||
| 	return syscall.Wait4(pid, wstatus, options, rusage) | 	return syscall.Wait4(pid, wstatus, options, rusage) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (direct) printf(format string, v ...any)          { log.Printf(format, v...) } | func (direct) printf(format string, v ...any)   { log.Printf(format, v...) } | ||||||
| func (direct) fatal(v ...any)                          { log.Fatal(v...) } | func (direct) fatal(v ...any)                   { log.Fatal(v...) } | ||||||
| func (direct) fatalf(format string, v ...any)          { log.Fatalf(format, v...) } | func (direct) fatalf(format string, v ...any)   { log.Fatalf(format, v...) } | ||||||
| func (direct) verbose(v ...any)                        { msg.Verbose(v...) } | func (direct) verbose(v ...any)                 { msg.Verbose(v...) } | ||||||
| func (direct) verbosef(format string, v ...any)        { msg.Verbosef(format, v...) } | func (direct) verbosef(format string, v ...any) { msg.Verbosef(format, v...) } | ||||||
| func (direct) suspend()                                { msg.Suspend() } | func (direct) suspend()                         { msg.Suspend() } | ||||||
| func (direct) resume() bool                            { return msg.Resume() } | func (direct) resume() bool                     { return msg.Resume() } | ||||||
| func (direct) beforeExit()                             { msg.BeforeExit() } | func (direct) beforeExit()                      { msg.BeforeExit() } | ||||||
| func (direct) printBaseErr(err error, fallback string) { msg.PrintBaseErr(err, fallback) } |  | ||||||
|  | |||||||
| @ -162,3 +162,6 @@ func TestErrnoFallback(t *testing.T) { | |||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // InternalMessageFromError exports messageFromError for other tests. | ||||||
|  | func InternalMessageFromError(err error) (string, bool) { return messageFromError(err) } | ||||||
|  | |||||||
| @ -116,7 +116,7 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV | |||||||
| 		if errors.Is(err, EBADF) { | 		if errors.Is(err, EBADF) { | ||||||
| 			k.fatal("invalid setup descriptor") | 			k.fatal("invalid setup descriptor") | ||||||
| 		} | 		} | ||||||
| 		if errors.Is(err, ErrNotSet) { | 		if errors.Is(err, ErrReceiveEnv) { | ||||||
| 			k.fatal("HAKUREI_SETUP not set") | 			k.fatal("HAKUREI_SETUP not set") | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| @ -187,10 +187,7 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV | |||||||
| 			if m, ok := messageFromError(err); ok { | 			if m, ok := messageFromError(err); ok { | ||||||
| 				k.fatal(m) | 				k.fatal(m) | ||||||
| 			} else { | 			} else { | ||||||
| 				k.printBaseErr(err, | 				k.fatalf("cannot prepare op at index %d: %v", i, err) | ||||||
| 					fmt.Sprintf("cannot prepare op at index %d:", i)) |  | ||||||
| 				k.beforeExit() |  | ||||||
| 				k.exit(1) |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @ -231,10 +228,7 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV | |||||||
| 			if m, ok := messageFromError(err); ok { | 			if m, ok := messageFromError(err); ok { | ||||||
| 				k.fatal(m) | 				k.fatal(m) | ||||||
| 			} else { | 			} else { | ||||||
| 				k.printBaseErr(err, | 				k.fatalf("cannot apply op at index %d: %v", i, err) | ||||||
| 					fmt.Sprintf("cannot apply op at index %d:", i)) |  | ||||||
| 				k.beforeExit() |  | ||||||
| 				k.exit(1) |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -51,7 +51,7 @@ func TestInitEntrypoint(t *testing.T) { | |||||||
| 				{"lockOSThread", expectArgs{}, nil, nil}, | 				{"lockOSThread", expectArgs{}, nil, nil}, | ||||||
| 				{"getpid", expectArgs{}, 1, nil}, | 				{"getpid", expectArgs{}, 1, nil}, | ||||||
| 				{"setPtracer", expectArgs{uintptr(0)}, nil, nil}, | 				{"setPtracer", expectArgs{uintptr(0)}, nil, nil}, | ||||||
| 				{"receive", expectArgs{"HAKUREI_SETUP", new(initParams), new(uintptr)}, nil, ErrNotSet}, | 				{"receive", expectArgs{"HAKUREI_SETUP", new(initParams), new(uintptr)}, nil, ErrReceiveEnv}, | ||||||
| 				{"fatal", expectArgs{[]any{"HAKUREI_SETUP not set"}}, nil, nil}, | 				{"fatal", expectArgs{[]any{"HAKUREI_SETUP not set"}}, nil, nil}, | ||||||
| 			}, | 			}, | ||||||
| 		}, nil}, | 		}, nil}, | ||||||
| @ -410,9 +410,7 @@ func TestInitEntrypoint(t *testing.T) { | |||||||
| 				{"mount", expectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil}, | 				{"mount", expectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil}, | ||||||
| 				/* begin early */ | 				/* begin early */ | ||||||
| 				{"evalSymlinks", expectArgs{"/"}, "/", errUnique}, | 				{"evalSymlinks", expectArgs{"/"}, "/", errUnique}, | ||||||
| 				{"printBaseErr", expectArgs{errUnique, "cannot prepare op at index 0:"}, nil, nil}, | 				{"fatalf", expectArgs{"cannot prepare op at index %d: %v", []any{0, errUnique}}, nil, nil}, | ||||||
| 				{"beforeExit", expectArgs{}, nil, nil}, |  | ||||||
| 				{"exit", expectArgs{1}, nil, nil}, |  | ||||||
| 				/* end early */ | 				/* end early */ | ||||||
| 			}, | 			}, | ||||||
| 		}, nil}, | 		}, nil}, | ||||||
| @ -798,9 +796,7 @@ func TestInitEntrypoint(t *testing.T) { | |||||||
| 				{"verbosef", expectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil}, | 				{"verbosef", expectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil}, | ||||||
| 				{"mkdirAll", expectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil}, | 				{"mkdirAll", expectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil}, | ||||||
| 				{"mount", expectArgs{"proc", "/sysroot/proc", "proc", uintptr(0xe), ""}, nil, errUnique}, | 				{"mount", expectArgs{"proc", "/sysroot/proc", "proc", uintptr(0xe), ""}, nil, errUnique}, | ||||||
| 				{"printBaseErr", expectArgs{errUnique, "cannot apply op at index 1:"}, nil, nil}, | 				{"fatalf", expectArgs{"cannot apply op at index %d: %v", []any{1, errUnique}}, nil, nil}, | ||||||
| 				{"beforeExit", expectArgs{}, nil, nil}, |  | ||||||
| 				{"exit", expectArgs{1}, nil, nil}, |  | ||||||
| 				/* end apply */ | 				/* end apply */ | ||||||
| 			}, | 			}, | ||||||
| 		}, nil}, | 		}, nil}, | ||||||
|  | |||||||
| @ -1,25 +1,17 @@ | |||||||
| package container | package container | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"log" | 	"log" | ||||||
| 	"os" |  | ||||||
| 	"reflect" |  | ||||||
| 	"sync/atomic" | 	"sync/atomic" | ||||||
| 	"testing" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type Msg interface { | type Msg interface { | ||||||
| 	IsVerbose() bool | 	IsVerbose() bool | ||||||
| 	Verbose(v ...any) | 	Verbose(v ...any) | ||||||
| 	Verbosef(format string, v ...any) | 	Verbosef(format string, v ...any) | ||||||
| 	WrapErr(err error, a ...any) error |  | ||||||
| 	PrintBaseErr(err error, fallback string) |  | ||||||
| 
 | 
 | ||||||
| 	Suspend() | 	Suspend() | ||||||
| 	Resume() bool | 	Resume() bool | ||||||
| 
 |  | ||||||
| 	BeforeExit() | 	BeforeExit() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -37,32 +29,6 @@ func (msg *DefaultMsg) Verbosef(format string, v ...any) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // checkedWrappedErr implements error with strict checks for wrapped values. |  | ||||||
| type checkedWrappedErr struct { |  | ||||||
| 	err error |  | ||||||
| 	a   []any |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *checkedWrappedErr) Error() string { return fmt.Sprintf("%v, a = %s", c.err, c.a) } |  | ||||||
| func (c *checkedWrappedErr) Is(err error) bool { |  | ||||||
| 	var concreteErr *checkedWrappedErr |  | ||||||
| 	if !errors.As(err, &concreteErr) { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 	return reflect.DeepEqual(c, concreteErr) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (msg *DefaultMsg) WrapErr(err error, a ...any) error { |  | ||||||
| 	// provide a mostly bulletproof path to bypass this behaviour in tests |  | ||||||
| 	if testing.Testing() && os.Getenv("GOPATH") != Nonexistent { |  | ||||||
| 		return &checkedWrappedErr{err, a} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	log.Println(a...) |  | ||||||
| 	return err |  | ||||||
| } |  | ||||||
| func (msg *DefaultMsg) PrintBaseErr(err error, fallback string) { log.Println(fallback, err) } |  | ||||||
| 
 |  | ||||||
| func (msg *DefaultMsg) Suspend()     { msg.inactive.Store(true) } | func (msg *DefaultMsg) Suspend()     { msg.inactive.Store(true) } | ||||||
| func (msg *DefaultMsg) Resume() bool { return msg.inactive.CompareAndSwap(true, false) } | func (msg *DefaultMsg) Resume() bool { return msg.inactive.CompareAndSwap(true, false) } | ||||||
| func (msg *DefaultMsg) BeforeExit()  {} | func (msg *DefaultMsg) BeforeExit()  {} | ||||||
|  | |||||||
| @ -1,21 +1,15 @@ | |||||||
| package container_test | package container_test | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"errors" |  | ||||||
| 	"log" | 	"log" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"sync/atomic" | 	"sync/atomic" | ||||||
| 	"syscall" |  | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
| 	"hakurei.app/container" | 	"hakurei.app/container" | ||||||
| 	"hakurei.app/internal/hlog" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestDefaultMsg(t *testing.T) { | func TestDefaultMsg(t *testing.T) { | ||||||
| 	// bypass WrapErr testing behaviour |  | ||||||
| 	t.Setenv("GOPATH", container.Nonexistent) |  | ||||||
| 
 |  | ||||||
| 	{ | 	{ | ||||||
| 		w := log.Writer() | 		w := log.Writer() | ||||||
| 		f := log.Flags() | 		f := log.Flags() | ||||||
| @ -48,21 +42,6 @@ func TestDefaultMsg(t *testing.T) { | |||||||
| 		} | 		} | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	t.Run("wrapErr", func(t *testing.T) { |  | ||||||
| 		buf := new(strings.Builder) |  | ||||||
| 		log.SetOutput(buf) |  | ||||||
| 		log.SetFlags(0) |  | ||||||
| 		if err := msg.WrapErr(syscall.EBADE, "\x00", "\x00"); err != syscall.EBADE { |  | ||||||
| 			t.Errorf("WrapErr: %v", err) |  | ||||||
| 		} |  | ||||||
| 		msg.PrintBaseErr(syscall.ENOTRECOVERABLE, "cannot cuddle cat:") |  | ||||||
| 
 |  | ||||||
| 		want := "\x00 \x00\ncannot cuddle cat: state not recoverable\n" |  | ||||||
| 		if buf.String() != want { |  | ||||||
| 			t.Errorf("WrapErr: %q, want %q", buf.String(), want) |  | ||||||
| 		} |  | ||||||
| 	}) |  | ||||||
| 
 |  | ||||||
| 	t.Run("inactive", func(t *testing.T) { | 	t.Run("inactive", func(t *testing.T) { | ||||||
| 		{ | 		{ | ||||||
| 			inactive := msg.Resume() | 			inactive := msg.Resume() | ||||||
| @ -83,25 +62,6 @@ func TestDefaultMsg(t *testing.T) { | |||||||
| 
 | 
 | ||||||
| 	// the function is a noop | 	// the function is a noop | ||||||
| 	t.Run("beforeExit", func(t *testing.T) { msg.BeforeExit() }) | 	t.Run("beforeExit", func(t *testing.T) { msg.BeforeExit() }) | ||||||
| 
 |  | ||||||
| 	t.Run("checkedWrappedErr", func(t *testing.T) { |  | ||||||
| 		// temporarily re-enable testing behaviour |  | ||||||
| 		t.Setenv("GOPATH", "") |  | ||||||
| 		wrappedErr := msg.WrapErr(syscall.ENOTRECOVERABLE, "cannot cuddle cat:", syscall.ENOTRECOVERABLE) |  | ||||||
| 
 |  | ||||||
| 		t.Run("string", func(t *testing.T) { |  | ||||||
| 			want := "state not recoverable, a = [cannot cuddle cat: state not recoverable]" |  | ||||||
| 			if got := wrappedErr.Error(); got != want { |  | ||||||
| 				t.Errorf("Error: %q, want %q", got, want) |  | ||||||
| 			} |  | ||||||
| 		}) |  | ||||||
| 
 |  | ||||||
| 		t.Run("bad concrete type", func(t *testing.T) { |  | ||||||
| 			if errors.Is(wrappedErr, syscall.ENOTRECOVERABLE) { |  | ||||||
| 				t.Error("incorrect type assertion") |  | ||||||
| 			} |  | ||||||
| 		}) |  | ||||||
| 	}) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type panicWriter struct{} | type panicWriter struct{} | ||||||
| @ -139,9 +99,6 @@ func (out *testOutput) Verbosef(format string, v ...any) { | |||||||
| 	out.t.Logf(format, v...) | 	out.t.Logf(format, v...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (out *testOutput) WrapErr(err error, a ...any) error       { return hlog.WrapErr(err, a...) } |  | ||||||
| func (out *testOutput) PrintBaseErr(err error, fallback string) { hlog.PrintBaseError(err, fallback) } |  | ||||||
| 
 |  | ||||||
| func (out *testOutput) Suspend() { | func (out *testOutput) Suspend() { | ||||||
| 	if out.suspended.CompareAndSwap(false, true) { | 	if out.suspended.CompareAndSwap(false, true) { | ||||||
| 		out.Verbose("suspend called") | 		out.Verbose("suspend called") | ||||||
|  | |||||||
| @ -10,10 +10,3 @@ func SetOutput(v Msg) { | |||||||
| 		msg = v | 		msg = v | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 |  | ||||||
| func wrapErrSuffix(err error, a ...any) error { |  | ||||||
| 	if err == nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	return msg.WrapErr(err, append(a, err)...) |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -1,8 +1,6 @@ | |||||||
| package container | package container | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"reflect" |  | ||||||
| 	"syscall" |  | ||||||
| 	"testing" | 	"testing" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| @ -31,70 +29,13 @@ func TestGetSetOutput(t *testing.T) { | |||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestWrapErr(t *testing.T) { |  | ||||||
| 	{ |  | ||||||
| 		out := GetOutput() |  | ||||||
| 		t.Cleanup(func() { SetOutput(out) }) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var wrapFp *func(error, ...any) error |  | ||||||
| 	s := new(stubOutput) |  | ||||||
| 	SetOutput(s) |  | ||||||
| 	wrapFp = &s.wrapF |  | ||||||
| 
 |  | ||||||
| 	testCases := []struct { |  | ||||||
| 		name    string |  | ||||||
| 		f       func(t *testing.T) |  | ||||||
| 		wantErr error |  | ||||||
| 		wantA   []any |  | ||||||
| 	}{ |  | ||||||
| 		{"suffix nil", func(t *testing.T) { |  | ||||||
| 			if err := wrapErrSuffix(nil, "\x00"); err != nil { |  | ||||||
| 				t.Errorf("wrapErrSuffix: %v", err) |  | ||||||
| 			} |  | ||||||
| 		}, nil, nil}, |  | ||||||
| 		{"suffix val", func(t *testing.T) { |  | ||||||
| 			if err := wrapErrSuffix(syscall.ENOTRECOVERABLE, "\x00\x00"); err != syscall.ENOTRECOVERABLE { |  | ||||||
| 				t.Errorf("wrapErrSuffix: %v", err) |  | ||||||
| 			} |  | ||||||
| 		}, syscall.ENOTRECOVERABLE, []any{"\x00\x00", syscall.ENOTRECOVERABLE}}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for _, tc := range testCases { |  | ||||||
| 		t.Run(tc.name, func(t *testing.T) { |  | ||||||
| 			var ( |  | ||||||
| 				gotErr error |  | ||||||
| 				gotA   []any |  | ||||||
| 			) |  | ||||||
| 			*wrapFp = func(err error, a ...any) error { gotErr = err; gotA = a; return err } |  | ||||||
| 
 |  | ||||||
| 			tc.f(t) |  | ||||||
| 			if gotErr != tc.wantErr { |  | ||||||
| 				t.Errorf("WrapErr: err = %v, want %v", gotErr, tc.wantErr) |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			if !reflect.DeepEqual(gotA, tc.wantA) { |  | ||||||
| 				t.Errorf("WrapErr: a = %v, want %v", gotA, tc.wantA) |  | ||||||
| 			} |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type stubOutput struct { | type stubOutput struct { | ||||||
| 	wrapF func(error, ...any) error | 	wrapF func(error, ...any) error | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (*stubOutput) IsVerbose() bool            { panic("unreachable") } | func (*stubOutput) IsVerbose() bool         { panic("unreachable") } | ||||||
| func (*stubOutput) Verbose(...any)             { panic("unreachable") } | func (*stubOutput) Verbose(...any)          { panic("unreachable") } | ||||||
| func (*stubOutput) Verbosef(string, ...any)    { panic("unreachable") } | func (*stubOutput) Verbosef(string, ...any) { panic("unreachable") } | ||||||
| func (*stubOutput) PrintBaseErr(error, string) { panic("unreachable") } | func (*stubOutput) Suspend()                { panic("unreachable") } | ||||||
| func (*stubOutput) Suspend()                   { panic("unreachable") } | func (*stubOutput) Resume() bool            { panic("unreachable") } | ||||||
| func (*stubOutput) Resume() bool               { panic("unreachable") } | func (*stubOutput) BeforeExit()             { panic("unreachable") } | ||||||
| func (*stubOutput) BeforeExit()                { panic("unreachable") } |  | ||||||
| 
 |  | ||||||
| func (s *stubOutput) WrapErr(err error, v ...any) error { |  | ||||||
| 	if s.wrapF == nil { |  | ||||||
| 		panic("unreachable") |  | ||||||
| 	} |  | ||||||
| 	return s.wrapF(err, v...) |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -8,11 +8,6 @@ import ( | |||||||
| 	"syscall" | 	"syscall" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var ( |  | ||||||
| 	ErrNotSet   = errors.New("environment variable not set") |  | ||||||
| 	ErrFdFormat = errors.New("bad file descriptor representation") |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Setup appends the read end of a pipe for setup params transmission and returns its fd. | // Setup appends the read end of a pipe for setup params transmission and returns its fd. | ||||||
| func Setup(extraFiles *[]*os.File) (int, *gob.Encoder, error) { | func Setup(extraFiles *[]*os.File) (int, *gob.Encoder, error) { | ||||||
| 	if r, w, err := os.Pipe(); err != nil { | 	if r, w, err := os.Pipe(); err != nil { | ||||||
| @ -24,19 +19,23 @@ func Setup(extraFiles *[]*os.File) (int, *gob.Encoder, error) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | var ( | ||||||
|  | 	ErrReceiveEnv = errors.New("environment variable not set") | ||||||
|  | ) | ||||||
|  | 
 | ||||||
| // Receive retrieves setup fd from the environment and receives params. | // Receive retrieves setup fd from the environment and receives params. | ||||||
| func Receive(key string, e any, fdp *uintptr) (func() error, error) { | func Receive(key string, e any, fdp *uintptr) (func() error, error) { | ||||||
| 	var setup *os.File | 	var setup *os.File | ||||||
| 
 | 
 | ||||||
| 	if s, ok := os.LookupEnv(key); !ok { | 	if s, ok := os.LookupEnv(key); !ok { | ||||||
| 		return nil, ErrNotSet | 		return nil, ErrReceiveEnv | ||||||
| 	} else { | 	} else { | ||||||
| 		if fd, err := strconv.Atoi(s); err != nil { | 		if fd, err := strconv.Atoi(s); err != nil { | ||||||
| 			return nil, ErrFdFormat | 			return nil, errors.Unwrap(err) | ||||||
| 		} else { | 		} else { | ||||||
| 			setup = os.NewFile(uintptr(fd), "setup") | 			setup = os.NewFile(uintptr(fd), "setup") | ||||||
| 			if setup == nil { | 			if setup == nil { | ||||||
| 				return nil, syscall.EBADF | 				return nil, syscall.EDOM | ||||||
| 			} | 			} | ||||||
| 			if fdp != nil { | 			if fdp != nil { | ||||||
| 				*fdp = setup.Fd() | 				*fdp = setup.Fd() | ||||||
|  | |||||||
| @ -29,8 +29,8 @@ func TestSetupReceive(t *testing.T) { | |||||||
| 			}) | 			}) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if _, err := container.Receive(key, nil, nil); !errors.Is(err, container.ErrNotSet) { | 		if _, err := container.Receive(key, nil, nil); !errors.Is(err, container.ErrReceiveEnv) { | ||||||
| 			t.Errorf("Receive: error = %v, want %v", err, container.ErrNotSet) | 			t.Errorf("Receive: error = %v, want %v", err, container.ErrReceiveEnv) | ||||||
| 		} | 		} | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| @ -38,8 +38,8 @@ func TestSetupReceive(t *testing.T) { | |||||||
| 		const key = "TEST_ENV_FORMAT" | 		const key = "TEST_ENV_FORMAT" | ||||||
| 		t.Setenv(key, "") | 		t.Setenv(key, "") | ||||||
| 
 | 
 | ||||||
| 		if _, err := container.Receive(key, nil, nil); !errors.Is(err, container.ErrFdFormat) { | 		if _, err := container.Receive(key, nil, nil); !errors.Is(err, strconv.ErrSyntax) { | ||||||
| 			t.Errorf("Receive: error = %v, want %v", err, container.ErrFdFormat) | 			t.Errorf("Receive: error = %v, want %v", err, strconv.ErrSyntax) | ||||||
| 		} | 		} | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| @ -47,8 +47,8 @@ func TestSetupReceive(t *testing.T) { | |||||||
| 		const key = "TEST_ENV_RANGE" | 		const key = "TEST_ENV_RANGE" | ||||||
| 		t.Setenv(key, "-1") | 		t.Setenv(key, "-1") | ||||||
| 
 | 
 | ||||||
| 		if _, err := container.Receive(key, nil, nil); !errors.Is(err, syscall.EBADF) { | 		if _, err := container.Receive(key, nil, nil); !errors.Is(err, syscall.EDOM) { | ||||||
| 			t.Errorf("Receive: error = %v, want %v", err, syscall.EBADF) | 			t.Errorf("Receive: error = %v, want %v", err, syscall.EDOM) | ||||||
| 		} | 		} | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -62,7 +62,7 @@ func (a *App) String() string { | |||||||
| 	return fmt.Sprintf("(unsealed app %s)", a.id) | 	return fmt.Sprintf("(unsealed app %s)", a.id) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Seal determines the outcome of [hst.Config] as a [SealedApp]. | // Seal determines the [Outcome] of [hst.Config]. | ||||||
| // Values stored in and referred to by [hst.Config] might be overwritten and must not be used again. | // Values stored in and referred to by [hst.Config] might be overwritten and must not be used again. | ||||||
| func (a *App) Seal(config *hst.Config) (*Outcome, error) { | func (a *App) Seal(config *hst.Config) (*Outcome, error) { | ||||||
| 	a.mu.Lock() | 	a.mu.Lock() | ||||||
|  | |||||||
| @ -65,7 +65,7 @@ func ShimMain() { | |||||||
| 		if errors.Is(err, syscall.EBADF) { | 		if errors.Is(err, syscall.EBADF) { | ||||||
| 			log.Fatal("invalid config descriptor") | 			log.Fatal("invalid config descriptor") | ||||||
| 		} | 		} | ||||||
| 		if errors.Is(err, container.ErrNotSet) { | 		if errors.Is(err, container.ErrReceiveEnv) { | ||||||
| 			log.Fatal("HAKUREI_SHIM not set") | 			log.Fatal("HAKUREI_SHIM not set") | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -33,14 +33,16 @@ func (e *OpError) Error() string { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	switch { | 	switch { | ||||||
| 	case errors.As(e.Err, new(*os.PathError)), errors.As(e.Err, new(*net.OpError)): | 	case errors.As(e.Err, new(*os.PathError)), | ||||||
|  | 		errors.As(e.Err, new(*net.OpError)), | ||||||
|  | 		errors.As(e.Err, new(*container.StartError)): | ||||||
| 		return e.Err.Error() | 		return e.Err.Error() | ||||||
| 
 | 
 | ||||||
| 	default: | 	default: | ||||||
| 		if !e.Revert { | 		if !e.Revert { | ||||||
| 			return "cannot apply " + e.Op + ": " + e.Err.Error() | 			return "apply " + e.Op + ": " + e.Err.Error() | ||||||
| 		} else { | 		} else { | ||||||
| 			return "cannot revert " + e.Op + ": " + e.Err.Error() | 			return "revert " + e.Op + ": " + e.Err.Error() | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -26,11 +26,11 @@ func TestOpError(t *testing.T) { | |||||||
| 			ErrDBusConfig, syscall.ENOTRECOVERABLE}, | 			ErrDBusConfig, syscall.ENOTRECOVERABLE}, | ||||||
| 
 | 
 | ||||||
| 		{"apply", newOpError("tmpfile", syscall.EBADE, false), | 		{"apply", newOpError("tmpfile", syscall.EBADE, false), | ||||||
| 			"cannot apply tmpfile: invalid exchange", | 			"apply tmpfile: invalid exchange", | ||||||
| 			syscall.EBADE, syscall.EBADF}, | 			syscall.EBADE, syscall.EBADF}, | ||||||
| 
 | 
 | ||||||
| 		{"revert", newOpError("wayland", syscall.EBADF, true), | 		{"revert", newOpError("wayland", syscall.EBADF, true), | ||||||
| 			"cannot revert wayland: bad file descriptor", | 			"revert wayland: bad file descriptor", | ||||||
| 			syscall.EBADF, syscall.EBADE}, | 			syscall.EBADF, syscall.EBADE}, | ||||||
| 
 | 
 | ||||||
| 		{"path", newOpError("tmpfile", &os.PathError{Op: "stat", Path: "/run/dbus", Err: syscall.EISDIR}, false), | 		{"path", newOpError("tmpfile", &os.PathError{Op: "stat", Path: "/run/dbus", Err: syscall.EISDIR}, false), | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user