1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
|
//go:build testtool
package sandbox
/*
#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"
"iter"
"runtime"
"sync"
"unsafe"
)
var (
ErrMountinfoParse = errors.New("invalid mountinfo records")
ErrMountinfoIter = errors.New("cannot allocate iterator")
ErrMountinfoFault = errors.New("cannot iterate on filesystems")
)
type (
Mountinfo struct {
mu sync.RWMutex
p string
err error
tb *C.struct_libmnt_table
itr *C.struct_libmnt_iter
fs *C.struct_libmnt_fs
}
// 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))
}
func NewMountinfo(p string) *Mountinfo { m := new(Mountinfo); m.p = p; return m }
func (m *Mountinfo) Err() error { m.mu.RLock(); defer m.mu.RUnlock(); return m.err }
func (m *Mountinfo) Parse() error {
m.mu.Lock()
defer m.mu.Unlock()
if m.tb != nil {
panic("open called twice")
}
if m.p == "" {
m.tb = C.mnt_new_table_from_file(C.HAKUREI_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
}
runtime.SetFinalizer(m, (*Mountinfo).Unref)
return nil
}
func (m *Mountinfo) Unref() {
m.mu.Lock()
defer m.mu.Unlock()
if m.tb == nil {
panic("unref called before parse")
}
C.mnt_unref_table(m.tb)
C.mnt_free_iter(m.itr)
runtime.SetFinalizer(m, nil)
}
func (m *Mountinfo) Entries() iter.Seq[*MountinfoEntry] {
return func(yield func(*MountinfoEntry) bool) {
m.mu.Lock()
defer m.mu.Unlock()
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
}
}
}
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 (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)
}
|