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,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'fuse_version'
4
+
5
+ module FFI
6
+ module Libfuse
7
+ # Its a trap!
8
+ #
9
+ # Implements the self-pipe trick for handling signals in multi-threaded ruby programs
10
+ class Ackbar
11
+ # Read side of the self-pipe
12
+ # @return [IO] for use with IO.select only
13
+ # @see monitor
14
+ # @see next
15
+ attr_reader :pr
16
+
17
+ class << self
18
+ # Run a block with the given traps, restoring the original traps on completion
19
+ # @param [Hash] traps map of signal to handler as per Signal.trap
20
+ # @yieldparam [Ackbar] signals
21
+ # @return [Object] the result of the block
22
+ def trap(traps, force: false)
23
+ signals = new(traps, force: force)
24
+ yield signals
25
+ ensure
26
+ signals&.restore
27
+ end
28
+ end
29
+
30
+ # @param [Hash<Symbol|String|Integer,String|Proc>] traps
31
+ # Map of signal or signo to signal handler as per Signal.trap
32
+ # @param [Boolean] force
33
+ # If not set traps that are not currently set to 'DEFAULT' will be ignored
34
+ def initialize(traps, force: false, signal: Signal)
35
+ @signal = signal
36
+ @traps = traps.transform_keys { |k| signame(k) }
37
+ @pr, @pw = ::IO.pipe
38
+ @monitor = nil
39
+ @restore = @traps.map { |(sig, handler)| [sig, trap(sig, handler, force: force)] }.to_h
40
+ end
41
+
42
+ # Handle the next available signal on the pipe (without blocking)
43
+ # @return [Boolean] false if pipe is closed, true if pipe would block
44
+ # @return [Array<String,Object>] signal name, signal result when a signal has been processed
45
+ def next
46
+ signame = signal.signame(@pr.read_nonblock(1).unpack1('c'))
47
+ t = @traps[signame]
48
+ [signame, t.arity.zero? ? t.call : t.call(signame)]
49
+ rescue EOFError
50
+ # the signal pipe writer is closed - we are exiting.
51
+ @pr.close
52
+ false
53
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN
54
+ # oh well...
55
+ true
56
+ end
57
+
58
+ # Restore traps as they were at #new and close the write side of the pipe
59
+ def restore
60
+ @restore.each_pair { |signame, handler| signal.trap(signame, handler) }
61
+ @restore = nil
62
+ @pw.close
63
+ @monitor&.join
64
+ end
65
+ alias close restore
66
+
67
+ # Start a thread to monitor for signals optionally yields between signals
68
+ # @param [String] name The name of the monitor thread
69
+ # @yieldreturn [Integer] timeout in seconds or nil to wait until signal or {restore}
70
+ # @return [Thread] the monitor thread
71
+ def monitor(name: 'SignalMonitor')
72
+ @monitor ||= Thread.new do
73
+ Thread.current.name = name
74
+ loop do
75
+ timeout = block_given? ? yield : nil
76
+
77
+ ready, _ignore_writable, _errors = ::IO.select([@pr], [], [], timeout)
78
+
79
+ break if ready&.include?(@pr) && !self.next
80
+ end
81
+ end
82
+ end
83
+
84
+ # @!visibility private
85
+ # normalize signal identifiers to things that are keys in Signal.list
86
+ def signame(sig)
87
+ sig.is_a?(Integer) ? signal.signame(sig) : sig.to_s.upcase.sub(/^SIG/, '')
88
+ end
89
+
90
+ private
91
+
92
+ attr_reader :signal
93
+
94
+ def trap(signame, handler, force: false)
95
+ prev =
96
+ case handler
97
+ when String, Symbol
98
+ signal.trap(signame, handler.to_s)
99
+ else
100
+ signal.trap(signame) { |signo| send_signal(signo) }
101
+ end
102
+
103
+ signal.trap(signame, prev) unless force || prev == 'DEFAULT'
104
+ prev
105
+ end
106
+
107
+ def send_signal(signo)
108
+ @pw&.write([signo].pack('c')) unless @pw&.closed?
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../fuse_context'
4
+
5
+ module FFI
6
+ module Libfuse
7
+ module Adapter
8
+ # Wrapper module to inject {FuseContext} as first arg to each callback method (except :destroy)
9
+ #
10
+ # {ThreadLocalContext} may be a less intrusive means to make the context available to callbacks
11
+ module Context
12
+ # @!visibility private
13
+ def fuse_wrappers(*wrappers)
14
+ wrappers.unshift(
15
+ {
16
+ wrapper: proc { |_fm, *args, **_, &b| self.class.context_callback(*args, &b) },
17
+ excludes: %i[destroy]
18
+ }
19
+ )
20
+ return wrappers unless defined?(super)
21
+
22
+ super(*wrappers)
23
+ end
24
+
25
+ module_function
26
+
27
+ # @yieldparam [FuseContext] ctx
28
+ # @yieldparam [Array] *args
29
+ def context_callback(*args)
30
+ ctx = FuseContext.get
31
+ ctx = nil if ctx.null?
32
+ yield ctx, *args
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FFI
4
+ module Libfuse
5
+ module Adapter
6
+ # Debug callbacks
7
+ #
8
+ # When included in a filesystem class, and if debugging is enabled via {Main#fuse_debug}, then installs a wrapper
9
+ # via #{FuseCallbacks#fuse_wrappers} to log callbacks via #warn
10
+ module Debug
11
+ DEFAULT_FORMAT = "%<p>s %<n>s %<t>s %<m>s(%<a>s)\n\t=> %<r>s"
12
+
13
+ # @return [Boolean] true if debug is enabled
14
+ def debug?
15
+ @debug
16
+ end
17
+
18
+ # Configure debug output
19
+ # @abstract
20
+ # @return [Hash<Symbol,String>] options to pass to {debug_callback}
21
+ def debug_config
22
+ {}
23
+ end
24
+
25
+ # @!visibility private
26
+ def fuse_wrappers(*wrappers)
27
+ conf = { prefix: self.class.name }.merge!(debug_config)
28
+ # validate config for bad formats, strftime etc
29
+ Debug.debug_format(:test_debug, [], :result, **conf)
30
+ wrappers << proc { |fm, *args, &b| Debug.debug_callback(fm, *args, **conf, &b) } if debug?
31
+ return wrappers unless defined?(super)
32
+
33
+ super(*wrappers)
34
+ end
35
+
36
+ # @!visibility private
37
+ def fuse_debug(enabled)
38
+ super if defined?(super)
39
+ @debug = enabled
40
+ end
41
+
42
+ module_function
43
+
44
+ # Debug fuse method, args and result
45
+ # @param [Symbol] fuse_method the callback name
46
+ # @param [Array] args callback arguments
47
+ # @param [Hash<Symbol,String>] options see {debug} for defaults
48
+ # @option options [String] prefix
49
+ # @option options [String] strftime a date time format
50
+ # @option options [String] format format string with fields
51
+ #
52
+ # * %<n>: Time.now - use strftime option to control time format
53
+ # * %<t>: Thread name
54
+ # * %<m>: Fuse method
55
+ # * %<a>: Comma separate list of arguments
56
+ # * %<r>: Result of the method call (or any error raised)
57
+ # * %<p>: The value of prefix option
58
+ def debug_callback(fuse_method, *args, **options)
59
+ debug(fuse_method, args, yield(*args), **options)
60
+ rescue SystemCallError => e
61
+ # expected behaviour
62
+ debug(fuse_method, args, "#{e.class.name}(errno=#{e.errno}): #{e.message}", **options)
63
+ raise
64
+ rescue StandardError, ScriptError => e
65
+ # unexpected, debug with backtrace
66
+ debug(fuse_method, args, (["#{e.class.name}: #{e.message}"] + e.backtrace).join("\n\t"), **options)
67
+ raise
68
+ end
69
+
70
+ # @!visibility private
71
+ def debug(fuse_method, args, result, **options)
72
+ warn debug_format(fuse_method, args, result, **options)
73
+ result
74
+ end
75
+
76
+ # @!visibility private
77
+ def debug_format(fuse_method, args, result, prefix: 'DEBUG', strftime: '%FT%T%:z', format: DEFAULT_FORMAT)
78
+ format(format,
79
+ p: prefix,
80
+ n: Time.now.strftime(strftime),
81
+ t: Thread.current.name || Thread.current,
82
+ m: fuse_method,
83
+ a: args.map(&:to_s).join(','),
84
+ r: result)
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FFI
4
+ module Libfuse
5
+ module Adapter
6
+ # Wrapper module to assist filesystem written for Fuse3 to be compatible with Fuse2
7
+ module Fuse2Compat
8
+ # @!visibility private
9
+ # Wrapper shim for fuse methods to ensure compatibility with Fuse2
10
+ module Fuse2Prepend
11
+ include Adapter
12
+
13
+ def getattr(path, stat, fuse_file_info = nil)
14
+ super(path, stat, fuse_file_info)
15
+ end
16
+
17
+ def truncate(path, size, fuse_file_info = nil)
18
+ super(path, size, fuse_file_info)
19
+ end
20
+
21
+ def init(fuse_conn_info, fuse_config = nil)
22
+ super(fuse_conn_info, fuse_config)
23
+ end
24
+
25
+ def chown(path, uid, gid, fuse_file_info = nil)
26
+ super(path, uid, gid, fuse_file_info)
27
+ end
28
+
29
+ def chmod(path, mode, fuse_file_info = nil)
30
+ super(path, mode, fuse_file_info)
31
+ end
32
+
33
+ def utimens(path, atime, mtime, fuse_file_info = nil)
34
+ super(path, atime, mtime, fuse_file_info)
35
+ end
36
+
37
+ def readdir(path, buffer, filler, offset, fuse_file_info, fuse_readdir_flag = 0)
38
+ filler = proc { |buf, name, stat, off = 0, _fuse_fill_dir_flag = 0| filler.call(buf, name, stat, off) }
39
+ super(path, buffer, filler, offset, fuse_file_info, fuse_readdir_flag)
40
+ end
41
+
42
+ def fgetattr(*args)
43
+ getattr(*args)
44
+ end
45
+
46
+ def ftruncate(*args)
47
+ truncate(*args)
48
+ end
49
+
50
+ def fuse_respond_to?(fuse_method)
51
+ fuse_method = fuse_method[1..].to_sym if %i[fgetattr ftruncate].include?(fuse_method)
52
+ super(fuse_method)
53
+ end
54
+
55
+ def fuse_flags
56
+ res = defined?(super) ? super : []
57
+ if respond_to?(:init_fuse_config)
58
+ fuse_config = FuseConfig.new
59
+ init_fuse_config(fuse_config, :fuse2)
60
+ res << :nullpath_ok if fuse_config.nullpath_ok?
61
+ end
62
+
63
+ res
64
+ end
65
+ end
66
+
67
+ # @!visibility private
68
+ module Fuse3Prepend
69
+ def init(*args)
70
+ init_fuse_config(args.detect { |a| a.is_a?(FuseConfig) }) if respond_to?(:init_fuse_config)
71
+ super if defined?(super)
72
+ end
73
+ end
74
+
75
+ # @!visibility private
76
+ def self.included(mod)
77
+ prepend_module = Libfuse.FUSE_MAJOR_VERSION == 2 ? Fuse2Prepend : Fuse3Prepend
78
+ mod.prepend(prepend_module)
79
+ end
80
+
81
+ # @!method init_fuse_config(fuse_config,compat)
82
+ # @abstract
83
+ # Define this method to configure the fuse config object so that under Fuse2 the config options
84
+ # can be converted to appropriate flags.
85
+ #
86
+ # @param [FuseConfig] fuse_config the fuse config object
87
+ # @param [Symbol] compat either :fuse2 or :fuse3
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FFI
4
+ module Libfuse
5
+ module Adapter
6
+ # Wrapper module to assist filesystem written for Fuse2 to be compatible with Fuse3
7
+ #
8
+ # @note there are some deprecated Fuse2 features that have no forwards compatibility in Fuse3
9
+ #
10
+ # - flag nopath became fuse_config.nullpath_ok but it is not compatible since chmod/chown etc will now
11
+ # receive a null path but the filesystem cannot receive the new fuse_file_info arg
12
+ #
13
+ module Fuse3Support
14
+ # The actual module methods that are prepended
15
+ # @!visibility private
16
+ module Prepend
17
+ include Adapter
18
+
19
+ # Inner adapters received Fuse2 compatible args
20
+ def fuse3_compat?
21
+ false
22
+ end
23
+
24
+ def getattr(*args)
25
+ fi = args.pop
26
+ return fgetattr(*args, fi) if fi && respond_to?(:fgetattr)
27
+
28
+ super(*args)
29
+ end
30
+
31
+ def truncate(*args)
32
+ fi = args.pop
33
+ return ftruncate(*args, fi) if fi && respond_to?(:ftruncate)
34
+
35
+ super(*args)
36
+ end
37
+
38
+ def chown(*args)
39
+ args.pop
40
+ super(*args)
41
+ end
42
+
43
+ def chmod(*args)
44
+ args.pop
45
+ super(*args)
46
+ end
47
+
48
+ # TODO: Fuse3 deprecated flag utime_omit_ok - which meant that UTIME_OMIT and UTIME_NOW are passed through
49
+ # instead of ????
50
+ # Strictly if the flag is not set this compat shim should convert utime now values to Time.now
51
+ # but there is no way to handle OMIT
52
+ def utimens(*args)
53
+ args.pop
54
+ super(*args)
55
+ end
56
+
57
+ def init(*args)
58
+ args.pop
59
+
60
+ # TODO: populate FuseConfig with output from fuse_flags/FuseConnInfo where appropriate
61
+ super(*args)
62
+ end
63
+
64
+ def readdir(*args, &block)
65
+ # swallow the flag arg unknown to fuse 2
66
+ args.pop
67
+
68
+ args = args.map do |a|
69
+ next a unless a.is_a?(FFI::Function)
70
+
71
+ # wrap the filler proc for the extra flags argument
72
+ proc { |buf, name, stat, off| a.call(buf, name, stat, off, 0) }
73
+ end
74
+
75
+ super(*args, &block)
76
+ end
77
+ end
78
+
79
+ # @!visibility private
80
+ def self.included(mod)
81
+ # We prepend our shim module so caller doesn't have to call super
82
+ mod.prepend(Prepend) if FUSE_MAJOR_VERSION > 2
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../fuse_context'
4
+
5
+ module FFI
6
+ module Libfuse
7
+ module Adapter
8
+ # Wrapper module to handle interrupts
9
+ #
10
+ # Include this module if you want all requests to check for interruption before processing
11
+ #
12
+ # To handle interrupts only for specific callbacks just call {Libfuse.raise_interrupt} or
13
+ # {Libfuse.fuse_interrupted?} during callback processing rather than including this adapter
14
+ module Interrupt
15
+ # @!visibility private
16
+ def fuse_wrappers(*wrappers)
17
+ wrappers << {
18
+ wrapper: proc { |_fuse_method, *args, &b| Interrupt.interrupt_callback(*args, &b) },
19
+ excludes: %i[init destroy]
20
+ }
21
+ return wrappers unless defined?(super)
22
+
23
+ super(*wrappers)
24
+ end
25
+
26
+ module_function
27
+
28
+ # @raise [Errno::EINTR] if the fuse request is marked as interrupted
29
+ def interrupt_callback(*args)
30
+ return -Errno::EINTR::Errno if Libfuse.fuse_interrupted?
31
+
32
+ yield(*args)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end