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