ffi-libfuse 0.0.1.rctest12 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +3 -1
  3. data/CHANGELOG.md +60 -0
  4. data/LICENSE +21 -0
  5. data/README.md +127 -44
  6. data/lib/ffi/accessors.rb +6 -6
  7. data/lib/ffi/boolean_int.rb +27 -0
  8. data/lib/ffi/devt.rb +23 -0
  9. data/lib/ffi/encoding.rb +38 -0
  10. data/lib/ffi/flock.rb +7 -5
  11. data/lib/ffi/gnu_extensions.rb +1 -1
  12. data/lib/ffi/libfuse/ackbar.rb +3 -3
  13. data/lib/ffi/libfuse/adapter/context.rb +12 -10
  14. data/lib/ffi/libfuse/adapter/fuse2_compat.rb +52 -51
  15. data/lib/ffi/libfuse/adapter/fuse3_support.rb +7 -4
  16. data/lib/ffi/libfuse/adapter/interrupt.rb +1 -1
  17. data/lib/ffi/libfuse/adapter/ruby.rb +499 -148
  18. data/lib/ffi/libfuse/adapter/safe.rb +12 -11
  19. data/lib/ffi/libfuse/adapter.rb +1 -2
  20. data/lib/ffi/libfuse/callbacks.rb +1 -1
  21. data/lib/ffi/libfuse/filesystem/accounting.rb +116 -0
  22. data/lib/ffi/libfuse/filesystem/mapped_dir.rb +74 -0
  23. data/lib/ffi/libfuse/filesystem/mapped_files.rb +141 -0
  24. data/lib/ffi/libfuse/filesystem/pass_through_dir.rb +55 -0
  25. data/lib/ffi/libfuse/filesystem/pass_through_file.rb +45 -0
  26. data/lib/ffi/libfuse/filesystem/utils.rb +102 -0
  27. data/lib/ffi/libfuse/filesystem/virtual_dir.rb +306 -0
  28. data/lib/ffi/libfuse/filesystem/virtual_file.rb +94 -0
  29. data/lib/ffi/libfuse/filesystem/virtual_fs.rb +196 -0
  30. data/lib/ffi/libfuse/filesystem/virtual_node.rb +101 -0
  31. data/lib/ffi/libfuse/filesystem.rb +25 -0
  32. data/lib/ffi/libfuse/fuse2.rb +32 -24
  33. data/lib/ffi/libfuse/fuse3.rb +28 -18
  34. data/lib/ffi/libfuse/fuse_args.rb +71 -34
  35. data/lib/ffi/libfuse/fuse_buffer.rb +128 -26
  36. data/lib/ffi/libfuse/fuse_callbacks.rb +1 -5
  37. data/lib/ffi/libfuse/fuse_common.rb +60 -61
  38. data/lib/ffi/libfuse/fuse_config.rb +134 -143
  39. data/lib/ffi/libfuse/fuse_conn_info.rb +310 -134
  40. data/lib/ffi/libfuse/fuse_context.rb +45 -3
  41. data/lib/ffi/libfuse/fuse_operations.rb +57 -21
  42. data/lib/ffi/libfuse/fuse_opt.rb +1 -1
  43. data/lib/ffi/libfuse/fuse_version.rb +10 -6
  44. data/lib/ffi/libfuse/gem_version.rb +54 -0
  45. data/lib/ffi/libfuse/main.rb +96 -48
  46. data/lib/ffi/libfuse/test_helper.rb +145 -0
  47. data/lib/ffi/libfuse/version.rb +1 -1
  48. data/lib/ffi/libfuse.rb +13 -4
  49. data/lib/ffi/ruby_object.rb +4 -1
  50. data/lib/ffi/stat/constants.rb +9 -0
  51. data/lib/ffi/stat/native.rb +36 -6
  52. data/lib/ffi/stat/time_spec.rb +26 -10
  53. data/lib/ffi/stat.rb +111 -22
  54. data/lib/ffi/stat_vfs.rb +59 -1
  55. data/lib/ffi/struct_wrapper.rb +22 -1
  56. data/sample/hello_fs.rb +54 -0
  57. data/sample/memory_fs.rb +5 -181
  58. data/sample/no_fs.rb +20 -21
  59. data/sample/pass_through_fs.rb +30 -0
  60. metadata +83 -10
  61. data/lib/ffi/libfuse/adapter/thread_local_context.rb +0 -36
  62. data/lib/ffi/libfuse/test/operations.rb +0 -56
  63. data/lib/ffi/libfuse/test.rb +0 -3
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'filesystem/virtual_fs'
4
+
5
+ module FFI
6
+ module Libfuse
7
+ # This module namespace contains classes and modules to assist with building and composing filesystems
8
+ #
9
+ # ### Virtual Filesystems
10
+ # Classes to help compose in-memory filesystems {VirtualFS}, {VirtualDir}, {VirtualFile}
11
+ #
12
+ # ### Mapped Filesystem
13
+ # Modules to map paths in the fuse filesystem to either real files or other filesystem objects
14
+ # {MappedFiles}, {MappedDir}
15
+ #
16
+ # ### Pass-through filesystems
17
+ # Classes using the mapped modules above to pass fuse callbacks directly to an underlying file/directory
18
+ # {PassThroughDir}, {PassThroughFile}
19
+ #
20
+ # ### Utilities
21
+ # {Utils} similar to FileUtils to operate directly on filesystem objects, eg to build up initial content
22
+ module Filesystem
23
+ end
24
+ end
25
+ end
@@ -20,9 +20,9 @@ module FFI
20
20
  attach_function :fuse_chan_fd, [:chan], :int
