rfusefs 0.8.0

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/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
+