diff --git a/api/types/types_model.go b/api/types/types_model.go index d8041240..550afc4c 100644 --- a/api/types/types_model.go +++ b/api/types/types_model.go @@ -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//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. diff --git a/api/types/types_mount_info.go b/api/types/types_mount_info.go new file mode 100644 index 00000000..b591d798 --- /dev/null +++ b/api/types/types_mount_info.go @@ -0,0 +1,49 @@ +package types + +// MountInfo reveals information about a particular mounted filesystem. This +// struct is populated from the content in the /proc//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 +} diff --git a/api/types/types_mount_info_darwin.go b/api/types/types_mount_info_darwin.go new file mode 100644 index 00000000..50cc141c --- /dev/null +++ b/api/types/types_mount_info_darwin.go @@ -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 (.+) \((.+?)(?:, (.+))\)$`) diff --git a/api/types/types_mount_info_darwin_test.go b/api/types/types_mount_info_darwin_test.go new file mode 100644 index 00000000..f4357f05 --- /dev/null +++ b/api/types/types_mount_info_darwin_test.go @@ -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()) +} diff --git a/api/types/types_mount_info_linux.go b/api/types/types_mount_info_linux.go new file mode 100644 index 00000000..72be172e --- /dev/null +++ b/api/types/types_mount_info_linux.go @@ -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 (.+?)(?: \((.+)\))?$`) diff --git a/api/types/types_mount_info_linux_test.go b/api/types/types_mount_info_linux_test.go new file mode 100644 index 00000000..d8337344 --- /dev/null +++ b/api/types/types_mount_info_linux_test.go @@ -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()) +} diff --git a/api/types/types_mount_opts.go b/api/types/types_mount_opts.go new file mode 100644 index 00000000..aa05468b --- /dev/null +++ b/api/types/types_mount_opts.go @@ -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 +} diff --git a/api/types/types_mount_opts_darwin.go b/api/types/types_mount_opts_darwin.go new file mode 100644 index 00000000..868e32fa --- /dev/null +++ b/api/types/types_mount_opts_darwin.go @@ -0,0 +1,99 @@ +package types + +const ( + + // MountOptUnknown is an unknown option. + MountOptUnknown = MountOption(0) + + // MountOptReadOnly will mount the file system read-only. + MountOptReadOnly = MountOption(0x00000001) + + // MountOptNoSUID will not allow set-user-identifier or set-group-identifier + // bits to take effect. + MountOptNoSUID = MountOption(0x00000008) + + // MountOptNoDev will not interpret character or block special devices on + // the file system. + MountOptNoDev = MountOption(0x00000010) + + // MountOptNoExec will not allow execution of any binaries on the mounted + // file system. + MountOptNoExec = MountOption(0x00000004) + + // MountOptSync will allow I/O to the file system to be done synchronously. + MountOptSync = MountOption(0x00000002) + + // MountOptNoATime will not update the file access time when reading from + // a file. + MountOptNoATime = MountOption(0x10000000) + + // MountOptLocal indicates the file system is stored locally. + MountOptLocal = MountOption(0x00001000) + + // MountOptQuota indicates quotas are enabled on the file system. + MountOptQuota = MountOption(0x00002000) + + // MountOptRootFS identifies the root file system. + MountOptRootFS = MountOption(0x00004000) + + // MountOptDontBrowse indicates the file system is not appropriate path to + // user data + MountOptDontBrowse = MountOption(0x00100000) + + // MountOptIgnoreOwnership indicates ownership information on file system + // objects will be ignored + MountOptIgnoreOwnership = MountOption(0x00200000) + + // MountOptAutoMounted indicates file system was mounted by auto mounter + MountOptAutoMounted = MountOption(0x00400000) + + // MountOptJournaled indicates file system is journaled + MountOptJournaled = MountOption(0x00800000) + + // MountOptNoUserXattr indicates user extended attributes are not allowed + MountOptNoUserXattr = MountOption(0x01000000) + + // MountOptDefWrite indicates the file system should defer writes + MountOptDefWrite = MountOption(0x02000000) + + // MountOptMultiLabel indicates MAC support for individual labels + MountOptMultiLabel = MountOption(0x04000000) +) + +var ( + mountOptToStr = map[MountOption]string{ + MountOptReadOnly: "read-only", + MountOptNoSUID: "nosuid", + MountOptNoDev: "nodev", + MountOptNoExec: "noexec", + MountOptSync: "sync", + MountOptNoATime: "noatime", + MountOptLocal: "local", + MountOptQuota: "quota", + MountOptRootFS: "rootfs", + MountOptDontBrowse: "nobrowse", + MountOptIgnoreOwnership: "noowners", + MountOptAutoMounted: "automounted", + MountOptJournaled: "journaled", + MountOptNoUserXattr: "nouserxattr", + MountOptDefWrite: "defwrite", + } + + mountStrToOpt = map[string]MountOption{ + "read-only": MountOptReadOnly, + "nosuid": MountOptNoSUID, + "nodev": MountOptNoDev, + "noexec": MountOptNoExec, + "sync": MountOptSync, + "noatime": MountOptNoATime, + "local": MountOptLocal, + "quota": MountOptQuota, + "rootfs": MountOptRootFS, + "nobrowse": MountOptDontBrowse, + "noowners": MountOptIgnoreOwnership, + "automounted": MountOptAutoMounted, + "journaled": MountOptJournaled, + "nouserxattr": MountOptNoUserXattr, + "defwrite": MountOptDefWrite, + } +) diff --git a/api/types/types_mount_opts_darwin_test.go b/api/types/types_mount_opts_darwin_test.go new file mode 100644 index 00000000..d7ade5f3 --- /dev/null +++ b/api/types/types_mount_opts_darwin_test.go @@ -0,0 +1,92 @@ +// +build darwin + +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMountOptionParse(t *testing.T) { + assert.Equal(t, MountOptReadOnly, ParseMountOption("read-only")) + assert.Equal(t, MountOptNoSUID, ParseMountOption("nosuid")) + assert.Equal(t, MountOptNoExec, ParseMountOption("noexec")) +} + +func TestMountOptionsParse(t *testing.T) { + exepctedOpts := MountOptions{ + MountOptReadOnly, + MountOptNoSUID, + MountOptNoDev, + MountOptNoExec, + } + opts := ParseMountOptions("read-only, nosuid, nodev, noexec, bindfs") + assert.Equal(t, exepctedOpts, opts) + + assert.Nil(t, ParseMountOptions("")) +} + +func TestMountOptionsMarshalText(t *testing.T) { + opts := MountOptions{ + MountOptReadOnly, + MountOptNoSUID, + MountOptNoDev, + MountOptNoExec, + } + assert.Equal(t, "read-only, nosuid, nodev, noexec", opts.String()) +} + +func TestMountOptionsUnmarshalText(t *testing.T) { + var opts MountOptions + err := opts.UnmarshalText( + []byte("read-only, nosuid, nodev, noexec, bindfs")) + if err != nil { + assert.NoError(t, err) + t.FailNow() + } + exepctedOpts := MountOptions{ + MountOptReadOnly, + MountOptNoSUID, + MountOptNoDev, + MountOptNoExec, + } + assert.Equal(t, exepctedOpts, opts) +} + +func TestMountOptionsMarshalJSON(t *testing.T) { + opts := MountOptions{ + MountOptReadOnly, + MountOptNoSUID, + MountOptNoDev, + MountOptNoExec, + } + buf, err := opts.MarshalJSON() + if err != nil { + assert.NoError(t, err) + t.FailNow() + } + assert.Equal(t, `["read-only","nosuid","nodev","noexec"]`, string(buf)) +} + +func TestMountOptionsUnmarshalJSON(t *testing.T) { + var opts MountOptions + err := opts.UnmarshalJSON([]byte(`[ + "read-only", + "nosuid", + "bindfs", + "nodev", + "noexec" +]`)) + if err != nil { + assert.NoError(t, err) + t.FailNow() + } + exepctedOpts := MountOptions{ + MountOptReadOnly, + MountOptNoSUID, + MountOptNoDev, + MountOptNoExec, + } + assert.Equal(t, exepctedOpts, opts) +} diff --git a/api/types/types_mount_opts_linux.go b/api/types/types_mount_opts_linux.go new file mode 100644 index 00000000..8b3146c0 --- /dev/null +++ b/api/types/types_mount_opts_linux.go @@ -0,0 +1,143 @@ +package types + +import "syscall" + +const ( + // MountOptUnknown is an unknown option. + MountOptUnknown = MountOption(0) + + // MountOptReadOnly will mount the file system read-only. + MountOptReadOnly = MountOption(syscall.MS_RDONLY) + + // MountOptNoSUID will not allow set-user-identifier or set-group-identifier + // bits to take effect. + MountOptNoSUID = MountOption(syscall.MS_NOSUID) + + // MountOptNoDev will not interpret character or block special devices on + // the file system. + MountOptNoDev = MountOption(syscall.MS_NODEV) + + // MountOptNoExec will not allow execution of any binaries on the mounted + // file system. + MountOptNoExec = MountOption(syscall.MS_NOEXEC) + + // MountOptSync will allow I/O to the file system to be done synchronously. + MountOptSync = MountOption(syscall.MS_SYNCHRONOUS) + + // MountOptDirSync will force all directory updates within the file system + // to be done synchronously. This affects the following system calls: + // create, link, unlink, symlink, mkdir, rmdir, mknod and rename. + MountOptDirSync = MountOption(syscall.MS_DIRSYNC) + + // MountOptRemount will attempt to remount an already-mounted file system. + // This is commonly used to change the mount flags for a file system, + // especially to make a readonly file system writeable. It does not change + // device or mount point. + MountOptRemount = MountOption(syscall.MS_REMOUNT) + + // MountOptMandLock will force mandatory locks on a filesystem. + MountOptMandLock = MountOption(syscall.MS_MANDLOCK) + + // MountOptNoATime will not update the file access time when reading from + // a file. + MountOptNoATime = MountOption(syscall.MS_NOATIME) + + // MountOptNoDirATime will not update the directory access time. + MountOptNoDirATime = MountOption(syscall.MS_NODIRATIME) + + // MountOptBind remounts a subtree somewhere else. + MountOptBind = MountOption(syscall.MS_BIND) + + // MountOptRBind remounts a subtree and all possible submounts somewhere + // else. + MountOptRBind = MountOption(syscall.MS_BIND | syscall.MS_REC) + + // MountOptUnbindable creates a mount which cannot be cloned through a bind + // operation. + MountOptUnbindable = MountOption(syscall.MS_UNBINDABLE) + + // MountOptRUnbindable marks the entire mount tree as UNBINDABLE. + MountOptRUnbindable = MountOption(syscall.MS_UNBINDABLE | syscall.MS_REC) + + // MountOptPrivate creates a mount which carries no propagation abilities. + MountOptPrivate = MountOption(syscall.MS_PRIVATE) + + // MountOptRPrivate marks the entire mount tree as PRIVATE. + MountOptRPrivate = MountOption(syscall.MS_PRIVATE | syscall.MS_REC) + + // MountOptSlave creates a mount which receives propagation from its master, + // but not vice versa. + MountOptSlave = MountOption(syscall.MS_SLAVE) + + // MountOptRSlave marks the entire mount tree as SLAVE. + MountOptRSlave = MountOption(syscall.MS_SLAVE | syscall.MS_REC) + + // MountOptShared creates a mount which provides the ability to create + // mirrors of that mount such that mounts and unmounts within any of the + // mirrors propagate to the other mirrors. + MountOptShared = MountOption(syscall.MS_SHARED) + + // MountOptRShared marks the entire mount tree as SHARED. + MountOptRShared = MountOption(syscall.MS_SHARED | syscall.MS_REC) + + // MountOptRelATime updates inode access times relative to modify or + // change time. + MountOptRelATime = MountOption(syscall.MS_RELATIME) + + // MountOptStrictATime allows to explicitly request full atime updates. + // This makes it possible for the kernel to default to relatime or noatime + // but still allow userspace to override it. + MountOptStrictATime = MountOption(syscall.MS_STRICTATIME) +) + +var ( + mountOptToStr = map[MountOption]string{ + MountOptReadOnly: "ro", + MountOptNoSUID: "nosuid", + MountOptNoDev: "nodev", + MountOptNoExec: "noexec", + MountOptSync: "sync", + MountOptDirSync: "dirsync", + MountOptRemount: "remount", + MountOptMandLock: "mand", + MountOptNoATime: "noatime", + MountOptNoDirATime: "nodiratime", + MountOptBind: "bind", + MountOptRBind: "rbind", + MountOptUnbindable: "unbindable", + MountOptRUnbindable: "runbindable", + MountOptPrivate: "private", + MountOptRPrivate: "rprivate", + MountOptSlave: "slave", + MountOptRSlave: "rslave", + MountOptShared: "shared", + MountOptRShared: "rshared", + MountOptRelATime: "relatime", + MountOptStrictATime: "strictatime", + } + + mountStrToOpt = map[string]MountOption{ + "ro": MountOptReadOnly, + "nosuid": MountOptNoSUID, + "nodev": MountOptNoDev, + "noexec": MountOptNoExec, + "sync": MountOptSync, + "dirsync": MountOptDirSync, + "remount": MountOptRemount, + "mand": MountOptMandLock, + "noatime": MountOptNoATime, + "nodiratime": MountOptNoDirATime, + "bind": MountOptBind, + "rbind": MountOptRBind, + "unbindable": MountOptUnbindable, + "runbindable": MountOptRUnbindable, + "private": MountOptPrivate, + "rprivate": MountOptRPrivate, + "slave": MountOptSlave, + "rslave": MountOptRSlave, + "shared": MountOptShared, + "rshared": MountOptRShared, + "relatime": MountOptRelATime, + "strictatime": MountOptStrictATime, + } +) diff --git a/api/types/types_mount_opts_linux_test.go b/api/types/types_mount_opts_linux_test.go new file mode 100644 index 00000000..079616a4 --- /dev/null +++ b/api/types/types_mount_opts_linux_test.go @@ -0,0 +1,92 @@ +// +build linux + +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMountOptionParse(t *testing.T) { + assert.Equal(t, MountOptReadOnly, ParseMountOption("ro")) + assert.Equal(t, MountOptNoSUID, ParseMountOption("nosuid")) + assert.Equal(t, MountOptNoExec, ParseMountOption("noexec")) +} + +func TestMountOptionsParse(t *testing.T) { + exepctedOpts := MountOptions{ + MountOptReadOnly, + MountOptNoSUID, + MountOptNoDev, + MountOptNoExec, + } + opts := ParseMountOptions("ro,nosuid,nodev,noexec,bindfs") + assert.Equal(t, exepctedOpts, opts) + + assert.Nil(t, ParseMountOptions("")) +} + +func TestMountOptionsMarshalText(t *testing.T) { + opts := MountOptions{ + MountOptReadOnly, + MountOptNoSUID, + MountOptNoDev, + MountOptNoExec, + } + assert.Equal(t, "ro,nosuid,nodev,noexec", opts.String()) +} + +func TestMountOptionsUnmarshalText(t *testing.T) { + var opts MountOptions + err := opts.UnmarshalText( + []byte("ro,nosuid,nodev,noexec,bindfs")) + if err != nil { + assert.NoError(t, err) + t.FailNow() + } + exepctedOpts := MountOptions{ + MountOptReadOnly, + MountOptNoSUID, + MountOptNoDev, + MountOptNoExec, + } + assert.Equal(t, exepctedOpts, opts) +} + +func TestMountOptionsMarshalJSON(t *testing.T) { + opts := MountOptions{ + MountOptReadOnly, + MountOptNoSUID, + MountOptNoDev, + MountOptNoExec, + } + buf, err := opts.MarshalJSON() + if err != nil { + assert.NoError(t, err) + t.FailNow() + } + assert.Equal(t, `["ro","nosuid","nodev","noexec"]`, string(buf)) +} + +func TestMountOptionsUnmarshalJSON(t *testing.T) { + var opts MountOptions + err := opts.UnmarshalJSON([]byte(`[ + "ro", + "nosuid", + "bindfs", + "nodev", + "noexec" +]`)) + if err != nil { + assert.NoError(t, err) + t.FailNow() + } + exepctedOpts := MountOptions{ + MountOptReadOnly, + MountOptNoSUID, + MountOptNoDev, + MountOptNoExec, + } + assert.Equal(t, exepctedOpts, opts) +}