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.
@@ -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 = PathMapperFS.new(options)
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
- @root = MNode.new(nil)
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
- File.open(unmap(path),"w") do |f|
234
- f.print(contents)
235
- end
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).options[:xattr] || {}
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
- file = @openfiles.delete(path)
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
- file.close if file
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
@@ -1,3 +1,3 @@
1
1
  module RFuseFS
2
- VERSION="1.0.2.RC0"
2
+ VERSION="1.0.2.RC1"
3
3
  end
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 (yet). The original FuseFS had special handling for editor
181
- # swap/backup but this does not seem to be required, eg for the demo filesystems.
182
- # If it is required it can be implemented in a filesystem
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.4")
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
- unless ARGV.length > 0 && File.directory?(ARGV[0])
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
@@ -11,12 +11,9 @@ class HelloDir
11
11
  "Hello, World!\n"
12
12
  end
13
13
 
14
- def size(path)
15
- read_file(path).size
16
- end
17
14
  end
18
15
 
19
- if __FILE__ == $0
16
+ if __FILE__ == $0
20
17
  require 'rfusefs'
21
18
  hellodir = HelloDir.new
22
19
  FuseFS.start(hellodir,*ARGV)
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