container/vfs: wrap decoder errors
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Test / Create distribution (push) Successful in 34s
				
			
		
			
				
	
				Test / Sandbox (push) Successful in 2m8s
				
			
		
			
				
	
				Test / Hakurei (push) Successful in 3m15s
				
			
		
			
				
	
				Test / Hpkg (push) Successful in 3m33s
				
			
		
			
				
	
				Test / Sandbox (race detector) (push) Successful in 4m30s
				
			
		
			
				
	
				Test / Hakurei (race detector) (push) Successful in 5m19s
				
			
		
			
				
	
				Test / Flake checks (push) Successful in 1m35s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Test / Create distribution (push) Successful in 34s
				
			Test / Sandbox (push) Successful in 2m8s
				
			Test / Hakurei (push) Successful in 3m15s
				
			Test / Hpkg (push) Successful in 3m33s
				
			Test / Sandbox (race detector) (push) Successful in 4m30s
				
			Test / Hakurei (race detector) (push) Successful in 5m19s
				
			Test / Flake checks (push) Successful in 1m35s
				
			This passes line information and handles strconv errors so it reads better. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
		
							parent
							
								
									905b9f9785
								
							
						
					
					
						commit
						50972096cd
					
				| @ -149,7 +149,7 @@ func (p *procPaths) remount(target string, flags uintptr) error { | ||||
| 	return p.mountinfo(func(d *vfs.MountInfoDecoder) error { | ||||
| 		n, err := d.Unfold(targetKFinal) | ||||
| 		if err != nil { | ||||
| 			if errors.Is(err, ESTALE) { | ||||
| 			if errors.As(err, new(vfs.UnfoldTargetError)) { | ||||
| 				return msg.WrapErr(err, | ||||
| 					fmt.Sprintf("mount point %q never appeared in mountinfo", targetKFinal)) | ||||
| 			} | ||||
|  | ||||
| @ -109,7 +109,7 @@ func TestRemount(t *testing.T) { | ||||
| 			{"close", expectArgs{0xdeadbeef}, nil, errUnique}, | ||||
| 		}}, errUnique}, | ||||
| 
 | ||||
| 		{"mountinfo stale", func(k syscallDispatcher) error { | ||||
| 		{"mountinfo no match", func(k syscallDispatcher) error { | ||||
| 			return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) | ||||
| 		}, [][]kexpect{{ | ||||
| 			{"evalSymlinks", expectArgs{"/sysroot/nix"}, "/sysroot/.hakurei", nil}, | ||||
| @ -118,7 +118,7 @@ func TestRemount(t *testing.T) { | ||||
| 			{"readlink", expectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/.hakurei", nil}, | ||||
| 			{"close", expectArgs{0xdeadbeef}, nil, nil}, | ||||
| 			{"openNew", expectArgs{"/host/proc/self/mountinfo"}, newConstFile(sampleMountinfoNix), nil}, | ||||
| 		}}, msg.WrapErr(syscall.ESTALE, `mount point "/sysroot/.hakurei" never appeared in mountinfo`)}, | ||||
| 		}}, msg.WrapErr(&vfs.DecoderError{Op: "unfold", Line: -1, Err: vfs.UnfoldTargetError("/sysroot/.hakurei")}, `mount point "/sysroot/.hakurei" never appeared in mountinfo`)}, | ||||
| 
 | ||||
| 		{"mountinfo", func(k syscallDispatcher) error { | ||||
| 			return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) | ||||
| @ -128,7 +128,7 @@ func TestRemount(t *testing.T) { | ||||
| 			{"readlink", expectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/nix", nil}, | ||||
| 			{"close", expectArgs{0xdeadbeef}, nil, nil}, | ||||
| 			{"openNew", expectArgs{"/host/proc/self/mountinfo"}, newConstFile("\x00"), nil}, | ||||
| 		}}, wrapErrSuffix(vfs.ErrMountInfoFields, `cannot parse mountinfo:`)}, | ||||
| 		}}, wrapErrSuffix(&vfs.DecoderError{Op: "parse", Line: 0, Err: vfs.ErrMountInfoFields}, `cannot parse mountinfo:`)}, | ||||
| 
 | ||||
