ffi-libfuse 0.0.1.pre → 0.1.0.rc20220550

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +3 -1
  3. data/CHANGES.md +14 -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/gnu_extensions.rb +1 -1
  11. data/lib/ffi/libfuse/ackbar.rb +6 -8
  12. data/lib/ffi/libfuse/adapter/context.rb +12 -10
  13. data/lib/ffi/libfuse/adapter/fuse2_compat.rb +52 -51
  14. data/lib/ffi/libfuse/adapter/fuse3_support.rb +0 -1
  15. data/lib/ffi/libfuse/adapter/ruby.rb +499 -148
  16. data/lib/ffi/libfuse/adapter/safe.rb +1 -1
  17. data/lib/ffi/libfuse/adapter.rb +1 -2
  18. data/lib/ffi/libfuse/callbacks.rb +1 -1
  19. data/lib/ffi/libfuse/filesystem/accounting.rb +116 -0
  20. data/lib/ffi/libfuse/filesystem/mapped_dir.rb +74 -0
  21. data/lib/ffi/libfuse/filesystem/mapped_files.rb +141 -0
  22. data/lib/ffi/libfuse/filesystem/pass_through_dir.rb +55 -0
  23. data/lib/ffi/libfuse/filesystem/pass_through_file.rb +45 -0
  24. data/lib/ffi/libfuse/filesystem/utils.rb +102 -0
  25. data/lib/ffi/libfuse/filesystem/virtual_dir.rb +306 -0
  26. data/lib/ffi/libfuse/filesystem/virtual_file.rb +94 -0
  27. data/lib/ffi/libfuse/filesystem/virtual_fs.rb +188 -0
  28. data/lib/ffi/libfuse/filesystem/virtual_node.rb +101 -0
  29. data/lib/ffi/libfuse/filesystem.rb +25 -0
  30. data/lib/ffi/libfuse/fuse2.rb +21 -21
  31. data/lib/ffi/libfuse/fuse3.rb +12 -12
  32. data/lib/ffi/libfuse/fuse_args.rb +69 -34
  33. data/lib/ffi/libfuse/fuse_buffer.rb +128 -26
  34. data/lib/ffi/libfuse/fuse_callbacks.rb +1 -5
  35. data/lib/ffi/libfuse/fuse_common.rb +55 -61
  36. data/lib/ffi/libfuse/fuse_config.rb +134 -143
  37. data/lib/ffi/libfuse/fuse_conn_info.rb +310 -134
  38. data/lib/ffi/libfuse/fuse_context.rb +45 -3
  39. data/lib/ffi/libfuse/fuse_operations.rb +43 -19
  40. data/lib/ffi/libfuse/fuse_version.rb +10 -6
  41. data/lib/ffi/libfuse/main.rb +80 -37
  42. data/lib/ffi/libfuse/thread_pool.rb +1 -1
  43. data/lib/ffi/libfuse/version.rb +1 -1
  44. data/lib/ffi/libfuse.rb +13 -4
  45. data/lib/ffi/ruby_object.rb +1 -1
  46. data/lib/ffi/stat/constants.rb +9 -0
  47. data/lib/ffi/stat/native.rb +36 -6
  48. data/lib/ffi/stat/time_spec.rb +28 -12
  49. data/lib/ffi/stat.rb +111 -22
  50. data/lib/ffi/stat_vfs.rb +59 -1
  51. data/lib/ffi/struct_wrapper.rb +22 -1
  52. data/sample/hello_fs.rb +54 -0
  53. data/sample/memory_fs.rb +5 -181
  54. data/sample/no_fs.rb +20 -21
  55. data/sample/pass_through_fs.rb +30 -0
  56. metadata +66 -7
  57. data/lib/ffi/libfuse/adapter/thread_local_context.rb +0 -36
@@ -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,8 +107,8 @@ 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)
@@ -132,12 +126,16 @@ module FFI
132
126
  @io ||= ::IO.for_fd(Libfuse.fuse_chan_fd(@ch), 'r', autoclose: false)
133
127
  end
134
128
 
135
- def process
129
+ def fuse_exited?
130
+ !Libfuse.fuse_exited2(@fuse).zero?
131
+ end
132
+
133
+ def fuse_process
136
134
  cmd = Libfuse.fuse_read_cmd(@fuse)
137
- Libfuse.fuse_process_cmd(@fuse, cmd) if cmd && !cmd.null?
135
+ return false if cmd.null?
138
136
 
139
- # return true unless fuse has exited.
140
- Libfuse.fuse_exited2(@fuse).zero?
137
+ Libfuse.fuse_process_cmd(@fuse, cmd)
138
+ true
141
139
  end
142
140
 
143
141
  private
@@ -147,9 +145,11 @@ module FFI
147
145
  end
