container: wrap mount syscall errno
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Test / Create distribution (push) Successful in 33s
				
			
		
			
				
	
				Test / Sandbox (push) Successful in 1m39s
				
			
		
			
				
	
				Test / Hakurei (push) Successful in 3m16s
				
			
		
			
				
	
				Test / Hpkg (push) Successful in 3m37s
				
			
		
			
				
	
				Test / Sandbox (race detector) (push) Successful in 3m56s
				
			
		
			
				
	
				Test / Hakurei (race detector) (push) Successful in 5m17s
				
			
		
			
				
	
				Test / Flake checks (push) Successful in 1m36s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Test / Create distribution (push) Successful in 33s
				
			Test / Sandbox (push) Successful in 1m39s
				
			Test / Hakurei (push) Successful in 3m16s
				
			Test / Hpkg (push) Successful in 3m37s
				
			Test / Sandbox (race detector) (push) Successful in 3m56s
				
			Test / Hakurei (race detector) (push) Successful in 5m17s
				
			Test / Flake checks (push) Successful in 1m36s
				
			This is the first step to deprecating the generalised error wrapping error message pattern. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
		
							parent
							
								
									b14690aa77
								
							
						
					
					
						commit
						84ad9791e2
					
				| @ -225,7 +225,7 @@ func (direct) pivotRoot(newroot, putold string) (err error) { | |||||||
| 	return syscall.PivotRoot(newroot, putold) | 	return syscall.PivotRoot(newroot, putold) | ||||||
| } | } | ||||||
| func (direct) mount(source, target, fstype string, flags uintptr, data string) (err error) { | func (direct) mount(source, target, fstype string, flags uintptr, data string) (err error) { | ||||||
| 	return syscall.Mount(source, target, fstype, flags, data) | 	return mount(source, target, fstype, flags, data) | ||||||
| } | } | ||||||
| func (direct) unmount(target string, flags int) (err error) { | func (direct) unmount(target string, flags int) (err error) { | ||||||
| 	return syscall.Unmount(target, flags) | 	return syscall.Unmount(target, flags) | ||||||
|  | |||||||
							
								
								
									
										60
									
								
								container/errors.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								container/errors.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,60 @@ | |||||||
|  | package container | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"os" | ||||||
|  | 	"syscall" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type MountError struct { | ||||||
|  | 	Source, Target, Fstype string | ||||||
|  | 
 | ||||||
|  | 	Flags uintptr | ||||||
|  | 	Data  string | ||||||
|  | 	syscall.Errno | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *MountError) Unwrap() error { | ||||||
|  | 	if e.Errno == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return e.Errno | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *MountError) Error() string { | ||||||
|  | 	if e.Flags&syscall.MS_BIND != 0 { | ||||||
|  | 		if e.Flags&syscall.MS_REMOUNT != 0 { | ||||||
|  | 			return "remount " + e.Target + ": " + e.Errno.Error() | ||||||
|  | 		} | ||||||
|  | 		return "bind " + e.Source + " on " + e.Target + ": " + e.Errno.Error() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if e.Fstype != FstypeNULL { | ||||||
|  | 		return "mount " + e.Fstype + " on " + e.Target + ": " + e.Errno.Error() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// fallback case: if this is reached, the conditions for it to occur should be handled above | ||||||
|  | 	return "mount " + e.Target + ": " + e.Errno.Error() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // errnoFallback returns the concrete errno from an error, or a [os.PathError] fallback. | ||||||
|  | func errnoFallback(op, path string, err error) (syscall.Errno, *os.PathError) { | ||||||
|  | 	var errno syscall.Errno | ||||||
|  | 	if !errors.As(err, &errno) { | ||||||
|  | 		return 0, &os.PathError{Op: op, Path: path, Err: err} | ||||||
|  | 	} | ||||||
|  | 	return errno, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // mount wraps syscall.Mount for error handling. | ||||||
|  | func mount(source, target, fstype string, flags uintptr, data string) error { | ||||||
|  | 	err := syscall.Mount(source, target, fstype, flags, data) | ||||||
|  | 	if err == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	if errno, pathError := errnoFallback("mount", target, err); pathError != nil { | ||||||
|  | 		return pathError | ||||||
|  | 	} else { | ||||||
|  | 		return &MountError{source, target, fstype, flags, data, errno} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										109
									
								
								container/errors_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								container/errors_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,109 @@ | |||||||
|  | package container | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"os" | ||||||
|  | 	"reflect" | ||||||
|  | 	"syscall" | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestMountError(t *testing.T) { | ||||||
|  | 	testCases := []struct { | ||||||
|  | 		name  string | ||||||
|  | 		err   error | ||||||
|  | 		errno syscall.Errno | ||||||
|  | 		want  string | ||||||
|  | 	}{ | ||||||
|  | 		{"bind", &MountError{ | ||||||
|  | 			Source: "/host/nix/store", | ||||||
|  | 			Target: "/sysroot/nix/store", | ||||||
|  | 			Fstype: FstypeNULL, | ||||||
|  | 			Flags:  syscall.MS_SILENT | syscall.MS_BIND | syscall.MS_REC, | ||||||
|  | 			Data:   zeroString, | ||||||
|  | 			Errno:  syscall.ENOSYS, | ||||||
|  | 		}, syscall.ENOSYS, | ||||||
|  | 			"bind /host/nix/store on /sysroot/nix/store: function not implemented"}, | ||||||
|  | 
 | ||||||
|  | 		{"remount", &MountError{ | ||||||
|  | 			Source: SourceNone, | ||||||
|  | 			Target: "/sysroot/nix/store", | ||||||
|  | 			Fstype: FstypeNULL, | ||||||
|  | 			Flags:  syscall.MS_SILENT | syscall.MS_BIND | syscall.MS_REMOUNT, | ||||||
|  | 			Data:   zeroString, | ||||||
|  | 			Errno:  syscall.EPERM, | ||||||
|  | 		}, syscall.EPERM, | ||||||
|  | 			"remount /sysroot/nix/store: operation not permitted"}, | ||||||
|  | 
 | ||||||
|  | 		{"overlay", &MountError{ | ||||||
|  | 			Source: SourceOverlay, | ||||||
|  | 			Target: sysrootPath, | ||||||
|  | 			Fstype: FstypeOverlay, | ||||||
|  | 			Data:   `lowerdir=/host/var/lib/planterette/base/debian\:f92c9052`, | ||||||
|  | 			Errno:  syscall.EINVAL, | ||||||
|  | 		}, syscall.EINVAL, | ||||||
|  | 			"mount overlay on /sysroot: invalid argument"}, | ||||||
|  | 
 | ||||||
|  | 		{"fallback", &MountError{ | ||||||
|  | 			Source: SourceNone, | ||||||
|  | 			Target: sysrootPath, | ||||||
|  | 			Fstype: FstypeNULL, | ||||||
|  | 			Errno:  syscall.ENOTRECOVERABLE, | ||||||
|  | 		}, syscall.ENOTRECOVERABLE, | ||||||
|  | 			"mount /sysroot: state not recoverable"}, | ||||||
|  | 	} | ||||||
|  | 	for _, tc := range testCases { | ||||||
|  | 		t.Run(tc.name, func(t *testing.T) { | ||||||
|  | 			t.Run("is", func(t *testing.T) { | ||||||
|  | 				if !errors.Is(tc.err, tc.errno) { | ||||||
|  | 					t.Errorf("Is: %#v is not %v", tc.err, tc.errno) | ||||||
|  | 				} | ||||||
|  | 			}) | ||||||
|  | 			t.Run("error", func(t *testing.T) { | ||||||
|  | 				if got := tc.err.Error(); got != tc.want { | ||||||
|  | 					t.Errorf("Error: %q, want %q", got, tc.want) | ||||||
|  | 				} | ||||||
|  | 			}) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	t.Run("zero", func(t *testing.T) { | ||||||
|  | 		if errors.Is(new(MountError), syscall.Errno(0)) { | ||||||
|  | 			t.Errorf("Is: zero MountError unexpected true") | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestErrnoFallback(t *testing.T) { | ||||||
|  | 	testCases := []struct { | ||||||
|  | 		name      string | ||||||
|  | 		err       error | ||||||
|  | 		wantErrno syscall.Errno | ||||||
|  | 		wantPath  *os.PathError | ||||||
|  | 	}{ | ||||||
|  | 		{"mount", &MountError{ | ||||||
|  | 			Errno: syscall.ENOTRECOVERABLE, | ||||||
|  | 		}, syscall.ENOTRECOVERABLE, nil}, | ||||||
|  | 
 | ||||||
|  | 		{"path errno", &os.PathError{ | ||||||
|  | 			Err: syscall.ETIMEDOUT, | ||||||
|  | 		}, syscall.ETIMEDOUT, nil}, | ||||||
|  | 
 | ||||||
|  | 		{"fallback", errUnique, 0, &os.PathError{ | ||||||
|  | 			Op:   "fallback", | ||||||
|  | 			Path: "/proc/nonexistent", | ||||||
|  | 			Err:  errUnique, | ||||||
|  | 		}}, | ||||||
|  | 	} | ||||||
|  | 	for _, tc := range testCases { | ||||||
|  | 		t.Run(tc.name, func(t *testing.T) { | ||||||
|  | 			errno, err := errnoFallback(tc.name, Nonexistent, tc.err) | ||||||
|  | 			if errno != tc.wantErrno { | ||||||
|  | 				t.Errorf("errnoFallback: errno = %v, want %v", errno, tc.wantErrno) | ||||||
|  | 			} | ||||||
|  | 			if !reflect.DeepEqual(err, tc.wantPath) { | ||||||
|  | 				t.Errorf("errnoFallback: pathError = %#v, want %#v", err, tc.wantPath) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user