ffi-libfuse 0.0.1.rctest12 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +3 -1
  3. data/CHANGELOG.md +60 -0
  4. data/LICENSE +21 -0
  5. data/README.md +127 -44
  6. data/lib/ffi/accessors.rb +6 -6
  7. data/lib/ffi/boolean_int.rb +27 -0
  8. data/lib/ffi/devt.rb +23 -0
  9. data/lib/ffi/encoding.rb +38 -0
  10. data/lib/ffi/flock.rb +7 -5
  11. data/lib/ffi/gnu_extensions.rb +1 -1
  12. data/lib/ffi/libfuse/ackbar.rb +3 -3
  13. data/lib/ffi/libfuse/adapter/context.rb +12 -10
  14. data/lib/ffi/libfuse/adapter/fuse2_compat.rb +52 -51
  15. data/lib/ffi/libfuse/adapter/fuse3_support.rb +7 -4
  16. data/lib/ffi/libfuse/adapter/interrupt.rb +1 -1
  17. data/lib/ffi/libfuse/adapter/ruby.rb +499 -148
  18. data/lib/ffi/libfuse/adapter/safe.rb +12 -11
  19. data/lib/ffi/libfuse/adapter.rb +1 -2
  20. data/lib/ffi/libfuse/callbacks.rb +1 -1
  21. data/lib/ffi/libfuse/filesystem/accounting.rb +116 -0
  22. data/lib/ffi/libfuse/filesystem/mapped_dir.rb +74 -0
  23. data/lib/ffi/libfuse/filesystem/mapped_files.rb +141 -0
  24. data/lib/ffi/libfuse/filesystem/pass_through_dir.rb +55 -0
  25. data/lib/ffi/libfuse/filesystem/pass_through_file.rb +45 -0
  26. data/lib/ffi/libfuse/filesystem/utils.rb +102 -0
  27. data/lib/ffi/libfuse/filesystem/virtual_dir.rb +306 -0
  28. data/lib/ffi/libfuse/filesystem/virtual_file.rb +94 -0
  29. data/lib/ffi/libfuse/filesystem/virtual_fs.rb +196 -0
  30. data/lib/ffi/libfuse/filesystem/virtual_node.rb +101 -0
  31. data/lib/ffi/libfuse/filesystem.rb +25 -0
  32. data/lib/ffi/libfuse/fuse2.rb +32 -24
  33. data/lib/ffi/libfuse/fuse3.rb +28 -18
  34. data/lib/ffi/libfuse/fuse_args.rb +71 -34
  35. data/lib/ffi/libfuse/fuse_buffer.rb +128 -26
  36. data/lib/ffi/libfuse/fuse_callbacks.rb +1 -5
  37. data/lib/ffi/libfuse/fuse_common.rb +60 -61
  38. data/lib/ffi/libfuse/fuse_config.rb +134 -143
  39. data/lib/ffi/libfuse/fuse_conn_info.rb +310 -134
  40. data/lib/ffi/libfuse/fuse_context.rb +45 -3
  41. data/lib/ffi/libfuse/fuse_operations.rb +57 -21
  42. data/lib/ffi/libfuse/fuse_opt.rb +1 -1
  43. data/lib/ffi/libfuse/fuse_version.rb +10 -6
  44. data/lib/ffi/libfuse/gem_version.rb +54 -0
  45. data/lib/ffi/libfuse/main.rb +96 -48
  46. data/lib/ffi/libfuse/test_helper.rb +145 -0
  47. data/lib/ffi/libfuse/version.rb +1 -1
  48. data/lib/ffi/libfuse.rb +13 -4
  49. data/lib/ffi/ruby_object.rb +4 -1
  50. data/lib/ffi/stat/constants.rb +9 -0
  51. data/lib/ffi/stat/native.rb +36 -6
  52. data/lib/ffi/stat/time_spec.rb +26 -10
  53. data/lib/ffi/stat.rb +111 -22
  54. data/lib/ffi/stat_vfs.rb +59 -1
  55. data/lib/ffi/struct_wrapper.rb +22 -1
  56. data/sample/hello_fs.rb +54 -0
  57. data/sample/memory_fs.rb +5 -181
  58. data/sample/no_fs.rb +20 -21
  59. data/sample/pass_through_fs.rb +30 -0
  60. metadata +83 -10
  61. data/lib/ffi/libfuse/adapter/thread_local_context.rb +0 -36
  62. data/lib/ffi/libfuse/test/operations.rb +0 -56
  63. data/lib/ffi/libfuse/test.rb +0 -3
