rfusefs 1.0.0 → 1.0.1.RC0

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/lib/rfusefs.rb CHANGED
@@ -8,6 +8,59 @@ require 'rfusefs/version'
8
8
  module FuseFS
9
9
  @mounts = { }
10
10
 
11
+ # Convenience method to launch a FuseFS filesystem with nice error messages
12
+ #
13
+ # @param [Array<String>] argv command line arguments
14
+ # @param [Array<Symbol>] options list of additional options
15
+ # @param [String] option_usage describing additional option usage
16
+ # @param [String] device a description of the device field
17
+ # @param [String] exec the executable file
18
+ #
19
+ # @yieldparam [Hash<Symbol,String>] options
20
+ # options parsed from ARGV including...
21
+ # * :device - the optional mount device
22
+ # * :mountpoint - required mountpoint
23
+ # * :help - true if -h was supplied
24
+ #
25
+ # @yieldreturn [FuseDir] an RFuseFS filesystem
26
+ #
27
+ # @example
28
+ # MY_OPTIONS = [ :myfs ]
29
+ # OPTION_USAGE = " -o myfs=VAL how to use the myfs option"
30
+ #
31
+ # # Normally from the command line...
32
+ # ARGV = [ "some/device", "/mnt/point", "-h", "-o", "debug,myfs=aValue" ]
33
+ #
34
+ # FuseFS.main(ARGV, MY_OPTIONS, OPTION_USAGE, "/path/to/somedevice", $0) do |options|
35
+ #
36
+ # # options ==
37
+ # { :device => "some/device",
38
+ # :mountpoint => "/mnt/point",
39
+ # :help => true,
40
+ # :debug => true,
41
+ # :myfs => "aValue"
42
+ # }
43
+ #
44
+ # fs = MyFS.new(options)
45
+ # end
46
+ #
47
+ def FuseFS.main(argv=ARGV,options=[],option_usage="",device=nil,exec=File.basename($0))
48
+ options = RFuse.parse_options(argv,*options)
49
+
50
+ if options[:mountpoint]
51
+ fs = yield options
52
+
53
+ puts RFuse.usage(device,exec) if options[:help] || !fs
54
+ puts "Options:\n" if options[:help]
55
+
56
+ FuseFS.start(fs,*argv) if fs || options[:help]
57
+
58
+ puts option_usage if options[:help]
59
+ else
60
+ puts "rfusefs: failed, no mountpoint provided",RFuse.usage(device,exec)
61
+ end
62
+ end
63
+
11
64
  # Start the FuseFS root at mountpoint with opts.
12
65
  # @param [Object] root see {set_root}
13
66
  # @param mountpoint [String] {mount_under}
@@ -15,7 +68,6 @@ module FuseFS
15
68
  # @note RFuseFS extension
16
69
  # @return [void]
17
70
  def FuseFS.start(root,mountpoint,*opts)
18
- print "Starting FuseFS #{root} at #{mountpoint} with #{opts}\n"
19
71
  Signal.trap("TERM") { FuseFS.exit() }
20
72
  Signal.trap("INT") { FuseFS.exit() }
21
73
  FuseFS.set_root(root)
@@ -89,7 +141,9 @@ module FuseFS
89
141
  # you cannot access your filesystem using ruby File operations.
90
142
  # @note RFuseFS extension
91
143
  def FuseFS.run
144
+ @fs.mounted()
92
145
  @fuse.loop if @fuse.mounted?
146
+ @fs.unmounted()
93
147
  end
94
148
 
95
149
  # Exit the run loop and teardown FUSE
