rfusefs 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/samples/sqlfs.rb ADDED
@@ -0,0 +1,134 @@
1
+ # sqlfs.rb
2
+ #
3
+ # The SQL-db proof of concept for FuseFS
4
+ #
5
+ # Author: Greg Millam
6
+
7
+ require "rubygems"
8
+ require 'fusefs'
9
+
10
+ require 'mysql'
11
+
12
+ class SqlFS < FuseFS::FuseDir
13
+ class DBTable
14
+ attr_accessor :name, :key, :fields
15
+ end
16
+ def initialize(host,user,pass,db)
17
+ @sql = Mysql.connect(host,user,pass,db)
18
+ @tables = Hash.new(nil)
19
+
20
+ tables = @sql.query('show tables')
21
+
22
+ tables.each do |i,|
23
+ table = DBTable.new
24
+ table.name = i
25
+ table.fields = {}
26
+ res = @sql.query("describe #{i}")
27
+ res.each do |field,type,null,key,default,extra|
28
+ table.fields[field] = type
29
+ if (key =~ /pri/i)
30
+ table.key = field
31
+ end
32
+ end
33
+ @tables[i] = table if table.key
34
+ end
35
+ end
36
+ def directory?(path)
37
+ tname, key, field = scan_path(path)
38
+ table = @tables[tname]
39
+ case
40
+ when tname.nil?
41
+ true # This means "/"
42
+ when table.nil?
43
+ false
44
+ when field
45
+ false # Always a file
46
+ when key
47
+ res = @sql.query("SELECT #{table.key}, 1 FROM #{table.name} WHERE #{table.key} = '#{Mysql.escape_string(key)}'")
48
+ res.num_rows > 0 # If there was a result, it exists.
49
+ else
50
+ true # It's just a table.
51
+ end
52
+ end
53
+ def file?(path)
54
+ tname, key, field = scan_path(path)
55
+ table = @tables[tname]
56
+ case
57
+ when field.nil?
58
+ false # Only field entries are files.
59
+ when table.nil?
60
+ false
61
+ when ! @tables[tname].fields.include?(field)
62
+ false # Invalid field.
63
+ when field
64
+ res = @sql.query("SELECT #{table.key}, 1 FROM #{table.name} WHERE #{table.key} = '#{Mysql.escape_string(key)}'")
65
+ res.num_rows > 0
66
+ end
67
+ end
68
+ def can_delete?(path)
69
+ # This helps editors, but we don't really use it.
70
+ true
71
+ end
72
+ def can_write?(path)
73
+ # Since this is basically only for editing files,
74
+ # we just call file?
75
+ file?(path)
76
+ end
77
+ def contents(path)
78
+ # since this is only called when directory? is true,
79
+ # We'll assume valid entries.
80
+ tname, key, field = scan_path(path)
81
+ table = @tables[tname]
82
+ case
83
+ when tname.nil?
84
+ @tables.keys.sort # Just the tables.
85
+ when key
86
+ table.fields.keys.sort
87
+ else
88
+ # I limit to 200 so 'ls' doesn't hang all the time :D
89
+ res = @sql.query("SELECT #{table.key}, 1 FROM #{table.name} ORDER BY #{table.key} LIMIT 100")
90
+ ret = []
91
+ res.each do |val,one|
92
+ ret << val if val.size > 0
93
+ end
94
+ ret
95
+ end
96
+ end
97
+ def write_to(path,body)
98
+ # Since this is only called after can_write?(), we assume
99
+ # Valid fields.
100
+ tname, key, field = scan_path(path)
101
+ table = @tables[tname]
102
+ res = @sql.query("UPDATE #{table.name} SET #{field} = '#{Mysql.escape_string(body)}' WHERE #{table.key} = '#{key}'")
103
+ end
104
+ def read_file(path)
105
+ # Again, as this is only called after file?, assume valid fields.
106
+ tname, key, field = scan_path(path)
107
+ table = @tables[tname]
108
+ res = @sql.query("SELECT #{field} FROM #{table.name} WHERE #{table.key} = '#{key}'")
109
+ res.fetch_row[0]
110
+ end
111
+ end
112
+
113
+ if (File.basename($0) == File.basename(__FILE__))
114
+ if ARGV.size != 5
115
+ puts "Usage: #{$0} <directory> <host> <user> <pass> <db>"
116
+ exit
117
+ end
118
+
119
+ dirname, host, user, pass, db = ARGV
120
+
121
+ if (! File.directory?(dirname))
122
+ puts "#{dirname} is not a directory"
123
+ end
124
+
125
+ root = SqlFS.new(host,user,pass,db)
126
+
127
+ # Set the root FuseFS
128
+ FuseFS.set_root(root)
129
+
130
+ # root.contents("/quotes")
131
+
132
+ FuseFS.mount_under(dirname)
133
+ FuseFS.run # This doesn't return until we're unmounted.
134
+ end
data/samples/yamlfs.rb ADDED
@@ -0,0 +1,168 @@
1
+ # yamlfs.rb
2
+ #
3
+
4
+ require "rubygems"
5
+ require 'fusefs'
6
+ include FuseFS
7
+
8
+ require 'yaml'
9
+
10
+ class YAMLFS < FuseFS::FuseDir
11
+ def initialize(filename)
12
+ @filename = filename
13
+ begin
14
+ @fs = YAML.load(IO.read(filename))
15
+ rescue Exception
16
+ @fs = Hash.new()
17
+ end
18
+ end
19
+ def save
20
+ File.open(@filename,'w') do |fout|
21
+ fout.puts(YAML.dump(@fs))
22
+ end
23
+ end
24
+ def contents(path)
25
+ items = scan_path(path)
26
+ node = items.inject(@fs) do |node,item|
27
+ item ? node[item] : node
28
+ end
29
+ node.keys.sort
30
+ end
31
+ def directory?(path)
32
+ items = scan_path(path)
33
+ node = items.inject(@fs) do |node,item|
34
+ item ? node[item] : node
35
+ end
36
+ node.is_a?(Hash)
37
+ end
38
+ def file?(path)
39
+ items = scan_path(path)
40
+ node = items.inject(@fs) do |node,item|
41
+ item ? node[item] : node
42
+ end
43
+ node.is_a?(String)
44
+ end
45
+ def touch(path)
46
+ puts "#{path} has been pushed like a button!"
47
+ end
48
+
49
+ # File reading
50
+ def read_file(path)
51
+ items = scan_path(path)
52
+ node = items.inject(@fs) do |node,item|
53
+ item ? node[item] : node
54
+ end
55
+ node.to_s
56
+ end
57
+
58
+ def size(path)
59
+ read_file(path).size
60
+ end
61
+
62
+ # File writing
63
+ def can_write?(path)
64
+ items = scan_path(path)
65
+ name = items.pop # Last is the filename.
66
+ node = items.inject(@fs) do |node,item|
67
+ item ? node[item] : node
68
+ end
69
+ node.is_a?(Hash)
70
+ rescue Exception => er
71
+ puts "Error! #{er}"
72
+ end
73
+ def write_to(path,body)
74
+ items = scan_path(path)
75
+ name = items.pop # Last is the filename.
76
+ node = items.inject(@fs) do |node,item|
77
+ item ? node[item] : node
78
+ end
79
+ node[name] = body
80
+ self.save
81
+ rescue Exception => er
82
+ puts "Error! #{er}"
83
+ end
84
+
85
+ # Delete a file
86
+ def can_delete?(path)
87
+ items = scan_path(path)
88
+ node = items.inject(@fs) do |node,item|
89
+ item ? node[item] : node
90
+ end
91
+ node.is_a?(String)
92
+ rescue Exception => er
93
+ puts "Error! #{er}"
94
+ end
95
+ def delete(path)
96
+ items = scan_path(path)
97
+ name = items.pop # Last is the filename.
98
+ node = items.inject(@fs) do |node,item|
99
+ item ? node[item] : node
100
+ end
101
+ node.delete(name)
102
+ self.save
103
+ rescue Exception => er
104
+ puts "Error! #{er}"
105
+ end
106
+
107
+ # mkdirs
108
+ def can_mkdir?(path)
109
+ items = scan_path(path)
110
+ name = items.pop # Last is the filename.
111
+ node = items.inject(@fs) do |node,item|
112
+ item ? node[item] : node
113
+ end
114
+ node.is_a?(Hash)
115
+ rescue Exception => er
116
+ puts "Error! #{er}"
117
+ end
118
+ def mkdir(path)
119
+ items = scan_path(path)
120
+ name = items.pop # Last is the filename.
121
+ node = items.inject(@fs) do |node,item|
122
+ item ? node[item] : node
123
+ end
124
+ node[name] = Hash.new
125
+ self.save
126
+ end
127
+
128
+ # rmdir
129
+ def can_rmdir?(path)
130
+ items = scan_path(path)
131
+ node = items.inject(@fs) do |node,item|
132
+ item ? node[item] : node
133
+ end
134
+ node.is_a?(Hash) && node.empty?
135
+ end
136
+ def rmdir(path)
137
+ items = scan_path(path)
138
+ name = items.pop # Last is the filename.
139
+ node = items.inject(@fs) do |node,item|
140
+ item ? node[item] : node
141
+ end
142
+ node.delete(name)
143
+ self.save
144
+ end
145
+ end
146
+
147
+ if (File.basename($0) == File.basename(__FILE__))
148
+ if (ARGV.size < 2)
149
+ puts "Usage: #{$0} <directory> <yamlfile> <options>"
150
+ exit
151
+ end
152
+
153
+ dirname, yamlfile = ARGV.shift, ARGV.shift
154
+
155
+ unless File.directory?(dirname)
156
+ puts "Usage: #{dirname} is not a directory."
157
+ exit
158
+ end
159
+
160
+ root = YAMLFS.new(yamlfile)
161
+
162
+ # Set the root FuseFS
163
+ FuseFS.set_root(root)
164
+
165
+ FuseFS.mount_under(dirname, *ARGV)
166
+
167
+ FuseFS.run # This doesn't return until we're unmounted.
168
+ end
@@ -0,0 +1,400 @@
1
+ require 'spec_helper'
2
+
3
+ describe FuseFS do
4
+ TEST_FILE = "/aPath/aFile"
5
+ TEST_DIR = "/aPath"
6
+ ROOT_PATH = "/"
7
+ Struct.new("FuseFileInfo",:flags,:fh)
8
+
9
+ describe "an empty FuseFS object" do
10
+ before(:each) do
11
+ @fuse = FuseFS::RFuseFSAPI.new(Object.new())
12
+ end
13
+
14
+ it "should return an appropriate Stat for the root directory" do
15
+ stat = @fuse.getattr(ROOT_PATH)
16
+ stat.should respond_to(:dev)
17
+ (stat.mode & FuseFS::Stat::S_IFDIR).should_not == 0
18
+ (stat.mode & FuseFS::Stat::S_IFREG).should == 0
19
+ permissions(stat.mode).should == 0555
20
+ end
21
+
22
+ it "should have an empty root directory" do
23
+ filler = mock("entries_filler")
24
+ filler.should_receive(:push).with(".",nil,0)
25
+ filler.should_receive(:push).with("..",nil,0)
26
+ @fuse.readdir("/",filler,nil,nil)
27
+ end
28
+
29
+ it "should raise ENOENT for other paths" do
30
+ lambda { @fuse.getattr("/somepath") }.should raise_error(Errno::ENOENT)
31
+ end
32
+
33
+ it "should not allow new files or directories" do
34
+ lambda { @fuse.mknod("/afile",0100644,0) }.should raise_error(Errno::EACCES)
35
+ lambda { @fuse.mkdir("/adir",0040555) }.should raise_error(Errno::EACCES)
36
+ end
37
+ end
38
+
39
+ describe "a FuseFS filesystem" do
40
+ before(:each) do
41
+ @mock_fuse = mock("FuseFS")
42
+ @fuse = FuseFS::RFuseFSAPI.new(@mock_fuse)
43
+ end
44
+
45
+ describe :readdir do
46
+ before(:each) do
47
+ @mock_fuse.should_receive(:contents).with("/apath").and_return(["afile"])
48
+ end
49
+
50
+ it "should add . and .. to the results of :contents when listing a directory" do
51
+ filler = mock("entries_filler")
52
+ filler.should_receive(:push).with(".",nil,0)
53
+ filler.should_receive(:push).with("..",nil,0)
54
+ filler.should_receive(:push).with("afile",nil,0)
55
+ @fuse.readdir("/apath",filler,nil,nil)
56
+ end
57
+
58
+ end
59
+
60
+ describe :getattr do
61
+
62
+ #Root directory is special (ish) so we need to run these specs twice.
63
+ [ROOT_PATH,TEST_DIR].each do |dir|
64
+
65
+ context "of a directory #{ dir }" do
66
+
67
+ before(:each) do
68
+ @mock_fuse.stub!(:file?).and_return(false)
69
+ @mock_fuse.should_receive(:directory?).with(dir).at_most(:once).and_return(true)
70
+ @checkfile = (dir == "/" ? "" : dir ) + FuseFS::RFuseFS::CHECK_FILE
71
+ end
72
+
73
+ it "should return a Stat like object representing a directory" do
74
+ @mock_fuse.should_receive(:can_write?).with(@checkfile).at_most(:once).and_return(false)
75
+ @mock_fuse.should_receive(:can_mkdir?).with(@checkfile).at_most(:once).and_return(false)
76
+ stat = @fuse.getattr(dir)
77
+ #Apparently find relies on nlink accurately listing the number of files/directories or nlink being 1
78
+ stat.nlink.should == 1
79
+ filetype(stat.mode).should == FuseFS::Stat::S_IFDIR
80
+ permissions(stat.mode).should == 0555
81
+ end
82
+
83
+
84
+ it "should return writable mode if can_mkdir?" do
85
+ @mock_fuse.should_receive(:can_mkdir?).with(@checkfile).at_most(:once).and_return(true)
86
+
87
+ stat = @fuse.getattr(dir)
88
+ permissions(stat.mode).should == 0777
89
+ end
90
+
91
+ it "should return writable mode if can_write?" do
92
+ @mock_fuse.should_receive(:can_write?).with(@checkfile).at_most(:once).and_return(true)
93
+
94
+ stat = @fuse.getattr(dir)
95
+ permissions(stat.mode).should == 0777
96
+
97
+ end
98
+
99
+ it "should return times in the result if available" do
100
+ @mock_fuse.should_receive(:times).with(dir).and_return([10,20,30])
101
+ stat = @fuse.getattr(dir)
102
+ stat.atime.should == 10
103
+ stat.mtime.should == 20
104
+ stat.ctime.should == 30
105
+ end
106
+ end
107
+ end
108
+
109
+ describe "a file" do
110
+
111
+ before(:each) do
112
+ @file="/aPath/aFile"
113
+ @mock_fuse.stub!(:directory?).and_return(false)
114
+ @mock_fuse.should_receive(:file?).with(@file).at_most(:once).and_return(true)
115
+ end
116
+
117
+
118
+ it "should return a Stat like object representing a file" do
119
+ stat = @fuse.getattr(@file)
120
+ (stat.mode & FuseFS::Stat::S_IFDIR).should == 0
121
+ (stat.mode & FuseFS::Stat::S_IFREG).should_not == 0
122
+ permissions(stat.mode).should == 0444
123
+ end
124
+
125
+ it "should indicate executable mode if executable?" do
126
+ @mock_fuse.should_receive(:executable?).with(@file).and_return(true)
127
+ stat = @fuse.getattr(@file)
128
+ permissions(stat.mode).should == 0555
129
+ end
130
+
131
+ it "should indicate writable mode if can_write?" do
132
+ @mock_fuse.should_receive(:can_write?).with(@file).and_return(true)
133
+ stat = @fuse.getattr(@file)
134
+ permissions(stat.mode).should == 0666
135
+ end
136
+
137
+ it "should by 777 mode if can_write? and exectuable?" do
138
+ @mock_fuse.should_receive(:can_write?).with(@file).and_return(true)
139
+ @mock_fuse.should_receive(:executable?).with(@file).and_return(true)
140
+ stat = @fuse.getattr(@file)
141
+ permissions(stat.mode).should == 0777
142
+ end
143
+
144
+ it "should include size in the result if available" do
145
+ @mock_fuse.should_receive(:size).with(@file).and_return(234)
146
+ stat = @fuse.getattr(@file)
147
+ stat.size.should == 234
148
+ end
149
+
150
+ it "should include times in the result if available" do
151
+ @mock_fuse.should_receive(:times).with(@file).and_return([22,33,44])
152
+ stat = @fuse.getattr(@file)
153
+ stat.atime.should == 22
154
+ stat.mtime.should == 33
155
+ stat.ctime.should == 44
156
+ end
157
+ end
158
+
159
+ it "should raise ENOENT for a path that does not exist" do
160
+ @mock_fuse.should_receive(:file?).with(TEST_FILE).and_return(false)
161
+ @mock_fuse.should_receive(:directory?).with(TEST_FILE).and_return(false)
162
+ lambda{stat = @fuse.getattr(TEST_FILE) }.should raise_error(Errno::ENOENT)
163
+ end
164
+ end
165
+
166
+ context "creating files and directories" do
167
+
168
+ it ":mknod should raise EACCES unless :can_write?" do
169
+ @mock_fuse.stub!(:file?).with(TEST_FILE).and_return(false)
170
+ @mock_fuse.stub!(:directory?).with(TEST_FILE).and_return(false)
171
+ @mock_fuse.should_receive(:can_write?).with(TEST_FILE).and_return(false)
172
+ lambda{@fuse.mknod(TEST_FILE,0100644,nil)}.should raise_error(Errno::EACCES)
173
+ end
174
+
175
+ it ":mkdir should raise EACCES unless :can_mkdir?" do
176
+ @mock_fuse.stub!(:file?).with(TEST_FILE).and_return(false)
177
+ @mock_fuse.stub!(:directory?).with(TEST_FILE).and_return(false)
178
+ @mock_fuse.should_receive(:can_mkdir?).with(TEST_FILE).and_return(false)
179
+ lambda{@fuse.mkdir(TEST_FILE,004555)}.should raise_error(Errno::EACCES)
180
+ end
181
+
182
+ it ":mknod should raise EACCES unless mode requests a regular file" do
183
+ @mock_fuse.stub!(:file?).with(TEST_FILE).and_return(false)
184
+ @mock_fuse.stub!(:directory?).with(TEST_FILE).and_return(false)
185
+ @mock_fuse.stub!(:can_write?).with(TEST_FILE).and_return(true)
186
+ lambda{@fuse.mknod(TEST_FILE,FuseFS::Stat::S_IFLNK | 0644,nil)}.should raise_error(Errno::EACCES)
187
+ end
188
+
189
+ it ":mknod should result in getattr returning a Stat like object representing an empty file" do
190
+ @mock_fuse.stub!(:file?).with(TEST_FILE).and_return(false)
191
+ @mock_fuse.stub!(:directory?).with(TEST_FILE).and_return(false)
192
+ @mock_fuse.stub!(:can_write?).with(TEST_FILE).and_return(true)
193
+ @fuse.mknod(TEST_FILE,FuseFS::Stat::S_IFREG | 0644,nil)
194
+
195
+ stat = @fuse.getattr(TEST_FILE)
196
+ filetype(stat.mode).should == FuseFS::Stat::S_IFREG
197
+ stat.size.should == 0
198
+ end
199
+
200
+ it ":mkdir should not raise error if can_mkdir?" do
201
+ @mock_fuse.should_receive(:can_mkdir?).with(TEST_FILE).and_return(true)
202
+ @fuse.mkdir(TEST_FILE,004555)
203
+ end
204
+
205
+ end
206
+
207
+ context "reading files" do
208
+ it "should read the contents of a file" do
209
+ ffi = Struct::FuseFileInfo.new()
210
+ ffi.flags = Fcntl::O_RDONLY
211
+ @mock_fuse.stub!(:file?).with(TEST_FILE).and_return(true)
212
+ @mock_fuse.stub!(:read_file).with(TEST_FILE).and_return("Hello World\n")
213
+ @fuse.open(TEST_FILE,ffi)
214
+ #to me fuse is backwards -- size, offset!
215
+ @fuse.read(TEST_FILE,5,0,ffi).should == "Hello"
216
+ @fuse.read(TEST_FILE,4,6,ffi).should == "Worl"
217
+ @fuse.read(TEST_FILE,10,8,ffi).should == "rld\n"
218
+ @fuse.flush(TEST_FILE,ffi)
219
+ @fuse.release(TEST_FILE,ffi)
220
+ end
221
+ end
222
+
223
+ context "writing files" do
224
+ it "should overwrite a file opened WR_ONLY" do
225
+ ffi = Struct::FuseFileInfo.new()
226
+ ffi.flags = Fcntl::O_WRONLY
227
+ @mock_fuse.stub!(:can_write?).with(TEST_FILE).and_return(true)
228
+ @mock_fuse.stub!(:read_file).with(TEST_FILE).and_return("I'm writing a file\n")
229
+ @mock_fuse.should_receive(:write_to).once().with(TEST_FILE,"My new contents\n")
230
+ @fuse.open(TEST_FILE,ffi)
231
+ @fuse.ftruncate(TEST_FILE,0,ffi)
232
+ @fuse.write(TEST_FILE,"My new c",0,ffi)
233
+ @fuse.write(TEST_FILE,"ontents\n",8,ffi)
234
+ @fuse.flush(TEST_FILE,ffi)
235
+ #that's right flush can be called more than once.
236
+ @fuse.flush(TEST_FILE,ffi)
237
+ #but then we can write some more and flush again
238
+ @fuse.release(TEST_FILE,ffi)
239
+ end
240
+
241
+ it "should append to a file opened WR_ONLY | APPEND" do
242
+ ffi = Struct::FuseFileInfo.new()
243
+ ffi.flags = Fcntl::O_WRONLY | Fcntl::O_APPEND
244
+ @mock_fuse.stub!(:can_write?).with(TEST_FILE).and_return(true)
245
+ @mock_fuse.stub!(:read_file).with(TEST_FILE).and_return("I'm writing a file\n")
246
+ @mock_fuse.should_receive(:write_to).once().with(TEST_FILE,"I'm writing a file\nMy new contents\n")
247
+ @fuse.open(TEST_FILE,ffi)
248
+ @fuse.write(TEST_FILE,"My new c",0,ffi)
249
+ @fuse.write(TEST_FILE,"ontents\n",8,ffi)
250
+ @fuse.flush(TEST_FILE,ffi)
251
+ #that's right flush can be called more than once. But we should only write-to the first time
252
+ @fuse.flush(TEST_FILE,ffi)
253
+ @fuse.release(TEST_FILE,ffi)
254
+
255
+ end
256
+
257
+ it "should do sensible things for files opened RDWR"
258
+
259
+ end
260
+
261
+ context "raw reading" do
262
+ it "should call the raw_read/raw_close if raw_open returns true" do
263
+ ffi = Struct::FuseFileInfo.new()
264
+ ffi.flags = Fcntl::O_RDONLY
265
+ @mock_fuse.stub!(:can_write?).with(TEST_FILE).and_return(true)
266
+ @mock_fuse.should_receive(:raw_open).with(TEST_FILE,"r",true).and_return("raw")
267
+ @mock_fuse.should_receive(:raw_read).with(TEST_FILE,5,0,"raw").and_return("12345")
268
+ @mock_fuse.should_receive(:raw_read).with(TEST_FILE,5,5,"raw").and_return("67890")
269
+ @mock_fuse.should_receive(:raw_close).with(TEST_FILE,"raw")
270
+ @fuse.open(TEST_FILE,ffi)
271
+ @fuse.read(TEST_FILE,0,5,ffi).should == "12345"
272
+ @fuse.read(TEST_FILE,5,5,ffi).should == "67890"
273
+ @fuse.flush(TEST_FILE,ffi)
274
+ @fuse.release(TEST_FILE,ffi)
275
+ end
276
+
277
+ end
278
+
279
+ context "raw writing" do
280
+ it "should call raw_truncate,raw_write,raw_close if raw_open returns true" do
281
+ ffi = Struct::FuseFileInfo.new()
282
+ ffi.flags = Fcntl::O_WRONLY
283
+ raw = Object.new()
284
+ @mock_fuse.stub!(:can_write?).with(TEST_FILE).and_return(true)
285
+ @mock_fuse.should_receive(:raw_open).with(TEST_FILE,"w",true).and_return(raw)
286
+ @mock_fuse.should_receive(:raw_truncate).with(TEST_FILE,0,raw)
287
+ @mock_fuse.should_receive(:raw_write).with(TEST_FILE,0,5,"12345",raw).once().and_return(5)
288
+ @mock_fuse.should_receive(:raw_write).with(TEST_FILE,5,5,"67890",raw).once().and_return(5)
289
+ @mock_fuse.should_receive(:raw_close).with(TEST_FILE,raw)
290
+ @fuse.open(TEST_FILE,ffi)
291
+ @fuse.ftruncate(TEST_FILE,0,ffi)
292
+ @fuse.write(TEST_FILE,"12345",0,ffi).should == 5
293
+ @fuse.write(TEST_FILE,"67890",5,ffi).should == 5
294
+ @fuse.flush(TEST_FILE,ffi)
295
+ @fuse.release(TEST_FILE,ffi)
296
+ end
297
+
298
+ it "should pass 'wa' to raw_open if fuse sends WRONLY | APPEND" do
299
+ ffi = Struct::FuseFileInfo.new()
300
+ ffi.flags = Fcntl::O_WRONLY | Fcntl::O_APPEND
301
+ raw = Object.new()
302
+ @mock_fuse.stub!(:can_write?).with(TEST_FILE).and_return(true)
303
+ @mock_fuse.should_receive(:raw_open).with(TEST_FILE,"wa",true).and_return(raw)
304
+ @fuse.open(TEST_FILE,ffi)
305
+ end
306
+ end
307
+
308
+ context "deleting files" do
309
+ it "should raise EACCES unless :can_delete?" do
310
+ @mock_fuse.should_receive(:can_delete?).with(TEST_FILE).and_return(false)
311
+ lambda {@fuse.unlink(TEST_FILE)}.should raise_error(Errno::EACCES)
312
+ end
313
+
314
+ it "should :delete without error if :can_delete?" do
315
+ @mock_fuse.stub!(:can_delete?).with(TEST_FILE).and_return(true)
316
+ @mock_fuse.should_receive(:delete).with(TEST_FILE)
317
+ @fuse.unlink(TEST_FILE)
318
+ end
319
+
320
+ it "should remove entries created with mknod that have never been opened" do
321
+ @mock_fuse.stub!(:file?).with(TEST_FILE).and_return(false)
322
+ @mock_fuse.stub!(:directory?).with(TEST_FILE).and_return(false)
323
+ @mock_fuse.stub!(:can_delete?).with(TEST_FILE).and_return(true)
324
+ @mock_fuse.stub!(:can_write?).with(TEST_FILE).and_return(true)
325
+ @fuse.mknod(TEST_FILE,FuseFS::Stat::S_IFREG | 0644,nil)
326
+ @fuse.unlink(TEST_FILE)
327
+ lambda {@fuse.getattr(TEST_FILE)}.should raise_error(Errno::ENOENT)
328
+ end
329
+ end
330
+
331
+ context "deleting directories" do
332
+ it "should raise EACCES unless :can_rmdir?" do
333
+ @mock_fuse.should_receive(:can_rmdir?).with(TEST_DIR).and_return(false)
334
+ lambda{@fuse.rmdir(TEST_DIR)}.should raise_error(Errno::EACCES)
335
+ end
336
+
337
+ it "should :rmdir without error if :can_rmdir?" do
338
+ @mock_fuse.stub!(:can_rmdir?).with(TEST_DIR).and_return(true)
339
+ @fuse.rmdir(TEST_DIR)
340
+ end
341
+ end
342
+
343
+ context "touching files" do
344
+ it "should call :touch in response to utime" do
345
+ @mock_fuse.should_receive(:touch).with(TEST_FILE,220)
346
+ @fuse.utime(TEST_FILE,100,220)
347
+ end
348
+ end
349
+
350
+ context "renaming files" do
351
+ before(:each) do
352
+ @oldfile = "/aPath/oldFile"
353
+ @newfile = "/aNewFile"
354
+ @mock_fuse.stub!(:file?).with(@oldfile).and_return(true)
355
+ @mock_fuse.stub!(:directory?).with(@oldfile).and_return(false)
356
+ end
357
+ it "should raise EACCES unless :can_write? the new file" do
358
+ @mock_fuse.stub!(:can_delete?).with(@oldfile).and_return(true)
359
+ @mock_fuse.should_receive(:can_write?).with(@newfile).and_return(false)
360
+ lambda {@fuse.rename(@oldfile,@newfile)}.should raise_error(Errno::EACCES)
361
+ end
362
+
363
+ it "should raise EACCES unless :can_delete the old file" do
364
+ @mock_fuse.stub!(:can_write?).with(@newfile).and_return(true)
365
+ @mock_fuse.should_receive(:can_delete?).with(@oldfile).and_return(false)
366
+ lambda {@fuse.rename(@oldfile,@newfile)}.should raise_error(Errno::EACCES)
367
+ end
368
+
369
+ it "should copy and delete files" do
370
+ @mock_fuse.stub!(:can_write?).with(@newfile).and_return(true)
371
+ @mock_fuse.stub!(:can_delete?).with(@oldfile).and_return(true)
372
+ @mock_fuse.should_receive(:read_file).with(@oldfile).and_return("some contents\n")
373
+ @mock_fuse.should_receive(:write_to).with(@newfile,"some contents\n")
374
+ @mock_fuse.should_receive(:delete).with(@oldfile)
375
+ @fuse.rename(@oldfile,@newfile)
376
+ end
377
+
378
+ it "should not copy and delete files if fs responds_to? :rename" do
379
+ @mock_fuse.should_receive(:rename).with(@oldfile,@newfile).and_return(true)
380
+ @fuse.rename(@oldfile,@newfile)
381
+ end
382
+
383
+ it "should raise EACCES if moving a directory and rename not supported" do
384
+ @mock_fuse.stub!(:file?).with(@oldfile).and_return(false)
385
+ @mock_fuse.stub!(:directory?).with(@oldfile).and_return(true)
386
+ @mock_fuse.stub!(:can_write?).with(@newfile).and_return(true)
387
+ @mock_fuse.stub!(:can_delete?).with(@oldfile).and_return(true)
388
+ lambda{@fuse.rename(@oldfile,@newfile)}.should raise_error(Errno::EACCES)
389
+ end
390
+
391
+ end
392
+
393
+ end
394
+
395
+ describe "a FuseFS filesystem with gid/uid specific behaviour" do
396
+ it "should provide context uid and gid for all API methods"
397
+ end
398
+ end
399
+
400
+