test/sandbox: verify outcome via mountinfo
Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
@@ -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
|
||||
tb *C.struct_libmnt_table
|
||||
itr *C.struct_libmnt_iter
|
||||
|
||||
fs *C.struct_libmnt_fs
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func OpenMounts(name string) (*MountsFile, error) {
|
||||
f := new(MountsFile)
|
||||
f.m = new(mounts)
|
||||
f.m.p = name
|
||||
return f, f.m.open()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
for f.m.scan() {
|
||||
e := new(Mntent)
|
||||
f.m.copy(e)
|
||||
if !yield(e) {
|
||||
return
|
||||
}
|
||||
}
|
||||
f.done = true
|
||||
f.m.close()
|
||||
// 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"`
|
||||
}
|
||||
)
|
||||
|
||||
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))
|
||||
}
|
||||
if m.tb == nil {
|
||||
return ErrMountinfoParse
|
||||
}
|
||||
m.itr = C.mnt_new_iter(C.MNT_ITER_FORWARD)
|
||||
if m.itr == nil {
|
||||
C.mnt_unref_table(m.tb)
|
||||
return ErrMountinfoIter
|
||||
}
|
||||
|
||||
name := C.CString(m.p)
|
||||
f, err := C.setmntent(name, C.F_SET_TYPE)
|
||||
C.free(unsafe.Pointer(name))
|
||||
|
||||
if f == nil {
|
||||
return err
|
||||
}
|
||||
m.f = f
|
||||
runtime.SetFinalizer(m, (*mounts).close)
|
||||
return err
|
||||
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)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
m.ent, m.err = C.getmntent(m.f)
|
||||
return m.ent != nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user