ffi-libfuse 0.0.1.pre

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 (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