data/rfusefs.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rfusefs/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "rfusefs"
7
+ s.version = RFuseFS::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Grant Gardner"]
10
+ s.email = ["grant@lastweekend.com.au"]
11
+ s.homepage = "http://rubygems.org/gems/rfusefs"
12
+ s.summary = %q{Filesystem in Ruby Userspace}
13
+ s.description = %q{A more Ruby like way to write FUSE filesystems - inspired by (compatible with) FuseFS, implemented over RFuse}
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,spec-fusefs}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.has_rdoc = 'yard'
21
+ s_extra_rdoc_files = 'History.rdoc'
22
+
23
+ s.add_dependency("rfuse", ">= 1.0.4RC0")
24
+ s.add_development_dependency("rake")
25
+ s.add_development_dependency("rspec")
26
+ s.add_development_dependency("yard")
27
+ s.add_development_dependency("redcarpet")
28
+ s.add_development_dependency("sqlite3")
29
+ s.add_development_dependency("rb-inotify")
30
+ end
data/spec/metadir_spec.rb CHANGED
@@ -220,44 +220,44 @@ describe FuseFS::MetaDir do
220
220
 
221
221
  end
222
222
  context "in a mounted FUSE filesystem" do
223
+
224
+ let(:mountpoint) { Pathname.new(Dir.mktmpdir(["rfusefs","metadir"])) }
225
+ let(:metadir) { FuseFS::MetaDir.new() }
226
+ let(:testdir) { mountpoint + "test" }
227
+ let(:testfile) { testdir + "hello.txt" }
228
+
223
229
  before(:all) do
224
- tmpdir = Pathname.new(Dir.tmpdir) + "rfusefs"
225
- tmpdir.mkdir unless tmpdir.directory?
226
- @mountpoint = tmpdir + "metadir_spec"
227
- puts "#{@mountpoint}"
228
- @mountpoint.mkdir unless @mountpoint.directory?
229
- @metadir = FuseFS::MetaDir.new()
230
- @metadir.mkdir("/test")
231
- @metadir.write_to("/test/hello.txt","Hello World!\n")
232
- FuseFS.mount(@metadir,@mountpoint)
233
- @testdir = (@mountpoint + "test")
234
- @testfile = (@testdir + "hello.txt")
230
+ metadir.mkdir("/test")
231
+ metadir.write_to("/test/hello.txt","Hello World!\n")
232
+ FuseFS.mount(metadir,mountpoint)
235
233
  #Give FUSE some time to get started
236
- sleep(1)
234
+ sleep(0.5)
237
235
  end
238
236
 
239
237
  after(:all) do
240
- FuseFS.unmount(@mountpoint)
238
+ FuseFS.unmount(mountpoint)
239
+ sleep(0.5)
240
+ FileUtils.rm_r(mountpoint)
241
241
  end
242
242
 
243
243
  it "should list directory contents" do
244
- @testdir.entries().should =~ pathnames(".","..","hello.txt")
244
+ testdir.entries().should =~ pathnames(".","..","hello.txt")
245
245
  end
246
246
 
247
247
  it "should read files" do
248
- @testfile.file?.should be_true
249
- @testfile.read().should == "Hello World!\n"
248
+ testfile.file?.should be_true
249
+ testfile.read().should == "Hello World!\n"
250
250
  end
251
251
 
252
252
  it "should create directories" do
253
- newdir = @testdir + "newdir"
253
+ newdir = testdir + "newdir"
254
254
  newdir.mkdir()
255
255
  newdir.directory?.should be_true
256
- @testdir.entries().should =~ pathnames(".","..","hello.txt","newdir")
256
+ testdir.entries().should =~ pathnames(".","..","hello.txt","newdir")
257
257
  end
258
258
 
259
259
  it "should create files" do
260
- newfile = @testdir + "newfile"
260
+ newfile = testdir + "newfile"
261
261
  newfile.open("w") do |file|
262
262
  file << "A new file\n"
263
263
  end
@@ -265,14 +265,14 @@ describe FuseFS::MetaDir do
265
265
  end
266
266
 
267
267
  it "should move directories" do
268
- fromdir = @testdir + "fromdir"
268
+ fromdir = testdir + "fromdir"
269
269
  fromdir.mkdir()
270
270
  subfile = fromdir + "afile"
271
271
  subfile.open("w") do |file|
272
272
  file << "testfile\n"
273
273
  end
274
274
 