@@ -12,28 +12,36 @@ require_relative '../flock'
12
12
  require_relative 'thread_pool'
13
13
  require_relative '../stat'
14
14
  require_relative '../struct_array'
15
+ require_relative '../encoding'
15
16
  require_relative 'fuse_callbacks'
16
17
 
17
18
  module FFI
18
19
  # Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
19
20
  module Libfuse
21
+ # All paths are encoded in ruby's view of the filesystem encoding
22
+ typedef Encoding.for('filesystem'), :fs_string
23
+
20
24
  # typedef int (*fuse_fill_dir_t) (void *buf, const char *name, const struct stat *stbuf, off_t off);
21
- fill_dir_t_args = [:pointer, :string, Stat.by_ref, :off_t]
25
+ fill_dir_t_args = [:pointer, :fs_string, Stat.by_ref, :off_t]
22
26
  if FUSE_MAJOR_VERSION > 2
23
27
  enum :fuse_readdir_flags, [:fuse_readdir_plus, (1 << 0)]
24
28
  enum :fuse_fill_dir_flags, [:fuse_fill_dir_plus, (1 << 1)]
25
29
  fill_dir_t_args << :fuse_fill_dir_flags
26
30
  end
27
31
 
32
+ bitmask :fuse_ioctl_flags, %i[compat unrestricted retry dir]
28
33
  bitmask :lock_op, [:lock_sh, 0, :lock_ex, 2, :lock_nb, 4, :lock_un, 8]
29
34
  bitmask :falloc_mode, %i[keep_size punch_hole no_hide_stale collapse_range zero_range insert_range unshare_range]
30
35
  bitmask :flags_mask, %i[nullpath_ok nopath utime_omit_ok] if FUSE_MAJOR_VERSION < 3
31
- enum :xattr, [:xattr_create, 1, :xattr_replace]
36
+
37
+ # @!visibility private
38
+ XAttr = enum :xattr, [:xattr_create, 1, :xattr_replace]
39
+
32
40
  callback :fill_dir_t, fill_dir_t_args, :int
33
41
 
34
42
  # The file system operations as specified in libfuse.
35
43
  #
36
- # All Callback and Configuration methods are optional, but some are essential for a useful filesystem
44
+ # All Callback methods are optional, but some are essential for a useful filesystem
37
45
  # e.g. {getattr},{readdir}
38
46
  #
39
47
  # Almost all callback operations take a path which can be of any length and will return 0 for success, or raise a
@@ -42,6 +50,14 @@ module FFI
42
50
  class FuseOperations < FFI::Struct
43
51
  include FuseCallbacks
44
52
 
53
+ # Callbacks that are expected to return meaningful positive integers
54
+ MEANINGFUL_RETURN = %i[read write write_buf lseek copy_file_range getxattr listxattr].freeze
55
+
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)
59
+ end
60
+
45
61
  # Container to dynamically build up the operations layout which is dependent on the loaded libfuse version
46
62
  op = {}
47
63
 
@@ -144,7 +160,7 @@ module FFI
144
160
  # @return [Integer] 0 for success or -ve errno
145
161
 
146
162
  # int (*symlink) (const char *, const char *);