21
21
  attach_function :fuse_read_cmd, [:fuse], :cmd, blocking: true
22
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
23
+ attach_function :fuse_unmount2, :fuse_unmount, %i[string chan], :void, blocking: true
25
24
  attach_function :fuse_loop_mt2, :fuse_loop_mt, [:fuse], :int, blocking: true
25
+ attach_function :fuse_exited2, :fuse_exited, [:fuse], :int
26
26
 
27
27
  class << self
28
28
  # @!visibility private
@@ -62,28 +62,22 @@ module FFI
62
62
  foreground = foreground_ptr.get(:int, 0) == 1
63
63
 
64
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) }
65
+ args.parse!(Main::STANDARD_OPTIONS, [result, handler]) { |**op_args| fuse_opt_proc(**op_args) }
66
66
  result
67
67
  end
68
68
 
69
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
70
+ def fuse_opt_proc(data:, key:, **)
71
+ run_args, handler = data
74
72
  case key
75
73
  when :show_help
76
74
  warn Main::HELP
77
- if handler.respond_to?(:fuse_help) && (help = handler.fuse_help)
78
- warn help
79
- end
75
+ warn handler.fuse_help if handler.respond_to?(:fuse_help)
80
76
  when :debug
81
77
  handler.fuse_debug(true) if handler.respond_to?(:fuse_debug)
82
78
  when :show_version
83
79
  warn Main.version
84
- if handler.respond_to?(:fuse_version) && (version = handler.fuse_version)
85
- warn version
86
- end
80
+ warn handler.fuse_version if handler.respond_to?(:fuse_version)
87
81
  else
88
82
  return :keep
89
83
  end
@@ -103,7 +97,7 @@ module FFI
103
97
 
104
98
  # Have we requested an unmount (note not actually checking if OS sees the fs as mounted)
105
99
  def mounted?
106
- @fuse && Libfuse.fuse_exited2(@fuse).zero?
100
+ @fuse && !fuse_exited?
107
101
  end
108
102
 
109
103
  def initialize(mountpoint, args, operations, private_data)
@@ -113,17 +107,15 @@ module FFI
113
107
  # Hang on to our ops and private data
114
108
  @operations = operations
115
109
 
116
- # Note this outputs the module args.
117
- @ch = Libfuse.fuse_mount2(@mountpoint, args)
110
+ # Note this outputs the module args. OSX cannot handle null mountpoint with -h/-V
111
+ @ch = Libfuse.fuse_mount2(@mountpoint || '', args)
118
112
  @ch = nil if @ch&.null?
