ffi-libfuse 0.3.4 → 0.4.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/README.md +1 -1
  4. data/lib/ffi/accessors.rb +21 -7
  5. data/lib/ffi/boolean_int.rb +1 -1
  6. data/lib/ffi/devt.rb +3 -3
  7. data/lib/ffi/libfuse/adapter/debug.rb +53 -15
  8. data/lib/ffi/libfuse/adapter/fuse2_compat.rb +38 -21
  9. data/lib/ffi/libfuse/adapter/fuse3_support.rb +0 -1
  10. data/lib/ffi/libfuse/adapter/ruby.rb +210 -159
  11. data/lib/ffi/libfuse/adapter/safe.rb +69 -21
  12. data/lib/ffi/libfuse/callbacks.rb +2 -1
  13. data/lib/ffi/libfuse/filesystem/accounting.rb +1 -1
  14. data/lib/ffi/libfuse/filesystem/mapped_files.rb +33 -7
  15. data/lib/ffi/libfuse/filesystem/pass_through_dir.rb +0 -1
  16. data/lib/ffi/libfuse/filesystem/virtual_dir.rb +293 -126
  17. data/lib/ffi/libfuse/filesystem/virtual_file.rb +85 -79
  18. data/lib/ffi/libfuse/filesystem/virtual_fs.rb +34 -15
  19. data/lib/ffi/libfuse/filesystem/virtual_link.rb +60 -0
  20. data/lib/ffi/libfuse/filesystem/virtual_node.rb +104 -87
  21. data/lib/ffi/libfuse/filesystem.rb +1 -1
  22. data/lib/ffi/libfuse/fuse2.rb +3 -2
  23. data/lib/ffi/libfuse/fuse3.rb +1 -1
  24. data/lib/ffi/libfuse/fuse_args.rb +5 -2
  25. data/lib/ffi/libfuse/fuse_buf.rb +112 -0
  26. data/lib/ffi/libfuse/fuse_buf_vec.rb +228 -0
  27. data/lib/ffi/libfuse/fuse_common.rb +10 -4
  28. data/lib/ffi/libfuse/fuse_config.rb +16 -7
  29. data/lib/ffi/libfuse/fuse_operations.rb +86 -41
  30. data/lib/ffi/libfuse/gem_helper.rb +2 -9
  31. data/lib/ffi/libfuse/io.rb +56 -0
  32. data/lib/ffi/libfuse/main.rb +27 -24
  33. data/lib/ffi/libfuse/test_helper.rb +68 -60
  34. data/lib/ffi/libfuse/version.rb +1 -1
  35. data/lib/ffi/libfuse.rb +1 -1
  36. data/lib/ffi/stat/native.rb +4 -4
  37. data/lib/ffi/stat.rb +19 -3
  38. data/lib/ffi/struct_array.rb +2 -1
  39. data/sample/hello_fs.rb +1 -1
  40. metadata +6 -3
  41. data/lib/ffi/libfuse/fuse_buffer.rb +0 -257
@@ -7,7 +7,7 @@ require 'set'
7
7
  module FFI
8
8
  module Libfuse
9
9
  module Adapter
10
- # This module assists with converting native C libfuse into idiomatic Ruby
10
+ # This module assists with converting native C libfuse into idiomatic and duck-typed Ruby behaviour
11
11
  #
12
12
  # Class Method helpers
13
13
  # ===
@@ -77,6 +77,7 @@ module FFI
77
77
  return true if @filler.call(@buf, name, fill_stat, offset, *fill_flags).zero?
78
78
 
79
79
  @buf = nil
80
+ false
80
81
  end
81
82
 
82
83
  # @return [Proc] a proc to pass to something that yields like #{fill}
@@ -101,8 +102,6 @@ module FFI
101
102
  end
102
103
  end
103
104
 
104
- # rubocop:disable Metrics/ModuleLength
105
-
106
105
  # Can be prepended to concrete filesystem implementations to skip duplicate handling of {Debug}, {Safe}
107
106
  #
