ffi-libfuse 0.0.1.rctest12 → 0.1.0.rc20220550

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +3 -1
  3. data/CHANGES.md +14 -0
  4. data/LICENSE +21 -0
  5. data/README.md +127 -44
  6. data/lib/ffi/accessors.rb +6 -6
  7. data/lib/ffi/boolean_int.rb +27 -0
  8. data/lib/ffi/devt.rb +23 -0
  9. data/lib/ffi/encoding.rb +38 -0
  10. data/lib/ffi/gnu_extensions.rb +1 -1
  11. data/lib/ffi/libfuse/ackbar.rb +3 -3
  12. data/lib/ffi/libfuse/adapter/context.rb +12 -10
  13. data/lib/ffi/libfuse/adapter/fuse2_compat.rb +52 -51
  14. data/lib/ffi/libfuse/adapter/fuse3_support.rb +0 -1
  15. data/lib/ffi/libfuse/adapter/ruby.rb +499 -148
  16. data/lib/ffi/libfuse/adapter/safe.rb +1 -1
  17. data/lib/ffi/libfuse/adapter.rb +1 -2
  18. data/lib/ffi/libfuse/callbacks.rb +1 -1
  19. data/lib/ffi/libfuse/filesystem/accounting.rb +116 -0
  20. data/lib/ffi/libfuse/filesystem/mapped_dir.rb +74 -0
  21. data/lib/ffi/libfuse/filesystem/mapped_files.rb +141 -0
  22. data/lib/ffi/libfuse/filesystem/pass_through_dir.rb +55 -0
  23. data/lib/ffi/libfuse/filesystem/pass_through_file.rb +45 -0
  24. data/lib/ffi/libfuse/filesystem/utils.rb +102 -0
  25. data/lib/ffi/libfuse/filesystem/virtual_dir.rb +306 -0
  26. data/lib/ffi/libfuse/filesystem/virtual_file.rb +94 -0
  27. data/lib/ffi/libfuse/filesystem/virtual_fs.rb +188 -0
  28. data/lib/ffi/libfuse/filesystem/virtual_node.rb +101 -0
  29. data/lib/ffi/libfuse/filesystem.rb +25 -0
  30. data/lib/ffi/libfuse/fuse2.rb +21 -21
  31. data/lib/ffi/libfuse/fuse3.rb +12 -12
  32. data/lib/ffi/libfuse/fuse_args.rb +69 -34
  33. data/lib/ffi/libfuse/fuse_buffer.rb +128 -26
  34. data/lib/ffi/libfuse/fuse_callbacks.rb +1 -5
  35. data/lib/ffi/libfuse/fuse_common.rb +55 -61
  36. data/lib/ffi/libfuse/fuse_config.rb +134 -143
  37. data/lib/ffi/libfuse/fuse_conn_info.rb +310 -134
  38. data/lib/ffi/libfuse/fuse_context.rb +45 -3
  39. data/lib/ffi/libfuse/fuse_operations.rb +43 -19
  40. data/lib/ffi/libfuse/fuse_version.rb +10 -6
  41. data/lib/ffi/libfuse/main.rb +80 -37
  42. data/lib/ffi/libfuse/version.rb +1 -1
  43. data/lib/ffi/libfuse.rb +13 -4
  44. data/lib/ffi/ruby_object.rb +1 -1
  45. data/lib/ffi/stat/constants.rb +9 -0
  46. data/lib/ffi/stat/native.rb +36 -6
  47. data/lib/ffi/stat/time_spec.rb +26 -10
  48. data/lib/ffi/stat.rb +111 -22
  49. data/lib/ffi/stat_vfs.rb +59 -1
  50. data/lib/ffi/struct_wrapper.rb +22 -1
  51. data/sample/hello_fs.rb +54 -0
  52. data/sample/memory_fs.rb +5 -181
  53. data/sample/no_fs.rb +20 -21
  54. data/sample/pass_through_fs.rb +30 -0
  55. metadata +77 -4
  56. data/lib/ffi/libfuse/adapter/thread_local_context.rb +0 -36
@@ -2,178 +2,379 @@
2
2
 
3
3
  require_relative 'safe'
4
4
  require_relative 'debug'
5
+ require 'set'
5
6
 
6
7
  module FFI
7
8
  module Libfuse
8
9
  module Adapter
9
- # Wrapper module to give more natural ruby signatures to the fuse callbacks, in particular to avoid dealing with
10
- # FFI Pointers
10
+ # This module assists with converting native C libfuse into idiomatic Ruby
11
+ #
12
+ # Class Method helpers
13
+ # ===
14
+ # These functions deal with the native fuse FFI::Pointers, Buffers etc
15
+ #
16
+ # The class {ReaddirFiller} assists with the complexity of the readdir callback
17
+ #
18
+ # FUSE Callbacks
19
+ # ===
20
+ #
21
+ # Including this module in a Filesystem module changes the method signatures for callbacks to be more idiomatic
22
+ # ruby (via prepending {Prepend}). It also includes the type 1 adapters {Context}, {Debug} and {Safe}
23
+ #
24
+ # Filesystems that return {::IO} like objects from #{open} need not implement other file io operations
25
+ # Similarly Filesystems that return {::Dir} like objects from #{opendir} need not implement #{readdir}
11
26
  #
