rfusefs 1.0.2.RC1 → 1.1.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.
@@ -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
-