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/.gemtest +0 -0
- data/History.txt +9 -0
- data/README.rdoc +108 -0
- data/Rakefile +25 -0
- data/TODO.txt +7 -0
- data/lib/fuse/rfusefs-fuse.rb +497 -0
- data/lib/fusefs/dirlink.rb +46 -0
- data/lib/fusefs/metadir.rb +244 -0
- data/lib/fusefs/pathmapper.rb +193 -0
- data/lib/fusefs.rb +8 -0
- data/lib/rfusefs.rb +318 -0
- data/samples/demo.rb +64 -0
- data/samples/dictfs.rb +84 -0
- data/samples/hello.rb +24 -0
- data/samples/openurifs.rb +53 -0
- data/samples/railsfs.rb +77 -0
- data/samples/sqlfs.rb +134 -0
- data/samples/yamlfs.rb +168 -0
- data/spec/rfusefs_spec.rb +400 -0
- data/spec/sample_spec.rb +29 -0
- data/spec/spec_helper.rb +41 -0
- data/spec-fusefs/fusefs_spec.rb +12 -0
- data.tar.gz.sig +0 -0
- metadata +166 -0
- metadata.gz.sig +1 -0
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
|
+
|