| 		{"mount", func(k syscallDispatcher) error { | ||||
| 			return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) | ||||
|  | ||||
| @ -248,7 +248,7 @@ func TestProcPaths(t *testing.T) { | ||||
| 					t.Fatalf("WriteFile: error = %v", err) | ||||
| 				} | ||||
| 
 | ||||
| 				wantErr := wrapErrSuffix(vfs.ErrMountInfoFields, "cannot parse mountinfo:") | ||||
| 				wantErr := wrapErrSuffix(&vfs.DecoderError{Op: "parse", Line: 0, Err: vfs.ErrMountInfoFields}, "cannot parse mountinfo:") | ||||
| 				if err := newProcPaths(direct{}, tempDir).mountinfo(func(d *vfs.MountInfoDecoder) error { return d.Decode(new(*vfs.MountInfo)) }); !errors.Is(err, wantErr) { | ||||
| 					t.Fatalf("mountinfo: error = %v, want %v", err, wantErr) | ||||
| 				} | ||||
|  | ||||
| @ -24,6 +24,32 @@ var ( | ||||
| 	ErrMountInfoSep    = errors.New("bad optional fields separator") | ||||
| ) | ||||
| 
 | ||||
| type DecoderError struct { | ||||
| 	Op   string | ||||
| 	Line int | ||||
| 	Err  error | ||||
| } | ||||
| 
 | ||||
| func (e *DecoderError) Unwrap() error { return e.Err } | ||||
| func (e *DecoderError) Error() string { | ||||
| 	var s string | ||||
| 
 | ||||
| 	var numError *strconv.NumError | ||||
| 	switch { | ||||
| 	case errors.As(e.Err, &numError) && numError != nil: | ||||
| 		s = "numeric field " + strconv.Quote(numError.Num) + " " + numError.Err.Error() | ||||
| 
 | ||||
| 	default: | ||||
| 		s = e.Err.Error() | ||||
| 	} | ||||
| 
 | ||||
| 	var atLine string | ||||
| 	if e.Line >= 0 { | ||||
| 		atLine = " at line " + strconv.Itoa(e.Line) | ||||
| 	} | ||||
| 	return e.Op + " mountinfo" + atLine + ": " + s | ||||
| } | ||||
| 
 | ||||
| type ( | ||||
| 	// A MountInfoDecoder reads and decodes proc_pid_mountinfo(5) entries from an input stream. | ||||
| 	MountInfoDecoder struct { | ||||
| @ -32,6 +58,7 @@ type ( | ||||
| 
 | ||||
| 		current  *MountInfo | ||||
| 		parseErr error | ||||
| 		curLine  int | ||||
| 		complete bool | ||||
| 	} | ||||
| 
 | ||||
| @ -132,9 +159,12 @@ func (d *MountInfoDecoder) Entries() iter.Seq[*MountInfoEntry] { | ||||
| 
 | ||||
| func (d *MountInfoDecoder) Err() error { | ||||
| 	if err := d.s.Err(); err != nil { | ||||
| 		return err | ||||
| 		return &DecoderError{"scan", d.curLine, err} | ||||
| 	} | ||||
| 	return d.parseErr | ||||
| 	if d.parseErr != nil { | ||||
| 		return &DecoderError{"parse", d.curLine, d.parseErr} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (d *MountInfoDecoder) scan() bool { | ||||
| @ -160,6 +190,7 @@ func (d *MountInfoDecoder) scan() bool { | ||||
| 		d.current.Next = m | ||||
| 		d.current = d.current.Next | ||||
| 	} | ||||
| 	d.curLine++ | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -4,6 +4,7 @@ import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"iter" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"reflect" | ||||
| 	"slices" | ||||
| @ -15,62 +16,102 @@ import ( | ||||
| 	"hakurei.app/container/vfs" | ||||
| ) | ||||
| 
 | ||||
| func TestDecoderError(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		name    string | ||||
| 		err     *vfs.DecoderError | ||||
| 		want    string | ||||
| 		target  error | ||||
| 		targetF error | ||||
| 	}{ | ||||
| 		{"errno", &vfs.DecoderError{Op: "parse", Line: 0xdeadbeef, Err: syscall.ENOTRECOVERABLE}, | ||||
| 			"parse mountinfo at line 3735928559: state not recoverable", syscall.ENOTRECOVERABLE, syscall.EROFS}, | ||||
| 
 | ||||
| 		{"strconv", &vfs.DecoderError{Op: "parse", Line: 0xdeadbeef, Err: &strconv.NumError{Func: "Atoi", Num: "meow", Err: strconv.ErrSyntax}}, | ||||
| 			`parse mountinfo at line 3735928559: numeric field "meow" invalid syntax`, strconv.ErrSyntax, os.ErrInvalid}, | ||||
| 
 | ||||
| 		{"unfold", &vfs.DecoderError{Op: "unfold", Line: -1, Err: vfs.UnfoldTargetError("/proc/nonexistent")}, | ||||
| 			"unfold mountinfo: mount point /proc/nonexistent never appeared in mountinfo", vfs.UnfoldTargetError("/proc/nonexistent"), os.ErrNotExist}, | ||||
| 	} | ||||
| 	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.want { | ||||
| 					t.Errorf("Error: %s, want %s", got, tc.want) | ||||
| 				} | ||||
| 			}) | ||||
| 
 | ||||
| 			t.Run("is", func(t *testing.T) { | ||||
| 				if !errors.Is(tc.err, tc.target) { | ||||
| 					t.Errorf("Is: unexpected false") | ||||
| 				} | ||||
| 				if errors.Is(tc.err, tc.targetF) { | ||||
| 					t.Errorf("Is: unexpected true") | ||||
| 				} | ||||
| 			}) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestMountInfo(t *testing.T) { | ||||
| 	testCases := []mountInfoTest{ | ||||
| 		{"count", sampleMountinfoBase + ` | ||||
| 21 20 0:53/ /mnt/test rw,relatime - tmpfs  rw | ||||
| 21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`, | ||||
| 			vfs.ErrMountInfoFields, "", nil, nil, nil}, | ||||
| 			&vfs.DecoderError{Op: "parse", Line: 6, Err: vfs.ErrMountInfoFields}, | ||||
| 			"", nil, nil, nil}, | ||||
| 
 | ||||
| 		{"sep", sampleMountinfoBase + ` | ||||
| 21 20 0:53 / /mnt/test rw,relatime shared:212 _ tmpfs  rw | ||||
| 21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`, | ||||
| 			vfs.ErrMountInfoSep, "", nil, nil, nil}, | ||||
| 			&vfs.DecoderError{Op: "parse", Line: 6, Err: vfs.ErrMountInfoSep}, | ||||
| 			"", nil, nil, nil}, | ||||
| 
 | ||||
| 		{"id", sampleMountinfoBase + ` | ||||
| id 20 0:53 / /mnt/test rw,relatime shared:212 - tmpfs  rw | ||||
| 21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`, | ||||
| 			strconv.ErrSyntax, "", nil, nil, nil}, | ||||
| 			&vfs.DecoderError{Op: "parse", Line: 6, Err: &strconv.NumError{Func: "Atoi", Num: "id", Err: strconv.ErrSyntax}}, | ||||
| 			"", nil, nil, nil}, | ||||
| 
 | ||||
| 		{"parent", sampleMountinfoBase + ` | ||||
| 21 parent 0:53 / /mnt/test rw,relatime shared:212 - tmpfs  rw | ||||
| 21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`, | ||||
| 			strconv.ErrSyntax, "", nil, nil, nil}, | ||||
| 			&vfs.DecoderError{Op: "parse", Line: 6, Err: &strconv.NumError{Func: "Atoi", Num: "parent", Err: strconv.ErrSyntax}}, "", nil, nil, nil}, | ||||
| 
 | ||||
| 		{"devno", sampleMountinfoBase + ` | ||||
| 21 20 053 / /mnt/test rw,relatime shared:212 - tmpfs  rw | ||||
| 21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`, | ||||
| 			nil, "unexpected EOF", nil, nil, nil}, | ||||
| 			nil, "parse mountinfo at line 6: unexpected EOF", nil, nil, nil}, | ||||
| 
 | ||||
| 		{"maj", sampleMountinfoBase + ` | ||||
| 21 20 maj:53 / /mnt/test rw,relatime shared:212 - tmpfs  rw | ||||
| 21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`, | ||||
| 			nil, "expected integer", nil, nil, nil}, | ||||
| 			nil, "parse mountinfo at line 6: expected integer", nil, nil, nil}, | ||||
| 
 | ||||
| 		{"min", sampleMountinfoBase + ` | ||||
| 21 20 0:min / /mnt/test rw,relatime shared:212 - tmpfs  rw | ||||
| 21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`, | ||||
| 			nil, "expected integer", nil, nil, nil}, | ||||
| 			nil, "parse mountinfo at line 6: expected integer", nil, nil, nil}, | ||||
| 
 | ||||
