diff options
| author | Ophestra <cat@gensokyo.uk> | 2026-10-03 17:49:27 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2026-10-03 18:15:20 +0900 |
| commit | aa41002e8078a3ab00896cbd403e573c6dd817c2 (patch) | |
| tree | 9ebcf82d99ff95a97a8661ffdaf86dd20c9213bf /test/internal/mountinfo/mountinfo.go | |
| parent | 4697e493fe59a2e61b5e8e8a9d0ae4b8e338e378 (diff) | |
test/internal/mountinfo: move from sandbox
This also improves its API.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'test/internal/mountinfo/mountinfo.go')
| -rw-r--r-- | test/internal/mountinfo/mountinfo.go | 177 |
1 files changed, 177 insertions, 0 deletions
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) +} |