12
- # @note includes {Debug} and {Safe}
13
27
  module Ruby
14
- # adapter module prepended to the including module
15
- # @!visibility private
28
+ # Helper class for {FuseOperations#readdir}
29
+ # @example
30
+ # def readdir(path, buf, filler, _offset, _ffi, *flags)
31
+ # rdf = FFI::Adapter::Ruby::ReaddirFiller.new(buf, filler)
32
+ # %w[. ..].each { |dot_entry| rdf.fill(dot_entry) }
33
+ # entries.each { |entry| rdf.fill(entry) }
34
+ # end
35
+ # @example
36
+ # # short version
37
+ # def readdir(path, buf, filler, _offset, _ffi, *flags)
38
+ # %w[. ..].concat(entries).each(&FFI::Adapter::Ruby::ReaddirFiller.new(buf,filler))
39
+ # end
40
+ class ReaddirFiller
41
+ # @param [FFI::Pointer] buf from #{FuseOperations#readdir}
42
+ # @param [FFI::Function] filler from #{FuseOperations#readdir}
43
+ # @param [Boolean] fuse3 does the filler function expect fuse 3 compatibility
44
+ def initialize(buf, filler, fuse3: FUSE_MAJOR_VERSION >= 3)
45
+ @buf = buf
46
+ @filler = filler
47
+ @stat_buf = nil
48
+ @fuse3 = fuse3
49
+ end
50
+
51
+ # Fill readdir from a directory handle
52
+ # @param [#seek, #read, #tell] dir_handle
53
+ # @param [Integer] offset
54
+ # @raise [Errno::ENOTSUP] unless dir_handle quacks like ::Dir
55
+ def readdir_fh(dir_handle, offset = 0)
56
+ raise Errno::ENOTSUP unless %i[seek read tell].all? { |m| dir_handle.respond_to?(m) }
57
+
58
+ dir_handle.seek(offset)
59
+ loop while (name = dir_handle.read) && fill(name, offset: dir_handle.tell)
60
+ end
61
+
62
+ # @param [String] name a directory entry
63
+ # @param [FFI::Stat|Hash<Symbol,Integer>|nil] stat or stat fields to fill a {::FFI::Stat}
64
+ #
65
+ # Note sending nil values will cause Fuse to issue #getattr operations for each entry
66
+ #
67
+ # It is safe to reuse the same {FFI::Stat} object between calls
68
+ # @param [Integer] offset
69
+ # @param [Boolean] fill_dir_plus true if stat has full attributes for inode caching
70
+ # @return [Boolean] true if the buffer accepted the entry
71
+ # @raise [StopIteration] if called after a previous call returned false
72
+ def fill(name, stat: nil, offset: 0, fill_dir_plus: false)
73
+ raise StopIteration unless @buf
74
+
75
+ fill_flags = fill_flags(fill_dir_plus: fill_dir_plus)
76
+ fill_stat = fill_stat(stat)
77
+ return true if @filler.call(@buf, name, fill_stat, offset, *fill_flags).zero?
78
+
79
+ @buf = nil
80
+ end
81
+
82
+ # @return [Proc] a proc to pass to something that yields like #{fill}
83
+ def to_proc
84
+ proc do |name, stat: nil, offset: 0, fill_dir_plus: false|
85
+ fill(name, stat: stat, offset: offset, fill_dir_plus: fill_dir_plus)
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ def fill_flags(fill_dir_plus:)
92
+ return [] unless @fuse3
93
+
94
+ [fill_dir_plus ? :fuse_fill_dir_plus : 0]
95
+ end
96
+
97
+ def fill_stat(from)
98
+ return from if !from || from.is_a?(::FFI::Stat)
99
+
100
+ (@stat_buf ||= ::FFI::Stat.new).fill(from)
101
+ end
102
+ end
103
+
16
104
  # rubocop:disable Metrics/ModuleLength
17
- module Shim
105
+
106
+ # Can be prepended to concrete filesystem implementations to skip duplicate handling of {Debug}, {Safe}
107
+ #
108
+ # @note callbacks still expect to be ultimately handled by {Safe}, ie they raise SystemCallError and can
109
+ # return non Integer results
110
+ module Prepend
18
111
  include Adapter
19
112
 
