// 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 #include #include 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) }