119
113
  if @ch
120
114
  @fuse = Libfuse.fuse_new2(@ch, args, @operations, @operations.size, private_data)
121
115
  @fuse = nil if @fuse&.null?
122
116
  end
123
117
  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))
118
+ define_finalizer
127
119
  end
128
120
 
129
121
  # [IO] /dev/fuse file descriptor for use with IO.select
@@ -132,12 +124,16 @@ module FFI
132
124
  @io ||= ::IO.for_fd(Libfuse.fuse_chan_fd(@ch), 'r', autoclose: false)
133
125
  end
134
126
 
135
- def process
127
+ def fuse_exited?
128
+ !Libfuse.fuse_exited2(@fuse).zero?
129
+ end
130
+
131
+ def fuse_process
136
132
  cmd = Libfuse.fuse_read_cmd(@fuse)
137
- Libfuse.fuse_process_cmd(@fuse, cmd) if cmd && !cmd.null?
133
+ return false if cmd.null?
138
134
 
139
- # return true unless fuse has exited.
140
- Libfuse.fuse_exited2(@fuse).zero?
135
+ Libfuse.fuse_process_cmd(@fuse, cmd)
136
+ true
141
137
  end
142
138
 
143
139
  private
@@ -147,9 +143,21 @@ module FFI
147
143
  end
148
144
 
149
145
  def unmount
146
+ return unless @ch
147
+
150
148
  c = @ch
151
149
  @ch = nil
152
- Libfuse.fuse_unmount2(mountpoint, c) if c
150
+ Libfuse.fuse_unmount2(mountpoint, c)
151
+ ensure
152
+ # Can't unmount twice
153
+ define_finalizer
154
+ end
155
+
156
+ def define_finalizer
157
+ # if we unmount/destroy in the finalizer then the private_data object cannot be used in destory
158
+ # as it's weakref will have been GC'd
159
+ ObjectSpace.undefine_finalizer(self)
160
+ ObjectSpace.define_finalizer(self, self.class.finalize_fuse(@fuse, @mountpoint, @ch))
153
161
  end
154
162
  end
155
163
 
@@ -27,7 +27,7 @@ module FFI
27
27
  attach_function :fuse_session_fd, [:session], :int
28
28
  attach_function :fuse_session_exit, [:session], :void
29
29
  attach_function :fuse_session_exited, [:session], :int
30
- attach_function :fuse_unmount3, :fuse_unmount, %i[fuse], :void
30
+ attach_function :fuse_unmount3, :fuse_unmount, %i[fuse], :void, blocking: true
31
31
  attach_function :fuse_session_receive_buf, [:session, FuseBuf.by_ref], :int, blocking: true
32
32
  attach_function :fuse_session_process_buf, [:session, FuseBuf.by_ref], :void, blocking: true
33
33
 
@@ -56,7 +56,7 @@ module FFI
56
56
  class << self
57
57
  def parse_cmdline(args, handler: nil)
58
58
  cmdline_opts = FuseCmdlineOpts.new
59
- Libfuse.fuse_parse_cmdline3(args, cmdline_opts)
59
+ return nil unless Libfuse.fuse_parse_cmdline3(args, cmdline_opts).zero?
60
60
 
61
61
  handler&.fuse_debug(cmdline_opts.debug) if handler.respond_to?(:fuse_debug)
62
62
 
@@ -87,10 +87,10 @@ module FFI
87
87
  $stdout.puts "\n#{handler.fuse_help}" if handler.respond_to?(:fuse_help)
88
88
  end
89
89
 
90
- def finalize_fuse(fuse)
90
+ def finalize_fuse(fuse, mounted)
91
91
  proc do
92
92
  if fuse
93
- Libfuse.fuse_unmount3(fuse)
93
+ Libfuse.fuse_unmount3(fuse) if mounted
94
94
  Libfuse.fuse_destroy(fuse)
95
95
  end
96
96
  end
@@ -101,7 +101,7 @@ module FFI
101
101
 