20
- # Fuse3 support outer module can override this
113
+ # Returns true if our we can support fuse_method callback
114
+ #
115
+ # ie. when
116
+ #
117
+ # * fuse_method is implemented directly by our superclass
118
+ # * the adapter can handle the callback via
119
+ # * file or directory handle returned from {#open} or {#opendir}
120
+ # * fallback to an alternate implementation (eg {#read_buf} -> {#read}, {#write_buf} -> {#write})
121
+ def fuse_respond_to?(fuse_method)
122
+ fuse_methods =
123
+ case fuse_method
124
+ when :read, :write, :flush, :release
125
+ %i[open]
126
+ when :read_buf
127
+ %i[open read]
128
+ when :write_buf
129
+ %i[open write]
130
+ when :readdir, :releasedir
131
+ %i[opendir]
132
+ else
133
+ []
134
+ end
135
+ fuse_methods << fuse_method
136
+
137
+ fuse_methods.any? { |m| super(m) }
138
+ end
139
+
140
+ # Helper to test if path is root
141
+ def root?(path)
142
+ path.respond_to?(:root?) ? path.root? : path.to_s == '/'
143
+ end
144
+
145
+ # @!visibility private
146
+ # Fuse 3 compatibility
147
+ # @return [Boolean] true if this filesystem is receiving Fuse3 compatible arguments
148
+ # @see Fuse3Support
21
149
  def fuse3_compat?
22
150
  FUSE_MAJOR_VERSION >= 3
23
151
  end
24
152
 
25
- def write(*args)
26
- *pre, path, buf, size, offset, info = args
27
- super(*pre, path, buf.read_bytes(size), offset, info)
28
- size
153
+ # @!group FUSE Callbacks
154
+
155
+ # Writes data to path via
156
+ #
157
+ # * super as per {Ruby#write} if defined
158
+ # * {Ruby.write_fh} on ffi.fh
159
+ def write(path, buf, size = buf.size, offset = 0, ffi = nil)
160
+ return Ruby.write_fh(buf, size, offset, ffi&.fh) unless defined?(super)
161
+
162
+ Ruby.write_data(buf, size) { |data| super(path, data, offset, ffi) }
29
163
  end
30
164
 
31
- def read(*args)
32
- *pre, path, buf, size, offset, info = args
33
- res = super(*pre, path, size, offset, info)
165
+ # Writes data to path with {FuseBuf}s via
166
+ #
167
+ # * super directly if defined
168
+ # * {FuseBufVec#copy_to_fd} if ffi.fh has non-nil :fileno
169
+ # * {FuseBufVec#copy_to_str} with the result of {Ruby#write}
170
+ def write_buf(path, bufv, offset, ffi)
171
+ return super if defined?(super)
34
172
 
35
- return -Errno::ERANGE::Errno unless res.size <= size
173
+ fd = ffi&.fh&.fileno
174
+ return bufv.copy_to_fd(fd, offset) if fd
36
175
 
37
- buf.write_bytes(res)
38
- res.size
176
+ data = bufv.copy_to_str
177
+ write(path, data, data.size, offset, ffi)
39
178
  end
40
179
 
41
- def readlink(*args)
42
- *pre, path, buf, size = args
43
- link = super(*pre, path)
44
- buf.write_bytes(link, [0..size])
45
- 0
180
+ # Flush data to path via
181
+ #
182
+ # * super if defined
183
+ # * :flush on ffi.fh if defined
184
+ def flush(path, ffi)
185
+ return super if defined?(super)
186
+
187
+ fh = ffi&.fh
188
+ fh.flush if fh.respond_to?(:flush)
46
189
  end
47
190
 
48
- # rubocop:disable Metrics/AbcSize
49
-
50
- # changes args (removes buf and filler), processes return, changes block
51
- # *pre, path, buf, filler, offset, fuse_file_info, flag = nil
52
- def readdir(*args)
53
- flag_arg = args.pop if fuse3_compat?
54
- *pre, buf, filler, offset, fuse_file_info = args
55
- args = pre + [offset, fuse_file_info]
56
- args << flag_arg if fuse3_compat?
57
- buffer_available = true
58
- super(*args) do |name, stat, buffered = false, flag = 0|
59
- raise StopIteration unless buffer_available
60
-
61
- offset = buffered if buffered.is_a?(Integer)
62
- offset += 1 if buffered && !buffered.is_a?(Integer) # auto-track offsets
63
- stat = Stat.new.fill(stat) if stat && !stat.is_a?(Stat)
64
- filler_args = [buf, name, stat, offset]
65
- filler_args << flag if fuse3_compat?
66
- buffer_available = filler.call(*filler_args).zero?
191
+ # Sync data to path via
192
+ #
193
+ # * super as per {Ruby#fsync} if defined
194
+ # * :datasync on ffi.fh if defined and datasync is non-zero
195
+ # * :fysnc on ffi.fh if defined
196
+ def fsync(path, datasync, ffi)
197
+ return super(path, datasync != 0, ffi) if defined?(super)
198
+
199
+ fh = ffi&.fh
200
+ return fh.datasync if datasync && fh.respond_to?(:datasync)
201
+
202
+ fh.fsync if fh.respond_to?(:fsync)
203
+ end
204
+
205
+ # Read data from path via
206
+ #
207
+ # * super as per {Ruby#read} if defined
208
+ # * ffi.fh as per {Ruby.read}
209
+ def read(path, buf, size, offset, ffi)
210
+ Ruby.read(buf, size, offset) do
211
+ defined?(super) ? super(path, size, offset, ffi) : ffi&.fh
67
212
  end
