ffi-libfuse 0.3.4 → 0.4.1

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/README.md +1 -1
  4. data/lib/ffi/accessors.rb +419 -93
  5. data/lib/ffi/boolean_int.rb +1 -1
  6. data/lib/ffi/devt.rb +36 -10
  7. data/lib/ffi/flock.rb +31 -27
  8. data/lib/ffi/libfuse/adapter/context.rb +1 -1
  9. data/lib/ffi/libfuse/adapter/debug.rb +54 -16
  10. data/lib/ffi/libfuse/adapter/fuse2_compat.rb +43 -26
  11. data/lib/ffi/libfuse/adapter/fuse3_support.rb +7 -8
  12. data/lib/ffi/libfuse/adapter/interrupt.rb +1 -1
  13. data/lib/ffi/libfuse/adapter/pathname.rb +1 -1
  14. data/lib/ffi/libfuse/adapter/ruby.rb +211 -160
  15. data/lib/ffi/libfuse/adapter/safe.rb +70 -22
  16. data/lib/ffi/libfuse/callbacks.rb +2 -1
  17. data/lib/ffi/libfuse/filesystem/accounting.rb +1 -1
  18. data/lib/ffi/libfuse/filesystem/mapped_files.rb +33 -7
  19. data/lib/ffi/libfuse/filesystem/pass_through_dir.rb +0 -1
  20. data/lib/ffi/libfuse/filesystem/virtual_dir.rb +294 -127
  21. data/lib/ffi/libfuse/filesystem/virtual_file.rb +85 -79
  22. data/lib/ffi/libfuse/filesystem/virtual_fs.rb +34 -15
  23. data/lib/ffi/libfuse/filesystem/virtual_link.rb +60 -0
  24. data/lib/ffi/libfuse/filesystem/virtual_node.rb +104 -87
  25. data/lib/ffi/libfuse/filesystem.rb +1 -1
  26. data/lib/ffi/libfuse/fuse2.rb +3 -2
  27. data/lib/ffi/libfuse/fuse3.rb +6 -6
  28. data/lib/ffi/libfuse/fuse_args.rb +14 -21
  29. data/lib/ffi/libfuse/fuse_buf.rb +112 -0
  30. data/lib/ffi/libfuse/fuse_buf_vec.rb +228 -0
  31. data/lib/ffi/libfuse/fuse_cmdline_opts.rb +19 -16
  32. data/lib/ffi/libfuse/fuse_common.rb +10 -4
  33. data/lib/ffi/libfuse/fuse_config.rb +35 -23
  34. data/lib/ffi/libfuse/fuse_conn_info.rb +1 -1
  35. data/lib/ffi/libfuse/fuse_context.rb +2 -1
  36. data/lib/ffi/libfuse/fuse_loop_config.rb +68 -20
  37. data/lib/ffi/libfuse/fuse_operations.rb +86 -41
  38. data/lib/ffi/libfuse/gem_helper.rb +2 -9
  39. data/lib/ffi/libfuse/io.rb +56 -0
  40. data/lib/ffi/libfuse/main.rb +33 -26
  41. data/lib/ffi/libfuse/test_helper.rb +67 -61
  42. data/lib/ffi/libfuse/version.rb +1 -1
  43. data/lib/ffi/libfuse.rb +1 -1
  44. data/lib/ffi/stat/native.rb +4 -4
  45. data/lib/ffi/stat.rb +35 -12
  46. data/lib/ffi/stat_vfs.rb +1 -2
  47. data/lib/ffi/struct_array.rb +2 -1
  48. data/lib/ffi/struct_wrapper.rb +6 -4
  49. data/sample/hello_fs.rb +1 -1
  50. metadata +6 -3
  51. data/lib/ffi/libfuse/fuse_buffer.rb +0 -257
@@ -1,43 +1,91 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../accessors'
4
+ require_relative '../boolean_int'
4
5
 
