rfusefs 1.1.0 → 1.1.1.rc20201114.37

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 660236e6a6dc3a765736e122e3e08d449685839332ec45d29afc0dc8a7420a00
4
- data.tar.gz: 8e5bcffceda937d6e5e35fca6a0f6f407a85160e867167ff21a4fd42aa917496
3
+ metadata.gz: 56f162c21060ca29f73f4af1323d4e5913199fba944f63ae3de239467ef03898
4
+ data.tar.gz: 55e32321f1df3ad1c9f85f17b8b1a88fd2038d0fb713e7ba2996dc1f4e7c1546
5
5
  SHA512:
6
- metadata.gz: 915e78c9df89edfa7b0bd4155f3348e9ae80a9e0063dfb5eaf1210c4bd5ee0646cf6a417e511ec5304442d033c0bc17a81907855bb977f043be03e77ab54c287
7
- data.tar.gz: 9f871748305c9c7da9b58c2ccdb161dd065fa1a029ad4037ccb8b591f9da4bb9199a40d2d33e7fa2c81ae826bbbd6fa84d5cf4ff1ecefcfecaf901ca3fe07315
6
+ metadata.gz: 02620a1786ab417c1653596a2ee952235b8a2fd14d92ee05d089514e2420e15d15844c7991bc9b08f30f889dac5c8871949b0bd718515f1ac9a2a998f7fec390
7
+ data.tar.gz: 3cec6f71911dfbf1dbdc65424cf2a61c13dd96b4e2226396afa1c7684e6981e1f38663dd1eb44df79746d9b1c5994def790601dddbccf8dbed4cfe01f318349a
data/CHANGES.md CHANGED
@@ -1,4 +1,7 @@
1
-
1
+ 1.1.1 / 2020-11
2
+ ---------------
3
+ * fix gemspec
4
+
2
5
  1.1.0 / 2020-10
3
6
  ---------------
4
7
 