102
102
  # Have we requested an unmount (note not actually checking if OS sees the fs as mounted)
103
103
  def mounted?
104
- (se = session) && Libfuse.fuse_session_exited(se).zero?
104
+ session && !fuse_exited? && @mounted
105
105
  end
106
106
 
107
107
  def initialize(mountpoint, args, operations, private_data)
@@ -119,24 +119,22 @@ module FFI
119
119
 
120
120
  @mounted = @fuse && Libfuse.fuse_mount3(@fuse, @mountpoint).zero?
121
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))
122
+ define_finalizer
125
123
  end
126
124
 
127
- def process
128
- buf = Thread.current[:fuse_buffer] ||= FuseBuf.new
129
- se = Thread.current[:fuse_session] ||= session
125
+ def fuse_exited?
126
+ !Libfuse.fuse_session_exited(session).zero?
127
+ end
130
128
 
129
+ def fuse_process
130
+ se = session
131
+ buf = Thread.current[:fuse_buffer] ||= FuseBuf.new
131
132
  res = Libfuse.fuse_session_receive_buf(se, buf)
132
133
 
133
- # errors, or exiting
134
- return false if res.negative?
135
-
136
- Libfuse.fuse_session_process_buf(se, buf) if res.positive?
134
+ return false unless res.positive?
137
135
 
138
- # return true unless fuse has exited.
139
- Libfuse.fuse_session_exited(se).zero?
136
+ Libfuse.fuse_session_process_buf(se, buf)
137
+ true
140
138
  end
141
139
 
142
140
  # [IO] /dev/fuse file descriptor for use with IO.select
@@ -152,7 +150,19 @@ module FFI
152
150
  end
153
151
 
154
152
  def unmount
155
- Libfuse.fuse_unmount3(@fuse) if @mounted && @fuse && !@fuse.null?
153
+ return unless @mounted && @fuse && !@fuse.null?
154
+
155
+ Libfuse.fuse_unmount3(@fuse)
156
+ @mounted = false
157
+ ensure
158
+ define_finalizer
159
+ end
160
+
161
+ def define_finalizer
162
+ # if we unmount/destroy in the finalizer then the private_data object cannot be used in destroy
163
+ # as it's weakref will have been GC'd
164
+ ObjectSpace.undefine_finalizer(self)
165
+ ObjectSpace.define_finalizer(self, self.class.finalize_fuse(@fuse, @mounted))
156
166
  end
157
167
  end
158
168
 
@@ -11,11 +11,16 @@ module FFI
11
11
  class FuseArgs < FFI::Struct
12
12
  layout :argc, :int, :argv, :pointer, :allocated, :int
13
13
 
14
- # Create an fuse_args struct from command line options
14
+ # Create a fuse_args struct from command line options
15
15
  # @param [Array<String>] argv command line args
16
- # args[0] is expected to be program name
16
+ #
17
+ # first arg is expected to be program name and is ignored by fuse_opt_parse
18
+ # it is handled specially only in fuse_parse_cmdline (ie no subtype is given)
17
19
  # @return [FuseArgs]
20
+ # @example
21
+ # FFI::Libfuse::FuseArgs.create($0,*ARGV)
18
22
  def self.create(*argv)
23
+ argv.unshift('ffi-libfuse') if argv.empty? || argv[0].start_with?('-')
19
24
  new.fill(*argv)
20
25
  end
21
26
 
@@ -34,13 +39,13 @@ module FFI
34
39
  end
35
40
 
36
41
  # @!attribute [r] argc
37
- # @return [Integer] count of args
42
+ # @return [Integer] count of args
38
43
  def argc
39
44
  self[:argc]
40
45
  end
41
46
 
42
47
  # @!attribute [r] argv
43
- # @return [Array<String>]
48
+ # @return [Array<String>] list of args
44
49
  def argv
45
50
  # noinspection RubyResolve
46
51
  self[:argv].get_array_of_pointer(0, argc).map(&:read_string)
@@ -53,7 +58,7 @@ module FFI
53
58
 
