diff --git a/fuseops/ops.go b/fuseops/ops.go index b26d989..609249f 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -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 diff --git a/mount_darwin.go b/mount_darwin.go index 8ecf640..d3dbbb9 100644 --- a/mount_darwin.go +++ b/mount_darwin.go @@ -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 } diff --git a/samples/flushfs/flush_fs_test.go b/samples/flushfs/flush_fs_test.go index e787998..8b5d9aa 100644 --- a/samples/flushfs/flush_fs_test.go +++ b/samples/flushfs/flush_fs_test.go @@ -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() { diff --git a/samples/statfs/statfs_darwin_test.go b/samples/statfs/statfs_darwin_test.go index b19600e..9845fc4 100644 --- a/samples/statfs/statfs_darwin_test.go +++ b/samples/statfs/statfs_darwin_test.go @@ -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 {