rfusefs 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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