275
- movedir = (@mountpoint + "movedir")
275
+ movedir = (mountpoint + "movedir")
276
276
  movedir.directory?.should be_false
277
277
  fromdir.rename(movedir)
278
278
  movedir.directory?.should be_true
@@ -283,10 +283,10 @@ describe FuseFS::MetaDir do
283
283
  end
284
284
 
285
285
  it "should move files" do
286
- movefile = (@mountpoint + "moved.txt")
286
+ movefile = (mountpoint + "moved.txt")
287
287
  movefile.file?.should be_false
288
- @testfile.should be_true
289
- @testfile.rename(movefile)
288
+ testfile.should be_true
289
+ testfile.rename(movefile)
290
290
  movefile.read.should == "Hello World!\n"
291
291
  end
292
292
 
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+ require 'rfusefs'
3
+ require 'tmpdir'
4
+ require 'pathname'
5
+
6
+ describe "a mounted FuseFS" do
7
+ let(:mountpoint) { Pathname.new(Dir.mktmpdir("rfusefs_mount_unmount")) }
8
+
9
+ after(:each) { FileUtils.rmdir mountpoint }
10
+
11
+ it "should get mounted and unmounted callbacks" do
12
+ mock_fs = FuseFS::FuseDir.new()
13
+ mock_fs.should_receive(:mounted)
14
+ mock_fs.should_receive(:unmounted)
15
+
16
+ t = Thread.new { sleep 0.5 ; puts "exiting" ; FuseFS.exit }
17
+ FuseFS.start(mock_fs,mountpoint)
18
+ t.join
19
+ end
20
+ end
21
+
@@ -1,112 +1,202 @@
1
1
  #Pathmapper is hard to test because it is difficult to mock Dir/Pathname/File etc...
2
2
  require "spec_helper"
3
3
  require "fusefs/pathmapper"
4
+ require 'tmpdir'
5
+ require 'pathname'
4
6
 
5
- describe FuseFS::PathMapperFS do
6
- before(:all) do
7
- @tmpdir = mktmpdir("pathmapper")
8
- #create directory
9
- pathmap(@tmpdir + "hello.txt","/textfiles/hello")
10
- pathmap(@tmpdir + "mysong.mp3","/artist/album/mysong.mp3")
11
- pathmap(@tmpdir + "apicture.jpeg","/pictures/201103/apicture.jpg")
12
- @pathmapFS = FuseFS::PathMapperFS.create(@tmpdir) do |file|
7
+ class PMFixture
8
+ attr_reader :tmpdir
9
+
10
+ def initialize()
11
+ @tmpdir = Pathname.new(Dir.mktmpdir("rfusefs_pathmapper"))
12
+ pathmap(@tmpdir + "hello.txt","/textfiles/hello")
13
+ pathmap(@tmpdir + "mysong.mp3","/artist/album/mysong.mp3")
14
+ pathmap(@tmpdir + "apicture.jpeg","/pictures/201103/apicture.jpg")
15
+ end
16
+
17
+ def pathmap(real_file,mapped_path)
18
+ File.open(real_file.to_s,"w") do |f|
19
+ f << mapped_path
20
+ end
21
+ end
22
+
23
+ def fs
24
+ @fs ||= FuseFS::PathMapperFS.create(@tmpdir) do |file|
13
25
  File.file?(file) ? IO.read(file.to_s) : nil
14
- end
15
- end
16
-
17
- context "in ruby" do
18
-
19
- context "standard mapping" do
20
-
21
- it "should map files and directories" do
26
+ end
27
+ end
28
+
29
+ def mount()
30
+ return @mountpoint if @mountpoint
31
+ @mountpoint = Pathname.new(Dir.mktmpdir("rfusefs_pathmapper_mnt"))
32
+ FuseFS.mount(fs,@mountpoint)
33
+ sleep(0.5)
34
+ @mountpoint
35
+ end
36
+
37
+ def cleanup
38
+ if @mountpoint
39
+ FuseFS.unmount(@mountpoint)
40
+ sleep(0.5)
41
+ FileUtils.rmdir(@mountpoint)
42
+ end
43
+ FileUtils.rm_r(@tmpdir)
44
+ end
45
+ end
46
+
47
+
48
+ describe FuseFS::PathMapperFS do
49
+ before(:each) do
50
+ @fixture = PMFixture.new
51
+ @tmpdir = @fixture.tmpdir
52
+ @pathmapFS = @fixture.fs
53
+ end
54
+
55
+ after(:each) do
56
+ @fixture.cleanup
57
+ end
58
+
59
+ context "fusefs api" do
60
+
61
+ it "maps files and directories" do
22
62
  @pathmapFS.directory?("/").should be_true
