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.
@@ -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