Skip to content

Commit 87b79da

Browse files
committed
Mount Options
1 parent 686fc19 commit 87b79da

11 files changed

Lines changed: 750 additions & 43 deletions

api/types/types_model.go

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -51,49 +51,6 @@ type Instance struct {
5151
Fields map[string]string `json:"fields,omitempty"`
5252
}
5353

54-
// MountInfo reveals information about a particular mounted filesystem. This
55-
// struct is populated from the content in the /proc/<pid>/mountinfo file.
56-
type MountInfo struct {
57-
// ID is a unique identifier of the mount (may be reused after umount).
58-
ID int `json:"id"`
59-
60-
// Parent indicates the ID of the mount parent (or of self for the top of
61-
// the mount tree).
62-
Parent int `json:"parent"`
63-
64-
// Major indicates one half of the device ID which identifies the device
65-
// class.
66-
Major int `json:"major"`
67-
68-
// Minor indicates one half of the device ID which identifies a specific
69-
// instance of device.
70-
Minor int `json:"minor"`
71-
72-
// Root of the mount within the filesystem.
73-
Root string `json:"root"`
74-
75-
// MountPoint indicates the mount point relative to the process's root.
76-
MountPoint string `json:"mountPoint"`
77-
78-
// Opts represents mount-specific options.
79-
Opts string `json:"opts"`
80-
81-
// Optional represents optional fields.
82-
Optional string `json:"optional"`
83-
84-
// FSType indicates the type of filesystem, such as EXT3.
85-
FSType string `json:"fsType"`
86-
87-
// DevicePath is the path of the mounted path.
88-
DevicePath FileSystemDevicePath `json:"devicePath"`
89-
90-
// VFSOpts represents per super block options.
91-
VFSOpts string `json:"vfsOpts"`
92-
93-
// Fields are additional properties that can be defined for this type.
94-
Fields map[string]string `json:"fields,omitempty"`
95-
}
96-
9754
// Snapshot provides information about a storage-layer snapshot.
9855
type Snapshot struct {
9956
// A description of the snapshot.

api/types/types_mount_info.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package types
2+
3+
// MountInfo reveals information about a particular mounted filesystem. This
4+
// struct is populated from the content in the /proc/<pid>/mountinfo file.
5+
type MountInfo struct {
6+
7+
// DevicePath is the path of the mounted path.
8+
DevicePath FileSystemDevicePath `json:"devicePath"`
9+
10+
// MountPoint indicates the mount point relative to the process's root.
11+
MountPoint string `json:"mountPoint"`
12+
13+
// FSType indicates the type of filesystem, such as EXT3.
14+
FSType string `json:"fsType"`
15+
16+
// Opts represents mount-specific options.
17+
Opts MountOptions `json:"opts"`
18+
}
19+
20+
// MarshalText marshals the MountInfo object to its textual representation.
21+
func (i *MountInfo) String() string {
22+
if s, err := i.MarshalText(); err == nil {
23+
return string(s)
24+
}
25+
return ""
26+
}
27+
28+
// ParseMountInfo parses mount information.
29+
func ParseMountInfo(text string) *MountInfo {
30+
i := &MountInfo{}
31+
i.UnmarshalText([]byte(text))
32+
return i
33+
}
34+
35+
// UnmarshalText marshals the MountInfo from its textual representation.
36+
func (i *MountInfo) UnmarshalText(data []byte) error {
37+
38+
m := mountInfoRX.FindSubmatch(data)
39+
if len(m) == 0 {
40+
return nil
41+
}
42+
43+
i.DevicePath = FileSystemDevicePath(m[1])
44+
i.MountPoint = string(m[2])
45+
i.FSType = string(m[3])
46+
i.Opts.UnmarshalText(m[4])
47+
48+
return nil
49+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package types
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"regexp"
7+
)
8+
9+
/*
10+
$ mount
11+
/dev/disk1 on / (hfs, local, journaled)
12+
devfs on /dev (devfs, local, nobrowse)
13+
map -hosts on /net (autofs, nosuid, automounted, nobrowse)
14+
map auto_home on /home (autofs, automounted, nobrowse)
15+
/tmp/one on /private/tmp/bind-one (osxfusefs, nodev, nosuid, synchronous, mounted by akutz)
16+
bindfs@osxfuse1 on /private/tmp/bind-two (osxfusefs, nodev, nosuid, read-only, synchronous, mounted by akutz)
17+
/dev/disk2s1 on /Volumes/VirtualBox (hfs, local, nodev, nosuid, read-only, noowners, quarantine, mounted by akutz)
18+
*/
19+
20+
// MarshalText marshals the MountInfo object to its textual representation.
21+
func (i *MountInfo) MarshalText() ([]byte, error) {
22+
buf := &bytes.Buffer{}
23+
fmt.Fprintf(buf, "%s on %s (%s", i.DevicePath, i.MountPoint, i.FSType)
24+
if len(i.Opts) == 0 {
25+
fmt.Fprint(buf, ")")
26+
} else {
27+
fmt.Fprintf(buf, ", %s)", i.Opts)
28+
}
29+
return buf.Bytes(), nil
30+
}
31+
32+
/*
33+
mountInfoRX is the regex used for matching the output of the Linux mount cmd
34+
35+
$1 = devicePath
36+
$2 = mountPoint
37+
$3 = fileSystemType
38+
$4 = mountOpts
39+
*/
40+
var mountInfoRX = regexp.MustCompile(`^(.+) on (.+) \((.+?)(?:, (.+))\)$`)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// +build darwin
2+
3+
package types
4+
5+
import (
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestMountInfoParse(t *testing.T) {
12+
expected := &MountInfo{
13+
DevicePath: FileSystemDevicePath("/dev/disk1"),
14+
MountPoint: "/",
15+
Opts: MountOptions{MountOptLocal, MountOptJournaled},
16+
FSType: "hfs",
17+
}
18+
actual := ParseMountInfo("/dev/disk1 on / (hfs, local, journaled)")
19+
assert.Equal(t, expected, actual)
20+
assert.Equal(t, "/dev/disk1 on / (hfs, local, journaled)", actual.String())
21+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package types
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"regexp"
7+
)
8+
9+
/*
10+
$ mount
11+
/dev/mapper/mea--vg-root on / type ext4 (rw,errors=remount-ro)
12+
proc on /proc type proc (rw,noexec,nosuid,nodev)
13+
sysfs on /sys type sysfs (rw,noexec,nosuid,nodev)
14+
none on /sys/fs/cgroup type tmpfs (rw)
15+
none on /sys/fs/fuse/connections type fusectl (rw)
16+
none on /sys/kernel/debug type debugfs (rw)
17+
none on /sys/kernel/security type securityfs (rw)
18+
udev on /dev type devtmpfs (rw,mode=0755)
19+
devpts on /dev/pts type devpts (rw,noexec,nosuid,gid=5,mode=0620)
20+
tmpfs on /run type tmpfs (rw,noexec,nosuid,size=10%,mode=0755)
21+
none on /run/lock type tmpfs (rw,noexec,nosuid,nodev,size=5242880)
22+
none on /run/shm type tmpfs (rw,nosuid,nodev)
23+
none on /run/user type tmpfs (rw,noexec,nosuid,nodev,size=104857600,mode=0755)
24+
none on /sys/fs/pstore type pstore (rw)
25+
/dev/sda1 on /boot type ext2 (rw)
26+
systemd on /sys/fs/cgroup/systemd type cgroup (rw,noexec,nosuid,nodev,none,name=systemd)
27+
go on /media/sf_go type vboxsf (gid=999,rw)
28+
/tmp/one on /tmp/one-bind type none (rw,bind)
29+
*/
30+
31+
// MarshalText marshals the MountInfo object to its textual representation.
32+
func (i *MountInfo) MarshalText() ([]byte, error) {
33+
buf := &bytes.Buffer{}
34+
fmt.Fprintf(buf, "%s on %s type %s", i.DevicePath, i.MountPoint, i.FSType)
35+
if len(i.Opts) > 0 {
36+
fmt.Fprintf(buf, " (%s)", i.Opts)
37+
}
38+
return buf.Bytes(), nil
39+
}
40+
41+
/*
42+
mountInfoRX is the regex used for matching the output of the Linux mount cmd
43+
44+
$1 = devicePath
45+
$2 = mountPoint
46+
$3 = fileSystemType
47+
$4 = mountOpts
48+
*/
49+
var mountInfoRX = regexp.MustCompile(`^(.+) on (.+) type (.+?)(?: \((.+)\))?$`)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// +build linux
2+
3+
package types
4+
5+
import (
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestMountInfoParse(t *testing.T) {
12+
expected := &MountInfo{
13+
DevicePath: FileSystemDevicePath("proc"),
14+
MountPoint: "/proc",
15+
Opts: MountOptions{MountOptNoExec, MountOptNoSUID, MountOptNoDev},
16+
FSType: "proc",
17+
}
18+
actual := ParseMountInfo("proc on /proc type proc (rw,noexec,nosuid,nodev)")
19+
assert.Equal(t, expected, actual)
20+
assert.Equal(
21+
t, "proc on /proc type proc (noexec,nosuid,nodev)", actual.String())
22+
}

api/types/types_mount_opts.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package types
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"runtime"
7+
"strings"
8+
9+
"github.com/akutz/goof"
10+
)
11+
12+
// MountOption is a mount option.
13+
type MountOption int
14+
15+
// MountOptions are a mount options string.
16+
type MountOptions []MountOption
17+
18+
// String returns the string representation of the MountOption.
19+
func (o MountOption) String() string {
20+
if buf, err := o.MarshalText(); err == nil {
21+
return string(buf)
22+
}
23+
return ""
24+
}
25+
26+
func (o MountOption) bytes() []byte {
27+
if v, ok := mountOptToStr[o]; ok {
28+
return []byte(v)
29+
}
30+
return nil
31+
}
32+
33+
// ParseMountOption parses a mount option.
34+
func ParseMountOption(text string) MountOption {
35+
o := MountOptUnknown
36+
o.UnmarshalText([]byte(text))
37+
return o
38+
}
39+
40+
// MarshalText marshals the MountOption to its string representation.
41+
func (o MountOption) MarshalText() ([]byte, error) {
42+
if buf := o.bytes(); buf != nil {
43+
return buf, nil
44+
}
45+
return nil, goof.WithField("opt", int(o), "invalid mount option")
46+
}
47+
48+
// UnmarshalText marshals the MountOption from its string representation.
49+
func (o *MountOption) UnmarshalText(data []byte) error {
50+
text := string(data)
51+
if v, ok := mountStrToOpt[strings.ToLower(text)]; ok {
52+
*o = v
53+
return nil
54+
}
55+
return goof.WithField("opt", text, "invalid mount option")
56+
}
57+
58+
const (
59+
commaByteVal byte = 44
60+
spaceByteVal byte = 32
61+
)
62+
63+
var (
64+
commaSepBuf = []byte{commaByteVal}
65+
commaSpaceSepBuf = []byte{commaByteVal, spaceByteVal}
66+
)
67+
68+
// ParseMountOptions parses a mount options string.
69+
func ParseMountOptions(text string) MountOptions {
70+
var opts MountOptions
71+
if err := opts.UnmarshalText([]byte(text)); err == nil {
72+
return opts
73+
}
74+
return nil
75+
}
76+
77+
// String returns the string representation of the MountOptions object.
78+
func (opts MountOptions) String() string {
79+
if s, err := opts.MarshalText(); err == nil {
80+
return string(s)
81+
}
82+
return ""
83+
}
84+
85+
// MarshalText marshals the MountOptions to its string representation.
86+
func (opts MountOptions) MarshalText() ([]byte, error) {
87+
buf := &bytes.Buffer{}
88+
for x, o := range opts {
89+
if v, ok := mountOptToStr[o]; ok {
90+
buf.WriteString(v)
91+
if x < (len(opts) - 1) {
92+
switch runtime.GOOS {
93+
case "linux":
94+
buf.WriteString(",")
95+
case "darwin":
96+
buf.WriteString(", ")
97+
}
98+
}
99+
}
100+
}
101+
return buf.Bytes(), nil
102+
}
103+
104+
// UnmarshalText marshals the MountOptions from its string representation.
105+
func (opts *MountOptions) UnmarshalText(text []byte) error {
106+
var sepBuf []byte
107+
switch runtime.GOOS {
108+
case "linux":
109+
sepBuf = commaSepBuf
110+
case "darwin":
111+
sepBuf = commaSpaceSepBuf
112+
}
113+
optBufs := bytes.Split(text, sepBuf)
114+
for _, optText := range optBufs {
115+
if o := ParseMountOption(string(optText)); o != MountOptUnknown {
116+
*opts = append(*opts, o)
117+
}
118+
}
119+
return nil
120+
}
121+
122+
// MarshalJSON marshals the MountOptions to its JSON representation.
123+
func (opts MountOptions) MarshalJSON() ([]byte, error) {
124+
strOpts := make([]string, len(opts))
125+
for i, o := range opts {
126+
strOpts[i] = o.String()
127+
}
128+
return json.Marshal(strOpts)
129+
}
130+
131+
// UnmarshalJSON marshals the MountOptions from its JSON representation.
132+
func (opts *MountOptions) UnmarshalJSON(text []byte) error {
133+
strOpts := []string{}
134+
if err := json.Unmarshal(text, &strOpts); err != nil {
135+
return err
136+
}
137+
for _, optText := range strOpts {
138+
if o := ParseMountOption(optText); o != MountOptUnknown {
139+
*opts = append(*opts, o)
140+
}
141+
}
142+
return nil
143+
}

0 commit comments

Comments
 (0)