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.
- checksums.yaml +7 -0
- data/.yardopts +2 -0
- data/CHANGES.md +40 -0
- data/LICENSE +24 -0
- data/README.md +83 -0
- data/TODO.md +7 -0
- data/lib/rfusefs.rb +20 -48
- metadata +38 -81
- data/.gitignore +0 -9
- data/.travis.yml +0 -8
- data/Gemfile +0 -4
- data/History.rdoc +0 -28
- data/README.rdoc +0 -106
- data/Rakefile +0 -22
- data/TODO.txt +0 -6
- data/lib/fuse/fusedir.rb +0 -313
- data/lib/fuse/rfusefs-fuse.rb +0 -506
- data/lib/fusefs/dirlink.rb +0 -46
- data/lib/fusefs/metadir.rb +0 -287
- data/lib/fusefs/pathmapper.rb +0 -436
- data/lib/fusefs/sqlitemapper.rb +0 -115
- data/lib/rfusefs/version.rb +0 -3
- data/rfusefs.gemspec +0 -31
- data/samples/demo.rb +0 -57
- data/samples/dictfs.rb +0 -74
- data/samples/hello.rb +0 -20
- data/samples/openurifs.rb +0 -53
- data/samples/railsfs.rb +0 -77
- data/samples/sqlfs.rb +0 -134
- data/samples/yamlfs.rb +0 -168
- data/spec-fusefs/fusefs_spec.rb +0 -12
- data/spec/metadir_spec.rb +0 -364
- data/spec/mount_unmount_spec.rb +0 -21
- data/spec/pathmapper_spec.rb +0 -417
- data/spec/rfusefs_spec.rb +0 -477
- data/spec/sample_spec.rb +0 -30
- data/spec/spec_helper.rb +0 -42
- data/spec/sqlitemapper_spec.rb +0 -135
data/lib/fusefs/dirlink.rb
DELETED
@@ -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
|
data/lib/fusefs/metadir.rb
DELETED
@@ -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
|
data/lib/fusefs/pathmapper.rb
DELETED
@@ -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
|
-
|