Add support for osxfuse 3.

There are a few changes to tests and documentation to reflect changes to
osxfuse behavior. All of them should probably be seen as backwards
compatible. The tests now test the osxfuse 3 behavior, and for this
reason we should consider killing osxfuse 2 support after some time.

Closes #18.
Closes #19.
geesefs-0-30-9
Aaron Jacobs 2016-10-16 09:14:47 +01:00
commit 3b8b4e55df
4 changed files with 139 additions and 70 deletions

View File

@ -59,7 +59,7 @@ type StatFSOp struct {
//
// On Linux this can be any value, and will be faithfully returned to the
// caller of statfs(2) (see the code walk above). On OS X it appears that
// only powers of 2 in the range [2^9, 2^17] are preserved, and a value of
// only powers of 2 in the range [2^7, 2^20] are preserved, and a value of
// zero is treated as 4096.
//
// This interface does not distinguish between blocks and block fragments.
@ -85,7 +85,7 @@ type StatFSOp struct {
// transfer block size".
//
// On Linux this can be any value. On OS X it appears that only powers of 2
// in the range [2^12, 2^20] are faithfully preserved, and a value of zero is
// in the range [2^12, 2^25] are faithfully preserved, and a value of zero is
// treated as 65536.
IoSize uint32
@ -696,9 +696,10 @@ type SyncFileOp struct {
// * On OS X, if a user modifies a mapped file via the mapping before
// closing the file with close(2), the WriteFileOps for the modifications
// may not be received before the FlushFileOp for the close(2) (cf.
// http://goo.gl/kVmNcx).
// https://github.com/osxfuse/osxfuse/issues/202). It appears that this may
// be fixed in osxfuse 3 (cf. https://goo.gl/rtvbko).
//
// * However, even on OS X you can arrange for writes via a mapping to be
// * However, you safely can arrange for writes via a mapping to be
// flushed by calling msync(2) followed by close(2). On OS X msync(2)
// will cause a WriteFileOps to go through and close(2) will cause a
// FlushFile as usual (cf. http://goo.gl/kVmNcx). On Linux, msync(2) does

View File

@ -14,10 +14,53 @@ import (
)
var errNoAvail = errors.New("no available fuse devices")
var errNotLoaded = errors.New("osxfusefs is not loaded")
var errNotLoaded = errors.New("osxfuse is not loaded")
func loadOSXFUSE() error {
cmd := exec.Command("/Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs")
// errOSXFUSENotFound is returned from Mount when the OSXFUSE installation is
// not detected. Make sure OSXFUSE is installed.
var errOSXFUSENotFound = errors.New("cannot locate OSXFUSE")
// osxfuseInstallation describes the paths used by an installed OSXFUSE
// version.
type osxfuseInstallation struct {
// Prefix for the device file. At mount time, an incrementing number is
// suffixed until a free FUSE device is found.
DevicePrefix string
// Path of the load helper, used to load the kernel extension if no device
// files are found.
Load string
// Path of the mount helper, used for the actual mount operation.
Mount string
// Environment variable used to pass the path to the executable calling the
// mount helper.
DaemonVar string
}
var (
osxfuseInstallations = []osxfuseInstallation{
// v3
{
DevicePrefix: "/dev/osxfuse",
Load: "/Library/Filesystems/osxfuse.fs/Contents/Resources/load_osxfuse",
Mount: "/Library/Filesystems/osxfuse.fs/Contents/Resources/mount_osxfuse",
DaemonVar: "MOUNT_OSXFUSE_DAEMON_PATH",
},
// v2
{
DevicePrefix: "/dev/osxfuse",
Load: "/Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs",
Mount: "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs",
DaemonVar: "MOUNT_FUSEFS_DAEMON_PATH",
},
}
)
func loadOSXFUSE(bin string) error {
cmd := exec.Command(bin)
cmd.Dir = "/"
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@ -25,10 +68,10 @@ func loadOSXFUSE() error {
return err
}
func openOSXFUSEDev() (dev *os.File, err error) {
func openOSXFUSEDev(devPrefix string) (dev *os.File, err error) {
// Try each device name.
for i := uint64(0); ; i++ {
path := fmt.Sprintf("/dev/osxfuse%d", i)
path := devPrefix + strconv.FormatUint(i, 10)
dev, err = os.OpenFile(path, os.O_RDWR, 0000)
if os.IsNotExist(err) {
if i == 0 {
@ -52,11 +95,12 @@ func openOSXFUSEDev() (dev *os.File, err error) {
}
func callMount(
bin string,
daemonVar string,
dir string,
cfg *MountConfig,
dev *os.File,
ready chan<- error) (err error) {
const bin = "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs"
// The mount helper doesn't understand any escaping.
for k, v := range cfg.toMap() {
@ -85,8 +129,15 @@ func callMount(
)
cmd.ExtraFiles = []*os.File{dev}
cmd.Env = os.Environ()
// OSXFUSE <3.3.0
cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_CALL_BY_LIB=")
cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_DAEMON_PATH="+bin)
// OSXFUSE >=3.3.0
cmd.Env = append(cmd.Env, "MOUNT_OSXFUSE_CALL_BY_LIB=")
daemon := os.Args[0]
if daemonVar != "" {
cmd.Env = append(cmd.Env, daemonVar+"="+daemon)
}
var buf bytes.Buffer
cmd.Stdout = &buf
@ -122,34 +173,45 @@ func mount(
dir string,
cfg *MountConfig,
ready chan<- error) (dev *os.File, err error) {
// Open the device.
dev, err = openOSXFUSEDev()
// Find the version of osxfuse installed on this machine.
for _, loc := range osxfuseInstallations {
if _, err := os.Stat(loc.Mount); os.IsNotExist(err) {
// try the other locations
continue
}
// Special case: we may need to explicitly load osxfuse. Load it, then try
// again.
if err == errNotLoaded {
err = loadOSXFUSE()
// Open the device.
dev, err = openOSXFUSEDev(loc.DevicePrefix)
// Special case: we may need to explicitly load osxfuse. Load it, then
// try again.
if err == errNotLoaded {
err = loadOSXFUSE(loc.Load)
if err != nil {
err = fmt.Errorf("loadOSXFUSE: %v", err)
return
}
dev, err = openOSXFUSEDev(loc.DevicePrefix)
}
// Propagate errors.
if err != nil {
err = fmt.Errorf("loadOSXFUSE: %v", err)
err = fmt.Errorf("openOSXFUSEDev: %v", err)
return
}
dev, err = openOSXFUSEDev()
}
// Call the mount binary with the device.
err = callMount(loc.Mount, loc.DaemonVar, dir, cfg, dev, ready)
if err != nil {
dev.Close()
err = fmt.Errorf("callMount: %v", err)
return
}
// Propagate errors.
if err != nil {
err = fmt.Errorf("openOSXFUSEDev: %v", err)
return
}
// Call the mount binary with the device.
err = callMount(dir, cfg, dev, ready)
if err != nil {
dev.Close()
err = fmt.Errorf("callMount: %v", err)
return
}
err = errOSXFUSENotFound
return
}

View File

@ -605,19 +605,13 @@ func (t *NoErrorsTest) Mmap_NoMsync_MunmapBeforeClose() {
ExpectThat(t.getFlushes(), ElementsAre())
ExpectThat(t.getFsyncs(), ElementsAre())
// Close the file. We should see a flush. On Darwin, this will contain out of
// date contents (cf. https://github.com/osxfuse/osxfuse/issues/202).
// Close the file. We should see a flush with up to date contents.
err = t.f1.Close()
t.f1 = nil
AssertEq(nil, err)
if isDarwin {
ExpectThat(t.getFlushes(), ElementsAre("taco"))
ExpectThat(t.getFsyncs(), ElementsAre())
} else {
ExpectThat(t.getFlushes(), ElementsAre("paco"))
ExpectThat(t.getFsyncs(), ElementsAre())
}
ExpectThat(t.getFlushes(), ElementsAre("paco"))
ExpectThat(t.getFsyncs(), ElementsAre())
}
func (t *NoErrorsTest) Mmap_NoMsync_CloseBeforeMunmap() {

View File

@ -70,7 +70,7 @@ func (t *StatFSTest) Syscall_ZeroValues() {
ExpectEq(0, stat.Bavail)
ExpectEq(0, stat.Files)
ExpectEq(0, stat.Ffree)
ExpectEq("osxfusefs", convertName(stat.Fstypename[:]))
ExpectEq("osxfuse", convertName(stat.Fstypename[:]))
ExpectEq(t.canonicalDir, convertName(stat.Mntonname[:]))
ExpectEq(fsName, convertName(stat.Mntfromname[:]))
}
@ -105,7 +105,7 @@ func (t *StatFSTest) Syscall_NonZeroValues() {
ExpectEq(canned.BlocksAvailable, stat.Bavail)
ExpectEq(canned.Inodes, stat.Files)
ExpectEq(canned.InodesFree, stat.Ffree)
ExpectEq("osxfusefs", convertName(stat.Fstypename[:]))
ExpectEq("osxfuse", convertName(stat.Fstypename[:]))
ExpectEq(t.canonicalDir, convertName(stat.Mntonname[:]))
ExpectEq(fsName, convertName(stat.Mntfromname[:]))
}
@ -120,8 +120,8 @@ func (t *StatFSTest) BlockSizes() {
expectedBsize uint32
}{
0: {0, 4096},
1: {1, 512},
2: {3, 512},
1: {1, 128},
2: {3, 128},
3: {511, 512},
4: {512, 512},
5: {513, 1024},
@ -129,16 +129,22 @@ func (t *StatFSTest) BlockSizes() {
7: {1024, 1024},
8: {4095, 4096},
9: {1 << 16, 1 << 16},
10: {1<<17 - 1, 1 << 17},
11: {1 << 17, 1 << 17},
12: {1<<17 + 1, 1 << 17},
13: {1 << 18, 1 << 17},
14: {1 << 20, 1 << 17},
15: {math.MaxInt32 - 1, 1 << 17},
16: {math.MaxInt32, 1 << 17},
17: {math.MaxInt32 + 1, 512},
18: {math.MaxInt32 + 1<<15, 1 << 15},
19: {math.MaxUint32, 1 << 17},
10: {1 << 17, 1 << 17},
11: {1 << 18, 1 << 18},
12: {1 << 19, 1 << 19},
13: {1<<20 - 1, 1 << 20},
14: {1 << 20, 1 << 20},
15: {1<<20 + 1, 1 << 20},
16: {1 << 21, 1 << 20},
17: {1 << 22, 1 << 20},
18: {math.MaxInt32 - 1, 1 << 20},
19: {math.MaxInt32, 1 << 20},
20: {math.MaxInt32 + 1, 128},
21: {math.MaxInt32 + 1<<15, 1 << 15},
22: {math.MaxUint32, 1 << 20},
}
for i, tc := range testCases {
@ -170,23 +176,29 @@ func (t *StatFSTest) IoSizes() {
fsIoSize uint32
expectedIosize uint32
}{
0: {0, 65536},
1: {1, 4096},
2: {3, 4096},
3: {4095, 4096},
4: {4096, 4096},
5: {4097, 8192},
6: {8191, 8192},
7: {8192, 8192},
8: {8193, 16384},
9: {1<<20 - 1, 1 << 20},
0: {0, 65536},
1: {1, 4096},
2: {3, 4096},
3: {4095, 4096},
4: {4096, 4096},
5: {4097, 8192},
6: {8191, 8192},
7: {8192, 8192},
8: {8193, 16384},
9: {1 << 18, 1 << 18},
10: {1 << 20, 1 << 20},
11: {1<<20 + 1, 1 << 20},
12: {math.MaxInt32 - 1, 1 << 20},
13: {math.MaxInt32, 1 << 20},
14: {math.MaxInt32 + 1, 4096},
15: {math.MaxInt32 + 1<<15, 1 << 15},
16: {math.MaxUint32, 1 << 20},
11: {1 << 23, 1 << 23},
12: {1<<25 - 1, 1 << 25},
13: {1 << 25, 1 << 25},
14: {1<<25 + 1, 1 << 25},
15: {math.MaxInt32 - 1, 1 << 25},
16: {math.MaxInt32, 1 << 25},
17: {math.MaxInt32 + 1, 4096},
18: {math.MaxInt32 + 1<<15, 1 << 15},
19: {math.MaxUint32, 1 << 25},
}
for i, tc := range testCases {