5
6
  module FFI
6
7
  module Libfuse
7
- # struct fuse_loop_config {
8
- # int clone_fd;
9
- # unsigned int max_idle_threads;
10
- # };
8
+ # For native fuse_loop_mt only
11
9
  class FuseLoopConfig < FFI::Struct
12
10
  include(FFI::Accessors)
13
11
 
14
- layout(
15
- clone_fd: :int,
16
- max_idle: :int
17
- )
18
-
19
- # @!attribute [rw] clone_fd
12
+ # @!attribute [w] clone_fd?
20
13
  # whether to use separate device fds for each thread (may increase performance)
21
14
  # Unused by ffi-libfuse as we do not call fuse_loop_mt
22
15
  # @return [Boolean]
23
- ffi_attr_reader(:clone_fd) do |v|
24
- v != 0
25
- end
26
-
27
- ffi_attr_writer(:clone_fd) do |v|
28
- v ? 1 : 0
29
- end
30
16
 
31
- # @!attribute [rw] max_idle_threads
17
+ # @!attribute [w] max_idle_threads
32
18
  # The maximum number of available worker threads before they start to get deleted when they become idle. If not
33
19
  # specified, the default is 10.
34
20
  #
35
21
  # Adjusting this has performance implications; a very small number of threads in the pool will cause a lot of
36
22
  # thread creation and deletion overhead and performance may suffer. When set to 0, a new thread will be created
37
23
  # to service every operation.
38
- #
24
+ # @deprecated at Fuse 3.12. Use max_threads instead
39
25
  # @return [Integer] the maximum number of threads to leave idle
40
- ffi_attr_accessor(:max_idle)
26
+
27
+ # @!attribute [w] max_threads
28
+ # @return [Integer]
29
+ # @since Fuse 3.12
30
+
31
+ if FUSE_VERSION >= 312
32
+ layout(
33
+ version_id: :int,
34
+ clone_fd: :bool_int,
35
+ max_idle_threads: :uint,
36
+ max_threads: :uint
37
+ )
38
+
39
+ Libfuse.attach_function :fuse_loop_cfg_create, [], by_ref
40
+ Libfuse.attach_function :fuse_loop_cfg_destroy, [:pointer], :void
41
+
42
+ ffi_attr_reader(:clone_fd?)
43
+ Libfuse.attach_function :fuse_loop_cfg_set_clone_fd, %i[pointer uint], :void
44
+ def clone_fd=(bool_val)
45
+ Libfuse.fuse_loop_cfg_set_clone_fd(to_ptr, bool_val ? 1 : 0)
46
+ end
47
+
48
+ ffi_attr_reader(:max_idle_threads)
49
+ Libfuse.attach_function :fuse_loop_cfg_set_idle_threads, %i[pointer uint], :uint
50
+ def max_idle_threads=(val)
51
+ Libfuse.fuse_loop_cfg_set_idle_threads(to_ptr, val) if val
52
+ end
53
+
54
+ Libfuse.attach_function :fuse_loop_cfg_set_max_threads, %i[pointer uint], :uint
55
+ ffi_attr_reader(:max_threads)
56
+ def max_threads=(val)
57
+ Libfuse.fuse_loop_cfg_set_max_threads(to_ptr, val) if val
58
+ end
59
+
60
+ class << self
61
+ def create(max_idle_threads: nil, max_threads: 10, clone_fd: false, **_)
62
+ cfg = Libfuse.fuse_loop_cfg_create
63
+ ObjectSpace.define_finalizer(cfg, finalizer(cfg.to_ptr))
64
+ cfg.clone_fd = clone_fd
65
+ cfg.max_idle_threads = max_idle_threads if max_idle_threads
66
+ cfg.max_threads = max_threads if max_threads
67
+ cfg
68
+ end
69
+
70
+ def finalizer(ptr)
71
+ proc { |_| Libfuse.fuse_loop_cfg_destroy(ptr) }
72
+ end
73
+ end
74
+ else
75
+ layout(
76
+ clone_fd: :bool_int,
77
+ max_idle_threads: :uint
78
+ )
79
+
80
+ ffi_attr_accessor(:clone_fd?)
81
+ ffi_attr_accessor(:max_idle_threads)
82
+
83
+ class << self
84
+ def create(clone_fd: false, max_idle_threads: 10, **_)
85
+ new.fill(max_idle_threads: max_idle_threads, clone_fd: clone_fd)
86
+ end
87
+ end
88
+ end
41
89
  end