23
63
  @pathmapFS.directory?("/textfiles").should be_true
24
64
  @pathmapFS.directory?("/pictures/201103").should be_true
25
65
  @pathmapFS.file?("/textfiles/hello").should be_true
66
+ @pathmapFS.directory?("/textfiles/hello").should be_false
26
67
  @pathmapFS.file?("/artist/album/mysong.mp3").should be_true
68
+ @pathmapFS.directory?("/artist/album/mysong.mp3").should be_false
69
+ @pathmapFS.file?("/some/unknown/path").should be_false
70
+ @pathmapFS.directory?("/some/unknown/path").should be_false
27
71
  end
28
-
29
- it "should list the mapped contents of directories" do
72
+
73
+ it "lists the mapped contents of directories" do
30
74
  @pathmapFS.contents("/").should =~ [ "textfiles","artist","pictures" ]
31
75
  @pathmapFS.contents("/artist").should =~ [ "album" ]
32
76
  @pathmapFS.contents("/textfiles").should =~ [ "hello" ]
33
77
  end
34
78
 
35
- it "should report the size of a file" do
79
+ it "reports the size of a file" do
36
80
  @pathmapFS.size("/textfiles/hello").should == 16
37
81
  end
38
82
 
39
- it "should report the atime,mtime and ctime of the mapped file" do
83
+ it "reads the contents of a file" do
84
+ @pathmapFS.read_file("/textfiles/hello").should == "/textfiles/hello"
85
+ end
86
+
87
+ it "does not allow writes" do
88
+ @pathmapFS.can_write?("/textfiles/hello").should be_false
89
+ end
90
+
91
+ it "reports the atime,mtime and ctime of the mapped file" do
40
92
  atime,mtime,ctime = @pathmapFS.times("/pictures/201103/apicture.jpg")
41
93
  picture = @tmpdir + "apicture.jpeg"
42
94
  atime.should == picture.atime()
43
95
  mtime.should == picture.mtime()
44
96
  ctime.should == picture.ctime()
45
97
  end
46
- end
47
98
 
48
- context "writing to a pathmapped FS"
49
- context "using raw access"
99
+ it "reports filesystem statistics"
100
+
101
+ context "writing to a pathmapped FS" do
102
+ before(:each) do
103
+ @pathmapFS.allow_write=true
104
+ @pathmapFS.write_to("textfiles/hello","updated content")
105
+ end
106
+
107
+ it "updates the contents of the real file" do
108
+ hello_path = @tmpdir + "hello.txt"
109
+ hello_path.read.should == "updated content"
110
+ end
111
+
112
+ it "updates the contents of the mapped file" do
113
+ @pathmapFS.read_file("textfiles/hello").should == "updated content"
114
+ end
115
+
116
+ it "changes the reported file size" do
117
+ @pathmapFS.size("textfiles/hello").should == 15
118
+ end
119
+
120
+ it "changes the filesystem statistics"
121
+ end
122
+
123
+ end
50
124
 
51
125
  context "a real Fuse mounted filesystem" do
52
- before(:all) do
53
- @mountpoint = Pathname.new(Dir.tmpdir) + "rfusefs-pathmapper"
54
- @mountpoint.mkdir unless @mountpoint.directory?
55
- @pathmapFS.use_raw_file_access = true
56
- FuseFS.mount(@pathmapFS,@mountpoint)
57
- sleep(1)
126
+ before(:each) do
127
+ @pathmapFS.allow_write=true
128
+ @mountpoint = @fixture.mount
58
129
  end