68
213
  end
69
- # rubocop:enable Metrics/AbcSize
70
214
 
71
- def setxattr(*args)
72
- # fuse converts the void* data buffer to a const char* null terminated string
73
- # which libfuse reads directly, so size is irrelevant
74
- *pre, path, name, data, _size, flags = args
75
- super(*pre, path, name, data, flags)
215
+ # Read data with {FuseBuf}s via
216
+ #
217
+ # * super if defined
218
+ # * ffi.fh.fileno if defined and not nil
219
+ # * result of {#read}
220
+ def read_buf(path, bufp, size, offset, ffi)
221
+ return super if defined?(super)
222
+
223
+ Ruby.read_buf(bufp, size, offset) do
224
+ fh = ffi&.fh
225
+ fd = fh.fileno if fh.respond_to?(:fileno)
226
+ next fd if fd
227
+
228
+ read(path, nil, size, offset, ffi)
229
+ end
76
230
  end
77
231
 
78
- def getxattr(*args)
79
- *pre, path, name, buf, size = args
80
- res = super(*pre, path, name)
232
+ # Read link name from path via super as per {Ruby#readlink}
233
+ def readlink(path, buf, size)
234
+ raise Errno::ENOTSUP unless defined?(super)
81
235
 
82
- return -Errno::ENODATA::Errno unless res
236
+ Ruby.readlink(buf, size) { super(path, size) }
237
+ end
83
238
 
84
- res = res.to_s
239
+ # Read directory entries via
240
+ #
241
+ # * super as per {Ruby#readdir} if defined
242
+ # * ffi.fh using {ReaddirFiller#readdir_fh}
243
+ def readdir(path, buf, filler, offset, ffi, flag_arg = nil)
244
+ rd_filler = ReaddirFiller.new(buf, filler, fuse3: fuse3_compat?)
85
245
 
86
- return res.size if size.zero?
87
- return -Errno::ERANGE::Errno if res.size > size
246
+ flag_args = {}
247
+ flag_args[:readdir_plus] = (flag_arg == :fuse_readdir_plus) if fuse3_compat?
248
+ return super(path, offset, ffi, **flag_args, &rd_filler) if defined?(super)
88
249
 
89
- buf.write_bytes(res)
90
- res.size
250
+ rd_filler.readdir_fh(ffi.fh, offset)
251
+ rescue StopIteration
252
+ # do nothing
91
253
  end
92
254
 
93
- def listxattr(*args)
94
- *pre, path, buf, size = args
95
- res = super(*pre, path)
96
- res.reduce(0) do |offset, name|
97
- name = name.to_s
98
- unless size.zero?
99
- return -Errno::ERANGE::Errno if offset + name.size >= size
255
+ # Set extended attributes via super as per {Ruby#setxattr}
256
+ def setxattr(path, name, data, _size, flags)
257
+ raise Errno::ENOTSUP unless defined?(super)
100
258
 
101
- buf.put_string(offset, name) # put string includes the NUL terminator
102
- end
103
- offset + name.size + 1
104
- end
259
+ # fuse converts the void* data buffer to a const char* null terminated string
260
+ # which libfuse reads directly, so size is irrelevant
261
+ super(path, name, data, flags)
105
262
  end
106
263
 
107
- def read_buf(*args)
108
- *pre, path, bufp, size, offset, fuse_file_info = args
109
- buf = FuseBufVec.new
110
- super(*pre, path, buf, size, offset, fuse_file_info)
111
- bufp.put_pointer(0, buf)
112
- 0
264
+ # Get extended attributes via super as per {Ruby#getxattr}
265
+ def getxattr(path, name, buf, size)
266
+ raise Errno::ENOTSUP unless defined?(super)
267
+
268
+ Ruby.getxattr(buf, size) { super(path, name) }
113
269
  end
114
270
 
115
- # extract atime, mtime from times array, convert from FFI::Stat::TimeSpec to ruby Time
116
- def utimens(*args)
117
- ffi = args.pop if fuse3_compat?
118
- *pre, path, times = args
271
+ # List extended attributes via super as per {Ruby#listxattr}
272
+ def listxattr(*args)
273
+ raise Errno::ENOTSUP unless defined?(super)
119
274
 
