rfusefs 1.0.0 → 1.0.1.RC0

Sign up to get free protection for your applications and to get access to all the features.
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"