108
107
  # @note callbacks still expect to be ultimately handled by {Safe}, ie they raise SystemCallError and can
@@ -152,29 +151,97 @@ module FFI
152
151
 
153
152
  # @!group FUSE Callbacks
154
153
 
154
+ # Read directory entries via
155
+ #
156
+ # * super as per {Ruby#readdir} if defined
157
+ # * ffi.fh using {ReaddirFiller#readdir_fh}
158
+ def readdir(path, buf, filler, offset, ffi, flag_arg = nil)
159
+ rd_filler = ReaddirFiller.new(buf, filler, fuse3: fuse3_compat?)
160
+
161
+ flag_args = {}
162
+ flag_args[:readdir_plus] = (flag_arg == :fuse_readdir_plus) if fuse3_compat?
163
+ return super(path, offset, ffi, **flag_args, &rd_filler) if defined?(super)
164
+
165
+ rd_filler.readdir_fh(ffi.fh, offset)
166
+ rescue StopIteration
167
+ # do nothing
168
+ end
169
+
170
+ # Read data from path via
171
+ #
172
+ # * super as per {Ruby#read} if defined (or returns nil|false)
173
+ # * ffi.fh as per {Ruby.read}
174
+ def read(path, buf, size, offset, ffi)
175
+ io, super_offset = defined?(super) && Ruby.rescue_not_implemented { super(path, size, offset, ffi) }
176
+ offset = super_offset if io
177
+ io ||= ffi.fh
178
+
179
+ return [io, offset] unless buf # nil buf as called from read_buf, just wants the io/data back
180
+
181
+ Ruby.read(buf, size, offset) { io }
182
+ end
183
+
184
+ # Read data with {FuseBuf}s via
185
+ #
186
+ # * super if defined
187
+ # * ffi.fh.fileno if defined and not nil
188
+ # * result of {#read}
189
+ def read_buf(path, bufp, size, offset, ffi)
190
+ io, super_offset = defined?(super) && Ruby.rescue_not_implemented { super(path, size, offset, ffi) }
191
+ offset = super_offset if io
192
+
193
+ io ||= ffi.fh if ffi.fh.is_a?(Integer) || ffi.fh.respond_to?(:fileno)
194
+
195
+ io, offset = read(path, nil, size, offset, ffi) unless io
196
+
197
+ Ruby.read_buf(bufp, size, offset) { io }
198
+ end
199
+
200
+ # Read link name from path via super as per {Ruby#readlink}
201
+ def readlink(path, buf, size)
202
+ raise Errno::ENOTSUP unless defined?(super)
203
+
204
+ Ruby.readlink(buf, size) { super(path, size) }
205
+ end
206
+
155
207
  # Writes data to path via
156
208
  #
157
- # * super as per {Ruby#write} if defined
158
- # * {Ruby.write_fh} on ffi.fh
209
+ # * super if defined
210
+ # * ffi.fh if not null and quacks like IO (see {IO.write})
211
+ #
159
212
  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) }
213
+ Ruby.write(buf, size) do |data|
214
+ (defined?(super) && Ruby.rescue_not_implemented { super(path, data, offset, ffi) }) || [ffi&.fh, offset]
215
+ end
163
216
  end
164
217
 
165
- # Writes data to path with {FuseBuf}s via
218
+ # Writes data to path with {FuseBufVec} via
219
+ #
220
+ # * super directly if defined and returns truthy
221
+ # * ffi.fh if it represents a file descriptor
222
+ # * {#write}
166
223
  #
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
224
  def write_buf(path, bufv, offset, ffi)
171
- return super if defined?(super)
225
+ super_result =
226
+ if defined?(super)
227
+ Ruby.rescue_not_implemented do
228
+ super(path, offset, ffi) do |fh = nil, *flags|
229
+ Ruby.write_buf(bufv) { |data| data || [fh, *flags] }
230
+ end
231
+ end
232
+ end
172
233
 