147
- op[:symlink] = [:string]
163
+ op[:symlink] = [:fs_string]
148
164
 
149
165
  # @!method rename(from_path,to_path)
150
166
  # @abstract
@@ -155,7 +171,7 @@ module FFI
155
171
  # @return [Integer] 0 for success or -ve errno
156
172
 
157
173
  # int (*rename) (const char *, const char *);
158
- op[:rename] = [:string]
174
+ op[:rename] = [:fs_string]
159
175
 
160
176
  # @!method link(path,target)
161
177
  # @abstract
@@ -166,7 +182,7 @@ module FFI
166
182
  # @return [Integer] 0 for success or -ve errno
167
183
 
168
184
  # int (*link) (const char *, const char *);
169
- op[:link] = [:string]
185
+ op[:link] = [:fs_string]
170
186
 
171
187
  # @!method chmod(path,mode,fuse_file_info=nil)
172
188
  # @abstract
@@ -208,6 +224,7 @@ module FFI
208
224
  op[:truncate] = [:off_t]
209
225
  op[:truncate] << FuseFileInfo.by_ref if FUSE_MAJOR_VERSION >= 3
210
226
 
227
+ # Not directly implemented see utimens
211
228
  # int (*utime) (const char *, struct utimbuf *);
212
229
  op[:utime] = [:pointer] if FUSE_MAJOR_VERSION < 3
213
230
 
@@ -563,7 +580,7 @@ module FFI
563
580
  # @param [String] path
564
581
  # @param [FuseFileInfo] fuse_file_info
565
582
  # For checking lock ownership, the 'fuse_file_info->owner' argument must be used.
566
- # @param [Symbol] cmd either :f_getlck, :f_setlck or :f_setlkw.
583
+ # @param [Symbol] cmd either :getlck, :setlck or :setlkw.
567
584
  # @param [Flock] flock
568
585
  # For the meaning of fields in 'struct flock' see the man page for fcntl(2). The whence field will always
569
586
  # be set to :seek_set.
@@ -660,18 +677,21 @@ module FFI
660
677
  # @param [Integer] cmd
661
678
  # @param [FFI::Pointer] arg
662
679
  # @param [FuseFileInfo] fuse_file_info
663
- # @param [Integer] flags
664
- # @param [FFI::Pointer] data
680
+ # @param [Array<Symbol>] flags
665
681
  #
666
- # flags will have FUSE_IOCTL_COMPAT set for 32bit ioctls in 64bit environment. The size and direction of data
667
- # is determined by _IOC_*() decoding of cmd. For _IOC_NONE, data will be NULL, for _IOC_WRITE data is out
668
- # area, for _IOC_READ in area and if both are set in/out area. In all non-NULL cases, the area is of
669
- # _IOC_SIZE(cmd) bytes.
682
+ # - :compat 32bit compat ioctl on 64bit machine
683
+ # - :unrestricted not restricted to well-formed ioctls, retry allowed (lowlevel fuse)
684
+ # - :retry retry with new iovecs (lowlevel fuse)
685
+ # - :dir is a directory file handle
670
686
  #
671
- # If flags has FUSE_IOCTL_DIR then the fuse_file_info refers to a directory file handle.
687
+ # @param [FFI::Pointer] data
688
+ #
689
+ # The size and direction of data is determined by _IOC_*() decoding of cmd. For _IOC_NONE, data will be NULL,
690
+ # for _IOC_WRITE data is out area, for _IOC_READ in area and if both are set in/out area. In all non-NULL
691
+ # cases, the area is of _IOC_SIZE(cmd) bytes.
672
692
 
673
693
  # int (*ioctl) (const char *, int cmd, void *arg, struct fuse_file_info *, unsigned int flags, void *data);
674
- op[:ioctl] = [:int, :pointer, FuseFileInfo.by_ref, :uint, :pointer]
694
+ op[:ioctl] = [:int, :pointer, FuseFileInfo.by_ref, :fuse_ioctl_flags, :pointer]
675
695
 