59
130
 
60
- it "should map files and directories" do
131
+ it "maps files and directories" do
61
132
  (@mountpoint + "textfiles").directory?.should be_true
62
- (@mountpoint + "pictures/201103").directory?.should be_true
63
133
  (@mountpoint + "textfiles/hello").file?.should be_true
64
- (@mountpoint + "artist/album/mysong.mp3").file?.should be_true
65
134
  end
66
135
 
67
- it "should list the mapped contents of directories" do
68
- (@mountpoint + "textfiles").entries.should =~ pathnames(".","..","hello")
136
+ it "lists the mapped contents of directories" do
137
+ (@mountpoint + "textfiles").entries.should =~ pathnames(".","..","hello")
69
138
  end
70
139
 
71
- it "should represent the stat information of the underlying files" do
140
+ it "represents the stat information of the underlying files" do
72
141
  hellopath=(@mountpoint + "textfiles/hello")
73
142
  realpath=(@tmpdir + "hello.txt")
74
143
  mappedstat = hellopath.stat
75
144
  realstat = realpath.stat
76
145
  mappedstat.size.should == realstat.size
77
- puts "#{realstat.atime.to_f},#{mappedstat.atime.to_f}"
78
146
  mappedstat.atime.should == realstat.atime
79
147
  mappedstat.mtime.should == realstat.mtime
80
148
  mappedstat.ctime.should == realstat.ctime
81
149
  end
82
150
 
83
- it "should read the files" do
84
- hellopath = (@mountpoint + "textfiles/hello")
151
+ it "reads the files" do
152
+ hellopath= @mountpoint + "textfiles/hello"
85
153
  hellopath.read.should == "/textfiles/hello"
86
- hellopath.open do |f|
154
+ end
155
+
156
+ it "writes the files" do
157
+ hellopath= @mountpoint + "textfiles/hello"
158
+ real_path = @tmpdir + "hello.txt"
159
+ hellopath.open("w") do |f|
160
+ f.print "updated content"
161
+ end
162
+ hellopath.read.should == "updated content"
163
+ real_path.read.should == "updated content"
164
+ end
165
+ end
166
+
167
+ context "a real Fuse mount with raw file access" do
168
+
169
+ before(:each) do
170
+ @pathmapFS.use_raw_file_access = true
171
+ @pathmapFS.allow_write = true
172
+ @mountpoint = @fixture.mount
173
+ end
174
+
175
+ it "reads files" do
176
+ hello_path = (@mountpoint + "textfiles/hello")
177
+ hello_path.open do |f|
87
178
  f.seek(2)
88
179
  f.read(3).should == "ext"
89
180
  end
90
- hellopath.sysopen do |f|
181
+
182
+ hello_path.sysopen do |f|
91
183
  f.sysseek(1)
92
184
  f.sysread(3).should == "tex"
93
185
  end
94
186
  end
95
187
 
96
-
97
- after(:all) do
98
- FuseFS.unmount(@mountpoint)
188
+ it "writes files" do
189
+ hello_path = (@mountpoint + "textfiles/hello")
190
+ real_path = @tmpdir + "hello.txt"
191
+ hello_path.open("r+") do |f|
192
+ f.sysseek(2)
193
+ f.syswrite("zzz")
194
+ f.sysseek(0)
195
+ f.sysread(6).should == "/tzzzf"
196
+ end
197
+
198
+ real_path.read.should == "/tzzzfiles/hello"
99
199
  end
100
200
 
101
201
  end
102
-
103
-
104
-
105
- end
106
-
107
- after(:all) do
108
- FileUtils.rm_rf(@tmpdir.to_s)
109
- end
110
-
111
-
112
202
  end
data/spec/rfusefs_spec.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe FuseFS do
4
+
4
5
  TEST_FILE = "/aPath/aFile"
5
6
  TEST_DIR = "/aPath"
