aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/testsuite/mountinfo/mountinfo.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/testsuite/mountinfo/mountinfo.go')
-rw-r--r--internal/testsuite/mountinfo/mountinfo.go177
1 files changed, 177 insertions, 0 deletions
diff --git a/internal/testsuite/mountinfo/mountinfo.go b/internal/testsuite/mountinfo/mountinfo.go
new file mode 100644
index 00000000..19892cd2
--- /dev/null
+++ b/internal/testsuite/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)
+}