rfuse 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/ext/rfuse/rfuse.h ADDED
@@ -0,0 +1,3 @@
1
+ #include <ruby.h>
2
+
3
+ void rfuse_init(VALUE module);
@@ -0,0 +1,12 @@
1
+ #include "rfuse.h"
2
+ #include "filler.h"
3
+ #include "file_info.h"
4
+ #include "context.h"
5
+
6
+ void Init_rfuse() {
7
+ VALUE mRFuse=rb_define_module("RFuse");
8
+ file_info_init(mRFuse);
9
+ context_init(mRFuse);
10
+ rfiller_init(mRFuse);
11
+ rfuse_init(mRFuse);
12
+ }
@@ -0,0 +1,3 @@
1
+ module RFuse
2
+ VERSION = "1.0.0"
3
+ end
data/lib/rfuse-ng.rb ADDED
@@ -0,0 +1 @@
1
+ require 'rfuse'
data/lib/rfuse.rb ADDED
@@ -0,0 +1,211 @@
1
+ require 'fcntl'
2
+ require 'rfuse/version'
3
+ require 'rfuse/rfuse'
4
+
5
+ # Ruby FUSE (Filesystem in USErspace) binding
6
+ module RFuse
7
+
8
+ # Used by listxattr
9
+ def self.packxattr(xattrs)
10
+ case xattrs
11
+ when Array
12
+ xattrs.join("\000")
13
+ when String
14
+ #assume already \0 separated list of keys
15
+ xattrs
16
+ else
17
+ raise RFuse::Error, ":listxattr must return Array or String, got #{xattrs.inspect}"
18
+ end
19
+ end
20
+
21
+ class Fuse
22
+
23
+ # Main processing loop
24
+ #
25
+ # Use {#exit} to stop processing (or externally call fusermount -u)
26
+ #
27
+ #
28
+ # Other ruby threads can continue while loop is running, however
29
+ # no thread can operate on the filesystem itself (ie with File or Dir methods)
30
+ #
31
+ # @return [void]
32
+ # @raise [RFuse::Error] if already running or not mounted
33
+ #
34
+ def loop()
35
+ raise RFuse::Error, "Already running!" if @running
36
+ raise RFuse::Error, "FUSE not mounted" unless mounted?
37
+ @running = true
38
+ while @running do
39
+ begin
40
+ ready, ignore, errors = IO.select([@fuse_io,@pr],[],[@fuse_io])
41
+
42
+ if ready.include?(@pr)
43
+
44
+ @pr.getc
45
+ @running = false
46
+
47
+ elsif errors.include?(@fuse_io)
48
+
49
+ @running = false
50
+ raise RFuse::Error, "FUSE error"
51
+
52
+ elsif ready.include?(@fuse_io)
53
+ if process() < 0
54
+ # Fuse has been unmounted externally
55
+ # TODO: mounted? should now return false
56
+ # fuse_exited? is not true...
57
+ @running = false
58
+ end
59
+ end
60
+ rescue Interrupt
61
+ #oh well...
62
+ end
63
+ end
64
+ end
65
+
66
+ # Stop processing {#loop}
67
+ # eg called from Signal handlers, or some other monitoring thread
68
+ def exit
69
+ @pw.putc(0)
70
+ end
71
+
72
+ private
73
+
74
+ # Called by the C iniitialize
75
+ # afer the filesystem has been mounted successfully
76
+ def ruby_initialize
77
+ @pr,@pw = IO.pipe()
78
+
79
+ # The FD was created by FUSE so we don't want
80
+ # ruby to do anything with it during GC
81
+ @fuse_io = IO.for_fd(fd(),"r",:autoclose => false)
82
+ end
83
+ end
84
+
85
+ #This class is useful to make your filesystem implementation
86
+ #debuggable and testable without needing to mount an actual filesystem
87
+ #or inherit from {Fuse}
88
+ class FuseDelegator < Fuse
89
+
90
+ # Available fuse methods -see http://fuse.sourceforge.net/doxygen/structfuse__operations.html
91
+ # Note :getdir and :utime are deprecated
92
+ # :ioctl, :poll are not implemented in the C extension
93
+ FUSE_METHODS = [ :getattr, :readlink, :getdir, :mknod, :mkdir,
94
+ :unlink, :rmdir, :symlink, :rename, :link,
95
+ :chmod, :chown, :truncate, :utime, :open,
96
+ :create, :read, :write, :statfs, :flush,
97
+ :release, :fsync, :setxattr, :getxattr, :listxattr,:removexattr,
98
+ :opendir, :readdir, :releasedir, :fsycndir,
99
+ :init, :destroy, :access, :ftruncate, :fgetattr, :lock,
100
+ :utimens, :bmap, :ioctl, :poll ]
101
+
102
+ # @param [Object] fuse_object your filesystem object that responds to fuse methods
103
+ # @param [String] mountpoint existing directory where the filesystem will be mounted
104
+ # @param [String...] options fuse mount options (use "-h" to see a list)
105
+ #
106
+ # Create and mount a filesystem
107
+ #
108
+ # If ruby debug is enabled then each call to fuse_object will be represented on $stderr
109
+ def initialize(fuse_object,mountpoint,*options)
110
+ @fuse_delegate = fuse_object
111
+ define_fuse_methods(fuse_object)
112
+ super(mountpoint,options)
113
+ end
114
+
115
+ private
116
+ def define_fuse_methods(fuse_object)
117
+ #Wrap all the rfuse methods in a context block
118
+ FUSE_METHODS.each do |method|
119
+ if fuse_object.respond_to?(method)
120
+ method_name = method.id2name
121
+ instance_eval(<<-EOM, "(__FUSE_DELEGATE__)",1)
122
+ def #{method_name} (*args,&block)
123
+ begin
124
+ $stderr.puts "==> #{ self }.#{ method_name }(\#{args.inspect })" if $DEBUG
125
+ result = @fuse_delegate.send(:#{method_name},*args,&block)
126
+ $stderr.puts "<== #{ self }.#{ method_name }()" if $DEBUG
127
+ result
128
+ rescue Exception => ex
129
+ $@.delete_if{|s| /^\\(__FUSE_DELEGATE__\\):/ =~ s}
130
+ $stderr.puts(ex.message) unless ex.respond_to?(:errno) || $DEBUG
131
+ Kernel::raise
132
+ end
133
+ end
134
+ EOM
135
+ end
136
+ end
137
+ end
138
+
139
+ end #class FuseDelegator
140
+
141
+ # Helper class to return from :getattr method
142
+ class Stat
143
+ # Format mask
144
+ S_IFMT = 0170000
145
+ # Directory
146
+ S_IFDIR = 0040000
147
+ # Character device
148
+ S_IFCHR = 0020000
149
+ # Block device
150
+ S_IFBLK = 0060000
151
+ # Regular file
152
+ S_IFREG = 0100000
153
+ # FIFO.
154
+ S_IFIFO = 0010000
155
+ # Symbolic link
156
+ S_IFLNK = 0120000
157
+ # Socket
158
+ S_IFSOCK = 0140000
159
+
160
+ # @param [Fixnum] mode file permissions
161
+ # @param [Hash<Symbol,Fixnum>] values initial values for other attributes
162
+ #
163
+ # @return [Stat] representing a directory
164
+ def self.directory(mode=0,values = { })
165
+ return self.new(S_IFDIR,mode,values)
166
+ end
167
+
168
+ # @param [Fixnum] mode file permissions
169
+ # @param [Hash<Symbol,Fixnum>] values initial values for other attributes
170
+ #
171
+ # @return [Stat] representing a regular file
172
+ def self.file(mode=0,values = { })
173
+ return self.new(S_IFREG,mode,values)
174
+ end
175
+
176
+ # @return [Integer] see stat(2)
177
+ attr_accessor :uid,:gid,:mode,:size, :dev,:ino,:nlink,:rdev,:blksize,:blocks
178
+
179
+ # @return [Integer, Time] see stat(2)
180
+ attr_accessor :atime,:mtime,:ctime
181
+
182
+ def initialize(type,permissions,values = { })
183
+ values[:mode] = ((type & S_IFMT) | (permissions & 07777))
184
+ @uid,@gid,@size,@mode,@atime,@mtime,@ctime,@dev,@ino,@nlink,@rdev,@blksize,@blocks = Array.new(13,0)
185
+ values.each_pair do |k,v|
186
+ instance_variable_set("@#{ k }",v)
187
+ end
188
+ end
189
+ end
190
+
191
+ # Helper class to return from :statfs (eg for df output)
192
+ # All attributes are Integers and default to 0
193
+ class StatVfs
194
+
195
+ # @return [Integer]
196
+ attr_accessor :f_bsize,:f_frsize,:f_blocks,:f_bfree,:f_bavail
197
+
198
+ # @return [Integer]
199
+ attr_accessor :f_files,:f_ffree,:f_favail,:f_fsid,:f_flag,:f_namemax
200
+
201
+ # values can be symbols or strings but drop the pointless f_ prefix
202
+ def initialize(values={ })
203
+ @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)
204
+ values.each_pair do |k,v|
205
+ prefix = k.startswith?("f_") ? "" : "f_"
206
+ instance_variable_set("@#{prefix}#{k}",v)
207
+ end
208
+ end
209
+ end
210
+
211
+ end #Module RFuse
data/lib/rfuse_ng.rb ADDED
@@ -0,0 +1,3 @@
1
+ # only here because of old version with odd naming (dash/underscore)
2
+
3
+ require 'rfuse'
data/rfuse.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rfuse/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "rfuse"
7
+ s.version = RFuse::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Grant Gardner"]
10
+ s.email = ["grant@lastweekend.com.au"]
11
+ s.homepage = "http://rubygems.org/gems/rfuse"
12
+ s.summary = %q{Ruby language binding for FUSE}
13
+ s.description = %q{Ruby language binding for FUSE. It was forked from rfuse and modified for Ruby 1.9.2.}
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.extensions = 'ext/rfuse/extconf.rb'
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency("rake")
22
+ s.add_development_dependency("rspec")
23
+ s.add_development_dependency("yard")
24
+ s.add_development_dependency("redcarpet")
25
+ s.add_development_dependency("ffi-xattr")
26
+ end
@@ -0,0 +1,339 @@
1
+ #!/usr/bin/ruby
2
+
3
+ # TestFS for RFuse
4
+
5
+ require "rfuse"
6
+
7
+ class MyDir < Hash
8
+ attr_accessor :name, :mode , :actime, :modtime, :uid, :gid
9
+ def initialize(name,mode)
10
+ @uid=0
11
+ @gid=0
12
+ @actime=Time.now
13
+ @modtime=Time.now
14
+ @xattr=Hash.new
15
+ @name=name
16
+ @mode=mode
17
+ end
18
+
19
+ def stat
20
+ RFuse::Stat.directory(mode,:uid => uid, :gid => gid, :atime => actime, :mtime => modtime,
21
+ :size => size)
22
+ end
23
+
24
+ def listxattr()
25
+ @xattr.keys()
26
+ end
27
+ def setxattr(name,value,flag)
28
+ @xattr[name]=value #TODO:don't ignore flag
29
+ end
30
+ def getxattr(name)
31
+ return @xattr[name]
32
+ end
33
+ def removexattr(name)
34
+ @xattr.delete(name)
35
+ end
36
+ def size
37
+ return 48 #for testing only
38
+ end
39
+ def isdir
40
+ true
41
+ end
42
+ def insert_obj(obj,path)
43
+ d=self.search(File.dirname(path))
44
+ if d.isdir then
45
+ d[obj.name]=obj
46
+ else
47
+ raise Errno::ENOTDIR.new(d.name)
48
+ end
49
+ return d
50
+ end
51
+ def remove_obj(path)
52
+ d=self.search(File.dirname(path))
53
+ d.delete(File.basename(path))
54
+ end
55
+ def search(path)
56
+ p=path.split('/').delete_if {|x| x==''}
57
+ if p.length==0 then
58
+ return self
59
+ else
60
+ return self.follow(p)
61
+ end
62
+ end
63
+ def follow (path_array)
64
+ if path_array.length==0 then
65
+ return self
66
+ else
67
+ d=self[path_array.shift]
68
+ if d then
69
+ return d.follow(path_array)
70
+ else
71
+ raise Errno::ENOENT.new
72
+ end
73
+ end
74
+ end
75
+ def to_s
76
+ return "Dir: " + @name + "(" + @mode.to_s + ")"
77
+ end
78
+ end
79
+
80
+ class MyFile
81
+ attr_accessor :name, :mode, :actime, :modtime, :uid, :gid, :content
82
+ def initialize(name,mode,uid,gid)
83
+ @actime=0
84
+ @modtime=0
85
+ @xattr=Hash.new
86
+ @content=""
87
+ @uid=uid
88
+ @gid=gid
89
+ @name=name
90
+ @mode=mode
91
+ end
92
+
93
+ def stat
94
+ RFuse::Stat.file(mode,:uid => uid, :gid => gid, :atime => actime, :mtime => modtime,
95
+ :size => size)
96
+ end
97
+
98
+ def listxattr()
99
+ @xattr.keys
100
+ end
101
+
102
+ def setxattr(name,value,flag)
103
+ @xattr[name]=value #TODO:don't ignore flag
104
+ end
105
+
106
+ def getxattr(name)
107
+ return @xattr[name]
108
+ end
109
+ def removexattr(name)
110
+ @xattr.delete(name)
111
+ end
112
+ def size
113
+ return content.size
114
+ end
115
+ def isdir
116
+ false
117
+ end
118
+ def follow(path_array)
119
+ if path_array.length != 0 then
120
+ raise Errno::ENOTDIR.new
121
+ else
122
+ return self
123
+ end
124
+ end
125
+ def to_s
126
+ return "File: " + @name + "(" + @mode.to_s + ")"
127
+ end
128
+ end
129
+
130
+ class MyFuse
131
+
132
+ def initialize(root)
133
+ @root=root
134
+ end
135
+
136
+ # The new readdir way, c+p-ed from getdir
137
+ def readdir(ctx,path,filler,offset,ffi)
138
+ d=@root.search(path)
139
+ if d.isdir then
140
+ d.each {|name,obj|
141
+ filler.push(name,obj.stat,0)
142
+ }
143
+ else
144
+ raise Errno::ENOTDIR.new(path)
145
+ end
146
+ end
147
+
148
+ def getattr(ctx,path)
149
+ d = @root.search(path)
150
+ return d.stat
151
+ end #getattr
152
+
153
+ def mkdir(ctx,path,mode)
154
+ @root.insert_obj(MyDir.new(File.basename(path),mode),path)
155
+ end #mkdir
156
+
157
+ def mknod(ctx,path,mode,major,minor)
158
+ @root.insert_obj(MyFile.new(File.basename(path),mode,ctx.uid,ctx.gid),path)
159
+ end #mknod
160
+
161
+ def open(ctx,path,ffi)
162
+ end
163
+
164
+ #def release(ctx,path,fi)
165
+ #end
166
+
167
+ #def flush(ctx,path,fi)
168
+ #end
169
+
170
+ def chmod(ctx,path,mode)
171
+ d=@root.search(path)
172
+ d.mode=mode
173
+ end
174
+
175
+ def chown(ctx,path,uid,gid)
176
+ d=@root.search(path)
177
+ d.uid=uid
178
+ d.gid=gid
179
+ end
180
+
181
+ def truncate(ctx,path,offset)
182
+ d=@root.search(path)
183
+ d.content = d.content[0..offset]
184
+ end
185
+
186
+ def utime(ctx,path,actime,modtime)
187
+ d=@root.search(path)
188
+ d.actime=actime
189
+ d.modtime=modtime
190
+ end
191
+
192
+ def unlink(ctx,path)
193
+ @root.remove_obj(path)
194
+ end
195
+
196
+ def rmdir(ctx,path)
197
+ @root.remove_obj(path)
198
+ end
199
+
200
+ #def symlink(ctx,path,as)
201
+ #end
202
+
203
+ def rename(ctx,path,as)
204
+ d = @root.search(path)
205
+ @root.remove_obj(path)
206
+ @root.insert_obj(d,path)
207
+ end
208
+
209
+ #def link(ctx,path,as)
210
+ #end
211
+
212
+ def read(ctx,path,size,offset,fi)
213
+ d = @root.search(path)
214
+ if (d.isdir)
215
+ raise Errno::EISDIR.new(path)
216
+ return nil
217
+ else
218
+ return d.content[offset..offset + size - 1]
219
+ end
220
+ end
221
+
222
+ def write(ctx,path,buf,offset,fi)
223
+ d=@root.search(path)
224
+ if (d.isdir)
225
+ raise Errno::EISDIR.new(path)
226
+ else
227
+ d.content[offset..offset+buf.length - 1] = buf
228
+ end
229
+ return buf.length
230
+ end
231
+
232
+ def setxattr(ctx,path,name,value,size,flags)
233
+ d=@root.search(path)
234
+ d.setxattr(name,value,flags)
235
+ end
236
+
237
+ def getxattr(ctx,path,name)
238
+ d=@root.search(path)
239
+ if (d)
240
+ value=d.getxattr(name)
241
+ if (!value)
242
+ value=""
243
+ #raise Errno::ENOENT.new #TODO raise the correct error :
244
+ #NOATTR which is not implemented in Linux/glibc
245
+ end
246
+ else
247
+ raise Errno::ENOENT.new
248
+ end
249
+ return value
250
+ end
251
+
252
+ def listxattr(ctx,path)
253
+ d=@root.search(path)
254
+ value= d.listxattr()
255
+ return value
256
+ end
257
+
258
+ def removexattr(ctx,path,name)
259
+ d=@root.search(path)
260
+ d.removexattr(name)
261
+ end
262
+
263
+ #def opendir(ctx,path,ffi)
264
+ #end
265
+
266
+ #def releasedir(ctx,path,ffi)
267
+ #end
268
+
269
+ #def fsyncdir(ctx,path,meta,ffi)
270
+ #end
271
+
272
+ # Some random numbers to show with df command
273
+ def statfs(ctx,path)
274
+ s = RFuse::StatVfs.new()
275
+ s.f_bsize = 1024
276
+ s.f_frsize = 1024
277
+ s.f_blocks = 1000000
278
+ s.f_bfree = 500000
279
+ s.f_bavail = 990000
280
+ s.f_files = 10000
281
+ s.f_ffree = 9900
282
+ s.f_favail = 9900
283
+ s.f_fsid = 23423
284
+ s.f_flag = 0
285
+ s.f_namemax = 10000
286
+ return s
287
+ end
288
+
289
+ def ioctl(ctx, path, cmd, arg, ffi, flags, data)
290
+ # FT: I was not been able to test it.
291
+ print "*** IOCTL: command: ", cmd, "\n"
292
+ end
293
+
294
+ def poll(ctx, path, ffi, ph, reventsp)
295
+ print "*** POLL: ", path, "\n"
296
+ # This is how we notify the caller if something happens:
297
+ ph.notifyPoll();
298
+ # when the GC harvests the object it calls fuse_pollhandle_destroy
299
+ # by itself.
300
+ end
301
+
302
+ def init(ctx,rfuseconninfo)
303
+ print "RFuse TestFS started\n"
304
+ print "init called\n"
305
+ print "proto_major:#{rfuseconninfo.proto_major}\n"
306
+ end
307
+
308
+ end #class Fuse
309
+
310
+ if ARGV.length == 0
311
+ print "\n"
312
+ print "Usage: [ruby [--debug]] #{$0} mountpoint [mount_options...]\n"
313
+ print "\n"
314
+ print " mountpoint must be an existing directory\n"
315
+ print " mount_option '-h' will list supported options\n"
316
+ print "\n"
317
+ print " For verbose debugging output use --debug to ruby\n"
318
+ print " and '-odebug' as mount_option\n"
319
+ print "\n"
320
+ exit(1)
321
+ end
322
+
323
+ fs = MyFuse.new(MyDir.new("",0777));
324
+
325
+ fo = RFuse::FuseDelegator.new(fs,*ARGV)
326
+
327
+ if fo.mounted?
328
+ Signal.trap("TERM") { print "Caught TERM\n" ; fo.exit }
329
+ Signal.trap("INT") { print "Caught INT\n"; fo.exit }
330
+
331
+ begin
332
+ fo.loop
333
+ rescue
334
+ print "Error:" + $!
335
+ ensure
336
+ fo.unmount if fo.mounted?
337
+ print "Unmounted #{ARGV[0]}\n"
338
+ end
339
+ end