rfusefs 1.0.2.RC0 → 1.0.2.RC1
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/README.rdoc +5 -4
- data/Rakefile +3 -0
- data/lib/fuse/fusedir.rb +106 -22
- data/lib/fuse/rfusefs-fuse.rb +98 -27
- data/lib/fusefs/metadir.rb +46 -4
- data/lib/fusefs/pathmapper.rb +121 -20
- data/lib/rfusefs/version.rb +1 -1
- data/lib/rfusefs.rb +6 -3
- data/rfusefs.gemspec +3 -1
- data/samples/demo.rb +4 -6
- data/samples/hello.rb +1 -4
- data/spec/metadir_spec.rb +69 -0
- data/spec/pathmapper_spec.rb +276 -61
- data/spec/rfusefs_spec.rb +73 -17
- metadata +36 -4
data/README.rdoc
CHANGED
@@ -36,23 +36,24 @@ explore the contents under <mountpoint>
|
|
36
36
|
|
37
37
|
Happy Filesystem Hacking!
|
38
38
|
|
39
|
-
=== the hello world filesystem in
|
39
|
+
=== the hello world filesystem in 14 LOC
|
40
40
|
|
41
41
|
require 'rfusefs'
|
42
42
|
|
43
43
|
class HelloDir
|
44
|
+
|
44
45
|
def contents(path)
|
45
46
|
['hello.txt']
|
46
47
|
end
|
48
|
+
|
47
49
|
def file?(path)
|
48
50
|
path == '/hello.txt'
|
49
51
|
end
|
52
|
+
|
50
53
|
def read_file(path)
|
51
54
|
"Hello, World!\n"
|
52
55
|
end
|
53
|
-
|
54
|
-
read_file(path).size
|
55
|
-
end
|
56
|
+
|
56
57
|
end
|
57
58
|
|
58
59
|
# Usage: #{$0} mountpoint [mount_options]
|
data/Rakefile
CHANGED
data/lib/fuse/fusedir.rb
CHANGED
@@ -1,11 +1,68 @@
|
|
1
1
|
module FuseFS
|
2
2
|
|
3
|
+
# Helper for filesystem accounting
|
4
|
+
class StatsHelper
|
5
|
+
|
6
|
+
# @return [Integer] size of filesystem in bytes
|
7
|
+
attr_accessor :max_space
|
8
|
+
# @return [Integer] maximum number of (virtual) inodes
|
9
|
+
attr_accessor :max_nodes
|
10
|
+
|
11
|
+
# If set true, adjustments that cause space/nodes to exceed
|
12
|
+
# the maximums will raise ENOSPC (no space left on device)
|
13
|
+
# @return [Boolean]
|
14
|
+
attr_accessor :strict
|
15
|
+
|
16
|
+
# @return [Integer] used space in bytes
|
17
|
+
attr_reader :space
|
18
|
+
|
19
|
+
# @return [Integer] used inodes (typically count of files and directories)
|
20
|
+
attr_reader :nodes
|
21
|
+
|
22
|
+
#
|
23
|
+
# @param [Integer] max_space
|
24
|
+
# @param [Integer] max_nodes
|
25
|
+
# @param [Booleanr] strict
|
26
|
+
def initialize(max_space=nil,max_nodes=nil,strict=false)
|
27
|
+
@nodes = 0
|
28
|
+
@space = 0
|
29
|
+
@max_space = max_space
|
30
|
+
@max_nodes = max_nodes
|
31
|
+
@strict = strict
|
32
|
+
end
|
33
|
+
|
34
|
+
# Adjust accumlated statistics
|
35
|
+
# @param [Integer] delta_space change in {#space} usage
|
36
|
+
# @param [Integer] delta_nodes change in {#nodes} usage
|
37
|
+
#
|
38
|
+
# @return [void]
|
39
|
+
# @raise [Errno::ENOSPC] if {#strict} and adjusted {#space}/{#nodes} would exceed {#max_space} or {#max_nodes}
|
40
|
+
def adjust(delta_space,delta_nodes=0)
|
41
|
+
@nodes += delta_nodes
|
42
|
+
@space += delta_space
|
43
|
+
raise Errno::ENOSPC if @strict && ( @nodes > @max_nodes || @space > @max_space )
|
44
|
+
end
|
45
|
+
|
46
|
+
# @overload to_statistics()
|
47
|
+
# @return [Array<Integer>] in format expected by {FuseDir#statistics}
|
48
|
+
# @overload to_statistics(free_space,free_nodes)
|
49
|
+
# Calculate total space so that free space remains fixed
|
50
|
+
# @param [Integer] free_space available space in bytes
|
51
|
+
# @param [Integer] free_nodes available (virtual) inodes
|
52
|
+
# @return [Array<Integer>] in format expected by {FuseDir#statistics}
|
53
|
+
def to_statistics(free_space=nil,free_nodes=nil)
|
54
|
+
total_space = free_space ? space + free_space : max_space
|
55
|
+
total_nodes = free_nodes ? nodes + free_nodes : max_nodes
|
56
|
+
[ @space, @nodes, total_space, total_nodes ]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
3
60
|
# This class is equivalent to using Object.new() as the virtual directory
|
4
61
|
# for target for {FuseFS.start}. It exists primarily to document the API
|
5
|
-
# but can also be used as a superclass for your filesystem
|
6
|
-
#
|
62
|
+
# but can also be used as a superclass for your filesystem providing sensible defaults
|
63
|
+
#
|
7
64
|
# == Method call sequences
|
8
|
-
#
|
65
|
+
#
|
9
66
|
# === Stat (getattr)
|
10
67
|
#
|
11
68
|
# FUSE itself will generally stat referenced files and validate the results
|
@@ -38,10 +95,10 @@ module FuseFS
|
|
38
95
|
# FUSE confirms path for the new file is a directory
|
39
96
|
#
|
40
97
|
# * {#can_write?} is checked at file open
|
41
|
-
# * {#write_to} is called when the file is flushed or closed
|
98
|
+
# * {#write_to} is called when the file is synced, flushed or closed
|
99
|
+
#
|
100
|
+
# See also {#raw_open}, {#raw_truncate}, {#raw_write}, {#raw_sync}, {#raw_close}
|
42
101
|
#
|
43
|
-
# See also {#raw_open}, {#raw_truncate}, {#raw_write}, {#raw_close}
|
44
|
-
#
|
45
102
|
# === Deleting files
|
46
103
|
#
|
47
104
|
# FUSE confirms path is a file before we call {#can_delete?} then {#delete}
|
@@ -105,12 +162,12 @@ module FuseFS
|
|
105
162
|
|
106
163
|
# File size
|
107
164
|
# @abstract FuseFS api
|
108
|
-
# @return [
|
109
|
-
def size(path);
|
165
|
+
# @return [Integer] the size in byte of a file (lots of applications rely on this being accurate )
|
166
|
+
def size(path); read_file(path).length ;end
|
110
167
|
|
111
168
|
# File time information. RFuseFS extension.
|
112
169
|
# @abstract FuseFS api
|
113
|
-
# @return [Array<
|
170
|
+
# @return [Array<Integer, Time>] a 3 element array [ atime, mtime. ctime ] (good for rsync etc)
|
114
171
|
def times(path);return INIT_TIMES;end
|
115
172
|
|
116
173
|
# @abstract FuseFS api
|
@@ -176,21 +233,29 @@ module FuseFS
|
|
176
233
|
def raw_open(path,mode,rfusefs = nil);end
|
177
234
|
|
178
235
|
# RFuseFS extension.
|
236
|
+
# @abstract FuseFS api
|
179
237
|
#
|
180
|
-
#
|
181
|
-
#
|
238
|
+
# @overload raw_truncate(path,offset,raw)
|
239
|
+
# Truncate an open file to offset bytes
|
240
|
+
# @param [String] path
|
241
|
+
# @param [Integer] offset
|
242
|
+
# @param [Object] raw the filehandle returned from {#raw_open}
|
243
|
+
# @return [void]
|
182
244
|
#
|
183
|
-
#
|
184
|
-
#
|
185
|
-
#
|
186
|
-
#
|
187
|
-
|
245
|
+
# @overload raw_truncate(path,offset)
|
246
|
+
# Optionally truncate a file to offset bytes directly
|
247
|
+
# @param [String] path
|
248
|
+
# @param [Integer] offset
|
249
|
+
# @return [Boolean]
|
250
|
+
# if truncate has been performed, otherwise the truncation will be performed with {#read_file} and {#write_to}
|
251
|
+
#
|
252
|
+
def raw_truncate(path,offset,raw=nil);end
|
188
253
|
|
189
254
|
# Read _sz_ bytes from file at path (or filehandle raw) starting at offset off
|
190
|
-
#
|
255
|
+
#
|
191
256
|
# @param [String] path
|
192
|
-
# @param [
|
193
|
-
# @param [
|
257
|
+
# @param [Integer] offset
|
258
|
+
# @param [Integer] size
|
194
259
|
# @param [Object] raw the filehandle returned by {#raw_open}
|
195
260
|
# @abstract FuseFS api
|
196
261
|
# @return [void]
|
@@ -201,17 +266,36 @@ module FuseFS
|
|
201
266
|
# @return [void]
|
202
267
|
def raw_write(path,off,sz,buf,raw=nil);end
|
203
268
|
|
269
|
+
|
270
|
+
# Sync buffered data to your filesystem
|
271
|
+
# @param [String] path
|
272
|
+
# @param [Boolena] datasync only sync user data, not metadata
|
273
|
+
# @param [Object] raw the filehandle return by {#raw_open}
|
274
|
+
def raw_sync(path,datasync,raw=nil);end
|
275
|
+
|
204
276
|
# Close the file previously opened at path (or filehandle raw)
|
205
277
|
# @abstract FuseFS api
|
206
278
|
# @return [void]
|
207
279
|
def raw_close(path,raw=nil);end
|
208
280
|
|
209
281
|
# RFuseFS extension.
|
210
|
-
# Extended attributes.
|
282
|
+
# Extended attributes.
|
283
|
+
# @param [String] path
|
284
|
+
# @return [Hash] extended attributes for this path.
|
285
|
+
# The returned object will be manipulated directly using :[] :[]=,, :keys and :delete
|
286
|
+
# so the default (a new empty hash on every call) will not retain attributes that are set
|
287
|
+
# @abstract FuseFS api
|
288
|
+
def xattr(path); {} ; end
|
289
|
+
|
290
|
+
# RFuseFS extensions.
|
291
|
+
# File system statistics
|
211
292
|
# @param [String] path
|
212
|
-
# @return [
|
293
|
+
# @return [Array<Integer>] the statistics
|
294
|
+
# used_space (in bytes), used_files, max_space, max_files
|
295
|
+
# See {StatsHelper}
|
296
|
+
# @return [RFuse::StatVfs] or raw statistics
|
213
297
|
# @abstract FuseFS api
|
214
|
-
def
|
298
|
+
def statistics(path); [0,0,0,0]; end
|
215
299
|
|
216
300
|
# RFuseFS extension.
|
217
301
|
# Called when the filesystem is mounted
|
data/lib/fuse/rfusefs-fuse.rb
CHANGED
@@ -16,15 +16,23 @@ module FuseFS
|
|
16
16
|
@path = path
|
17
17
|
@modified = false
|
18
18
|
@contents = ""
|
19
|
+
@size = 0
|
19
20
|
end
|
20
21
|
|
21
22
|
def read(offset,size)
|
22
23
|
contents[offset,size]
|
23
24
|
end
|
24
25
|
|
26
|
+
def create
|
27
|
+
@contents = ""
|
28
|
+
@modified = true
|
29
|
+
end
|
30
|
+
|
25
31
|
def write(offset,data)
|
32
|
+
# TODO: why append?
|
26
33
|
if append? || offset >= contents.length
|
27
34
|
#ignore offset
|
35
|
+
#TODO: should this zero fill?
|
28
36
|
contents << data
|
29
37
|
else
|
30
38
|
contents[offset,data.length]=data
|
@@ -92,6 +100,10 @@ module FuseFS
|
|
92
100
|
def initialize(root)
|
93
101
|
@root = root
|
94
102
|
@created_files = { }
|
103
|
+
|
104
|
+
# Keep track of changes to file counts and sizes made via Fuse - for #statfs
|
105
|
+
@adj_nodes = 0
|
106
|
+
@adj_size = 0
|
95
107
|
|
96
108
|
#Define method missing for our filesystem
|
97
109
|
#so we can just call all the API methods as required.
|
@@ -123,8 +135,8 @@ module FuseFS
|
|
123
135
|
|
124
136
|
return wrap_context(ctx,__method__,path) if ctx
|
125
137
|
|
126
|
-
uid = Process.
|
127
|
-
gid = Process.
|
138
|
+
uid = Process.uid
|
139
|
+
gid = Process.gid
|
128
140
|
|
129
141
|
if path == "/" || @root.directory?(path)
|
130
142
|
#set "w" flag based on can_mkdir? || can_write? to path + "/._rfuse_check"
|
@@ -135,14 +147,13 @@ module FuseFS
|
|
135
147
|
#nlink is set to 1 because apparently this makes find work.
|
136
148
|
return RFuse::Stat.directory(mode,{ :uid => uid, :gid => gid, :nlink => 1, :atime => atime, :mtime => mtime, :ctime => ctime })
|
137
149
|
elsif @created_files.has_key?(path)
|
138
|
-
|
139
|
-
return RFuse::Stat.file(@created_files[path],{ :uid => uid, :gid => gid, :atime => now, :mtime => now, :ctime => now })
|
150
|
+
return @created_files[path]
|
140
151
|
elsif @root.file?(path)
|
141
152
|
#Set mode from can_write and executable
|
142
153
|
mode = 0444
|
143
154
|
mode |= 0222 if @root.can_write?(path)
|
144
155
|
mode |= 0111 if @root.executable?(path)
|
145
|
-
size =
|
156
|
+
size = size(path)
|
146
157
|
atime,mtime,ctime = @root.times(path)
|
147
158
|
return RFuse::Stat.file(mode,{ :uid => uid, :gid => gid, :size => size, :atime => atime, :mtime => mtime, :ctime => ctime })
|
148
159
|
else
|
@@ -160,6 +171,7 @@ module FuseFS
|
|
160
171
|
end
|
161
172
|
|
162
173
|
@root.mkdir(path)
|
174
|
+
@adj_nodes += 1
|
163
175
|
end #mkdir
|
164
176
|
|
165
177
|
def mknod(ctx,path,mode,major,minor)
|
@@ -170,10 +182,15 @@ module FuseFS
|
|
170
182
|
raise Errno::EACCES.new(path)
|
171
183
|
end
|
172
184
|
|
173
|
-
|
185
|
+
now = Time.now
|
186
|
+
stat = RFuse::Stat.file(mode,{ :uid => Process.uid, :gid => Process.gid, :atime => now, :mtime => now, :ctime => now })
|
187
|
+
|
188
|
+
@created_files[path] = stat
|
189
|
+
@adj_nodes += 1
|
174
190
|
end #mknod
|
175
191
|
|
176
192
|
#ftruncate - eg called after opening a file for write without append
|
193
|
+
#sizes are adjusted at file close
|
177
194
|
def ftruncate(ctx,path,offset,ffi)
|
178
195
|
|
179
196
|
return wrap_context(ctx,__method__,path,offset,ffi) if ctx
|
@@ -182,10 +199,11 @@ module FuseFS
|
|
182
199
|
|
183
200
|
if fh.raw
|
184
201
|
@root.raw_truncate(path,offset,fh.raw)
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
202
|
+
if (offset <= 0)
|
203
|
+
fh.contents = ""
|
204
|
+
else
|
205
|
+
fh.contents = fh.contents[0..offset]
|
206
|
+
end
|
189
207
|
end
|
190
208
|
end
|
191
209
|
|
@@ -197,14 +215,16 @@ module FuseFS
|
|
197
215
|
raise Errno::EACESS.new(path)
|
198
216
|
end
|
199
217
|
|
218
|
+
current_size = size(path)
|
200
219
|
unless @root.raw_truncate(path,offset)
|
201
220
|
contents = @root.read_file(path)
|
202
221
|
if (offset <= 0)
|
203
|
-
|
222
|
+
@root.write_to(path,"")
|
204
223
|
elsif offset < contents.length
|
205
224
|
@root.write_to(path,contents[0..offset] )
|
206
225
|
end
|
207
226
|
end
|
227
|
+
@adj_size = @adj_size - current_size + (offset <= 0 ? 0 : offset)
|
208
228
|
end #truncate
|
209
229
|
|
210
230
|
# Open. Create a FileHandler and store in fuse file info
|
@@ -222,17 +242,15 @@ module FuseFS
|
|
222
242
|
end
|
223
243
|
|
224
244
|
unless fh.raw
|
225
|
-
|
226
245
|
if fh.rdonly?
|
227
246
|
fh.contents = @root.read_file(path)
|
228
|
-
elsif fh.
|
247
|
+
elsif fh.writing?
|
229
248
|
unless @root.can_write?(path)
|
230
249
|
raise Errno::EACCES.new(path)
|
231
250
|
end
|
232
251
|
|
233
252
|
if @created_files.has_key?(path)
|
234
|
-
|
235
|
-
fh.contents = "";
|
253
|
+
fh.create
|
236
254
|
else
|
237
255
|
if fh.rdwr? || fh.append?
|
238
256
|
fh.contents = @root.read_file(path)
|
@@ -245,9 +263,9 @@ module FuseFS
|
|
245
263
|
raise Errno::ENOPERM.new(path)
|
246
264
|
end
|
247
265
|
end
|
266
|
+
|
248
267
|
#If we get this far, save our filehandle in the FUSE structure
|
249
268
|
ffi.fh=fh
|
250
|
-
|
251
269
|
end
|
252
270
|
|
253
271
|
def read(ctx,path,size,offset,ffi)
|
@@ -285,7 +303,21 @@ module FuseFS
|
|
285
303
|
else
|
286
304
|
return fh.write(offset,buf)
|
287
305
|
end
|
306
|
+
end
|
307
|
+
|
308
|
+
def fsync(ctx,path,datasync,ffi)
|
309
|
+
return wrap_context(ctx,__method__,path,datasync,ffi) if ctx
|
310
|
+
fh = ffi.fh
|
288
311
|
|
312
|
+
if fh && fh.raw
|
313
|
+
if FuseFS::RFUSEFS_COMPATIBILITY
|
314
|
+
@root.raw_sync(path,datasync != 0,fh.raw)
|
315
|
+
else
|
316
|
+
@root.raw_sync(path,datasync != 0)
|
317
|
+
end
|
318
|
+
else
|
319
|
+
flush(nil,path,ffi)
|
320
|
+
end
|
289
321
|
end
|
290
322
|
|
291
323
|
def flush(ctx,path,ffi)
|
@@ -298,13 +330,11 @@ module FuseFS
|
|
298
330
|
#if it was created with mknod it now exists in the filesystem...
|
299
331
|
@created_files.delete(path)
|
300
332
|
end
|
301
|
-
|
302
333
|
end
|
303
334
|
|
304
335
|
def release(ctx,path,ffi)
|
305
336
|
return wrap_context(ctx,__method__,path,ffi) if ctx
|
306
337
|
|
307
|
-
flush(nil,path,ffi)
|
308
338
|
|
309
339
|
fh = ffi.fh
|
310
340
|
if fh && fh.raw
|
@@ -313,8 +343,10 @@ module FuseFS
|
|
313
343
|
else
|
314
344
|
@root.raw_close(path)
|
315
345
|
end
|
346
|
+
else
|
347
|
+
# Probably just had flush called, but no harm calling it again
|
348
|
+
flush(nil,path,ffi)
|
316
349
|
end
|
317
|
-
|
318
350
|
end
|
319
351
|
|
320
352
|
#def chmod(path,mode)
|
@@ -327,9 +359,7 @@ module FuseFS
|
|
327
359
|
return wrap_context(ctx,__method__,path,actime,modtime) if ctx
|
328
360
|
|
329
361
|
#Touch...
|
330
|
-
if @root.respond_to?(:touch)
|
331
|
-
@root.touch(path,modtime)
|
332
|
-
end
|
362
|
+
@root.touch(path,modtime) if @root.respond_to?(:touch)
|
333
363
|
end
|
334
364
|
|
335
365
|
def unlink(ctx,path)
|
@@ -338,7 +368,10 @@ module FuseFS
|
|
338
368
|
unless @root.can_delete?(path)
|
339
369
|
raise Errno::EACCES.new(path)
|
340
370
|
end
|
341
|
-
|
371
|
+
|
372
|
+
@adj_size = @adj_size - size(path)
|
373
|
+
|
374
|
+
@created_files.delete(path)
|
342
375
|
@root.delete(path)
|
343
376
|
end
|
344
377
|
|
@@ -371,8 +404,8 @@ module FuseFS
|
|
371
404
|
#def link(path,as)
|
372
405
|
#end
|
373
406
|
|
374
|
-
def setxattr(ctx,path,name,value)
|
375
|
-
return wrap_context(ctx,__method__,path,name,value) if ctx
|
407
|
+
def setxattr(ctx,path,name,value,flags)
|
408
|
+
return wrap_context(ctx,__method__,path,name,value,flags) if ctx
|
376
409
|
@root.xattr(path)[name]=value
|
377
410
|
end
|
378
411
|
|
@@ -399,12 +432,46 @@ module FuseFS
|
|
399
432
|
#def releasedir(path,ffi)
|
400
433
|
#end
|
401
434
|
|
435
|
+
#
|
402
436
|
#def fsyncdir(path,meta,ffi)
|
403
437
|
#end
|
404
438
|
|
405
439
|
# Some random numbers to show with df command
|
406
|
-
#
|
407
|
-
#
|
440
|
+
# bsize preferred block size = 1K unless @root provides something different
|
441
|
+
# frsize = bsize (but apparently unused)
|
442
|
+
# blocks = total number of blocks
|
443
|
+
# bfree = number of free blocks
|
444
|
+
# bavail = bfree if mounted -o allow_other
|
445
|
+
# files = count of all files
|
446
|
+
# ffree - count of free file inode
|
447
|
+
#
|
448
|
+
def statfs(ctx,path)
|
449
|
+
return wrap_context(ctx,__method__,path) if ctx
|
450
|
+
block_size = 1024
|
451
|
+
|
452
|
+
stats = @root.statistics(path)
|
453
|
+
case stats
|
454
|
+
when Array
|
455
|
+
used_space, used_files, total_space, total_files = stats
|
456
|
+
used_files ||= 0
|
457
|
+
used_space ||= 0
|
458
|
+
total_files ||= used_files
|
459
|
+
total_space ||= used_space
|
460
|
+
result = RFuse::StatVfs.new(
|
461
|
+
"bsize" => block_size,
|
462
|
+
"frsize" => block_size,
|
463
|
+
"blocks" => total_space / block_size,
|
464
|
+
"bfree" => (total_space - used_space)/block_size,
|
465
|
+
"bavail" => (total_space - used_space)/block_size,
|
466
|
+
"files" => total_files,
|
467
|
+
"ffree" => (total_files - used_files)
|
468
|
+
)
|
469
|
+
return result
|
470
|
+
else
|
471
|
+
#expected to quack like rfuse:statvfs
|
472
|
+
return stats
|
473
|
+
end
|
474
|
+
end
|
408
475
|
|
409
476
|
def mounted()
|
410
477
|
@root.mounted()
|
@@ -431,5 +498,9 @@ module FuseFS
|
|
431
498
|
self.class.context(ctx) { send(method,nil,*args) }
|
432
499
|
end
|
433
500
|
|
501
|
+
def size(path)
|
502
|
+
@root.respond_to?(:size) ? @root.size(path) : @root.read_file(path).length
|
503
|
+
end
|
504
|
+
|
434
505
|
end #class RFuseFS
|
435
506
|
end #Module FuseFS
|
data/lib/fusefs/metadir.rb
CHANGED
@@ -21,9 +21,15 @@ module FuseFS
|
|
21
21
|
|
22
22
|
DEFAULT_FS = FuseDir.new()
|
23
23
|
|
24
|
-
|
24
|
+
# @return [StatsHelper] helper for filesystem accounting (df etc)
|
25
|
+
attr_reader :stats
|
26
|
+
|
27
|
+
def initialize(stats = nil)
|
25
28
|
@subdirs = Hash.new(nil)
|
26
29
|
@files = Hash.new(nil)
|
30
|
+
@xattr = Hash.new() { |h,k| h[k] = Hash.new }
|
31
|
+
@stats = stats || StatsHelper.new()
|
32
|
+
@stats.adjust(0,1)
|
27
33
|
end
|
28
34
|
|
29
35
|
def split_path(path)
|
@@ -57,6 +63,13 @@ module FuseFS
|
|
57
63
|
end
|
58
64
|
end
|
59
65
|
|
66
|
+
# Extended attributes
|
67
|
+
def xattr(path)
|
68
|
+
pathmethod(:xattr,path) do | path |
|
69
|
+
@xattr[path]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
60
73
|
def read_file(path)
|
61
74
|
pathmethod(:read_file,path) do |filename|
|
62
75
|
@files[filename].to_s
|
@@ -78,6 +91,14 @@ module FuseFS
|
|
78
91
|
|
79
92
|
def write_to(path,contents)
|
80
93
|
pathmethod(:write_to,path,contents) do |filename, filecontents |
|
94
|
+
adj_size = filecontents.to_s.length
|
95
|
+
adj_nodes = 1
|
96
|
+
if @files.has_key?(filename)
|
97
|
+
adj_size = adj_size - @files[filename].to_s.length
|
98
|
+
adj_nodes = 0
|
99
|
+
end
|
100
|
+
@stats.adjust(adj_size,adj_nodes)
|
101
|
+
|
81
102
|
@files[filename] = filecontents
|
82
103
|
end
|
83
104
|
end
|
@@ -91,7 +112,8 @@ module FuseFS
|
|
91
112
|
|
92
113
|
def delete(path)
|
93
114
|
pathmethod(:delete,path) do |filename|
|
94
|
-
@files.delete(filename)
|
115
|
+
contents = @files.delete(filename)
|
116
|
+
@stats.adjust(-contents.to_s.length,-1)
|
95
117
|
end
|
96
118
|
end
|
97
119
|
|
@@ -104,7 +126,7 @@ module FuseFS
|
|
104
126
|
|
105
127
|
def mkdir(path,dir=nil)
|
106
128
|
pathmethod(:mkdir,path,dir) do | dirname,dirobj |
|
107
|
-
dirobj ||= MetaDir.new
|
129
|
+
dirobj ||= MetaDir.new(@stats)
|
108
130
|
@subdirs[dirname] = dirobj
|
109
131
|
end
|
110
132
|
end
|
@@ -119,6 +141,7 @@ module FuseFS
|
|
119
141
|
def rmdir(path)
|
120
142
|
pathmethod(:rmdir,path) do |dirname|
|
121
143
|
@subdirs.delete(dirname)
|
144
|
+
@stats.adjust(0,-1)
|
122
145
|
end
|
123
146
|
end
|
124
147
|
|
@@ -135,6 +158,8 @@ module FuseFS
|
|
135
158
|
if @files.has_key?(from_base)
|
136
159
|
return false unless can_delete?(from_base) && to_fusefs.can_write?(to_path)
|
137
160
|
to_fusefs.write_to(to_path,@files[from_base])
|
161
|
+
to_fusefs.xattr(to_path).merge!(@xattr[from_base])
|
162
|
+
@xattr.delete(from_base)
|
138
163
|
@files.delete(from_base)
|
139
164
|
elsif @subdirs.has_key?(from_base)
|
140
165
|
# we don't check can_rmdir? because that would prevent us
|
@@ -142,7 +167,11 @@ module FuseFS
|
|
142
167
|
return false unless mount_user? && to_fusefs.can_mkdir?(to_path)
|
143
168
|
begin
|
144
169
|
to_fusefs.mkdir(to_path,@subdirs[from_base])
|
170
|
+
to_fusefs.xattr(to_path).merge!(@xattr[from_base])
|
171
|
+
@xattr.delete(from_base)
|
145
172
|
@subdirs.delete(from_base)
|
173
|
+
@stats.adjust(0,-1)
|
174
|
+
return true
|
146
175
|
rescue ArgumentError
|
147
176
|
# to_rest does not support mkdir with an arbitrary object
|
148
177
|
return false
|
@@ -188,7 +217,20 @@ module FuseFS
|
|
188
217
|
end
|
189
218
|
end
|
190
219
|
|
191
|
-
|
220
|
+
# path is ignored? - recursively calculate for all subdirs - but cache and then rely on fuse to keep count
|
221
|
+
def statistics(path)
|
222
|
+
pathmethod(:statistics,path) do |stats_path|
|
223
|
+
if @subdirs.has_key?(stats_path)
|
224
|
+
#unlike all the other functions where this metadir applies
|
225
|
+
#the function to @subdirs - we need to pass it on
|
226
|
+
@subdirs[stats_path].statistics("/")
|
227
|
+
else
|
228
|
+
@stats.to_statistics
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
default_methods = FuseDir.public_instance_methods.select { |m|
|
192
234
|
![:mounted,:unmounted].include?(m) &&
|
193
235
|
!self.public_method_defined?(m) && FuseDir.instance_method(m).owner == FuseDir
|
194
236
|
}
|