rfusefs 0.8.0

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/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
+