173
- fd = ffi&.fh&.fileno
174
- return bufv.copy_to_fd(fd, offset) if fd
234
+ return super_result if super_result
175
235
 
176
- data = bufv.copy_to_str
177
- write(path, data, data.size, offset, ffi)
236
+ Ruby.write_buf(bufv) do |data|
237
+ # only handle fileno, otherwise fall back to write (which will try other kinds of IO)
238
+ if data
239
+ # fallback to #write
240
+ write(path, data, data.size, offset, ffi)
241
+ else
242
+ [ffi&.fh.respond_to?(:fileno) && ffi.fh.fileno, offset]
243
+ end
244
+ end
178
245
  end
179
246
 
180
247
  # Flush data to path via
@@ -202,56 +269,6 @@ module FFI
202
269
  fh.fsync if fh.respond_to?(:fsync)
203
270
  end
204
271
 
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
212
- end
213
- end
214
-
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
230
- end
231
-
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)
235
-
236
- Ruby.readlink(buf, size) { super(path, size) }
237
- end
238
-
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?)
245
-
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)
249
-
250
- rd_filler.readdir_fh(ffi.fh, offset)
251
- rescue StopIteration
252
- # do nothing
253
- end
254
-
255
272
  # Set extended attributes via super as per {Ruby#setxattr}
256
273
  def setxattr(path, name, data, _size, flags)
257
274
  raise Errno::ENOTSUP unless defined?(super)
@@ -351,8 +368,6 @@ module FFI
351
368
  handles.delete(ffi.fh)
352
369
  end
353
370
  end
354
- # rubocop:enable Metrics/ModuleLength
355
-
356
371
  # @!group FUSE Callbacks
357
372
 
358
373
  # @!method create(path, mode, fuse_file_info)
@@ -394,40 +409,78 @@ module FFI
394
409
  # @return [Object] directory handle (available to future operations in fuse_file_info.fh)
395
410
  # @see FuseOperations#opendir
396
411
 
397
- # @!method write(path,data,offset,info)
412
+ # @!method write(path,data,offset,ffi)
398
413
  # @abstract
399
- # Write file data. If not implemented will attempt to use info.fh as an IO via :pwrite, or :seek + :write
414
+ # Write file data. If not implemented will pass ffi.fh (from {open}) to {IO.write}
400
415
  # @param [String] path
401
416
  # @param [String] data
402
417
  # @param [Integer] offset
403
- # @param [FuseFileInfo] info
404
- # @return [void]
405
- # @raise [Errno::ENOTSUP] if not implemented and info.fh does not quack like IO
418
+ # @param [FuseFileInfo] ffi
419
+ # @return [Integer] number of bytes written (<= data.size)
420
+ # @return [IO] an IO object (data will be sent to {IO.write})
421
+ # @return [nil|false] treat as not implemented
422
+ # @raise [NotImplementedError, Errno::ENOTSUP] treat as not implemented
406
423
  # @see FuseOperations#write
407
424
 
408
- # @!method write_buf(path,buffers,offset,info)
425
+ # @!method write_buf(path,offset,ffi,&buffer)
409
426
  # @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