120
- # Empty times means set both to current time
121
- times = [Stat::TimeSpec.now, Stat::TimeSpec.now] unless times&.size == 2
275
+ path, buf, size = args
276
+ Ruby.listxattr(buf, size) { super(path) }
277
+ end
122
278
 
123
- # If both times are set to UTIME_NOW, make sure they get the same value!
124
- now = times.any?(&:now?) && Time.now
125
- atime, mtime = times.map { |t| t.time(now) }
279
+ # Set file atime, mtime via super as per {Ruby#utimens}
280
+ def utimens(path, times, *fuse3_args)
281
+ raise Errno::ENOTSUP unless defined?(super)
126
282
 
127
- args = pre + [path, atime, mtime]
128
- args << ffi if fuse3_compat?
129
- super(*args)
283
+ atime, mtime = Stat::TimeSpec.fill_times(times[0, 2], 2).map(&:time)
284
+ super(path, atime, mtime, *fuse3_args)
130
285
  0
131
286
  end
132
287
 
133
- # accept a filehandle object as result of these methods
134
- # keep a reference to the filehandle until corresponding release
288
+ # @!method create(path, mode, ffi)
289
+ # Calls super if defined as per {Ruby#create} storing result in ffi.fh and protecting it from GC
290
+ # until {#release}
291
+
292
+ # @!method open(path, ffi)
293
+ # Calls super if defined as per {Ruby#open} storing result in ffi.fh and protecting it from GC
294
+ # until {#release}
295
+
296
+ # @!method opendir(path, ffi)
297
+ # Calls super if defined as per {Ruby#opendir} storing result in ffi.fh and protecting it from GC
298
+ # until {#releasedir}
299
+
135
300
  %i[create open opendir].each do |fuse_method|
136
301
  define_method(fuse_method) do |*args|
137
- fh = super(*args)
138
- store_filehandle(args.last, fh)
302
+ fh = super(*args) if fuse_super_respond_to?(fuse_method)
303
+ store_handle(args.last, fh)
139
304
  0
140
305
  end
141
306
  end
142
307
 
143
- %i[release release_dir].each do |fuse_method|
144
- define_method(fuse_method) do |*args|
145
- super(*args)
308
+ # @!method release(path, ffi)
309
+ # Calls super if defined and allows ffi.fh to be GC'd
310
+
311
+ # @!method releasedir(path, ffi)
312
+ # Calls super if defined and allows ffi.fh to be GC'd
313
+
314
+ %i[release releasedir].each do |fuse_method|
315
+ define_method(fuse_method) do |path, ffi|
316
+ super(path, ffi) if fuse_super_respond_to?(fuse_method)
146
317
  ensure
147
- release_filehandle(args.last)
318
+ release_handle(ffi)
148
319
  end
149
320
  end
150
321
 
322
+ # Calls super if defined and storing result to protect from GC until {#destroy}
323
+ def init(*args)
324
+ o = super(*args) if fuse_super_respond_to?(:init)
325
+ handles << o if o
326
+ end
327
+
328
+ # Calls super if defined and allows init_obj to be GC'd
329
+ def destroy(init_obj)
330
+ super if fuse_super_respond_to?(:destroy)
331
+ handles.delete(init_obj) if init_obj
332
+ end
333
+
334
+ # @!endgroup
151
335
  private
152
336
 
153
- def store_filehandle(ffi, filehandle)
154
- return unless filehandle
337
+ def handles
338
+ @handles ||= Set.new.compare_by_identity
339
+ end
340
+
341
+ def store_handle(ffi, file_handle)
342
+ return unless file_handle
155
343
 
156
- @filehandles ||= {}
157
- @filehandles[ffi]
158
- ffi.fh = filehandle
344
+ handles << file_handle
345
+ ffi.fh = file_handle
159
346
  end
160
347
 
161
- def release_filehandle(ffi)
348
+ def release_handle(ffi)
162
349
  return unless ffi.fh
163
350
 
164
- @filehandles.delete(ffi.fh.object_id)
351
+ handles.delete(ffi.fh)
165
352
  end
166
353
  end
167
354
  # rubocop:enable Metrics/ModuleLength
168
355
 
169
356
  # @!group FUSE Callbacks
170
357
 
358
+ # @!method create(path, mode, fuse_file_info)
359
+ # Create file
360
+ # @abstract
361
+ # @param [String] path
362
+ # @param [FuseFileInfo] fuse_file_info
363
+ # @return [Object] file handle available to future operations in fuse_file_info.fh
364
+
171
365
  # @!method open(path,fuse_file_info)
172
366
  # File open
173
367
  # @abstract
174
368
  # @param [String] path
175
369
  # @param [FuseFileInfo] fuse_file_info
