Skip to content

Commit

Permalink
Mount Options
Browse files Browse the repository at this point in the history
  • Loading branch information
akutz committed Jun 2, 2016
1 parent cd300c8 commit 470ca6d
Show file tree
Hide file tree
Showing 11 changed files with 750 additions and 43 deletions.
43 changes: 0 additions & 43 deletions api/types/types_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,49 +51,6 @@ type Instance struct {
Fields map[string]string `json:"fields,omitempty"`
}

// MountInfo reveals information about a particular mounted filesystem. This
// struct is populated from the content in the /proc/<pid>/mountinfo file.
type MountInfo struct {
// ID is a unique identifier of the mount (may be reused after umount).
ID int `json:"id"`

// Parent indicates the ID of the mount parent (or of self for the top of
// the mount tree).
Parent int `json:"parent"`

// Major indicates one half of the device ID which identifies the device
// class.
Major int `json:"major"`

// Minor indicates one half of the device ID which identifies a specific
// instance of device.
Minor int `json:"minor"`

// Root of the mount within the filesystem.
Root string `json:"root"`

// MountPoint indicates the mount point relative to the process's root.
MountPoint string `json:"mountPoint"`

// Opts represents mount-specific options.
Opts string `json:"opts"`

// Optional represents optional fields.
Optional string `json:"optional"`

// FSType indicates the type of filesystem, such as EXT3.
FSType string `json:"fsType"`

// DevicePath is the path of the mounted path.
DevicePath FileSystemDevicePath `json:"devicePath"`

// VFSOpts represents per super block options.
VFSOpts string `json:"vfsOpts"`

// Fields are additional properties that can be defined for this type.
Fields map[string]string `json:"fields,omitempty"`
}

