ffi-libfuse 0.3.4 → 0.4.1

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