diff --git a/fuse/mount_darwin.go b/fuse/mount_darwin.go index 8fccdfb0..eb4587d9 100644 --- a/fuse/mount_darwin.go +++ b/fuse/mount_darwin.go @@ -26,7 +26,7 @@ func unixgramSocketpair() (l, r *os.File, err error) { // Create a FUSE FS on the specified mount point. The returned // mount point is always absolute. -func mount(mountPoint string, opts *MountOptions) (fd int, err error) { +func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) { local, remote, err := unixgramSocketpair() if err != nil { return @@ -65,9 +65,14 @@ func mount(mountPoint string, opts *MountOptions) (fd int, err error) { if err != nil { return -1, err } - ready := make(chan error, 1) + go func() { - // wait inside a goroutine or otherwise it would block forever for unknown reasons + // On macos, mount_osxfuse is not just a suid + // wrapper. It interacts with the FS setup, and will + // not exit until the filesystem has successfully + // responded to INIT and STATFS. This means we can + // only wait on the mount_osxfuse process after the + // server has fully started if err := cmd.Wait(); err != nil { err = fmt.Errorf("mount_osxfusefs failed: %v. Stderr: %s, Stdout: %s", err, errOut.String(), out.String()) @@ -81,9 +86,7 @@ func mount(mountPoint string, opts *MountOptions) (fd int, err error) { // acquired through normal operations (e.g. open). // Buf for fd, we have to set CLOEXEC manually syscall.CloseOnExec(fd) - if err == nil { - err = <-ready - } + return fd, err } diff --git a/fuse/mount_freebsd.go b/fuse/mount_freebsd.go index 19faf264..8ac01fdb 100644 --- a/fuse/mount_freebsd.go +++ b/fuse/mount_freebsd.go @@ -41,7 +41,7 @@ func callMountFuseFs(mountPoint string, opts *MountOptions) (fd int, err error) return int(f.Fd()), nil } -func mount(mountPoint string, opts *MountOptions) (fd int, err error) { +func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) { // Using the same logic from libfuse to prevent chaos for { f, err := os.OpenFile("/dev/null", os.O_RDWR, 0o000) @@ -74,6 +74,7 @@ func mount(mountPoint string, opts *MountOptions) (fd int, err error) { // Buf for fd, we have to set CLOEXEC manually syscall.CloseOnExec(fd) + close(ready) return fd, err } diff --git a/fuse/mount_linux.go b/fuse/mount_linux.go index 805c6c45..96111d3b 100644 --- a/fuse/mount_linux.go +++ b/fuse/mount_linux.go @@ -27,7 +27,7 @@ func unixgramSocketpair() (l, r *os.File, err error) { // Create a FUSE FS on the specified mount point without using // fusermount. -func mountDirect(mountPoint string, opts *MountOptions) (fd int, err error) { +func mountDirect(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) { fd, err = syscall.Open("/dev/fuse", os.O_RDWR, 0) // use syscall.Open since we want an int fd if err != nil { return @@ -76,6 +76,8 @@ func mountDirect(mountPoint string, opts *MountOptions) (fd int, err error) { return } + // success + close(ready) return } @@ -135,9 +137,9 @@ func callFusermount(mountPoint string, opts *MountOptions) (fd int, err error) { // Create a FUSE FS on the specified mount point. The returned // mount point is always absolute. -func mount(mountPoint string, opts *MountOptions) (fd int, err error) { +func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) { if opts.DirectMount || opts.DirectMountStrict { - fd, err := mountDirect(mountPoint, opts) + fd, err := mountDirect(mountPoint, opts, ready) if err == nil { return fd, nil } else if opts.Debug { @@ -166,6 +168,7 @@ func mount(mountPoint string, opts *MountOptions) (fd int, err error) { // acquired through normal operations (e.g. open). // Buf for fd, we have to set CLOEXEC manually syscall.CloseOnExec(fd) + close(ready) return fd, err } diff --git a/fuse/server.go b/fuse/server.go index e907737e..ab0ebecb 100644 --- a/fuse/server.go +++ b/fuse/server.go @@ -77,6 +77,9 @@ type Server struct { loops sync.WaitGroup serving bool // for preventing duplicate Serve() calls + // Used to implement WaitMount on macos. + ready chan error + // for implementing single threaded processing. requestProcessingMu sync.Mutex @@ -209,6 +212,7 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server maxReaders: maxReaders, retrieveTab: make(map[uint64]*retrieveCacheRequest), singleReader: useSingleReader, + ready: make(chan error, 1), } ms.reqPool.New = func() interface{} { return &requestAlloc{ @@ -238,7 +242,7 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server } mountPoint = filepath.Clean(filepath.Join(cwd, mountPoint)) } - fd, err := mount(mountPoint, &o) + fd, err := mount(mountPoint, &o, ms.ready) if err != nil { return nil, err } @@ -995,6 +999,10 @@ func (in *InitIn) supportsRenameSwap() bool { // avoid racing between accessing the (empty or not yet mounted) // mountpoint, and the OS trying to setup the user-space mount. func (ms *Server) WaitMount() error { + err := <-ms.ready + if err != nil { + return err + } if parseFuseFd(ms.mountPoint) >= 0 { // Magic `/dev/fd/N` mountpoint. We don't know the real mountpoint, so // we cannot run the poll hack.