-
414
- # @!method read(path,size,offset,info)
427
+ # Write buffered data to a file via one of the following techniques
428
+ #
429
+ # 1. Yield object and flags to &buffer to write data directly to a {FuseBufVec}, File or IO
430
+ # and return the yield result (number of bytes written)
431
+ #
432
+ # 2. Yield with no params (or explicitly nil and flags) to retrieve String data (via {FuseBufVec#copy_to_str})
433
+ # to write to path@offset, returning the number of bytes written (eg data.size)
434
+ #
435
+ # 3. Return nil, false or do not implement to
436
+ # * try ffi.fh.fileno (from {#open}) as a file descriptor
437
+ # * or otherwise fallback to {#write}
438
+ #
439
+ # @param [String] path
440
+ # @param [Integer] offset
441
+ # @param [FuseFileInfo] ffi
442
+ # @param [Proc] buffer
443
+ # @yield [io = nil, *flags] Send data to io, or if not set, receive data as a string
444
+ # @yieldparam [FuseBufVec] io write directly into these buffers via {FuseBufVec.copy_to}
445
+ # @yieldparam [Integer|:fileno] io write to an open file descriptor via {FuseBufVec.copy_to_fd}
446
+ # @yieldparam [IO] io quacks like IO passed to {IO.write} to receive data
447
+ # @yieldparam [nil|false] io pull data from buffers into a String
448
+ # @yieldparam [Array<Symbol>] flags see {FuseBufVec}
449
+ # @yieldreturn [String] if io not supplied, the chunk of data to write is returned
450
+ # @yieldreturn [Integer] the number of bytes written to io
451
+ # @return [Integer] number of bytes written (<= data.size)
452
+ # @return [nil|false] treat as not implemented (do not yield AND return nil/false)
453
+ # @raise [NotImplementedError, Errno::ENOTSUP] treat as not implemented
454
+ # @see FuseOperations#write_buf
455
+
456
+ # @!method read(path,size,offset,ffi)
415
457
  # @abstract
416
- # Read file data. If not implemented will attempt to use info.fh to perform the read.
458
+ # Read file data.
459
+ #
460
+ # If not implemented will send ffi.fh (from {open}) to {IO.read}
417
461
  # @param [String] path
418
462
  # @param [Integer] size
419
463
  # @param [Integer] offset
420
- # @param [FuseFileInfo] info
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
464
+ # @param [FuseFileInfo] ffi
465
+ # @return [Array<Object,Integer>] io, offset will be passed to {IO.read}(io, size, offset)
466
+ # @return [nil|false] treat as not implemented
467
+ # @raise [NotImplementedError, Errno::ENOTSUP] treat as not implemented
424
468
  # @see FuseOperations#read
425
- # @see FuseOperations#read_buf
426
469
 
427
- # @!method read_buf(path,buffers,size,offset,info)
470
+ # @!method read_buf(path,size,offset,info)
428
471
  # @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
472
+ # Read file data directly from a buffer
473
+ #
474
+ # If not implemented first tries ffi.fh.fileno (from {#open}) as a file descriptor before
475
+ # falling back to {#read}
476
+ # @param [String] path
477
+ # @param [Integer] size
478
+ # @param [Integer] offset
479
+ # @param [FuseFileInfo] info
480
+ # @return [Array<Object,Integer>] io, offset passed to {FuseBufVec#copy_to_io}(io, offset)
481
+ # @return [nil|false] treat as not implemented
482
+ # @raise [NotImplementedError, Errno::ENOTSUP] treat as not implemented
483
+ # @see FuseOperations#read_buf
431
484
 
432
485
  # @!method readlink(path, size)
433
486
  # @abstract
@@ -524,10 +577,18 @@ module FFI
524
577
  mod.prepend(Prepend)
525
578
  mod.include(Context)
526
579
  mod.include(Debug)
527
- mod.include(Safe)
528
580
  end
529
581
 
530
582
  class << self
583
+ # Helper to rescue not implemented or not supported errors from sub-filesystems
584
+ # @return[Object] result of block
585
+ # @return[nil] if block raises NotImplementedError or Errno::ENOTSUP
586
+ def rescue_not_implemented
587
+ yield
588
+ rescue NotImplementedError, Errno::ENOTSUP
589
+ nil
590
+ end
591
+
531
592
  # Helper for implementing {FuseOperations#readlink}
532
593
  # @param [FFI::Pointer] buf
533
594
  # @param [Integer] size
@@ -541,27 +602,25 @@ module FFI
541
602
  raise Errno::ENOTSUP unless link
542
603
  raise Errno::ENAMETOOLONG unless link.size < size # includes terminating NUL
543
604
 
544
- buf.put_string(link)
605
+ buf.put_string(0, link) # with NULL terminator.
545
606
  0
546
607
  end
547
608
 
548
609
  # Helper for implementing {FuseOperations#read}
549
610
  # @param [FFI::Pointer] buf
