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