rfusefs 1.0.2.RC0 → 1.0.2.RC1
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/README.rdoc +5 -4
- data/Rakefile +3 -0
- data/lib/fuse/fusedir.rb +106 -22
- data/lib/fuse/rfusefs-fuse.rb +98 -27
- data/lib/fusefs/metadir.rb +46 -4
- data/lib/fusefs/pathmapper.rb +121 -20
- data/lib/rfusefs/version.rb +1 -1
- data/lib/rfusefs.rb +6 -3
- data/rfusefs.gemspec +3 -1
- data/samples/demo.rb +4 -6
- data/samples/hello.rb +1 -4
- data/spec/metadir_spec.rb +69 -0
- data/spec/pathmapper_spec.rb +276 -61
- data/spec/rfusefs_spec.rb +73 -17
- metadata +36 -4
data/lib/fusefs/pathmapper.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'ffi-xattr'
|
1
2
|
|
2
3
|
module FuseFS
|
3
4
|
|
@@ -8,24 +9,71 @@ module FuseFS
|
|
8
9
|
|
9
10
|
# Represents a mappted file or directory
|
10
11
|
class MNode
|
11
|
-
|
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
|
+
|
12
52
|
# @return [Hash<String,MNode>] list of files in a directory, nil for file nodes
|
13
53
|
attr_reader :files
|
14
54
|
|
55
|
+
# Useful when mapping a file to store attributes against the
|
56
|
+
# parent directory
|
15
57
|
# @return [MNode] parent directory
|
16
58
|
attr_reader :parent
|
17
59
|
|
60
|
+
#
|
18
61
|
# @return [Hash] metadata for this node
|
19
62
|
attr_reader :options
|
20
63
|
|
64
|
+
#
|
21
65
|
# @return [String] path to backing file, or nil for directory nodes
|
22
66
|
attr_reader :real_path
|
23
67
|
|
68
|
+
|
24
69
|
# @!visibility private
|
25
|
-
def initialize(parent_dir)
|
70
|
+
def initialize(parent_dir,stats)
|
26
71
|
@parent = parent_dir
|
27
72
|
@files = {}
|
28
73
|
@options = {}
|
74
|
+
@stats = stats
|
75
|
+
@stats_size = 0
|
76
|
+
@stats.adjust(0,1)
|
29
77
|
end
|
30
78
|
|
31
79
|
# @!visibility private
|
@@ -33,6 +81,7 @@ module FuseFS
|
|
33
81
|
@options.merge!(options)
|
34
82
|
@real_path = real_path
|
35
83
|
@files = nil
|
84
|
+
updated
|
36
85
|
self
|
37
86
|
end
|
38
87
|
|
@@ -76,9 +125,24 @@ module FuseFS
|
|
76
125
|
def[]=(key,value)
|
77
126
|
options[key]=value
|
78
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
|
79
143
|
end
|
80
144
|
|
81
|
-
# Convert FuseFS raw_mode strings to IO open mode strings
|
145
|
+
# Convert FuseFS raw_mode strings back to IO open mode strings
|
82
146
|
def self.open_mode(raw_mode)
|
83
147
|
case raw_mode
|
84
148
|
when "r"
|
@@ -106,6 +170,10 @@ module FuseFS
|
|
106
170
|
# default is false
|
107
171
|
attr_accessor :allow_write
|
108
172
|
|
173
|
+
#
|
174
|
+
# @return [StatsHelper] accumulated filesystem statistics
|
175
|
+
attr_reader :stats
|
176
|
+
|
109
177
|
# Creates a new Path Mapper filesystem over an existing directory
|
110
178
|
# @param [String] dir
|
111
179
|
# @param [Hash] options
|
@@ -114,8 +182,8 @@ module FuseFS
|
|
114
182
|
# @see #initialize
|
115
183
|
# @see #map_directory
|
116
184
|
def PathMapperFS.create(dir,options={ },&block)
|
117
|
-
pm_fs =
|
118
|
-
pm_fs.map_directory(dir,&block)
|
185
|
+
pm_fs = self.new(options)
|
186
|
+
pm_fs.map_directory(dir,&block)
|
119
187
|
return pm_fs
|
120
188
|
end
|
121
189
|
|
@@ -123,8 +191,13 @@ module FuseFS
|
|
123
191
|
# @param [Hash] options
|
124
192
|
# @option options [Boolean] :use_raw_file_access
|
125
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)
|
126
196
|
def initialize(options = { })
|
127
|
-
@
|
197
|
+
@stats = StatsHelper.new()
|
198
|
+
@stats.max_space = options[:max_space]
|
199
|
+
@stats.max_nodes = options[:max_nodes]
|
200
|
+
@root = MNode.new(nil,@stats)
|
128
201
|
@use_raw_file_access = options[:use_raw_file_access]
|
129
202
|
@allow_write = options[:allow_write]
|
130
203
|
end
|
@@ -230,9 +303,9 @@ module FuseFS
|
|
230
303
|
|
231
304
|
# @!visibility private
|
232
305
|
def write_to(path,contents)
|
233
|
-
|
234
|
-
|
235
|
-
|
306
|
+
node = node(path)
|
307
|
+
File.open(node.real_path,"w") { |f| f.print(contents) }
|
308
|
+
node.updated
|
236
309
|
end
|
237
310
|
|
238
311
|
# @!visibility private
|
@@ -254,7 +327,7 @@ module FuseFS
|
|
254
327
|
|
255
328
|
# @!visibility private
|
256
329
|
def xattr(path)
|
257
|
-
result = node(path).
|
330
|
+
result = node(path).xattr
|
258
331
|
end
|
259
332
|
|
260
333
|
# @!visibility private
|
@@ -300,33 +373,61 @@ module FuseFS
|
|
300
373
|
file.sysseek(offset)
|
301
374
|
file.syswrite(buf[0,sz])
|
302
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
|
303
386
|
|
304
387
|
# @!visibility private
|
305
388
|
def raw_close(path,file=nil)
|
306
|
-
unless file
|
307
|
-
|
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
|
308
402
|
end
|
309
|
-
|
403
|
+
|
404
|
+
end
|
405
|
+
|
406
|
+
# @!visibility private
|
407
|
+
def statistics(path)
|
408
|
+
@stats.to_statistics
|
310
409
|
end
|
311
410
|
|
312
411
|
private
|
313
|
-
|
412
|
+
|
314
413
|
def make_node(path)
|
315
|
-
#split path into components
|
414
|
+
#split path into components
|
316
415
|
components = path.to_s.scan(/[^\/]+/)
|
317
416
|
components.inject(@root) { |parent_dir, file|
|
318
|
-
parent_dir.files[file] ||= MNode.new(parent_dir)
|
417
|
+
parent_dir.files[file] ||= MNode.new(parent_dir,@stats)
|
319
418
|
}
|
320
419
|
end
|
321
|
-
|
420
|
+
|
322
421
|
def recursive_cleanup(dir_node,&block)
|
323
|
-
dir_node.files.delete_if do |path,child|
|
324
|
-
if child.file?
|
422
|
+
dir_node.files.delete_if do |path,child|
|
423
|
+
del = if child.file?
|
325
424
|
yield child
|
326
425
|
else
|
327
|
-
recursive_cleanup(child,&block)
|
426
|
+
recursive_cleanup(child,&block)
|
328
427
|
child.files.size == 0
|
329
428
|
end
|
429
|
+
child.deleted if del
|
430
|
+
del
|
330
431
|
end
|
331
432
|
end
|
332
433
|
end
|
data/lib/rfusefs/version.rb
CHANGED
data/lib/rfusefs.rb
CHANGED
@@ -177,9 +177,12 @@ module FuseFS
|
|
177
177
|
Thread.current[:fusefs_reader_gid]
|
178
178
|
end
|
179
179
|
|
180
|
-
# Not supported in RFuseFS
|
181
|
-
#
|
182
|
-
#
|
180
|
+
# Not supported in RFuseFS.
|
181
|
+
#
|
182
|
+
# The original FuseFS had special handling for editor swap/backup files
|
183
|
+
# which appears to be a workaround for a bug where zero length files
|
184
|
+
# where never written to. This "bug" is fixed since RFuseFS 1.0.2
|
185
|
+
#
|
183
186
|
# @deprecated
|
184
187
|
def self.handle_editor(bool)
|
185
188
|
#do nothing
|
data/rfusefs.gemspec
CHANGED
@@ -20,10 +20,12 @@ Gem::Specification.new do |s|
|
|
20
20
|
s.has_rdoc = 'yard'
|
21
21
|
s_extra_rdoc_files = 'History.rdoc'
|
22
22
|
|
23
|
-
s.add_dependency("rfuse", ">= 1.0.
|
23
|
+
s.add_dependency("rfuse", ">= 1.0.5RC0")
|
24
24
|
s.add_development_dependency("rake")
|
25
25
|
s.add_development_dependency("rspec")
|
26
26
|
s.add_development_dependency("yard")
|
27
27
|
s.add_development_dependency("redcarpet")
|
28
28
|
s.add_development_dependency("sqlite3")
|
29
|
+
s.add_development_dependency("sys-filesystem")
|
30
|
+
s.add_development_dependency("ffi-xattr", ">= 0.1.1")
|
29
31
|
end
|
data/samples/demo.rb
CHANGED
@@ -6,6 +6,9 @@ require 'fusefs/dirlink'
|
|
6
6
|
include FuseFS
|
7
7
|
|
8
8
|
root = MetaDir.new
|
9
|
+
root.stats.total_space = 1024*1024
|
10
|
+
root.stats.total_nodes = 1024
|
11
|
+
root.stats.strict = true
|
9
12
|
|
10
13
|
class Counter
|
11
14
|
def initialize
|
@@ -51,9 +54,4 @@ root.write_to('/animal',Randwords.new('duck','dog','cat','duck billed platypus',
|
|
51
54
|
|
52
55
|
root.mkdir("/#{ENV['USER']}",FuseFS::DirLink.new(ENV['HOME']))
|
53
56
|
|
54
|
-
|
55
|
-
puts "Usage: #{$0} <mountpoint> <mountoptions>"
|
56
|
-
exit
|
57
|
-
end
|
58
|
-
|
59
|
-
FuseFS.start(root, *ARGV)
|
57
|
+
FuseFS.main(ARGV) { | options | root }
|
data/samples/hello.rb
CHANGED
data/spec/metadir_spec.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'tmpdir'
|
3
|
+
require 'sys/filesystem'
|
4
|
+
require 'ffi-xattr'
|
3
5
|
|
4
6
|
describe FuseFS::MetaDir do
|
5
7
|
|
@@ -38,6 +40,10 @@ describe FuseFS::MetaDir do
|
|
38
40
|
it "should indicate the size of a file" do
|
39
41
|
@metadir.size("/test/hello/hello.txt").should be "Hello World!\n".length
|
40
42
|
end
|
43
|
+
|
44
|
+
it "should report filesystem statistics" do
|
45
|
+
@metadir.statistics("/").should == [ 13, 5, nil, nil ]
|
46
|
+
end
|
41
47
|
end
|
42
48
|
|
43
49
|
context "with write access" do
|
@@ -102,6 +108,15 @@ describe FuseFS::MetaDir do
|
|
102
108
|
@metadir.read_file("/test/other/more/hello/hello.txt").should == "Hello World!\n"
|
103
109
|
end
|
104
110
|
|
111
|
+
it "should maintain filesystem statistics" do
|
112
|
+
# remove a directory
|
113
|
+
@metadir.rmdir("/test/hello/emptydir")
|
114
|
+
|
115
|
+
# replace text for (the only) existing file
|
116
|
+
@metadir.write_to("/test/hello/hello.txt","new text")
|
117
|
+
|
118
|
+
@metadir.statistics("/").should == [ 8, 4, nil, nil ]
|
119
|
+
end
|
105
120
|
end
|
106
121
|
|
107
122
|
context "with readonly access" do
|
@@ -216,6 +231,17 @@ describe FuseFS::MetaDir do
|
|
216
231
|
@metadir.rename("/test/aDir","/test/fusefs/newpath/to/dir").should be_false
|
217
232
|
end
|
218
233
|
|
234
|
+
it "should pass on #statistics" do
|
235
|
+
@fusefs.should_receive(:statistics).with("/path/to/file")
|
236
|
+
|
237
|
+
@metadir.statistics("test/fusefs/path/to/file")
|
238
|
+
end
|
239
|
+
|
240
|
+
it "should pass on #statistics for root" do
|
241
|
+
@fusefs.should_receive(:statistics).with("/")
|
242
|
+
|
243
|
+
@metadir.statistics("test/fusefs")
|
244
|
+
end
|
219
245
|
end
|
220
246
|
|
221
247
|
end
|
@@ -229,6 +255,8 @@ describe FuseFS::MetaDir do
|
|
229
255
|
before(:all) do
|
230
256
|
metadir.mkdir("/test")
|
231
257
|
metadir.write_to("/test/hello.txt","Hello World!\n")
|
258
|
+
metadir.xattr("/test/hello.txt")["user.test"] = "an extended attribute"
|
259
|
+
metadir.xattr("/test")["user.test"] = "a dir attribute"
|
232
260
|
FuseFS.mount(metadir,mountpoint)
|
233
261
|
#Give FUSE some time to get started
|
234
262
|
sleep(0.5)
|
@@ -249,6 +277,25 @@ describe FuseFS::MetaDir do
|
|
249
277
|
testfile.read().should == "Hello World!\n"
|
250
278
|
end
|
251
279
|
|
280
|
+
it "should read and write extended attributes from files" do
|
281
|
+
x = Xattr.new(testfile.to_s)
|
282
|
+
x["user.test"].should == "an extended attribute"
|
283
|
+
|
284
|
+
x["user.new"] = "new"
|
285
|
+
|
286
|
+
Xattr.new(testfile.to_s)["user.new"].should == "new"
|
287
|
+
end
|
288
|
+
|
289
|
+
it "should write extended attributes for directories" do
|
290
|
+
x = Xattr.new(testdir.to_s)
|
291
|
+
|
292
|
+
x["user.test"].should == "a dir attribute"
|
293
|
+
x["user.new"] = "new dir"
|
294
|
+
|
295
|
+
Xattr.new(testdir.to_s)["user.new"].should == "new dir"
|
296
|
+
end
|
297
|
+
|
298
|
+
|
252
299
|
it "should create directories" do
|
253
300
|
newdir = testdir + "newdir"
|
254
301
|
newdir.mkdir()
|
@@ -290,6 +337,28 @@ describe FuseFS::MetaDir do
|
|
290
337
|
movefile.read.should == "Hello World!\n"
|
291
338
|
end
|
292
339
|
|
340
|
+
|
341
|
+
it "should report filesystem statistics" do
|
342
|
+
bigfile = testdir + "bigfile"
|
343
|
+
bigfile.open("w") do |file|
|
344
|
+
file << ("x" * 2048)
|
345
|
+
end
|
346
|
+
|
347
|
+
statfs = Sys::Filesystem.stat(mountpoint.to_s)
|
348
|
+
|
349
|
+
# These are fixed
|
350
|
+
statfs.block_size.should == 1024
|
351
|
+
statfs.fragment_size.should == 1024
|
352
|
+
|
353
|
+
# These are dependant on the tests above creating files/directories
|
354
|
+
statfs.files.should == 8
|
355
|
+
statfs.files_available == 8
|
356
|
+
|
357
|
+
# assume test are less than 1 block, so dependant on bigfile above
|
358
|
+
statfs.blocks.should == 2
|
359
|
+
statfs.blocks_available.should == 0
|
360
|
+
statfs.blocks_free.should == 0
|
361
|
+
end
|
293
362
|
end
|
294
363
|
|
295
364
|
end
|