550
611
  # @param [Integer] size
551
- # @param [Integer] offset
552
- # @return [Integer] size of data read
612
+ # @param [Integer, nil] offset
613
+ # @return [Integer] returns the size of data read into buf
553
614
  # @yield []
554
- # @yieldreturn [String, #pread, #pwrite] the resulting data or IO like object
615
+ # @yieldreturn [String, IO] the resulting data or IO like object
555
616
  # @raise [Errno::ENOTSUP] if no data is returned
556
617
  # @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
618
+ # @see Libfuse::IO.read
619
+ def read(buf, size, offset = nil)
620
+ io = yield
621
+ raise Errno::ENOTSUP unless io
563
622
 
564
- data = data_to_str(data, size, offset)
623
+ data = Libfuse::IO.read(io, size, offset)
565
624
  raise Errno::ERANGE unless data.size <= size
566
625
 
567
626
  buf.write_bytes(data)
@@ -572,76 +631,68 @@ module FFI
572
631
  # @param [FFI::Pointer] bufp
573
632
  # @param [Integer] size
574
633
  # @param [Integer] offset
634
+ # @return [Integer] 0 for success
635
+ # @raise [Errno:ENOTSUP] if no data or io is returned
575
636
  # @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
637
+ # @yieldreturn [FuseBufVec] list of buffers to read from
638
+ # @yieldreturn [String, IO, Integer, :fileno] String, IO or file_descriptor to read from
639
+ # (see {FuseBufVec.create})
640
+ def read_buf(bufp, size, offset = nil)
641
+ io = yield
642
+ raise Errno::ENOTSUP unless io
643
+
644
+ fbv = io.is_a?(FuseBufVec) ? io : FuseBufVec.create(io, size, offset)
645
+ fbv.store_to(bufp)
581
646
 
582
- bufp.write_pointer(data_to_bufvec(data, size, offset).to_ptr)
583
647
  0
584
648
  end
585
649
 
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}
650
+ # Helper to implement #{FuseOperations#write}
651
+ # yields the data and receives expects IO to write the data to
652
+ # @param [FFI::Pointer] buf
588
653
  # @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)
654
+ # @yield(data)
655
+ # @yieldparam [String] data data to write
656
+ # @yieldreturn [nil|false] data has not been handled (raises Errno::ENOTSUP)
657
+ # @yieldreturn [Integer] number of bytes written (will not send to {IO.write})
658
+ # @yieldreturn [IO] to use with {IO.write}
659
+ # @return [Integer] number of bytes written
660
+ # @raise [Errno::ENOTSUP] if nothing is returned from yield
661
+ def write(buf, size)
662
+ data = buf.read_bytes(size) if buf.respond_to?(:read_bytes)
663
+ data ||= buf.to_s
595
664
 
596
- io.to_s
597
- end
665
+ io, offset = yield data
598
666
 
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)
667
+ raise Errno::ENOSUP unless io
668
+ return io if io.is_a?(Integer)
607
669
 
608
- str = data_to_str(data, size, offset)
609
- FuseBufVec.init(autorelease: false, size: str.size, mem: FFI::MemoryPointer.from_string(str))
670
+ Libfuse::IO.write(io, data, offset)
610
671
  end
611
672
 
612
- # Helper to implement #{FuseOperations#write}
613
- # @param [FFI::Pointer|:to_s] buf
614
- # @param [Integer] size
615
- # @return [Integer] size
673
+ # Helper to implement #{FuseOperations#write_buf}
674
+ #
675
+ # Yields firstly with data = nil
676
+ # A returned truthy object is sent to buvf.copy_to_io
677
+ # Otherwise yields again with string data from bufv.copy_to_str expecting the caller to write the data
678
+ # and return the number of bytes written
679
+ #
680
+ # @param [FuseBufVec] bufv
616
681
  # @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