148
146
 
149
147
  def unmount
148
+ return unless @ch
149
+
150
150
  c = @ch
151
151
  @ch = nil
152
- Libfuse.fuse_unmount2(mountpoint, c) if c
152
+ Libfuse.fuse_unmount2(mountpoint, c)
153
153
  end
154
154
  end
155
155
 
@@ -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
 
@@ -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?
105
105
  end
106
106
 
107
107
  def initialize(mountpoint, args, operations, private_data)
@@ -124,19 +124,19 @@ module FFI
124
124
  ObjectSpace.define_finalizer(self, self.class.finalize_fuse(@fuse))
125
125
  end
126
126
 
127
- def process
128
- buf = Thread.current[:fuse_buffer] ||= FuseBuf.new
129
- se = Thread.current[:fuse_session] ||= session
127
+ def fuse_exited?
128
+ !Libfuse.fuse_session_exited(session).zero?
129
+ end
130
130
 
131
+ def fuse_process
132
+ se = session
133
+ buf = Thread.current[:fuse_buffer] ||= FuseBuf.new
131
134
  res = Libfuse.fuse_session_receive_buf(se, buf)
132
135
 
133
- # errors, or exiting
134
- return false if res.negative?
135
-
136
- Libfuse.fuse_session_process_buf(se, buf) if res.positive?
136
+ return false unless res.positive?
137
137
 
138
- # return true unless fuse has exited.
139
- Libfuse.fuse_session_exited(se).zero?
138
+ Libfuse.fuse_session_process_buf(se, buf)
139
+ true
140
140
  end
141
141
 
142
142
  # [IO] /dev/fuse file descriptor for use with IO.select
@@ -11,10 +11,13 @@ 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
17
18
  # @return [FuseArgs]
19
+ # @example
20
+ # FFI::Libfuse::FuseArgs.create($0,*ARGV)
18
21
  def self.create(*argv)
19
22
  new.fill(*argv)
20
23
  end
@@ -34,13 +37,13 @@ module FFI
34
37
  end
35
38
 
36
39
  # @!attribute [r] argc
37
- # @return [Integer] count of args
40
+ # @return [Integer] count of args
38
41
  def argc
39
42
  self[:argc]
40
43
  end
41
44
 
42
45
  # @!attribute [r] argv
43
- # @return [Array<String>]
46
+ # @return [Array<String>] list of args
44
47
  def argv
45
48
  # noinspection RubyResolve
46
49
  self[:argv].get_array_of_pointer(0, argc).map(&:read_string)
@@ -53,7 +56,7 @@ module FFI
53
56
 
54
57
  # @!visibility private
55
58
  def inspect
56
- "#{self.class.name} - #{%i[argc argv allocated].map { |m| [m, send(m)] }.to_h}"
59
+ "#{self.class.name} - #{%i[argc argv allocated].to_h { |m| [m, send(m)] }}"
57
60
  end
58
61
 
59
62
  # Add an arg to this arg list
@@ -72,6 +75,8 @@ module FFI
72
75
  #
73
76
  # Option parsing function
74
77
  #
78
+ # Wraps fuse_opt_parse() in ruby sugar and safety
79
+ #
75
80
  # @param [Hash<String,Symbol>] opts option schema
76
81
  #
77
82
  # hash keys are a String template to match arguments against
@@ -80,7 +85,7 @@ module FFI
80
85
  # beginning with "-o"
81
86
  # 2. "foo", "foo-bar", etc. These match "-ofoo", "-ofoo-bar" or the relevant option in a comma separated option
82
87
  # list
83
- # 3. "bar=", "--foo=", etc. These are variations of 1) and 2) which have a parameter
88
+ # 3. "bar=", "--foo=", etc. These are variations of 1) and 2) which have a parameter value
84
89
  # 4. '%' Formats Not Supported (or needed for Ruby!)
85
90
  # 5. "-x ", etc. Matches either "-xparam" or "-x param" as two separate arguments
86
91
  # 6. '%' Formats Not Supported
@@ -91,45 +96,49 @@ module FFI
91
96
  # - :discard Argument is not passed to block, but behave as if the block returns :discard
92
97
  # - any other value is yielded as 'key' property on matching argument
93
98
  #
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.
99
+ # note that multiple templates can refer to the same key to support multiple option styles
103
100
  #
101
+ # @param [Object] data an optional object that will be passed to the block
102
+ # @param [Array<Symbol>|nil] ignore
103
+ # the keys in this list will be kept without being passed to the block. pass nil to observe all keys
104
+ # @yield [key:, value:, match:, data:, out:]
105
+ # block is called for each remaining arg
104
106
  # @yieldparam [Symbol] key determines why the processing function was called
105
107
  #
106
108
  # - :unmatched for arguments that *do not match* any supplied option
