diff --git a/api/types/types_model.go b/api/types/types_model.go index 5a778659..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"` - - // Source indicates filesystem specific information or "none". - Source string `json:"source"` - - // 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. @@ -105,7 +62,8 @@ type Snapshot struct { // The snapshot's ID. ID string `json:"id"` - // The time (epoch) at which the request to create the snapshot was submitted. + // The time (epoch) at which the request to create the snapshot was + // submitted. StartTime int64 `json:"startTime,omitempty"` // The status of the snapshot. @@ -173,9 +131,9 @@ func (v *Volume) MountPoint() string { // VolumeAttachment provides information about an object attached to a // storage volume. type VolumeAttachment struct { - // The name of the device on which the volume to which the object is - // attached is mounted. - DeviceName string `json:"deviceName"` + // DevicePath is the name of the device on which the volume to which the + // object is attached is mounted. + DevicePath FileSystemDevicePath `json:"devicePath"` // MountPoint is the mount point for the volume. This field is set when a // volume is retrieved via an integration driver. 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) +} diff --git a/api/types/types_paths_fsdev.go b/api/types/types_paths_fsdev.go new file mode 100644 index 00000000..35b7f259 --- /dev/null +++ b/api/types/types_paths_fsdev.go @@ -0,0 +1,38 @@ +package types + +import "regexp" + +// FileSystemDevicePath is a path to a filesystem device. +type FileSystemDevicePath string + +// String returns the string representation of the file system device path. +func (p FileSystemDevicePath) String() string { + return string(p) +} + +var ( + nfsDevPathRX = regexp.MustCompile(`^([^:]+):(.+)$`) +) + +// IsNFS returns information about a file system device path as if the path is +// an NFS export. +func (p FileSystemDevicePath) IsNFS() ( + ok bool, + remoteHost string, + remoteDir string) { + + m := nfsDevPathRX.FindStringSubmatch(string(p)) + if len(m) == 0 { + return false, "", "" + } + + return true, m[1], m[2] +} + +// IsBind returns a flag indicating whether or not the path appears to be a +// bind mount path. This is decided based on whether or not the device path is +// in the /dev directory. +func (p FileSystemDevicePath) IsBind() bool { + nfs, _, _ := p.IsNFS() + return !nfs && p.isBind() +} diff --git a/api/types/types_paths_fsdev_other.go b/api/types/types_paths_fsdev_other.go new file mode 100644 index 00000000..12522336 --- /dev/null +++ b/api/types/types_paths_fsdev_other.go @@ -0,0 +1,15 @@ +// +build !linux,!darwin + +package types + +import ( + "fmt" + "runtime" +) + +// isBind returns a flag indicating whether or not the path appears to be a +// bind mount path. +func (p FileSystemDevicePath) isBind() bool { + panic(fmt.Errorf( + "FileSystemDevicePath.IsBind unsupported on %s", runtime.GOOS)) +} diff --git a/api/types/types_paths_fsdev_test.go b/api/types/types_paths_fsdev_test.go new file mode 100644 index 00000000..7ee1629d --- /dev/null +++ b/api/types/types_paths_fsdev_test.go @@ -0,0 +1,56 @@ +package types + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFileSystemDevicePathIsNFS(t *testing.T) { + + nfs, remoteHost, remoteDir := FileSystemDevicePath( + "/dev/xvda").IsNFS() + assert.False(t, nfs) + + nfs, remoteHost, remoteDir = FileSystemDevicePath( + "server1:/shares/mine").IsNFS() + assert.True(t, nfs) + assert.Equal(t, "server1", remoteHost) + assert.Equal(t, "/shares/mine", remoteDir) + + nfs, remoteHost, remoteDir = FileSystemDevicePath( + "/home/myhome/share").IsNFS() + assert.False(t, nfs) +} + +func TestFileSystemDevicePathIsBind(t *testing.T) { + + assert.False(t, FileSystemDevicePath("/dev/xvda").IsBind()) + assert.False(t, FileSystemDevicePath("server1:/shares/mine").IsBind()) + assert.True(t, FileSystemDevicePath("/home/myhome/share").IsBind()) +} + +func TestFileSystemDevicePathMarshalJSON(t *testing.T) { + + buf, err := json.Marshal(FileSystemDevicePath(`/dev/xvda`)) + if err != nil { + assert.NoError(t, err) + t.FailNow() + } + assert.EqualValues(t, []byte(`"/dev/xvda"`), buf) + + buf, err = json.Marshal(FileSystemDevicePath(`server1:/shares/mine`)) + if err != nil { + assert.NoError(t, err) + t.FailNow() + } + assert.EqualValues(t, []byte(`"server1:/shares/mine"`), buf) + + buf, err = json.Marshal(FileSystemDevicePath(`/home/myhome/share`)) + if err != nil { + assert.NoError(t, err) + t.FailNow() + } + assert.EqualValues(t, []byte(`"/home/myhome/share"`), buf) +} diff --git a/api/types/types_paths_fsdev_unix.go b/api/types/types_paths_fsdev_unix.go new file mode 100644 index 00000000..6d5cb81b --- /dev/null +++ b/api/types/types_paths_fsdev_unix.go @@ -0,0 +1,18 @@ +// +build linux darwin + +package types + +import ( + "regexp" +) + +var ( + bindDevPathRX = regexp.MustCompile(`^/dev/.+$`) +) + +// isBind returns a flag indicating whether or not the path appears to be a +// bind mount path. This is decided based on whether or not the device path is +// in the /dev directory. +func (p FileSystemDevicePath) isBind() bool { + return !bindDevPathRX.MatchString(string(p)) +} diff --git a/client/client.go b/client/client.go index 8103ca7a..2ded6011 100644 --- a/client/client.go +++ b/client/client.go @@ -12,8 +12,8 @@ import ( "github.com/emccode/libstorage/api/utils" apicnfg "github.com/emccode/libstorage/api/utils/config" - // load the local imports - _ "github.com/emccode/libstorage/imports/local" + // load the client imports + _ "github.com/emccode/libstorage/imports/client" ) type client struct { diff --git a/drivers/os/darwin/darwin.go b/drivers/os/darwin/darwin.go deleted file mode 100644 index e036ee0d..00000000 --- a/drivers/os/darwin/darwin.go +++ /dev/null @@ -1,91 +0,0 @@ -// +build darwin - -package darwin - -import ( - "runtime" - - "github.com/akutz/gofig" - "github.com/akutz/goof" - - "github.com/emccode/libstorage/api/registry" - "github.com/emccode/libstorage/api/types" -) - -const driverName = "darwin" - -var ( - errUnknownOS = goof.New("unknown OS") - errUnknownFileSystem = goof.New("unknown file system") - errUnsupportedFileSystem = goof.New("unsupported file system") -) - -func init() { - registry.RegisterOSDriver(driverName, newDriver) - gofig.Register(configRegistration()) -} - -type driver struct { - config gofig.Config -} - -func newDriver() types.OSDriver { - return &driver{} -} - -func (d *driver) Init(ctx types.Context, config gofig.Config) error { - if runtime.GOOS != "darwin" { - return errUnknownOS - } - d.config = config - return nil -} - -func (d *driver) Name() string { - return driverName -} - -func (d *driver) Mounts( - ctx types.Context, - deviceName, mountPoint string, - opts types.Store) ([]*types.MountInfo, error) { - - return nil, nil -} - -func (d *driver) Mount( - ctx types.Context, - deviceName, mountPoint string, - opts *types.DeviceMountOpts) error { - - return nil -} - -func (d *driver) Unmount( - ctx types.Context, - mountPoint string, - opts types.Store) error { - - return nil -} - -func (d *driver) IsMounted( - ctx types.Context, - mountPoint string, - opts types.Store) (bool, error) { - - return false, nil -} - -func (d *driver) Format( - ctx types.Context, - deviceName string, - opts *types.DeviceFormatOpts) error { - - return nil -} - -func configRegistration() *gofig.Registration { - r := gofig.NewRegistration("Darwin") - return r -} diff --git a/drivers/os/darwin/darwin_os.go b/drivers/os/darwin/darwin_os.go deleted file mode 100644 index 145d0408..00000000 --- a/drivers/os/darwin/darwin_os.go +++ /dev/null @@ -1 +0,0 @@ -package darwin diff --git a/drivers/os/linux/linux_docs.go b/drivers/os/linux/linux_docs.go deleted file mode 100644 index cdde6245..00000000 --- a/drivers/os/linux/linux_docs.go +++ /dev/null @@ -1,10 +0,0 @@ -// +build linux - -/* -Package linux is the OS driver for linux. In order to reduce external -dependencies, this package borrows the following packages: - - - github.com/docker/docker/pkg/mount - - github.com/opencontainers/runc/libcontainer/label -*/ -package linux diff --git a/drivers/os/linux/linux_label.go b/drivers/os/linux/linux_label.go deleted file mode 100644 index 915f4838..00000000 --- a/drivers/os/linux/linux_label.go +++ /dev/null @@ -1,29 +0,0 @@ -// +build linux - -package linux - -import ( - "fmt" -) - -/* -formatMountLabel returns a string to be used by the mount command. -The format of this string will be used to alter the labeling of the mountpoint. -The string returned is suitable to be used as the options field of the mount -command. - -If you need to have additional mount point options, you can pass them in as -the first parameter. Second parameter is the label that you wish to apply -to all content in the mount point. -*/ -func formatMountLabel(src, mountLabel string) string { - if mountLabel != "" { - switch src { - case "": - src = fmt.Sprintf("context=%q", mountLabel) - default: - src = fmt.Sprintf("%s,context=%q", src, mountLabel) - } - } - return src -} diff --git a/drivers/os/linux/linux_os.go b/drivers/os/linux/linux_os.go deleted file mode 100644 index c4fa7a1d..00000000 --- a/drivers/os/linux/linux_os.go +++ /dev/null @@ -1 +0,0 @@ -package linux diff --git a/drivers/os/linux/linux.go b/drivers/os/unix/unix.go similarity index 74% rename from drivers/os/linux/linux.go rename to drivers/os/unix/unix.go index b48342e4..bb78dc05 100644 --- a/drivers/os/linux/linux.go +++ b/drivers/os/unix/unix.go @@ -1,6 +1,13 @@ -// +build linux +// +build linux darwin -package linux +/* +Package unix is the OS driver for linux and darwin. In order to reduce external +dependencies, this package borrows the following packages: + + - github.com/docker/docker/pkg/mount + - github.com/opencontainers/runc/libcontainer/label +*/ +package unix import ( "bytes" @@ -9,8 +16,9 @@ import ( "os/exec" "runtime" "strings" + "syscall" + "time" - log "github.com/Sirupsen/logrus" "github.com/akutz/gofig" "github.com/akutz/goof" @@ -18,10 +26,9 @@ import ( "github.com/emccode/libstorage/api/types" ) -const driverName = "linux" +var driverName = runtime.GOOS var ( - errUnknownOS = goof.New("unknown OS") errUnknownFileSystem = goof.New("unknown file system") errUnsupportedFileSystem = goof.New("unsupported file system") ) @@ -40,9 +47,6 @@ func newDriver() types.OSDriver { } func (d *driver) Init(ctx types.Context, config gofig.Config) error { - if runtime.GOOS != "linux" { - return errUnknownOS - } d.config = config return nil } @@ -56,7 +60,7 @@ func (d *driver) Mounts( deviceName, mountPoint string, opts types.Store) ([]*types.MountInfo, error) { - mounts, err := getMounts() + mounts, err := mounts(ctx, deviceName, mountPoint, opts) if err != nil { return nil, err } @@ -122,7 +126,24 @@ func (d *driver) Unmount( mountPoint string, opts types.Store) error { - return unmount(mountPoint) + var ( + err error + isMounted bool + ) + + isMounted, err = d.IsMounted(ctx, mountPoint, opts) + if err != nil || !isMounted { + return err + } + + for i := 0; i < 10; i++ { + if err = syscall.Unmount(mountPoint, 0); err == nil { + return nil + } + time.Sleep(100 * time.Millisecond) + } + + return nil } func (d *driver) IsMounted( @@ -130,7 +151,18 @@ func (d *driver) IsMounted( mountPoint string, opts types.Store) (bool, error) { - return mounted(mountPoint) + entries, err := mounts(ctx, "", mountPoint, opts) + if err != nil { + return false, err + } + + // Search the table for the mountpoint + for _, e := range entries { + if e.MountPoint == mountPoint { + return true, nil + } + } + return false, nil } func (d *driver) Format( @@ -138,43 +170,7 @@ func (d *driver) Format( deviceName string, opts *types.DeviceFormatOpts) error { - fsType, err := probeFsType(deviceName) - if err != nil && err != errUnknownFileSystem { - return err - } - fsDetected := fsType != "" - - ctx.WithFields(log.Fields{ - "fsDetected": fsDetected, - "fsType": fsType, - "deviceName": deviceName, - "overwriteFs": opts.OverwriteFS, - "driverName": driverName}).Info("probe information") - - if opts.OverwriteFS || !fsDetected { - switch opts.NewFSType { - case "ext4": - if err := exec.Command( - "mkfs.ext4", "-F", deviceName).Run(); err != nil { - return goof.WithFieldE( - "deviceName", deviceName, - "error creating filesystem", - err) - } - case "xfs": - if err := exec.Command( - "mkfs.xfs", "-f", deviceName).Run(); err != nil { - return goof.WithFieldE( - "deviceName", deviceName, - "error creating filesystem", - err) - } - default: - return errUnsupportedFileSystem - } - } - - return nil + return format(ctx, deviceName, opts) } func (d *driver) isNfsDevice(device string) bool { @@ -182,6 +178,7 @@ func (d *driver) isNfsDevice(device string) bool { } func (d *driver) nfsMount(device, target string) error { + command := exec.Command("mount", device, target) output, err := command.CombinedOutput() if err != nil { @@ -246,6 +243,28 @@ func probeFsType(device string) (string, error) { return "", errUnknownFileSystem } +/* +formatMountLabel returns a string to be used by the mount command. +The format of this string will be used to alter the labeling of the mountpoint. +The string returned is suitable to be used as the options field of the mount +command. + +If you need to have additional mount point options, you can pass them in as +the first parameter. Second parameter is the label that you wish to apply +to all content in the mount point. +*/ +func formatMountLabel(src, mountLabel string) string { + if mountLabel != "" { + switch src { + case "": + src = fmt.Sprintf("context=%q", mountLabel) + default: + src = fmt.Sprintf("%s,context=%q", src, mountLabel) + } + } + return src +} + func (d *driver) volumeMountPath(target string) string { return fmt.Sprintf("%s%s", target, d.volumeRootPath()) } diff --git a/drivers/os/unix/unix_darwin.c b/drivers/os/unix/unix_darwin.c new file mode 100644 index 00000000..101b6f1c --- /dev/null +++ b/drivers/os/unix/unix_darwin.c @@ -0,0 +1,22 @@ +// +build darwin + +#include "unix_darwin.h" +#include + +statfs_result _statfs(char* path) { + statfs_result r; + r.val = (struct statfs*)malloc(sizeof(struct statfs)); + r.err = statfs((const char*) path, r.val) == 0 ? 0 : errno; + return r; +} + +getmntinfo_result _getmntinfo(int flags) { + getmntinfo_result r; + r.err = 0; + r.len = getmntinfo(&r.val, flags); + if (r.len < 1) { + r.err = errno; + r.len = 0; + } + return r; +} diff --git a/drivers/os/unix/unix_darwin.go b/drivers/os/unix/unix_darwin.go new file mode 100644 index 00000000..3331dafc --- /dev/null +++ b/drivers/os/unix/unix_darwin.go @@ -0,0 +1,171 @@ +// +build darwin + +package unix + +//#cgo CFLAGS: -I${SRCDIR} +//#include "unix_darwin.h" +import "C" +import ( + "unsafe" + + "github.com/akutz/goof" + + "github.com/emccode/libstorage/api/types" +) + +const ( + // ReadOnly will mount the file system read-only. + ReadOnly = C.MNT_RDONLY + + // NoSetUserID will not allow set-user-identifier or set-group-identifier + // bits to take effect. + NoSetUserID = C.MNT_NOSUID + + // NoDev will not interpret character or block special devices on the file + // system. + NoDev = C.MNT_NODEV + + // NoExec will not allow execution of any binaries on the mounted file + // system. + NoExec = C.MNT_NOEXEC + + // Synchronous will allow I/O to the file system to be done synchronously. + Synchronous = C.MNT_SYNCHRONOUS + + // NoAccessTime will not update the file access time when reading from a + // file. + NoAccessTime = C.MNT_NOATIME + + // Wait instructs calls to get information about a filesystem to refresh + // information about a filesystem before returning it, causing the call to + // block until the refresh operation is complete. + Wait = C.MNT_WAIT + + // NoWait instructs calls to get information about a filesystem to return + // any available information immediately without waiting. + NoWait = C.MNT_NOWAIT +) + +type fsInfo struct { + blockSize int64 + ioSize int64 + blocks int64 + blocksFree int64 + blocksAvail int64 + files int64 + filesFree int64 + fileSystemTypeID int8 + fileSystemTypeName string + mountPath string + devicePath string + mountFlags int64 +} + +func statFS(mountPoint string) (*fsInfo, error) { + + r := C._statfs(C.CString(mountPoint)) + if r.val != nil { + defer C.free(unsafe.Pointer(r.val)) + } + + if r.err != 0 { + return nil, goof.WithFields(goof.Fields{ + "mountPoint": mountPoint, + "error": r.err, + }, "statFS error") + } + + return toFSInfoFromStatFS(r.val), nil +} + +func mounts( + ctx types.Context, + deviceName, mountPoint string, + opts types.Store) ([]*types.MountInfo, error) { + + fsInfo, err := getMountInfo(ctx, true) + if err != nil { + return nil, err + } + + return toMountInfoArray(fsInfo), nil +} + +func mount(device, target, mType, options string) error { + + return nil +} + +func format( + ctx types.Context, + deviceName string, + opts *types.DeviceFormatOpts) error { + + return nil +} + +func getMountInfo(ctx types.Context, wait bool) ([]*fsInfo, error) { + + var flags int + if wait { + flags = Wait + } else { + flags = NoWait + } + + r := C._getmntinfo(C.int(flags)) + if r.err != 0 { + return nil, goof.WithFields(goof.Fields{ + "wait": wait, + "flags": flags, + "len": r.len, + "error": r.err, + }, "getMountInfo error") + } + + ctx.WithField("len", r.len).Debug("got mount info") + fsiList := make([]*fsInfo, r.len) + miSlice := (*[1 << 30]C.struct_statfs)(unsafe.Pointer(r.val))[:r.len:r.len] + + for x, mi := range miSlice { + fsiList[x] = toFSInfoFromStatFS(&mi) + } + + return fsiList, nil +} + +func toMountInfoArray(val []*fsInfo) []*types.MountInfo { + + newVal := make([]*types.MountInfo, len(val)) + for x, fsi := range val { + newVal[x] = toMountInfo(fsi) + } + return newVal +} + +func toMountInfo(val *fsInfo) *types.MountInfo { + + return &types.MountInfo{ + Source: val.devicePath, + MountPoint: val.mountPath, + FSType: val.fileSystemTypeName, + } +} + +func toFSInfoFromStatFS(val *C.struct_statfs) *fsInfo { + + return &fsInfo{ + blockSize: int64(val.f_bsize), + ioSize: int64(val.f_iosize), + blocks: int64(val.f_blocks), + blocksFree: int64(val.f_bfree), + blocksAvail: int64(val.f_bavail), + files: int64(val.f_files), + filesFree: int64(val.f_ffree), + fileSystemTypeID: int8(val.f_type), + fileSystemTypeName: C.GoString(&val.f_fstypename[0]), + mountPath: C.GoString(&val.f_mntonname[0]), + devicePath: C.GoString(&val.f_mntfromname[0]), + mountFlags: int64(val.f_flags), + } +} diff --git a/drivers/os/unix/unix_darwin.h b/drivers/os/unix/unix_darwin.h new file mode 100644 index 00000000..469a8a98 --- /dev/null +++ b/drivers/os/unix/unix_darwin.h @@ -0,0 +1,20 @@ +// +build darwin + +#include +#include +#include + +typedef struct { + struct statfs* val; + int err; +} statfs_result; + +statfs_result _statfs(char* path); + +typedef struct { + int len; + struct statfs* val; + int err; +} getmntinfo_result; + +getmntinfo_result _getmntinfo(int flags); diff --git a/drivers/os/unix/unix_darwin_test.go b/drivers/os/unix/unix_darwin_test.go new file mode 100644 index 00000000..f618bc62 --- /dev/null +++ b/drivers/os/unix/unix_darwin_test.go @@ -0,0 +1,67 @@ +// +build darwin + +package unix + +import ( + "testing" + + "github.com/akutz/goof" + "github.com/stretchr/testify/assert" + + "github.com/emccode/libstorage/api/context" + "github.com/emccode/libstorage/api/utils" +) + +func init() { + goof.IncludeFieldsInError = true + goof.IncludeFieldsInString = true + goof.IncludeFieldsInFormat = true +} + +func TestMounts(t *testing.T) { + + ctx := context.Background() + store := utils.NewStore() + d := newDriver() + + mounts, err := d.Mounts(ctx, "", "", store) + assert.NoError(t, err) + assert.True(t, len(mounts) > 1) + + mounts, err = d.Mounts(ctx, "", "/", store) + assert.NoError(t, err) + assert.True(t, len(mounts) == 1) +} + +func TestIsMounted(t *testing.T) { + + ctx := context.Background() + store := utils.NewStore() + d := newDriver() + + isMounted, err := d.IsMounted(ctx, "/", store) + assert.NoError(t, err) + assert.True(t, isMounted) +} + +func TestStatFS(t *testing.T) { + + r, err := statFS("/") + assert.NoError(t, err) + t.Logf("%+v", r) +} + +func TestGetMountInfoAndStatFS(t *testing.T) { + + r, err := getMountInfo(context.Background(), true) + assert.NoError(t, err) + if err != nil { + t.FailNow() + } + + for _, fsi := range r { + statFSResult, err := statFS(fsi.mountPath) + assert.NoError(t, err) + t.Logf("%+v", statFSResult) + } +} diff --git a/drivers/os/linux/linux_mount.go b/drivers/os/unix/unix_linux.go similarity index 89% rename from drivers/os/linux/linux_mount.go rename to drivers/os/unix/unix_linux.go index 53db9c69..f286535f 100644 --- a/drivers/os/linux/linux_mount.go +++ b/drivers/os/unix/unix_linux.go @@ -1,16 +1,18 @@ -// +build linux - -package linux +package unix import ( "bufio" "fmt" "io" "os" + "os/exec" "strings" "syscall" "time" + log "github.com/Sirupsen/logrus" + "github.com/akutz/goof" + "github.com/emccode/libstorage/api/types" ) @@ -263,8 +265,12 @@ func parseTmpfsOptions(options string) (int, string, error) { return flags, data, nil } -// getMounts retrieves a list of mounts for the current running process. -func getMounts() ([]*types.MountInfo, error) { +// mounts retrieves a list of mounts for the current running process. +func mounts( + ctx types.Context, + deviceName, mountPoint string, + opts types.Store) ([]*types.MountInfo, error) { + return parseMountTable() } @@ -354,3 +360,47 @@ func forceUnmount(target string) (err error) { } return } + +func format( + ctx types.Context, + deviceName string, + opts *types.DeviceFormatOpts) error { + + fsType, err := probeFsType(deviceName) + if err != nil && err != errUnknownFileSystem { + return err + } + fsDetected := fsType != "" + + ctx.WithFields(log.Fields{ + "fsDetected": fsDetected, + "fsType": fsType, + "deviceName": deviceName, + "overwriteFs": opts.OverwriteFS, + "driverName": driverName}).Info("probe information") + + if opts.OverwriteFS || !fsDetected { + switch opts.NewFSType { + case "ext4": + if err := exec.Command( + "mkfs.ext4", "-F", deviceName).Run(); err != nil { + return goof.WithFieldE( + "deviceName", deviceName, + "error creating filesystem", + err) + } + case "xfs": + if err := exec.Command( + "mkfs.xfs", "-f", deviceName).Run(); err != nil { + return goof.WithFieldE( + "deviceName", deviceName, + "error creating filesystem", + err) + } + default: + return errUnsupportedFileSystem + } + } + + return nil +} diff --git a/imports/local/imports_local.go b/imports/client/imports_client.go similarity index 88% rename from imports/local/imports_local.go rename to imports/client/imports_client.go index 8f47b4d1..a85ef262 100644 --- a/imports/local/imports_local.go +++ b/imports/client/imports_client.go @@ -1,4 +1,4 @@ -package local +package client import ( // load the config @@ -7,10 +7,6 @@ import ( // load the libStorage storage driver _ "github.com/emccode/libstorage/drivers/storage/libstorage" - // load the os drivers - _ "github.com/emccode/libstorage/drivers/os/darwin" - _ "github.com/emccode/libstorage/drivers/os/linux" - // load the integration drivers _ "github.com/emccode/libstorage/drivers/integration/docker" diff --git a/imports/client/imports_client_unix.go b/imports/client/imports_client_unix.go new file mode 100644 index 00000000..61f37fa5 --- /dev/null +++ b/imports/client/imports_client_unix.go @@ -0,0 +1,8 @@ +// +build linux darwin + +package client + +import ( + // load the os drivers + _ "github.com/emccode/libstorage/drivers/os/unix" +)