54
59
  # @!visibility private
55
60
  def inspect
56
- "#{self.class.name} - #{%i[argc argv allocated].map { |m| [m, send(m)] }.to_h}"
61
+ "#{self.class.name} - #{%i[argc argv allocated].to_h { |m| [m, send(m)] }}"
57
62
  end
58
63
 
59
64
  # Add an arg to this arg list
@@ -72,6 +77,8 @@ module FFI
72
77
  #
73
78
  # Option parsing function
74
79
  #
80
+ # Wraps fuse_opt_parse() in ruby sugar and safety
81
+ #
75
82
  # @param [Hash<String,Symbol>] opts option schema
76
83
  #
77
84
  # hash keys are a String template to match arguments against
@@ -80,7 +87,7 @@ module FFI
80
87
  # beginning with "-o"
81
88
  # 2. "foo", "foo-bar", etc. These match "-ofoo", "-ofoo-bar" or the relevant option in a comma separated option
82
89
  # list
83
- # 3. "bar=", "--foo=", etc. These are variations of 1) and 2) which have a parameter
90
+ # 3. "bar=", "--foo=", etc. These are variations of 1) and 2) which have a parameter value
84
91
  # 4. '%' Formats Not Supported (or needed for Ruby!)
85
92
  # 5. "-x ", etc. Matches either "-xparam" or "-x param" as two separate arguments
86
93
  # 6. '%' Formats Not Supported
@@ -91,45 +98,49 @@ module FFI
91
98
  # - :discard Argument is not passed to block, but behave as if the block returns :discard
92
99
  # - any other value is yielded as 'key' property on matching argument
93
100
  #
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.
101
+ # note that multiple templates can refer to the same key to support multiple option styles
103
102
  #
103
+ # @param [Object] data an optional object that will be passed to the block
104
+ # @param [Array<Symbol>|nil] ignore
105
+ # the keys in this list will be kept without being passed to the block. pass nil to observe all keys
106
+ # @yield [key:, value:, match:, data:, out:]
107
+ # block is called for each remaining arg
104
108
  # @yieldparam [Symbol] key determines why the processing function was called
105
109
  #
106
110
  # - :unmatched for arguments that *do not match* any supplied option
107
111
  # - :non_option for non-option arguments (after -- or not beginning with -)
108
112
  # - with appropriate value from opts hash for a matching argument
109
- #
110
- # @yieldparam [FuseArgs] outargs can {add} or {insert} additional args as required
111
- #
113
+ # @yieldparam [String|Boolean] value the option value or true for option without a value
114
+ # @yieldparam [String] match the matching template specification (ie a key in opts)
115
+ # @yieldparam [Object] data
116
+ # @yieldparam [FuseArgs] out can {add} or {insert} additional args as required
112
117
  # eg. if one arg implies another
113
118
  #
114
119
  # @yieldreturn [Symbol] the argument action
115
120
  #
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
121
+ # - :error an error, alternatively raise {Error}
122
+ # - :keep retain the current argument for further processing
123
+ # - :handled,:discard remove the current argument from further processing
124
+ # @return [nil|self] nil on error otherwise self
125
+ def parse!(opts, data = nil, ignore: %i[non_option unmatched], &block)
126
+ ignore ||= []
127
+
128
+ # first create an array of unique symbols such that positive indexes are custom options and negative indexes are
129
+ # special values (see fuse_opt.h), ie so we can turn the integer received in fuse_opt_proc back into a symbol
123
130
  symbols = opts.values.uniq + %i[discard keep non_option unmatched]
124
131
 
132
+ # transform our symbol keys into integers suitable for FuseOpts
125
133
  int_opts = opts.transform_values do |v|
126
134
  %i[discard keep].include?(v) ? symbols.rindex(v) - symbols.size : symbols.index(v)
127
135
  end
128
136
 
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)
137
+ # keep track of opt templates by key so we extract parameter values from arg
138
+ param_opts, bool_opts = opts.keys.partition { |t| t =~ /(\s+|=)$/ }.map do |opt_list|
139
+ opt_list.group_by { |t| opts[t] }
140
+ end
131
141
 
