rfusefs 0.8.0 → 1.0.0.RC0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +9 -0
- data/Gemfile +4 -0
- data/History.rdoc +20 -0
- data/README.rdoc +20 -15
- data/Rakefile +10 -17
- data/TODO.txt +1 -2
- data/lib/fuse/fusedir.rb +202 -0
- data/lib/fuse/rfusefs-fuse.rb +402 -482
- data/lib/fusefs/dirlink.rb +1 -1
- data/lib/fusefs/metadir.rb +240 -240
- data/lib/fusefs/pathmapper.rb +189 -188
- data/lib/rfusefs/version.rb +3 -0
- data/lib/rfusefs.rb +109 -301
- data/samples/demo.rb +9 -14
- data/samples/dictfs.rb +4 -14
- data/samples/hello.rb +1 -2
- data/spec/metadir_spec.rb +295 -0
- data/spec/pathmapper_spec.rb +112 -0
- data/spec/rfusefs_spec.rb +77 -76
- data/spec/sample_spec.rb +3 -2
- data/spec/spec_helper.rb +7 -1
- metadata +112 -124
- data/.gemtest +0 -0
- data/History.txt +0 -9
- data.tar.gz.sig +0 -0
- metadata.gz.sig +0 -1
data/lib/rfusefs.rb
CHANGED
@@ -1,318 +1,126 @@
|
|
1
1
|
# RFuseFS.rb
|
2
|
-
|
2
|
+
require 'fuse/fusedir'
|
3
3
|
require 'fuse/rfusefs-fuse'
|
4
|
+
require 'rfusefs/version'
|
4
5
|
|
5
|
-
# This is FuseFS compatible module built over RFuse
|
6
|
+
# This is FuseFS compatible module built over RFuse
|
6
7
|
|
7
8
|
module FuseFS
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
# Forks {start} so you can access your filesystem with ruby File
|
26
|
-
# operations (eg for testing). *RFuseFS* extension
|
27
|
-
def FuseFS.mount(mountpoint,root = nil,*opts)
|
28
|
-
|
29
|
-
pid = Kernel.fork do
|
30
|
-
FuseFS.start(mountpoint, root,*opts)
|
31
|
-
end
|
32
|
-
@mounts[mountpoint] = pid
|
33
|
-
pid
|
34
|
-
end
|
35
|
-
|
36
|
-
# Unmount a filesystem
|
37
|
-
# @param mountpoint [String] If nil?, unmounts the filesystem started with {start}
|
38
|
-
# otherwise signals the forked process started with {mount}
|
39
|
-
# to exit and unmount. *RFuseFS* extension
|
40
|
-
def FuseFS.unmount(mountpoint=nil)
|
41
|
-
|
42
|
-
if (mountpoint)
|
43
|
-
if @mounts.has_key?(mountpoint)
|
44
|
-
pid = @mounts[mountpoint]
|
45
|
-
print "Sending TERM to forked FuseFS (#{pid})\n"
|
46
|
-
Process.kill("TERM",pid)
|
47
|
-
Process.waitpid(pid)
|
48
|
-
else
|
49
|
-
raise "Unknown mountpoint #{mountpoint}"
|
50
|
-
end
|
51
|
-
else
|
52
|
-
#Local unmount, make sure we only try to unmount once
|
53
|
-
if @fuse
|
54
|
-
print "Unmounting #{@fuse.mountname}\n"
|
55
|
-
@fuse.unmount()
|
56
|
-
end
|
57
|
-
@fuse = nil
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
# Set the root virtual directory
|
62
|
-
# @param root [Object] an object implementing a subset of {FuseFS::API}
|
63
|
-
def FuseFS.set_root(root)
|
64
|
-
@root=root
|
65
|
-
end
|
66
|
-
|
67
|
-
# This will cause FuseFS to virtually mount itself under the given path. {set_root} must have
|
68
|
-
# been called previously.
|
69
|
-
# @param path [String] an existing directory where the filesystem will be virtually mounted
|
70
|
-
# @param opts [Array<String>] are FUSE options. Most likely, you will only want 'allow_other'
|
71
|
-
# or 'allow_root'. The two are mutually exclusive in FUSE, but allow_other
|
72
|
-
# will let other users, including root, access your filesystem. allow_root
|
73
|
-
# will only allow root to access it.
|
74
|
-
#
|
75
|
-
# Also available for FuseFS users are:
|
76
|
-
# default_permissions, max_read=N, fsname=NAME.
|
77
|
-
#
|
78
|
-
# For more information, see http://fuse.sourceforge.net
|
79
|
-
def FuseFS.mount_under(path,*opts)
|
80
|
-
@fuse = RFuseFS.new(path,opts,[],@root)
|
81
|
-
end
|
82
|
-
|
83
|
-
# This is the main loop waiting on then executing filesystem operations from the
|
84
|
-
# kernel.
|
85
|
-
#
|
86
|
-
# @note Running in a separate thread is generally not useful. In particular
|
87
|
-
# you cannot access your filesystem using ruby File operations.
|
88
|
-
def FuseFS.run
|
89
|
-
unless @fuse
|
90
|
-
raise "fuse is not mounted"
|
91
|
-
end
|
92
|
-
|
93
|
-
begin
|
94
|
-
io = IO.for_fd(@fuse.fd)
|
95
|
-
rescue Errno::EBADF
|
96
|
-
raise "fuse not mounted"
|
97
|
-
end
|
98
|
-
|
99
|
-
@running = true
|
100
|
-
while @running
|
101
|
-
begin
|
102
|
-
#We wake up every 2 seconds to check we are still running.
|
103
|
-
IO.select([io],[],[],2)
|
104
|
-
if @fuse.process() < 0
|
105
|
-
@running = false
|
106
|
-
end
|
107
|
-
rescue Errno::EBADF
|
108
|
-
@running = false
|
109
|
-
rescue Interrupt
|
110
|
-
#do nothing
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
# Exit the run loop and teardown FUSE
|
116
|
-
# Most useful from Signal.trap() or Kernel.at_exit()
|
117
|
-
def FuseFS.exit
|
118
|
-
@running = false
|
119
|
-
|
120
|
-
if @fuse
|
121
|
-
print "Exitting FUSE #{@fuse.mountname}\n"
|
122
|
-
@fuse.exit
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
# When the filesystem is accessed, the accessor's uid is returned
|
127
|
-
# You can use this in determining your permissions, or even provide different files
|
128
|
-
# for different users.
|
129
|
-
def self.reader_uid
|
130
|
-
Thread.current[:fusefs_reader_uid]
|
131
|
-
end
|
132
|
-
|
133
|
-
# When the filesystem is accessed, the accessor's gid is returned
|
134
|
-
def self.reader_gid
|
135
|
-
Thread.current[:fusefs_reader_gid]
|
136
|
-
end
|
137
|
-
|
138
|
-
# Not supported in RFuseFS (yet). The original FuseFS had special handling for editor
|
139
|
-
# swap/backup but this does not seem to be required, eg for the demo filesystems.
|
140
|
-
# If it is required it can be implemented in a filesystem
|
141
|
-
def self.handle_editor(bool)
|
142
|
-
#do nothing
|
143
|
-
end
|
144
|
-
|
145
|
-
# Defines convenience methods for path manipulation. You should typically inherit
|
146
|
-
# from here in your own directory projects
|
147
|
-
class FuseDir
|
148
|
-
|
149
|
-
# base,rest = split_path(path)
|
150
|
-
# @return [Array<String,String>] base,rest. base is the first directory in
|
151
|
-
# path, and rest is nil> or the remaining path.
|
152
|
-
# Typically if rest is not nil? you should
|
153
|
-
# recurse the paths
|
154
|
-
def split_path(path)
|
155
|
-
cur, *rest = path.scan(/[^\/]+/)
|
156
|
-
if rest.empty?
|
157
|
-
[ cur, nil ]
|
158
|
-
else
|
159
|
-
[ cur, File::SEPARATOR + File.join(rest) ]
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
# base,*rest = scan_path(path)
|
164
|
-
# @return [Array<String>] all directory and file elements in path. Useful
|
165
|
-
# when encapsulating an entire fs into one object
|
166
|
-
def scan_path(path)
|
167
|
-
path.scan(/[^\/]+/)
|
9
|
+
@mounts = { }
|
10
|
+
|
11
|
+
# Start the FuseFS root at mountpoint with opts.
|
12
|
+
# @param [Object] root see {set_root}
|
13
|
+
# @param mountpoint [String] {mount_under}
|
14
|
+
# @param [String...] opts FUSE mount options see {mount_under}
|
15
|
+
# @note RFuseFS extension
|
16
|
+
# @return [void]
|
17
|
+
def FuseFS.start(root,mountpoint,*opts)
|
18
|
+
print "Starting FuseFS #{root} at #{mountpoint} with #{opts}\n"
|
19
|
+
Signal.trap("TERM") { FuseFS.exit() }
|
20
|
+
Signal.trap("INT") { FuseFS.exit() }
|
21
|
+
FuseFS.set_root(root)
|
22
|
+
FuseFS.mount_under(mountpoint,*opts)
|
23
|
+
FuseFS.run
|
24
|
+
FuseFS.unmount()
|
168
25
|
end
|
169
|
-
end
|
170
|
-
|
171
|
-
|
172
|
-
# This class is equivalent to using Object.new() as the virtual directory
|
173
|
-
# for target for FuseFS.start(). It exists only to document the API
|
174
|
-
#
|
175
|
-
# == Method call sequences
|
176
|
-
#
|
177
|
-
# === Stat (getattr)
|
178
|
-
#
|
179
|
-
# FUSE itself will generally stat referenced files and validate the results
|
180
|
-
# before performing any file/directory operations so this sequence is called
|
181
|
-
# very often
|
182
|
-
#
|
183
|
-
# 1. {#directory?} is checked first
|
184
|
-
# * {#can_write?} OR {#can_mkdir?} with .\_rfusefs_check\_ to determine write permissions
|
185
|
-
# * {#times} is called to determine atime,mtime,ctime info for the directory
|
186
|
-
#
|
187
|
-
# 2. {#file?} is checked next
|
188
|
-
# * {#can_write?}, {#executable?}, {#size}, {#times} are called to fill out the details
|
189
|
-
#
|
190
|
-
# 3. otherwise we tell FUSE that the path does not exist
|
191
|
-
#
|
192
|
-
# === List directory
|
193
|
-
#
|
194
|
-
# FUSE confirms the path is a directory (via stat above) before we call {#contents}
|
195
|
-
#
|
196
|
-
# FUSE will generally go on to stat each directory entry in the results
|
197
|
-
#
|
198
|
-
# === Reading files
|
199
|
-
#
|
200
|
-
# FUSE confirms path is a file before we call {#read_file}
|
201
|
-
#
|
202
|
-
# For fine control of file access see {#raw_open}, {#raw_read}, {#raw_close}
|
203
|
-
#
|
204
|
-
# === Writing files
|
205
|
-
#
|
206
|
-
# FUSE confirms path for the new file is a directory
|
207
|
-
#
|
208
|
-
# * {#can_write?} is checked at file open
|
209
|
-
# * {#write_to} is called when the file is flushed or closed
|
210
|
-
#
|
211
|
-
# See also {#raw_open}, {#raw_truncate}, {#raw_write}, {#raw_close}
|
212
|
-
#
|
213
|
-
# === Deleting files
|
214
|
-
#
|
215
|
-
# FUSE confirms path is a file before we call {#can_delete?} then {#delete}
|
216
|
-
#
|
217
|
-
# === Creating directories
|
218
|
-
#
|
219
|
-
# FUSE confirms parent is a directory before we call {#can_mkdir?} then {#mkdir}
|
220
|
-
#
|
221
|
-
# === Deleting directories
|
222
|
-
#
|
223
|
-
# FUSE confirms path is a directory before we call {#can_rmdir?} then {#rmdir}
|
224
|
-
#
|
225
|
-
# === Renaming files and directories
|
226
|
-
#
|
227
|
-
# FUSE confirms the rename is valid (eg. not renaming a directory to a file)
|
228
|
-
#
|
229
|
-
# * Try {#rename} to see if the virtual directory wants to handle this itself
|
230
|
-
# * If rename returns false/nil then we try to copy/delete (files only) ie.
|
231
|
-
# * {#file?}(from), {#can_write?}(to), {#can_delete?}(from) and if all true
|
232
|
-
# * {#read_file}(from), {#write_to}(to), {#delete}(from)
|
233
|
-
# * otherwise reject the rename
|
234
|
-
class API < FuseDir
|
235
|
-
|
236
|
-
# @return [Boolean] true if path is a directory
|
237
|
-
def directory?(path);end
|
238
|
-
|
239
|
-
# @return [Boolean] true if path is a file
|
240
|
-
def file?(path);end
|
241
|
-
|
242
|
-
# @return [Array<String>] array of file and directory names within path
|
243
|
-
def contents(path);return [];end
|
244
|
-
|
245
|
-
# @return [Boolean] true if path is an executable file
|
246
|
-
def executable?(path);end
|
247
|
-
|
248
|
-
# File size
|
249
|
-
# @return [Fixnum] the size in byte of a file (lots of applications rely on this being accurate )
|
250
|
-
def size(path);return 0;end
|
251
|
-
|
252
|
-
# File time information. RFuseFS extension.
|
253
|
-
# @return [Array<Fixnum>] a 3 element array [ atime, mtime. ctime ] (good for rsync etc)
|
254
|
-
def times(path);return INIT_TIMES;end
|
255
|
-
|
256
|
-
# @return [String] the contents of the file at path
|
257
|
-
def read_file(path);end
|
258
|
-
|
259
|
-
# @return [Boolean] true if the user can write to file at path
|
260
|
-
def can_write?(path);end
|
261
|
-
|
262
|
-
# Write the contents of str to file at path
|
263
|
-
def write_to(path,str);end
|
264
26
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
# @return [Boolean] true if user can remove a directory at path
|
278
|
-
def can_rmdir?(path);end
|
27
|
+
# Forks {FuseFS.start} so you can access your filesystem with ruby File
|
28
|
+
# operations (eg for testing).
|
29
|
+
# @note This is an *RFuseFS* extension
|
30
|
+
# @return [void]
|
31
|
+
def FuseFS.mount(root,mountpoint,*opts)
|
32
|
+
|
33
|
+
pid = Kernel.fork do
|
34
|
+
FuseFS.start(root,mountpoint,*opts)
|
35
|
+
end
|
36
|
+
@mounts[mountpoint] = pid
|
37
|
+
pid
|
38
|
+
end
|
279
39
|
|
280
|
-
|
281
|
-
|
40
|
+
# Unmount a filesystem
|
41
|
+
# @param mountpoint [String] If nil?, unmounts the filesystem started with {start}
|
42
|
+
# otherwise signals the forked process started with {mount}
|
43
|
+
# to exit and unmount.
|
44
|
+
# @note RFuseFS extension
|
45
|
+
# @return [void]
|
46
|
+
def FuseFS.unmount(mountpoint=nil)
|
47
|
+
|
48
|
+
if (mountpoint)
|
49
|
+
if @mounts.has_key?(mountpoint)
|
50
|
+
pid = @mounts[mountpoint]
|
51
|
+
print "Sending TERM to forked FuseFS (#{pid})\n"
|
52
|
+
Process.kill("TERM",pid)
|
53
|
+
Process.waitpid(pid)
|
54
|
+
else
|
55
|
+
raise "Unknown mountpoint #{mountpoint}"
|
56
|
+
end
|
57
|
+
else
|
58
|
+
#Local unmount, make sure we only try to unmount once
|
59
|
+
if @fuse && @fuse.mounted?
|
60
|
+
print "Unmounting #{@fuse.mountname}\n"
|
61
|
+
@fuse.unmount()
|
62
|
+
end
|
63
|
+
@fuse = nil
|
64
|
+
end
|
65
|
+
end
|
282
66
|
|
283
|
-
|
284
|
-
|
67
|
+
# Set the root virtual directory
|
68
|
+
# @param root [Object] an object implementing a subset of {FuseFS::API}
|
69
|
+
# @return [void]
|
70
|
+
def FuseFS.set_root(root)
|
71
|
+
@fs=RFuseFS.new(root)
|
72
|
+
end
|
285
73
|
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
74
|
+
# This will cause FuseFS to virtually mount itself under the given path. {set_root} must have
|
75
|
+
# been called previously.
|
76
|
+
# @param [String] mountpoint an existing directory where the filesystem will be virtually mounted
|
77
|
+
# @param [Array<String>] args
|
78
|
+
# These are as expected by the "mount" command. Note in particular that the first argument
|
79
|
+
# is expected to be the mount point. For more information, see http://fuse.sourceforge.net
|
80
|
+
# and the manual pages for "mount.fuse"
|
81
|
+
def FuseFS.mount_under(mountpoint, *args)
|
82
|
+
@fuse = RFuse::FuseDelegator.new(@fs,mountpoint,*args)
|
83
|
+
end
|
290
84
|
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
85
|
+
# This is the main loop waiting on then executing filesystem operations from the
|
86
|
+
# kernel.
|
87
|
+
#
|
88
|
+
# Note: Running in a separate thread is generally not useful. In particular
|
89
|
+
# you cannot access your filesystem using ruby File operations.
|
90
|
+
# @note RFuseFS extension
|
91
|
+
def FuseFS.run
|
92
|
+
@fuse.loop if @fuse.mounted?
|
93
|
+
end
|
298
94
|
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
#
|
304
|
-
# This method can also be invoked (without raw) outside of an open file context. See
|
305
|
-
# FUSE documentation on truncate() vs ftruncate()
|
306
|
-
def raw_truncate(path,off,raw=nil);end
|
95
|
+
# Exit the run loop and teardown FUSE
|
96
|
+
# Most useful from Signal.trap() or Kernel.at_exit()
|
97
|
+
def FuseFS.exit
|
98
|
+
@running = false
|
307
99
|
|
308
|
-
|
309
|
-
|
100
|
+
if @fuse
|
101
|
+
print "Exitting FUSE #{@fuse.mountname}\n"
|
102
|
+
@fuse.exit
|
103
|
+
end
|
104
|
+
end
|
310
105
|
|
311
|
-
|
312
|
-
|
106
|
+
# @return [Fixnum] the calling process uid
|
107
|
+
# You can use this in determining your permissions, or even provide different files
|
108
|
+
# for different users.
|
109
|
+
def self.reader_uid
|
110
|
+
Thread.current[:fusefs_reader_uid]
|
111
|
+
end
|
313
112
|
|
314
|
-
|
315
|
-
|
113
|
+
# @return [Fixnum] the calling process gid
|
114
|
+
def self.reader_gid
|
115
|
+
Thread.current[:fusefs_reader_gid]
|
116
|
+
end
|
316
117
|
|
317
|
-
|
118
|
+
# Not supported in RFuseFS (yet). The original FuseFS had special handling for editor
|
119
|
+
# swap/backup but this does not seem to be required, eg for the demo filesystems.
|
120
|
+
# If it is required it can be implemented in a filesystem
|
121
|
+
# @deprecated
|
122
|
+
def self.handle_editor(bool)
|
123
|
+
#do nothing
|
124
|
+
end
|
318
125
|
end
|
126
|
+
|
data/samples/demo.rb
CHANGED
@@ -1,22 +1,12 @@
|
|
1
1
|
require "rubygems"
|
2
2
|
require 'rfusefs'
|
3
|
+
require 'fusefs/metadir'
|
4
|
+
require 'fusefs/dirlink'
|
3
5
|
|
4
6
|
include FuseFS
|
5
7
|
|
6
8
|
root = MetaDir.new
|
7
9
|
|
8
|
-
# if (ARGV.size != 1)
|
9
|
-
# puts "Usage: #{$0} <directory>"
|
10
|
-
# exit
|
11
|
-
# end
|
12
|
-
|
13
|
-
dirname = ARGV.shift
|
14
|
-
|
15
|
-
unless File.directory?(dirname)
|
16
|
-
puts "Usage: #{$0} <directory>"
|
17
|
-
exit
|
18
|
-
end
|
19
|
-
|
20
10
|
class Counter
|
21
11
|
def initialize
|
22
12
|
@counter = 0
|
@@ -59,6 +49,11 @@ root.write_to('/counter',Counter.new)
|
|
59
49
|
root.write_to('/color',Randwords.new('red','blue','green','purple','yellow','bistre','burnt sienna','jade'))
|
60
50
|
root.write_to('/animal',Randwords.new('duck','dog','cat','duck billed platypus','silly fella'))
|
61
51
|
|
62
|
-
root.mkdir("/#{ENV['USER']}",DirLink.new(ENV['HOME']))
|
52
|
+
root.mkdir("/#{ENV['USER']}",FuseFS::DirLink.new(ENV['HOME']))
|
53
|
+
|
54
|
+
unless ARGV.length > 0 && File.directory?(ARGV[0])
|
55
|
+
puts "Usage: #{$0} <mountpoint> <mountoptions>"
|
56
|
+
exit
|
57
|
+
end
|
63
58
|
|
64
|
-
FuseFS.start(
|
59
|
+
FuseFS.start(root, *ARGV)
|
data/samples/dictfs.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# dictfs.rb
|
2
|
-
#
|
3
2
|
|
4
3
|
require "rubygems"
|
5
4
|
require 'fusefs'
|
6
5
|
include FuseFS
|
7
6
|
|
7
|
+
#TODO: GG Don't know which "dict" this was referring to
|
8
8
|
require 'dict'
|
9
9
|
|
10
10
|
class DictFS < FuseFS::FuseDir
|
@@ -61,24 +61,14 @@ that file up on dict.org!
|
|
61
61
|
end
|
62
62
|
|
63
63
|
if (File.basename($0) == File.basename(__FILE__))
|
64
|
-
|
65
|
-
puts "Usage: #{$0} <
|
66
|
-
exit
|
67
|
-
end
|
68
|
-
|
69
|
-
dirname = ARGV.shift
|
70
|
-
|
71
|
-
unless File.directory?(dirname)
|
72
|
-
puts "Usage: #{dirname} is not a directory."
|
64
|
+
unless (ARGV.length > 0 && File.directory?(ARGV[0]))
|
65
|
+
puts "Usage: #{$0} <mountpoint> <mount_options>"
|
73
66
|
exit
|
74
67
|
end
|
75
68
|
|
76
69
|
root = DictFS.new
|
77
70
|
|
78
71
|
# Set the root FuseFS
|
79
|
-
FuseFS.
|
80
|
-
|
81
|
-
FuseFS.mount_under(dirname)
|
72
|
+
FuseFS.start(root,*ARGV)
|
82
73
|
|
83
|
-
FuseFS.run # This doesn't return until we're unmounted.
|
84
74
|
end
|