107
109
  # - :non_option for non-option arguments (after -- or not beginning with -)
108
110
  # - with appropriate value from opts hash for a matching argument
109
- #
110
- # @yieldparam [FuseArgs] outargs can {add} or {insert} additional args as required
111
- #
111
+ # @yieldparam [String|Boolean] value the option value or true for option without a value
112
+ # @yieldparam [String] match the matching template specification (ie a key in opts)
113
+ # @yieldparam [Object] data
114
+ # @yieldparam [FuseArgs] out can {add} or {insert} additional args as required
112
115
  # eg. if one arg implies another
113
116
  #
114
117
  # @yieldreturn [Symbol] the argument action
115
118
  #
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
119
+ # - :error an error, alternatively raise {Error}
120
+ # - :keep retain the current argument for further processing
121
+ # - :handled,:discard remove the current argument from further processing
122
+ # @return [nil|self] nil on error otherwise self
123
+ def parse!(opts, data = nil, ignore: %i[non_option unmatched], &block)
124
+ ignore ||= []
125
+
126
+ # first create an array of unique symbols such that positive indexes are custom options and negative indexes are
127
+ # special values (see fuse_opt.h), ie so we can turn the integer received in fuse_opt_proc back into a symbol
123
128
  symbols = opts.values.uniq + %i[discard keep non_option unmatched]
124
129
 
130
+ # transform our symbol keys into integers suitable for FuseOpts
125
131
  int_opts = opts.transform_values do |v|
126
132
  %i[discard keep].include?(v) ? symbols.rindex(v) - symbols.size : symbols.index(v)
127
133
  end
128
134
 
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)
135
+ # keep track of opt templates by key so we extract parameter values from arg
136
+ param_opts, bool_opts = opts.keys.partition { |t| t =~ /(\s+|=)$/ }.map do |opt_list|
137
+ opt_list.group_by { |t| opts[t] }
138
+ end
131
139
 
132
- result.zero? ? self : nil
140
+ fop = fuse_opt_proc(symbols, bool_opts, param_opts, ignore, &block)
141
+ Libfuse.fuse_opt_parse(self, data, int_opts, fop).zero? ? self : nil
133
142
  end
134
143
 
135
144
  private
@@ -137,15 +146,41 @@ module FFI
137
146
  # Valid return values from parse! block
138
147
  FUSE_OPT_PROC_RETURN = { error: -1, keep: 1, handled: 0, discard: 0 }.freeze
139
148
 
140
- def fuse_opt_proc(data, arg, key, out, &block)
141
- res = block.call(data, arg, key, out)
149
+ def fuse_opt_proc(symbols, bool_opts, param_opts, ignore, &block)
150
+ proc do |data, arg, key, out|
151
+ key = symbols[key]
152
+ next FUSE_OPT_PROC_RETURN.fetch(:keep) if ignore.include?(key)
153
+
154
+ match, value =
155
+ if %i[unmatched non_option].include?(key)
156
+ [nil, arg]
157
+ elsif bool_opts[key]&.include?(arg)
158
+ [arg, true]
159
+ elsif (opt = param_opts[key]&.detect { |t| arg.start_with?(t.rstrip) })
160
+ # Contrary to fuse_opt.h the separating space is not always stripped from these options
161
+ # https://github.com/libfuse/libfuse/issues/667
162
+ [opt, arg[opt.rstrip.length..].lstrip]
163
+ else
164
+ warn "FuseOptProc error - Cannot match option for #{arg}"
165
+ next -1
166
+ end
167
+
168
+ safe_opt_proc(key: key, value: value, match: match, data: data, out: out, &block)
169
+ end
170
+ end
171
+
172
+ def safe_opt_proc(**args, &block)
173
+ res = block.call(**args)
142
174
  res.is_a?(Integer) ? res : FUSE_OPT_PROC_RETURN.fetch(res)
143
175
  rescue KeyError => e
144
176
  warn "FuseOptProc error - Unknown result #{e.key}"
145
- -1
146
- rescue StandardError => e
147
- warn "FuseOptProc error - #{e.class.name}:#{e.message}"
148
- -1
177
+ FUSE_OPT_PROC_RETURN.fetch(:error)
178
+ rescue Error => e
179
+ warn "#{e.message}: #{args.select { |k, _v| %i[key value].include?(k) }}\n#{argv}"
180
+ FUSE_OPT_PROC_RETURN.fetch(:error)
181
+ rescue StandardError, ScriptError => e
182
+ warn "FuseOptProc error - #{e.class.name}:#{e.message}\n\t#{e.backtrace.join("\n\t")}"
183
+ FUSE_OPT_PROC_RETURN.fetch(:error)
149
184
  end
150
185
  end
151
186
 
@@ -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