676
696
  # @!method poll(path,fuse_file_info,ph,reventsp)
677
697
  # @abstract
@@ -697,8 +717,9 @@ module FFI
697
717
  # @abstract
698
718
  # Write contents of buffer to an open file
699
719
  #
700
- # Similar to the write() method, but data is supplied in a generic buffer. Use fuse_buf_copy() to transfer
701
- # data to the destination.
720
+ # Similar to the write() method, but data is supplied in a generic buffer.
721
+ # Use {FuseBufVec#copy_to_fd} to copy data to an open file descriptor, or {FuseBufVec#copy_to_str} to extract
722
+ # string data from the buffer
702
723
  #
703
724
  # @param [String] path
704
725
  # @param [FuseBufVec] buf
@@ -804,8 +825,8 @@ module FFI
804
825
  # );
805
826
  op[:copy_file_range] =
806
827
  callback [
807
- :string, FuseFileInfo.by_ref, :off_t,
808
- :string, FuseFileInfo.by_ref, :off_t,
828
+ :fs_string, FuseFileInfo.by_ref, :off_t,
829
+ :fs_string, FuseFileInfo.by_ref, :off_t,
809
830
  :size_t, :int
810
831
  ], :ssize_t
811
832
 
@@ -820,7 +841,7 @@ module FFI
820
841
  # @see lseek(2)
821
842
 
822
843
  # off_t (*lseek) (const char *, off_t off, int whence, struct fuse_file_info *);
823
- op[:lseek] = callback [:string, :off_t, Flock::Enums::SeekWhence, FuseFileInfo.by_ref], :off_t
844
+ op[:lseek] = callback [:fs_string, :off_t, Flock::Enums::SeekWhence, FuseFileInfo.by_ref], :off_t
824
845
  end
825
846
  end
826
847
 
@@ -829,7 +850,7 @@ module FFI
829
850
  layout_data = op.transform_values do |v|
830
851
  if v.is_a?(Array) && !v.last.is_a?(Integer)
831
852
  # A typical fuse callback
832
- callback([:string] + v, :int)
853
+ callback([:fs_string] + v, :int)
833
854
  else
834
855
  v
835
856
  end
@@ -865,6 +886,21 @@ module FFI
865
886
  fuse_flags.concat(delegate.fuse_flags) if delegate.respond_to?(:fuse_flags)
866
887
  send(:[]=, :flags, fuse_flags.uniq)
867
888
  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
868
904
  end
869
905
  end
870
906
  end
@@ -19,7 +19,7 @@ module FFI
19
19
  def fill(template, value)
20
20
  str_ptr = FFI::MemoryPointer.from_string(template)
21
21
  self[:template] = str_ptr
22
- self[:offset] = (2**(8 * FFI::Type::INT.size)) - 1 # -(1U) in a LONG!!
22
+ self[:offset] = (2**(FFI::Type::INT.size * 8)) - 1 # -(1U) in a LONG!!
23
23
  self[:value] = value.to_i
24
24
  self
25
25
  end
@@ -9,15 +9,18 @@ module FFI
9
9
  module Libfuse
10
10
  extend FFI::Library
11
11
 
12
+ libs =
13
+ case FFI::Platform::NAME
14
+ when 'x86_64-darwin'
15
+ %w[libfuse.2.dylib]
16
+ else
17
+ %w[libfuse3.so.3 libfuse.so.2]
18
+ end
12
19
  # The fuse library to load from 'LIBFUSE' environment variable if set, otherwise prefer Fuse3 over Fuse2
13
- LIBFUSE = ENV['LIBFUSE'] || %w[libfuse3.so.3 libfuse.so.2]
20
+ LIBFUSE = ENV.fetch('LIBFUSE', libs)
14
21
  ffi_lib(LIBFUSE)
15
22
 