@@ -0,0 +1,328 @@
1
+ module FuseFS
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
+
60
+ # This class is equivalent to using Object.new() as the virtual directory
61
+ # for target for {FuseFS.start}. It exists primarily to document the API
62
+ # but can also be used as a superclass for your filesystem providing sensible defaults
63
+ #
64
+ # == Method call sequences
65
+ #
66
+ # === Stat (getattr)
67
+ #
68
+ # FUSE itself will generally stat referenced files and validate the results
69
+ # before performing any file/directory operations so this sequence is called
70
+ # very often
71
+ #
72
+ # 1. {#directory?} is checked first
73
+ # * {#can_write?} OR {#can_mkdir?} with .\_rfusefs_check\_ to determine write permissions
74
+ # * {#times} is called to determine atime,mtime,ctime info for the directory
75
+ #
76
+ # 2. {#file?} is checked next
77
+ # * {#can_write?}, {#executable?}, {#size}, {#times} are called to fill out the details
78
+ #
79
+ # 3. otherwise we tell FUSE that the path does not exist
80
+ #
81
+ # === List directory
82
+ #
83
+ # FUSE confirms the path is a directory (via stat above) before we call {#contents}
84
+ #
85
+ # FUSE will generally go on to stat each directory entry in the results
86
+ #
87
+ # === Reading files
88
+ #
89
+ # FUSE confirms path is a file before we call {#read_file}
90
+ #
91
+ # For fine control of file access see {#raw_open}, {#raw_read}, {#raw_close}
92
+ #
93
+ # === Writing files
94
+ #
95
+ # FUSE confirms path for the new file is a directory
96
+ #
97
+ # * {#can_write?} is checked at file open
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}
101
+ #
102
+ # === Deleting files
103
+ #
104
+ # FUSE confirms path is a file before we call {#can_delete?} then {#delete}
105
+ #
106
+ # === Creating directories
107
+ #
108
+ # FUSE confirms parent is a directory before we call {#can_mkdir?} then {#mkdir}
109
+ #
110
+ # === Deleting directories
111
+ #
112
+ # FUSE confirms path is a directory before we call {#can_rmdir?} then {#rmdir}
113
+ #
114
+ # === Renaming files and directories
115
+ #
116
+ # FUSE confirms the rename is valid (eg. not renaming a directory to a file)
117
+ #
118
+ # * Try {#rename} to see if the virtual directory wants to handle this itself
119
+ # * If rename returns false/nil then we try to copy/delete (files only) ie.
120
+ # * {#file?}(from), {#can_write?}(to), {#can_delete?}(from) and if all true
121
+ # * {#read_file}(from), {#write_to}(to), {#delete}(from)
122
+ # * otherwise reject the rename
123
+ #
124
+ # === Signals
125
+ #
126
+ # The filesystem can handle a signal by providing a `sig<name>` method. eg 'sighup'
127
+ # {#sigint} and {#sigterm} are handled by default to provide a means to exit the filesystem
128
+ class FuseDir
129
+
130
+ # @!method sigint()
131
+ # @return [void]
132
+ # Handle the INT signal and exit the filesystem
133
+
134
+ # @!method sigterm()
135
+ # @return [void]
136
+ # Handle the TERM signal and exit the filesystem
137
+
138
+ INIT_TIMES = Array.new(3,0)
139
+
140
+ # base,rest = split_path(path)
141
+ # @return [Array<String,String>] base,rest. base is the first directory in
142
+ # path, and rest is nil> or the remaining path.
143
+ # Typically if rest is not nil? you should
144
+ # recurse the paths
145
+ def split_path(path)
146
+ cur, *rest = path.scan(/[^\/]+/)
147
+ if rest.empty?
148
+ [ cur, nil ]
149
+ else
150
+ [ cur, File::SEPARATOR + File.join(rest) ]
151
+ end
152
+ end
153
+
154
+ # base,*rest = scan_path(path)
155
+ # @return [Array<String>] all directory and file elements in path. Useful
156
+ # when encapsulating an entire fs into one object
157
+ def scan_path(path)
158
+ path.scan(/[^\/]+/)
159
+ end
160
+
161
+ # @abstract FuseFS api
162
+ # @return [Boolean] true if path is a directory
163
+ def directory?(path);return false;end
164
+
165
+ # @abstract FuseFS api
166
+ # @return [Boolean] true if path is a file
167
+ def file?(path);end
168
+
169
+ # @abstract FuseFS api
170
+ # @return [Array<String>] array of file and directory names within path
171
+ def contents(path);return [];end
172
+
173
+ # @abstract FuseFS api
174
+ # @return [Boolean] true if path is an executable file
175
+ def executable?(path);return false;end
176
+
177
+ # File size
178
+ # @abstract FuseFS api
179
+ # @return [Integer] the size in byte of a file (lots of applications rely on this being accurate )
180
+ def size(path); read_file(path).length ;end
181
+
182
+ # File time information. RFuseFS extension.
183
+ # @abstract FuseFS api
184
+ # @return [Array<Integer, Time>] a 3 element array [ atime, mtime. ctime ] (good for rsync etc)
185
+ def times(path);return INIT_TIMES;end
186
+
187
+ # @abstract FuseFS api
188
+ # @return [String] the contents of the file at path
189
+ def read_file(path);return "";end
190
+
191
+ # @abstract FuseFS api
192
+ # @return [Boolean] true if the user can write to file at path
193
+ def can_write?(path);return false;end
194
+
195
+ # Write the contents of str to file at path
196
+ # @abstract FuseFS api
197
+ # @return [void]
198
+ def write_to(path,str);end
199
+
200
+ # @abstract FuseFS api
201
+ # @return [Boolean] true if the user can delete the file at path
202
+ def can_delete?(path);return false;end
203
+
204
+ # Delete the file at path
205
+ # @abstract FuseFS api
206
+ # @return [void]
207
+ def delete(path);end
208
+
209
+ # @abstract FuseFS api
210
+ # @return [Boolean] true if user can make a directory at path
211
+ def can_mkdir?(path);return false;end
212
+
213
+ # Make a directory at path
214
+ # @abstract FuseFS api
215
+ # @return [void]
216
+ def mkdir(path);end
217
+
218
+ # @abstract FuseFS api
219
+ # @return [Boolean] true if user can remove a directory at path
220
+ def can_rmdir?(path);return false;end
221
+
222
+ # Remove the directory at path
223
+ # @abstract FuseFS api
224
+ # @return [void]
225
+ def rmdir(path);end
226
+
227
+ # Neat toy. Called when a file is touched or has its timestamp explicitly modified
228
+ # @abstract FuseFS api
229
+ # @return [void]
230
+ def touch(path,modtime);end
231
+
232
+ # Move a file or directory.
233
+ # @abstract FuseFS api
234
+ # @return [Boolean] true to indicate the rename has been handled,
235
+ # otherwise will fallback to copy/delete
236
+ def rename(from_path,to_path);end
237
+
238
+ # Raw file access
239
+ # @abstract FuseFS api
240
+ # @param mode [String] "r","w" or "rw", with "a" if file is opened for append
241
+ # @param rfusefs [Boolean] will be "true" if RFuseFS extensions are available
242
+ # @return [nil] to indicate raw operations are not implemented
243
+ # @return [Object] a filehandle
244
+ # Under RFuseFS this object will be passed back in to the other raw
245
+ # methods as the optional parameter _raw_
246
+ #
247
+ def raw_open(path,mode,rfusefs = nil);end
248
+
249
+ # RFuseFS extension.
250
+ # @abstract FuseFS api
251
+ #
252
+ # @overload raw_truncate(path,offset,raw)
253
+ # Truncate an open file to offset bytes
254
+ # @param [String] path
255
+ # @param [Integer] offset
256
+ # @param [Object] raw the filehandle returned from {#raw_open}
257
+ # @return [void]
258
+ #
259
+ # @overload raw_truncate(path,offset)
260
+ # Optionally truncate a file to offset bytes directly
261
+ # @param [String] path
262
+ # @param [Integer] offset
263
+ # @return [Boolean]
264
+ # if truncate has been performed, otherwise the truncation will be performed with {#read_file} and {#write_to}
265
+ #
266
+ def raw_truncate(path,offset,raw=nil);end
267
+
268
+ # Read _sz_ bytes from file at path (or filehandle raw) starting at offset off
269
+ #
270
+ # @param [String] path
271
+ # @param [Integer] offset
272
+ # @param [Integer] size
273
+ # @param [Object] raw the filehandle returned by {#raw_open}
274
+ # @abstract FuseFS api
275
+ # @return [String] _sz_ bytes contents from file at path (or filehandle raw) starting at offset off
276
+ def raw_read(path,offset,size,raw=nil);end
277
+
278
+ # Write _sz_ bytes from file at path (or filehandle raw) starting at offset off
279
+ # @abstract FuseFS api
280
+ # @return [void]
281
+ def raw_write(path,off,sz,buf,raw=nil);end
282
+
283
+
284
+ # Sync buffered data to your filesystem
285
+ # @param [String] path
286
+ # @param [Boolean] datasync only sync user data, not metadata
287
+ # @param [Object] raw the filehandle return by {#raw_open}
288
+ def raw_sync(path,datasync,raw=nil);end
289
+
290
+ # Close the file previously opened at path (or filehandle raw)
291
+ # @abstract FuseFS api
292
+ # @return [void]
293
+ def raw_close(path,raw=nil);end
294
+
295
+ # RFuseFS extension.
296
+ # Extended attributes.
297
+ # @param [String] path
298
+ # @return [Hash] extended attributes for this path.
299
+ # The returned object will be manipulated directly using :[] :[]=,, :keys and :delete
300
+ # so the default (a new empty hash on every call) will not retain attributes that are set
301
+ # @abstract FuseFS api
302
+ def xattr(path); {} ; end
303
+
304
+ # RFuseFS extensions.
305
+ # File system statistics
306
+ # @param [String] path
307
+ # @return [Array<Integer>] the statistics
308
+ # used_space (in bytes), used_files, max_space, max_files
309
+ # See {StatsHelper}
310
+ # @return [RFuse::StatVfs] or raw statistics
311
+ # @abstract FuseFS api
312
+ def statistics(path); [0,0,0,0]; end
313
+
314
+ # RFuseFS extension.
315
+ # Called when the filesystem is mounted
316
+ # @return [void]
317
+ def mounted();end
318
+
319
+ # RFuseFS extension.
320
+ # Called when the filesystem is unmounted
321
+ # @return [void]
322
+ def unmounted();end
323
+
324
+
325
+ end
326
+
327
+ DEFAULT_FS = FuseDir.new()
328
+ end
@@ -0,0 +1,540 @@
1
+ # RFuseFS - FuseFS over RFuse
2
+ require 'rfuse'
3
+ require 'fcntl'
4
+ require 'forwardable'
5
+
6
+ module FuseFS
7
+ #Which raw api should we use?
8
+ RFUSEFS_COMPATIBILITY = true unless FuseFS.const_defined?(:RFUSEFS_COMPATIBILITY)
9
+
10
+ class FileHandle
11
+ @@fh = 0
12
+ attr_reader :id,:flags,:path
13
+ attr_accessor :raw,:contents
14
+ def initialize(path,flags)
15
+ @id = (@@fh += 1)
16
+ @flags = flags
17
+ @path = path
18
+ @modified = false
19
+ @contents = ""
20
+ @size = 0
21
+ end
22
+
23
+ def read(offset,size)
24
+ contents[offset,size]
25
+ end
26
+
27
+ def create
28
+ @contents = ""
29
+ @modified = true
30
+ end
31
+
32
+ def write(offset,data)
33
+ # TODO: why append?
34
+ if append? || offset >= contents.length
35
+ #ignore offset
36
+ #TODO: should this zero fill?
37
+ contents << data
38
+ else
39
+ contents[offset,data.length]=data
40
+ end
41
+ @modified = true
42
+ return data.length
43
+ end
44
+
45
+ def flush
46
+ @modified = false
47
+ contents
48
+ end
49
+
50
+ def modified?
51
+ @modified
52
+ end
53
+
54
+ def accmode
55
+ flags & Fcntl::O_ACCMODE
56
+ end
57
+
58
+ def rdwr?
59
+ accmode == Fcntl::O_RDWR
60
+ end
61
+
62
+ def wronly?
63
+ accmode == Fcntl::O_WRONLY
64
+ end
65
+
66
+ def rdonly?
67
+ accmode == Fcntl::O_RDONLY
68
+ end
69
+
70
+ def append?
71
+ writing? && (flags & Fcntl::O_APPEND != 0)
72
+ end
73
+
74
+ def reading?
75
+ rdonly? || rdwr?
76
+ end
77
+
78
+ def writing?
79
+ wronly? || rdwr?
80
+ end
81
+
82
+ def raw_mode
83
+ mode_str = case accmode
84
+ when Fcntl::O_RDWR; "rw"
85
+ when Fcntl::O_RDONLY; "r"
86
+ when Fcntl::O_WRONLY; "w"
87
+ end
88
+
89
+ mode_str << "a" if append?
90
+ return mode_str
91
+ end
92
+ end
93
+
94
+ # The real RFuse::Fuse object used by FuseFS
95
+ class Fuse < RFuse::FuseDelegator
96
+ def initialize(fs,*args)
97
+ @fs = fs
98
+ super
99
+ end
100
+
101
+ def run
102
+ begin
103
+ @fs.mounted
104
+ super
105
+ ensure
106
+ @fs.unmounted
107
+ end if mounted?
108
+ end
109
+
110
+ # Implements RFuseFS via RFuse
111
+ # The path supplied to these methods is generally validated by FUSE itself
112
+ # with a prior "getattr" call so we do not revalidate here.
113
+ # http://sourceforge.net/apps/mediawiki/fuse/index.php?title=FuseInvariants
114
+ class Root
115
+
116
+ # We forward some methods
117
+ include Forwardable
118
+
119
+ CHECK_FILE="/._rfuse_check_"
120
+
121
+ def initialize(root)
122
+ @root = root
123
+ @created_files = { }
124
+
125
+ # Keep track of changes to file counts and sizes made via Fuse - for #statfs
126
+ @adj_nodes = 0
127
+ @adj_size = 0
128
+
129
+ #Define method missing for our filesystem
130
+ #so we can just call all the API methods as required.
131
+ def @root.method_missing(method,*args)
132
+ # our filesystem might implement method_missing itself
133
+ super
134
+ rescue NoMethodError
135
+ DEFAULT_FS.send(method,*args)
136
+ end
137
+
138
+ # Define sig<name> methods to handle signals
139
+ sigmethods = Signal.list.keys.map { |sn| "sig#{sn.downcase}".to_sym }.select { |sm| @root.respond_to?(sm) }
140
+ def_delegators(:@root,*sigmethods)
141
+ end
142
+
143
+ def readdir(ctx,path,filler,offset,ffi)
144
+
145
+ return wrap_context(ctx,__method__,path,filler,offset,ffi) if ctx
146
+
147
+ #Always have "." and ".."
148
+ filler.push(".",nil,0)
149
+ filler.push("..",nil,0)
150
+
151
+ files = @root.contents(path)
152
+
153
+ files.each do | filename |
154
+ filler.push(filename,nil,0)
155
+ end
156
+
157
+ end
158
+
159
+ def getattr(ctx,path)
160
+
161
+ return wrap_context(ctx,__method__,path) if ctx
162
+
163
+ uid = Process.uid
164
+ gid = Process.gid
165
+
166
+ if path == "/" || @root.directory?(path)
167
+ #set "w" flag based on can_mkdir? || can_write? to path + "/._rfuse_check"
168
+ write_test_path = (path == "/" ? "" : path) + CHECK_FILE
169
+
170
+ mode = (@root.can_mkdir?(write_test_path) || @root.can_write?(write_test_path)) ? 0777 : 0555
171
+ atime,mtime,ctime = @root.times(path)
172
+ #nlink is set to 1 because apparently this makes find work.
173
+ return RFuse::Stat.directory(mode,{ :uid => uid, :gid => gid, :nlink => 1, :atime => atime, :mtime => mtime, :ctime => ctime })
174
+ elsif @created_files.has_key?(path)
175
+ return @created_files[path]
176
+ elsif @root.file?(path)
177
+ #Set mode from can_write and executable
178
+ mode = 0444
179
+ mode |= 0222 if @root.can_write?(path)
180
+ mode |= 0111 if @root.executable?(path)
181
+ size = size(path)
182
+ atime,mtime,ctime = @root.times(path)
183
+ return RFuse::Stat.file(mode,{ :uid => uid, :gid => gid, :size => size, :atime => atime, :mtime => mtime, :ctime => ctime })
184
+ else
185
+ raise Errno::ENOENT.new(path)
186
+ end
187
+
188
+ end #getattr
189
+
190
+ def mkdir(ctx,path,mode)
191
+
192
+ return wrap_context(ctx,__method__,path,mode) if ctx
193
+
194
+ unless @root.can_mkdir?(path)
195
+ raise Errno::EACCES.new(path)
196
+ end
197
+
198
+ @root.mkdir(path)
199
+ @adj_nodes += 1
200
+ end #mkdir
201
+
202
+ def mknod(ctx,path,mode,major,minor)
203
+
204
+ return wrap_context(ctx,__method__,path,mode,major,minor) if ctx
205
+
206
+ unless ((RFuse::Stat::S_IFMT & mode) == RFuse::Stat::S_IFREG ) && @root.can_write?(path)
207
+ raise Errno::EACCES.new(path)
208
+ end
209
+
210
+ now = Time.now
211
+ stat = RFuse::Stat.file(mode,{ :uid => Process.uid, :gid => Process.gid, :atime => now, :mtime => now, :ctime => now })
212
+
213
+ @created_files[path] = stat
214
+ @adj_nodes += 1
215
+ end #mknod
216
+
217
+ #ftruncate - eg called after opening a file for write without append
218
+ #sizes are adjusted at file close
219
+ def ftruncate(ctx,path,offset,ffi)
220
+
221
+ return wrap_context(ctx,__method__,path,offset,ffi) if ctx
222
+
223
+ fh = ffi.fh
224
+
225
+ if fh.raw
226
+ @root.raw_truncate(path,offset,fh.raw)
227
+ if (offset <= 0)
228
+ fh.contents = ""
229
+ else
230
+ fh.contents = fh.contents[0..offset]
231
+ end
232
+ end
233
+ end
234
+
235
+ #truncate a file outside of open files
236
+ def truncate(ctx,path,offset)
237
+ return wrap_context(ctx,__method__,path,offset) if ctx
238
+
239
+ unless @root.can_write?(path)
240
+ raise Errno::EACESS.new(path)
241
+ end
242
+
243
+ current_size = size(path)
244
+ unless @root.raw_truncate(path,offset)
245
+ contents = @root.read_file(path)
246
+ if (offset <= 0)
247
+ @root.write_to(path,"")
248
+ elsif offset < contents.length
249
+ @root.write_to(path,contents[0..offset] )
250
+ end
251
+ end
252
+ @adj_size = @adj_size - current_size + (offset <= 0 ? 0 : offset)
253
+ end #truncate
254
+
255
+ # Open. Create a FileHandler and store in fuse file info
256
+ # This will be returned to us in read/write
257
+ # No O_CREATE (mknod first?), no O_TRUNC (truncate first)
258
+ def open(ctx,path,ffi)
259
+ return wrap_context(ctx,__method__,path,ffi) if ctx
260
+ fh = FileHandle.new(path,ffi.flags)
261
+
262
+ #Save the value return from raw_open to be passed back in raw_read/write etc..
263
+ if (FuseFS::RFUSEFS_COMPATIBILITY)
264
+ fh.raw = @root.raw_open(path,fh.raw_mode,true)
265
+ else
266
+ fh.raw = @root.raw_open(path,fh.raw_mode)
267
+ end
268
+
269
+ unless fh.raw
270
+ if fh.rdonly?
271
+ fh.contents = @root.read_file(path)
272
+ elsif fh.writing?
273
+ unless @root.can_write?(path)
274
+ raise Errno::EACCES.new(path)
275
+ end
276
+
277
+ if @created_files.has_key?(path)
278
+ fh.create
279
+ else
280
+ if fh.rdwr? || fh.append?
281
+ fh.contents = @root.read_file(path)
282
+ else #wronly && !append
283
+ #We should get a truncate 0, but might as well play it safe
284
+ fh.contents = ""
285
+ end
286
+ end
287
+ else
288
+ raise Errno::ENOPERM.new(path)
289
+ end
290
+ end
291
+
292
+ #If we get this far, save our filehandle in the FUSE structure
293
+ ffi.fh=fh
294
+ end
295
+
296
+ def read(ctx,path,size,offset,ffi)
297
+ return wrap_context(ctx,__method__,path,size,offset,ffi) if ctx
298
+
299
+ fh = ffi.fh
300
+
301
+ if fh.raw
302
+ if FuseFS::RFUSEFS_COMPATIBILITY
303
+ return @root.raw_read(path,offset,size,fh.raw)
304
+ else
305
+ return @root.raw_read(path,offset,size)
306
+ end
307
+ elsif offset >= 0
308
+ return fh.read(offset,size)
309
+ else
310
+ #TODO: Raise? what does a negative offset mean
311
+ return ""
312
+ end
313
+ rescue EOFError
314
+ return ""
315
+ end
316
+
317
+ def write(ctx,path,buf,offset,ffi)
318
+ return wrap_context(ctx,__method__,path,buf,offset,ffi) if ctx
319
+ fh = ffi.fh
320
+
321
+ if fh.raw
322
+ if FuseFS::RFUSEFS_COMPATIBILITY
323
+ return @root.raw_write(path,offset,buf.length,buf,fh.raw)
324
+ else
325
+ @root.raw_write(path,offset,buf.length,buf)
326
+ return buf.length
327
+ end
328
+ else
329
+ return fh.write(offset,buf)
330
+ end
331
+ end
332
+
333
+ def fsync(ctx,path,datasync,ffi)
334
+ return wrap_context(ctx,__method__,path,datasync,ffi) if ctx
335
+ fh = ffi.fh
336
+
337
+ if fh && fh.raw
338
+ if FuseFS::RFUSEFS_COMPATIBILITY
339
+ @root.raw_sync(path,datasync != 0,fh.raw)
340
+ else
341
+ @root.raw_sync(path,datasync != 0)
342
+ end
343
+ else
344
+ flush(nil,path,ffi)
345
+ end
346
+ end
347
+
348
+ def flush(ctx,path,ffi)
349
+ return wrap_context(ctx,__method__,path,ffi) if ctx
350
+ fh = ffi.fh
351
+
352
+ if fh && !fh.raw && fh.modified?
353
+ #write contents to the file and mark it unmodified
354
+ @root.write_to(path,fh.flush())
355
+ #if it was created with mknod it now exists in the filesystem...
356
+ @created_files.delete(path)
357
+ end
358
+ end
359
+
360
+ def release(ctx,path,ffi)
361
+ return wrap_context(ctx,__method__,path,ffi) if ctx
362
+
363
+
364
+ fh = ffi.fh
365
+ if fh && fh.raw
366
+ if (FuseFS::RFUSEFS_COMPATIBILITY)
367
+ @root.raw_close(path,fh.raw)
368
+ else
369
+ @root.raw_close(path)
370
+ end
371
+ # if was handled as raw, then assume the file has now been created (or not)
372
+ @created_files.delete(path)
373
+ else
374
+ # Probably just had flush called, but no harm calling it again
375
+ flush(nil,path,ffi)
376
+ end
377
+ end
378
+
379
+ #def chmod(path,mode)
380
+ #end
381
+
382
+ #def chown(path,uid,gid)
383
+ #end
384
+
385
+ def utime(ctx,path,actime,modtime)
386
+ return wrap_context(ctx,__method__,path,actime,modtime) if ctx
387
+
388
+ #Touch...
389
+ @root.touch(path,modtime) if @root.respond_to?(:touch)
390
+ end
391
+
392
+ def unlink(ctx,path)
393
+ return wrap_context(ctx,__method__,path) if ctx
394
+
395
+ unless @root.can_delete?(path)
396
+ raise Errno::EACCES.new(path)
397
+ end
398
+
399
+ @adj_size = @adj_size - size(path)
400
+
401
+ @created_files.delete(path)
402
+ @root.delete(path)
403
+ end
404
+
405
+ def rmdir(ctx,path)
406
+ return wrap_context(ctx,__method__,path) if ctx
407
+
408
+ unless @root.can_rmdir?(path)
409
+ raise Errno::EACCES.new(path)
410
+ end
411
+ @root.rmdir(path)
412
+ end
413
+
414
+ #def symlink(path,as)
415
+ #end
416
+
417
+ def rename(ctx,from,to)
418
+ return wrap_context(ctx,__method__,from,to) if ctx
419
+
420
+ if @root.rename(from,to)
421
+ # nothing to do
422
+ elsif @root.file?(from) && @root.can_write?(to) && @root.can_delete?(from)
423
+ contents = @root.read_file(from)
424
+ @root.write_to(to,contents)
425
+ @root.delete(from)
426
+ else
427
+ raise Errno::EACCES.new("Unable to move directory #{from}")
428
+ end
429
+ end
430
+
431
+ #def link(path,as)
432
+ #end
433
+
434
+ def setxattr(ctx,path,name,value,flags)
435
+ return wrap_context(ctx,__method__,path,name,value,flags) if ctx
436
+ @root.xattr(path)[name]=value
437
+ end
438
+
439
+ def getxattr(ctx,path,name)
440
+ return wrap_context(ctx,__method__,path,name) if ctx
441
+ result = @root.xattr(path)[name]
442
+ raise Errno::ENODATA.new("No attribute #{name}") unless result
443
+ result.to_s
444
+ end
445
+
446
+ def listxattr(ctx,path)
447
+ return wrap_context(ctx,__method__,path) if ctx
448
+ @root.xattr(path).keys
449
+ end
450
+
451
+ def removexattr(ctx,path,name)
452
+ return wrap_context(ctx,__method__,path,name) if ctx
453
+ @root.xattr(path).delete(name)
454
+ end
455
+
456
+ #def opendir(path,ffi)
457
+ #end
458
+
459
+ #def releasedir(path,ffi)
460
+ #end
461
+
462
+ #
463
+ #def fsyncdir(path,meta,ffi)
464
+ #end
465
+
466
+ # Some random numbers to show with df command
467
+ # bsize preferred block size = 1K unless @root provides something different
468
+ # frsize = bsize (but apparently unused)
469
+ # blocks = total number of blocks
470
+ # bfree = number of free blocks
471
+ # bavail = bfree if mounted -o allow_other
472
+ # files = count of all files
473
+ # ffree - count of free file inode
474
+ #
475
+ def statfs(ctx,path)
476
+ return wrap_context(ctx,__method__,path) if ctx
477
+ block_size = 1024
478
+
479
+ stats = @root.statistics(path)
480
+ case stats
481
+ when Array
482
+ used_space, used_files, total_space, total_files = stats
483
+ used_files ||= 0
484
+ used_space ||= 0
485
+ total_files ||= used_files
486
+ total_space ||= used_space
487
+ result = RFuse::StatVfs.new(
488
+ "bsize" => block_size,
489
+ "frsize" => block_size,
490
+ "blocks" => total_space / block_size,
491
+ "bfree" => (total_space - used_space)/block_size,
492
+ "bavail" => (total_space - used_space)/block_size,
493
+ "files" => total_files,
494
+ "ffree" => (total_files - used_files)
495
+ )
496
+ return result
497
+ else
498
+ #expected to quack like rfuse:statvfs
499
+ return stats
500
+ end
501
+ end
502
+
503
+ def mounted()
504
+ @root.mounted()
505
+ end
506
+
507
+ def unmounted()
508
+ @root.unmounted()
509
+ end
510
+
511
+ def self.context(ctx,&block)
512
+ begin
513
+ Thread.current[:fusefs_reader_uid] = ctx.uid
514
+ Thread.current[:fusefs_reader_gid] = ctx.gid
515
+ yield
516
+ ensure
517
+ Thread.current[:fusefs_reader_uid] = nil
518
+ Thread.current[:fusefs_reader_gid] = nil
519
+ end
520
+ end
521
+
522
+ # @private - no doc
523
+ def to_s
524
+ "RFuseFS::#{@root}"
525
+ end
526
+
527
+ private
528
+
529
+ def wrap_context(ctx,method,*args)
530
+ self.class.context(ctx) { send(method,nil,*args) }
531
+ end
532
+
533
+ def size(path)
534
+ @root.respond_to?(:size) ? @root.size(path) : @root.read_file(path).length
535
+ end
536
+
537
+ end #class Root
538
+
539
+ end #class Fuse
540
+ end #Module FuseFS