diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-03-24 01:39:31 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-03-24 01:42:38 +0900 |
| commit | 0eb1bc6301591a9d8ebcd348238c2e98ee2a7bc0 (patch) | |
| tree | 011dd19d7e22ae3f8dfaf654d131184415e973b7 /test/sandbox/mount.go | |
| parent | 1eb837eab820e6a0365bddea93e0d1d91f957f8a (diff) | |
test/sandbox: verify outcome via mountinfo
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'test/sandbox/mount.go')
| -rw-r--r-- | test/sandbox/mount.go | 215 |
1 files changed, 104 insertions, 111 deletions
diff --git a/test/sandbox/mount.go b/test/sandbox/mount.go index d65824f2..27dc6bae 100644 --- a/test/sandbox/mount.go +++ b/test/sandbox/mount.go @@ -1,16 +1,18 @@ package sandbox /* +#cgo linux pkg-config: --static mount + #include <stdlib.h> #include <stdio.h> -#include <mntent.h> +#include <libmount.h> -const char *F_PROC_MOUNTS = ""; -const char *F_SET_TYPE = "r"; +const char *F_MOUNTINFO_PATH = "/proc/self/mountinfo"; */ import "C" import ( + "errors" "fmt" "iter" "runtime" @@ -18,147 +20,138 @@ import ( "unsafe" ) -type Mntent struct { - /* name of mounted filesystem */ - FSName string `json:"fsname"` - /* filesystem path prefix */ - Dir string `json:"dir"` - /* mount type (see mntent.h) */ - Type string `json:"type"` - /* mount options (see mntent.h) */ - Opts string `json:"opts"` - /* dump frequency in days */ - Freq int `json:"freq"` - /* pass number on parallel fsck */ - Passno int `json:"passno"` -} +var ( + ErrMountinfoParse = errors.New("invalid mountinfo records") + ErrMountinfoIter = errors.New("cannot allocate iterator") + ErrMountinfoFault = errors.New("cannot iterate on filesystems") +) -func (e *Mntent) String() string { - return fmt.Sprintf("%s %s %s %s %d %d", - e.FSName, e.Dir, e.Type, e.Opts, e.Freq, e.Passno) -} +type ( + Mountinfo struct { + mu sync.RWMutex + p string + err error -func (e *Mntent) Is(want *Mntent) bool { - if want == nil { - return e == nil - } - return (e.FSName == want.FSName || want.FSName == "\x00") && - (e.Dir == want.Dir || want.Dir == "\x00") && - (e.Type == want.Type || want.Type == "\x00") && - (e.Opts == want.Opts || want.Opts == "\x00") && - (e.Freq == want.Freq || want.Freq == -1) && - (e.Passno == want.Passno || want.Passno == -1) -} - -type MountsFile struct { - m *mounts - mu sync.Mutex - done bool -} + tb *C.struct_libmnt_table + itr *C.struct_libmnt_iter -func OpenMounts(name string) (*MountsFile, error) { - f := new(MountsFile) - f.m = new(mounts) - f.m.p = name - return f, f.m.open() -} + fs *C.struct_libmnt_fs + } -func (f *MountsFile) Err() error { return f.m.Err() } -func (f *MountsFile) Entries() iter.Seq[*Mntent] { - return func(yield func(*Mntent) bool) { - f.mu.Lock() - defer f.mu.Unlock() - if f.done { - return - } + // 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"` + } +) - for f.m.scan() { - e := new(Mntent) - f.m.copy(e) - if !yield(e) { - return - } - } - f.done = true - f.m.close() +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)) } -type mounts struct { - p string - f *C.FILE - mu sync.RWMutex +func NewMountinfo(p string) *Mountinfo { m := new(Mountinfo); m.p = p; return m } - ent *C.struct_mntent - err error -} +func (m *Mountinfo) Err() error { m.mu.RLock(); defer m.mu.RUnlock(); return m.err } -func (m *mounts) open() error { +func (m *Mountinfo) Parse() error { m.mu.Lock() defer m.mu.Unlock() - if m.f != nil { + if m.tb != nil { panic("open called twice") } if m.p == "" { - m.p = "/proc/mounts" + m.tb = C.mnt_new_table_from_file(C.F_MOUNTINFO_PATH) + } else { + name := C.CString(m.p) + m.tb = C.mnt_new_table_from_file(name) + C.free(unsafe.Pointer(name)) } - - name := C.CString(m.p) - f, err := C.setmntent(name, C.F_SET_TYPE) - C.free(unsafe.Pointer(name)) - - if f == nil { - return err + if m.tb == nil { + return ErrMountinfoParse } - m.f = f - runtime.SetFinalizer(m, (*mounts).close) - return err + 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 *mounts) close() { +func (m *Mountinfo) Unref() { m.mu.Lock() defer m.mu.Unlock() - if m.f == nil { - panic("close called before open") + if m.tb == nil { + panic("unref called before parse") } - C.endmntent(m.f) + C.mnt_unref_table(m.tb) + C.mnt_free_iter(m.itr) runtime.SetFinalizer(m, nil) } -func (m *mounts) scan() bool { - m.mu.Lock() - defer m.mu.Unlock() +func (m *Mountinfo) Entries() iter.Seq[*MountinfoEntry] { + return func(yield func(*MountinfoEntry) bool) { + m.mu.Lock() + defer m.mu.Unlock() - if m.f == nil { - panic("invalid file") - } + C.mnt_reset_iter(m.itr, -1) - m.ent, m.err = C.getmntent(m.f) - return m.ent != nil + 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 (m *mounts) Err() error { - m.mu.RLock() - defer m.mu.RUnlock() - - return m.err +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 (m *mounts) copy(v *Mntent) { - m.mu.RLock() - defer m.mu.RUnlock() - - if m.ent == nil { - panic("invalid entry") - } - v.FSName = C.GoString(m.ent.mnt_fsname) - v.Dir = C.GoString(m.ent.mnt_dir) - v.Type = C.GoString(m.ent.mnt_type) - v.Opts = C.GoString(m.ent.mnt_opts) - v.Freq = int(m.ent.mnt_freq) - v.Passno = int(m.ent.mnt_passno) +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) } |