16
- # @!scope class
17
- # @!method fuse_version()
18
- # @return [Integer] the fuse version
19
- # See {FUSE_VERSION} which captures this result in a constant
20
-
23
+ # @!visibility private
21
24
  attach_function :fuse_version, [], :int
22
25
 
23
26
  # prior to 3.10 this is Major * 10 + Minor, after 3.10 and later is Major * 100 + Minor
@@ -36,6 +39,7 @@ module FFI
36
39
  require_relative '../gnu_extensions'
37
40
 
38
41
  extend(GNUExtensions)
42
+
39
43
  # libfuse2 has busted symbols
40
44
  ffi_lib_versions(%w[FUSE_2.9.1 FUSE_2.9 FUSE_2.8 FUSE_2.7 FUSE_2.6 FUSE_2.5 FUSE_2.4 FUSE_2.3 FUSE_2.2])
41
45
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'version'
4
+
5
+ module FFI
6
+ # Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
7
+ module Libfuse
8
+ # @!visibility private
9
+
10
+ SEMVER_TAG_REGEX = /^v\d+\.\d+\.\d+/.freeze
11
+
12
+ # branches the format is refs/heads/<branch_name>,
13
+ # tags it is refs/tags/<tag_name>.
14
+ # for pull requests it is refs/pull/<pr_number>/merge,
15
+ GIT_REF_TYPES = { 'heads' => :branch, 'tags' => :tag, 'pull' => :pull }.freeze
16
+
17
+ def self.git_ref(env: ENV)
18
+ ref = env.fetch('GIT_REF') do
19
+ # get branch ref, or detached head ref to tag
20
+ `git symbolic-ref HEAD 2>/dev/null || git name-rev HEAD | awk '{ gsub(/[\\^~@].*$/,"",$2); printf("refs/%s\\n",$2)}'`.strip # rubocop:disable Layout/LineLength
21
+ rescue StandardError
22
+ nil
23
+ end
24
+
25
+ return [ref, nil] unless ref&.start_with?('refs/')
26
+
27
+ _refs, ref_type, ref_name = ref.split('/', 3)
28
+ [ref_name, GIT_REF_TYPES[ref_type]]
29
+ end
30
+
31
+ def self.gem_version(main_branch: 'main', version: VERSION, env: ENV)
32
+ ref_name, ref_type = git_ref(env: env)
33
+
34
+ version =
35
+ case ref_type
36
+ when :branch
37
+ ref_name == main_branch ? [version] : [version, ref_name]
38
+ when :tag
39
+ SEMVER_TAG_REGEX.match?(ref_name) ? [ref_name[1..]] : [version, ref_name]
40
+ when :pull
41
+ pr_number, merge, _rest = ref_name.split('/')
42
+ # GITHUB_BASE_REF The name of the base ref or target branch of the pull request in a workflow run
43
+ base_ref = env.fetch('GIT_BASE_REF', 'undefined')
44
+ [version, base_ref, "#{merge}#{pr_number}"]
45
+ else
46
+ [version, 'pre', ref_name]
47
+ end.select { |p| p && !p.empty? }.join('.').tr('//_-', '')
48
+
49
+ [version, ref_name, ref_type]
50
+ end
51
+
52
+ GEM_VERSION, GIT_REF_NAME, GIT_REF_TYPE = gem_version
53
+ end
54
+ end
@@ -9,12 +9,26 @@ module FFI
9
9
  # Controls the main run loop for a FUSE filesystem
10
10
  module Main
11
11
  class << self
12
+ # Builds default argument list for #{fuse_main} regardless of being called directly or from mount.fuse3
13
+ #
14
+ # @param [Array<String>] extra_args additional arguments to add to $0 and *ARGV
15
+ # @return [Array<String>]
16
+ # @see https://github.com/libfuse/libfuse/issues/621
17
+ def default_args(*extra_args)
18
+ args = ARGV.dup
19
+
20
+ # If called from mount.fuse3 we already have a 'source' argument which should go at args[0]
21
+ args.unshift($0) unless args.size >= 2 && args[0..1].all? { |a| !a.start_with?('-') }
22
+ args.concat(extra_args)
23
+ end
24
+
12
25
  # Main function of FUSE
