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