176
- # @return [Object] file handle (available to future operations in fuse_file_info.fh)
370
+ # @return [Object] file handle available to future operations in fuse_file_info.fh
371
+ #
372
+ # File handles are kept from being GC'd until {FuseOperations#release}
373
+ #
374
+ # If the file handle quacks like {::IO} then the file io operations
375
+ # :read, :write, :flush, :fsync, :release will be invoked on the file handle if not implemented
376
+ # by the filesystem
377
+ #
177
378
  # @see FuseOperations#open
178
379
 
179
380
  # @!method create(path,mode,fuse_file_info)
@@ -194,78 +395,79 @@ module FFI
194
395
  # @see FuseOperations#opendir
195
396
 
196
397
  # @!method write(path,data,offset,info)
197
- # Write file data
198
398
  # @abstract
399
+ # Write file data. If not implemented will attempt to use info.fh as an IO via :pwrite, or :seek + :write
199
400
  # @param [String] path
200
401
  # @param [String] data
201
402
  # @param [Integer] offset
202
403
  # @param [FuseFileInfo] info
203
404
  # @return [void]
405
+ # @raise [Errno::ENOTSUP] if not implemented and info.fh does not quack like IO
204
406
  # @see FuseOperations#write
205
407
 
408
+ # @!method write_buf(path,buffers,offset,info)
409
+ # @abstract
410
+ # Write file data from buffers
411
+ # If not implemented, will try to use info.fh,fileno to perform libfuse' file descriptor io, otherwise
412
+ # the string data is extracted from buffers and passed to #{write}
413
+
206
414
  # @!method read(path,size,offset,info)
207
415
  # @abstract
208
- # Read file data
209
- #
416
+ # Read file data. If not implemented will attempt to use info.fh to perform the read.
210
417
  # @param [String] path
211
418
  # @param [Integer] size
212
419
  # @param [Integer] offset
213
420
  # @param [FuseFileInfo] info
214
- #
215
421
  # @return [String] the data, expected to be exactly size bytes, except if EOF
422
+ # @return [#pread, #read] something that supports :pread, or :seek and :read
423
+ # @raise [Errno::ENOTSUP] if not implemented, and info.fh does not quack like IO
216
424
  # @see FuseOperations#read
425
+ # @see FuseOperations#read_buf
426
+
427
+ # @!method read_buf(path,buffers,size,offset,info)
428
+ # @abstract
429
+ # If not implemented and info.fh has :fileno then libfuse' file descriptor io will be used,
430
+ # otherwise will use {read} to populate buffers
217
431
 
218
- # @!method readlink(path)
432
+ # @!method readlink(path, size)
219
433
  # @abstract
220
434
  # Resolve target of a symbolic link
221
435
  # @param [String] path
222
- # @return [String] the link target
436
+ # @param [Integer] size
437
+ # @return [String] the link target, truncated to size if necessary
223
438
  # @see FuseOperations#readlink
224
439
 
225
- # @!method readdir(path,offset,fuse_file_info,&filler)
440
+ # @!method readdir(path,offset,fuse_file_info, readdir_plus:, &filler)
226
441
  # @abstract
227
442
  # List directory entries
228
443
  #
229
- # The filesystem may choose between two modes of operation:
444
+ # The filesystem may choose between three modes of operation:
230
445
  #
231
- # 1) The readdir implementation ignores the offset parameter yielding only name, stat pairs for each entry.
446
+ # 1) The readdir implementation ignores the offset parameter yielding only name, and optional stat
232
447
  # The yield will always return true so the whole directory is read in a single readdir operation.
233
448
  #
234
449
  # 2) The readdir implementation keeps track of the offsets of the directory entries. It uses the offset
235
- # parameter and always yields buffered=true to the filler function. When the buffer is full, or an error
236
- # occurs, yielding to the filler function will return false. Subsequent yields will raise StopIteration
450
+ # parameter to restart the iteration and yields non-zero offsets for each entry. When the buffer is full, or
451
+ # an error occurs, yielding to the filler function will return false. Subsequent yields will raise
452
+ # StopIteration
453
+ #
454
+ # 3) Return a Dir like object from {opendir} and do not implement this method. The directory
455
+ # will be enumerated from offset via calling :seek, :read and :tell on fuse_file_info.fh
237
456
  #
238
457
  # @param [String] path
239
458
  # @param [Integer] offset the starting offset (inclusive!)
240
459
  #
241
460
  # this is either 0 for a new operation, or the last value previously yielded to the filler function
242
- # (when it returned false to indicate the buffer was full). Type 2 implementations should therefore include
461
+ # (when it returned false to indicate the buffer was full).
243
462
  #
244
463
  # @param [FuseFileInfo] fuse_file_info