13
26
  #
14
27
  # This function:
15
28
  #
16
29
  # - parses command line options - see {fuse_parse_cmdline}
17
- # and exits immediately if help or version options were processed
30
+ # exiting immediately if help or version options were processed
31
+ # - calls {#fuse_debug}, {#fuse_options}, {#fuse_configure} if implemented by operations
18
32
  # - installs signal handlers for INT, HUP, TERM to unmount and exit filesystem
19
33
  # - installs custom signal handlers if operations implements {fuse_traps}
20
34
  # - creates a fuse handle mounted with registered operations - see {fuse_create}
@@ -22,7 +36,7 @@ module FFI
22
36
  #
23
37
  # @param [Array<String>] argv mount.fuse arguments
24
38
  # expects progname, mountpoint, options....
25
- # @param [FuseArgs] args
39
+ # @param [FuseArgs|Array<String>] args
26
40
  # alternatively constructed args
27
41
  # @param [Object|FuseOperations] operations
28
42
  # something that responds to the fuse callbacks and optionally our abstract configuration methods
@@ -30,13 +44,18 @@ module FFI
30
44
  # any data to be made available to the {FuseOperations#init} callback
31
45
  #
32
46
  # @return [Integer] suitable for process exit code
33
- def fuse_main(*argv, operations:, args: argv, private_data: nil)
47
+ def fuse_main(*argv, operations:, args: argv.any? ? argv : default_args, private_data: nil)
34
48
  run_args = fuse_parse_cmdline(args: args, handler: operations)
35
49
  return 2 unless run_args
36
50
 
37
51
  fuse_args = run_args.delete(:args)
38
52
  mountpoint = run_args.delete(:mountpoint)
39
53
 
54
+ return 3 unless fuse_configure(operations: operations, **run_args)
55
+
56
+ warn "FuseCreate: mountpoint: #{mountpoint}, args: [#{fuse_args.argv.join(' ')}]" if run_args[:debug]
57
+ warn "FuseRun: #{run_args}" if run_args[:debug]
58
+
40
59
  fuse = fuse_create(mountpoint, args: fuse_args, operations: operations, private_data: private_data)
41
60
 
42
61
  return 0 if run_args[:show_help] || run_args[:show_version]
@@ -44,52 +63,43 @@ module FFI
44
63
 
45
64
  return unless fuse
46
65
 
47
- warn run_args.to_s if run_args[:debug]
48
-
49
66
  fuse.run(**run_args)
50
67
  end
68
+ alias main fuse_main
51
69
 
52
70
  # Parse command line arguments
53
71
  #
54
72
  # - parses standard command line options (-d -s -h -V)
55
73
  # will call {fuse_debug}, {fuse_version}, {fuse_help} if implemented by handler
56
- # - parses custom options if handler implements {fuse_options} and {fuse_opt_proc}
74
+ # - calls {fuse_options} for custom option processing if implemented by handler
57
75
  # - records signal handlers if operations implements {fuse_traps}
58
76
  # - parses standard fuse mount options
59
77
  #
60
78
  # @param [Array<String>] argv mount.fuse arguments
61
- # expects progname, [fsname,] mountpoint, options.... from mount.fuse3
79
+ # expects progname, mountpoint, options....
62
80
  # @param [FuseArgs] args
63
81
  # alternatively constructed args
64
82
  # @param [Object] handler
65
83
  # something that responds to our abstract configuration methods
66
- # @param [Object] private_data passed to handler.fuse_opt_proc
67
- #
68
84
  # @return [Hash<Symbol,Object>]
