Class: FFI::Libfuse::FuseCommon Abstract

Inherits:
Object
  • Object
show all
Defined in:
lib/ffi/libfuse/fuse_common.rb

Overview

This class is abstract.

Base class for Fuse, which itself is just a constant pointing to Fuse2 or Fuse3

Note:

This class documents the Ruby re-implementation of native fuse functions. It will not generally be used directly.

Instance Method Summary collapse

Instance Method Details

#daemonizeObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Ruby implementation of fuse_daemonize which does not work under MRI probably due to the way ruby needs to understand native threads.



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/ffi/libfuse/fuse_common.rb', line 129

def daemonize
  raise 'Cannot daemonize without support for fork' unless Process.respond_to?(:fork)

  # pipe to wait for fork
  pr, pw = ::IO.pipe

  if Process.fork
    # rubocop:disable Style/RescueModifier
    status = pr.read(1).unpack1('c') rescue 1
    Kernel.exit!(status)
    # rubocop:enable Style/RescueModifier
  end

  begin
    Process.setsid

    Dir.chdir '/'

    # close/redirect file descriptors
    $stdin.close

    [$stdout, $stderr].each { |io| io.reopen('/dev/null', 'w') }

    pw.write([0].pack('c'))
  ensure
    pw.close
  end
end

#default_trapsObject

Ruby implementation of fuse default traps

  • INT, HUP, TERM, TSTP to unmount and exit filesystem
  • PIPE is ignored


121
122
123
124
# File 'lib/ffi/libfuse/fuse_common.rb', line 121

def default_traps
  exproc = ->(signame) { exit(signame) }
  @default_traps ||= { INT: exproc, HUP: exproc, TERM: exproc, TSTP: exproc, PIPE: 'IGNORE' }
end

#exit(_signame = nil) ⇒ Thread

Starts a thread to unmount the filesystem and stop the processing loop. generally expected to be called from a signal handler

Returns:

  • (Thread)

    the unmount thread



213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/ffi/libfuse/fuse_common.rb', line 213

def exit(_signame = nil)
  return unless @fuse

  # Unmount/exit in a separate thread so the main fuse thread can keep running.
  @exit ||= Thread.new do
    unmount

    # without this sleep before exit, MacOS does not complete unmounting
    sleep 0.2 if mac_fuse?

    Libfuse.fuse_exit(@fuse)
    true
  end
end

#fuse_clean_cacheObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Keep track of time until next cache clean for the caching performed by libfuse itself



160
161
162
163
164
165
166
167
168
# File 'lib/ffi/libfuse/fuse_common.rb', line 160

def fuse_clean_cache
  now = Time.now
  @next_cache_clean ||= now
  return @next_cache_clean - now unless now >= @next_cache_clean

  delay = Libfuse.fuse_clean_cache(@fuse)
  @next_cache_clean = now + delay
  delay
end

#fuse_loop(**_options) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Ruby implementation of single threaded fuse loop



172
173
174
# File 'lib/ffi/libfuse/fuse_common.rb', line 172

def fuse_loop(**_options)
  safe_fuse_process until fuse_exited?
end

#fuse_loop_mt(max_idle_threads: 10, max_threads: nil, **_options) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Ruby implementation of multi threaded fuse loop

We cannot simulate the clone_fd behaviour of fuse_loop_mt as the required fd is not exposed in the low level fuse api.



183
184
185
186
187
188
189
# File 'lib/ffi/libfuse/fuse_common.rb', line 183

def fuse_loop_mt(max_idle_threads: 10, max_threads: nil, **_options)
  ThreadPool.new(name: 'FuseThread', max_idle: max_idle_threads.to_i, max_active: max_threads&.to_i) do
    raise StopIteration if fuse_exited?

    safe_fuse_process
  end.join
end

#run(native: false, **options) ⇒ Integer

Run the mounted filesystem until exit

Parameters:

  • native (Boolean) (defaults to: false)

    should we run the C native fuse_loop functions or the ruby implementation

  • options (Hash<Symbol,Object>)

    passed to #run_native or #run_ruby

Returns:

  • (Integer)

    an exit code for the fuse process (0 for success)



42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/ffi/libfuse/fuse_common.rb', line 42

def run(native: false, **options)
  return false unless mounted?

  native ? run_native(**options) : run_ruby(**options)
rescue Errno => e
  -e.errno
rescue StandardError, ScriptError => e
  warn "#{e}\n#{e.backtrace.join("\n")}"
  -1
ensure
  teardown
end

#run_native(foreground: true, single_thread: true, **options) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Running fuse loop natively

Pros

  • clone_fd will work
  • filesystem interrupts may work

Cons

  • multi-threading will create a new ruby thread for every callback
  • cannot daemonize multi-threaded (hangs) TODO: Why - pthread_lock?, GVL?
  • cannot pass signals to the filesystem
  • connot use fuse_context (because the ruby thread is not the native thread)

Parameters:

  • foreground (Boolean) (defaults to: true)
  • single_thread (Boolean) (defaults to: true)


105
106
107
108
109
110
111
112
113
114
115
# File 'lib/ffi/libfuse/fuse_common.rb', line 105

def run_native(foreground: true, single_thread: true, **options)
  raise 'Cannot run daemonized native multi-thread fuse_loop' if !single_thread && !foreground

  clear_default_traps
  (se = session) && Libfuse.fuse_set_signal_handlers(se)

  Libfuse.fuse_daemonize(foreground ? 1 : 0)
  single_thread ? Libfuse.fuse_loop(@fuse) : native_fuse_loop_mt(**options)
ensure
  (se = session) && Libfuse.fuse_remove_signal_handlers(se)
end

#run_ruby(foreground: true, single_thread: true, traps: {}, remember: false, **options) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Implement fuse loop in ruby

Pros:

Cons:

  • clone_fd is ignored
  • filesystem interrupts probably can't work

Parameters:

  • foreground (Boolean) (defaults to: true)
  • single_thread (Boolean) (defaults to: true)
  • traps (Hash<String,Proc|nil>) (defaults to: {})

    as per Signal.trap these are merged over #default_traps for INT, HUP, TERM that unmount and exit filesystem. A nil value for these default signals will leave any existing signal handle in place.

  • remember (Integer) (defaults to: false)

    fuse cache timeout



75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/ffi/libfuse/fuse_common.rb', line 75

def run_ruby(foreground: true, single_thread: true, traps: {}, remember: false, **options)
  traps = default_traps.merge(traps).keep_if { |_, v| v }
  Ackbar.trap(default_traps.merge(traps)) do |signals|
    daemonize unless foreground

    # Monitor for signals (and cache cleaning if required)
    signals.monitor { fuse_cache_timeout(remember) }

    single_thread ? fuse_loop(**options) : fuse_loop_mt(**options)
    0
  end
end