132
- result.zero? ? self : nil
142
+ fop = fuse_opt_proc(symbols, bool_opts, param_opts, ignore, &block)
143
+ Libfuse.fuse_opt_parse(self, data, int_opts, fop).zero? ? self : nil
133
144
  end
134
145
 
135
146
  private
@@ -137,15 +148,41 @@ module FFI
137
148
  # Valid return values from parse! block
138
149
  FUSE_OPT_PROC_RETURN = { error: -1, keep: 1, handled: 0, discard: 0 }.freeze
139
150
 
140
- def fuse_opt_proc(data, arg, key, out, &block)
141
- res = block.call(data, arg, key, out)
151
+ def fuse_opt_proc(symbols, bool_opts, param_opts, ignore, &block)
152
+ proc do |data, arg, key, out|
153
+ key = symbols[key]
154
+ next FUSE_OPT_PROC_RETURN.fetch(:keep) if ignore.include?(key)
155
+
156
+ match, value =
157
+ if %i[unmatched non_option].include?(key)
158
+ [nil, arg]
159
+ elsif bool_opts[key]&.include?(arg)
160
+ [arg, true]
161
+ elsif (opt = param_opts[key]&.detect { |t| arg.start_with?(t.rstrip) })
162
+ # Contrary to fuse_opt.h the separating space is not always stripped from these options
163
+ # https://github.com/libfuse/libfuse/issues/667
164
+ [opt, arg[opt.rstrip.length..].lstrip]
165
+ else
166
+ warn "FuseOptProc error - Cannot match option for #{arg}"
167
+ next FUSE_OPT_PROC_RETURN.fetch(:error)
168
+ end
169
+
170
+ safe_opt_proc(key: key, value: value, match: match, data: data, out: out, &block)
171
+ end
172
+ end
173
+
174
+ def safe_opt_proc(**args, &block)
175
+ res = block.call(**args)
142
176
  res.is_a?(Integer) ? res : FUSE_OPT_PROC_RETURN.fetch(res)
143
177
  rescue KeyError => e
144
178
  warn "FuseOptProc error - Unknown result #{e.key}"
145
- -1
146
- rescue StandardError => e
147
- warn "FuseOptProc error - #{e.class.name}:#{e.message}"
148
- -1
179
+ FUSE_OPT_PROC_RETURN.fetch(:error)
180
+ rescue Error => e
181
+ warn "#{e.message}: #{args.select { |k, _v| %i[key value].include?(k) }}\n#{argv}"
182
+ FUSE_OPT_PROC_RETURN.fetch(:error)
183
+ rescue StandardError, ScriptError => e
184
+ warn "FuseOptProc error - #{e.class.name}:#{e.message}\n\t#{e.backtrace.join("\n\t")}"
185
+ FUSE_OPT_PROC_RETURN.fetch(:error)
149
186
  end
150
187
  end
151
188
 
@@ -14,7 +14,6 @@ module FFI
14
14
  # Generic data buffer for I/O, extended attributes, etc...Data may be supplied as a memory pointer or as a file
15
15
  # descriptor
16
16
  #
17
- # @todo define helper methods to create buffers pointing to file_descriptors or allocated memory
18
17
  class FuseBuf < FFI::Struct