245
- #
464
+ # @param [Boolean] readdir_plus true if extended readdir is supported (Fuse3 only)
246
465
  # @raise [SystemCallError] an appropriate Errno value
247
466
  # @return [void]
248
- #
249
- # @yieldparam [String] name the name of a directory entry
250
- # @yieldparam [Stat|nil] stat the directory entry stat
251
- #
252
- # Note sending nil values will cause Fuse to issue #getattr operations for each entry
253
- #
254
- # @yieldparam [Integer|Boolean] offset (optional - default false)
255
- #
256
- # integer value will be used as offset. The last value yielded (with false return) will be used
257
- # for the next readdir call
258
- #
259
- # otherwise truthy to indicate support for restart from monotonically increasing offset
260
- #
261
- # false to indicate type 1 operation - full listing
262
- #
263
- # @yieldreturn [Boolean]
264
- #
265
- # * true if buffer accepted the directory entry
266
- # * false on first time buffer is full. StopIteration will be raised on subsequent yields
267
- #
467
+ # @yield [name,stat:,offset:,fill_dir_plus:]
468
+ # See {ReaddirFiller#fill}
268
469
  # @see FuseOperations#readdir
470
+ # @see ReaddirFiller#fill
269
471
 
270
472
  # @!method getxattr(path,name)
271
473
  # @abstract
@@ -275,7 +477,7 @@ module FFI
275
477
  # @return [nil|String] the attribute value or nil if it does not exist
276
478
  # @see FuseOperations#getxattr
277
479
 
278
- # @!method listxattr(path,name)
480
+ # @!method listxattr(path)
279
481
  # @abstract
280
482
  # List extended attributes
281
483
  # @param [String] path
@@ -294,17 +496,6 @@ module FFI
294
496
  # @raise [Errno::ENODATA] for :xattr_replace and name does not already exist
295
497
  # @see FuseOperations#setxattr
296
498
 
297
- # @!method read_buf(path,buffers,size,offset,fuse_file_info)
298
- # @abstract
299
- # Read through fuse data buffers
300
- # @param [String] path
301
- # @param [FuseBufVec] buffers
302
- # @param [Integer] size
303
- # @param [Integer] offset
304
- # @param [FuseFileInfo] fuse_file_info
305
- # @return [void]
306
- # @see FuseOperations#read_buf
307
-
308
499
  # @!method utimens(path,atime,mtime,fuse_file_info=nil)
309
500
  # @abstract
310
501
  # Change the access and/or modification times of a file with nanosecond resolution
@@ -320,13 +511,173 @@ module FFI
320
511
  # Either atime or mtime can be nil corresponding to utimensat(2) receiving UTIME_OMIT.
321
512
  # The special value UTIME_NOW passed from Fuse is automatically set to the current time
322
513
 
514
+ # @!method fsync(path, datasync, fuse_file_info)
515
+ # @abstract
516
+ # @param [String] path
517
+ # @param [Boolean] datasync if true only the user data should be flushed, not the meta data.
518
+ # @param [FuseFileInfo] fuse_file_info
519
+
323
520
  # @!endgroup
324
521
 
325
522
  # @!visibility private
326
523
  def self.included(mod)
327
- mod.prepend(Shim)
328
- mod.include(Safe)
524
+ mod.prepend(Prepend)
525
+ mod.include(Context)
329
526
  mod.include(Debug)
