Browse Source

Merge pull request #4812 from kzys/freebsd-mount

FreeBSD support on pkg/mount
Victor Vieux 11 years ago
parent
commit
d7587aa976

+ 62 - 0
pkg/mount/flags.go

@@ -0,0 +1,62 @@
+package mount
+
+import (
+	"strings"
+)
+
+// Parse fstab type mount options into mount() flags
+// and device specific data
+func parseOptions(options string) (int, string) {
+	var (
+		flag int
+		data []string
+	)
+
+	flags := map[string]struct {
+		clear bool
+		flag  int
+	}{
+		"defaults":      {false, 0},
+		"ro":            {false, RDONLY},
+		"rw":            {true, RDONLY},
+		"suid":          {true, NOSUID},
+		"nosuid":        {false, NOSUID},
+		"dev":           {true, NODEV},
+		"nodev":         {false, NODEV},
+		"exec":          {true, NOEXEC},
+		"noexec":        {false, NOEXEC},
+		"sync":          {false, SYNCHRONOUS},
+		"async":         {true, SYNCHRONOUS},
+		"dirsync":       {false, DIRSYNC},
+		"remount":       {false, REMOUNT},
+		"mand":          {false, MANDLOCK},
+		"nomand":        {true, MANDLOCK},
+		"atime":         {true, NOATIME},
+		"noatime":       {false, NOATIME},
+		"diratime":      {true, NODIRATIME},
+		"nodiratime":    {false, NODIRATIME},
+		"bind":          {false, BIND},
+		"rbind":         {false, RBIND},
+		"private":       {false, PRIVATE},
+		"relatime":      {false, RELATIME},
+		"norelatime":    {true, RELATIME},
+		"strictatime":   {false, STRICTATIME},
+		"nostrictatime": {true, STRICTATIME},
+	}
+
+	for _, o := range strings.Split(options, ",") {
+		// If the option does not exist in the flags table or the flag
+		// is not supported on the platform,
+		// then it is a data value for a specific fs type
+		if f, exists := flags[o]; exists && f.flag != 0 {
+			if f.clear {
+				flag &= ^f.flag
+			} else {
+				flag |= f.flag
+			}
+		} else {
+			data = append(data, o)
+		}
+	}
+	return flag, strings.Join(data, ",")
+}

+ 28 - 0
pkg/mount/flags_freebsd.go

@@ -0,0 +1,28 @@
+// +build freebsd,cgo
+
+package mount
+
+/*
+#include <sys/mount.h>
+*/
+import "C"
+
+const (
+	RDONLY      = C.MNT_RDONLY
+	NOSUID      = C.MNT_NOSUID
+	NOEXEC      = C.MNT_NOEXEC
+	SYNCHRONOUS = C.MNT_SYNCHRONOUS
+	NOATIME     = C.MNT_NOATIME
+
+	BIND        = 0
+	DIRSYNC     = 0
+	MANDLOCK    = 0
+	NODEV       = 0
+	NODIRATIME  = 0
+	PRIVATE     = 0
+	RBIND       = 0
+	RELATIVE    = 0
+	RELATIME    = 0
+	REMOUNT     = 0
+	STRICTATIME = 0
+)

+ 17 - 56
pkg/mount/flags_linux.go

@@ -3,62 +3,23 @@
 package mount
 
 import (
-	"strings"
 	"syscall"
 )
 
