diff --git a/client/client.go b/client/client.go index c6faf1ca..7eb98738 100644 --- a/client/client.go +++ b/client/client.go @@ -10,8 +10,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..bccd6e00 100644 --- a/drivers/os/linux/linux_mount.go +++ b/drivers/os/unix/unix_linux.go @@ -1,16 +1,16 @@ -// +build linux - -package linux +package unix import ( "bufio" "fmt" "io" "os" + "os/exec" "strings" "syscall" "time" + "github.com/akutz/goof" "github.com/emccode/libstorage/api/types" ) @@ -263,8 +263,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 +358,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..3c8a58cb --- /dev/null +++ b/imports/client/imports_client_unix.go @@ -0,0 +1,6 @@ +// +build linux darwin + +package client + +// load the os drivers +import _ "github.com/emccode/libstorage/drivers/os/unix" diff --git a/imports/config/imports_config.go b/imports/config/imports_config.go index b01f4cb9..3c19cacd 100644 --- a/imports/config/imports_config.go +++ b/imports/config/imports_config.go @@ -6,6 +6,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/akutz/gofig" + "github.com/emccode/libstorage/api/types" "github.com/emccode/libstorage/api/utils/paths" )