rfusefs 1.0.3 → 1.1.0.rc202009.34

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