rfusefs 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/rfusefs.rb ADDED
@@ -0,0 +1,318 @@
1
+ # RFuseFS.rb
2
+ #gem 'rfuse-ng' , "=0.4.0"
3
+ require 'fuse/rfusefs-fuse'
4
+
5
+ # This is FuseFS compatible module built over RFuse-NG
6
+
7
+ module FuseFS
8
+ VERSION = "0.8.0"
9
+ @mounts = { }
10
+
11
+ # Start the FuseFS root at mountpoint with opts. *RFuseFS* extension
12
+ # @param mountpoint [String] {mount_under}
13
+ # @param root [Object] see {set_root}
14
+ # @param opts [Array<String>] FUSE options see {mount_under}
15
+ def FuseFS.start(mountpoint,root,*opts)
16
+ print "Starting FuseFS #{root} at #{mountpoint} with #{opts}\n"
17
+ Signal.trap("TERM") { FuseFS.exit() }
18
+ Signal.trap("INT") { FuseFS.exit() }
19
+ FuseFS.set_root(root)
20
+ FuseFS.mount_under(mountpoint,*opts)
21
+ FuseFS.run()
22
+ FuseFS.unmount()
23
+ end
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(/[^\/]+/)
168
+ 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
+
265
+ # @return [Boolean] true if the user can delete the file at path
266
+ def can_delete?(path);end
267
+
268
+ # Delete the file at path
269
+ def delete(path);end
270
+
271
+ # @return [Boolean] true if user can make a directory at path
272
+ def can_mkdir?(path);end
273
+
274
+ # Make a directory at path
275
+ def mkdir(path);end
276
+
277
+ # @return [Boolean] true if user can remove a directory at path
278
+ def can_rmdir?(path);end
279
+
280
+ # Remove the directory at path
281
+ def rmdir(path);end
282
+
283
+ # Neat toy. Called when a file is touched or has its timestamp explicitly modified
284
+ def touch(path,modtime);end
285
+
286
+ # Move a file or directory.
287
+ # @return [Object] non nil/false to indicate the rename has been handled,
288
+ # otherwise will fallback to copy/delete
289
+ def rename(from_path,to_path);end
290
+
291
+ # Raw file access
292
+ # @param mode [String] "r","w" or "rw", with "a" if file is opened for append
293
+ # @return [Object] a non nil object if you want lower level control of file operations
294
+ # Under RFuseFS this object will be passed back in to the other raw
295
+ # methods as the optional parameter _raw_
296
+ #
297
+ def raw_open(path,mode);end
298
+
299
+ # RFuseFS extension.
300
+ #
301
+ # Truncate file at path (or filehandle raw) to offset bytes. Called immediately after a file is opened
302
+ # for write without append.
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
307
+
308
+ # Read _sz_ bytes from file at path (or filehandle raw) starting at offset off
309
+ def raw_read(path,off,sz,raw=nil);end
310
+
311
+ # Write _sz_ bytes from file at path (or filehandle raw) starting at offset off
312
+ def raw_write(path,off,sz,buf,raw=nil);end
313
+
314
+ # Close the file previously opened at path (or filehandle raw)
315
+ def raw_close(path,raw=nil);end
316
+
317
+ end
318
+ end
data/samples/demo.rb ADDED
@@ -0,0 +1,64 @@
1
+ require "rubygems"
2
+ require 'rfusefs'
3
+
4
+ include FuseFS
5
+
6
+ root = MetaDir.new
7
+
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
+ class Counter
21
+ def initialize
22
+ @counter = 0
23
+ end
24
+ def to_s
25
+ @counter += 1
26
+ @counter.to_s + "\n"
27
+ end
28
+ def size
29
+ @counter.to_s.size
30
+ end
31
+ end
32
+
33
+ class Randwords
34
+ def initialize(*ary)
35
+ @ary = ary.flatten
36
+ end
37
+ def to_s
38
+ @ary[rand(@ary.size)].to_s + "\n"
39
+ end
40
+ def size
41
+ @size ||= @ary.map{|v| v.size}.max
42
+ end
43
+ end
44
+
45
+ root.write_to('/hello',"Hello, World!\n")
46
+
47
+ progress = '.'
48
+
49
+ root.write_to('/progress',progress)
50
+
51
+ Thread.new do
52
+ 20.times do
53
+ sleep 5
54
+ progress << '.'
55
+ end
56
+ end
57
+
58
+ root.write_to('/counter',Counter.new)
59
+ root.write_to('/color',Randwords.new('red','blue','green','purple','yellow','bistre','burnt sienna','jade'))
60
+ root.write_to('/animal',Randwords.new('duck','dog','cat','duck billed platypus','silly fella'))
61
+
62
+ root.mkdir("/#{ENV['USER']}",DirLink.new(ENV['HOME']))
63
+
64
+ FuseFS.start(dirname,root,'nolocalcaches', *ARGV)
data/samples/dictfs.rb ADDED
@@ -0,0 +1,84 @@
1
+ # dictfs.rb
2
+ #
3
+
4
+ require "rubygems"
5
+ require 'fusefs'
6
+ include FuseFS
7
+
8
+ require 'dict'
9
+
10
+ class DictFS < FuseFS::FuseDir
11
+ def initialize
12
+ @servers = ['dict.org','alt0.dict.org']
13
+ @database = DICT::ALL_DATABASES
14
+ @strategy = 'exact'
15
+ @match_strategy = DICT::DEFAULT_MATCH_STRATEGY
16
+ @port = DICT::DEFAULT_PORT
17
+
18
+ @dict = DICT.new(@servers, @port, false, false)
19
+ @dict.client("%s v%s" % ["Dictionary","1.0"])
20
+ end
21
+ def contents(path)
22
+ # The 'readme' file
23
+ ['readme']
24
+ end
25
+ def file?(path)
26
+ base, rest = split_path(path)
27
+ rest.nil? # DictFS doesn't have subdirs.
28
+ end
29
+ def read_file(path)
30
+ word, rest = split_path(path)
31
+ word.downcase!
32
+ if word == "readme"
33
+ return %Q[
34
+ DictFS: You may not see the files, but if you cat any file here, it will look
35
+ that file up on dict.org!
36
+ ].lstrip
37
+ end
38
+ puts "Looking up #{word}"
39
+ m = @dict.match(@database, @strategy, word)
40
+ if m
41
+ contents = []
42
+ m.each do |db,words|
43
+ words.each do |w|
44
+ defs = @dict.define(db,w)
45
+ str = []
46
+ defs.each do |d|
47
+ str << "Definition of '#{w}' (by #{d.description})"
48
+ d.definition.each do |line|
49
+ str << " #{line.strip}"
50
+ end
51
+ contents << str.join("\n")
52
+ end
53
+ end
54
+ end
55
+ contents << ''
56
+ contents.join("\n")
57
+ else
58
+ "No dictionary definitions found\n"
59
+ end
60
+ end
61
+ end
62
+
63
+ if (File.basename($0) == File.basename(__FILE__))
64
+ if (ARGV.size != 1)
65
+ puts "Usage: #{$0} <directory>"
66
+ exit
67
+ end
68
+
69
+ dirname = ARGV.shift
70
+
71
+ unless File.directory?(dirname)
72
+ puts "Usage: #{dirname} is not a directory."
73
+ exit
74
+ end
75
+
76
+ root = DictFS.new
77
+
78
+ # Set the root FuseFS
79
+ FuseFS.set_root(root)
80
+
81
+ FuseFS.mount_under(dirname)
82
+
83
+ FuseFS.run # This doesn't return until we're unmounted.
84
+ end
data/samples/hello.rb ADDED
@@ -0,0 +1,24 @@
1
+ class HelloDir
2
+ def contents(path)
3
+ ['hello.txt']
4
+ end
5
+
6
+ def file?(path)
7
+ path == '/hello.txt'
8
+ end
9
+
10
+ def read_file(path)
11
+ "Hello, World!\n"
12
+ end
13
+
14
+ def size(path)
15
+ read_file(path).size
16
+ end
17
+ end
18
+
19
+ if __FILE__ == $0
20
+ require 'rfusefs'
21
+ hellodir = HelloDir.new
22
+ mountpoint = ARGV.shift
23
+ FuseFS.start(mountpoint,hellodir)
24
+ end
@@ -0,0 +1,53 @@
1
+ # openurifs.rb
2
+ #
3
+
4
+ require "rubygems"
5
+ require 'fusefs'
6
+ include FuseFS
7
+
8
+ require 'open-uri'
9
+
10
+ class OpenUriFS < FuseFS::FuseDir
11
+ def contents(path)
12
+ # The 'readme' file
13
+ []
14
+ end
15
+ def directory?(path)
16
+ uri = scan_path(path)
17
+ fn = uri.pop
18
+ return true if fn =~ /\.(com|org|net|us|de|jp|ru|uk|biz|info)$/
19
+ return true if fn =~ /^\d+\.\d+\.\d+\.\d+$/
20
+ ! (fn =~ /\./) # Does the last item doesn't contain a '.' ?
21
+ end
22
+ def file?(path)
23
+ !directory?(path)
24
+ end
25
+ def read_file(path)
26
+ proto, rest = split_path(path)
27
+ uri = "#{proto}://#{rest}"
28
+ open(uri).read
29
+ end
30
+ end
31
+
32
+ if (File.basename($0) == File.basename(__FILE__))
33
+ if (ARGV.size != 1)
34
+ puts "Usage: #{$0} <directory>"
35
+ exit
36
+ end
37
+
38
+ dirname = ARGV.shift
39
+
40
+ unless File.directory?(dirname)
41
+ puts "Usage: #{dirname} is not a directory."
42
+ exit
43
+ end
44
+
45
+ root = OpenUriFS.new
46
+
47
+ # Set the root FuseFS
48
+ FuseFS.set_root(root)
49
+
50
+ FuseFS.mount_under(dirname)
51
+
52
+ FuseFS.run # This doesn't return until we're unmounted.
53
+ end
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # RailsFS, as written by _why_the_lucky_stiff
4
+ #
5
+ # Full instructions:
6
+ # http://redhanded.hobix.com/inspect/railsfsAfterACoupleMinutesOfToolingWithFuseWhoa.html
7
+ #
8
+ =begin
9
+ Instructions cut and paste from _why's blog:
10
+
11
+ Save the railfs.rb script as script/filesys in your Rails app. (If you'd
12
+ rather not cut-and-paste the above, it's here.)
13
+
14
+ Now, run mkdir ~/railsmnt. Then, script/filesys ~/railsmnt. The rules are as
15
+ follows:
16
+
17
+ * ls ~/railsmnt will give you a list of tables.
18
+ * ls ~/railsmnt/table will list IDs from the table.
19
+ * cat ~/railsmnt/table/id will display a record in YAML.
20
+ * vim ~/railsmnt/table/id to edit the record in YAML!
21
+
22
+ =end
23
+
24
+ require "rubygems"
25
+ require 'fusefs'
26
+ require File.dirname(__FILE__) + '/../config/environment'
27
+
28
+ class RailsFS < FuseFS::FuseDir
29
+ def initialize
30
+ @classes = {}
31
+ require 'find'
32
+ Find.find( File.join(RAILS_ROOT, 'app/models') ) do |model|
33
+ if /(\w+)\.rb$/ =~ model
34
+ model = $1
35
+ ( @classes[model] = Kernel::const_get( Inflector.classify( model ) ) ).
36
+ find :first rescue @classes.delete( model )
37
+ end
38
+ end
39
+ end
40
+ def directory? path
41
+ tname, key = scan_path path
42
+ table = @classes[tname]
43
+ if table.nil?; false # /table
44
+ elsif key; false # /table/id
45
+ else; true end
46
+ end
47
+ def file? path
48
+ tname, key = scan_path path
49
+ table = @classes[tname]
50
+ key and table and table.find( key )
51
+ end
52
+ def can_delete?; true end
53
+ def can_write? path; file? path end
54
+ def contents path
55
+ tname, key = scan_path path
56
+ table = @classes[tname]
57
+ if tname.nil?; @classes.keys.sort # /
58
+ else; table.find( :all ).map { |o| o.id.to_s } end # /table
59
+ end
60
+ def write_to path, body
61
+ obj = YAML::load( body )
62
+ obj.save
63
+ end
64
+ def read_file path
65
+ tname, key = scan_path path
66
+ table = @classes[tname]
67
+ YAML::dump( table.find( key ) )
68
+ end
69
+ end
70
+
71
+ if (File.basename($0) == File.basename(__FILE__))
72
+ root = RailsFS.new
73
+ FuseFS.set_root(root)
74
+ FuseFS.mount_under(ARGV[0])
75
+ FuseFS.run # This doesn't return until we're unmounted.
76
+ end
77
+