42
90
  end
43
91
  end
@@ -1,19 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'fuse_version'
4
- require_relative '../ruby_object'
5
4
  require_relative 'fuse_conn_info'
6
- require_relative 'fuse_buffer'
5
+ require_relative 'fuse_config'
6
+ require_relative 'fuse_buf_vec'
7
7
  require_relative 'fuse_context'
8
8
  require_relative 'fuse_file_info'
9
9
  require_relative 'fuse_poll_handle'
10
+ require_relative 'fuse_callbacks'
11
+ require_relative '../ruby_object'
10
12
  require_relative '../stat_vfs'
11
13
  require_relative '../flock'
12
- require_relative 'thread_pool'
13
14
  require_relative '../stat'
14
- require_relative '../struct_array'
15
15
  require_relative '../encoding'
16
- require_relative 'fuse_callbacks'
17
16
 
18
17
  module FFI
19
18
  # Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
@@ -44,18 +43,80 @@ module FFI
44
43
  # All Callback methods are optional, but some are essential for a useful filesystem
45
44
  # e.g. {getattr},{readdir}
46
45
  #
47
- # Almost all callback operations take a path which can be of any length and will return 0 for success, or raise a
48
- # {::SystemCallError} on failure
46
+ # Almost all callback operations take a path which can be of any length and will return 0 for success or a negative
47
+ # `Errno` value on failure.
49
48
  #
50
49
  class FuseOperations < FFI::Struct
51
50
  include FuseCallbacks
52
51
 
52
+ # Callbacks that have no return value
53
+ VOID_RETURN = %i[init destroy].freeze
54
+
53
55
  # Callbacks that are expected to return meaningful positive integers
54
56
  MEANINGFUL_RETURN = %i[read write write_buf lseek copy_file_range getxattr listxattr].freeze
55
57
 
