From 0cd2df6d03a7d78811b7164d7edd69e1fbbb2b48 Mon Sep 17 00:00:00 2001 From: akutz Date: Mon, 23 May 2016 13:14:39 -0500 Subject: [PATCH 1/3] Unix OS Driver This patch renames the Linux and Darwin OS drivers to the Unix OS driver. This driver is loaded for both the Linux and Darwin OSs. The Darwin implementation lacks implementations of the Mount & Format functions. --- client/client.go | 4 +- drivers/os/darwin/darwin.go | 91 ---------- drivers/os/darwin/darwin_os.go | 1 - drivers/os/linux/linux_docs.go | 10 - drivers/os/linux/linux_label.go | 29 --- drivers/os/linux/linux_os.go | 1 - drivers/os/{linux/linux.go => unix/unix.go} | 115 +++++++----- drivers/os/unix/unix_darwin.c | 22 +++ drivers/os/unix/unix_darwin.go | 171 ++++++++++++++++++ drivers/os/unix/unix_darwin.h | 20 ++ drivers/os/unix/unix_darwin_test.go | 67 +++++++ .../linux_mount.go => unix/unix_linux.go} | 60 +++++- .../imports_client.go} | 6 +- imports/client/imports_client_unix.go | 8 + 14 files changed, 413 insertions(+), 192 deletions(-) delete mode 100644 drivers/os/darwin/darwin.go delete mode 100644 drivers/os/darwin/darwin_os.go delete mode 100644 drivers/os/linux/linux_docs.go delete mode 100644 drivers/os/linux/linux_label.go delete mode 100644 drivers/os/linux/linux_os.go rename drivers/os/{linux/linux.go => unix/unix.go} (74%) create mode 100644 drivers/os/unix/unix_darwin.c create mode 100644 drivers/os/unix/unix_darwin.go create mode 100644 drivers/os/unix/unix_darwin.h create mode 100644 drivers/os/unix/unix_darwin_test.go rename drivers/os/{linux/linux_mount.go => unix/unix_linux.go} (89%) rename imports/{local/imports_local.go => client/imports_client.go} (88%) create mode 100644 imports/client/imports_client_unix.go 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" +) From cd300c81f40db2dee62facf4492207172cbef99f Mon Sep 17 00:00:00 2001 From: akutz Date: Wed, 1 Jun 2016 00:45:41 -0500 Subject: [PATCH 2/3] FileSystemDevicePath --- api/types/types_model.go | 13 ++++--- api/types/types_paths_fsdev.go | 38 +++++++++++++++++++ api/types/types_paths_fsdev_other.go | 15 ++++++++ api/types/types_paths_fsdev_test.go | 56 ++++++++++++++++++++++++++++ api/types/types_paths_fsdev_unix.go | 18 +++++++++ 5 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 api/types/types_paths_fsdev.go create mode 100644 api/types/types_paths_fsdev_other.go create mode 100644 api/types/types_paths_fsdev_test.go create mode 100644 api/types/types_paths_fsdev_unix.go diff --git a/api/types/types_model.go b/api/types/types_model.go index 5a778659..d8041240 100644 --- a/api/types/types_model.go +++ b/api/types/types_model.go @@ -84,8 +84,8 @@ type MountInfo struct { // FSType indicates the type of filesystem, such as EXT3. FSType string `json:"fsType"` - // Source indicates filesystem specific information or "none". - Source string `json:"source"` + // DevicePath is the path of the mounted path. + DevicePath FileSystemDevicePath `json:"devicePath"` // VFSOpts represents per super block options. VFSOpts string `json:"vfsOpts"` @@ -105,7 +105,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 +174,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_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)) +} From 470ca6d6c8a7994f3b3eba51d427692c9c7b2c9f Mon Sep 17 00:00:00 2001 From: akutz Date: Wed, 1 Jun 2016 16:53:46 -0500 Subject: [PATCH 3/3] Mount Options --- api/types/types_model.go | 43 ------- api/types/types_mount_info.go | 49 ++++++++ api/types/types_mount_info_darwin.go | 40 ++++++ api/types/types_mount_info_darwin_test.go | 21 ++++ api/types/types_mount_info_linux.go | 49 ++++++++ api/types/types_mount_info_linux_test.go | 22 ++++ api/types/types_mount_opts.go | 143 ++++++++++++++++++++++ api/types/types_mount_opts_darwin.go | 99 +++++++++++++++ api/types/types_mount_opts_darwin_test.go | 92 ++++++++++++++ api/types/types_mount_opts_linux.go | 143 ++++++++++++++++++++++ api/types/types_mount_opts_linux_test.go | 92 ++++++++++++++ 11 files changed, 750 insertions(+), 43 deletions(-) create mode 100644 api/types/types_mount_info.go create mode 100644 api/types/types_mount_info_darwin.go create mode 100644 api/types/types_mount_info_darwin_test.go create mode 100644 api/types/types_mount_info_linux.go create mode 100644 api/types/types_mount_info_linux_test.go create mode 100644 api/types/types_mount_opts.go create mode 100644 api/types/types_mount_opts_darwin.go create mode 100644 api/types/types_mount_opts_darwin_test.go create mode 100644 api/types/types_mount_opts_linux.go create mode 100644 api/types/types_mount_opts_linux_test.go 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) +}