-// Parse fstab type mount options into mount() flags
-// and device specific data
-func parseOptions(options string) (int, string) {
-	var (
-		flag int
-		data []string
-	)
-
-	flags := map[string]struct {
-		clear bool
-		flag  int
-	}{
-		"defaults":      {false, 0},
-		"ro":            {false, syscall.MS_RDONLY},
-		"rw":            {true, syscall.MS_RDONLY},
-		"suid":          {true, syscall.MS_NOSUID},
-		"nosuid":        {false, syscall.MS_NOSUID},
-		"dev":           {true, syscall.MS_NODEV},
-		"nodev":         {false, syscall.MS_NODEV},
-		"exec":          {true, syscall.MS_NOEXEC},
-		"noexec":        {false, syscall.MS_NOEXEC},
-		"sync":          {false, syscall.MS_SYNCHRONOUS},
-		"async":         {true, syscall.MS_SYNCHRONOUS},
-		"dirsync":       {false, syscall.MS_DIRSYNC},
-		"remount":       {false, syscall.MS_REMOUNT},
-		"mand":          {false, syscall.MS_MANDLOCK},
-		"nomand":        {true, syscall.MS_MANDLOCK},
-		"atime":         {true, syscall.MS_NOATIME},
-		"noatime":       {false, syscall.MS_NOATIME},
-		"diratime":      {true, syscall.MS_NODIRATIME},
-		"nodiratime":    {false, syscall.MS_NODIRATIME},
-		"bind":          {false, syscall.MS_BIND},
-		"rbind":         {false, syscall.MS_BIND | syscall.MS_REC},
-		"private":       {false, syscall.MS_PRIVATE},
-		"relatime":      {false, syscall.MS_RELATIME},
-		"norelatime":    {true, syscall.MS_RELATIME},
-		"strictatime":   {false, syscall.MS_STRICTATIME},
-		"nostrictatime": {true, syscall.MS_STRICTATIME},
-	}
-
-	for _, o := range strings.Split(options, ",") {
-		// If the option does not exist in the flags table then it is a
-		// data value for a specific fs type
-		if f, exists := flags[o]; exists {
-			if f.clear {
-				flag &= ^f.flag
-			} else {
-				flag |= f.flag
-			}
-		} else {
-			data = append(data, o)
-		}
-	}
-	return flag, strings.Join(data, ",")
-}
+const (
+	RDONLY      = syscall.MS_RDONLY
+	NOSUID      = syscall.MS_NOSUID
+	NODEV       = syscall.MS_NODEV
+	NOEXEC      = syscall.MS_NOEXEC
+	SYNCHRONOUS = syscall.MS_SYNCHRONOUS
+	DIRSYNC     = syscall.MS_DIRSYNC
+	REMOUNT     = syscall.MS_REMOUNT
+	MANDLOCK    = syscall.MS_MANDLOCK
+	NOATIME     = syscall.MS_NOATIME
+	NODIRATIME  = syscall.MS_NODIRATIME
+	BIND        = syscall.MS_BIND
+	RBIND       = syscall.MS_BIND | syscall.MS_REC
+	PRIVATE     = syscall.MS_PRIVATE
+	RELATIME    = syscall.MS_RELATIME
+	STRICTATIME = syscall.MS_STRICTATIME
+)

+ 19 - 4
pkg/mount/flags_unsupported.go

@@ -1,7 +1,22 @@
-// +build !linux !amd64
+// +build !linux,!freebsd linux,!amd64 freebsd,!cgo
 
 package mount
 
-func parseOptions(options string) (int, string) {
-	panic("Not implemented")
-}
+const (
+	BIND        = 0
+	DIRSYNC     = 0
+	MANDLOCK    = 0
+	NOATIME     = 0
+	NODEV       = 0
+	NODIRATIME  = 0
+	NOEXEC      = 0
+	NOSUID      = 0
+	PRIVATE     = 0
+	RBIND       = 0
+	RELATIME    = 0
+	RELATIVE    = 0
+	REMOUNT     = 0
+	STRICTATIME = 0
+	SYNCHRONOUS = 0
+	RDONLY      = 0
+)

+ 41 - 14
pkg/mount/mount_test.go

