File: mounted_file_system.go

package info (click to toggle)
golang-github-jacobsa-fuse 0.0~git20150625-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 424 kB
  • ctags: 590
  • sloc: makefile: 5
file content (247 lines) | stat: -rw-r--r-- 7,935 bytes parent folder | download
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
}