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/.gemtest ADDED
File without changes
data/History.txt ADDED
@@ -0,0 +1,9 @@
1
+ === 0.8.0 / 2011-02-19
2
+
3
+ * Initial port from fusefs
4
+
5
+ * Improved raw methods
6
+ * new "times" api for including atime,mtime,ctime in stat results
7
+ * metadir allow mv directories
8
+ * includes PathMapperFS
9
+
data/README.rdoc ADDED
@@ -0,0 +1,108 @@
1
+ = rfusefs
2
+
3
+ * https://github.com/lwoggardner/rfusefs
4
+
5
+ == DESCRIPTION
6
+
7
+ RFuseFS is a port of the FuseFS[http://rubyforge.org/projects/fusefs]
8
+ library aimed at allowing Ruby programmers to quickly and easily create
9
+ virtual filesystems with little more than a few lines of code.
10
+
11
+ RFuseFS is api compatible with {FuseFS (0.7.0)}[https://github.com/duairc/fusefs]
12
+
13
+ == SYNOPSIS
14
+
15
+ FuseFS provides a layer of abstraction to a programmer who wants to create a
16
+ virtual filesystem via FUSE.
17
+
18
+ FuseFS programs consist of two parts:
19
+
20
+ 1. FuseFS, which is defined in 'rfusefs.rb'
21
+ 2. An object that defines a virtual directory by implementing some
22
+ subset of the {FuseFS::API}
23
+
24
+ To write a FuseFS program, you must:
25
+
26
+ * Define and create a Directory object that responds to the methods required
27
+ by FuseFS for its desired use.
28
+
29
+ * Call FuseFS.start <mountpoint> <virtualdir>
30
+ where <mountpoint> is a real directory on your filesystem
31
+ and <virtualdir> with an object defining your virtual directory.
32
+
33
+ See samples under /samples and also the following starter classes
34
+
35
+ * {FuseFS::MetaDir}
36
+ * {FuseFS::DirLink}
37
+ * {FuseFS::PathMapperFS}
38
+
39
+ To use the filesystem open up your favourite file browser/terminal and
40
+ explore the contents under <mountpoint>
41
+
42
+ Happy Filesystem Hacking!
43
+
44
+ === the hello world filesystem in 14 LOC
45
+
46
+ require 'rfusefs'
47
+
48
+ class HelloDir
49
+ def contents(path)
50
+ ['hello.txt']
51
+ end
52
+ def file?(path)
53
+ path == '/hello.txt'
54
+ end
55
+ def read_file(path)
56
+ "Hello, World!\n"
57
+ end
58
+ end
59
+
60
+ hellodir = HelloDir.new
61
+
62
+ FuseFS.start(ARGV.shift,hellodir)
63
+
64
+ == REQUIREMENTS:
65
+
66
+ * FUSE (http://fuse.sourceforge.org)
67
+ * Ruby (>=1.8)
68
+ * rfuse_ng (>=0.5.3)
69
+
70
+ == INSTALL:
71
+
72
+ * gem install rfusefs
73
+
74
+ == DEVELOPERS:
75
+
76
+ After checking out the source, run:
77
+
78
+ $ rake newb
79
+
80
+ This task will install any missing dependencies, run the tests/specs,
81
+ and generate the RDoc.
82
+
83
+ == LICENSE:
84
+
85
+ (The MIT License)
86
+
87
+ * Copyright (c) 2005 Greg Millam. (FuseFS)
88
+ * Copyright (c) 2009 Kyle Maxwell. (FuseFS)
89
+ * Copyright (c) 2011 Grant Gardner. (RFuseFS)
90
+
91
+ Permission is hereby granted, free of charge, to any person obtaining
92
+ a copy of this software and associated documentation files (the
93
+ 'Software'), to deal in the Software without restriction, including
94
+ without limitation the rights to use, copy, modify, merge, publish,
95
+ distribute, sublicense, and/or sell copies of the Software, and to
96
+ permit persons to whom the Software is furnished to do so, subject to
97
+ the following conditions:
98
+
99
+ The above copyright notice and this permission notice shall be
100
+ included in all copies or substantial portions of the Software.
101
+
102
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
103
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
104
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
105
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
106
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
107
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
108
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,25 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ # Hoe.plugin :compiler
7
+ # Hoe.plugin :gem_prelude_sucks
8
+ # Hoe.plugin :inline
9
+ # Hoe.plugin :racc
10
+ # Hoe.plugin :rubyforge
11
+ Hoe.plugin :yard
12
+ Hoe.plugin :git
13
+
14
+ Hoe.spec 'rfusefs' do
15
+ self.readme_file="README.rdoc"
16
+ developer('Grant Gardner', 'grant@lastweekend.com.au')
17
+ extra_deps << [ 'rfuse-ng' , '>= 0.5.3' ]
18
+ end
19
+
20
+ #We need to run the fusefs compatibility specs separately
21
+ RSpec::Core::RakeTask.new(:spec) do |t|
22
+ t.pattern = 'spec-fusefs/**/*_spec.rb'
23
+ end
24
+
25
+ # vim: syntax=ruby
data/TODO.txt ADDED
@@ -0,0 +1,7 @@
1
+
2
+ = TODO
3
+
4
+ * No option in FuseFS for fuse library opts
5
+
6
+
7
+
@@ -0,0 +1,497 @@
1
+ # RFuseFS - FuseFS over RFuse
2
+ require 'rfuse_ng'
3
+ require 'fcntl'
4
+
5
+ module FuseFS
6
+ #Which raw api should we use?
7
+ RFUSEFS_COMPATIBILITY = true unless FuseFS.const_defined?(:RFUSEFS_COMPATIBILITY)
8
+
9
+
10
+ # File/Directory attributes
11
+ class Stat
12
+ S_IFMT = 0170000 # Format mask
13
+ S_IFDIR = 0040000 # Directory.
14
+ S_IFCHR = 0020000 # Character device.
15
+ S_IFBLK = 0060000 # Block device.
16
+ S_IFREG = 0100000 # Regular file.
17
+ S_IFIFO = 0010000 # FIFO.
18
+ S_IFLNK = 0120000 # Symbolic link.
19
+ S_IFSOCK = 0140000 # Socket.
20
+
21
+ def self.directory(mode=0,values = { })
22
+ return self.new(S_IFDIR,mode,values)
23
+ end
24
+
25
+ def self.file(mode=0,values = { })
26
+ return self.new(S_IFREG,mode,values)
27
+ end
28
+
29
+ attr_accessor :uid,:gid,:mode,:size,:atime,:mtime,:ctime
30
+ attr_accessor :dev,:ino,:nlink,:rdev,:blksize,:blocks
31
+
32
+ def initialize(type,permissions,values = { })
33
+ values[:mode] = ((type & S_IFMT) | (permissions & 07777))
34
+ @uid,@gid,@size,@mode,@atime,@mtime,@ctime,@dev,@ino,@nlink,@rdev,@blksize,@blocks = Array.new(13,0)
35
+ values.each_pair do |k,v|
36
+ instance_variable_set("@#{ k }",v)
37
+ end
38
+ end
39
+ end
40
+
41
+ # Filesystem attributes (eg for df output)
42
+ class StatVfs
43
+ attr_accessor :f_bsize,:f_frsize,:f_blocks,:f_bfree,:f_bavail
44
+ attr_accessor :f_files,:f_ffree,:f_favail,:f_fsid,:f_flag,:f_namemax
45
+ #values can be symbols or strings but drop the pointless f_ prefix
46
+ def initialize(values={ })
47
+ @f_bsize, @f_frsize, @f_blocks, @f_bfree, @f_bavail, @f_files, @f_ffree, @f_favail,@f_fsid, @f_flag,@f_namemax = Array.new(13,0)
48
+ values.each_pair do |k,v|
49
+ instance_variable_set("@f_#{ k }",v)
50
+ end
51
+ end
52
+ end
53
+
54
+ class FileHandle
55
+ @@fh = 0
56
+ attr_reader :id,:flags,:path
57
+ attr_accessor :raw,:contents
58
+ def initialize(path,flags)
59
+ @id = (@@fh += 1)
60
+ @flags = flags
61
+ @path = path
62
+ @modified = false
63
+ @contents = ""
64
+ end
65
+
66
+ def read(offset,size)
67
+ contents[offset,size]
68
+ end
69
+
70
+ def write(offset,data)
71
+ if append? || offset >= contents.length
72
+ #ignore offset
73
+ contents << data
74
+ else
75
+ contents[offset,data.length]=data
76
+ end
77
+ @modified = true
78
+ return data.length
79
+ end
80
+
81
+ def flush
82
+ @modified = false
83
+ contents
84
+ end
85
+
86
+ def modified?
87
+ @modified
88
+ end
89
+
90
+ def accmode
91
+ flags & Fcntl::O_ACCMODE
92
+ end
93
+
94
+ def rdwr?
95
+ accmode == Fcntl::O_RDWR
96
+ end
97
+
98
+ def wronly?
99
+ accmode == Fcntl::O_WRONLY
100
+ end
101
+
102
+ def rdonly?
103
+ accmode == Fcntl::O_RDONLY
104
+ end
105
+
106
+ def append?
107
+ writing? && (flags & Fcntl::O_APPEND != 0)
108
+ end
109
+
110
+ def reading?
111
+ rdonly? || rdwr?
112
+ end
113
+
114
+ def writing?
115
+ wronly? || rdwr?
116
+ end
117
+
118
+ def raw_mode
119
+ mode_str = case accmode
120
+ when Fcntl::O_RDWR; "rw"
121
+ when Fcntl::O_RDONLY; "r"
122
+ when Fcntl::O_WRONLY; "w"
123
+ end
124
+
125
+ mode_str << "a" if append?
126
+ return mode_str
127
+ end
128
+ end
129
+
130
+ #This is the class associated with the rfuse_ng extension
131
+ #We use a delegator here so we can test the RFuseFSAPI
132
+ #without actually mounting FUSE
133
+ class RFuseFS < RFuse::Fuse
134
+ CHECK_FILE="/._rfuse_check_"
135
+
136
+ #Wrap all the rfuse methods in a context block
137
+ [:readdir,:getattr,:mkdir,:mknod,
138
+ :truncate,:open,:read,:write,:flush,:release,
139
+ :utime,:rmdir,:unlink,:rename].each do |method|
140
+ method = method.id2name
141
+ class_eval(<<-EOM, "(__FUSE_DELEGATE__)",1 )
142
+ def #{method} (ctx,*args,&block)
143
+ begin
144
+ RFuseFS.context(ctx) do
145
+ puts "==> #{ self }.#{ method }(\#{args.inspect })" if $DEBUG
146
+ result = @delegate.send(:#{method},*args,&block)
147
+ puts "<== #{ self }.#{ method }() => \#{ result.inspect }" if $DEBUG
148
+ result
149
+ end
150
+ rescue Exception => ex
151
+ $@.delete_if{|s| /^\\(__FUSE_DELEGATE__\\):/ =~s}
152
+ puts(ex.message)
153
+ Kernel::raise
154
+ end
155
+ end
156
+ EOM
157
+ end
158
+
159
+ def initialize(mnt,kernelopt,libopt,root)
160
+ @delegate = RFuseFSAPI.new(root)
161
+ super(mnt.to_s,kernelopt,libopt)
162
+
163
+ end
164
+
165
+ def init(ctx,rfuseconninfo)
166
+ #print "Init #{rfuseconninfo.inspect}\n"
167
+ return nil
168
+ end
169
+
170
+ private
171
+ def self.context(ctx)
172
+ begin
173
+ Thread.current[:fusefs_reader_uid] = ctx.uid
174
+ Thread.current[:fusefs_reader_gid] = ctx.gid
175
+ yield if block_given?
176
+ ensure
177
+ Thread.current[:fusefs_reader_uid] = nil
178
+ Thread.current[:fusefs_reader_gid] = nil
179
+ end
180
+ end
181
+ end #class RFuseFS
182
+
183
+ # Implements the FuseFS api.
184
+ # The path supplied to these methods is generally validated by FUSE itself
185
+ # with a prior "getattr" call so we do not revalidate here.
186
+ # http://sourceforge.net/apps/mediawiki/fuse/index.php?title=FuseInvariants
187
+ class RFuseFSAPI
188
+
189
+ #If not implemented by our filesystem these values are returned
190
+ API_METHODS = {
191
+ :can_write? => false,
192
+ :write_to => nil,
193
+ :can_delete? => false,
194
+ :delete => nil,
195
+ :can_mkdir? => false,
196
+ :mkdir => nil,
197
+ :can_rmdir? => false,
198
+ :rmdir => nil,
199
+ :touch => nil,
200
+ :rename => nil,
201
+ :raw_truncate => nil,
202
+ :raw_open => nil,
203
+ :raw_read => nil,
204
+ :raw_write => nil,
205
+ :raw_close => nil,
206
+ :size => 0,
207
+ :times => Array.new(3,0),
208
+ :contents => Array.new(),
209
+ :file? => false,
210
+ :directory? => false,
211
+ :executable? => false
212
+ }
213
+
214
+ def initialize(root)
215
+ @root = root
216
+ @created_files = { }
217
+
218
+ #Define method missing for our filesystem
219
+ #so we can just call all the API methods as required.
220
+ def @root.method_missing(method,*args)
221
+ if API_METHODS.has_key?(method)
222
+ return API_METHODS[method]
223
+ else
224
+ super
225
+ end
226
+
227
+ end
228
+ end
229
+
230
+
231
+ def readdir(path,filler,offset,ffi)
232
+
233
+ #Always have "." and ".."
234
+ filler.push(".",nil,0)
235
+ filler.push("..",nil,0)
236
+
237
+ @root.contents(path).each do | filename |
238
+ filler.push(filename,nil,0)
239
+ end
240
+
241
+ end
242
+
243
+ def getattr(path)
244
+ uid = Process.gid
245
+ gid = Process.uid
246
+
247
+ if path == "/" || @root.directory?(path)
248
+ #set "w" flag based on can_mkdir? || can_write? to path + "/._rfuse_check"
249
+ write_test_path = (path == "/" ? "" : path) + RFuseFS::CHECK_FILE
250
+
251
+ mode = (@root.can_mkdir?(write_test_path) || @root.can_write?(write_test_path)) ? 0777 : 0555
252
+ atime,mtime,ctime = @root.times(path)
253
+ #nlink is set to 1 because apparently this makes find work.
254
+ return Stat.directory(mode,{ :uid => uid, :gid => gid, :nlink => 1, :atime => atime, :mtime => mtime, :ctime => ctime })
255
+ elsif @created_files.has_key?(path)
256
+ now = Time.now.to_i
257
+ return Stat.file(@created_files[path],{ :uid => uid, :gid => gid, :atime => now, :mtime => now, :ctime => now })
258
+ elsif @root.file?(path)
259
+ #Set mode from can_write and executable
260
+ mode = 0444
261
+ mode |= 0222 if @root.can_write?(path)
262
+ mode |= 0111 if @root.executable?(path)
263
+ size = @root.size(path)
264
+ atime,mtime,ctime = @root.times(path)
265
+ return Stat.file(mode,{ :uid => uid, :gid => gid, :size => size, :atime => atime, :mtime => mtime, :ctime => ctime })
266
+ else
267
+ raise Errno::ENOENT.new(path)
268
+ end
269
+
270
+ end #getattr
271
+
272
+ def mkdir(path,mode)
273
+
274
+ unless @root.can_mkdir?(path)
275
+ raise Errno::EACCES.new(path)
276
+ end
277
+
278
+ @root.mkdir(path)
279
+ end #mkdir
280
+
281
+ def mknod(path,mode,dev)
282
+
283
+ unless ((Stat::S_IFMT & mode) == Stat::S_IFREG ) && @root.can_write?(path)
284
+ raise Errno::EACCES.new(path)
285
+ end
286
+
287
+ @created_files[path] = mode
288
+ end #mknod
289
+
290
+ #ftruncate - eg called after opening a file for write without append
291
+ def ftruncate(path,offset,ffi)
292
+ fh = ffi.fh
293
+
294
+ if fh.raw
295
+ @root.raw_truncate(path,offset,fh.raw)
296
+ elsif (offset <= 0)
297
+ fh.contents = ""
298
+ else
299
+ fh.contents = fh.contents[0..offset]
300
+ end
301
+ end
302
+
303
+ #truncate a file outside of open files
304
+ def truncate(path,offset)
305
+
306
+ unless @root.can_write?(path)
307
+ raise Errno::EACESS.new(path)
308
+ end
309
+
310
+ unless @root.raw_truncate(path,offset)
311
+ contents = @root.read_file(path)
312
+ if (offset <= 0)
313
+ @root.write_to(path,"")
314
+ elsif offset < contents.length
315
+ @root.write_to(path,contents[0..offset] )
316
+ end
317
+ end
318
+ end #truncate
319
+
320
+ # Open. Create a FileHandler and store in fuse file info
321
+ # This will be returned to us in read/write
322
+ # No O_CREATE (mknod first?), no O_TRUNC (truncate first)
323
+ def open(path,ffi)
324
+ fh = FileHandle.new(path,ffi.flags)
325
+
326
+ #Save the value return from raw_open to be passed back in raw_read/write etc..
327
+ if (FuseFS::RFUSEFS_COMPATIBILITY)
328
+ fh.raw = @root.raw_open(path,fh.raw_mode,true)
329
+ else
330
+ fh.raw = @root.raw_open(path,fh.raw_mode)
331
+ end
332
+
333
+ unless fh.raw
334
+
335
+ if fh.rdonly?
336
+ fh.contents = @root.read_file(path)
337
+ elsif fh.rdwr? || fh.wronly?
338
+ unless @root.can_write?(path)
339
+ raise Errno::EACCES.new(path)
340
+ end
341
+
342
+ if @created_files.has_key?(path)
343
+ #we have an empty file
344
+ fh.contents = "";
345
+ else
346
+ if fh.rdwr? || fh.append?
347
+ fh.contents = @root.read_file(path)
348
+ else #wronly && !append
349
+ #We should get a truncate 0, but might as well play it safe
350
+ fh.contents = ""
351
+ end
352
+ end
353
+ else
354
+ raise Errno::ENOPERM.new(path)
355
+ end
356
+ end
357
+ #If we get this far, save our filehandle in the FUSE structure
358
+ ffi.fh=fh
359
+
360
+ end
361
+
362
+ def read(path,size,offset,ffi)
363
+ fh = ffi.fh
364
+
365
+ if fh.raw
366
+ if FuseFS::RFUSEFS_COMPATIBILITY
367
+ return @root.raw_read(path,offset,size,fh.raw)
368
+ else
369
+ return @root.raw_read(path,offset,size)
370
+ end
371
+ elsif offset >= 0
372
+ return fh.read(offset,size)
373
+ else
374
+ #TODO: Raise? what does a negative offset mean
375
+ return ""
376
+ end
377
+
378
+
379
+ end
380
+
381
+ def write(path,buf,offset,ffi)
382
+ fh = ffi.fh
383
+
384
+ if fh.raw
385
+ if FuseFS::RFUSEFS_COMPATIBILITY
386
+ return @root.raw_write(path,offset,buf.length,buf,fh.raw)
387
+ else
388
+ @root.raw_write(path,offset,buf.length,buf)
389
+ return buf.length
390
+ end
391
+ else
392
+ return fh.write(offset,buf)
393
+ end
394
+
395
+ end
396
+
397
+ def flush(path,ffi)
398
+ fh = ffi.fh
399
+
400
+ if !fh.raw && fh.modified?
401
+ #write contents to the file and mark it unmodified
402
+ @root.write_to(path,fh.flush())
403
+ #if it was created with mknod it now exists in the filesystem...
404
+ @created_files.delete(path)
405
+ end
406
+
407
+ end
408
+
409
+ def release(path,ffi)
410
+
411
+ flush(path,ffi)
412
+
413
+ fh = ffi.fh
414
+ if fh.raw
415
+ if (FuseFS::RFUSEFS_COMPATIBILITY)
416
+ @root.raw_close(path,fh.raw)
417
+ else
418
+ @root.raw_close(path)
419
+ end
420
+ end
421
+
422
+ end
423
+
424
+ #def chmod(path,mode)
425
+ #end
426
+
427
+ #def chown(path,uid,gid)
428
+ #end
429
+
430
+ def utime(path,actime,modtime)
431
+ #Touch...
432
+ if @root.respond_to?(:touch)
433
+ @root.touch(path,modtime)
434
+ end
435
+ end
436
+
437
+ def unlink(path)
438
+ unless @root.can_delete?(path)
439
+ raise Errno::EACCES.new(path)
440
+ end
441
+ @created_files.delete(path)
442
+ @root.delete(path)
443
+ end
444
+
445
+ def rmdir(path)
446
+ unless @root.can_rmdir?(path)
447
+ raise Errno::EACCES.new(path)
448
+ end
449
+ @root.rmdir(path)
450
+ end
451
+
452
+ #def symlink(path,as)
453
+ #end
454
+
455
+ def rename(from,to)
456
+ if @root.rename(from,to)
457
+ # nothing to do
458
+ elsif @root.file?(from) && @root.can_write?(to) && @root.can_delete?(from)
459
+ contents = @root.read_file(from)
460
+ @root.write_to(to,contents)
461
+ @root.delete(from)
462
+ else
463
+ raise Errno::EACCES.new("Unable to move directory #{from}")
464
+ end
465
+ end
466
+
467
+ #def link(path,as)
468
+ #end
469
+
470
+ # def setxattr(path,name,value,size,flags)
471
+ # end
472
+
473
+ # def getxattr(path,name,size)
474
+ # end
475
+
476
+ # def listxattr(path,size)
477
+ # end
478
+
479
+ # def removexattr(path,name)
480
+ # end
481
+
482
+ #def opendir(path,ffi)
483
+ #end
484
+
485
+ #def releasedir(path,ffi)
486
+ #end
487
+
488
+ #def fsyncdir(path,meta,ffi)
489
+ #end
490
+
491
+ # Some random numbers to show with df command
492
+ #def statfs(path)
493
+ #end
494
+
495
+ end #class RFuseFSAPI
496
+
497
+ end #Module FuseFS
@@ -0,0 +1,46 @@
1
+ module FuseFS
2
+
3
+ # A FuseFS over an existing directory
4
+ class DirLink
5
+
6
+ def initialize(dir)
7
+ File.directory?(dir) or raise ArgumentError, "DirLink.initialize expects a valid directory!"
8
+ @base = dir
9
+ end
10
+
11
+ def directory?(path)
12
+ File.directory?(File.join(@base,path))
13
+ end
14
+
15
+ def file?(path)
16
+ File.file?(File.join(@base,path))
17
+ end
18
+
19
+ def size(path)
20
+ File.size(File.join(@base,path))
21
+ end
22
+
23
+ def contents(path)
24
+ fn = File.join(@base,path)
25
+ Dir.entries(fn).map { |file|
26
+ file = file.sub(/^#{fn}\/?/,'')
27
+ if ['..','.'].include?(file)
28
+ nil
29
+ else
30
+ file
31
+ end
32
+ }.compact.sort
33
+ end
34
+
35
+ def read_file(path)
36
+ fn = File.join(@base,path)
37
+ if File.file?(fn)
38
+ IO.read(fn)
39
+ else
40
+ 'No such file'
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+ end