rfusefs 1.0.2.RC1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,46 +0,0 @@
1
- module FuseFS
2
-
3
- # A FuseFS over an existing directory
4
- class DirLink < FuseDir
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
@@ -1,287 +0,0 @@
1
- module FuseFS
2
-
3
- # A full in-memory filesystem defined with hashes. It is writable to the
4
- # user that mounted it
5
- # may create and edit files within it, as well as the programmer
6
- # === Usage
7
- # root = Metadir.new()
8
- # root.mkdir("/hello")
9
- # root.write_to("/hello/world","Hello World!\n")
10
- # root.write_to("/hello/everybody","Hello Everyone!\n")
11
- #
12
- # FuseFS.start(mntpath,root)
13
- #
14
- # Because Metadir is fully recursive, you can mount your own or other defined
15
- # directory structures under it. For example, to mount a dictionary filesystem
16
- # (see samples/dictfs.rb), use:
17
- #
18
- # root.mkdir("/dict",DictFS.new())
19
- #
20
- class MetaDir
21
-
22
- DEFAULT_FS = FuseDir.new()
23
-
24
- # @return [StatsHelper] helper for filesystem accounting (df etc)
25
- attr_reader :stats
26
-
27
- def initialize(stats = nil)
28
- @subdirs = Hash.new(nil)
29
- @files = Hash.new(nil)
30
- @xattr = Hash.new() { |h,k| h[k] = Hash.new }
31
- @stats = stats || StatsHelper.new()
32
- @stats.adjust(0,1)
33
- end
34
-
35
- def split_path(path)
36
- DEFAULT_FS.split_path(path)
37
- end
38
-
39
- def scan_path
40
- DEFAULT_FS.scan_path(path)
41
- end
42
-
43
- def directory?(path)
44
- pathmethod(:directory?,path) do |filename|
45
- !filename || filename == "/" || @subdirs.has_key?(filename)
46
- end
47
- end
48
-
49
- def file?(path)
50
- pathmethod(:file?,path) do |filename|
51
- @files.has_key?(filename)
52
- end
53
- end
54
-
55
- #List directory contents
56
- def contents(path)
57
- pathmethod(:contents,path) do | filename |
58
- if !filename
59
- (@files.keys + @subdirs.keys).sort.uniq
60
- else
61
- @subdirs[filename].contents("/")
62
- end
63
- end
64
- end
65
-
66
- # Extended attributes
67
- def xattr(path)
68
- pathmethod(:xattr,path) do | path |
69
- @xattr[path]
70
- end
71
- end
72
-
73
- def read_file(path)
74
- pathmethod(:read_file,path) do |filename|
75
- @files[filename].to_s
76
- end
77
- end
78
-
79
- def size(path)
80
- pathmethod(:size,path) do | filename |
81
- return @files[filename].to_s.length
82
- end
83
- end
84
-
85
- #can_write only applies to files... see can_mkdir for directories...
86
- def can_write?(path)
87
- pathmethod(:can_write?,path) do |filename|
88
- return mount_user?
89
- end
90
- end
91
-
92
- def write_to(path,contents)
93
- pathmethod(:write_to,path,contents) do |filename, filecontents |
94
- adj_size = filecontents.to_s.length
95
- adj_nodes = 1
96
- if @files.has_key?(filename)
97
- adj_size = adj_size - @files[filename].to_s.length
98
- adj_nodes = 0
99
- end
100
- @stats.adjust(adj_size,adj_nodes)
101
-
102
- @files[filename] = filecontents
103
- end
104
- end
105
-
106
- # Delete a file
107
- def can_delete?(path)
108
- pathmethod(:can_delete?,path) do |filename|
109
- return mount_user?
110
- end
111
- end
112
-
113
- def delete(path)
114
- pathmethod(:delete,path) do |filename|
115
- contents = @files.delete(filename)
116
- @stats.adjust(-contents.to_s.length,-1)
117
- end
118
- end
119
-
120
- #mkdir - does not make intermediate dirs!
121
- def can_mkdir?(path)
122
- pathmethod(:can_mkdir?,path) do |dirname|
123
- return mount_user?
124
- end
125
- end
126
-
127
- def mkdir(path,dir=nil)
128
- pathmethod(:mkdir,path,dir) do | dirname,dirobj |
129
- dirobj ||= MetaDir.new(@stats)
130
- @subdirs[dirname] = dirobj
131
- end
132
- end
133
-
134
- # Delete an existing directory make sure it is not empty
135
- def can_rmdir?(path)
136
- pathmethod(:can_rmdir?,path) do |dirname|
137
- return mount_user? && @subdirs.has_key?(dirname) && @subdirs[dirname].contents("/").empty?
138
- end
139
- end
140
-
141
- def rmdir(path)
142
- pathmethod(:rmdir,path) do |dirname|
143
- @subdirs.delete(dirname)
144
- @stats.adjust(0,-1)
145
- end
146
- end
147
-
148
- def rename(from_path,to_path,to_fusefs = self)
149
-
150
- from_base,from_rest = split_path(from_path)
151
-
152
- case
153
- when !from_base
154
- # Shouldn't ever happen.
155
- raise Errno::EACCES.new("Can't move root")
156
- when !from_rest
157
- # So now we have a file or directory to move
158
- if @files.has_key?(from_base)
159
- return false unless can_delete?(from_base) && to_fusefs.can_write?(to_path)
160
- to_fusefs.write_to(to_path,@files[from_base])
161
- to_fusefs.xattr(to_path).merge!(@xattr[from_base])
162
- @xattr.delete(from_base)
163
- @files.delete(from_base)
164
- elsif @subdirs.has_key?(from_base)
165
- # we don't check can_rmdir? because that would prevent us
166
- # moving non empty directories
167
- return false unless mount_user? && to_fusefs.can_mkdir?(to_path)
168
- begin
169
- to_fusefs.mkdir(to_path,@subdirs[from_base])
170
- to_fusefs.xattr(to_path).merge!(@xattr[from_base])
171
- @xattr.delete(from_base)
172
- @subdirs.delete(from_base)
173
- @stats.adjust(0,-1)
174
- return true
175
- rescue ArgumentError
176
- # to_rest does not support mkdir with an arbitrary object
177
- return false
178
- end
179
- else
180
- #We shouldn't get this either
181
- return false
182
- end
183
- when @subdirs.has_key?(from_base)
184
- begin
185
- if to_fusefs != self
186
- #just keep recursing..
187
- return @subdirs[from_base].rename(from_rest,to_path,to_fusefs)
188
- else
189
- to_base,to_rest = split_path(to_path)
190
- if from_base == to_base
191
- #mv within a subdir, just pass it on
192
- return @subdirs[from_base].rename(from_rest,to_rest)
193
- else
194
- #OK, this is the tricky part, we want to move something further down
195
- #our tree into something in another part of the tree.
196
- #from this point on we keep a reference to the fusefs that owns
197
- #to_path (ie us) and pass it down, but only if the eventual path
198
- #is writable anyway!
199
- if (file?(to_path))
200
- return false unless can_write?(to_path)
201
- else
202
- return false unless can_mkdir?(to_path)
203
- end
204
-
205
- return @subdirs[from_base].rename(from_rest,to_path,self)
206
- end
207
- end
208
- rescue NoMethodError
209
- #sub dir doesn't support rename
210
- return false
211
- rescue ArgumentError
212
- #sub dir doesn't support rename with additional to_fusefs argument
213
- return false
214
- end
215
- else
216
- return false
217
- end
218
- end
219
-
220
- # path is ignored? - recursively calculate for all subdirs - but cache and then rely on fuse to keep count
221
- def statistics(path)
222
- pathmethod(:statistics,path) do |stats_path|
223
- if @subdirs.has_key?(stats_path)
224
- #unlike all the other functions where this metadir applies
225
- #the function to @subdirs - we need to pass it on
226
- @subdirs[stats_path].statistics("/")
227
- else
228
- @stats.to_statistics
229
- end
230
- end
231
- end
232
-
233
- default_methods = FuseDir.public_instance_methods.select { |m|
234
- ![:mounted,:unmounted].include?(m) &&
235
- !self.public_method_defined?(m) && FuseDir.instance_method(m).owner == FuseDir
236
- }
237
-
238
- default_methods.each do |m|
239
- define_method(m) do |*args|
240
- pathmethod(m,*args) { |*args| DEFAULT_FS.send(m,*args) }
241
- end
242
- end
243
-
244
- private
245
- # is the accessing user the same as the user that mounted our FS?, used for
246
- # all write activity
247
- def mount_user?
248
- return Process.uid == FuseFS.reader_uid
249
- end
250
-
251
- #All our FuseFS methods follow the same pattern...
252
- def pathmethod(method, path,*args)
253
- base,rest = split_path(path)
254
-
255
- case
256
- when ! base
257
- #request for the root of our fs
258
- yield(nil,*args)
259
- when ! rest
260
- #base is the filename, no more directories to traverse
261
- yield(base,*args)
262
- when @subdirs.has_key?(base)
263
- #base is a subdirectory, pass it on if we can
264
- begin
265
- @subdirs[base].send(method,rest,*args)
266
- rescue NoMethodError
267
- #Oh well
268
- return DEFAULT_FS.send(method,rest,*args)
269
- rescue ArgumentError
270
- #can_mkdir,mkdir
271
- if args.pop.nil?
272
- #possibly a default arg, try sending again with one fewer arg
273
- @subdirs[base].send(method,rest,*args)
274
- else
275
- #definitely not a default arg, reraise
276
- Kernel.raise
277
- end
278
- end
279
- else
280
- #return the default response
281
- return DEFAULT_FS.send(method,path,*args)
282
- end
283
- end
284
-
285
-
286
- end
287
- end
@@ -1,436 +0,0 @@
1
- require 'ffi-xattr'
2
-
3
- module FuseFS
4
-
5
- # A FuseFS that maps files from their original location into a new path
6
- # eg tagged audio files can be mapped by title etc...
7
- #
8
- class PathMapperFS < FuseDir
9
-
10
- # Represents a mappted file or directory
11
- class MNode
12
-
13
- # Merge extended attributes with the ones from the underlying file
14
- class XAttr
15
-
16
- attr_reader :node, :file_xattr
17
-
18
- def initialize(node)
19
- @node = node
20
- @file_xattr = ::Xattr.new(node.real_path.to_s) if node.file?
21
- end
22
-
23
- def [](key)
24
- additional[key] || (file_xattr && file_xattr[key])
25
- end
26
-
27
- def []=(key,value)
28
- raise Errno::EACCES if additional.has_key?(key) || node.directory?
29
- file_xattr[key] = value
30
- end
31
-
32
- def delete(key)
33
- raise Errno::EACCES if additional.has_key?(key) || node.directory?
34
- file_xattr.remove(key)
35
- end
36
-
37
- def keys
38
- if file_xattr
39
- additional.keys + file_xattr.list
40
- else
41
- additional.keys
42
- end
43
- end
44
-
45
-
46
- def additional
47
- @node[:xattr] || {}
48
- end
49
-
50
- end
51
-
52
- # @return [Hash<String,MNode>] list of files in a directory, nil for file nodes
53
- attr_reader :files
54
-
55
- # Useful when mapping a file to store attributes against the
56
- # parent directory
57
- # @return [MNode] parent directory
58
- attr_reader :parent
59
-
60
- #
61
- # @return [Hash] metadata for this node
62
- attr_reader :options
63
-
64
- #
65
- # @return [String] path to backing file, or nil for directory nodes
66
- attr_reader :real_path
67
-
68
-
69
- # @!visibility private
70
- def initialize(parent_dir,stats)
71
- @parent = parent_dir
72
- @files = {}
73
- @options = {}
74
- @stats = stats
75
- @stats_size = 0
76
- @stats.adjust(0,1)
77
- end
78
-
79
- # @!visibility private
80
- def init_file(real_path,options)
81
- @options.merge!(options)
82
- @real_path = real_path
83
- @files = nil
84
- updated
85
- self
86
- end
87
-
88
- def init_dir(options)
89
- @options.merge!(options)
90
- self
91
- end
92
-
93
- # @return [Boolean] true if node represents a file, otherwise false
94
- def file?
95
- real_path && true
96
- end
97
-
98
- # @return [Boolean] true if node represents a directory, otherwise false
99
- def directory?
100
- files && true
101
- end
102
-
103
- # @return [Boolean] true if node is the root directory
104
- def root?
105
- @parent.nil?
106
- end
107
-
108
- # Compatibility and convenience method
109
- # @param [:pm_real_path,String,Symbol] key
110
- # @return [String] {#real_path} if key == :pm_real_path
111
- # @return [MNode] the node representing the file named key
112
- # @return [Object] shortcut for {#options}[key]
113
- def[](key)
114
- case key
115
- when :pm_real_path
116
- real_path
117
- when String
118
- files[key]
119
- else
120
- options[key]
121
- end
122
- end
123
-
124
- # Convenience method to set metadata into {#options}
125
- def[]=(key,value)
126
- options[key]=value
127
- end
128
-
129
- def xattr
130
- @xattr ||= XAttr.new(self)
131
- end
132
-
133
- def deleted
134
- @stats.adjust(-@stats_size,-1)
135
- @stats_size = 0
136
- end
137
-
138
- def updated
139
- new_size = File.size(real_path)
140
- @stats.adjust(new_size - @stats_size)
141
- @stats_size = new_size
142
- end
143
- end
144
-
145
- # Convert FuseFS raw_mode strings back to IO open mode strings
146
- def self.open_mode(raw_mode)
147
- case raw_mode
148
- when "r"
149
- "r"
150
- when "ra"
151
- "r" #not really sensible..
152
- when "rw"
153
- "r+"
154
- when "rwa"
155
- "a+"
156
- when "w"
157
- "w"
158
- when "wa"
159
- "a"
160
- end
161
- end
162
-
163
- # should raw file access should be used - useful for binary files
164
- # @return [Boolean]
165
- # default is false
166
- attr_accessor :use_raw_file_access
167
-
168
- # should filesystem support writing through to the real files
169
- # @return [Boolean]
170
- # default is false
171
- attr_accessor :allow_write
172
-
173
- #
174
- # @return [StatsHelper] accumulated filesystem statistics
175
- attr_reader :stats
176
-
177
- # Creates a new Path Mapper filesystem over an existing directory
178
- # @param [String] dir
179
- # @param [Hash] options
180
- # @yieldparam [String] file path to map
181
- # @yieldreturn [String]
182
- # @see #initialize
183
- # @see #map_directory
184
- def PathMapperFS.create(dir,options={ },&block)
185
- pm_fs = self.new(options)
186
- pm_fs.map_directory(dir,&block)
187
- return pm_fs
188
- end
189
-
190
- # Create a new Path Mapper filesystem
191
- # @param [Hash] options
192
- # @option options [Boolean] :use_raw_file_access
193
- # @option options [Boolean] :allow_write
194
- # @option options [Integer] :max_space available space for writes (for df)
195
- # @option options [Integer] :max_nodes available nodes for writes (for df)
196
- def initialize(options = { })
197
- @stats = StatsHelper.new()
198
- @stats.max_space = options[:max_space]
199
- @stats.max_nodes = options[:max_nodes]
200
- @root = MNode.new(nil,@stats)
201
- @use_raw_file_access = options[:use_raw_file_access]
202
- @allow_write = options[:allow_write]
203
- end
204
-
205
- # Recursively find all files and map according to the given block
206
- # @param [String...] dirs directories to list
207
- # @yieldparam [String] file path to map
208
- # @yieldreturn [String] the mapped path
209
- # @yieldreturn nil to skip mapping this file
210
- def map_directory(*dirs)
211
- require 'find'
212
- Find.find(*dirs) do |file|
213
- new_path = yield file
214
- map_file(file,new_path) if new_path
215
- end
216
- end
217
- alias :mapDirectory :map_directory
218
-
219
-
220
- # Add (or replace) a mapped file
221
- #
222
- # @param [String] real_path pointing at the real file location
223
- # @param [String] new_path the mapped path
224
- # @param [Hash<Symbol,Object>] options metadata for this path
225
- # @option options [Hash<String,String>] :xattr hash to be used as extended attributes
226
- # @return [MNode]
227
- # a node representing the mapped path. See {#node}
228
- def map_file(real_path,new_path,options = {})
229
- make_node(new_path).init_file(real_path,options)
230
- end
231
- alias :mapFile :map_file
232
-
233
- # Retrieve in memory node for a mapped path
234
- #
235
- # @param [String] path
236
- # @return [MNode] in memory node at path
237
- # @return nil if path does not exist in the filesystem
238
- def node(path)
239
- path_components = scan_path(path)
240
-
241
- #not actually injecting anything here, we're just following the hash of hashes...
242
- path_components.inject(@root) { |dir,file|
243
- break unless dir.files[file]
244
- dir.files[file]
245
- }
246
- end
247
-
248
- # Takes a mapped file name and returns the original real_path
249
- def unmap(path)
250
- node = node(path)
251
- (node && node.file?) ? node.real_path : nil
252
- end
253
-
254
- # Deletes files and directories.
255
- # Yields each {#node} in the filesystem and deletes it if the block returns true
256
- #
257
- # Useful if your filesystem is periodically remapping the entire contents and you need
258
- # to delete entries that have not been touched in the latest scan
259
- #
260
- # @yieldparam [Hash] filesystem node
261
- # @yieldreturn [true,false] should this node be deleted
262
- def cleanup(&block)
263
- recursive_cleanup(@root,&block)
264
- end
265
-
266
-
267
- # @!visibility private
268
- def directory?(path)
269
- possible_dir = node(path)
270
- possible_dir && possible_dir.directory?
271
- end
272
-
273
- # @!visibility private
274
- def contents(path)
275
- node(path).files.keys
276
- end
277
-
278
- # @!visibility private
279
- def file?(path)
280
- filename = unmap(path)
281
- filename && File.file?(filename)
282
- end
283
-
284
- # @!visibility private
285
- # only called if option :raw_reads is not set
286
- def read_file(path)
287
- IO.read(unmap(path))
288
- end
289
-
290
- # @!visibility private
291
- # We can only write to existing files
292
- # because otherwise we don't have anything to back it
293
- def can_write?(path)
294
- @allow_write && file?(path)
295
- end
296
-
297
- # Note we don't impleemnt can_mkdir? so this can
298
- # only be called by code. Really only useful to
299
- # create empty directories
300
- def mkdir(path,options = {})
301
- make_node(path).init_dir(options)
302
- end
303
-
304
- # @!visibility private
305
- def write_to(path,contents)
306
- node = node(path)
307
- File.open(node.real_path,"w") { |f| f.print(contents) }
308
- node.updated
309
- end
310
-
311
- # @!visibility private
312
- def size(path)
313
- File.size(unmap(path))
314
- end
315
-
316
- # @!visibility private
317
- def times(path)
318
- realpath = unmap(path)
319
- if (realpath)
320
- stat = File.stat(realpath)
321
- return [ stat.atime, stat.mtime, stat.ctime ]
322
- else
323
- # We're a directory
324
- return [0,0,0]
325
- end
326
- end
327
-
328
- # @!visibility private
329
- def xattr(path)
330
- result = node(path).xattr
331
- end
332
-
333
- # @!visibility private
334
- # Will create, store and return a File object for the underlying file
335
- # for subsequent use with the raw_read/raw_close methods
336
- # expects file? to return true before this method is called
337
- def raw_open(path,mode,rfusefs = nil)
338
-
339
- return false unless @use_raw_file_access
340
-
341
- return false if mode.include?("w") && (!@allow_write)
342
-
343
- @openfiles ||= Hash.new() unless rfusefs
344
-
345
- real_path = unmap(path)
346
-
347
- unless real_path
348
- if rfusefs
349
- raise Errno::ENOENT.new(path)
350
- else
351
- #fusefs will go on to call file?
352
- return false
353
- end
354
- end
355
-
356
- file = File.new(real_path,PathMapperFS.open_mode(mode))
357
-
358
- @openfiles[path] = file unless rfusefs
359
-
360
- return file
361
- end
362
-
363
- # @!visibility private
364
- def raw_read(path,off,sz,file=nil)
365
- file = @openfiles[path] unless file
366
- file.sysseek(off)
367
- file.sysread(sz)
368
- end
369
-
370
- # @!visibility private
371
- def raw_write(path,offset,sz,buf,file=nil)
372
- file = @openfiles[path] unless file
373
- file.sysseek(offset)
374
- file.syswrite(buf[0,sz])
375
- end
376
-
377
- # @!visibility private
378
- def raw_sync(path,datasync,file=nil)
379
- file = @openfiles[path] unless file
380
- if datasync
381
- file.fdatasync
382
- else
383
- file.sync
384
- end
385
- end
386
-
387
- # @!visibility private
388
- def raw_close(path,file=nil)
389
- file = @openfiles.delete(path) unless file
390
-
391
- if file && !file.closed?
392
- begin
393
- flags = file.fcntl(Fcntl::F_GETFL) & Fcntl::O_ACCMODE
394
- if flags == Fcntl::O_WRONLY || flags == Fcntl::O_RDWR
395
- #update stats
396
- node = node(path)
397
- node.updated if node
398
- end
399
- ensure
400
- file.close
401
- end
402
- end
403
-
404
- end
405
-
406
- # @!visibility private
407
- def statistics(path)
408
- @stats.to_statistics
409
- end
410
-
411
- private
412
-
413
- def make_node(path)
414
- #split path into components
415
- components = path.to_s.scan(/[^\/]+/)
416
- components.inject(@root) { |parent_dir, file|
417
- parent_dir.files[file] ||= MNode.new(parent_dir,@stats)
418
- }
419
- end
420
-
421
- def recursive_cleanup(dir_node,&block)
422
- dir_node.files.delete_if do |path,child|
423
- del = if child.file?
424
- yield child
425
- else
426
- recursive_cleanup(child,&block)
427
- child.files.size == 0
428
- end
429
- child.deleted if del
430
- del
431
- end
432
- end
433
- end
434
-
435
- end
436
-