rfuse 1.0.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/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