56
- # @return [Boolean] true if fuse_callback expects a meaningful integer return
57
- def self.meaningful_return?(fuse_callback)
58
- MEANINGFUL_RETURN.include?(fuse_callback)
58
+ # @!visibility private
59
+ # Methods to handle the path argument for most {path_callbacks}
60
+ NODE_PATH_METHODS = %i[first shift unshift].freeze
61
+
62
+ # @!visibility private
63
+ # Methods to handle the path argument for {link}, {symlink} and {rename}
64
+ LINK_PATH_METHODS = %i[last pop push].freeze
65
+
66
+ # @!visibility private
67
+ CALLBACK_PATH_ARG_METHODS = Hash.new(NODE_PATH_METHODS).merge(
68
+ {
69
+ link: LINK_PATH_METHODS,
70
+ symlink: LINK_PATH_METHODS,
71
+ rename: LINK_PATH_METHODS
72
+ }
73
+ ).freeze
74
+
75
+ class << self
76
+ # @return [Boolean] true if fuse_callback expects a meaningful integer return
77
+ def meaningful_return?(fuse_callback)
78
+ MEANINGFUL_RETURN.include?(fuse_callback)
79
+ end
80
+
81
+ # @return [Boolean] true if fuse_callback expects a void return
82
+ def void_return?(fuse_callback)
83
+ VOID_RETURN.include?(fuse_callback)
84
+ end
85
+
86
+ # Helper to determine how to handle the path argument for a path callback
87
+ # @param [Symbol] fuse_callback callback method name (must be one of #{path_callbacks})
88
+ # @return [Symbol, Symbol,Symbol] read, remove, add methods.
89
+ # [:last, :push, :pop] for :link, :symlink, :rename,
90
+ # [:first, :shift, :unshift] for everything else
91
+ # @example
92
+ # def wrap_callback(fuse_method, *args)
93
+ # read, remove, add = FFI::Libfuse::FuseOperations.path_arg_methods(fuse_method)
94
+ # path = args.send(read)
95
+ # # ... do something with path
96
+ #
97
+ # path = args.send(remove)
98
+ # # ... do something to make an alternate path
99
+ # args.send(add, adjusted_path)
100
+ # delegate.send(fuse_methoed, *args)
101
+ # end
102
+ def path_arg_methods(fuse_callback)
103
+ CALLBACK_PATH_ARG_METHODS[fuse_callback]
104
+ end
105
+
106
+ # @return [Set<Symbol>] list of callback methods
107
+ def fuse_callbacks
108
+ @fuse_callbacks ||= Set.new(members - [:flags])
109
+ end
110
+
111
+ # @return [Set<Symbol>] list of path callback methods
112
+ def path_callbacks
113
+ @path_callbacks ||= fuse_callbacks - VOID_RETURN
114
+ end
115
+ end
116
+
117
+ # @!visibility private
118
+ def fuse_callbacks
119
+ self.class.fuse_callbacks
59
120
  end
60
121
 
61
122
  # Container to dynamically build up the operations layout which is dependent on the loaded libfuse version
@@ -151,12 +212,11 @@ module FFI
151
212
  # int (*rmdir) (const char *);
152
213
  op[:rmdir] = []
153
214
 
154
- # @!method symlink(path,target)
215
+ # @!method symlink(target, path)
155
216
  # @abstract
156
217
  # Create a symbolic link
218
+ # @param [String] target
157
219
  # @param [String] path
158
- # @param [String] target the link target
159
- #
160
220
  # @return [Integer] 0 for success or -ve errno
161
221
 
162
222
  # int (*symlink) (const char *, const char *);
@@ -173,13 +233,13 @@ module FFI
173
233
  # int (*rename) (const char *, const char *);
174
234
  op[:rename] = [:fs_string]
175
235
 
176
- # @!method link(path,target)
236
+ # @!method link(target, path)
177
237
  # @abstract
178
238
  # Create a hard link to a file
179
- # @param [String] path
180
239
  # @param [String] target
181
- #
240
+ # @param [String] path
182
241
  # @return [Integer] 0 for success or -ve errno
242
+ # @see rename(2)
183
243
 
184
244
  # int (*link) (const char *, const char *);
185
245
  op[:link] = [:fs_string]
@@ -486,7 +546,6 @@ module FFI
486
546
  # fuse3: void *(*init) (struct fuse_conn_info *conn, struct fuse_config *cfg);
487
547
  op[:init] =
488
548
  if FUSE_MAJOR_VERSION >= 3
489
- require_relative 'fuse_config'
490
549
  callback([FuseConnInfo.by_ref, FuseConfig.by_ref], RubyObject)
491
550
  else
492
551
  callback([FuseConnInfo.by_ref], RubyObject)
@@ -618,7 +677,7 @@ module FFI
618
677
  #
619
678
 
620
679
  # int (*utimens) (const char *, const struct timespec tv[2]);
621
- op[:utimens] = [FFI::Stat::TimeSpec.array(2)]
680
+ op[:utimens] = [FFI::Stat::TimeSpec[2]]
622
681
  op[:utimens] << FuseFileInfo.by_ref if FUSE_MAJOR_VERSION >= 3
623
682
 
624
683
  # @!method bmap(path,blocksize,index)