@@ -3,12 +3,11 @@ package mount
 import (
 	"os"
 	"path"
-	"syscall"
 	"testing"
 )
 
 func TestMountOptionsParsing(t *testing.T) {
-	options := "bind,ro,size=10k"
+	options := "noatime,ro,size=10k"
 
 	flag, data := parseOptions(options)
 
@@ -16,7 +15,7 @@ func TestMountOptionsParsing(t *testing.T) {
 		t.Fatalf("Expected size=10 got %s", data)
 	}
 
-	expectedFlag := syscall.MS_BIND | syscall.MS_RDONLY
+	expectedFlag := NOATIME | RDONLY
 
 	if flag != expectedFlag {
 		t.Fatalf("Expected %d got %d", expectedFlag, flag)
@@ -31,10 +30,15 @@ func TestMounted(t *testing.T) {
 	defer os.RemoveAll(tmp)
 
 	var (
-		sourcePath = path.Join(tmp, "sourcefile.txt")
-		targetPath = path.Join(tmp, "targetfile.txt")
+		sourceDir  = path.Join(tmp, "source")
+		targetDir  = path.Join(tmp, "target")
+		sourcePath = path.Join(sourceDir, "file.txt")
+		targetPath = path.Join(targetDir, "file.txt")
 	)
 
+	os.Mkdir(sourceDir, 0777)
+	os.Mkdir(targetDir, 0777)
+
 	f, err := os.Create(sourcePath)
 	if err != nil {
 		t.Fatal(err)
@@ -48,23 +52,23 @@ func TestMounted(t *testing.T) {
 	}
 	f.Close()
 
-	if err := Mount(sourcePath, targetPath, "none", "bind,rw"); err != nil {
+	if err := Mount(sourceDir, targetDir, "none", "bind,rw"); err != nil {
 		t.Fatal(err)
 	}
 	defer func() {
-		if err := Unmount(targetPath); err != nil {
+		if err := Unmount(targetDir); err != nil {
 			t.Fatal(err)
 		}
 	}()
 
-	mounted, err := Mounted(targetPath)
+	mounted, err := Mounted(targetDir)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if !mounted {
-		t.Fatalf("Expected %s to be mounted", targetPath)
+		t.Fatalf("Expected %s to be mounted", targetDir)
 	}
-	if _, err := os.Stat(targetPath); err != nil {
+	if _, err := os.Stat(targetDir); err != nil {
 		t.Fatal(err)
 	}
 }
@@ -77,10 +81,15 @@ func TestMountReadonly(t *testing.T) {
 	defer os.RemoveAll(tmp)
 
 	var (
-		sourcePath = path.Join(tmp, "sourcefile.txt")
-		targetPath = path.Join(tmp, "targetfile.txt")
+		sourceDir  = path.Join(tmp, "source")
+		targetDir  = path.Join(tmp, "target")
+		sourcePath = path.Join(sourceDir, "file.txt")
+		targetPath = path.Join(targetDir, "file.txt")
 	)
 
+	os.Mkdir(sourceDir, 0777)
+	os.Mkdir(targetDir, 0777)
+
 	f, err := os.Create(sourcePath)
 	if err != nil {
 		t.Fatal(err)
@@ -94,11 +103,11 @@ func TestMountReadonly(t *testing.T) {
 	}
 	f.Close()
 
-	if err := Mount(sourcePath, targetPath, "none", "bind,ro"); err != nil {
+	if err := Mount(sourceDir, targetDir, "none", "bind,ro"); err != nil {
 		t.Fatal(err)
 	}
 	defer func() {
-		if err := Unmount(targetPath); err != nil {
+		if err := Unmount(targetDir); err != nil {
 			t.Fatal(err)
 		}
 	}()
@@ -108,3 +117,21 @@ func TestMountReadonly(t *testing.T) {
 		t.Fatal("Should not be able to open a ro file as rw")
 	}
 }
+
+func TestGetMounts(t *testing.T) {
+	mounts, err := GetMounts()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	root := false
+	for _, entry := range mounts {
+		if entry.Mountpoint == "/" {
+			root = true
+		}
+	}
+
+	if !root {
+		t.Fatal("/ should be mounted at least")
+	}
+}

+ 59 - 0
pkg/mount/mounter_freebsd.go

@@ -0,0 +1,59 @@
+package mount
+
+/*
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/_iovec.h>
+#include <sys/mount.h>
+#include <sys/param.h>
+*/
+import "C"
+
+import (
+	"fmt"
+	"strings"
+	"syscall"
+	"unsafe"
+)
+
+func allocateIOVecs(options []string) []C.struct_iovec {
+	out := make([]C.struct_iovec, len(options))
+	for i, option := range options {
+		out[i].iov_base = unsafe.Pointer(C.CString(option))
+		out[i].iov_len = C.size_t(len(option) + 1)
+	}
+	return out
+}
+
+func mount(device, target, mType string, flag uintptr, data string) error {
+	isNullFS := false
+
+	xs := strings.Split(data, ",")
+	for _, x := range xs {
+		if x == "bind" {
+			isNullFS = true
+		}
+	}
+
+	options := []string{"fspath", target}
+	if isNullFS {
+		options = append(options, "fstype", "nullfs", "target", device)
+	} else {
+		options = append(options, "fstype", mType, "from", device)
+	}
+	rawOptions := allocateIOVecs(options)
+	for _, rawOption := range rawOptions {
+		defer C.free(rawOption.iov_base)
+	}
+
+	if errno := C.nmount(&rawOptions[0], C.uint(len(options)), C.int(flag)); errno != 0 {
+		reason := C.GoString(C.strerror(*C.__error()))
+		return fmt.Errorf("Failed to call nmount: %s", reason)
+	}
+	return nil
+}
+
+func unmount(target string, flag int) error {
+	return syscall.Unmount(target, flag)
+}

+ 1 - 1
pkg/mount/mounter_unsupported.go

@@ -1,4 +1,4 @@
-// +build !linux !amd64
+// +build !linux,!freebsd linux,!amd64 freebsd,!cgo
 
 package mount
 

+ 0 - 72
pkg/mount/mountinfo.go

@@ -1,79 +1,7 @@
 package mount
 
-import (
-	"bufio"
-	"fmt"
-	"io"
-	"os"
-	"strings"
-)
-
-const (
-	/* 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
-	   (1)(2)(3)   (4)   (5)      (6)      (7)   (8) (9)   (10)         (11)
-
-	   (1) mount ID:  unique identifier of the mount (may be reused after umount)
-	   (2) parent ID:  ID of parent (or of self for the top of the mount tree)
-	   (3) major:minor:  value of st_dev for files on filesystem
-	   (4) root:  root of the mount within the filesystem
-	   (5) mount point:  mount point relative to the process's root
-	   (6) mount options:  per mount options
-	   (7) optional fields:  zero or more fields of the form "tag[:value]"
-	   (8) separator:  marks the end of the optional fields
-	   (9) filesystem type:  name of filesystem of the form "type[.subtype]"
-	   (10) mount source:  filesystem specific information or "none"
-	   (11) super options:  per super block options*/
-	mountinfoFormat = "%d %d %d:%d %s %s %s "
-)
-
 type MountInfo struct {
 	Id, Parent, Major, Minor int
 	Root, Mountpoint, Opts   string
 	Fstype, Source, VfsOpts  string
 }
-
-// Parse /proc/self/mountinfo because comparing Dev and ino does not work from bind mounts
-func parseMountTable() ([]*MountInfo, error) {
-	f, err := os.Open("/proc/self/mountinfo")
-	if err != nil {
-		return nil, err
-	}
-	defer f.Close()
-
-	return parseInfoFile(f)
-}
-
-func parseInfoFile(r io.Reader) ([]*MountInfo, error) {
-	var (
-		s   = bufio.NewScanner(r)
-		out = []*MountInfo{}
-	)
-
-	for s.Scan() {
-		if err := s.Err(); err != nil {
-			return nil, err
-		}
-
-		var (
-			p    = &MountInfo{}
-			text = s.Text()
-		)
-
-		if _, err := fmt.Sscanf(text, mountinfoFormat,
-			&p.Id, &p.Parent, &p.Major, &p.Minor,
-			&p.Root, &p.Mountpoint, &p.Opts); err != nil {
-			return nil, fmt.Errorf("Scanning '%s' failed: %s", text, err)
-		}
-		// Safe as mountinfo encodes mountpoints with spaces as \040.
-		index := strings.Index(text, " - ")
-		postSeparatorFields := strings.Fields(text[index+3:])
-		if len(postSeparatorFields) != 3 {
-			return nil, fmt.Errorf("Error did not find 3 fields post '-' in '%s'", text)
-		}
-		p.Fstype = postSeparatorFields[0]
-		p.Source = postSeparatorFields[1]
-		p.VfsOpts = postSeparatorFields[2]
-		out = append(out, p)
-	}
-	return out, nil
-}

+ 38 - 0
pkg/mount/mountinfo_freebsd.go

@@ -0,0 +1,38 @@
+package mount
+
+/*
+#include <sys/param.h>
+#include <sys/ucred.h>
+#include <sys/mount.h>
+*/
+import "C"
+
+import (
+	"fmt"
+	"reflect"
+	"unsafe"
+)
+
+// Parse /proc/self/mountinfo because comparing Dev and ino does not work from bind mounts
+func parseMountTable() ([]*MountInfo, error) {
+	var rawEntries *C.struct_statfs
+
+	count := int(C.getmntinfo(&rawEntries, C.MNT_WAIT))
+	if count == 0 {
+		return nil, fmt.Errorf("Failed to call getmntinfo")
+	}
+
+	var entries []C.struct_statfs
+	header := (*reflect.SliceHeader)(unsafe.Pointer(&entries))
+	header.Cap = count
+	header.Len = count
+	header.Data = uintptr(unsafe.Pointer(rawEntries))
+
+	var out []*MountInfo
+	for _, entry := range entries {
+		var mountinfo MountInfo
+		mountinfo.Mountpoint = C.GoString(&entry.f_mntonname[0])
+		out = append(out, &mountinfo)
+	}
+	return out, nil
+}

+ 73 - 0
pkg/mount/mountinfo_linux.go

@@ -0,0 +1,73 @@
+package mount
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"os"
+	"strings"
+)
+
+const (
+	/* 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
+	   (1)(2)(3)   (4)   (5)      (6)      (7)   (8) (9)   (10)         (11)
+
+	   (1) mount ID:  unique identifier of the mount (may be reused after umount)
+	   (2) parent ID:  ID of parent (or of self for the top of the mount tree)
+	   (3) major:minor:  value of st_dev for files on filesystem
+	   (4) root:  root of the mount within the filesystem
+	   (5) mount point:  mount point relative to the process's root
+	   (6) mount options:  per mount options
+	   (7) optional fields:  zero or more fields of the form "tag[:value]"
+	   (8) separator:  marks the end of the optional fields
+	   (9) filesystem type:  name of filesystem of the form "type[.subtype]"
+	   (10) mount source:  filesystem specific information or "none"
+	   (11) super options:  per super block options*/
+	mountinfoFormat = "%d %d %d:%d %s %s %s "
+)
+
+// Parse /proc/self/mountinfo because comparing Dev and ino does not work from bind mounts
+func parseMountTable() ([]*MountInfo, error) {
+	f, err := os.Open("/proc/self/mountinfo")
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+
+	return parseInfoFile(f)
+}
+
+func parseInfoFile(r io.Reader) ([]*MountInfo, error) {
+	var (
+		s   = bufio.NewScanner(r)
+		out = []*MountInfo{}
+	)
+
+	for s.Scan() {
+		if err := s.Err(); err != nil {
+			return nil, err
+		}
+
+		var (
+			p    = &MountInfo{}
+			text = s.Text()
+		)
+
+		if _, err := fmt.Sscanf(text, mountinfoFormat,
+			&p.Id, &p.Parent, &p.Major, &p.Minor,
+			&p.Root, &p.Mountpoint, &p.Opts); err != nil {
+			return nil, fmt.Errorf("Scanning '%s' failed: %s", text, err)
+		}
+		// Safe as mountinfo encodes mountpoints with spaces as \040.
+		index := strings.Index(text, " - ")
+		postSeparatorFields := strings.Fields(text[index+3:])
+		if len(postSeparatorFields) != 3 {
+			return nil, fmt.Errorf("Error did not find 3 fields post '-' in '%s'", text)
+		}
+		p.Fstype = postSeparatorFields[0]
+		p.Source = postSeparatorFields[1]
+		p.VfsOpts = postSeparatorFields[2]
+		out = append(out, p)
+	}
+	return out, nil
+}

+ 0 - 0
pkg/mount/mountinfo_test.go → pkg/mount/mountinfo_test_linux.go


+ 12 - 0
pkg/mount/mountinfo_unsupported.go

@@ -0,0 +1,12 @@
+// +build !linux,!freebsd freebsd,!cgo
+
+package mount
+
+import (
+	"fmt"
+	"runtime"
+)
+
+func parseMountTable() ([]*MountInfo, error) {
+	return nil, fmt.Errorf("mount.parseMountTable is not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
+}