| 		{"mountroot", sampleMountinfoBase + ` | ||||
| 21 20 0:53  /mnt/test rw,relatime - tmpfs  rw | ||||
| 21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`, | ||||
| 			vfs.ErrMountInfoEmpty, "", nil, nil, nil}, | ||||
| 			&vfs.DecoderError{Op: "parse", Line: 6, Err: vfs.ErrMountInfoEmpty}, "", nil, nil, nil}, | ||||
| 
 | ||||
| 		{"target", sampleMountinfoBase + ` | ||||
| 21 20 0:53 /  rw,relatime - tmpfs  rw | ||||
| 21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`, | ||||
| 			vfs.ErrMountInfoEmpty, "", nil, nil, nil}, | ||||
| 			&vfs.DecoderError{Op: "parse", Line: 6, Err: vfs.ErrMountInfoEmpty}, "", nil, nil, nil}, | ||||
| 
 | ||||
| 		{"vfs options", sampleMountinfoBase + ` | ||||
| 21 20 0:53 / /mnt/test  - tmpfs  rw | ||||
| 21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`, | ||||
| 			vfs.ErrMountInfoEmpty, "", nil, nil, nil}, | ||||
| 			&vfs.DecoderError{Op: "parse", Line: 6, Err: vfs.ErrMountInfoEmpty}, "", nil, nil, nil}, | ||||
| 
 | ||||