682
+ # @yieldparam [nil] data first yield is nil
683
+ # @yieldparam [String] data second yield is the data
684
+ # @yieldreturn [nil, Array<IO,Integer,Symbol...>] io, [offset,] *flags
685
+ # for first yield can return nil to indicate it wants the data as a string (via second yield)
686
+ # alternative an object to receive the data via {FuseBufVec#copy_to_io}(io, offset = nil, *flags)
687
+ # @yieldreturn [Integer] second yield must return number of bytes written
688
+ # @return [Integer] number of bytes written
689
+ # @raise [Errno::ENOTSUP] if nothing is returned from either yield
690
+ def write_buf(bufv)
691
+ fh, *flags = yield nil # what kind of result do we want
692
+ return bufv.copy_to_io(fh, offset, *flags) if fh
693
+
694
+ data = bufv.copy_to_str(*flags)
695
+ yield data || (raise Errno::ENOTSUP)
645
696
  end
646
697
 
647
698
  # Helper for implementing {FuseOperations#getxattr}
@@ -4,55 +4,103 @@ module FFI
4
4
  module Libfuse
5
5
  module Adapter
6
6
  # Safe callbacks convert return values into integer responses, and rescues errors
7
- #
8
- # Applies to all callbacks except :init, :destroy
9
7
  module Safe
10
8
  # @!visibility private
11
9
  def fuse_wrappers(*wrappers)
12
10
  wrappers << {
13
- wrapper: proc { |fm, *args, **_, &b| Safe.safe_callback(fm, *args, default_errno: default_errno, &b) },
14
- excludes: %i[init destroy]
11
+ wrapper: proc { |fm, *args, **_, &b| safe_integer_callback(fm, *args, default_errno: default_errno, &b) },
12
+ excludes: FuseOperations::VOID_RETURN + FuseOperations::MEANINGFUL_RETURN
13
+ }
14
+ wrappers << {
15
+ wrapper: proc { |fm, *args, **_, &b|
16
+ safe_meaningful_integer_callback(fm, *args, default_errno: default_errno, &b)
17
+ },
18
+ includes: FuseOperations::MEANINGFUL_RETURN
19
+ }
20
+ wrappers << {
21
+ wrapper: proc { |fm, *args, **_, &b| safe_void_callback(fm, *args, &b) },
22
+ includes: FuseOperations::VOID_RETURN
15
23
  }
16
24
  return wrappers unless defined?(super)
17
25
 
18
26
  super(*wrappers)
19
27
  end
20
28
 
21
- # @return [Integer] the default errno. ENOTRECOVERABLE unless overridden
29
+ # @return [Integer] the default errno to return for rescued errors. ENOTRECOVERABLE unless overridden
22
30
  def default_errno
23
31
  defined?(super) ? super : Errno::ENOTRECOVERABLE::Errno
24
32
  end
25
33
 
26
34
  module_function
27
35
 
28
- # Process the results of yielding *args for the fuse_method callback
36
+ # Process the result of yielding to the fuse callback to provide a safe return value to libfuse
37
+ #
38
+ # For callbacks in {FuseOperations.VOID_RETURN}
39
+ #
40
+ # * the return value and any unexpected errors raised are ignored
41
+ #
42
+ # For callbacks in {FuseOperations.MEANINGFUL_RETURN}
29
43
  #
30
- # @yieldreturn [SystemCallError] expected callback errors rescued to return equivalent -ve errno value
31
- # @yieldreturn [StandardError,ScriptError] unexpected callback errors are rescued
32
- # to return -ve {default_errno} after emitting backtrace to #warn
44
+ # * should raise appropriate `Errno` error for expected errors (eg `Errno::ENOENT`)
45
+ # * must return a value convertable to an integer (via :to_i) - either a positive meaningful value
46
+ # or a negative errno value which will be returned directly
47
+ # * otherwise default_errno is returned
33
48
  #
34
- # @yieldreturn [Integer]
49
+ # For remaining path callbacks
35
50
  #