@@ -655,17 +714,19 @@ module FFI
655
714
  # release, fsync, readdir, releasedir, fsyncdir, ftruncate, fgetattr, lock, ioctl and poll
656
715
  #
657
716
  # Closely related to flag_nullpath_ok, but if this flag is set then the path will not be calculaged even if
658
- # the file wasnt unlinked. However the path can still be non-NULL if it needs to be calculated for some other
659
- # reason.
717
+ # the file wasn't unlinked. However the path can still be non-NULL if it needs to be calculated for some
718
+ # other reason.
660
719
  #
661
720
  # - :utime_omit_ok
662
721
  #
663
722
  # Flag indicating that the filesystem accepts special UTIME_NOW and UTIME_OMIT values in its utimens
664
723
  # operation.
665
724
  #
666
- # @return [Array[]Symbol>] a list of flags to set capabilities
725
+ # @return [Array<Symbol>] a list of flags to set capabilities
667
726
  # @note Not available in Fuse3
668
727
  # @deprecated in Fuse3 use fuse_config object in {init}
728
+
729
+ # flags
669
730
  op[:flags] = :flags_mask if FUSE_MAJOR_VERSION < 3
670
731
 
671
732
  if FUSE_VERSION >= 28
@@ -717,7 +778,7 @@ module FFI
717
778
  # @abstract
718
779
  # Write contents of buffer to an open file
719
780
  #
720
- # Similar to the write() method, but data is supplied in a generic buffer.
781
+ # Similar to the {write} method, but data is supplied in a generic buffer.
721
782
  # Use {FuseBufVec#copy_to_fd} to copy data to an open file descriptor, or {FuseBufVec#copy_to_str} to extract
722
783
  # string data from the buffer
723
784
  #
@@ -734,16 +795,15 @@ module FFI
734
795
  # @!method read_buf(path,bufp,size,offset,fuse_file_info)
735
796
  # @abstract
736
797
  #
737
- # Similar to the read() method, but data is stored and returned in a generic buffer.
798
+ # Similar to the {read} method, but data is stored and returned in a generic buffer.
738
799
  #
739
800
  # No actual copying of data has to take place, the source file descriptor may simply be stored in the buffer
740
801
  # for later data transfer.
741
802
  #
742
803
  # @param [String] path
743
804
  # @param [FFI::Pointer<FuseBufVec>] bufp
744
- # The buffer must be allocated dynamically and stored at the location pointed to by bufp. If the buffer
745
- # contains memory regions, they too must be allocated using malloc(). The allocated memory will be freed by
746
- # the caller.
805
+ # The buffer must be allocated dynamically ({FuseBufVec.init})
806
+ # and stored at the location pointed to by bufp (see {FuseBufVec#store_to}(bufp)).
747
807
  # @param [Integer] size
748
808
  # @param [Integer] offset
749
809
  # @param [FuseFileInfo] fuse_file_info
@@ -886,21 +946,6 @@ module FFI
886
946
  fuse_flags.concat(delegate.fuse_flags) if delegate.respond_to?(:fuse_flags)
887
947
  send(:[]=, :flags, fuse_flags.uniq)
888
948
  end
889
-
890
- # @!visibility private
891
- def fuse_callbacks
892
- self.class.fuse_callbacks
893
- end
894
-
895
- # @return [Set<Symbol>] list of callback methods
896
- def self.fuse_callbacks
897
- @fuse_callbacks ||= Set.new(members - [:flags])
898
- end
899
-
900
- # @return [Set<Symbol>] list of path callback methods
901
- def self.path_callbacks
902
- @path_callbacks ||= fuse_callbacks - %i[init destroy]
903
- end
904
949
  end
905
950
  end
906
951
  end
@@ -5,13 +5,6 @@ module FFI
5
5
  module Libfuse
6
6
  # @!visibility private
7
7
  class GemHelper
