ffi-libfuse 0.3.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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