36
- # * -ve values returned directly
37
- # * +ve values returned directly for fuse_methods in {FuseOperations.MEANINGFUL_RETURN} list
38
- # * otherwise returns 0
51
+ # * should raise appropriate `Errno` error for expected errors (eg `Errno::ENOENT`)
52
+ # * may return a negative Integer (equivalent to raising the corresponding `SystemCallError`)
53
+ # * any other value returned is considered success and 0 is returned
54
+ # * unexpected errors raised are rescued and default_errno is returned
39
55
  #
40
- # @yieldreturn [Object] always returns 0 if no exception is raised
56
+ # @param [Integer] default_errno
57
+ # value to return for any unexpected errors
41
58
  #
59
+ # @return [nil]
60
+ # For void callbacks
61
+ # @return [Integer]
62
+ # For path callbacks, either 0 for success or a negative errno value
42
63
  def safe_callback(fuse_method, *args, default_errno: Errno::ENOTRECOVERABLE::Errno)
43
- result = yield(*args)
64
+ if FuseOperations::MEANINGFUL_RETURN.include?(fuse_method)
65
+ safe_meaningful_integer_callback(fuse_method, *args, default_errno: default_errno)
66
+ elsif FuseOperations::VOID_RETURN.include?(fuse_method)
67
+ safe_void_callback(fuse_method, *args)
68
+ else
69
+ safe_integer_callback(fuse_method, *args, default_errno: default_errno)
70
+ end
71
+ end
72
+
73
+ private
44
74
 
45
- return result.to_i if FuseOperations.meaningful_return?(fuse_method)
75
+ def safe_integer_callback(_, *args, default_errno: Errno::ENOTRECOVERABLE::Errno)
76
+ safe_errno(default_errno) do
77
+ result = yield(*args)
78
+ result.is_a?(Integer) && result.negative? ? result : 0
79
+ end
80
+ end
46
81
 
47
- 0
82
+ def safe_meaningful_integer_callback(_, *args, default_errno: Errno::ENOTRECOVERABLE::Errno)
83
+ safe_errno(default_errno) do
84
+ yield(*args).to_i
85
+ end
86
+ end
87
+
88
+ def safe_errno(default_errno)
89
+ yield
48
90
  rescue SystemCallError => e
49
91
  -e.errno
50
- rescue StandardError, ScriptError => e
51
- # rubocop:disable Layout/LineLength
52
- warn ["FFI::Libfuse error in #{fuse_method}", *e.backtrace.reverse, "#{e.class.name}:#{e.message}"].join("\n\t")
53
- # rubocop:enable Layout/LineLength
92
+ rescue StandardError, ScriptError
54
93
  -default_errno.abs
55
94
  end
95
+
96
+ # Process callbacks that return void, simply by swallowing unexpected errors
97
+ def safe_void_callback(_, *args)
98
+ yield(*args)
99
+ nil
100
+ rescue StandardError, ScriptError
101
+ # Swallow unexpected exceptions
102
+ nil
103
+ end
56
104
  end
57
105
  end
58
106
  end
@@ -45,8 +45,9 @@ module FFI
45
45
  delegate.respond_to?(method)
46
46
  end
47
47
 
48
- def wrap_callback(method, proc_wrapper = nil, wrapper: proc_wrapper, excludes: [], &block)
48
+ def wrap_callback(method, proc_wrapper = nil, wrapper: proc_wrapper, excludes: [], includes: nil, &block)
49
49
  return block if excludes.include?(method)
50
+ return block unless includes.nil? || includes.include?(method)
50
51
 
51
52
  # Wrapper proc takes fuse_method as first arg, but the resulting proc only takes the callback args
52
53
  # ie so wrappers should not yield the fuse_method onwards!!
@@ -5,7 +5,7 @@ require_relative '../../stat_vfs'
5
5
  module FFI
6
6
  module Libfuse
7
7
  module Filesystem
8
- # Helper for filesystem accounting
8
+ # Helper for filesystem accounting, ie to provide useful df output
9
9
  class Accounting
10
10
  OPTIONS = { 'max_space=' => :max_space, 'max_nodes=' => :max_nodes }.freeze
11
11