rfusefs 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+
data/lib/fusefs.rb ADDED
@@ -0,0 +1,8 @@
1
+
2
+ module FuseFS
3
+ RFUSEFS_COMPATIBILITY = false
4
+ end
5
+
6
+ require 'rfusefs'
7
+ require 'fusefs/metadir'
8
+