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