8
- SEMVER_TAG_REGEX = /^v\d+\.\d+\.\d+/.freeze
9
-
10
- # branches the format is refs/heads/<branch_name>,
11
- # tags it is refs/tags/<tag_name>.
12
- # for pull requests it is refs/pull/<pr_number>/merge,
13
- GIT_REF_TYPES = { 'heads' => :branch, 'tags' => :tag, 'pull' => :pull }.freeze
14
-
15
8
  class << self
16
9
  # set when install'd.
17
10
  attr_accessor :instance
@@ -33,7 +26,7 @@ module FFI
33
26
  return [ref, nil] unless ref&.start_with?('refs/')
34
27
 
35
28
  _refs, ref_type, ref_name = ref.split('/', 3)
36
- [ref_name, GIT_REF_TYPES[ref_type]]
29
+ [ref_name, { 'heads' => :branch, 'tags' => :tag, 'pull' => :pull }[ref_type]]
37
30
  end
38
31
 
39
32
  def gem_version(main_branch:, version:, env: ENV)
@@ -44,7 +37,7 @@ module FFI
44
37
  when :branch
45
38
  ref_name == main_branch ? [version] : [version, ref_name]
46
39
  when :tag
47
- SEMVER_TAG_REGEX.match?(ref_name) ? [ref_name[1..]] : [version, ref_name]
40
+ /^v\d+\.\d+\.\d+/.match?(ref_name) ? [ref_name[1..]] : [version, ref_name]
48
41
  when :pull
49
42
  pr_number, merge, _rest = ref_name.split('/')
50
43
  # GITHUB_BASE_REF The name of the base ref or target branch of the pull request in a workflow run
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FFI
4
+ module Libfuse
5
+ # Helpers for reading/writing to io like objects
6
+ module IO
7
+ class << self
8
+ # Helper to convert a ruby object to size bytes required by {FuseOperations#read}
9
+ # @param [#pread, #seek & #read, #to_s] io the source to read from, either...
10
+ #
11
+ # * an {::IO} like object via :pread(size, offset) or :seek(offset) then :read(size)
12
+ # * or the String from :to_s
13
+ #
14
+ # @param [Integer] size
15
+ # @param [Integer, nil] offset
16
+ # if nil the io is assumed to be already positioned
17
+ # @return [String] the extracted data
18
+ def read(io, size, offset = nil)
19
+ return io.pread(size, offset) if offset && io.respond_to?(:pread)
20
+
21
+ if (offset ? %i[seek read] : %i[read]).all? { |m| io.respond_to?(m) }
22
+ io.seek(offset) if offset
23
+ return io.read(size)
24
+ end
25
+
26
+ io.to_s[offset || 0, size] || ''
27
+ end
28
+
29
+ # Helper to write date to {::IO} or {::String} like objects for use with #{FuseOperations#write}
30
+ # @param [#pwrite, #seek & #write, #[]=] io an object that accepts String data via...
31
+ #
32
+ # * ```ruby io.pwrite(data, offset)```
33
+ # * ```ruby io.seek(offset) ; io.write(data)```
34
+ # * ```ruby io[offset, data.size] = data```
35
+ # @param [String] data
36
+ # @param [nil, Integer] offset
37
+ # if not nil start start writing at this position in io
38
+ # @return [Integer] number of bytes written
39
+ # @raise [Errno::EBADF] if io does not support the requisite methods
40
+ def write(io, data, offset = nil)
41
+ if offset && io.respond_to?(:pwrite)
42
+ io.pwrite(data, offset)
43
+ elsif (offset ? %i[seek write] : %i[write]).all? { |m| io.respond_to?(m) }
44
+ io.seek(offset) if offset
45
+ io.write(data)
46
+ elsif io.respond_to?(:[]=) # eg String
47
+ io[offset || 0, data.size] = data
48
+ data.size
49
+ else
50
+ raise "cannot :pwrite or :write to #{io}", Errno::EBADF
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -24,15 +24,13 @@ module FFI
24
24
 