69
- # * fsname [String]: the fsspec from /etc/fstab
70
85
  # * mountpoint [String]: the mountpoint argument
71
86
  # * args [FuseArgs]: remaining fuse_args to pass to {fuse_create}
72
87
  # * show_help [Boolean]: -h or --help
73
88
  # * show_version [Boolean]: -v or --version
74
89
  # * debug [Boolean]: -d
75
90
  # * others are options to pass to {FuseCommon#run}
76
- def fuse_parse_cmdline(*argv, args: argv, handler: nil, private_data: nil)
91
+ def fuse_parse_cmdline(*argv, args: argv.any? ? argv : default_args, handler: nil)
77
92
  args = fuse_init_args(args)
78
93
 
79
94
  # Parse args and print cmdline help
80
95
  run_args = Fuse.parse_cmdline(args, handler: handler)
96
+ return nil unless run_args
81
97
 
82
- # process custom options
83
- if %i[fuse_options fuse_opt_proc].all? { |m| handler.respond_to?(m) }
84
- parse_ok = args.parse!(handler.fuse_options, private_data) do |*p_args|
85
- handler.fuse_opt_proc(*p_args)
86
- end
87
- return unless parse_ok
88
- end
98
+ return nil if handler.respond_to?(:fuse_options) && !handler.fuse_options(args)
89
99
 
90
100
  run_args[:traps] = handler.fuse_traps if handler.respond_to?(:fuse_traps)
91
101
 
92
- args.parse!(RUN_OPTIONS, run_args) { |*opt_args| hash_opt_proc(*opt_args, discard: %i[native max_threads]) }
102
+ return nil unless parse_run_options(args, run_args)
93
103
 
94
104
  run_args[:args] = args
95
105
  run_args
@@ -97,40 +107,41 @@ module FFI
97
107
 
98
108
  # @return [FuseCommon|nil] the mounted filesystem or nil if not mounted
99
109
  def fuse_create(mountpoint, *argv, operations:, args: nil, private_data: nil)
100
- args = fuse_init_args(args || argv.unshift(mountpoint))
110
+ args = fuse_init_args(args || argv)
101
111
 
102
112
  operations = FuseOperations.new(delegate: operations) unless operations.is_a?(FuseOperations)
103
113
 
104
- fuse = Fuse.new(mountpoint, args, operations, private_data)
114
+ fuse = Fuse.new(mountpoint.to_s, args, operations, private_data)
105
115
  fuse if fuse.mounted?
106
116
  end
107
117
 
108
- # Helper fuse_opt_proc function to capture options into a hash
109
- #
110
- # See {FuseArgs.parse!}
111
- def hash_opt_proc(hash, arg, key, _out, discard: [])
112
- return :keep if %i[unmatched non_option].include?(key)
113
-
114
- hash[key] = arg =~ /=/ ? arg.split('=', 2).last : true
115
- discard.include?(key) ? :discard : :keep
118
+ # @!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
121
+
122
+ # Provide sensible values for FuseContext in case this is referenced during configure
123
+ FFI::Libfuse::FuseContext.overrides do
124
+ operations.fuse_configure
125
+ true
126
+ rescue Error => e
127
+ warn e.message
128
+ false
129
+ rescue StandardError, ScriptError => e
130
+ warn "#{e.class.name}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
131
+ false
132
+ end
116
133
  end
117
134
 
118
135
  # @!visibility private
119
-
120
- # Version text
121
136
  def version
122
137
  "#{name}: #{VERSION}"
123
138
  end
124
139
 
140
+ private
141
+
125
142
  def fuse_init_args(args)
126
143
  if args.is_a?(Array)
