aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-10-03 17:49:27 +0900
committerOphestra <cat@gensokyo.uk>2026-10-03 18:15:20 +0900
commitaa41002e8078a3ab00896cbd403e573c6dd817c2 (patch)
tree9ebcf82d99ff95a97a8661ffdaf86dd20c9213bf
parent4697e493fe59a2e61b5e8e8a9d0ae4b8e338e378 (diff)
test/internal/mountinfo: move from sandbox
This also improves its API. Signed-off-by: Ophestra <cat@gensokyo.uk>
-rw-r--r--.gitea/workflows/test.yml2
-rw-r--r--internal/rosa/package/hakurei.az3
-rw-r--r--internal/workflows/test.go2
-rw-r--r--test/internal/mountinfo/mountinfo.go177
-rw-r--r--test/internal/mountinfo/mountinfo_guard.go15
-rw-r--r--test/internal/mountinfo/mountinfo_test.go (renamed from test/internal/sandbox/mount_test.go)42
-rw-r--r--test/internal/sandbox/assert.go25
-rw-r--r--test/internal/sandbox/mount.go159
-rw-r--r--test/sandbox/tool/package.nix2
9 files changed, 235 insertions, 192 deletions
diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml
index 3cb08c5b..f1547782 100644
--- a/.gitea/workflows/test.yml
+++ b/.gitea/workflows/test.yml
@@ -1 +1 @@
-{"name":"Test","on":["push"],"jobs":{"hakurei":{"name":"Hakurei","runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run NixOS test","run":"nix build --out-link result --print-out-paths --print-build-logs ./test#checks.x86_64-linux.hakurei"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"hakurei-vm-output","path":"result/*","retention-days":1}}]},"race":{"name":"Hakurei (race detector)","runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run NixOS test","run":"nix build --out-link result --print-out-paths --print-build-logs ./test#checks.x86_64-linux.race"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"hakurei-race-vm-output","path":"result/*","retention-days":1}}]},"sandbox":{"name":"Sandbox","runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run NixOS test","run":"nix build --out-link result --print-out-paths --print-build-logs ./test#checks.x86_64-linux.sandbox"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"sandbox-vm-output","path":"result/*","retention-days":1}}]},"sandbox-race":{"name":"Sandbox (race detector)","runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run NixOS test","run":"nix build --out-link result --print-out-paths --print-build-logs ./test#checks.x86_64-linux.sandbox-race"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"sandbox-race-vm-output","path":"result/*","retention-days":1}}]},"sharefs":{"name":"ShareFS","runs-on":"rosa","steps":[{"name":"Fix container filesystem","run":"rm /var/run \u0026\u0026 ln -sf ../run /var"},{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Set up Go toolchain","uses":"actions/setup-go@v6","with":{"go-version-file":"go.mod"}},{"name":"Install packages","uses":"awalsh128/cache-apt-pkgs-action@v1","with":{"packages":"fuse3 fsmark","version":0,"execute_install_scripts":true}},{"name":"Request distribution","id":"dist","run":"HAKUREI_REV=\"$(git rev-parse --short HEAD)\" \u0026\u0026 /rosa/bin/mbf ci dist -o result . \"$(cat cmd/dist/VERSION)-$HAKUREI_REV\" \u0026\u0026 echo \"rev=$HAKUREI_REV\" \u003e\u003e \"$GITHUB_OUTPUT\""},{"name":"Install hakurei","run":"HAKUREI_VERSION=\"$(cat cmd/dist/VERSION)-${{ steps.dist.outputs.rev }}\" \u0026\u0026 tar xf result/hakurei-$HAKUREI_VERSION*-amd64.tar.gz \u0026\u0026 ./hakurei-$HAKUREI_VERSION*-amd64/install.sh \u0026\u0026 sudo -u ubuntu hakurei version \u0026\u0026 echo 'Defaults closefrom_override' \u003e /etc/sudoers.d/closefrom_override \u0026\u0026 mkdir /var/empty"},{"name":"Mount sharefs","run":"useradd -ru 1023 -md /var/lib/sdcard -k /var/empty -s /sbin/nologin media_rw \u0026\u0026 install -dm0 /sdcard \u0026\u0026 sharefs -o rw,noexec,nosuid,nodev,noatime,allow_other,mkdir,source=/var/lib/sdcard,setuid=1023,setgid=1023 /sdcard"},{"name":"Compile and run test suite","run":"sharefs -V \u0026\u0026 rm -rf result \u0026\u0026 go run -tags=testsuite ./test/sharefs"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"fs_mark","path":"result/*","retention-days":1}}]},"check":{"name":"Flake checks","needs":["hakurei","race","sandbox","sandbox-race"],"runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run checks","run":"nix --print-build-logs --experimental-features 'nix-command flakes' flake check ./test"}]},"dist":{"name":"Create distribution","runs-on":"rosa","steps":[{"name":"Fix container filesystem","run":"rm /var/run \u0026\u0026 ln -sf ../run /var"},{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Request distribution","id":"dist-test","run":"HAKUREI_REV=\"$(git rev-parse --short HEAD)\" \u0026\u0026 /rosa/bin/mbf ci dist -o result . \"$(cat cmd/dist/VERSION)-$HAKUREI_REV\" \u0026\u0026 echo \"rev=$HAKUREI_REV\" \u003e\u003e \"$GITHUB_OUTPUT\""},{"name":"Upload distribution","uses":"actions/upload-artifact@v3","with":{"name":"hakurei-${{ steps.dist-test.outputs.rev }}","path":"result/*","retention-days":1}}]}}}
+{"name":"Test","on":["push"],"jobs":{"hakurei":{"name":"Hakurei","runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run NixOS test","run":"nix build --out-link result --print-out-paths --print-build-logs ./test#checks.x86_64-linux.hakurei"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"hakurei-vm-output","path":"result/*","retention-days":1}}]},"race":{"name":"Hakurei (race detector)","runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run NixOS test","run":"nix build --out-link result --print-out-paths --print-build-logs ./test#checks.x86_64-linux.race"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"hakurei-race-vm-output","path":"result/*","retention-days":1}}]},"sandbox":{"name":"Sandbox","runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run NixOS test","run":"nix build --out-link result --print-out-paths --print-build-logs ./test#checks.x86_64-linux.sandbox"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"sandbox-vm-output","path":"result/*","retention-days":1}}]},"sandbox-race":{"name":"Sandbox (race detector)","runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run NixOS test","run":"nix build --out-link result --print-out-paths --print-build-logs ./test#checks.x86_64-linux.sandbox-race"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"sandbox-race-vm-output","path":"result/*","retention-days":1}}]},"sharefs":{"name":"ShareFS","runs-on":"rosa","steps":[{"name":"Fix container filesystem","run":"rm /var/run \u0026\u0026 ln -sf ../run /var"},{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Set up Go toolchain","uses":"actions/setup-go@v6","with":{"go-version-file":"go.mod"}},{"name":"Install packages","uses":"awalsh128/cache-apt-pkgs-action@v1","with":{"packages":"fuse3 fsmark","version":0,"execute_install_scripts":true}},{"name":"Request distribution","id":"dist","run":"HAKUREI_REV=\"$(git rev-parse --short HEAD)\" \u0026\u0026 /rosa/bin/mbf ci dist -o result . \"$(cat cmd/dist/VERSION)-$HAKUREI_REV\" \u0026\u0026 echo \"rev=$HAKUREI_REV\" \u003e\u003e \"$GITHUB_OUTPUT\""},{"name":"Install hakurei","run":"HAKUREI_VERSION=\"$(cat cmd/dist/VERSION)-${{ steps.dist.outputs.rev }}\" \u0026\u0026 tar xf result/hakurei-$HAKUREI_VERSION*-amd64.tar.gz \u0026\u0026 ./hakurei-$HAKUREI_VERSION*-amd64/install.sh \u0026\u0026 sudo -u ubuntu hakurei version \u0026\u0026 echo 'Defaults closefrom_override' \u003e /etc/sudoers.d/closefrom_override \u0026\u0026 mkdir /var/empty"},{"name":"Mount sharefs","run":"useradd -ru 1023 -md /var/lib/sdcard -k /var/empty -s /sbin/nologin media_rw \u0026\u0026 install -dm0 /sdcard \u0026\u0026 sharefs -o rw,noexec,nosuid,nodev,noatime,allow_other,mkdir,source=/var/lib/sdcard,setuid=1023,setgid=1023 /sdcard"},{"name":"Compile and run test suite","run":"sharefs -V \u0026\u0026 rm -rf result \u0026\u0026 go run -tags=testsuite ./test/sharefs"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"fs_mark","path":"result/*","retention-days":1}}]},"check":{"name":"Flake checks","needs":["hakurei","race","sandbox","sandbox-race"],"runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run checks","run":"nix --print-build-logs --experimental-features 'nix-command flakes' flake check ./test"}]},"dist":{"name":"Create distribution","runs-on":"rosa","steps":[{"name":"Fix container filesystem","run":"rm /var/run \u0026\u0026 ln -sf ../run /var"},{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Request distribution","id":"dist-test","run":"HAKUREI_REV=\"$(git rev-parse --short HEAD)\" \u0026\u0026 /rosa/bin/mbf ci dist -o result . \"$(cat cmd/dist/VERSION)-$HAKUREI_REV\" \u0026\u0026 echo \"rev=$HAKUREI_REV\" \u003e\u003e \"$GITHUB_OUTPUT\""},{"name":"Upload distribution","uses":"actions/upload-artifact@v3","with":{"name":"dist-${{ steps.dist-test.outputs.rev }}","path":"result/*","retention-days":1}}]}}}
diff --git a/internal/rosa/package/hakurei.az b/internal/rosa/package/hakurei.az
index 596eb956..5585002c 100644
--- a/internal/rosa/package/hakurei.az
+++ b/internal/rosa/package/hakurei.az
@@ -75,6 +75,7 @@ mkdir -p /work/system/bin/
xcb,
wayland,
wayland-protocols,
+ util-linux,
kernel-headers,
];
@@ -121,6 +122,7 @@ DESTDIR=/work "./${NAME}.sh"
xcb,
wayland,
wayland-protocols,
+ util-linux,
kernel-headers,
];
@@ -168,6 +170,7 @@ DESTDIR=/work "./${NAME}.sh"
xcb,
wayland,
wayland-protocols,
+ util-linux,
kernel-headers,
];
diff --git a/internal/workflows/test.go b/internal/workflows/test.go
index 40e1a076..723cf463 100644
--- a/internal/workflows/test.go
+++ b/internal/workflows/test.go
@@ -117,7 +117,7 @@ var _ = (&Workflow{
newCIRequest("distribution", "dist -o result", "dist-test"),
newUploadArtifact(
"distribution",
- "hakurei-${{ steps.dist-test.outputs.rev }}",
+ "dist-${{ steps.dist-test.outputs.rev }}",
),
},
}},
diff --git a/test/internal/mountinfo/mountinfo.go b/test/internal/mountinfo/mountinfo.go
new file mode 100644
index 00000000..19892cd2
--- /dev/null
+++ b/test/internal/mountinfo/mountinfo.go
@@ -0,0 +1,177 @@
+// Package mountinfo provides util-linux bindings for parsing
+// proc_pid_mountinfo(5).
+//
+// This package must never be used outside integration tests, a much better
+// implementation can be found in package vfs.
+//
+// Attempting to import this package outside testing causes the resulting
+// program to panic.
+package mountinfo
+
+/*
+#cgo linux pkg-config: --static mount
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <libmount.h>
+
+const char *HAKUREI_MOUNTINFO_PATH = "/proc/self/mountinfo";
+*/
+import "C"
+
+import (
+ "errors"
+ "fmt"
+ "runtime"
+ "unsafe"
+)
+
+var (
+ // ErrParse is returned by [Open] when encountering a bad record.
+ ErrParse = errors.New("invalid mountinfo record")
+ // ErrIter is returned by [Open] if an iterator cannot be allocated.
+ ErrIter = errors.New("cannot allocate iterator")
+ // ErrIterAdvance is stored when the iterator is unable to advance.
+ ErrIterAdvance = errors.New("unable to advance iterator")
+)
+
+type (
+ // Iter refers to libmnt iterator state.
+ Iter struct {
+ // Last stored error.
+ err error
+ // Whether iteration has concluded.
+ ok bool
+ // Whether Close had already been called.
+ closed bool
+
+ tb *C.struct_libmnt_table
+ itr *C.struct_libmnt_iter
+
+ fs *C.struct_libmnt_fs
+ }
+
+ // Entry represents deterministic mountinfo parts of a libmnt_fs entry.
+ Entry struct {
+ // mount ID: a unique ID for the mount (may be reused after umount(2)).
+ ID int `json:"id"`
+ // parent ID: the ID of the parent mount (or of self for the root of
+ // this mount namespace's mount tree).
+ Parent int `json:"parent"`
+ // root: the pathname of the directory in the filesystem which forms the
+ // root of this mount.
+ Root string `json:"root"`
+ // mount point: the pathname of the mount point relative to the
+ // process's root directory.
+ Target string `json:"target"`
+ // mount options: per-mount options (see mount(2)).
+ VfsOptstr string `json:"vfs_optstr"`
+ // filesystem type: the filesystem type in the form "type[.subtype]".
+ FsType string `json:"fstype"`
+ // mount source: filesystem-specific information or "none".
+ Source string `json:"source"`
+ // super options: per-superblock options (see mount(2)).
+ FsOptstr string `json:"fs_optstr"`
+ }
+)
+
+// Copy populates v with the current record.
+func (m *Iter) Copy(v *Entry) {
+ if m.fs == nil {
+ panic("invalid entry")
+ }
+ v.ID = int(C.mnt_fs_get_id(m.fs))
+ v.Parent = int(C.mnt_fs_get_parent_id(m.fs))
+ v.Root = C.GoString(C.mnt_fs_get_root(m.fs))
+ v.Target = C.GoString(C.mnt_fs_get_target(m.fs))
+ v.VfsOptstr = C.GoString(C.mnt_fs_get_vfs_options(m.fs))
+ v.FsType = C.GoString(C.mnt_fs_get_fstype(m.fs))
+ v.Source = C.GoString(C.mnt_fs_get_source(m.fs))
+ v.FsOptstr = C.GoString(C.mnt_fs_get_fs_options(m.fs))
+}
+
+// Err returns the saved iterator error.
+func (m *Iter) Err() error { return m.err }
+
+// Open opens a mountinfo document. If name is an empty string, the mountinfo
+// document of the current process is opened instead.
+func Open(name string) (*Iter, error) {
+ var m Iter
+ if name == "" {
+ m.tb = C.mnt_new_table_from_file(C.HAKUREI_MOUNTINFO_PATH)
+ } else {
+ _name := C.CString(name)
+ m.tb = C.mnt_new_table_from_file(_name)
+ C.free(unsafe.Pointer(_name))
+ }
+ if m.tb == nil {
+ return nil, ErrParse
+ }
+ m.itr = C.mnt_new_iter(C.MNT_ITER_FORWARD)
+ if m.itr == nil {
+ C.mnt_unref_table(m.tb)
+ return nil, ErrIter
+ }
+ m.ok = true
+
+ runtime.SetFinalizer(&m, (*Iter).Close)
+ return &m, nil
+}
+
+// Close frees the iterator.
+func (m *Iter) Close() {
+ if m.closed {
+ return
+ }
+ if m.tb == nil {
+ panic("unref called before open")
+ }
+
+ C.mnt_unref_table(m.tb)
+ C.mnt_free_iter(m.itr)
+ m.closed = true
+ runtime.SetFinalizer(m, nil)
+}
+
+// Reset resets the iterator to the first record for reuse.
+func (m *Iter) Reset() {
+ if m.err != nil {
+ panic("attempting to reset a faulted iterator")
+ }
+ m.ok = true
+ C.mnt_reset_iter(m.itr, -1)
+}
+
+// Next advances the iterator to the next record. The record may be copied if
+// Next returns true.
+func (m *Iter) Next() bool {
+ if !m.ok || m.err != nil {
+ return false
+ }
+
+ r := C.mnt_table_next_fs(m.tb, m.itr, &m.fs)
+ if r < 0 {
+ m.err = ErrIterAdvance
+ }
+ m.ok = r == 0
+ return m.ok
+}
+
+// EqualWithIgnore compares e with want, ignoring fields with the specified
+// ignore value.
+func (e *Entry) EqualWithIgnore(want *Entry, ignore string) bool {
+ return (e.ID == want.ID || want.ID == -1) &&
+ (e.Parent == want.Parent || want.Parent == -1) &&
+ (e.Root == want.Root || want.Root == ignore) &&
+ (e.Target == want.Target || want.Target == ignore) &&
+ (e.VfsOptstr == want.VfsOptstr || want.VfsOptstr == ignore) &&
+ (e.FsType == want.FsType || want.FsType == ignore) &&
+ (e.Source == want.Source || want.Source == ignore) &&
+ (e.FsOptstr == want.FsOptstr || want.FsOptstr == ignore)
+}
+
+// String returns a text representation of e loosely following the kernel format.
+func (e *Entry) String() string {
+ return fmt.Sprintf("%d %d %s %s %s %s %s %s",
+ e.ID, e.Parent, e.Root, e.Target, e.VfsOptstr, e.FsType, e.Source, e.FsOptstr)
+}
diff --git a/test/internal/mountinfo/mountinfo_guard.go b/test/internal/mountinfo/mountinfo_guard.go
new file mode 100644
index 00000000..aaf63ac4
--- /dev/null
+++ b/test/internal/mountinfo/mountinfo_guard.go
@@ -0,0 +1,15 @@
+//go:build !testsuite && !tester
+
+package mountinfo
+
+import (
+ "os"
+ "testing"
+)
+
+func init() {
+ if !testing.Testing() {
+ println("package mountinfo imported in non-testsuite program")
+ os.Exit(1)
+ }
+}
diff --git a/test/internal/sandbox/mount_test.go b/test/internal/mountinfo/mountinfo_test.go
index a565b0a0..45cfdf5f 100644
--- a/test/internal/sandbox/mount_test.go
+++ b/test/internal/mountinfo/mountinfo_test.go
@@ -1,13 +1,11 @@
-//go:build testtool
-
-package sandbox_test
+package mountinfo_test
import (
"os"
"path/filepath"
"testing"
- "hakurei.app/test/internal/sandbox"
+ "hakurei.app/test/internal/mountinfo"
)
func TestMountinfo(t *testing.T) {
@@ -15,7 +13,7 @@ func TestMountinfo(t *testing.T) {
name string
sample string
- want []*sandbox.MountinfoEntry
+ want []*mountinfo.Entry
}{
{"util-linux", `15 20 0:3 / /proc rw,relatime - proc /proc rw
16 20 0:15 / /sys rw,relatime - sysfs /sys rw
@@ -49,7 +47,7 @@ func TestMountinfo(t *testing.T) {
44 41 0:36 / /home/kzak/.gvfs rw,nosuid,nodev,relatime - fuse.gvfs-fuse-daemon gvfs-fuse-daemon rw,user_id=500,group_id=500
45 20 0:37 / /var/lib/nfs/rpc_pipefs rw,relatime - rpc_pipefs sunrpc rw
47 20 0:38 / /mnt/sounds rw,relatime - cifs //foo.home/bar/ rw,unc=\\foo.home\bar,username=kzak,domain=SRGROUP,uid=0,noforceuid,gid=0,noforcegid,addr=192.168.111.1,posixpaths,serverino,acl,rsize=16384,wsize=57344
-49 20 0:56 / /mnt/test/foobar rw,relatime,nosymfollow shared:323 - tmpfs tmpfs rw`, []*sandbox.MountinfoEntry{
+49 20 0:56 / /mnt/test/foobar rw,relatime,nosymfollow shared:323 - tmpfs tmpfs rw`, []*mountinfo.Entry{
e(15, 20, "/", "/proc", "rw,relatime", "proc", "/proc", "rw"),
e(16, 20, "/", "/sys", "rw,relatime", "sysfs", "/sys", "rw"),
e(17, 20, "/", "/dev", "rw,relatime", "devtmpfs", "udev", "rw,size=1983516k,nr_inodes=495879,mode=755"),
@@ -93,33 +91,35 @@ func TestMountinfo(t *testing.T) {
}
t.Run(tc.name, func(t *testing.T) {
- m := sandbox.NewMountinfo(name)
- if err := m.Parse(); err != nil {
- t.Fatalf("Parse: error = %v", err)
+ m, err := mountinfo.Open(name)
+ if err != nil {
+ t.Fatalf("Open: error = %v", err)
}
+ t.Cleanup(m.Close)
i := 0
- for ent := range m.Entries() {
+ var ent mountinfo.Entry
+ for m.Next() {
+ m.Copy(&ent)
+
if i == len(tc.want) {
- t.Errorf("Entries: got more than %d entries", i)
+ t.Errorf("Next: got more than %d entries", i)
t.FailNow()
}
if !ent.EqualWithIgnore(tc.want[i], "\x00") {
- t.Errorf("Entries: entry %d\n got: %#v\nwant: %#v", i,
+ t.Errorf("Next: entry %d\n got: %#v\nwant: %#v", i,
ent, &tc.want[i])
t.FailNow()
} else {
- t.Logf("%s", ent)
+ t.Logf("%s", &ent)
}
i++
}
- if err := m.Err(); err != nil {
- t.Fatalf("Mountinfo: error = %v", err)
+ if err = m.Err(); err != nil {
+ t.Fatalf("Err: %v", err)
}
-
- m.Unref()
})
if err := os.Remove(name); err != nil {
@@ -129,9 +129,11 @@ func TestMountinfo(t *testing.T) {
}
func e(
- id, parent int, root, target, vfsOptstr string, fsType, source, fsOptstr string,
-) *sandbox.MountinfoEntry {
- return &sandbox.MountinfoEntry{
+ id, parent int,
+ root, target, vfsOptstr string,
+ fsType, source, fsOptstr string,
+) *mountinfo.Entry {
+ return &mountinfo.Entry{
ID: id,
Parent: parent,
Root: root,
diff --git a/test/internal/sandbox/assert.go b/test/internal/sandbox/assert.go
index e6fe926b..2125419b 100644
--- a/test/internal/sandbox/assert.go
+++ b/test/internal/sandbox/assert.go
@@ -19,6 +19,8 @@ import (
"os"
"path/filepath"
"syscall"
+
+ "hakurei.app/test/internal/mountinfo"
)
var (
@@ -31,10 +33,10 @@ func printf(format string, v ...any) { printfFunc(format, v...) }
func fatalf(format string, v ...any) { fatalfFunc(format, v...) }
type TestCase struct {
- Env []string `json:"env"`
- FS *FS `json:"fs"`
- Mount []*MountinfoEntry `json:"mount"`
- Seccomp bool `json:"seccomp"`
+ Env []string `json:"env"`
+ FS *FS `json:"fs"`
+ Mount []*mountinfo.Entry `json:"mount"`
+ Seccomp bool `json:"seccomp"`
TrySocket string `json:"try_socket,omitempty"`
SocketAbstract bool `json:"socket_abstract,omitempty"`
@@ -120,15 +122,18 @@ func (t *T) MustCheck(want *TestCase) {
var fail bool
m := mustParseMountinfo(t.MountsPath)
i := 0
- for ent := range m.Entries() {
+ var ent mountinfo.Entry
+ for m.Next() {
+ m.Copy(&ent)
+
if i == len(want.Mount) {
fatalf("got more than %d entries", i)
}
if !ent.EqualWithIgnore(want.Mount[i], "//ignore") {
fail = true
- printf("[FAIL] %s", ent)
+ printf("[FAIL] %s", &ent)
} else {
- printf("[ OK ] %s", ent)
+ printf("[ OK ] %s", &ent)
}
i++
@@ -262,9 +267,9 @@ func mustDecode(wantFilePath string, v any) {
}
}
-func mustParseMountinfo(name string) *Mountinfo {
- m := NewMountinfo(name)
- if err := m.Parse(); err != nil {
+func mustParseMountinfo(name string) *mountinfo.Iter {
+ m, err := mountinfo.Open(name)
+ if err != nil {
fatalf("%v", err)
panic("unreachable")
}
diff --git a/test/internal/sandbox/mount.go b/test/internal/sandbox/mount.go
deleted file mode 100644
index 791a68b9..00000000
--- a/test/internal/sandbox/mount.go
+++ /dev/null
@@ -1,159 +0,0 @@
-//go:build testtool
-
-package sandbox
-
-/*
-#cgo linux pkg-config: --static mount
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <libmount.h>
-
-const char *HAKUREI_MOUNTINFO_PATH = "/proc/self/mountinfo";
-*/
-import "C"
-
-import (
- "errors"
- "fmt"
- "iter"
- "runtime"
- "sync"
- "unsafe"
-)
-
-var (
- ErrMountinfoParse = errors.New("invalid mountinfo records")
- ErrMountinfoIter = errors.New("cannot allocate iterator")
- ErrMountinfoFault = errors.New("cannot iterate on filesystems")
-)
-
-type (
- Mountinfo struct {
- mu sync.RWMutex
- p string
- err error
-
- tb *C.struct_libmnt_table
- itr *C.struct_libmnt_iter
-
- fs *C.struct_libmnt_fs
- }
-
- // MountinfoEntry represents deterministic mountinfo parts of a libmnt_fs entry.
- MountinfoEntry struct {
- // mount ID: a unique ID for the mount (may be reused after umount(2)).
- ID int `json:"id"`
- // parent ID: the ID of the parent mount (or of self for the root of this mount namespace's mount tree).
- Parent int `json:"parent"`
- // root: the pathname of the directory in the filesystem which forms the root of this mount.
- Root string `json:"root"`
- // mount point: the pathname of the mount point relative to the process's root directory.
- Target string `json:"target"`
- // mount options: per-mount options (see mount(2)).
- VfsOptstr string `json:"vfs_optstr"`
- // filesystem type: the filesystem type in the form "type[.subtype]".
- FsType string `json:"fstype"`
- // mount source: filesystem-specific information or "none".
- Source string `json:"source"`
- // super options: per-superblock options (see mount(2)).
- FsOptstr string `json:"fs_optstr"`
- }
-)
-
-func (m *Mountinfo) copy(v *MountinfoEntry) {
- if m.fs == nil {
- panic("invalid entry")
- }
- v.ID = int(C.mnt_fs_get_id(m.fs))
- v.Parent = int(C.mnt_fs_get_parent_id(m.fs))
- v.Root = C.GoString(C.mnt_fs_get_root(m.fs))
- v.Target = C.GoString(C.mnt_fs_get_target(m.fs))
- v.VfsOptstr = C.GoString(C.mnt_fs_get_vfs_options(m.fs))
- v.FsType = C.GoString(C.mnt_fs_get_fstype(m.fs))
- v.Source = C.GoString(C.mnt_fs_get_source(m.fs))
- v.FsOptstr = C.GoString(C.mnt_fs_get_fs_options(m.fs))
-}
-
-func NewMountinfo(p string) *Mountinfo { m := new(Mountinfo); m.p = p; return m }
-
-func (m *Mountinfo) Err() error { m.mu.RLock(); defer m.mu.RUnlock(); return m.err }
-
-func (m *Mountinfo) Parse() error {
- m.mu.Lock()
- defer m.mu.Unlock()
-
- if m.tb != nil {
- panic("open called twice")
- }
-
- if m.p == "" {
- m.tb = C.mnt_new_table_from_file(C.HAKUREI_MOUNTINFO_PATH)
- } else {
- name := C.CString(m.p)
- m.tb = C.mnt_new_table_from_file(name)
- C.free(unsafe.Pointer(name))
- }
- if m.tb == nil {
- return ErrMountinfoParse
- }
- m.itr = C.mnt_new_iter(C.MNT_ITER_FORWARD)
- if m.itr == nil {
- C.mnt_unref_table(m.tb)
- return ErrMountinfoIter
- }
-
- runtime.SetFinalizer(m, (*Mountinfo).Unref)
- return nil
-}
-
-func (m *Mountinfo) Unref() {
- m.mu.Lock()
- defer m.mu.Unlock()
-
- if m.tb == nil {
- panic("unref called before parse")
- }
-
- C.mnt_unref_table(m.tb)
- C.mnt_free_iter(m.itr)
- runtime.SetFinalizer(m, nil)
-}
-
-func (m *Mountinfo) Entries() iter.Seq[*MountinfoEntry] {
- return func(yield func(*MountinfoEntry) bool) {
- m.mu.Lock()
- defer m.mu.Unlock()
-
- C.mnt_reset_iter(m.itr, -1)
-
- var rc C.int
- ent := new(MountinfoEntry)
- for rc = C.mnt_table_next_fs(m.tb, m.itr, &m.fs); rc == 0; rc = C.mnt_table_next_fs(m.tb, m.itr, &m.fs) {
- m.copy(ent)
- if !yield(ent) {
- return
- }
- }
- if rc < 0 {
- m.err = ErrMountinfoFault
- return
- }
- }
-}
-
-func (e *MountinfoEntry) EqualWithIgnore(want *MountinfoEntry, ignore string) bool {
- return (e.ID == want.ID || want.ID == -1) &&
- (e.Parent == want.Parent || want.Parent == -1) &&
- (e.Root == want.Root || want.Root == ignore) &&
- (e.Target == want.Target || want.Target == ignore) &&
- (e.VfsOptstr == want.VfsOptstr || want.VfsOptstr == ignore) &&
- (e.FsType == want.FsType || want.FsType == ignore) &&
- (e.Source == want.Source || want.Source == ignore) &&
- (e.FsOptstr == want.FsOptstr || want.FsOptstr == ignore)
-}
-
-func (e *MountinfoEntry) String() string {
- return fmt.Sprintf("%d %d %s %s %s %s %s %s",
- e.ID, e.Parent, e.Root, e.Target, e.VfsOptstr, e.FsType, e.Source, e.FsOptstr)
-}
diff --git a/test/sandbox/tool/package.nix b/test/sandbox/tool/package.nix
index 13e18272..bd57b432 100644
--- a/test/sandbox/tool/package.nix
+++ b/test/sandbox/tool/package.nix
@@ -17,7 +17,7 @@ buildGoModule rec {
};
vendorHash = null;
- tags = [ "testtool" ];
+ tags = [ "testtool" "tester" ];
buildInputs = [ util-linux ];
nativeBuildInputs = [ pkg-config ];