// Snapshot provides information about a storage-layer snapshot.
type Snapshot struct {
// A description of the snapshot.
Expand Down
49 changes: 49 additions & 0 deletions api/types/types_mount_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package types

// MountInfo reveals information about a particular mounted filesystem. This
// struct is populated from the content in the /proc/<pid>/mountinfo file.
type MountInfo struct {

// DevicePath is the path of the mounted path.
DevicePath FileSystemDevicePath `json:"devicePath"`

// MountPoint indicates the mount point relative to the process's root.
MountPoint string `json:"mountPoint"`

// FSType indicates the type of filesystem, such as EXT3.
FSType string `json:"fsType"`

// Opts represents mount-specific options.
Opts MountOptions `json:"opts"`
}

// MarshalText marshals the MountInfo object to its textual representation.
func (i *MountInfo) String() string {
if s, err := i.MarshalText(); err == nil {
return string(s)
}
return ""
}

// ParseMountInfo parses mount information.
func ParseMountInfo(text string) *MountInfo {
i := &MountInfo{}
i.UnmarshalText([]byte(text))
return i
}

// UnmarshalText marshals the MountInfo from its textual representation.
func (i *MountInfo) UnmarshalText(data []byte) error {

m := mountInfoRX.FindSubmatch(data)
if len(m) == 0 {
return nil
}

i.DevicePath = FileSystemDevicePath(m[1])
i.MountPoint = string(m[2])
i.FSType = string(m[3])
i.Opts.UnmarshalText(m[4])

return nil
}
40 changes: 40 additions & 0 deletions api/types/types_mount_info_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package types

import (
"bytes"
"fmt"
"regexp"
)

/*
$ mount
/dev/disk1 on / (hfs, local, journaled)
devfs on /dev (devfs, local, nobrowse)
map -hosts on /net (autofs, nosuid, automounted, nobrowse)
map auto_home on /home (autofs, automounted, nobrowse)
/tmp/one on /private/tmp/bind-one (osxfusefs, nodev, nosuid, synchronous, mounted by akutz)
bindfs@osxfuse1 on /private/tmp/bind-two (osxfusefs, nodev, nosuid, read-only, synchronous, mounted by akutz)
/dev/disk2s1 on /Volumes/VirtualBox (hfs, local, nodev, nosuid, read-only, noowners, quarantine, mounted by akutz)
*/

// MarshalText marshals the MountInfo object to its textual representation.
func (i *MountInfo) MarshalText() ([]byte, error) {
buf := &bytes.Buffer{}
fmt.Fprintf(buf, "%s on %s (%s", i.DevicePath, i.MountPoint, i.FSType)
if len(i.Opts) == 0 {
fmt.Fprint(buf, ")")
} else {
fmt.Fprintf(buf, ", %s)", i.Opts)
}
return buf.Bytes(), nil
}

/*
mountInfoRX is the regex used for matching the output of the Linux mount cmd
$1 = devicePath
$2 = mountPoint
$3 = fileSystemType
$4 = mountOpts
*/
var mountInfoRX = regexp.MustCompile(`^(.+) on (.+) \((.+?)(?:, (.+))\)$`)
21 changes: 21 additions & 0 deletions api/types/types_mount_info_darwin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// +build darwin

package types

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestMountInfoParse(t *testing.T) {
expected := &MountInfo{
DevicePath: FileSystemDevicePath("/dev/disk1"),
MountPoint: "/",
Opts: MountOptions{MountOptLocal, MountOptJournaled},
FSType: "hfs",
}
actual := ParseMountInfo("/dev/disk1 on / (hfs, local, journaled)")
assert.Equal(t, expected, actual)
assert.Equal(t, "/dev/disk1 on / (hfs, local, journaled)", actual.String())
}
49 changes: 49 additions & 0 deletions api/types/types_mount_info_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package types

import (
"bytes"
"fmt"
"regexp"
)

/*
$ mount
/dev/mapper/mea--vg-root on / type ext4 (rw,errors=remount-ro)
proc on /proc type proc (rw,noexec,nosuid,nodev)
sysfs on /sys type sysfs (rw,noexec,nosuid,nodev)
none on /sys/fs/cgroup type tmpfs (rw)
none on /sys/fs/fuse/connections type fusectl (rw)
none on /sys/kernel/debug type debugfs (rw)
none on /sys/kernel/security type securityfs (rw)
udev on /dev type devtmpfs (rw,mode=0755)
devpts on /dev/pts type devpts (rw,noexec,nosuid,gid=5,mode=0620)
tmpfs on /run type tmpfs (rw,noexec,nosuid,size=10%,mode=0755)
none on /run/lock type tmpfs (rw,noexec,nosuid,nodev,size=5242880)
none on /run/shm type tmpfs (rw,nosuid,nodev)
none on /run/user type tmpfs (rw,noexec,nosuid,nodev,size=104857600,mode=0755)
none on /sys/fs/pstore type pstore (rw)
/dev/sda1 on /boot type ext2 (rw)
systemd on /sys/fs/cgroup/systemd type cgroup (rw,noexec,nosuid,nodev,none,name=systemd)
go on /media/sf_go type vboxsf (gid=999,rw)
/tmp/one on /tmp/one-bind type none (rw,bind)
*/

// MarshalText marshals the MountInfo object to its textual representation.
func (i *MountInfo) MarshalText() ([]byte, error) {
buf := &bytes.Buffer{}
fmt.Fprintf(buf, "%s on %s type %s", i.DevicePath, i.MountPoint, i.FSType)
if len(i.Opts) > 0 {
fmt.Fprintf(buf, " (%s)", i.Opts)
}
return buf.Bytes(), nil
}

/*
mountInfoRX is the regex used for matching the output of the Linux mount cmd
$1 = devicePath
$2 = mountPoint
$3 = fileSystemType
$4 = mountOpts
*/
var mountInfoRX = regexp.MustCompile(`^(.+) on (.+) type (.+?)(?: \((.+)\))?$`)
22 changes: 22 additions & 0 deletions api/types/types_mount_info_linux_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// +build linux

package types

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestMountInfoParse(t *testing.T) {
expected := &MountInfo{
DevicePath: FileSystemDevicePath("proc"),
MountPoint: "/proc",
Opts: MountOptions{MountOptNoExec, MountOptNoSUID, MountOptNoDev},
FSType: "proc",
}
actual := ParseMountInfo("proc on /proc type proc (rw,noexec,nosuid,nodev)")
assert.Equal(t, expected, actual)
assert.Equal(
t, "proc on /proc type proc (noexec,nosuid,nodev)", actual.String())
}
143 changes: 143 additions & 0 deletions api/types/types_mount_opts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package types

import (
"bytes"
"encoding/json"
"runtime"
"strings"

"github.com/akutz/goof"
)

// MountOption is a mount option.
type MountOption int

// MountOptions are a mount options string.
type MountOptions []MountOption

// String returns the string representation of the MountOption.
func (o MountOption) String() string {
if buf, err := o.MarshalText(); err == nil {
return string(buf)
}
return ""
}

func (o MountOption) bytes() []byte {
if v, ok := mountOptToStr[o]; ok {
return []byte(v)
}
return nil
}

// ParseMountOption parses a mount option.
func ParseMountOption(text string) MountOption {
o := MountOptUnknown
o.UnmarshalText([]byte(text))
return o
}

// MarshalText marshals the MountOption to its string representation.
func (o MountOption) MarshalText() ([]byte, error) {
if buf := o.bytes(); buf != nil {
return buf, nil
}
return nil, goof.WithField("opt", int(o), "invalid mount option")
}

// UnmarshalText marshals the MountOption from its string representation.
func (o *MountOption) UnmarshalText(data []byte) error {
text := string(data)
if v, ok := mountStrToOpt[strings.ToLower(text)]; ok {
*o = v
return nil
}
return goof.WithField("opt", text, "invalid mount option")
}

const (
commaByteVal byte = 44
spaceByteVal byte = 32
)

var (
commaSepBuf = []byte{commaByteVal}
commaSpaceSepBuf = []byte{commaByteVal, spaceByteVal}
)

// ParseMountOptions parses a mount options string.
func ParseMountOptions(text string) MountOptions {
var opts MountOptions
if err := opts.UnmarshalText([]byte(text)); err == nil {
return opts
}
return nil
}

// String returns the string representation of the MountOptions object.
func (opts MountOptions) String() string {
if s, err := opts.MarshalText(); err == nil {
return string(s)
}
return ""
}

// MarshalText marshals the MountOptions to its string representation.
func (opts MountOptions) MarshalText() ([]byte, error) {
buf := &bytes.Buffer{}
for x, o := range opts {
if v, ok := mountOptToStr[o]; ok {
buf.WriteString(v)
if x < (len(opts) - 1) {
switch runtime.GOOS {
case "linux":
buf.WriteString(",")
case "darwin":
buf.WriteString(", ")
}
}
}
}
return buf.Bytes(), nil
}

// UnmarshalText marshals the MountOptions from its string representation.
func (opts *MountOptions) UnmarshalText(text []byte) error {
var sepBuf []byte
switch runtime.GOOS {
case "linux":
sepBuf = commaSepBuf
case "darwin":
sepBuf = commaSpaceSepBuf
}
optBufs := bytes.Split(text, sepBuf)
for _, optText := range optBufs {
if o := ParseMountOption(string(optText)); o != MountOptUnknown {
*opts = append(*opts, o)
}
}
return nil
}

// MarshalJSON marshals the MountOptions to its JSON representation.
func (opts MountOptions) MarshalJSON() ([]byte, error) {
strOpts := make([]string, len(opts))
for i, o := range opts {
strOpts[i] = o.String()
}
return json.Marshal(strOpts)
}

// UnmarshalJSON marshals the MountOptions from its JSON representation.
func (opts *MountOptions) UnmarshalJSON(text []byte) error {
strOpts := []string{}
if err := json.Unmarshal(text, &strOpts); err != nil {
return err
}
for _, optText := range strOpts {
if o := ParseMountOption(optText); o != MountOptUnknown {
*opts = append(*opts, o)
}
}
return nil
}
Loading

0 comments on commit 470ca6d

Please sign in to comment.