rfusefs 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,244 @@
|
|
1
|
+
module FuseFS
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
# A full in-memory filesystem defined with hashes. It is writable to the
|
6
|
+
# user that mounted it
|
7
|
+
# may create and edit files within it, as well as the programmer
|
8
|
+
# === Usage
|
9
|
+
# root = Metadir.new()
|
10
|
+
# root.mkdir("/hello")
|
11
|
+
# root.write_to("/hello/world","Hello World!\n")
|
12
|
+
# root.write_to("/hello/everybody","Hello Everyone!\n")
|
13
|
+
#
|
14
|
+
# FuseFS.start(mntpath,root)
|
15
|
+
#
|
16
|
+
# Because Metadir is fully recursive, you can mount your own or other defined
|
17
|
+
# directory structures under it. For example, to mount a dictionary filesystem
|
18
|
+
# (see samples/dictfs.rb), use:
|
19
|
+
#
|
20
|
+
# root.mkdir("/dict",DictFS.new())
|
21
|
+
#
|
22
|
+
class MetaDir < FuseDir
|
23
|
+
|
24
|
+
def initialize()
|
25
|
+
@subdirs = Hash.new(nil)
|
26
|
+
@files = Hash.new(nil)
|
27
|
+
end
|
28
|
+
|
29
|
+
def directory?(path)
|
30
|
+
pathmethod(:directory?,path) do |filename|
|
31
|
+
!filename || filename == "/" || @subdirs.has_key?(filename)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def file?(path)
|
36
|
+
pathmethod(:file?,path) do |filename|
|
37
|
+
@files.has_key?(filename)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
#List directory contents
|
42
|
+
def contents(path)
|
43
|
+
pathmethod(:contents,path) do | filename |
|
44
|
+
if !filename
|
45
|
+
(@files.keys + @subdirs.keys).sort.uniq
|
46
|
+
else
|
47
|
+
@subdirs[filename].contents("/")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def read_file(path)
|
53
|
+
pathmethod(:read_file,path) do |filename|
|
54
|
+
@files[filename].to_s
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def size(path)
|
59
|
+
pathmethod(:size,path) do | filename |
|
60
|
+
return @files[filename].to_s.length
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
#can_write only applies to files... see can_mkdir for directories...
|
65
|
+
def can_write?(path)
|
66
|
+
# we have to recurse here because it might not be a MetaDir at
|
67
|
+
# the end of the path, but we don't have to check it is a file
|
68
|
+
# as the API guarantees that
|
69
|
+
pathmethod(:can_write?,path) do |filename|
|
70
|
+
return mount_user?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def write_to(path,contents)
|
75
|
+
pathmethod(:write_to,path,contents) do |filename, filecontents |
|
76
|
+
@files[filename] = filecontents
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Delete a file
|
81
|
+
def can_delete?(path)
|
82
|
+
pathmethod(:can_delete?,path) do |filename|
|
83
|
+
return mount_user?
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def delete(path)
|
88
|
+
pathmethod(:delete,path) do |filename|
|
89
|
+
@files.delete(filename)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
#mkdir - does not make intermediate dirs!
|
95
|
+
def can_mkdir?(path)
|
96
|
+
pathmethod(:can_mkdir?,path) do |dirname|
|
97
|
+
return mount_user?
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def mkdir(path,dir=nil)
|
102
|
+
pathmethod(:mkdir,path,dir) do | dirname,dirobj |
|
103
|
+
dirobj ||= MetaDir.new
|
104
|
+
@subdirs[dirname] = dirobj
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Delete an existing directory make sure it is not empty
|
109
|
+
def can_rmdir?(path)
|
110
|
+
pathmethod(:can_rmdir?,path) do |dirname|
|
111
|
+
return mount_user? && @subdirs.has_key?(dirname) && @subdirs[dirname].contents("/").empty?
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def rmdir(path)
|
116
|
+
pathmethod(:rmdir,path) do |dirname|
|
117
|
+
@subdirs.delete(dirname)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def rename(from_path,to_path,to_fusefs = self)
|
122
|
+
|
123
|
+
from_base,from_rest = split_path(from_path)
|
124
|
+
|
125
|
+
case
|
126
|
+
when !from_base
|
127
|
+
# Shouldn't ever happen.
|
128
|
+
raise Errno::EACCES.new("Can't move root")
|
129
|
+
when !from_rest
|
130
|
+
# So now we have a file or directory to move
|
131
|
+
if @files.has_key?(from_base)
|
132
|
+
return false unless can_delete?(from_base) && to_fusefs.can_write?(to_path)
|
133
|
+
to_fusefs.write_to(to_path,@files[from_base])
|
134
|
+
@files.delete(from_base)
|
135
|
+
elsif @subdirs.has_key?(from_base)
|
136
|
+
# we don't check can_rmdir? because that would prevent us
|
137
|
+
# moving non empty directories
|
138
|
+
return false unless mount_user? && to_fusefs.can_mkdir?(to_path)
|
139
|
+
begin
|
140
|
+
to_fusefs.mkdir(to_path,@subdirs[from_base])
|
141
|
+
@subdirs.delete(from_base)
|
142
|
+
rescue ArgumentError
|
143
|
+
# to_rest does not support mkdir with an arbitrary object
|
144
|
+
return false
|
145
|
+
end
|
146
|
+
else
|
147
|
+
#We shouldn't get this either
|
148
|
+
return false
|
149
|
+
end
|
150
|
+
when @subdirs.has_key?(from_base)
|
151
|
+
begin
|
152
|
+
if to_fusefs != self
|
153
|
+
#just keep recursing..
|
154
|
+
return @subdirs[from_base].rename(from_rest,to_path,to_fusefs)
|
155
|
+
else
|
156
|
+
to_base,to_rest = split_path(to_path)
|
157
|
+
if from_base == to_base
|
158
|
+
#mv within a subdir, just pass it on
|
159
|
+
return @subdirs[from_base].rename(from_rest,to_rest)
|
160
|
+
else
|
161
|
+
#OK, this is the tricky part, we want to move something further down
|
162
|
+
#our tree into something in another part of the tree.
|
163
|
+
#from this point on we keep a reference to the fusefs that owns
|
164
|
+
#to_path (ie us) and pass it down, but only if the eventual path
|
165
|
+
#is writable anyway!
|
166
|
+
if (file?(to_path))
|
167
|
+
return false unless can_write?(to_path)
|
168
|
+
else
|
169
|
+
return false unless can_mkdir?(to_path)
|
170
|
+
end
|
171
|
+
|
172
|
+
return @subdirs[from_base].rename(from_rest,to_path,self)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
|
177
|
+
rescue NoMethodError
|
178
|
+
#sub dir doesn't support rename
|
179
|
+
return false
|
180
|
+
rescue ArgumentError
|
181
|
+
#sub dir doesn't support rename with additional to_fusefs argument
|
182
|
+
return false
|
183
|
+
end
|
184
|
+
else
|
185
|
+
return false
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
private
|
190
|
+
|
191
|
+
# If api method not explicitly defined above, then pass it on
|
192
|
+
# to a potential FuseFS further down the chain
|
193
|
+
# If that turns out to be one of us then return the default
|
194
|
+
def method_missing(method,*args)
|
195
|
+
if (RFuseFSAPI::API_METHODS.has_key?(method))
|
196
|
+
pathmethod(method,*args) do
|
197
|
+
return RFuseFS::API_METHODS[method]
|
198
|
+
end
|
199
|
+
else
|
200
|
+
super
|
201
|
+
end
|
202
|
+
end
|
203
|
+
# is the accessing user the same as the user that mounted our FS?, used for
|
204
|
+
# all write activity
|
205
|
+
def mount_user?
|
206
|
+
return Process.uid == FuseFS.reader_uid
|
207
|
+
end
|
208
|
+
|
209
|
+
#All our FuseFS methods follow the same pattern...
|
210
|
+
def pathmethod(method, path,*args)
|
211
|
+
base,rest = split_path(path)
|
212
|
+
case
|
213
|
+
when ! base
|
214
|
+
#request for the root of our fs
|
215
|
+
yield(nil,*args)
|
216
|
+
when ! rest
|
217
|
+
#base is the filename, no more directories to traverse
|
218
|
+
yield(base,*args)
|
219
|
+
when @subdirs.has_key?(base)
|
220
|
+
#base is a subdirectory, pass it on if we can
|
221
|
+
begin
|
222
|
+
@subdirs[base].send(method,rest,*args)
|
223
|
+
rescue NoMethodError
|
224
|
+
#Oh well
|
225
|
+
return RFuseFSAPI::API_METHODS[method]
|
226
|
+
rescue ArgumentError
|
227
|
+
#can_mkdir,mkdir
|
228
|
+
if args.pop.nil?
|
229
|
+
#possibly a default arg, try sending again with one fewer arg
|
230
|
+
@subdirs[base].send(method,rest,*args)
|
231
|
+
else
|
232
|
+
#definitely not a default arg, reraise
|
233
|
+
Kernel.raise
|
234
|
+
end
|
235
|
+
end
|
236
|
+
else
|
237
|
+
#return the default response
|
238
|
+
return RFuseFSAPI::API_METHODS[method]
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
|
243
|
+
end
|
244
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
|
2
|
+
module FuseFS
|
3
|
+
|
4
|
+
# A FuseFS that maps files from files from their original location into a new path
|
5
|
+
# eg tagged audio files can be mapped by title etc...
|
6
|
+
class PathMapperFS < FuseDir
|
7
|
+
|
8
|
+
# Convert raw_mode strings to IO open mode strings
|
9
|
+
def self.open_mode(raw_mode)
|
10
|
+
case raw_mode
|
11
|
+
when "r"
|
12
|
+
"r"
|
13
|
+
when "ra"
|
14
|
+
"r" #not really sensible..
|
15
|
+
when "rw"
|
16
|
+
"w+"
|
17
|
+
when "rwa"
|
18
|
+
"a+"
|
19
|
+
when
|
20
|
+
"w"
|
21
|
+
when "wa"
|
22
|
+
"a"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
attr_accessor :use_raw_file_access, :allow_write
|
26
|
+
#Creates a PathMapperFS
|
27
|
+
#See #mapDirectory
|
28
|
+
def PathMapperFS.create(dir,options={ },&block)
|
29
|
+
pm_fs = PathMapperFS.new(options)
|
30
|
+
pm_fs.mapDirectory(dir) do |file|
|
31
|
+
block.call(file)
|
32
|
+
end
|
33
|
+
return pm_fs
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(options = { })
|
37
|
+
@root = { }
|
38
|
+
@use_raw_file_access = options[:use_raw_file_access]
|
39
|
+
@allow_write = options[:allow_write]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Adds new_path to our list of mapped files
|
43
|
+
#
|
44
|
+
# Returns a hash entry which stores the real_path under the :pm_real_path key.
|
45
|
+
def mapFile(real_path,new_path)
|
46
|
+
#split path into components
|
47
|
+
components = new_path.scan(/[^\/]+/)
|
48
|
+
|
49
|
+
#create a hash of hashes to represent our directory structure
|
50
|
+
new_file = components.inject(@root) { |directory, file|
|
51
|
+
directory[file] ||= Hash.new()
|
52
|
+
}
|
53
|
+
new_file[:pm_real_path] = real_path
|
54
|
+
return new_file
|
55
|
+
end
|
56
|
+
|
57
|
+
# Convenience method to recursively map all files according to the given block
|
58
|
+
def mapDirectory(*dirs)
|
59
|
+
require 'find'
|
60
|
+
Find.find(*dirs) do |file|
|
61
|
+
new_path = yield file
|
62
|
+
mapFile(file,new_path) if new_path
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Takes a mapped file name and returns the original real_path
|
67
|
+
def unmap(path)
|
68
|
+
possible_file = node(path)
|
69
|
+
return possible_file ? possible_file[:pm_real_path] : nil
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns true for any directory referenced by a mapped file
|
73
|
+
# See FuseFS API.txt
|
74
|
+
def directory?(path)
|
75
|
+
possible_dir = node(path)
|
76
|
+
possible_dir && !possible_dir[:pm_real_path]
|
77
|
+
end
|
78
|
+
|
79
|
+
# See FuseFS API.txt
|
80
|
+
# expects to be called only if directory? returns true
|
81
|
+
def contents(path)
|
82
|
+
node(path).keys
|
83
|
+
end
|
84
|
+
|
85
|
+
# See FuseFS API.txt
|
86
|
+
def file?(path)
|
87
|
+
filename = unmap(path)
|
88
|
+
filename && File.file?(filename)
|
89
|
+
end
|
90
|
+
|
91
|
+
# See FuseFS API.txt
|
92
|
+
# only called if option :raw_reads is not set
|
93
|
+
def read_file(path)
|
94
|
+
IO.read(unmap(path))
|
95
|
+
end
|
96
|
+
|
97
|
+
# We can only write to existing files
|
98
|
+
# because otherwise we don't have anything to back it
|
99
|
+
def can_write?(path)
|
100
|
+
@allow_write && file?(path)
|
101
|
+
end
|
102
|
+
|
103
|
+
def write_to(path,contents)
|
104
|
+
File.open(path) do |f|
|
105
|
+
f.print(contents)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# See FuseFS API.txt
|
110
|
+
def size(path)
|
111
|
+
File.size(unmap(path))
|
112
|
+
end
|
113
|
+
|
114
|
+
# See RFuseFS API.txt
|
115
|
+
def times(path)
|
116
|
+
realpath = unmap(path)
|
117
|
+
if (realpath)
|
118
|
+
stat = File.stat(realpath)
|
119
|
+
return [ stat.atime, stat.mtime, stat.ctime ]
|
120
|
+
else
|
121
|
+
# We're a directory
|
122
|
+
return [0,0,0]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# See FuseFS API.txt
|
127
|
+
# Will create, store and return a File object for the underlying file
|
128
|
+
# for subsequent use with the raw_read/raw_close methods
|
129
|
+
# expects file? to return true before this method is called
|
130
|
+
def raw_open(path,mode,rfusefs = nil)
|
131
|
+
|
132
|
+
return false unless @use_raw_file_access
|
133
|
+
|
134
|
+
return false if mode.include?("w") && (!@allow_writes)
|
135
|
+
|
136
|
+
@openfiles ||= Hash.new() unless rfusefs
|
137
|
+
|
138
|
+
real_path = unmap(path)
|
139
|
+
|
140
|
+
unless real_path
|
141
|
+
if rfusefs
|
142
|
+
raise Errno::ENOENT.new(path)
|
143
|
+
else
|
144
|
+
#fusefs will go on to call file?
|
145
|
+
return false
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
file = File.new(real_path,PathMapperFS.open_mode(mode))
|
150
|
+
|
151
|
+
@openfiles[path] = file unless rfusefs
|
152
|
+
|
153
|
+
return file
|
154
|
+
end
|
155
|
+
|
156
|
+
# See (R)FuseFS API.txt
|
157
|
+
def raw_read(path,off,sz,file=nil)
|
158
|
+
file = @openfiles[path] unless file
|
159
|
+
file.sysseek(off)
|
160
|
+
file.sysread(sz)
|
161
|
+
end
|
162
|
+
|
163
|
+
# See (R)FuseFS API.txt
|
164
|
+
def raw_write(path,offset,sz,buf,file=nil)
|
165
|
+
file = @openfiles[path] unless file
|
166
|
+
file.sysseek(off)
|
167
|
+
file.syswrite(buf[0,sz])
|
168
|
+
end
|
169
|
+
|
170
|
+
# See (R)FuseFS API.txt
|
171
|
+
def raw_close(path,file=nil)
|
172
|
+
unless file
|
173
|
+
file = @openfiles.delete(path)
|
174
|
+
end
|
175
|
+
file.close if file
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
# returns a hash representing a given node, if we have a mapped entry for it, nil otherwise
|
180
|
+
# this entry is a file if it has_key?(:pm_real_path), otherwise it is a directory.
|
181
|
+
def node(path)
|
182
|
+
path_components = scan_path(path)
|
183
|
+
|
184
|
+
#not actually injecting anything here, we're just following the hash of hashes...
|
185
|
+
path_components.inject(@root) { |dir,file|
|
186
|
+
break unless dir[file]
|
187
|
+
dir[file]
|
188
|
+
}
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
|