527
+ mod.include(Safe)
528
+ end
529
+
530
+ class << self
531
+ # Helper for implementing {FuseOperations#readlink}
532
+ # @param [FFI::Pointer] buf
533
+ # @param [Integer] size
534
+ # @yield []
535
+ # @yieldreturn [String] the link name
536
+ # @raise [Errno::ENOTSUP] if no data is returned
537
+ # @raise [Errno::ENAMETOOLONG] if data returned is larger than size
538
+ # @return [void]
539
+ def readlink(buf, size)
540
+ link = yield
541
+ raise Errno::ENOTSUP unless link
542
+ raise Errno::ENAMETOOLONG unless link.size < size # includes terminating NUL
543
+
544
+ buf.put_string(link)
545
+ 0
546
+ end
547
+
548
+ # Helper for implementing {FuseOperations#read}
549
+ # @param [FFI::Pointer] buf
550
+ # @param [Integer] size
551
+ # @param [Integer] offset
552
+ # @return [Integer] size of data read
553
+ # @yield []
554
+ # @yieldreturn [String, #pread, #pwrite] the resulting data or IO like object
555
+ # @raise [Errno::ENOTSUP] if no data is returned
556
+ # @raise [Errno::ERANGE] if data return is larger than size
557
+ # @see data_to_str
558
+ def read(buf, size, offset = 0)
559
+ data = yield
560
+ raise Errno::ENOTSUP unless data
561
+
562
+ return data unless buf # called from read_buf
563
+
564
+ data = data_to_str(data, size, offset)
565
+ raise Errno::ERANGE unless data.size <= size
566
+
567
+ buf.write_bytes(data)
568
+ data.size
569
+ end
570
+
571
+ # Helper for implementing {FuseOperations#read_buf}
572
+ # @param [FFI::Pointer] bufp
573
+ # @param [Integer] size
574
+ # @param [Integer] offset
575
+ # @yield []
576
+ # @yieldreturn [Integer|:fileno|String,:pread,:pwrite] a file descriptor, String or io like object
577
+ # @see data_to_bufvec
578
+ def read_buf(bufp, size, offset)
579
+ data = yield
580
+ raise Errno::ENOTSUP unless data
581
+
582
+ bufp.write_pointer(data_to_bufvec(data, size, offset).to_ptr)
583
+ 0
584
+ end
585
+
586
+ # Helper to convert input data to a string for use with {FuseOperations#read}
587
+ # @param [String|:pread|:read] io input data that is a String or quacks like {::IO}
588
+ # @param [Integer] size
589
+ # @param [Integer] offset
590
+ # @return [String] extracted data
591
+ def data_to_str(io, size, offset)
592
+ return io if io.is_a?(String)
593
+ return io.pread(size, offset) if io.respond_to?(:pread)
594
+ return io.read(size) if io.respond_to?(:read)
595
+
596
+ io.to_s
597
+ end
598
+
599
+ # Helper to convert string or IO to {FuseBufVec} for {FuseOperations#read_buf}
600
+ # @param [Integer|:fileno|String|:pread|:read] data the io like input data or an integer file descriptor
601
+ # @param [Integer] size
602
+ # @param [Integer] offset
603
+ # @return [FuseBufVec]
604
+ def data_to_bufvec(data, size, offset)
605
+ data = data.fileno if data.respond_to?(:fileno)
606
+ return FuseBufVec.init(autorelease: false, size: size, fd: data, pos: offset) if data.is_a?(Integer)
607
+
608
+ str = data_to_str(data, size, offset)
609
+ FuseBufVec.init(autorelease: false, size: str.size, mem: FFI::MemoryPointer.from_string(str))
610
+ end
611
+
612
+ # Helper to implement #{FuseOperations#write}
613
+ # @param [FFI::Pointer|:to_s] buf
614
+ # @param [Integer] size
615
+ # @return [Integer] size
616
+ # @yield [data]
617
+ # @yieldparam [String] data extracted from buf
618
+ # @yieldreturn [void]
619
+ def write_data(buf, size)
620
+ data = buf.read_bytes(size) if buf.respond_to?(:read_bytes)
621
+ data ||= buf.to_s
622
+ data = data[0..size] if data.size > size
623
+ yield data
624
+ size
625
+ end
626
+
627
+ # Helper to write a data buffer to an open file
628
+ # @param [FFI::Pointer] buf
629
+ # @param [Integer] size
630
+ # @param [Integer] offset
631
+ # @param [:pwrite,:seek,:write] handle an IO like file handle
632
+ # @return [Integer] size
633
+ # @raise [Errno::ENOTSUP] if handle is does not quack like an open file
634
+ def write_fh(buf, size, offset, handle)
635
+ write_data(buf, size) do |data|
636
+ if handle.respond_to?(:pwrite)
637
+ handle.pwrite(data, offset)
638
+ elsif handle.respond_to?(:write)
639
+ handle.seek(offset) if handle.respond_to?(:seek)
640
+ handle.write(data)
641
+ else
642
+ raise Errno::ENOTSUP
643
+ end
644
+ end
645
+ end
646
+
647
+ # Helper for implementing {FuseOperations#getxattr}
648
+ #
649
+ # @param [FFI::Pointer] buf
650
+ # @param [Integer] size
651
+ # @yieldreturn [String] the xattr name
652
+ def getxattr(buf, size)
653
+ res = yield
654
+ raise Errno::ENODATA unless res
655
+
656
+ res = res.to_s
657
+
658
+ return res.size if size.zero?
659
+ raise Errno::ERANGE if res.size > size
660
+
661
+ buf.write_bytes(res)
662
+ res.size
663
+ end
664
+
665
+ # Helper for implementing {FuseOperations#listxattr}
666
+ # @param [FFI::Pointer] buf
667
+ # @param [Integer] size
668
+ # @yieldreturn [Array<String>] a list of extended attribute names
669
+ def listxattr(buf, size)
670
+ res = yield
671
+ res.reduce(0) do |offset, name|
672
+ name = name.to_s
673
+ unless size.zero?
674
+ raise Errno::ERANGE if offset + name.size >= size
675
+
676
+ buf.put_string(offset, name) # put string includes the NUL terminator
677
+ end
678
+ offset + name.size + 1
679
+ end
680
+ end
330
681
  end
331
682
  end
332
683
  end