19
18
  layout(
20
19
  size: :size_t, # Size of data in bytes
@@ -26,19 +25,60 @@ module FFI
26
25
 
27
26
  # rubocop:disable Naming/MethodParameterName
28
27
 
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
28
+ # @!attribute [r] mem
29
+ # @return [FFI::Pointer] the memory in the buffer
30
+ def mem
31
+ self[:mem]
32
+ end
33
+ alias memory mem
34
+
35
+ # @!attribute [r] fd
36
+ # @return [Integer] the file descriptor number
37
+ def fd
38
+ self[:fd]
39
+ end
40
+ alias file_descriptor fd
41
+
42
+ # @return [Boolean] true if this a memory buffer
43
+ def mem?
44
+ !fd?
45
+ end
46
+
47
+ # @return [Boolean] true if this is a file descriptor buffer
48
+ def fd?
49
+ self[:flags].include?(:is_fd)
50
+ end
51
+ alias file_descriptor? fd?
52
+
53
+ # Resize mem to smaller than initially allocated
54
+ # @param [Integer] new_size
55
+ # @return [void]
56
+ def resize(new_size)
57
+ self[:size] = new_size
58
+ end
59
+
60
+ # @overload fill(size:, mem:, auto_release)
61
+ # Fill as a Memory buffer
62
+ # @param [Integer] size Size of data in bytes
63
+ # @param [FFI::Pointer] mem Memory pointer allocated to size if required
64
+ # @param [Boolean] autorelease
65
+ #
66
+ # @overload fill(fd:, fd_retry:, pos:)
67
+ # Fill as a FileDescriptor buffer
68
+ # @param [Integer] fd File descriptor
69
+ # @param [Boolean] fd_retry
70
+ # Retry operations on file descriptor
34
71
  #
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.
72
+ # If this flag is set then retry operation on file descriptor until .size bytes have been copied or an error
73
+ # or EOF is detected.
37
74
  #
38
- # @param [Integer] pos
39
- # If > 0 then used to seek to the given offset before performing operation on file descriptor.
75
+ # @param [Integer] pos
76
+ # If > 0 then used to seek to the given offset before performing operation on file descriptor.
40
77
  # @return [self]
41
- def fill(size:, mem: FFI::Pointer::NULL, fd: -1, fd_retry: false, pos: 0)
78
+ def fill(mem: FFI::Pointer::NULL, size: mem.null? ? 0 : mem.size, fd: -1, fd_retry: false, pos: 0)
79
+ mem = FFI::MemoryPointer.new(:char, size, true) if fd == -1 && mem.null? && size.positive?
80
+ mem.autorelease = to_ptr.autorelease? unless mem.null?
81
+
42
82
  self[:size] = size
43
83
  self[:mem] = mem
44
84
  self[:fd] = fd
@@ -60,47 +100,86 @@ module FFI
60
100
  #
61
101
  # Allocate dynamically to add more than one buffer.
62
102
  #
63
- # @todo find a use for {FuseOperations#read_buf} and implement necessary helpers
64
103
  class FuseBufVec < FFI::Struct
65
104
  layout(
66
105
  count: :size_t,
67
106
  idx: :size_t,
68
107
  off: :size_t,
69
- buf: :pointer
108
+ # but is treated as a variable length array of FuseBuf at size +1 following the struct.
109
+ buf: [FuseBuf, 1] # struct fuse_buf[1]
70
110
  )
111
+
71
112
  # @!attribute [r] count
72
- # @todo implement
73
113
  # @return [Integer] the number of buffers in the array
114
+ def count
115
+ self[:count]
116
+ end
74
117
 
75
118
  # @!attribute [r] index
76
- # @todo implement
77
119
  # @return [Integer] index of current buffer within the array
120
+ def index
121
+ self[:idx]
122
+ end
123
+ alias idx index
78
124
 
79
125
  # @!attribute [r] offset
80
- # @todo implement
81
126
  # @return [Integer] current offset within the current buffer
127
+ def offset
128
+ self[:off]
129
+ end
130
+ alias off offset
82
131
 
83
132
  # @!attribute [r] buffers
84
- # @todo implement
85
133
  # @return [Array<FuseBuf>] array of buffers
134
+ def buffers
135
+ @buffers ||= Array.new(count) do |i|
136
+ next self[:buf].first if i.zero?
137
+
138
+ FuseBuf.new(self[:buf].to_ptr + (i * FuseBuf.size))
139
+ end
140
+ end
141
+
142
+ # @!attribute [r] current
143
+ # @return [FuseBuf] the current buffer
144
+ def current
145
+ return self[:buf].first if index.zero?
146
+
147
+ FuseBuf.new(self[:buf].to_ptr + (index * FuseBuf.size))
148
+ end
86
149
 
87
- # @see #init
88
- def self.init(**buf_options)
89
- new.init(**buf_options)
150
+ # Create and initialise a new FuseBufVec
151
+ # @param [Boolean] autorelease should the struct be freed on GC (default NO!!!)
152
+ # @param [Hash] buf_options options for configuring the initial buffer. See {#init}
153
+ # @yield(buf,index)
154
+ # @yieldparam [FuseBuf] buf
155
+ # @yieldparam [Integer] index
156
+ # @yieldreturn [void]
157
+ # @return [FuseBufVec]
158
+ def self.init(autorelease: true, count: 1, **buf_options)
159
+ bufvec_ptr = FFI::MemoryPointer.new(:uchar, FuseBufVec.size + (FuseBuf.size * (count - 1)), true)
160
+ bufvec_ptr.autorelease = autorelease
161
+ bufvec = new(bufvec_ptr)
162
+ bufvec[:count] = count
163
+ bufvec.init(**buf_options)
164
+
165
+ buffers.each_with_index { |b, i| yield i, b } if block_given?
166
+ bufvec
90
167
  end
91
168
 
92
- # Allocate a vector containing a single buffer
169
+ # Set and initialise a specific buffer
93
170
  #
94
171
  # 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
172
+ # @param [Integer] index the index of the buffer
173
+ # @param [Hash<Symbol,Object>] buf_options see {FuseBuf#fill}
174
+ # @return [FuseBuf] the initial buffer
175
+ def init(index: 0, **buf_options)
176
+ self[:idx] = index
99
177
  self[:off] = 0
100
- self[:buf] = FuseBuf.new.fill(**buf_options), to_ptr
178
+ current.fill(**buf_options) unless buf_options.empty?
101
179
  self
102
180
  end
103
181
 
182
+ # Would pref this to be called #size but clashes with FFI::Struct, might need StructWrapper
104
183
  # @return [Integer] total size of data in a fuse buffer vector
105
184
  def buf_size
106
185
  Libfuse.fuse_buf_size(self)
@@ -141,6 +220,29 @@ module FFI
141
220
  def copy_to(dst, *flags)
142
221
  Libfuse.fuse_buf_copy(dst, self, flags)
143
222
  end
223
+
224
+ # Copy our data direct to file descriptor
225
+ # @param [Integer] fileno a file descriptor
226
+ # @param [Integer] offset
227
+ # @param [Array<Symbol>] flags - see {copy_to}
228
+ # @return [Integer] number of bytes copied
229
+ def copy_to_fd(fileno, offset = 0, *flags)
230
+ dst = self.class.init(size: buf_size, fd: fileno, pos: offset)
231
+ copy_to(dst, *flags)
232
+ end
233
+
234
+ # Copy to string via a temporary buffer
235
+ # @param [Array<Symbol>] flags - see {copy_to}
236
+ # @return [String] the extracted data
237
+ def copy_to_str(*flags)
238
+ dst = FuseBufVec.init(size: buf_size)
239
+ copied = copy_to(dst, *flags)
240
+ dst.current.memory.read_string(copied)
241
+ end
242
+
243
+ def copy_from(src, *flags)
244
+ Libfuse.fuse_buf_copy(self, src, flags)
245
+ end
144
246
  end
145
247
 
146
248
  attach_function :fuse_buf_size, [FuseBufVec.by_ref], :size_t
@@ -31,7 +31,7 @@ module FFI
31
31
 
32
32
  def initialize_callbacks(delegate:, wrappers: [])
33
33
  wrappers = delegate.fuse_wrappers(*wrappers) if delegate.respond_to?(:fuse_wrappers)
34
- super(callback_members, delegate: delegate, wrappers: wrappers)
34
+ super(fuse_callbacks, delegate: delegate, wrappers: wrappers)
35
35
  end
36
36
 
37
37
  def respond_to_callback?(method, delegate)
@@ -39,10 +39,6 @@ module FFI
39
39
 
40
40
  super
41
41
  end
42
-
43
- def callback_members
44
- members - [:flags]
45
- end
46
42
  end
47
43
  end
48
44
  end