25
25
  # Main function of FUSE
26
26
  #
27
- # This function:
28
- #
29
27
  # - parses command line options - see {fuse_parse_cmdline}
30
- # exiting immediately if help or version options were processed
31
- # - calls {#fuse_debug}, {#fuse_options}, {#fuse_configure} if implemented by operations
32
- # - installs signal handlers for INT, HUP, TERM to unmount and exit filesystem
33
- # - installs custom signal handlers if operations implements {fuse_traps}
34
- # - creates a fuse handle mounted with registered operations - see {fuse_create}
35
- # - calls either the single-threaded (option -s) or the multi-threaded event loop - see {FuseCommon#run}
28
+ # - calls {#fuse_configure} if implemented by operations
29
+ # - creates a fuse handle see {fuse_create}
30
+ # - returns 0 if help or version options were processed (ie after all messages have been printed by libfuse)
31
+ # - returns 2 if fuse handle is not successfully mounted
32
+ # - calls {#fuse_traps} if implemented by operations
33
+ # - calls run on the fuse handle with options from previous steps- see {FuseCommon#run}
36
34
  #
37
35
  # @param [Array<String>] argv mount.fuse arguments
38
36
  # expects progname, mountpoint, options....
@@ -46,23 +44,23 @@ module FFI
46
44
  # @return [Integer] suitable for process exit code
47
45
  def fuse_main(*argv, operations:, args: argv.any? ? argv : default_args, private_data: nil)
48
46
  run_args = fuse_parse_cmdline(args: args, handler: operations)
49
- return 2 unless run_args
50
47
 
51
48
  fuse_args = run_args.delete(:args)
52
49
  mountpoint = run_args.delete(:mountpoint)
53
50
 
54
- return 3 unless fuse_configure(operations: operations, **run_args)
51
+ show_only = run_args[:show_help] || run_args[:show_version]
52
+
53
+ return 3 if !show_only && !fuse_configure(operations)
55
54
 
56
55
  warn "FuseCreate: mountpoint: #{mountpoint}, args: [#{fuse_args.argv.join(' ')}]" if run_args[:debug]
57
56
  warn "FuseRun: #{run_args}" if run_args[:debug]
58
57
 
59
58
  fuse = fuse_create(mountpoint, args: fuse_args, operations: operations, private_data: private_data)
60
59
 
61
- return 0 if run_args[:show_help] || run_args[:show_version]
60
+ return 0 if show_only
62
61
  return 2 if !fuse || !mountpoint
63
62
 
64
- return unless fuse
65
-
63
+ run_args[:traps] = operations.fuse_traps if operations.respond_to?(:fuse_traps)
66
64
  fuse.run(**run_args)
67
65
  end
68
66
  alias main fuse_main
@@ -72,7 +70,6 @@ module FFI
72
70
  # - parses standard command line options (-d -s -h -V)
73
71
  # will call {fuse_debug}, {fuse_version}, {fuse_help} if implemented by handler
74
72
  # - calls {fuse_options} for custom option processing if implemented by handler
75
- # - records signal handlers if operations implements {fuse_traps}
76
73
  # - parses standard fuse mount options
77
74
  #
78
75
  # @param [Array<String>] argv mount.fuse arguments
@@ -88,24 +85,25 @@ module FFI
88
85
  # * show_version [Boolean]: -v or --version
89
86
  # * debug [Boolean]: -d
90
87
  # * others are options to pass to {FuseCommon#run}
88
+ # @return [nil] if args are not parsed successfully
91
89
  def fuse_parse_cmdline(*argv, args: argv.any? ? argv : default_args, handler: nil)
92
90
  args = fuse_init_args(args)
93
91
 
94
92
  # Parse args and print cmdline help
95
93
  run_args = Fuse.parse_cmdline(args, handler: handler)
96
- return nil unless run_args
97
94
 
