rfusefs 1.0.2.RC0 → 1.0.2.RC1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|