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,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FFI
4
+ module Libfuse
5
+ # This namespace contains helper Modules that alter the signature or behaviour of the native {FuseOperations}
6
+ # callbacks
7
+ #
8
+ # There are two types of Adapters
9
+ #
10
+ # 1) Wrap many fuse methods in a proc that manipulates the arguments in a consistent way
11
+ #
12
+ # These will implement {FuseOperations#fuse_wrappers} to add the proc which can then...
13
+ #
14
+ # * prepend additional arguments - eg. ({Context})
15
+ # * wrap common arguments - eg. ({Pathname})
16
+ # * handle return values/exceptions - eg. ({Safe})
17
+ # * or just wrap the underlying block - eg. ({Debug})
18
+ #
19
+ # 2) Override specific callback methods to change their signatures
20
+ #
21
+ # These will prepend an internal module that implements the callback methods, manipulating the
22
+ # argument list and passing on to super and then manipulating the result.
23
+ #
24
+ # The prepend module must include Adapter so that the module methods themselves do not register as
25
+ # callbacks unless there is an implementation by the including class.
26
+ #
27
+ # eg. {Ruby}, {Fuse2Compat}, {Fuse3Support}
28
+ module Adapter
29
+ # Does something other than an adapter (ie the underlying filesystem itself) implement fuse_method.
30
+ #
31
+ # Can be called by custom implementations of fuse_respond_to? to confirm that the implementing module
32
+ # is not a type 2 Adapter (which itself will call super)
33
+ def fuse_super_respond_to?(fuse_method)
34
+ return false unless respond_to?(fuse_method)
35
+
36
+ m = method(fuse_method)
37
+ m = m.super_method while m && Adapter.include?(m.owner)
38
+
39
+ m && true
40
+ end
41
+
42
+ # @!visibility private
43
+
44
+ # Use {#fuse_super_respond_to?} if no super implementation
45
+ def fuse_respond_to?(fuse_method)
46
+ return super if defined?(super)
47
+
48
+ fuse_super_respond_to?(fuse_method)
49
+ end
50
+
51
+ class << self
52
+ # @!visibility private
53
+
54
+ # is mod an Adapter
55
+ def include?(mod)
56
+ adapters.include?(mod)
57
+ end
58
+
59
+ def included(mod)
60
+ adapters << mod
61
+ end
62
+
63
+ def adapters
64
+ # Avoid Kernel#open being considered a fuse callback
65
+ @adapters ||= [Kernel, Object, BasicObject]
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ require_relative 'adapter/context'
73
+ require_relative 'adapter/thread_local_context'
74
+ require_relative 'adapter/debug'
75
+ require_relative 'adapter/ruby'
76
+ require_relative 'adapter/interrupt'
77
+ require_relative 'adapter/pathname'
78
+ require_relative 'adapter/fuse3_support'
79
+ require_relative 'adapter/fuse2_compat'
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FFI
4
+ module Libfuse
5
+ # Methods to register callbacks and wrappers
6
+ module Callbacks
7
+ # Registers block as a callback method
8
+ #
9
+ # @note wrappers are defined in inside out order
10
+ #
11
+ # @param [Symbol] method the callback being registered
12
+ # @param [Array<Proc,Hash<:wrapper,:excludes>,Object>] wrappers with each entry being
13
+ #
14
+ # - a Proc(method, *args, &block)
15
+ #
16
+ # either handling the callback itself or yielding *args (possibly after manipulation) onwards to &block.
17
+ # Do not include method in the yield args!
18
+ # - a Hash with keys
19
+ #
20
+ # - wrapper: [Proc] as above
21
+ # - excludes: [Array<Symbol>] names of methods that the proc should not apply to. Useful when registering
22
+ # the same list of wrappers for many methods
23
+ # - an Object responding to #method(*args,&block)
24
+ #
25
+ # @param [Proc] block if provided is used as the innermost block to handle the callback - equivalent to
26
+ # being the first entry in wrappers list
27
+ def register(method, wrappers = [], &block)
28
+ callback = wrappers.each.inject(block) do |b, w|
29
+ next wrap_callback(method, **w, &b) if w.is_a?(Hash)
30
+
31
+ wrap_callback(method, w, &b)
32
+ end
33
+ send(:[]=, method, callback)
34
+ end
35
+
36
+ private
37
+
38
+ def initialize_callbacks(callbacks, delegate:, wrappers: [])
39
+ callbacks.select { |m| respond_to_callback?(m, delegate) }.each do |m|
40
+ register(m, wrappers) { |*f_args| delegate.send(m, *f_args) }
41
+ end
42
+ end
43
+
44
+ def respond_to_callback?(method, delegate)
45
+ delegate.respond_to?(method)
46
+ end
47
+
48
+ def wrap_callback(method, proc_wrapper = nil, wrapper: proc_wrapper, excludes: [], &block)
49
+ return block if excludes.include?(method)
50
+
51
+ # Wrapper proc takes fuse_method as first arg, but the resulting proc only takes the callback args
52
+ # ie so wrappers should not yield the fuse_method onwards!!
53
+ return proc { |*args| wrapper.call(method, *args, &block) } if wrapper.is_a?(Proc)
54
+
55
+ return proc { |*args| wrapper.send(method, *args, &block) } if wrapper.respond_to?(method)
56
+
57
+ block
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'fuse_version'
4
+ require_relative 'fuse_operations'
5
+ require_relative 'fuse_args'
6
+ require_relative 'fuse_common'
7
+ require_relative '../ruby_object'
8
+
9
+ module FFI
10
+ # Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
11
+ module Libfuse
12
+ raise "Cannot load Fuse2 for #{FUSE_VERSION}" unless FUSE_MAJOR_VERSION == 2
13
+
14
+ typedef :pointer, :chan
15
+ typedef :pointer, :cmd
16
+
17
+ attach_function :fuse_parse_cmdline2, :fuse_parse_cmdline, [FuseArgs.by_ref, :pointer, :pointer, :pointer], :int
18
+ attach_function :fuse_mount2, :fuse_mount, [:string, FuseArgs.by_ref], :chan
19
+ attach_function :fuse_new2, :fuse_new, [:chan, FuseArgs.by_ref, FuseOperations.by_ref, :size_t, RubyObject], :fuse
20
+ attach_function :fuse_chan_fd, [:chan], :int
21
+ attach_function :fuse_read_cmd, [:fuse], :cmd, blocking: true
22
+ attach_function :fuse_process_cmd, %i[fuse cmd], :void, blocking: true
23
+ attach_function :fuse_exited2, :fuse_exited, [:fuse], :int
24
+ attach_function :fuse_unmount2, :fuse_unmount, %i[string chan], :void
25
+ attach_function :fuse_loop_mt2, :fuse_loop_mt, [:fuse], :int, blocking: true
26
+
27
+ class << self
28
+ # @!visibility private
29
+ # @!method fuse_parse_cmdline2
30
+ # @!method fuse_mount2
31
+ # @!method fuse_new2
32
+ # @!method fuse_chan_fd
33
+ # @!method fuse_read_cmd
34
+ # @!method fuse_process_cmd
35
+ # @!method fuse_exited2
36
+ # @!method fuse_unmount2
37
+ # @!method fuse_loop_mt2
38
+ end
39
+
40
+ # Helper class to managed a mounted fuse filesystem
41
+ # @!visibility private
42
+ class Fuse2 < FuseCommon
43
+ class << self
44
+ def parse_cmdline(args, handler: nil)
45
+ # This also handles -h to print help information on stderr
46
+ # Parse mountpoint, -f , -s from args
47
+ # @return [Array<(String,Boolean,Boolean)>|nil]
48
+ # mountpoint, multi_thread, foreground options from args if available
49
+ # nil if no mountpoint, or options is requesting help or version information
50
+ mountpoint_ptr = FFI::MemoryPointer.new(:pointer, 1)
51
+ multi_thread_ptr = FFI::MemoryPointer.new(:int, 1)
52
+ foreground_ptr = FFI::MemoryPointer.new(:int, 1)
53
+
54
+ return nil unless Libfuse.fuse_parse_cmdline2(args, mountpoint_ptr, multi_thread_ptr, foreground_ptr).zero?
55
+
56
+ # noinspection RubyResolve
57
+ mp_data_ptr = mountpoint_ptr.get_pointer(0)
58
+
59
+ mountpoint = mp_data_ptr.read_string unless mp_data_ptr.null?
60
+
61
+ multi_thread = multi_thread_ptr.get(:int, 0) == 1
62
+ foreground = foreground_ptr.get(:int, 0) == 1
63
+
64
+ result = { mountpoint: mountpoint, single_thread: !multi_thread, foreground: foreground }
65
+ args.parse!(Main::STANDARD_OPTIONS, [result, handler]) { |*proc_args| fuse_opt_proc(*proc_args) }
66
+ result
67
+ end
68
+
69
+ # Handle standard custom args
70
+ def fuse_opt_proc(custom, _arg, key, _outargs)
71
+ return :keep if %i[unmatched non_option].include?(key)
72
+
73
+ run_args, handler = custom
74
+ case key
75
+ when :show_help
76
+ warn Main::HELP
77
+ if handler.respond_to?(:fuse_help) && (help = handler.fuse_help)
78
+ warn help
79
+ end
80
+ when :debug
81
+ handler.fuse_debug(true) if handler.respond_to?(:fuse_debug)
82
+ when :show_version
83
+ warn Main.version
84
+ if handler.respond_to?(:fuse_version) && (version = handler.fuse_version)
85
+ warn version
86
+ end
87
+ else
88
+ return :keep
89
+ end
90
+ run_args[key] = true
91
+ :keep
92
+ end
93
+
94
+ def finalize_fuse(fuse, mountpoint, fuse_ch)
95
+ proc do
96
+ Libfuse.fuse_unmount2(mountpoint, fuse_ch) if fuse_ch
97
+ Libfuse.fuse_destroy(fuse) if fuse
98
+ end
99
+ end
100
+ end
101
+
102
+ attr_reader :mountpoint
103
+
104
+ # Have we requested an unmount (note not actually checking if OS sees the fs as mounted)
105
+ def mounted?
106
+ @fuse && Libfuse.fuse_exited2(@fuse).zero?
107
+ end
108
+
109
+ def initialize(mountpoint, args, operations, private_data)
110
+ super()
111
+ @mountpoint = mountpoint
112
+
113
+ # Hang on to our ops and private data
114
+ @operations = operations
115
+
116
+ # Note this outputs the module args.
117
+ @ch = Libfuse.fuse_mount2(@mountpoint, args)
118
+ @ch = nil if @ch&.null?
119
+ if @ch
120
+ @fuse = Libfuse.fuse_new2(@ch, args, @operations, @operations.size, private_data)
121
+ @fuse = nil if @fuse&.null?
122
+ end
123
+ ensure
124
+ # if we unmount/destroy in the finalizer then the private_data object cannot be used in destory
125
+ # as it's weakref will have been GC'd
126
+ ObjectSpace.define_finalizer(self, self.class.finalize_fuse(@fuse, @mountpoint, @ch))
127
+ end
128
+
129
+ # [IO] /dev/fuse file descriptor for use with IO.select
130
+ def io
131
+ # The FD is created (and destroyed?) by FUSE so we don't want ruby to do anything with it during GC
132
+ @io ||= ::IO.for_fd(Libfuse.fuse_chan_fd(@ch), 'r', autoclose: false)
133
+ end
134
+
135
+ def process
136
+ cmd = Libfuse.fuse_read_cmd(@fuse)
137
+ Libfuse.fuse_process_cmd(@fuse, cmd) if cmd && !cmd.null?
138
+
139
+ # return true unless fuse has exited.
140
+ Libfuse.fuse_exited2(@fuse).zero?
141
+ end
142
+
143
+ private
144
+
145
+ def native_fuse_loop_mt(**_options)
146
+ Libfuse.fuse_loop_mt2(@fuse)
147
+ end
148
+
149
+ def unmount
150
+ c = @ch
151
+ @ch = nil
152
+ Libfuse.fuse_unmount2(mountpoint, c) if c
153
+ end
154
+ end
155
+
156
+ # @!visibility private
157
+ Fuse = Fuse2
158
+ end
159
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'fuse_common'
4
+ require_relative 'fuse_cmdline_opts'
5
+ require_relative 'fuse_loop_config'
6
+ require_relative 'fuse_args'
7
+ require_relative 'fuse_operations'
8
+ require_relative '../ruby_object'
9
+
10
+ module FFI
11
+ # Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
12
+ module Libfuse
13
+ raise "Cannot load Fuse3 for #{FUSE_VERSION}" unless FUSE_MAJOR_VERSION == 3
14
+
15
+ attach_function :fuse_parse_cmdline3,
16
+ :fuse_parse_cmdline, [FuseArgs.by_ref, FuseCmdlineOpts.by_ref], :int
17
+ attach_function :fuse_cmdline_help, [], :void
18
+ attach_function :fuse_lowlevel_help, [], :void
19
+ attach_function :fuse_lib_help, [FuseArgs.by_ref], :void
20
+ attach_function :fuse_pkgversion, [], :string
21
+ attach_function :fuse_lowlevel_version, [], :void
22
+ attach_function :fuse_new3,
23
+ :fuse_new, [FuseArgs.by_ref, FuseOperations.by_ref, :size_t, RubyObject], :fuse
24
+ attach_function :fuse_mount3, :fuse_mount, %i[fuse string], :int
25
+ attach_function :fuse_loop_mt3,
26
+ :fuse_loop_mt, [:fuse, FuseLoopConfig.by_ref], :int, blocking: true
27
+ attach_function :fuse_session_fd, [:session], :int
28
+ attach_function :fuse_session_exit, [:session], :void
29
+ attach_function :fuse_session_exited, [:session], :int
30
+ attach_function :fuse_unmount3, :fuse_unmount, %i[fuse], :void
31
+ attach_function :fuse_session_receive_buf, [:session, FuseBuf.by_ref], :int, blocking: true
32
+ attach_function :fuse_session_process_buf, [:session, FuseBuf.by_ref], :void, blocking: true
33
+
34
+ class << self
35
+ # @!visibility private
36
+ # @!method fuse_parse_cmdline3
37
+ # @!method fuse_cmdline_help
38
+ # @!method fuse_lowlevel_help
39
+ # @!method fuse_lib_help
40
+ # @!method fuse_pkgversion
41
+ # @!method fuse_lowlevel_version
42
+ # @!method fuse_new3
43
+ # @!method fuse_mount3
44
+ # @!method fuse_loop_mt3
45
+ # @!method fuse_session_fd
46
+ # @!method fuse_session_exit
47
+ # @!method fuse_session_exited
48
+ # @!method fuse_unmount3
49
+ # @!method fuse_session_receive_buf
50
+ # @!method fuse_session_process_buf
51
+ end
52
+
53
+ # Helper class to managed a mounted fuse filesystem
54
+ # @!visibility private
55
+ class Fuse3 < FuseCommon
56
+ class << self
57
+ def parse_cmdline(args, handler: nil)
58
+ cmdline_opts = FuseCmdlineOpts.new
59
+ Libfuse.fuse_parse_cmdline3(args, cmdline_opts)
60
+
61
+ handler&.fuse_debug(cmdline_opts.debug) if handler.respond_to?(:fuse_debug)
62
+
63
+ # mimics fuse_main which exits after printing version info, even if -h
64
+ if cmdline_opts.show_version
65
+ show_version(handler)
66
+ elsif cmdline_opts.show_help
67
+ show_help(args, handler)
68
+ end
69
+
70
+ cmdline_opts.to_h
71
+ end
72
+
73
+ def show_version(handler)
74
+ $stdout.puts "FUSE library version #{Libfuse.fuse_pkgversion}"
75
+ Libfuse.fuse_lowlevel_version
76
+ $stdout.puts Main.version
77
+ $stdout.puts handler.fuse_version if handler.respond_to?(:fuse_version)
78
+ end
79
+
80
+ def show_help(args, handler)
81
+ $stdout.puts "usage: #{args.argv.first} [options] <mountpoint>\n\n"
82
+ $stdout.puts "FUSE options:\n"
83
+ Libfuse.fuse_cmdline_help
84
+ Libfuse.fuse_lib_help(args)
85
+ $stdout.puts "\n"
86
+ $stdout.puts Main::HELP
87
+ $stdout.puts "\n#{handler.fuse_help}" if handler.respond_to?(:fuse_help)
88
+ end
89
+
90
+ def finalize_fuse(fuse)
91
+ proc do
92
+ if fuse
93
+ Libfuse.fuse_unmount3(fuse)
94
+ Libfuse.fuse_destroy(fuse)
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ attr_reader :mountpoint
101
+
102
+ # Have we requested an unmount (note not actually checking if OS sees the fs as mounted)
103
+ def mounted?
104
+ (se = session) && Libfuse.fuse_session_exited(se).zero?
105
+ end
106
+
107
+ def initialize(mountpoint, args, operations, private_data)
108
+ super()
109
+ warn 'No mountpoint provided' unless mountpoint
110
+ return unless mountpoint
111
+
112
+ @mountpoint = mountpoint
113
+
114
+ # Hang on to our ops and private data
115
+ @operations = operations
116
+
117
+ @fuse = Libfuse.fuse_new3(args, @operations, @operations.size, private_data)
118
+ @fuse = nil if @fuse&.null?
119
+
120
+ @mounted = @fuse && Libfuse.fuse_mount3(@fuse, @mountpoint).zero?
121
+ ensure
122
+ # if we unmount/destroy in the finalizer then the private_data object cannot be used in destroy
123
+ # as it's weakref will have been GC'd
124
+ ObjectSpace.define_finalizer(self, self.class.finalize_fuse(@fuse))
125
+ end
126
+
127
+ def process
128
+ buf = Thread.current[:fuse_buffer] ||= FuseBuf.new
129
+ se = Thread.current[:fuse_session] ||= session
130
+
131
+ res = Libfuse.fuse_session_receive_buf(se, buf)
132
+
133
+ # errors, or exiting
134
+ return false if res.negative?
135
+
136
+ Libfuse.fuse_session_process_buf(se, buf) if res.positive?
137
+
138
+ # return true unless fuse has exited.
139
+ Libfuse.fuse_session_exited(se).zero?
140
+ end
141
+
142
+ # [IO] /dev/fuse file descriptor for use with IO.select
143
+ def io
144
+ # The FD is created (and destroyed?) by FUSE so we don't want ruby to do anything with it during GC
145
+ @io ||= ::IO.for_fd(Libfuse.fuse_session_fd(session), 'r', autoclose: false)
146
+ end
147
+
148
+ private
149
+
150
+ def native_fuse_loop_mt(max_idle_threads: 10, **_options)
151
+ Libfuse.fuse_loop_mt3(@fuse, FuseLoopConfig.new.fill(max_idle: max_idle_threads))
152
+ end
153
+
154
+ def unmount
155
+ Libfuse.fuse_unmount3(@fuse) if @mounted && @fuse && !@fuse.null?
156
+ end
157
+ end
158
+
159
+ # @!visibility private
160
+ Fuse = Fuse3
161
+ end
162
+ end