Merge pull request #4812 from kzys/freebsd-mount

FreeBSD support on pkg/mount
This commit is contained in:
Victor Vieux 2014-06-23 17:45:25 -07:00
commit 1b4ca55744
12 changed files with 350 additions and 147 deletions

62
mount/flags.go Normal file
View file

@ -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
mount/flags_freebsd.go Normal file
View file

@ -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
)

View file

@ -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
)

View file

@ -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
)

View file

@ -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
mount/mounter_freebsd.go Normal file
View file

@ -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)
}

View file

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

View file

@ -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
}

View file

@ -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
mount/mountinfo_linux.go Normal file
View file

@ -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
}

View file

@ -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)
}