summaryrefslogtreecommitdiff
path: root/cmd/ioworkload
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-20 15:06:02 +0300
committerPaul Buetow <paul@buetow.org>2026-05-20 15:06:02 +0300
commit271af607921ceabc640271c475a66e45b9460d3f (patch)
tree6ae443fd372dbeea947cba7bd5851f7936f354b5 /cmd/ioworkload
parent63184df8d5e30f70011a97d862103fa38d797bb3 (diff)
feat: add mount/fs management syscall tracing for c7
Diffstat (limited to 'cmd/ioworkload')
-rw-r--r--cmd/ioworkload/scenario_mountfs.go91
-rw-r--r--cmd/ioworkload/scenario_mountfs_test.go30
-rw-r--r--cmd/ioworkload/scenarios.go1
3 files changed, 122 insertions, 0 deletions
diff --git a/cmd/ioworkload/scenario_mountfs.go b/cmd/ioworkload/scenario_mountfs.go
new file mode 100644
index 0000000..4c48ded
--- /dev/null
+++ b/cmd/ioworkload/scenario_mountfs.go
@@ -0,0 +1,91 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "runtime"
+ "syscall"
+ "unsafe"
+
+ "golang.org/x/sys/unix"
+)
+
+type mountIDReq struct {
+ Size uint32
+ Pad uint32
+ MntID uint64
+ Param uint64
+}
+
+func mountfsManagement() error {
+ dir, cleanup, err := makeTempDir("mountfs-management")
+ if err != nil {
+ return err
+ }
+ defer cleanup()
+
+ mountPoint := filepath.Join(dir, "mnt")
+ if err := os.Mkdir(mountPoint, 0o755); err != nil {
+ return fmt.Errorf("mkdir mountpoint: %w", err)
+ }
+
+ swapFile := filepath.Join(dir, "swapfile")
+ if err := os.WriteFile(swapFile, []byte("swap"), 0o600); err != nil {
+ return fmt.Errorf("write swap file: %w", err)
+ }
+
+ mountPath := mustCStringPtr(mountPoint)
+ swapPath := mustCStringPtr(swapFile)
+ newRoot := mustCStringPtr(mountPoint)
+ putOld := mustCStringPtr(dir)
+ tmpfs := mustCStringPtr("tmpfs")
+ none := mustCStringPtr("none")
+ atFDCWDInt := int64(unix.AT_FDCWD)
+ atFDCWD := uintptr(atFDCWDInt)
+
+ // Best-effort coverage: these calls are expected to fail on most hosts
+ // without CAP_SYS_ADMIN, but still exercise syscall tracing paths.
+ _, _, _ = syscall.RawSyscall6(unix.SYS_MOUNT, uintptr(unsafe.Pointer(none)), uintptr(unsafe.Pointer(mountPath)), uintptr(unsafe.Pointer(tmpfs)), 0, 0, 0)
+ _, _, _ = syscall.RawSyscall(unix.SYS_UMOUNT2, uintptr(unsafe.Pointer(mountPath)), 0, 0)
+ _, _, _ = syscall.RawSyscall(unix.SYS_UMOUNT2, uintptr(unsafe.Pointer(mountPath)), uintptr(unix.MNT_DETACH), 0)
+ _, _, _ = syscall.RawSyscall6(unix.SYS_MOVE_MOUNT, atFDCWD, uintptr(unsafe.Pointer(mountPath)), atFDCWD, uintptr(unsafe.Pointer(mountPath)), 0, 0)
+ _, _, _ = syscall.RawSyscall(unix.SYS_FSMOUNT, ^uintptr(0), 0, 0)
+ _, _, _ = syscall.RawSyscall(unix.SYS_PIVOT_ROOT, uintptr(unsafe.Pointer(newRoot)), uintptr(unsafe.Pointer(putOld)), 0)
+ _, _, _ = syscall.RawSyscall6(unix.SYS_QUOTACTL, 0, uintptr(unsafe.Pointer(mountPath)), 0, 0, 0, 0)
+ _, _, _ = syscall.RawSyscall(unix.SYS_SWAPON, uintptr(unsafe.Pointer(swapPath)), 0, 0)
+ _, _, _ = syscall.RawSyscall(unix.SYS_SWAPOFF, uintptr(unsafe.Pointer(swapPath)), 0, 0)
+
+ req := mountIDReq{Size: uint32(unsafe.Sizeof(mountIDReq{}))}
+ var statBuf [256]byte
+ _, _, _ = syscall.RawSyscall6(unix.SYS_STATMOUNT, uintptr(unsafe.Pointer(&req)), uintptr(unsafe.Pointer(&statBuf[0])), uintptr(len(statBuf)), 0, 0, 0)
+
+ var mountIDs [8]uint64
+ _, _, _ = syscall.RawSyscall6(unix.SYS_LISTMOUNT, uintptr(unsafe.Pointer(&req)), uintptr(unsafe.Pointer(&mountIDs[0])), uintptr(len(mountIDs)), 0, 0, 0)
+
+ if nr, err := listnsSyscallNr(); err == nil {
+ var nsIDs [8]uint64
+ _, _, _ = syscall.RawSyscall6(nr, uintptr(unsafe.Pointer(&req)), uintptr(unsafe.Pointer(&nsIDs[0])), uintptr(len(nsIDs)), 0, 0, 0)
+ }
+
+ return nil
+}
+
+func listnsSyscallNr() (uintptr, error) {
+ return listnsSyscallNrForArch(runtime.GOARCH)
+}
+
+func listnsSyscallNrForArch(arch string) (uintptr, error) {
+ // __NR_listns was introduced from asm-generic numbering where amd64/arm64 use 470.
+ switch arch {
+ case "amd64", "arm64":
+ return 470, nil
+ default:
+ return 0, fmt.Errorf("listns syscall number not defined for GOARCH=%s", arch)
+ }
+}
+
+func mustCStringPtr(s string) *byte {
+ p, _ := unix.BytePtrFromString(s)
+ return p
+}
diff --git a/cmd/ioworkload/scenario_mountfs_test.go b/cmd/ioworkload/scenario_mountfs_test.go
new file mode 100644
index 0000000..c1c317a
--- /dev/null
+++ b/cmd/ioworkload/scenario_mountfs_test.go
@@ -0,0 +1,30 @@
+package main
+
+import "testing"
+
+func TestListnsSyscallNrForArch(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ arch string
+ want uintptr
+ wantErr bool
+ }{
+ {name: "amd64", arch: "amd64", want: 470},
+ {name: "arm64", arch: "arm64", want: 470},
+ {name: "unsupported", arch: "riscv64", wantErr: true},
+ } {
+ got, err := listnsSyscallNrForArch(tc.arch)
+ if tc.wantErr {
+ if err == nil {
+ t.Fatalf("%s: expected error", tc.name)
+ }
+ continue
+ }
+ if err != nil {
+ t.Fatalf("%s: unexpected error: %v", tc.name, err)
+ }
+ if got != tc.want {
+ t.Fatalf("%s: got %d, want %d", tc.name, got, tc.want)
+ }
+ }
+}
diff --git a/cmd/ioworkload/scenarios.go b/cmd/ioworkload/scenarios.go
index e0827a5..79d4e6d 100644
--- a/cmd/ioworkload/scenarios.go
+++ b/cmd/ioworkload/scenarios.go
@@ -34,6 +34,7 @@ var scenarios = map[string]func() error{
"pipe2-basic": pipe2Basic,
"eventfd-basic": eventfdBasic,
"eventfd2-basic": eventfd2Basic,
+ "mountfs-management": mountfsManagement,
"polling-epoll": pollingEpoll,
"sleep-syscalls": sleepSyscalls,
"family-mixed": familyMixed,