6
7
  ROOT_PATH = "/"
@@ -41,7 +42,7 @@ describe FuseFS do
41
42
  @mock_fuse = FuseFS::FuseDir.new()
42
43
  @fuse = FuseFS::RFuseFS.new(@mock_fuse)
43
44
  end
44
-
45
+
45
46
  describe :readdir do
46
47
  before(:each) do
47
48
  @mock_fuse.should_receive(:contents).with("/apath").and_return(["afile"])
@@ -390,7 +391,28 @@ describe FuseFS do
390
391
  end
391
392
 
392
393
  end
394
+ context "extended attributes" do
395
+
396
+ let(:xattr) { {} }
397
+ before(:each) { @mock_fuse.stub!(:xattr).with(TEST_FILE).and_return(xattr) }
398
+
399
+ it "should get, set and list attributes" do
400
+ xattr["user.one"] = "xattr one"
401
+
402
+ @fuse.getxattr(nil,TEST_FILE,"user.one").should == "xattr one"
403
+ @fuse.setxattr(nil,TEST_FILE,"user.two","xattr two")
404
+ xattr["user.two"].should == "xattr two"
405
+ @fuse.listxattr(nil,TEST_FILE).should =~ [ "user.one", "user.two" ]
406
+ @fuse.removexattr(nil,TEST_FILE,"user.two")
407
+ xattr.keys.should =~ [ "user.one" ]
408
+ end
409
+
410
+ it "should raise ENODATA is no attribute is available" do
411
+ lambda{@fuse.getxattr(nil,TEST_FILE,"user.xxxx") }.should raise_error(Errno::ENODATA)
412
+ end
393
413
 
414
+
415
+ end
394
416
  end
395
417
 
396
418
  describe "a FuseFS filesystem with gid/uid specific behaviour" do
data/spec/sample_spec.rb CHANGED
@@ -5,26 +5,26 @@ require 'pathname'
5
5
  require 'hello'
6
6
 
7
7
  describe "Access Hello World sample from Ruby file operations" do
8
+ let(:mountpoint) { Pathname.new(Dir.mktmpdir("rfusefs_sample")) }
9
+
8
10
  before(:all) do
9
- tmpdir = Pathname.new(Dir.tmpdir) + "rfusefs"
10
- tmpdir.mkdir unless tmpdir.directory?
11
- @mountpoint = tmpdir + "sample_spec"
12
- @mountpoint.mkdir unless @mountpoint.directory?
13
11
  hello = HelloDir.new()
14
- FuseFS.mount(hello,@mountpoint)
12
+ FuseFS.mount(hello,mountpoint)
15
13
  #Give FUSE some time to get started
16
- sleep(1)
14
+ sleep(0.5)
17
15
  end
18
16
 
19
17
  after(:all) do
20
- FuseFS.unmount(@mountpoint)
18
+ FuseFS.unmount(mountpoint)
19
+ sleep(0.5)
20
+ FileUtils.rmdir(mountpoint)
21
21
  end
22
22
 
23
23
  it "should list expected root directory contents" do
24
- Dir.entries(@mountpoint.to_s).should =~ [".","..","hello.txt"]
24
+ Dir.entries(mountpoint.to_s).should =~ [".","..","hello.txt"]
25
25
  end
26
26
 
27
27
  it "should output the expected sample contents" do
28
- (@mountpoint + "hello.txt").read().should == "Hello, World!\n"
28
+ (mountpoint + "hello.txt").read().should == "Hello, World!\n"
29
29
  end
30
30
  end
data/spec/spec_helper.rb CHANGED
@@ -30,11 +30,6 @@ module RFuseFSHelper
30
30
  FuseContext.new(uid,gid)
31
31
  end
32
32
 
33
- def pathmap(real_file,mapped_path)
34
- File.open(real_file.to_s,"w") do |f|
35
- f << mapped_path
36
- end
37
- end
38
33
 
39
34
  def mktmpdir(name)
40
35
  tmpdir = Pathname.new(Dir.tmpdir) + "rfusefs"