From 695b70b1e63da3b0ad1f6f1afe0d23d5fe5cce29 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:39:04 +1000 Subject: [PATCH 01/14] Pasted the implementation from bazilfuse. --- mount_darwin.go | 131 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 mount_darwin.go diff --git a/mount_darwin.go b/mount_darwin.go new file mode 100644 index 0000000..cf8646f --- /dev/null +++ b/mount_darwin.go @@ -0,0 +1,131 @@ +package fuseshim + +import ( + "bytes" + "errors" + "fmt" + "os" + "os/exec" + "strconv" + "strings" + "syscall" +) + +// OS X appears to cap the size of writes to 1 MiB. This constant is also used +// for sizing receive buffers, so make it as small as it can be without +// limiting write sizes. +const maxWrite = 1 << 20 + +var errNoAvail = errors.New("no available fuse devices") + +var errNotLoaded = errors.New("osxfusefs is not loaded") + +func loadOSXFUSE() error { + cmd := exec.Command("/Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs") + cmd.Dir = "/" + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Run() + return err +} + +func openOSXFUSEDev() (*os.File, error) { + var f *os.File + var err error + for i := uint64(0); ; i++ { + path := "/dev/osxfuse" + strconv.FormatUint(i, 10) + f, err = os.OpenFile(path, os.O_RDWR, 0000) + if os.IsNotExist(err) { + if i == 0 { + // not even the first device was found -> fuse is not loaded + return nil, errNotLoaded + } + + // we've run out of kernel-provided devices + return nil, errNoAvail + } + + if err2, ok := err.(*os.PathError); ok && err2.Err == syscall.EBUSY { + // try the next one + continue + } + + if err != nil { + return nil, err + } + return f, nil + } +} + +func callMount(dir string, conf *mountConfig, f *os.File, ready chan<- struct{}, errp *error) error { + bin := "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs" + + for k, v := range conf.options { + if strings.Contains(k, ",") || strings.Contains(v, ",") { + // Silly limitation but the mount helper does not + // understand any escaping. See TestMountOptionCommaError. + return fmt.Errorf("mount options cannot contain commas on darwin: %q=%q", k, v) + } + } + cmd := exec.Command( + bin, + "-o", conf.getOptions(), + // Tell osxfuse-kext how large our buffer is. It must split + // writes larger than this into multiple writes. + // + // OSXFUSE seems to ignore InitResponse.MaxWrite, and uses + // this instead. + "-o", "iosize="+strconv.FormatUint(maxWrite, 10), + // refers to fd passed in cmd.ExtraFiles + "3", + dir, + ) + cmd.ExtraFiles = []*os.File{f} + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_CALL_BY_LIB=") + // TODO this is used for fs typenames etc, let app influence it + cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_DAEMON_PATH="+bin) + var buf bytes.Buffer + cmd.Stdout = &buf + cmd.Stderr = &buf + + err := cmd.Start() + if err != nil { + return err + } + go func() { + err := cmd.Wait() + if err != nil { + if buf.Len() > 0 { + output := buf.Bytes() + output = bytes.TrimRight(output, "\n") + msg := err.Error() + ": " + string(output) + err = errors.New(msg) + } + } + *errp = err + close(ready) + }() + return err +} + +func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) { + f, err := openOSXFUSEDev() + if err == errNotLoaded { + err = loadOSXFUSE() + if err != nil { + return nil, err + } + // try again + f, err = openOSXFUSEDev() + } + if err != nil { + return nil, err + } + err = callMount(dir, conf, f, ready, errp) + if err != nil { + f.Close() + return nil, err + } + return f, nil +} From 56758cb302096f1173274d39c434117622e66de1 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:45:16 +1000 Subject: [PATCH 02/14] Gave mount a makeover. --- mount_darwin.go | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/mount_darwin.go b/mount_darwin.go index cf8646f..1f9bc55 100644 --- a/mount_darwin.go +++ b/mount_darwin.go @@ -1,4 +1,4 @@ -package fuseshim +package fuse import ( "bytes" @@ -109,23 +109,42 @@ func callMount(dir string, conf *mountConfig, f *os.File, ready chan<- struct{}, return err } -func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) { - f, err := openOSXFUSEDev() +// Begin the process of mounting at the given directory, returning a connection +// to the kernel. Mounting continues in the background, and is complete when an +// error is written to the supplied channel. The file system may need to +// service the connection in order for mounting to complete. +func mount( + dir string, + conf *mountConfig, + ready chan<- error) (dev *os.File, err error) { + // Open the device. + dev, err = openOSXFUSEDev() + + // Special case: we may need to explicitly load osxfuse. Load it, then try + // again. if err == errNotLoaded { err = loadOSXFUSE() if err != nil { - return nil, err + err = fmt.Errorf("loadOSXFUSE: %v", err) + return } - // try again - f, err = openOSXFUSEDev() + + dev, err = openOSXFUSEDev() } + + // Propagate errors. if err != nil { - return nil, err + err = fmt.Errorf("openOSXFUSEDev: %v", err) + return } - err = callMount(dir, conf, f, ready, errp) + + // Call the mount binary with the device. + err = callMount(dir, conf, dev, ready) if err != nil { - f.Close() - return nil, err + dev.Close() + err = fmt.Errorf("callMount: %v", err) + return } - return f, nil + + return } From 1bb0fd472497dcc36d3e586e6450878ec2d51420 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:45:34 +1000 Subject: [PATCH 03/14] Trimmed a bit. --- mount_darwin.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/mount_darwin.go b/mount_darwin.go index 1f9bc55..4980396 100644 --- a/mount_darwin.go +++ b/mount_darwin.go @@ -11,11 +11,6 @@ import ( "syscall" ) -// OS X appears to cap the size of writes to 1 MiB. This constant is also used -// for sizing receive buffers, so make it as small as it can be without -// limiting write sizes. -const maxWrite = 1 << 20 - var errNoAvail = errors.New("no available fuse devices") var errNotLoaded = errors.New("osxfusefs is not loaded") From eda994c2e66a9dc298c24b8ebdb7fad2b08f7485 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:48:17 +1000 Subject: [PATCH 04/14] Gave openOSXFUSEDev a makeover. --- mount_darwin.go | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/mount_darwin.go b/mount_darwin.go index 4980396..c540228 100644 --- a/mount_darwin.go +++ b/mount_darwin.go @@ -24,31 +24,29 @@ func loadOSXFUSE() error { return err } -func openOSXFUSEDev() (*os.File, error) { - var f *os.File - var err error +func openOSXFUSEDev() (dev *os.File, err error) { + // Try each device name. for i := uint64(0); ; i++ { - path := "/dev/osxfuse" + strconv.FormatUint(i, 10) - f, err = os.OpenFile(path, os.O_RDWR, 0000) + path := fmt.Sprintf("/dev/osxfuse%d", i) + dev, err = os.OpenFile(path, os.O_RDWR, 0000) if os.IsNotExist(err) { if i == 0 { - // not even the first device was found -> fuse is not loaded - return nil, errNotLoaded + // Not even the first device was found. Fuse must not be loaded. + err = errNotLoaded + return } - // we've run out of kernel-provided devices - return nil, errNoAvail + // Otherwise we've run out of kernel-provided devices + err = errNoAvail + return } if err2, ok := err.(*os.PathError); ok && err2.Err == syscall.EBUSY { - // try the next one + // This device is in use; try the next one. continue } - if err != nil { - return nil, err - } - return f, nil + return } } From a9693474e8b68e2bb96fc564f90f07fa2a767182 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:48:23 +1000 Subject: [PATCH 05/14] Fixed a build error. --- mount_darwin.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mount_darwin.go b/mount_darwin.go index c540228..ababb03 100644 --- a/mount_darwin.go +++ b/mount_darwin.go @@ -9,10 +9,11 @@ import ( "strconv" "strings" "syscall" + + "github.com/jacobsa/fuse/internal/buffer" ) var errNoAvail = errors.New("no available fuse devices") - var errNotLoaded = errors.New("osxfusefs is not loaded") func loadOSXFUSE() error { @@ -68,7 +69,7 @@ func callMount(dir string, conf *mountConfig, f *os.File, ready chan<- struct{}, // // OSXFUSE seems to ignore InitResponse.MaxWrite, and uses // this instead. - "-o", "iosize="+strconv.FormatUint(maxWrite, 10), + "-o", "iosize="+strconv.FormatUint(buffer.MaxWriteSize, 10), // refers to fd passed in cmd.ExtraFiles "3", dir, From 19934c68be62c2b1d2a1addbfe3a69d2d148c67c Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:51:18 +1000 Subject: [PATCH 06/14] Touched up callMount. --- mount_darwin.go | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/mount_darwin.go b/mount_darwin.go index ababb03..41137cd 100644 --- a/mount_darwin.go +++ b/mount_darwin.go @@ -51,16 +51,25 @@ func openOSXFUSEDev() (dev *os.File, err error) { } } -func callMount(dir string, conf *mountConfig, f *os.File, ready chan<- struct{}, errp *error) error { - bin := "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs" +func callMount( + 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 conf.options { if strings.Contains(k, ",") || strings.Contains(v, ",") { - // Silly limitation but the mount helper does not - // understand any escaping. See TestMountOptionCommaError. - return fmt.Errorf("mount options cannot contain commas on darwin: %q=%q", k, v) + return fmt.Errorf( + "mount options cannot contain commas on darwin: %q=%q", + k, + v) } } + + // Call the mount helper, passing in the device file and saving output into a + // buffer. cmd := exec.Command( bin, "-o", conf.getOptions(), @@ -74,33 +83,36 @@ func callMount(dir string, conf *mountConfig, f *os.File, ready chan<- struct{}, "3", dir, ) - cmd.ExtraFiles = []*os.File{f} + cmd.ExtraFiles = []*os.File{dev} cmd.Env = os.Environ() cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_CALL_BY_LIB=") // TODO this is used for fs typenames etc, let app influence it cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_DAEMON_PATH="+bin) + var buf bytes.Buffer cmd.Stdout = &buf cmd.Stderr = &buf - err := cmd.Start() + err = cmd.Start() if err != nil { - return err + return } + + // In the background, wait for the command to complete. go func() { err := cmd.Wait() if err != nil { if buf.Len() > 0 { output := buf.Bytes() output = bytes.TrimRight(output, "\n") - msg := err.Error() + ": " + string(output) - err = errors.New(msg) + err = fmt.Errorf("%v: %s", err, output) } } - *errp = err - close(ready) + + ready <- err }() - return err + + return } // Begin the process of mounting at the given directory, returning a connection From cd6c68c83f573d2478b6291b7df546021df971f3 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:54:02 +1000 Subject: [PATCH 07/14] Fixed some build errors. --- mount_darwin.go | 10 +++++----- mounted_file_system.go | 11 +++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/mount_darwin.go b/mount_darwin.go index 41137cd..428b73a 100644 --- a/mount_darwin.go +++ b/mount_darwin.go @@ -53,13 +53,13 @@ func openOSXFUSEDev() (dev *os.File, err error) { func callMount( dir string, - cfg *mountConfig, + 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 conf.options { + for k, v := range cfg.toMap() { if strings.Contains(k, ",") || strings.Contains(v, ",") { return fmt.Errorf( "mount options cannot contain commas on darwin: %q=%q", @@ -72,7 +72,7 @@ func callMount( // buffer. cmd := exec.Command( bin, - "-o", conf.getOptions(), + "-o", cfg.toOptionsString(), // Tell osxfuse-kext how large our buffer is. It must split // writes larger than this into multiple writes. // @@ -121,7 +121,7 @@ func callMount( // service the connection in order for mounting to complete. func mount( dir string, - conf *mountConfig, + cfg *MountConfig, ready chan<- error) (dev *os.File, err error) { // Open the device. dev, err = openOSXFUSEDev() @@ -145,7 +145,7 @@ func mount( } // Call the mount binary with the device. - err = callMount(dir, conf, dev, ready) + err = callMount(dir, cfg, dev, ready) if err != nil { dev.Close() err = fmt.Errorf("callMount: %v", err) diff --git a/mounted_file_system.go b/mounted_file_system.go index 4a57ec6..2d4ab4e 100644 --- a/mounted_file_system.go +++ b/mounted_file_system.go @@ -112,6 +112,17 @@ type MountConfig struct { Options map[string]string } +// Create a map containing all of the key=value mount options to be given to +// the mount helper. +func (c *MountConfig) toMap() (opts map[string]string) { + panic("TODO") +} + +// Create an options string suitable for passing to the mount helper. +func (c *MountConfig) toOptionsString() string { + panic("TODO") +} + // Convert to mount options to be passed to package fuseshim. func (c *MountConfig) bazilfuseOptions() (opts []fuseshim.MountOption) { isDarwin := runtime.GOOS == "darwin" From d2b9accc31805aed67ca858db591e4d71a3bc7a6 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:55:03 +1000 Subject: [PATCH 08/14] Moved MountConfig into a separate file. --- mount_config.go | 158 +++++++++++++++++++++++++++++++++++++++++ mounted_file_system.go | 136 ----------------------------------- 2 files changed, 158 insertions(+), 136 deletions(-) create mode 100644 mount_config.go diff --git a/mount_config.go b/mount_config.go new file mode 100644 index 0000000..8081591 --- /dev/null +++ b/mount_config.go @@ -0,0 +1,158 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fuse + +import ( + "log" + "runtime" + + "github.com/jacobsa/fuse/internal/fuseshim" + + "golang.org/x/net/context" +) + +// Optional configuration accepted by Mount. +type MountConfig struct { + // The context from which every op read from the connetion by the sever + // should inherit. If nil, context.Background() will be used. + OpContext context.Context + + // If non-empty, the name of the file system as displayed by e.g. `mount`. + // This is important because the `umount` command requires root privileges if + // it doesn't agree with /etc/fstab. + FSName string + + // Mount the file system in read-only mode. File modes will appear as normal, + // but opening a file for writing and metadata operations like chmod, + // chtimes, etc. will fail. + ReadOnly bool + + // A logger to use for logging errors. All errors are logged, with the + // exception of a few blacklisted errors that are expected. If nil, no error + // logging is performed. + ErrorLogger *log.Logger + + // A logger to use for logging debug information. If nil, no debug logging is + // performed. + DebugLogger *log.Logger + + // OS X only. + // + // Normally on OS X we mount with the novncache option + // (cf. http://goo.gl/1pTjuk), which disables entry caching in the kernel. + // This is because osxfuse does not honor the entry expiration values we + // return to it, instead caching potentially forever (cf. + // http://goo.gl/8yR0Ie), and it is probably better to fail to cache than to + // cache for too long, since the latter is more likely to hide consistency + // bugs that are difficult to detect and diagnose. + // + // This field disables the use of novncache, restoring entry caching. Beware: + // the value of ChildInodeEntry.EntryExpiration is ignored by the kernel, and + // entries will be cached for an arbitrarily long time. + EnableVnodeCaching bool + + // Additional key=value options to pass unadulterated to the underlying mount + // command. See `man 8 mount`, the fuse documentation, etc. for + // system-specific information. + // + // For expert use only! May invalidate other guarantees made in the + // documentation for this package. + Options map[string]string +} + +// Create a map containing all of the key=value mount options to be given to +// the mount helper. +func (c *MountConfig) toMap() (opts map[string]string) { + panic("TODO") +} + +// Create an options string suitable for passing to the mount helper. +func (c *MountConfig) toOptionsString() string { + panic("TODO") +} + +// Convert to mount options to be passed to package fuseshim. +func (c *MountConfig) bazilfuseOptions() (opts []fuseshim.MountOption) { + isDarwin := runtime.GOOS == "darwin" + + // Enable permissions checking in the kernel. See the comments on + // InodeAttributes.Mode. + opts = append(opts, fuseshim.SetOption("default_permissions", "")) + + // HACK(jacobsa): Work around what appears to be a bug in systemd v219, as + // shipped in Ubuntu 15.04, where it automatically unmounts any file system + // that doesn't set an explicit name. + // + // When Ubuntu contains systemd v220, this workaround should be removed and + // the systemd bug reopened if the problem persists. + // + // Cf. https://github.com/bazil/fuse/issues/89 + // Cf. https://bugs.freedesktop.org/show_bug.cgi?id=90907 + fsname := c.FSName + if runtime.GOOS == "linux" && fsname == "" { + fsname = "some_fuse_file_system" + } + + // Special file system name? + if fsname != "" { + opts = append(opts, fuseshim.FSName(fsname)) + } + + // Read only? + if c.ReadOnly { + opts = append(opts, fuseshim.ReadOnly()) + } + + // OS X: set novncache when appropriate. + if isDarwin && !c.EnableVnodeCaching { + opts = append(opts, fuseshim.SetOption("novncache", "")) + } + + // OS X: disable the use of "Apple Double" (._foo and .DS_Store) files, which + // just add noise to debug output and can have significant cost on + // network-based file systems. + // + // Cf. https://github.com/osxfuse/osxfuse/wiki/Mount-options + if isDarwin { + opts = append(opts, fuseshim.SetOption("noappledouble", "")) + } + + // Ask the Linux kernel for larger read requests. + // + // As of 2015-03-26, the behavior in the kernel is: + // + // * (http://goo.gl/bQ1f1i, http://goo.gl/HwBrR6) Set the local variable + // ra_pages to be init_response->max_readahead divided by the page size. + // + // * (http://goo.gl/gcIsSh, http://goo.gl/LKV2vA) Set + // backing_dev_info::ra_pages to the min of that value and what was sent + // in the request's max_readahead field. + // + // * (http://goo.gl/u2SqzH) Use backing_dev_info::ra_pages when deciding + // how much to read ahead. + // + // * (http://goo.gl/JnhbdL) Don't read ahead at all if that field is zero. + // + // Reading a page at a time is a drag. Ask for a larger size. + const maxReadahead = 1 << 20 + opts = append(opts, fuseshim.MaxReadahead(maxReadahead)) + + // Last but not least: other user-supplied options. + for k, v := range c.Options { + opts = append(opts, fuseshim.SetOption(k, v)) + } + + return +} diff --git a/mounted_file_system.go b/mounted_file_system.go index 2d4ab4e..d070353 100644 --- a/mounted_file_system.go +++ b/mounted_file_system.go @@ -16,8 +16,6 @@ package fuse import ( "fmt" - "log" - "runtime" "github.com/jacobsa/fuse/internal/fuseshim" @@ -63,140 +61,6 @@ func (mfs *MountedFileSystem) Join(ctx context.Context) error { } } -// Optional configuration accepted by Mount. -type MountConfig struct { - // The context from which every op read from the connetion by the sever - // should inherit. If nil, context.Background() will be used. - OpContext context.Context - - // If non-empty, the name of the file system as displayed by e.g. `mount`. - // This is important because the `umount` command requires root privileges if - // it doesn't agree with /etc/fstab. - FSName string - - // Mount the file system in read-only mode. File modes will appear as normal, - // but opening a file for writing and metadata operations like chmod, - // chtimes, etc. will fail. - ReadOnly bool - - // A logger to use for logging errors. All errors are logged, with the - // exception of a few blacklisted errors that are expected. If nil, no error - // logging is performed. - ErrorLogger *log.Logger - - // A logger to use for logging debug information. If nil, no debug logging is - // performed. - DebugLogger *log.Logger - - // OS X only. - // - // Normally on OS X we mount with the novncache option - // (cf. http://goo.gl/1pTjuk), which disables entry caching in the kernel. - // This is because osxfuse does not honor the entry expiration values we - // return to it, instead caching potentially forever (cf. - // http://goo.gl/8yR0Ie), and it is probably better to fail to cache than to - // cache for too long, since the latter is more likely to hide consistency - // bugs that are difficult to detect and diagnose. - // - // This field disables the use of novncache, restoring entry caching. Beware: - // the value of ChildInodeEntry.EntryExpiration is ignored by the kernel, and - // entries will be cached for an arbitrarily long time. - EnableVnodeCaching bool - - // Additional key=value options to pass unadulterated to the underlying mount - // command. See `man 8 mount`, the fuse documentation, etc. for - // system-specific information. - // - // For expert use only! May invalidate other guarantees made in the - // documentation for this package. - Options map[string]string -} - -// Create a map containing all of the key=value mount options to be given to -// the mount helper. -func (c *MountConfig) toMap() (opts map[string]string) { - panic("TODO") -} - -// Create an options string suitable for passing to the mount helper. -func (c *MountConfig) toOptionsString() string { - panic("TODO") -} - -// Convert to mount options to be passed to package fuseshim. -func (c *MountConfig) bazilfuseOptions() (opts []fuseshim.MountOption) { - isDarwin := runtime.GOOS == "darwin" - - // Enable permissions checking in the kernel. See the comments on - // InodeAttributes.Mode. - opts = append(opts, fuseshim.SetOption("default_permissions", "")) - - // HACK(jacobsa): Work around what appears to be a bug in systemd v219, as - // shipped in Ubuntu 15.04, where it automatically unmounts any file system - // that doesn't set an explicit name. - // - // When Ubuntu contains systemd v220, this workaround should be removed and - // the systemd bug reopened if the problem persists. - // - // Cf. https://github.com/bazil/fuse/issues/89 - // Cf. https://bugs.freedesktop.org/show_bug.cgi?id=90907 - fsname := c.FSName - if runtime.GOOS == "linux" && fsname == "" { - fsname = "some_fuse_file_system" - } - - // Special file system name? - if fsname != "" { - opts = append(opts, fuseshim.FSName(fsname)) - } - - // Read only? - if c.ReadOnly { - opts = append(opts, fuseshim.ReadOnly()) - } - - // OS X: set novncache when appropriate. - if isDarwin && !c.EnableVnodeCaching { - opts = append(opts, fuseshim.SetOption("novncache", "")) - } - - // OS X: disable the use of "Apple Double" (._foo and .DS_Store) files, which - // just add noise to debug output and can have significant cost on - // network-based file systems. - // - // Cf. https://github.com/osxfuse/osxfuse/wiki/Mount-options - if isDarwin { - opts = append(opts, fuseshim.SetOption("noappledouble", "")) - } - - // Ask the Linux kernel for larger read requests. - // - // As of 2015-03-26, the behavior in the kernel is: - // - // * (http://goo.gl/bQ1f1i, http://goo.gl/HwBrR6) Set the local variable - // ra_pages to be init_response->max_readahead divided by the page size. - // - // * (http://goo.gl/gcIsSh, http://goo.gl/LKV2vA) Set - // backing_dev_info::ra_pages to the min of that value and what was sent - // in the request's max_readahead field. - // - // * (http://goo.gl/u2SqzH) Use backing_dev_info::ra_pages when deciding - // how much to read ahead. - // - // * (http://goo.gl/JnhbdL) Don't read ahead at all if that field is zero. - // - // Reading a page at a time is a drag. Ask for a larger size. - const maxReadahead = 1 << 20 - opts = append(opts, fuseshim.MaxReadahead(maxReadahead)) - - // Last but not least: other user-supplied options. - for k, v := range c.Options { - opts = append(opts, fuseshim.SetOption(k, v)) - } - - return -} - // Attempt to mount a file system on the given directory, using the supplied // Server to serve connection requests. This function blocks until the file // system is successfully mounted. From baa7c29fad8bee992d9c1bdd518e55b077523b2c Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:56:54 +1000 Subject: [PATCH 09/14] Use the mount helper in Mount. --- mounted_file_system.go | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/mounted_file_system.go b/mounted_file_system.go index d070353..54532bb 100644 --- a/mounted_file_system.go +++ b/mounted_file_system.go @@ -17,8 +17,6 @@ package fuse import ( "fmt" - "github.com/jacobsa/fuse/internal/fuseshim" - "golang.org/x/net/context" ) @@ -74,10 +72,11 @@ func Mount( joinStatusAvailable: make(chan struct{}), } - // Open a fuseshim connection. - bfConn, err := fuseshim.Mount(mfs.dir, config.bazilfuseOptions()...) + // Begin the mounting process, which will continue in the background. + ready := make(chan error, 1) + dev, err := mount(dir, config, ready) if err != nil { - err = fmt.Errorf("fuseshim.Mount: %v", err) + err = fmt.Errorf("mount: %v", err) return } @@ -87,15 +86,14 @@ func Mount( opContext = context.Background() } - // Create our own Connection object wrapping it. + // Create a Connection object wrapping the device. connection, err := newConnection( opContext, config.DebugLogger, config.ErrorLogger, - bfConn) + dev) if err != nil { - bfConn.Close() err = fmt.Errorf("newConnection: %v", err) return } @@ -107,9 +105,9 @@ func Mount( close(mfs.joinStatusAvailable) }() - // Wait for the connection to say it is ready. - if err = connection.waitForReady(); err != nil { - err = fmt.Errorf("WaitForReady: %v", err) + // Wait for the mount process to complete. + if err = <-ready; err != nil { + err = fmt.Errorf("mount (background): %v", err) return } From 28605268713e6c49c9d7a0377f38307277e409bd Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:01:45 +1000 Subject: [PATCH 10/14] Updated newConnection. --- connection.go | 18 +++++++++++++++--- internal/fuseshim/fuse.go | 11 +++++++---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/connection.go b/connection.go index 4e83b8b..878f302 100644 --- a/connection.go +++ b/connection.go @@ -58,15 +58,27 @@ type Connection struct { cancelFuncs map[uint64]func() } -// Responsibility for closing the wrapped connection is transferred to the -// result. You must call c.close() eventually. +// Create a connection wrapping the supplied file descriptor connected to the +// kernel. You must eventually call c.close(). // // The loggers may be nil. func newConnection( parentCtx context.Context, debugLogger *log.Logger, errorLogger *log.Logger, - wrapped *fuseshim.Conn) (c *Connection, err error) { + dev *os.File) (c *Connection, err error) { + // Create an initialized a wrapped fuseshim connection. + wrapped := &fuseshim.Conn{ + Dev: dev, + } + + err = fuseshim.InitMount(wrapped, TODO, TODO) + if err != nil { + err = fmt.Errorf("fuseshim.InitMount: %v", err) + return + } + + // Create an object wrapping it. c = &Connection{ debugLogger: debugLogger, errorLogger: errorLogger, diff --git a/internal/fuseshim/fuse.go b/internal/fuseshim/fuse.go index 22fde82..8b2d3a1 100644 --- a/internal/fuseshim/fuse.go +++ b/internal/fuseshim/fuse.go @@ -164,7 +164,7 @@ func Mount(dir string, options ...MountOption) (*Conn, error) { } c.Dev = f - if err := initMount(c, &conf); err != nil { + if err := InitMount(c, conf.maxReadahead, conf.initFlags); err != nil { c.Close() return nil, err } @@ -181,7 +181,10 @@ func (e *OldVersionError) Error() string { return fmt.Sprintf("kernel FUSE version is too old: %v < %v", e.Kernel, e.LibraryMin) } -func initMount(c *Conn, conf *mountConfig) error { +func InitMount( + c *Conn, + maxReadahead uint32, + initFlags fusekernel.InitFlags) error { req, err := c.ReadRequest() if err != nil { if err == io.EOF { @@ -213,9 +216,9 @@ func initMount(c *Conn, conf *mountConfig) error { s := &InitResponse{ Library: proto, - MaxReadahead: conf.maxReadahead, + MaxReadahead: maxReadahead, MaxWrite: maxWrite, - Flags: fusekernel.InitBigWrites | conf.initFlags, + Flags: fusekernel.InitBigWrites | initFlags, } r.Respond(s) return nil From 91664cfc31f23af5d5e1310cc74cbd67a3c2002c Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:03:24 +1000 Subject: [PATCH 11/14] Fixed maxReadahead. --- connection.go | 21 ++++++++++++++++++++- mount_config.go | 20 -------------------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/connection.go b/connection.go index 878f302..ab465bd 100644 --- a/connection.go +++ b/connection.go @@ -32,6 +32,25 @@ import ( "github.com/jacobsa/fuse/internal/fuseshim" ) +// Ask the Linux kernel for larger read requests. +// +// As of 2015-03-26, the behavior in the kernel is: +// +// * (http://goo.gl/bQ1f1i, http://goo.gl/HwBrR6) Set the local variable +// ra_pages to be init_response->max_readahead divided by the page size. +// +// * (http://goo.gl/gcIsSh, http://goo.gl/LKV2vA) Set +// backing_dev_info::ra_pages to the min of that value and what was sent +// in the request's max_readahead field. +// +// * (http://goo.gl/u2SqzH) Use backing_dev_info::ra_pages when deciding +// how much to read ahead. +// +// * (http://goo.gl/JnhbdL) Don't read ahead at all if that field is zero. +// +// Reading a page at a time is a drag. Ask for a larger size. +const maxReadahead = 1 << 20 + // A connection to the fuse kernel process. type Connection struct { debugLogger *log.Logger @@ -72,7 +91,7 @@ func newConnection( Dev: dev, } - err = fuseshim.InitMount(wrapped, TODO, TODO) + err = fuseshim.InitMount(wrapped, maxReadahead, TODO) if err != nil { err = fmt.Errorf("fuseshim.InitMount: %v", err) return diff --git a/mount_config.go b/mount_config.go index 8081591..cf1fff8 100644 --- a/mount_config.go +++ b/mount_config.go @@ -129,26 +129,6 @@ func (c *MountConfig) bazilfuseOptions() (opts []fuseshim.MountOption) { opts = append(opts, fuseshim.SetOption("noappledouble", "")) } - // Ask the Linux kernel for larger read requests. - // - // As of 2015-03-26, the behavior in the kernel is: - // - // * (http://goo.gl/bQ1f1i, http://goo.gl/HwBrR6) Set the local variable - // ra_pages to be init_response->max_readahead divided by the page size. - // - // * (http://goo.gl/gcIsSh, http://goo.gl/LKV2vA) Set - // backing_dev_info::ra_pages to the min of that value and what was sent - // in the request's max_readahead field. - // - // * (http://goo.gl/u2SqzH) Use backing_dev_info::ra_pages when deciding - // how much to read ahead. - // - // * (http://goo.gl/JnhbdL) Don't read ahead at all if that field is zero. - // - // Reading a page at a time is a drag. Ask for a larger size. - const maxReadahead = 1 << 20 - opts = append(opts, fuseshim.MaxReadahead(maxReadahead)) - // Last but not least: other user-supplied options. for k, v := range c.Options { opts = append(opts, fuseshim.SetOption(k, v)) From ba66e02af11d9d55a0c8fdf84fa88aeea1d93fd1 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:05:01 +1000 Subject: [PATCH 12/14] Fixed initFlags. --- connection.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connection.go b/connection.go index ab465bd..b136e06 100644 --- a/connection.go +++ b/connection.go @@ -91,7 +91,7 @@ func newConnection( Dev: dev, } - err = fuseshim.InitMount(wrapped, maxReadahead, TODO) + err = fuseshim.InitMount(wrapped, maxReadahead, 0) if err != nil { err = fmt.Errorf("fuseshim.InitMount: %v", err) return From 440c9ee7f7441bb1c7c27535c99f7ad0b680384c Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:06:53 +1000 Subject: [PATCH 13/14] MountConfig.toMap --- mount_config.go | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/mount_config.go b/mount_config.go index cf1fff8..e20c915 100644 --- a/mount_config.go +++ b/mount_config.go @@ -18,8 +18,6 @@ import ( "log" "runtime" - "github.com/jacobsa/fuse/internal/fuseshim" - "golang.org/x/net/context" ) @@ -75,21 +73,12 @@ type MountConfig struct { // Create a map containing all of the key=value mount options to be given to // the mount helper. func (c *MountConfig) toMap() (opts map[string]string) { - panic("TODO") -} - -// Create an options string suitable for passing to the mount helper. -func (c *MountConfig) toOptionsString() string { - panic("TODO") -} - -// Convert to mount options to be passed to package fuseshim. -func (c *MountConfig) bazilfuseOptions() (opts []fuseshim.MountOption) { isDarwin := runtime.GOOS == "darwin" + opts = make(map[string]string) // Enable permissions checking in the kernel. See the comments on // InodeAttributes.Mode. - opts = append(opts, fuseshim.SetOption("default_permissions", "")) + opts["default_permissions"] = "" // HACK(jacobsa): Work around what appears to be a bug in systemd v219, as // shipped in Ubuntu 15.04, where it automatically unmounts any file system @@ -107,17 +96,17 @@ func (c *MountConfig) bazilfuseOptions() (opts []fuseshim.MountOption) { // Special file system name? if fsname != "" { - opts = append(opts, fuseshim.FSName(fsname)) + opts["fsname"] = fsname } // Read only? if c.ReadOnly { - opts = append(opts, fuseshim.ReadOnly()) + opts["ro"] = "" } // OS X: set novncache when appropriate. if isDarwin && !c.EnableVnodeCaching { - opts = append(opts, fuseshim.SetOption("novncache", "")) + opts["novncache"] = "" } // OS X: disable the use of "Apple Double" (._foo and .DS_Store) files, which @@ -126,13 +115,18 @@ func (c *MountConfig) bazilfuseOptions() (opts []fuseshim.MountOption) { // // Cf. https://github.com/osxfuse/osxfuse/wiki/Mount-options if isDarwin { - opts = append(opts, fuseshim.SetOption("noappledouble", "")) + opts["noappledouble"] = "" } // Last but not least: other user-supplied options. for k, v := range c.Options { - opts = append(opts, fuseshim.SetOption(k, v)) + opts[k] = v } return } + +// Create an options string suitable for passing to the mount helper. +func (c *MountConfig) toOptionsString() string { + panic("TODO") +} From f4d8f9816579c857d0fb97295c476e391dde8560 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:09:15 +1000 Subject: [PATCH 14/14] MountConfig.toOptionsString --- mount_config.go | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/mount_config.go b/mount_config.go index e20c915..dd76561 100644 --- a/mount_config.go +++ b/mount_config.go @@ -15,8 +15,10 @@ package fuse import ( + "fmt" "log" "runtime" + "strings" "golang.org/x/net/context" ) @@ -126,7 +128,26 @@ func (c *MountConfig) toMap() (opts map[string]string) { return } +func escapeOptionsKey(s string) (res string) { + res = s + res = strings.Replace(res, `\`, `\\`, -1) + res = strings.Replace(res, `,`, `\,`, -1) + return +} + // Create an options string suitable for passing to the mount helper. func (c *MountConfig) toOptionsString() string { - panic("TODO") + var components []string + for k, v := range c.toMap() { + k = escapeOptionsKey(k) + + component := k + if v != "" { + component = fmt.Sprintf("%s=%s", k, v) + } + + components = append(components, component) + } + + return strings.Join(components, ",") }