1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
|
// 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 (
"fmt"
"io/ioutil"
"log"
"runtime"
"github.com/jacobsa/bazilfuse"
"golang.org/x/net/context"
)
// A type that knows how to serve ops read from a connection.
type Server interface {
// Read and serve ops from the supplied connection until EOF. Do not return
// until all operations have been responded to. Must not be called more than
// once.
ServeOps(*Connection)
}
// A struct representing the status of a mount operation, with a method that
// waits for unmounting.
type MountedFileSystem struct {
dir string
// The result to return from Join. Not valid until the channel is closed.
joinStatus error
joinStatusAvailable chan struct{}
}
// Return the directory on which the file system is mounted (or where we
// attempted to mount it.)
func (mfs *MountedFileSystem) Dir() string {
return mfs.dir
}
// Block until a mounted file system has been unmounted. Do not return
// successfully until all ops read from the connection have been responded to
// (i.e. the file system server has finished processing all in-flight ops).
//
// The return value will be non-nil if anything unexpected happened while
// serving. May be called multiple times.
func (mfs *MountedFileSystem) Join(ctx context.Context) error {
select {
case <-mfs.joinStatusAvailable:
return mfs.joinStatus
case <-ctx.Done():
return ctx.Err()
}
}
// 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
// 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
}
// Convert to mount options to be passed to package bazilfuse.
func (c *MountConfig) bazilfuseOptions() (opts []bazilfuse.MountOption) {
isDarwin := runtime.GOOS == "darwin"
// Enable permissions checking in the kernel. See the comments on
// InodeAttributes.Mode.
opts = append(opts, bazilfuse.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, bazilfuse.FSName(fsname))
}
// Read only?
if c.ReadOnly {
opts = append(opts, bazilfuse.ReadOnly())
}
// OS X: set novncache when appropriate.
if isDarwin && !c.EnableVnodeCaching {
opts = append(opts, bazilfuse.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, bazilfuse.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, bazilfuse.MaxReadahead(maxReadahead))
// Last but not least: other user-supplied options.
for k, v := range c.Options {
opts = append(opts, bazilfuse.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.
func Mount(
dir string,
server Server,
config *MountConfig) (mfs *MountedFileSystem, err error) {
debugLogger := getDebugLogger()
// Initialize the struct.
mfs = &MountedFileSystem{
dir: dir,
joinStatusAvailable: make(chan struct{}),
}
// Open a bazilfuse connection.
debugLogger.Println("Opening a bazilfuse connection.")
bfConn, err := bazilfuse.Mount(mfs.dir, config.bazilfuseOptions()...)
if err != nil {
err = fmt.Errorf("bazilfuse.Mount: %v", err)
return
}
// Choose a parent context for ops.
opContext := config.OpContext
if opContext == nil {
opContext = context.Background()
}
// Create a /dev/null error logger if necessary.
errorLogger := config.ErrorLogger
if errorLogger == nil {
errorLogger = log.New(ioutil.Discard, "", 0)
}
// Create our own Connection object wrapping it.
connection, err := newConnection(
opContext,
debugLogger,
errorLogger,
bfConn)
if err != nil {
bfConn.Close()
err = fmt.Errorf("newConnection: %v", err)
return
}
// Serve the connection in the background. When done, set the join status.
go func() {
server.ServeOps(connection)
mfs.joinStatus = connection.close()
close(mfs.joinStatusAvailable)
}()
// Wait for the connection to say it is ready.
if err = connection.waitForReady(); err != nil {
err = fmt.Errorf("WaitForReady: %v", err)
return
}
return
}
|