98
- return nil if handler.respond_to?(:fuse_options) && !handler.fuse_options(args)
95
+ handler.fuse_options(args) if handler.respond_to?(:fuse_options)
99
96
 
100
- run_args[:traps] = handler.fuse_traps if handler.respond_to?(:fuse_traps)
101
-
102
- return nil unless parse_run_options(args, run_args)
97
+ parse_run_options(args, run_args)
103
98
 
104
99
  run_args[:args] = args
105
100
  run_args
101
+ rescue Error
102
+ nil
106
103
  end
107
104
 
108
- # @return [FuseCommon|nil] the mounted filesystem or nil if not mounted
105
+ # @return [FuseCommon] the mounted filesystem handle
106
+ # @return [nil] if not mounted (eg due to --help or --version, or an error)
109
107
  def fuse_create(mountpoint, *argv, operations:, args: nil, private_data: nil)
110
108
  args = fuse_init_args(args || argv)
111
109
 
@@ -116,8 +114,8 @@ module FFI
116
114
  end
117
115
 
118
116
  # @!visibility private
119
- def fuse_configure(operations:, show_help: false, show_version: false, **_)
120
- return true unless operations.respond_to?(:fuse_configure) && !show_help && !show_version
117
+ def fuse_configure(operations)
118
+ return true unless operations.respond_to?(:fuse_configure)
121
119
 
122
120
  # Provide sensible values for FuseContext in case this is referenced during configure
123
121
  FFI::Libfuse::FuseContext.overrides do
@@ -166,7 +164,8 @@ module FFI
166
164
  # @abstract
167
165
  # Called to allow filesystem to handle custom options and observe standard mount options #
168
166
  # @param [FuseArgs] args
169
- # @return [Boolean] true if args parsed successfully
167
+ # @raise [Error] if there is an error parsing the options
168
+ # @return [void]
170
169
  # @see FuseArgs#parse!
171
170
  # @example
172
171
  # OPTIONS = { 'config=' => :config, '-c ' => :config }
@@ -174,7 +173,7 @@ module FFI
174
173
  # args.parse!(OPTIONS) do |key:, value:, out:, **opts|
175
174
  #
176
175
  # # raise errors for invalid config
177
- # raise FFI::Libfuse::FuseArgs::Error, "Invalid config" unless valid_config?(key,value)
176
+ # raise FFI::Libfuse::Error, "Invalid config" unless valid_config?(key,value)
178
177
  #
179
178
  # # Configure the file system
180
179
  # @config = value if key == :config
@@ -189,15 +188,22 @@ module FFI
189
188
 
190
189
  # @!method fuse_traps
191
190
  # @abstract
192
- # @return [Hash<String|Symbol|Integer,String|Proc>]
191
+ # Passed to {FuseCommon#run} to allow filesystem to handle custom signal traps. These traps
192
+ # are merged over those from {FuseCommon#default_traps}. A nil value can be used to avoid a default trap
193
+ # being set.
194
+ # @return [Hash<String|Symbol|Integer,String|Proc|nil>]
193
195
  # map of signal name or number to signal handler as per Signal.trap
194
196
  # @example
195
197
  # def fuse_traps
196
- # { HUP: ->() { reload() }}
198
+ # {
199
+ # HUP: ->() { reload() },
200
+ # INT: nil
201
+ # }
197
202
  # end
198
203
 
199
204
  # @!method fuse_version
200
205
  # @abstract
206
+ # Called as part of generating output for the -V option
201
207
  # @return [String] a custom version string to output with -V option
202
208
 
203
209
  # @!method fuse_help
@@ -214,6 +220,7 @@ module FFI
214
220
  # @!method fuse_configure
215
221
  # @abstract
216
222
  # Called immediately before the filesystem is mounted, after options have been parsed
223
+ # (eg to validate required options)
217
224
  #
218
225
  # @raise [Error] to prevent the mount from proceeding
219
226
  # @return [void]