| 		{"FS type", sampleMountinfoBase + ` | ||||
| 21 20 0:53 / /mnt/test rw,relatime -   rw | ||||
| 21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755`, | ||||
| 			vfs.ErrMountInfoEmpty, "", nil, nil, nil}, | ||||
| 21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755 | ||||
| 21 20 0:53 / /mnt/test rw,relatime -   rw`, | ||||
| 			&vfs.DecoderError{Op: "parse", Line: 7, Err: vfs.ErrMountInfoEmpty}, "", nil, nil, nil}, | ||||
| 
 | ||||
| 		{"base", sampleMountinfoBase, nil, "", []*wantMountInfo{ | ||||
| 			m(15, 20, 0, 3, "/", "/proc", "rw,relatime", o(), "proc", "/proc", "rw", syscall.MS_RELATIME, nil), | ||||
| @ -266,9 +307,9 @@ func (tc *mountInfoTest) check(t *testing.T, d *vfs.MountInfoDecoder, funcName s | ||||
| 		}) | ||||
| 	} else if tc.wantNode != nil || tc.wantCollectF != nil { | ||||
| 		panic("invalid test case") | ||||
| 	} else if _, err := d.Unfold("/"); !errors.Is(err, tc.wantErr) { | ||||
| 	} else if _, err := d.Unfold("/"); !reflect.DeepEqual(err, tc.wantErr) { | ||||
| 		if tc.wantError == "" { | ||||
| 			t.Errorf("Unfold: error = %v, wantErr %v", | ||||
| 			t.Errorf("Unfold: error = %#v, wantErr %#v", | ||||
| 				err, tc.wantErr) | ||||
| 		} else if err != nil && err.Error() != tc.wantError { | ||||
| 			t.Errorf("Unfold: error = %q, wantError %q", | ||||
| @ -276,9 +317,9 @@ func (tc *mountInfoTest) check(t *testing.T, d *vfs.MountInfoDecoder, funcName s | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if err := gotErr(); !errors.Is(err, tc.wantErr) { | ||||
| 	if err := gotErr(); !reflect.DeepEqual(err, tc.wantErr) { | ||||
| 		if tc.wantError == "" { | ||||
| 			t.Errorf("%s: error = %v, wantErr %v", | ||||
| 			t.Errorf("%s: error = %#v, wantErr %#v", | ||||
| 				funcName, err, tc.wantErr) | ||||
| 		} else if err != nil && err.Error() != tc.wantError { | ||||
| 			t.Errorf("%s: error = %q, wantError %q", | ||||
|  | ||||
| @ -4,9 +4,14 @@ import ( | ||||
| 	"iter" | ||||
| 	"path" | ||||
| 	"strings" | ||||
| 	"syscall" | ||||
| ) | ||||
| 
 | ||||
| type UnfoldTargetError string | ||||
| 
 | ||||
| func (e UnfoldTargetError) Error() string { | ||||
| 	return "mount point " + string(e) + " never appeared in mountinfo" | ||||
| } | ||||
| 
 | ||||
| // MountInfoNode positions a [MountInfoEntry] in its mount hierarchy. | ||||
| type MountInfoNode struct { | ||||
| 	*MountInfoEntry | ||||
| @ -65,7 +70,8 @@ func (d *MountInfoDecoder) Unfold(target string) (*MountInfoNode, error) { | ||||
| 	} | ||||
| 
 | ||||
| 	if targetIndex == -1 { | ||||
| 		return nil, syscall.ESTALE | ||||
| 		// target does not exist in parsed mountinfo | ||||
| 		return nil, &DecoderError{Op: "unfold", Line: -1, Err: UnfoldTargetError(targetClean)} | ||||
| 	} | ||||
| 
 | ||||
| 	for _, cur := range mountinfo { | ||||
|  | ||||
| @ -1,11 +1,9 @@ | ||||
| package vfs_test | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"reflect" | ||||
| 	"slices" | ||||
| 	"strings" | ||||
| 	"syscall" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"hakurei.app/container/vfs" | ||||
| @ -26,7 +24,7 @@ func TestUnfold(t *testing.T) { | ||||
| 			"no match", | ||||
| 			sampleMountinfoBase, | ||||
| 			"/mnt", | ||||
| 			syscall.ESTALE, nil, nil, nil, | ||||
| 			&vfs.DecoderError{Op: "unfold", Line: -1, Err: vfs.UnfoldTargetError("/mnt")}, nil, nil, nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"cover", | ||||
| @ -55,7 +53,7 @@ func TestUnfold(t *testing.T) { | ||||
| 			d := vfs.NewMountInfoDecoder(strings.NewReader(tc.sample)) | ||||
| 			got, err := d.Unfold(tc.target) | ||||
| 
 | ||||
| 			if !errors.Is(err, tc.wantErr) { | ||||
| 			if !reflect.DeepEqual(err, tc.wantErr) { | ||||
| 				t.Errorf("Unfold: error = %v, wantErr %v", | ||||
| 					err, tc.wantErr) | ||||
| 			} | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user