ffi-libfuse 0.0.1.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +1 -0
  3. data/README.md +100 -0
  4. data/lib/ffi/accessors.rb +145 -0
  5. data/lib/ffi/devt.rb +30 -0
  6. data/lib/ffi/flock.rb +47 -0
  7. data/lib/ffi/gnu_extensions.rb +115 -0
  8. data/lib/ffi/libfuse/ackbar.rb +112 -0
  9. data/lib/ffi/libfuse/adapter/context.rb +37 -0
  10. data/lib/ffi/libfuse/adapter/debug.rb +89 -0
  11. data/lib/ffi/libfuse/adapter/fuse2_compat.rb +91 -0
  12. data/lib/ffi/libfuse/adapter/fuse3_support.rb +87 -0
  13. data/lib/ffi/libfuse/adapter/interrupt.rb +37 -0
  14. data/lib/ffi/libfuse/adapter/pathname.rb +23 -0
  15. data/lib/ffi/libfuse/adapter/ruby.rb +334 -0
  16. data/lib/ffi/libfuse/adapter/safe.rb +58 -0
  17. data/lib/ffi/libfuse/adapter/thread_local_context.rb +36 -0
  18. data/lib/ffi/libfuse/adapter.rb +79 -0
  19. data/lib/ffi/libfuse/callbacks.rb +61 -0
  20. data/lib/ffi/libfuse/fuse2.rb +159 -0
  21. data/lib/ffi/libfuse/fuse3.rb +162 -0
  22. data/lib/ffi/libfuse/fuse_args.rb +166 -0
  23. data/lib/ffi/libfuse/fuse_buffer.rb +155 -0
  24. data/lib/ffi/libfuse/fuse_callbacks.rb +48 -0
  25. data/lib/ffi/libfuse/fuse_cmdline_opts.rb +44 -0
  26. data/lib/ffi/libfuse/fuse_common.rb +249 -0
  27. data/lib/ffi/libfuse/fuse_config.rb +205 -0
  28. data/lib/ffi/libfuse/fuse_conn_info.rb +211 -0
  29. data/lib/ffi/libfuse/fuse_context.rb +79 -0
  30. data/lib/ffi/libfuse/fuse_file_info.rb +100 -0
  31. data/lib/ffi/libfuse/fuse_loop_config.rb +43 -0
  32. data/lib/ffi/libfuse/fuse_operations.rb +870 -0
  33. data/lib/ffi/libfuse/fuse_opt.rb +54 -0
  34. data/lib/ffi/libfuse/fuse_poll_handle.rb +59 -0
  35. data/lib/ffi/libfuse/fuse_version.rb +43 -0
  36. data/lib/ffi/libfuse/job_pool.rb +53 -0
  37. data/lib/ffi/libfuse/main.rb +200 -0
  38. data/lib/ffi/libfuse/test/operations.rb +56 -0
  39. data/lib/ffi/libfuse/test.rb +3 -0
  40. data/lib/ffi/libfuse/thread_pool.rb +147 -0
  41. data/lib/ffi/libfuse/version.rb +8 -0
  42. data/lib/ffi/libfuse.rb +24 -0
  43. data/lib/ffi/ruby_object.rb +95 -0
  44. data/lib/ffi/stat/constants.rb +29 -0
  45. data/lib/ffi/stat/native.rb +50 -0
  46. data/lib/ffi/stat/time_spec.rb +137 -0
  47. data/lib/ffi/stat.rb +96 -0
  48. data/lib/ffi/stat_vfs.rb +81 -0
  49. data/lib/ffi/struct_array.rb +39 -0
  50. data/lib/ffi/struct_wrapper.rb +100 -0
  51. data/sample/memory_fs.rb +189 -0
  52. data/sample/no_fs.rb +69 -0
  53. metadata +165 -0
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'fuse_version'
4
+ require_relative 'fuse_opt'
5
+ require_relative '../ruby_object'
6
+
7
+ module FFI
8
+ # Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
9
+ module Libfuse
10
+ # struct fuse_args
11
+ class FuseArgs < FFI::Struct
12
+ layout :argc, :int, :argv, :pointer, :allocated, :int
13
+
14
+ # Create an fuse_args struct from command line options
15
+ # @param [Array<String>] argv command line args
16
+ # args[0] is expected to be program name
17
+ # @return [FuseArgs]
18
+ def self.create(*argv)
19
+ new.fill(*argv)
20
+ end
21
+
22
+ # @!visibility private
23
+ # noinspection RubyResolve
24
+ def fill(*argv)
25
+ # Keep the args allocated while in scope
26
+ @arg_vector = FFI::MemoryPointer.new(:pointer, argv.size + 1)
27
+ @argv_strings = argv.map { |k| FFI::MemoryPointer.from_string(k.to_s) }
28
+ @arg_vector.write_array_of_pointer(@argv_strings)
29
+ @arg_vector[argv.size].put_pointer(0, FFI::Pointer::NULL)
30
+ self[:argv] = @arg_vector
31
+ self[:argc] = argv.size
32
+ self[:allocated] = 0
33
+ self
34
+ end
35
+
36
+ # @!attribute [r] argc
37
+ # @return [Integer] count of args
38
+ def argc
39
+ self[:argc]
40
+ end
41
+
42
+ # @!attribute [r] argv
43
+ # @return [Array<String>]
44
+ def argv
45
+ # noinspection RubyResolve
46
+ self[:argv].get_array_of_pointer(0, argc).map(&:read_string)
47
+ end
48
+
49
+ # @!visibility private
50
+ def allocated
51
+ self[:allocated]
52
+ end
53
+
54
+ # @!visibility private
55
+ def inspect
56
+ "#{self.class.name} - #{%i[argc argv allocated].map { |m| [m, send(m)] }.to_h}"
57
+ end
58
+
59
+ # Add an arg to this arg list
60
+ # @param [String] arg
61
+ def add(arg)
62
+ Libfuse.fuse_opt_add_arg(self, arg)
63
+ end
64
+
65
+ # Insert arg at pos in this struct via fuse_opt_insert_arg
66
+ # @param [Integer] pos index to insert arg at
67
+ # @param [String] arg
68
+ def insert(pos, arg)
69
+ Libfuse.fuse_opt_insert_arg(self, pos, arg)
70
+ end
71
+
72
+ #
73
+ # Option parsing function
74
+ #
75
+ # @param [Hash<String,Symbol>] opts option schema
76
+ #
77
+ # hash keys are a String template to match arguments against
78
+ #
79
+ # 1. "-x", "-foo", "--foo", "--foo-bar", etc. These match only themselves. Invalid values are "--" and anything
80
+ # beginning with "-o"
81
+ # 2. "foo", "foo-bar", etc. These match "-ofoo", "-ofoo-bar" or the relevant option in a comma separated option
82
+ # list
83
+ # 3. "bar=", "--foo=", etc. These are variations of 1) and 2) which have a parameter
84
+ # 4. '%' Formats Not Supported (or needed for Ruby!)
85
+ # 5. "-x ", etc. Matches either "-xparam" or "-x param" as two separate arguments
86
+ # 6. '%' Formats Not Supported
87
+ #
88
+ # hash values are the Symbol sent to the block for a matching argument
89
+ #
90
+ # - :keep Argument is not passed to block, but behave as if the block returns :keep
91
+ # - :discard Argument is not passed to block, but behave as if the block returns :discard
92
+ # - any other value is yielded as 'key' property on matching argument
93
+ #
94
+ # @param [Object] data an optional object that will be passed thru to the block
95
+ #
96
+ # @yieldparam [Object] data
97
+ # @yieldparam [String] arg is the whole argument or option including the parameter if exists.
98
+ #
99
+ # A two-argument option ("-x foo") is always converted to single argument option of the form "-xfoo" before this
100
+ # function is called.
101
+ #
102
+ # Options of the form '-ofoo' are yielded without the '-o' prefix.
103
+ #
104
+ # @yieldparam [Symbol] key determines why the processing function was called
105
+ #
106
+ # - :unmatched for arguments that *do not match* any supplied option
107
+ # - :non_option for non-option arguments (after -- or not beginning with -)
108
+ # - with appropriate value from opts hash for a matching argument
109
+ #
110
+ # @yieldparam [FuseArgs] outargs can {add} or {insert} additional args as required
111
+ #
112
+ # eg. if one arg implies another
113
+ #
114
+ # @yieldreturn [Symbol] the argument action
115
+ #
116
+ # - :error an error
117
+ # - :keep the current argument (to pass on further)
118
+ # - :handled,:discard success and discard the current argument (ie because it has been handled)
119
+ #
120
+ # @return [nil|FuseArgs] nil on error, self on success
121
+ def parse!(opts, data = nil, &block)
122
+ # turn option value symbols into integers including special negative values from fuse_opt.h
123
+ symbols = opts.values.uniq + %i[discard keep non_option unmatched]
124
+
125
+ int_opts = opts.transform_values do |v|
126
+ %i[discard keep].include?(v) ? symbols.rindex(v) - symbols.size : symbols.index(v)
127
+ end
128
+
129
+ fop = proc { |d, arg, key, outargs| fuse_opt_proc(d, arg, symbols[key], outargs, &block) }
130
+ result = Libfuse.fuse_opt_parse(self, data, int_opts, fop)
131
+
132
+ result.zero? ? self : nil
133
+ end
134
+
135
+ private
136
+
137
+ # Valid return values from parse! block
138
+ FUSE_OPT_PROC_RETURN = { error: -1, keep: 1, handled: 0, discard: 0 }.freeze
139
+
140
+ def fuse_opt_proc(data, arg, key, out, &block)
141
+ res = block.call(data, arg, key, out)
142
+ res.is_a?(Integer) ? res : FUSE_OPT_PROC_RETURN.fetch(res)
143
+ rescue KeyError => e
144
+ warn "FuseOptProc error - Unknown result #{e.key}"
145
+ -1
146
+ rescue StandardError => e
147
+ warn "FuseOptProc error - #{e.class.name}:#{e.message}"
148
+ -1
149
+ end
150
+ end
151
+
152
+ # typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs);
153
+ callback :fuse_opt_proc_t, [RubyObject, :string, :int, FuseArgs.by_ref], :int
154
+
155
+ attach_function :fuse_opt_parse, [FuseArgs.by_ref, RubyObject, FuseOpt::OptList, :fuse_opt_proc_t], :int
156
+ attach_function :fuse_opt_add_arg, [FuseArgs.by_ref, :string], :int
157
+ attach_function :fuse_opt_insert_arg, [FuseArgs.by_ref, :int, :string], :int
158
+
159
+ class << self
160
+ # @!visibility private
161
+ # @!method fuse_opt_parse
162
+ # @!method fuse_opt_add_arg
163
+ # @!method fuse_opt_insert_arg
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'fuse_version'
4
+
5
+ module FFI
6
+ # Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
7
+ module Libfuse
8
+ bitmask :fuse_buf_flags, [:is_fd, 1, :fd_seek, :fd_retry]
9
+ bitmask :fuse_buf_copy_flags, [:no_splice, 1, :force_splice, 2, :splice_move, 4, :splice_nonblock, 8]
10
+
11
+ #
12
+ # Single data buffer
13
+ #
14
+ # Generic data buffer for I/O, extended attributes, etc...Data may be supplied as a memory pointer or as a file
15
+ # descriptor
16
+ #
17
+ # @todo define helper methods to create buffers pointing to file_descriptors or allocated memory
18
+ class FuseBuf < FFI::Struct
19
+ layout(
20
+ size: :size_t, # Size of data in bytes
21
+ flags: :fuse_buf_flags, # Buffer flags
22
+ mem: :pointer, # Memory pointer - used if :is_fd flag is not set
23
+ fd: :int, # File descriptor - used if :is_fd is set
24
+ pos: :off_t # File position - used if :fd_seek flag is set.
25
+ )
26
+
27
+ # rubocop:disable Naming/MethodParameterName
28
+
29
+ # @param [Integer] size Size of data in bytes
30
+ # @param [Integer] fd File descriptor
31
+ # @param [FFI::Pointer] mem Memory pointer
32
+ # @param [Boolean] fd_retry
33
+ # Retry operation on file descriptor
34
+ #
35
+ # If this flag is set then retry operation on file descriptor until .size bytes have been copied or an error or
36
+ # EOF is detected.
37
+ #
38
+ # @param [Integer] pos
39
+ # If > 0 then used to seek to the given offset before performing operation on file descriptor.
40
+ # @return [self]
41
+ def fill(size:, mem: FFI::Pointer::NULL, fd: -1, fd_retry: false, pos: 0)
42
+ self[:size] = size
43
+ self[:mem] = mem
44
+ self[:fd] = fd
45
+ flags = []
46
+ flags << :is_fd if fd != -1
47
+ flags << :fd_seek if pos.positive?
48
+ flags << :fd_retry if fd_retry
49
+ self[:flags] = flags
50
+ self[:pos] = pos
51
+ self
52
+ end
53
+ end
54
+ # rubocop:enable Naming/MethodParameterName
55
+
56
+ #
57
+ # Data buffer vector
58
+ #
59
+ # An array of data buffers, each containing a memory pointer or a file descriptor.
60
+ #
61
+ # Allocate dynamically to add more than one buffer.
62
+ #
63
+ # @todo find a use for {FuseOperations#read_buf} and implement necessary helpers
64
+ class FuseBufVec < FFI::Struct
65
+ layout(
66
+ count: :size_t,
67
+ idx: :size_t,
68
+ off: :size_t,
69
+ buf: :pointer
70
+ )
71
+ # @!attribute [r] count
72
+ # @todo implement
73
+ # @return [Integer] the number of buffers in the array
74
+
75
+ # @!attribute [r] index
76
+ # @todo implement
77
+ # @return [Integer] index of current buffer within the array
78
+
79
+ # @!attribute [r] offset
80
+ # @todo implement
81
+ # @return [Integer] current offset within the current buffer
82
+
83
+ # @!attribute [r] buffers
84
+ # @todo implement
85
+ # @return [Array<FuseBuf>] array of buffers
86
+
87
+ # @see #init
88
+ def self.init(**buf_options)
89
+ new.init(**buf_options)
90
+ end
91
+
92
+ # Allocate a vector containing a single buffer
93
+ #
94
+ # See fuse_common.h FUSE_BUFVEC_INIT macro
95
+ # @param [Hash<Symbol,Object>] buf_options see {FuseBuf.fill}
96
+ def init(**buf_options)
97
+ self[:count] = 1
98
+ self[:idx] = 0
99
+ self[:off] = 0
100
+ self[:buf] = FuseBuf.new.fill(**buf_options), to_ptr
101
+ self
102
+ end
103
+
104
+ # @return [Integer] total size of data in a fuse buffer vector
105
+ def buf_size
106
+ Libfuse.fuse_buf_size(self)
107
+ end
108
+
109
+ # Copy data from one buffer vector to another
110
+ # @param [FuseBufVec] dst Destination buffer vector
111
+ # @param [Array<Symbol>] flags Buffer copy flags
112
+ # - :no_splice
113
+ # Don't use splice(2)
114
+ #
115
+ # Always fall back to using read and write instead of splice(2) to copy data from one file descriptor to
116
+ # another.
117
+ #
118
+ # If this flag is not set, then only fall back if splice is unavailable.
119
+ #
120
+ # - :force_splice
121
+ #
122
+ # Always use splice(2) to copy data from one file descriptor to another. If splice is not available, return
123
+ # -EINVAL.
124
+ #
125
+ # - :splice_move
126
+ #
127
+ # Try to move data with splice.
128
+ #
129
+ # If splice is used, try to move pages from the source to the destination instead of copying. See
130
+ # documentation of SPLICE_F_MOVE in splice(2) man page.
131
+ #
132
+ # - :splice_nonblock
133
+ #
134
+ # Don't block on the pipe when copying data with splice
135
+ #
136
+ # Makes the operations on the pipe non-blocking (if the pipe is full or empty). See SPLICE_F_NONBLOCK in
137
+ # the splice(2) man page.
138
+ #
139
+ # @return [Integer] actual number of bytes copied or -errno on error
140
+ #
141
+ def copy_to(dst, *flags)
142
+ Libfuse.fuse_buf_copy(dst, self, flags)
143
+ end
144
+ end
145
+
146
+ attach_function :fuse_buf_size, [FuseBufVec.by_ref], :size_t
147
+ attach_function :fuse_buf_copy, [FuseBufVec.by_ref, FuseBufVec.by_ref, :fuse_buf_copy_flags], :ssize_t
148
+
149
+ class << self
150
+ # @!visibility private
151
+ # @!method fuse_buf_size
152
+ # @!method fuse_buf_copy
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'callbacks'
4
+
5
+ module FFI
6
+ module Libfuse
7
+ # Methods to register callbacks and wrappers
8
+ module FuseCallbacks
9
+ include Callbacks
10
+
11
+ # @!group Configuration
12
+
13
+ # @!method fuse_wrappers(*wrappers)
14
+ # @abstract
15
+ # Wrappers change the behaviour/signature of the abstract fuse callback methods
16
+ #
17
+ # @param [Array] wrappers
18
+ # An initial list of wrappers
19
+ # @return [Array] the final list of wrappers.
20
+ # Implementations should append or prepend to the input wrappers as appropriate
21
+ #
22
+ # See {register} for what constitutes a valid wrapper
23
+
24
+ # @!method fuse_respond_to?(fuse_method)
25
+ # @abstract
26
+ # @param [Symbol] fuse_method a fuse callback method
27
+ # @return [Boolean] true if the fuse method should be registered
28
+
29
+ # @!endgroup
30
+ private
31
+
32
+ def initialize_callbacks(delegate:, wrappers: [])
33
+ wrappers = delegate.fuse_wrappers(*wrappers) if delegate.respond_to?(:fuse_wrappers)
34
+ super(callback_members, delegate: delegate, wrappers: wrappers)
35
+ end
36
+
37
+ def respond_to_callback?(method, delegate)
38
+ return delegate.fuse_respond_to?(method) if delegate.respond_to?(:fuse_respond_to?)
39
+
40
+ super
41
+ end
42
+
43
+ def callback_members
44
+ members - [:flags]
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../accessors'
4
+ require_relative 'fuse_loop_config'
5
+ module FFI
6
+ module Libfuse
7
+ #
8
+ # struct fuse_cmdline_opts {
9
+ # int singlethread;
10
+ # int foreground;
11
+ # int debug;
12
+ # int nodefault_subtype;
13
+ # char *mountpoint;
14
+ # int show_version;
15
+ # int show_help;
16
+ # int clone_fd;
17
+ # unsigned int max_idle_threads;
18
+ # };
19
+ # @!visibility private
20
+ class FuseCmdlineOpts < FFI::Struct
21
+ include(FFI::Accessors)
22
+
23
+ layout(
24
+ single_thread: :int,
25
+ foreground: :int,
26
+ debug: :int,
27
+ nodefault_subtype: :int,
28
+ mountpoint: :string,
29
+ show_version: :int,
30
+ show_help: :int,
31
+ clone_fd: :int,
32
+ max_idle_threads: :int
33
+ )
34
+
35
+ # int to booleans
36
+ ffi_attr_reader(:single_thread, :foreground, :debug, :nodefault_subtype, :show_version, :show_help,
37
+ :clone_fd) do |v|
38
+ v != 0
39
+ end
40
+
41
+ ffi_attr_reader(:max_idle_threads, :mountpoint)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,249 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'fuse_version'
4
+ require_relative 'thread_pool'
5
+ require_relative 'ackbar'
6
+
7
+ module FFI
8
+ # Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
9
+ module Libfuse
10
+ typedef :pointer, :fuse
11
+ typedef :pointer, :session
12
+
13
+ attach_function :fuse_get_session, [:fuse], :session
14
+ attach_function :fuse_set_signal_handlers, [:session], :int
15
+ attach_function :fuse_remove_signal_handlers, [:session], :void
16
+ attach_function :fuse_loop, [:fuse], :int, blocking: false
17
+ attach_function :fuse_clean_cache, [:fuse], :int
18
+ attach_function :fuse_exit, [:fuse], :void
19
+ attach_function :fuse_destroy, [:fuse], :void
20
+ attach_function :fuse_daemonize, [:int], :int
21
+
22
+ class << self
23
+ # @!visibility private
24
+ # @!method fuse_get_session(fuse)
25
+ # @!method fuse_set_signal_handlers(session)
26
+ # @!method fuse_remove_signal_handlers(session)
27
+ # @!method fuse_loop(fuse)
28
+ # @!method fuse_clean_cache(fuse)
29
+ # @!method fuse_exit(fuse)
30
+ # @!method fuse_destroy(fuse) void
31
+ # @!method fuse_daemonize(foreground) int
32
+ end
33
+
34
+ # @abstract Base class for Fuse, which itself is just a constant pointing to Fuse2 or Fuse3
35
+ # @note This class documents the Ruby re-implementation of native fuse functions. It will not generally be used
36
+ # directly.
37
+ class FuseCommon
38
+ # Run the mounted filesystem until exit
39
+ # @param [Boolean] native should we run the C native fuse_loop functions or the ruby implementation
40
+ # @param [Hash<Symbol,Object>] options passed to {run_native} or {run_ruby}
41
+ # @return [Integer] an exit code for the fuse process (0 for success)
42
+ def run(native: false, **options)
43
+ return false unless mounted?
44
+
45
+ if native
46
+ run_native(**options)
47
+ else
48
+ run_ruby(**options)
49
+ end
50
+ rescue Errno => e
51
+ -e.errno
52
+ rescue StandardError => e
53
+ warn e
54
+ warn e.backtrace.join("\n")
55
+ -1
56
+ ensure
57
+ teardown
58
+ end
59
+
60
+ # @api private
61
+ # @param [Boolean] foreground
62
+ # @param [Boolean] single_thread
63
+ # @param [Hash<String,Proc>] traps see {Ackbar.trap}
64
+ #
65
+ # Implement fuse loop in ruby
66
+ #
67
+ # Pros:
68
+ #
69
+ # * multi-threading works
70
+ # * can set max_threads @see https://github.com/libfuse/libfuse/issues/203
71
+ # * can send signals to the FS (eg to reload)
72
+ # * daemonize works
73
+ #
74
+ # Cons:
75
+ #
76
+ # * clone_fd is ignored
77
+ # * filesystem interrupts probably can't work
78
+ def run_ruby(foreground: true, single_thread: true, traps: {}, **options)
79
+ Ackbar.trap(default_traps.merge(traps)) do |signals|
80
+ daemonize unless foreground
81
+
82
+ if single_thread
83
+ fuse_loop(signals: signals, **options)
84
+ else
85
+ fuse_loop_mt(signals: signals, **options)
86
+ end
87
+ 0
88
+ end
89
+ end
90
+
91
+ # Running fuse loop natively
92
+ #
93
+ # Pros
94
+ #
95
+ # * clone_fd will work
96
+ # * filesystem interrupts may work
97
+ #
98
+ # Cons
99
+ #
100
+ # * multi-threading will create a new ruby thread for every callback
101
+ # * cannot daemonize multi-threaded (hangs) TODO: Why - pthread_lock?, GVL?
102
+ # * cannot pass signals to the filesystem
103
+ #
104
+ # @api private
105
+ # @param [Boolean] foreground
106
+ # @param [Boolean] single_thread
107
+ #
108
+ def run_native(foreground: true, single_thread: true, **options)
109
+ if !single_thread && !foreground
110
+ warn 'Cannot run native multi-thread fuse_loop when daemonized. Using single_thread mode'
111
+ single_thread = true
112
+ end
113
+
114
+ clear_default_traps
115
+ (se = session) && Libfuse.fuse_set_signal_handlers(se)
116
+
117
+ Libfuse.fuse_daemonize(foreground ? 1 : 0)
118
+
119
+ if single_thread
120
+ Libfuse.fuse_loop(@fuse)
121
+ else
122
+ native_fuse_loop_mt(**options)
123
+ end
124
+ ensure
125
+ (se = session) && Libfuse.fuse_remove_signal_handlers(se)
126
+ end
127
+
128
+ # Tell the processing loop to stop and force unmount the filesystem which is unfortunately required to make
129
+ # the processing threads, which are mostly blocked on io reads from /dev/fuse fd, to exit.
130
+ def exit
131
+ Libfuse.fuse_exit(@fuse) if @fuse
132
+ # Force threads blocked reading on #io to finish
133
+ unmount
134
+ end
135
+
136
+ # Ruby implementation of fuse default traps
137
+ # @see Ackbar
138
+ def default_traps
139
+ @default_traps ||= { INT: -> { exit }, HUP: -> { exit }, TERM: -> { exit }, PIPE: 'IGNORE' }
140
+ end
141
+
142
+ # @api private
143
+ # Ruby implementation of fuse_daemonize which does not work under MRI probably due to the way ruby needs to
144
+ # understand native threads.
145
+ def daemonize
146
+ raise 'Cannot daemonize without support for fork' unless Process.respond_to?(:fork)
147
+
148
+ # pipe to wait for fork
149
+ pr, pw = ::IO.pipe
150
+
151
+ if Process.fork
152
+ # rubocop:disable Style/RescueModifier
153
+ status = pr.read(1).unpack1('c') rescue 1
154
+ Kernel.exit!(status)
155
+ # rubocop:enable Style/RescueModifier
156
+ end
157
+
158
+ begin
159
+ Process.setsid
160
+
161
+ Dir.chdir '/'
162
+
163
+ # close/redirect file descriptors
164
+ $stdin.close
165
+
166
+ [$stdout, $stderr].each { |io| io.reopen('/dev/null', 'w') }
167
+
168
+ pw.write([0].pack('c'))
169
+ ensure
170
+ pw.close
171
+ end
172
+ end
173
+
174
+ # @api private
175
+ # Keep track of time until next cache clean for the caching performed by libfuse itself
176
+ def fuse_clean_cache
177
+ now = Time.now
178
+ @next_cache_clean ||= now
179
+ return @next_cache_clean - now unless now >= @next_cache_clean
180
+
181
+ delay = Libfuse.fuse_clean_cache(@fuse)
182
+ @next_cache_clean = now + delay
183
+ delay
184
+ end
185
+
186
+ # @api private
187
+ # Ruby implementation of single threaded fuse loop
188
+ def fuse_loop(signals:, remember: false, **_options)
189
+ loop do
190
+ break unless mounted?
191
+
192
+ timeout = remember ? fuse_clean_cache : nil
193
+
194
+ ready, _ignore_writable, errors = ::IO.select([io, signals.pr], [], [io], timeout)
195
+
196
+ next unless ready || errors
197
+
198
+ raise 'FUSE error' unless errors.empty?
199
+
200
+ break if ready.include?(io) && !process
201
+
202
+ break if ready.include?(signals.pr) && !signals.next
203
+ rescue Errno::EBADF
204
+ raise if mounted? # This will occur on exit
205
+ end
206
+ end
207
+
208
+ # @api private
209
+ # Ruby implementation of multi threaded fuse loop
210
+ #
211
+ # We cannot simulate the clone_fd behaviour of fuse_loop_mt as the required fd is not exposed in the low level
212
+ # fuse api.
213
+ #
214
+ # @see ThreadPool ThreadPool for the mechanism that controls creation and termination of worker threads
215
+ def fuse_loop_mt(signals:, max_idle_threads: 10, max_threads: nil, remember: false, **_options)
216
+ # Monitor for signals (and cache cleaning if required)
217
+
218
+ signals.monitor { remember ? fuse_clean_cache : nil }
219
+ ThreadPool.new(name: 'FuseThread', max_idle: max_idle_threads.to_i, max_active: max_threads) { process }.join
220
+ end
221
+
222
+ # @!visibility private
223
+ def teardown
224
+ return unless @fuse
225
+
226
+ unmount
227
+ Libfuse.fuse_destroy(@fuse)
228
+ ObjectSpace.undefine_finalizer(self)
229
+ @fuse = nil
230
+ end
231
+
232
+ # @!visibility private
233
+ def session
234
+ return nil unless @fuse
235
+
236
+ @session ||= Libfuse.fuse_get_session(@fuse)
237
+ @session.null? ? nil : @session
238
+ end
239
+
240
+ # Allow fuse to handle default signals
241
+ def clear_default_traps
242
+ %i[INT HUP TERM PIPE].each do |sig|
243
+ prev = Signal.trap(sig, 'SYSTEM_DEFAULT')
244
+ Signal.trap(sig, prev) unless prev == 'DEFAULT'
245
+ end
246
+ end
247
+ end
248
+ end
249
+ end