127
- args = args.map(&:to_s) # handle mountpoint as Pathname etc..
128
-
129
- # https://github.com/libfuse/libfuse/issues/621 handle "source" field sent from /etc/fstab via mount.fuse3
130
- # if arg[1] and arg[2] are both non option fields then replace arg1 with -ofsname=<arg1>
131
- unless args.size <= 2 || args[1]&.start_with?('-') || args[2]&.start_with?('-')
132
- args[1] = "-ofsname=#{args[1]}"
133
- end
144
+ warn "FuseArgs: #{args.join(' ')}" if args.include?('-d')
134
145
  args = FuseArgs.create(*args)
135
146
  end
136
147
 
@@ -138,23 +149,52 @@ module FFI
138
149
 
139
150
  raise ArgumentError "fuse main args: must be Array<String> or #{FuseArgs.class.name}"
140
151
  end
152
+
153
+ def parse_run_options(args, run_args)
154
+ args.parse!(RUN_OPTIONS) do |key:, value:, **|
155
+ run_args[key] = value
156
+ next :keep if (STANDARD_OPTIONS.values + %i[remember]).include?(key)
157
+
158
+ :discard
159
+ end
160
+ end
141
161
  end
142
162
 
143
163
  # @!group Abstract Configuration
144
164
 
145
- # @!method fuse_options
165
+ # @!method fuse_options(args)
146
166
  # @abstract
147
- # @return [Hash] custom option schema
148
- # @see FuseArgs#parse!
149
-
150
- # @!method fuse_opt_proc(data,arg,key,out)
151
- # @abstract
152
- # Process custom options
167
+ # Called to allow filesystem to handle custom options and observe standard mount options #
168
+ # @param [FuseArgs] args
169
+ # @return [Boolean] true if args parsed successfully
153
170
  # @see FuseArgs#parse!
171
+ # @example
172
+ # OPTIONS = { 'config=' => :config, '-c ' => :config }
173
+ # def fuse_options(args)
174
+ # args.parse!(OPTIONS) do |key:, value:, out:, **opts|
175
+ #
176
+ # # raise errors for invalid config
177
+ # raise FFI::Libfuse::FuseArgs::Error, "Invalid config" unless valid_config?(key,value)
178
+ #
179
+ # # Configure the file system
180
+ # @config = value if key == :config
181
+ #
182
+ # #Optionally manipulate other arguments for fuse_mount() based on the current argument and state
183
+ # out.add('-oopt=val')
184
+ #
185
+ # # Custom options must be marked :handled otherwise fuse_mount() will fail with unknown arguments
186
+ # :handled
187
+ # end
188
+ # end
154
189
 
155
190
  # @!method fuse_traps
156
191
  # @abstract
157
- # @return [Hash] map of signal name or number to signal handler as per Signal.trap
192
+ # @return [Hash<String|Symbol|Integer,String|Proc>]
193
+ # map of signal name or number to signal handler as per Signal.trap
194
+ # @example
195
+ # def fuse_traps
196
+ # { HUP: ->() { reload() }}
197
+ # end
158
198
 
159
199
  # @!method fuse_version
160
200
  # @abstract
@@ -162,14 +202,22 @@ module FFI
162
202
 
163
203
  # @!method fuse_help
164
204
  # @abstract
165
- # @return [String] help text to explain custom options to show with -h option
205
+ # Called as part of generating output for the -h option
206
+ # @return [String] help text to explain custom options
166
207
 
167
208
  # @!method fuse_debug(enabled)
168
209
  # @abstract
169
- # Indicate to the filesystem whether debugging option is in use.
210
+ # Called to indicate to the filesystem whether debugging option is in use.
170
211
  # @param [Boolean] enabled if -d option is in use
171
212
  # @return [void]
172
213
 
214
+ # @!method fuse_configure
215
+ # @abstract
216
+ # Called immediately before the filesystem is mounted, after options have been parsed
217
+ #
218
+ # @raise [Error] to prevent the mount from proceeding
219
+ # @return [void]
220
+
173
221
  # @!endgroup
174
222
 
175
223
  # @!visibility private