ffi-libfuse 0.3.4 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/README.md +1 -1
  4. data/lib/ffi/accessors.rb +419 -93
  5. data/lib/ffi/boolean_int.rb +1 -1
  6. data/lib/ffi/devt.rb +36 -10
  7. data/lib/ffi/flock.rb +31 -27
  8. data/lib/ffi/libfuse/adapter/context.rb +1 -1
  9. data/lib/ffi/libfuse/adapter/debug.rb +54 -16
  10. data/lib/ffi/libfuse/adapter/fuse2_compat.rb +43 -26
  11. data/lib/ffi/libfuse/adapter/fuse3_support.rb +7 -8
  12. data/lib/ffi/libfuse/adapter/interrupt.rb +1 -1
  13. data/lib/ffi/libfuse/adapter/pathname.rb +1 -1
  14. data/lib/ffi/libfuse/adapter/ruby.rb +211 -160
  15. data/lib/ffi/libfuse/adapter/safe.rb +70 -22
  16. data/lib/ffi/libfuse/callbacks.rb +2 -1
  17. data/lib/ffi/libfuse/filesystem/accounting.rb +1 -1
  18. data/lib/ffi/libfuse/filesystem/mapped_files.rb +33 -7
  19. data/lib/ffi/libfuse/filesystem/pass_through_dir.rb +0 -1
  20. data/lib/ffi/libfuse/filesystem/virtual_dir.rb +294 -127
  21. data/lib/ffi/libfuse/filesystem/virtual_file.rb +85 -79
  22. data/lib/ffi/libfuse/filesystem/virtual_fs.rb +34 -15
  23. data/lib/ffi/libfuse/filesystem/virtual_link.rb +60 -0
  24. data/lib/ffi/libfuse/filesystem/virtual_node.rb +104 -87
  25. data/lib/ffi/libfuse/filesystem.rb +1 -1
  26. data/lib/ffi/libfuse/fuse2.rb +3 -2
  27. data/lib/ffi/libfuse/fuse3.rb +6 -6
  28. data/lib/ffi/libfuse/fuse_args.rb +14 -21
  29. data/lib/ffi/libfuse/fuse_buf.rb +112 -0
  30. data/lib/ffi/libfuse/fuse_buf_vec.rb +228 -0
  31. data/lib/ffi/libfuse/fuse_cmdline_opts.rb +19 -16
  32. data/lib/ffi/libfuse/fuse_common.rb +10 -4
  33. data/lib/ffi/libfuse/fuse_config.rb +35 -23
  34. data/lib/ffi/libfuse/fuse_conn_info.rb +1 -1
  35. data/lib/ffi/libfuse/fuse_context.rb +2 -1
  36. data/lib/ffi/libfuse/fuse_loop_config.rb +68 -20
  37. data/lib/ffi/libfuse/fuse_operations.rb +86 -41
  38. data/lib/ffi/libfuse/gem_helper.rb +2 -9
  39. data/lib/ffi/libfuse/io.rb +56 -0
  40. data/lib/ffi/libfuse/main.rb +33 -26
  41. data/lib/ffi/libfuse/test_helper.rb +67 -61
  42. data/lib/ffi/libfuse/version.rb +1 -1
  43. data/lib/ffi/libfuse.rb +1 -1
  44. data/lib/ffi/stat/native.rb +4 -4
  45. data/lib/ffi/stat.rb +35 -12
  46. data/lib/ffi/stat_vfs.rb +1 -2
  47. data/lib/ffi/struct_array.rb +2 -1
  48. data/lib/ffi/struct_wrapper.rb +6 -4
  49. data/sample/hello_fs.rb +1 -1
  50. metadata +6 -3
  51. data/lib/ffi/libfuse/fuse_buffer.rb +0 -257
@@ -9,9 +9,6 @@ module FFI
9
9
  module Libfuse
10
10
  # Can be included test classes to assist with running/debugging filesystems
11
11
  module TestHelper
12
- # rubocop:disable Metrics/AbcSize
13
- # rubocop:disable Metrics/MethodLength
14
-
15
12
  # Runs the fuse loop on a pre configured fuse filesystem
16
13
  # @param [FuseOperations] operations
17
14
  # @param [Array<String>] args to pass to {FFI::Libfuse::Main.fuse_create}
@@ -19,7 +16,7 @@ module FFI
19
16
  # @yield [mnt]
20
17
  # caller can execute and test file operations using mnt and ruby File/Dir etc
21
18
  # the block is run in a forked process and is successful unless an exception is raised
22
- # @yieldparam [String] mnt the temporary direct used as the mount point
19
+ # @yieldparam [String] mnt the temporary directory used as the mount point
23
20
  # @raise [Error] if unexpected state is found during operations
24
21
  # @return [void]
25
22
  def with_fuse(operations, *args, **options)
@@ -34,34 +31,14 @@ module FFI
34
31
  yield mnt
35
32
  end
36
33
 
37
- fuse = FFI::Libfuse::Main.fuse_create(mnt, *args, operations: operations)
38
- raise FFI::Libfuse::Error, 'No fuse object returned from fuse_create' unless fuse
39
-
40
- # Rake owns INT
41
- fuse.default_traps.delete(:TERM)
42
- fuse.default_traps.delete(:INT)
43
-
44
- raise FFI::Libfuse::Error, 'fuse object is not mounted?' unless fuse.mounted?
45
-
46
- t = Thread.new { fuse.run(foreground: true, **options) }
47
-
48
- # TODO: Work out why waitpid2 hangs on mac unless the process has already finished
49
- sleep 10 if mac_fuse?
50
-
51
- _pid, block_status = Process.waitpid2(fpid)
52
- block_exit = block_status.exitstatus
53
- fuse.exit('fuse_helper')&.join
54
- run_result = t.value
55
-
56
- raise FFI::Libfuse::Error, 'fuse is still mounted after fuse.exit' if fuse.mounted?
57
- raise FFI::Libfuse::Error, "forked file operations failed with #{block_exit}" unless block_exit.zero?
58
- raise FFI::Libfuse::Error, "fuse run failed #{run_result}" unless run_result.zero?
34
+ run_fuse(mnt, *args, operations: operations, **options) do
35
+ # TODO: Work out why waitpid2 hangs on mac unless the process has already finished
36
+ sleep 10 if mac_fuse?
59
37
 
60
- if !mac_fuse? && mounted?(mnt)
61
- raise FFI::Libfuse::Error, "OS reports fuse is still mounted at #{mnt} after fuse.exit"
38
+ _pid, block_status = Process.waitpid2(fpid)
39
+ block_exit = block_status.exitstatus
40
+ raise FFI::Libfuse::Error, "forked file operations failed with #{block_exit}" unless block_exit.zero?
62
41
  end
63
-
64
- true
65
42
  end
66
43
  end
67
44
 
@@ -77,42 +54,28 @@ module FFI
77
54
  # @note if the filesystem is configured to daemonize then no output will be captured
78
55
  def run_filesystem(filesystem, *args, env: {})
79
56
  fsname = File.basename(filesystem)
80
- safe_fuse do |mnt|
81
- t = Thread.new do
82
- if defined?(Bundler)
83
- Bundler.with_unbundled_env do
84
- Open3.capture3(env, 'bundle', 'exec', filesystem.to_s, mnt, "-ofsname=#{fsname}", *args, binmode: true)
85
- end
86
- else
87
- Open3.capture3(env, filesystem.to_s, mnt, "-ofsname=#{fsname}", *args, binmode: true)
88
- end
89
- end
57
+
58
+ t, err = safe_fuse do |mnt|
59
+ t = Thread.new { open3_filesystem(args, env, filesystem, fsname, mnt) }
90
60
  sleep 1
91
61
 
92
62
  begin
93
- if block_given?
94
- raise Error, "#{fsname} not mounted at #{mnt}" unless mounted?(mnt, fsname)
95
-
96
- yield mnt
97
- end
98
- # rubocop:disable Lint/RescueException
99
- # Minitest::Assertion and other test assertion classes are not derived from StandardError
100
- rescue Exception => _err
101
- # rubocop:enable Lint/RescueException
102
- unmount(mnt) if mounted?(mnt)
103
- o, e, _s = t.value
104
- warn "Errors\n#{e}" unless e.empty?
105
- warn "Output\n#{o}" unless o.empty?
106
- raise
107
- end
63
+ raise Error, "#{fsname} not mounted at #{mnt}" if block_given? && !mounted?(mnt, fsname)
108
64
 
109
- unmount(mnt) if mounted?(mnt)
110
- o, e, s = t.value
111
- [o, e, s.exitstatus]
65
+ yield mnt if block_given?
66
+ [t, nil]
67
+ rescue Minitest::Assertion, StandardError => e
68
+ [t, e]
69
+ end
112
70
  end
71
+
72
+ o, e, s = t.value
73
+ return [o, e, s.exitstatus] unless err
74
+
75
+ warn "Errors\n#{e}" unless e.empty?
76
+ warn "Output\n#{o}" unless o.empty?
77
+ raise err
113
78
  end
114
- # rubocop:enable Metrics/AbcSize
115
- # rubocop:enable Metrics/MethodLength
116
79
 
117
80
  def mounted?(mnt, _filesystem = '.*')
118
81
  type, prefix = mac_fuse? ? %w[macfuse /private] : %w[fuse]
@@ -124,7 +87,7 @@ module FFI
124
87
  if mac_fuse?
125
88
  system("diskutil unmount force #{mnt} >/dev/null 2>&1")
126
89
  else
127
- system("fusermount -zu #{mnt} >/dev/null 2>&1")
90
+ system("fusermount#{FUSE_MAJOR_VERSION == 3 ? '3' : ''} -zu #{mnt} >/dev/null 2>&1")
128
91
  end
129
92
  end
130
93
 
@@ -140,6 +103,49 @@ module FFI
140
103
  def mac_fuse?
141
104
  FFI::Platform::IS_MAC
142
105
  end
106
+
107
+ private
108
+
109
+ def open3_filesystem(args, env, filesystem, fsname, mnt)
110
+ if ENV['BUNDLER_GEMFILE']
111
+ Open3.capture3(env, 'bundle', 'exec', filesystem.to_s, mnt, "-ofsname=#{fsname}", *args, binmode: true)
112
+ else
113
+ Open3.capture3(env, filesystem.to_s, mnt, "-ofsname=#{fsname}", *args, binmode: true)
114
+ end
115
+ end
116
+
117
+ def run_fuse(mnt, *args, operations:, **options)
118
+ fuse = mount_fuse(mnt, *args, operations: operations)
119
+ begin
120
+ t = Thread.new do
121
+ Thread.current.name = 'Fuse Run'
122
+ # Rake owns INT
123
+ fuse.run(foreground: true, traps: { INT: nil, TERM: nil }, **options)
124
+ end
125
+
126
+ yield
127
+ ensure
128
+ fuse.exit('fuse_helper')&.join
129
+ run_result = t.value
130
+
131
+ raise FFI::Libfuse::Error, 'fuse is still mounted after fuse.exit' if fuse.mounted?
132
+ raise FFI::Libfuse::Error, "fuse run failed #{run_result}" unless run_result.zero?
133
+
134
+ if !mac_fuse? && mounted?(mnt)
135
+ raise FFI::Libfuse::Error, "OS reports fuse is still mounted at #{mnt} after fuse.exit"
136
+ end
137
+ end
138
+ end
139
+
140
+ def mount_fuse(mnt, *args, operations:)
141
+ operations.fuse_debug(args.include?('-d')) if operations.respond_to?(:fuse_debug)
142
+
143
+ fuse = FFI::Libfuse::Main.fuse_create(mnt, *args, operations: operations)
144
+ raise FFI::Libfuse::Error, 'No fuse object returned from fuse_create' unless fuse
145
+ raise FFI::Libfuse::Error, 'fuse object is not mounted?' unless fuse.mounted?
146
+
147
+ fuse
148
+ end
143
149
  end
144
150
  end
145
151
  end
@@ -3,6 +3,6 @@
3
3
  module FFI
4
4
  # Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
5
5
  module Libfuse
6
- VERSION = '0.3.4'
6
+ VERSION = '0.4.1'
7
7
  end
8
8
  end
data/lib/ffi/libfuse.rb CHANGED
@@ -11,7 +11,7 @@ require_relative 'devt'
11
11
  module FFI
12
12
  # Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
13
13
  module Libfuse
14
- # Filesystems can raise this error to indicate errors from filesystem users
14
+ # Filesystems can raise this error to indicate misconfiguration issues etc...
15
15
  class Error < StandardError; end
16
16
 
17
17
  # Opinionated default args for {.main}.
@@ -27,14 +27,14 @@ module FFI
27
27
  :unused, [:long, 3]
28
28
 
29
29
  [['', :string], ['l', :string], ['f', :int]].each do |(prefix, ftype)|
30
- native_func = "native_#{prefix}stat".to_sym
31
- lib_func = "#{prefix}stat".to_sym
30
+ native_func = :"native_#{prefix}stat"
31
+ lib_func = :"#{prefix}stat"
32
32
  begin
33
33
  ::FFI::Stat.attach_function native_func, lib_func, [ftype, by_ref], :int
34
34
  rescue FFI::NotFoundError
35
35
  # gLibc 2.31 (Ubuntu focal) does not export these functions, it maps them to __xstat variants
36
- native_xfunc = "native_#{prefix}xstat".to_sym
37
- lib_xfunc = "__#{prefix}xstat".to_sym
36
+ native_xfunc = :"native_#{prefix}xstat"
37
+ lib_xfunc = :"__#{prefix}xstat"
38
38
  ::FFI::Stat.attach_function native_xfunc, lib_xfunc, [:int, ftype, by_ref], :int
39
39
  # 1 is 64 bit versions of struct stat, 3 is 32 bit
40
40
  ::FFI::Stat.define_singleton_method(native_func) { |file, buf| send(native_xfunc, 1, file, buf) }
data/lib/ffi/stat.rb CHANGED
@@ -34,9 +34,9 @@ module FFI
34
34
  int_members = Native
35
35
  .members
36
36
  .select { |m| m.to_s.start_with?('st_') && !m.to_s.end_with?('timespec') }
37
- .map { |m| m[3..].to_sym }
37
+ .to_h { |m| [:"#{m[3..]}", m] }
38
38
 
39
- ffi_attr_accessor(*int_members, format: 'st_%s')
39
+ ffi_attr_accessor(**int_members)
40
40
 
41
41
  # @!attribute [rw] atime
42
42
  # @return [Time] time of last access
@@ -47,12 +47,13 @@ module FFI
47
47
  # @!attribute [rw] ctime
48
48
  # @return [Time] time of last status change
49
49
 
50
- time_members = Native.members.select { |m| m.to_s =~ /^st_.*timespec$/ }.map { |m| m[3..-5].to_sym }
50
+ time_members = Native.members.select { |m| m.to_s =~ /^st_.*timespec$/ }.to_h { |m| [:"#{m[3..-5]}", m] }
51
51
 
52
- ffi_attr_reader(*time_members, format: 'st_%sspec', &:time)
52
+ ffi_attr_reader(**time_members, &:time)
53
53
 
54
- ffi_attr_writer(*time_members, format: 'st_%sspec', simple: false) do |sec, nsec = 0|
55
- self[__method__[0..-2].to_sym].set_time(sec, nsec)
54
+ ffi_attr_writer_method(**time_members) do |sec, nsec = 0|
55
+ _attr, member = ffi_attr_writer_member(__method__)
56
+ self[member].set_time(sec, nsec)
56
57
  end
57
58
 
58
59
  # Fill content for a regular file
@@ -62,9 +63,9 @@ module FFI
62
63
  # @param [Integer] gid
63
64
  # @param [Hash] args additional system specific stat fields
64
65
  # @return [self]
65
- def file(mode:, size:, uid: Process.uid, gid: Process.gid, **args)
66
+ def file(mode:, size:, nlink: 1, uid: Process.uid, gid: Process.gid, **args)
66
67
  mode = ((S_IFREG & S_IFMT) | (mode & 0o777))
67
- fill(mode: mode, size: size, uid: uid, gid: gid, **args)
68
+ fill(mode: mode, size: size, nlink: nlink, uid: uid, gid: gid, **args)
68
69
  end
69
70
 
70
71
  # Fill content for a directory
@@ -80,6 +81,18 @@ module FFI
80
81
  end
81
82
  alias directory dir
82
83
 
84
+ # Fill content for a symbolic link
85
+ # @param [Integer] size length of the target name (including null terminator)
86
+ # @param [Integer] mode
87
+ # @param [Integer] uid
88
+ # @param [Integer] gid
89
+ # @param [Hash] args additional system specific stat fields
90
+ # @return [self]
91
+ def symlink(size:, mode: 0o777, nlink: 1, uid: Process.uid, gid: Process.gid, **args)
92
+ mode = ((S_IFLNK & S_IFMT) | (mode & 0o777))
93
+ fill(mode: mode, nlink: nlink, size: size, uid: uid, gid: gid, **args)
94
+ end
95
+
83
96
  # Fill attributes from file (using native LIBC calls)
84
97
  # @param [Integer|:to_s] file descriptor or a file path
85
98
  # @param [Boolean] follow links
@@ -123,7 +136,7 @@ module FFI
123
136
  # @param [Integer] mask (see umask)
124
137
  # @param [Hash] overrides see {fill}
125
138
  # @return self
126
- def mask(mask = 0o4000, **overrides)
139
+ def mask(mask = S_ISUID, **overrides)
127
140
  fill(mode: mode & (~mask), **overrides)
128
141
  end
129
142
 
@@ -147,17 +160,27 @@ module FFI
147
160
  mode & S_ISVTX != 0
148
161
  end
149
162
 
163
+ def symlink?
164
+ mode & S_IFLNK != 0
165
+ end
166
+
150
167
  class << self
151
- # @!method file(stat,**fields)
168
+ # @!method file(**fields)
152
169
  # @return [Stat]
153
170
  # @raise [SystemCallError]
154
171
  # @see Stat#file
155
172
 
156
- # @!method dir(stat,**fields)
173
+ # @!method dir(**fields)
157
174
  # @return [Stat]
158
175
  # @raise [SystemCallError]
159
176
  # @see Stat#dir
160
- %i[file dir].each { |m| define_method(m) { |stat = new, **args| stat.send(m, **args) } }
177
+
178
+ # @!method symlink(**fields)
179
+ # @return [Stat]
180
+ # @raise [SystemCallError]
181
+ # @see Stat#symlink
182
+
183
+ %i[file dir symlink].each { |m| define_method(m) { |stat = new, **args| stat.send(m, **args) } }
161
184
  alias directory dir
162
185
 
163
186
  # @!method from(file, stat = new(), follow: false)
data/lib/ffi/stat_vfs.rb CHANGED
@@ -75,8 +75,7 @@ module FFI
75
75
  # @!attribute [rw] namemax
76
76
  # @return [Integer] Maximum filename length
77
77
 
78
- int_members = members.grep(/^f_/).map { |m| m[2..].to_sym }
79
- ffi_attr_accessor(*int_members, format: 'f_%s')
78
+ ffi_attr_accessor(**members.grep(/^f_/).to_h { |m| [m[2..].to_sym, m] })
80
79
 
81
80
  extend FFI::Library
82
81
  ffi_lib FFI::Library::LIBC
@@ -10,6 +10,7 @@ module FFI
10
10
  def array(size)
11
11
  ArrayConverter.new(self, size)
12
12
  end
13
+ alias [] array
13
14
 
14
15
  # @!visibility private
15
16
  # Helper to handle callbacks containing fixed length array of struct
@@ -28,7 +29,7 @@ module FFI
28
29
  def from_native(ptr, _ctx)
29
30
  return [] if ptr.null?
30
31
 
31
- Array.new(@size) { |i| @type.new(ptr + (i * @type.size)) }
32
+ Array.new(@size) { |i| @type.new(ptr + (i * @type.size)) }.freeze
32
33
  end
33
34
 
34
35
  def to_native(ary, _ctx)
@@ -27,13 +27,13 @@ module FFI
27
27
  return Pointer::NULL if value.nil?
28
28
 
29
29
  value = value.native if value.is_a?(StructWrapper)
30
- super(value, ctx)
30
+ super
31
31
  end
32
32
 
33
33
  def from_native(value, ctx)
34
34
  return nil if value.null?
35
35
 
36
- native = super(value, ctx)
36
+ native = super
37
37
  @wrapper_class.new(native)
38
38
  end
39
39
  end
@@ -100,12 +100,14 @@ module FFI
100
100
 
101
101
  # Get attribute
102
102
  def [](member_or_attr)
103
- @native[self.class.ffi_attr_readers.fetch(member_or_attr, member_or_attr)]
103
+ _attr, member = ffi_attr_reader_member(member_or_attr, member_or_attr)
104
+ @native[member]
104
105
  end
105
106
 
106
107
  # Set attribute
107
108
  def []=(member_or_attr, val)
108
- @native[self.class.ffi_attr_writers.fetch(member_or_attr, member_or_attr)] = val
109
+ _attr, member = ffi_attr_writer_member(member_or_attr, member_or_attr)
110
+ @native[member] = val
109
111
  end
110
112
 
111
113
  # Pass unimplemented methods on to {#native} underlying struct
data/sample/hello_fs.rb CHANGED
@@ -51,4 +51,4 @@ class HelloFS
51
51
  end
52
52
 
53
53
  # Start the file system
54
- FFI::Libfuse.fuse_main(operations: HelloFS.new) if __FILE__ == $0
54
+ exit(FFI::Libfuse.fuse_main(operations: HelloFS.new)) if __FILE__ == $0
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ffi-libfuse
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Grant Gardner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-08 00:00:00.000000000 Z
11
+ date: 2024-10-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -161,11 +161,13 @@ files:
161
161
  - lib/ffi/libfuse/filesystem/virtual_dir.rb
162
162
  - lib/ffi/libfuse/filesystem/virtual_file.rb
163
163
  - lib/ffi/libfuse/filesystem/virtual_fs.rb
164
+ - lib/ffi/libfuse/filesystem/virtual_link.rb
164
165
  - lib/ffi/libfuse/filesystem/virtual_node.rb
165
166
  - lib/ffi/libfuse/fuse2.rb
166
167
  - lib/ffi/libfuse/fuse3.rb
167
168
  - lib/ffi/libfuse/fuse_args.rb
168
- - lib/ffi/libfuse/fuse_buffer.rb
169
+ - lib/ffi/libfuse/fuse_buf.rb
170
+ - lib/ffi/libfuse/fuse_buf_vec.rb
169
171
  - lib/ffi/libfuse/fuse_callbacks.rb
170
172
  - lib/ffi/libfuse/fuse_cmdline_opts.rb
171
173
  - lib/ffi/libfuse/fuse_common.rb
@@ -180,6 +182,7 @@ files:
180
182
  - lib/ffi/libfuse/fuse_version.rb
181
183
  - lib/ffi/libfuse/gem_helper.rb
182
184
  - lib/ffi/libfuse/gem_version.rb
185
+ - lib/ffi/libfuse/io.rb
183
186
  - lib/ffi/libfuse/job_pool.rb
184
187
  - lib/ffi/libfuse/main.rb
185
188
  - lib/ffi/libfuse/test_helper.rb
@@ -1,257 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'fuse_version'
4
-
5
- module FFI
6
- # Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
7
- module Libfuse
8
- bitmask :fuse_buf_flags, [:is_fd, 1, :fd_seek, :fd_retry]
9
- bitmask :fuse_buf_copy_flags, [:no_splice, 1, :force_splice, 2, :splice_move, 4, :splice_nonblock, 8]
10
-
11
- #
12
- # Single data buffer
13
- #
14
- # Generic data buffer for I/O, extended attributes, etc...Data may be supplied as a memory pointer or as a file
15
- # descriptor
16
- #
17
- class FuseBuf < FFI::Struct
18
- layout(
19
- size: :size_t, # Size of data in bytes
20
- flags: :fuse_buf_flags, # Buffer flags
21
- mem: :pointer, # Memory pointer - used if :is_fd flag is not set
22
- fd: :int, # File descriptor - used if :is_fd is set
23
- pos: :off_t # File position - used if :fd_seek flag is set.
24
- )
25
-
26
- # rubocop:disable Naming/MethodParameterName
27
-
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
71
- #
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.
74
- #
75
- # @param [Integer] pos
76
- # If > 0 then used to seek to the given offset before performing operation on file descriptor.
77
- # @return [self]
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
-
82
- self[:size] = size
83
- self[:mem] = mem
84
- self[:fd] = fd
85
- flags = []
86
- flags << :is_fd if fd != -1
87
- flags << :fd_seek if pos.positive?
88
- flags << :fd_retry if fd_retry
89
- self[:flags] = flags
90
- self[:pos] = pos
91
- self
92
- end
93
- end
94
- # rubocop:enable Naming/MethodParameterName
95
-
96
- #
97
- # Data buffer vector
98
- #
99
- # An array of data buffers, each containing a memory pointer or a file descriptor.
100
- #
101
- # Allocate dynamically to add more than one buffer.
102
- #
103
- class FuseBufVec < FFI::Struct
104
- layout(
105
- count: :size_t,
106
- idx: :size_t,
107
- off: :size_t,
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]
110
- )
111
-
112
- # @!attribute [r] count
113
- # @return [Integer] the number of buffers in the array
114
- def count
115
- self[:count]
116
- end
117
-
118
- # @!attribute [r] index
119
- # @return [Integer] index of current buffer within the array
120
- def index
121
- self[:idx]
122
- end
123
- alias idx index
124
-
125
- # @!attribute [r] offset
126
- # @return [Integer] current offset within the current buffer
127
- def offset
128
- self[:off]
129
- end
130
- alias off offset
131
-
132
- # @!attribute [r] buffers
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
149
-
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
167
- end
168
-
169
- # Set and initialise a specific buffer
170
- #
171
- # See fuse_common.h FUSE_BUFVEC_INIT macro
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
177
- self[:off] = 0
178
- current.fill(**buf_options) unless buf_options.empty?
179
- self
180
- end
181
-
182
- # Would pref this to be called #size but clashes with FFI::Struct, might need StructWrapper
183
- # @return [Integer] total size of data in a fuse buffer vector
184
- def buf_size
185
- Libfuse.fuse_buf_size(self)
186
- end
187
-
188
- # Copy data from one buffer vector to another
189
- # @param [FuseBufVec] dst Destination buffer vector
190
- # @param [Array<Symbol>] flags Buffer copy flags
191
- # - :no_splice
192
- # Don't use splice(2)
193
- #
194
- # Always fall back to using read and write instead of splice(2) to copy data from one file descriptor to
195
- # another.
196
- #
197
- # If this flag is not set, then only fall back if splice is unavailable.
198
- #
199
- # - :force_splice
200
- #
201
- # Always use splice(2) to copy data from one file descriptor to another. If splice is not available, return
202
- # -EINVAL.
203
- #
204
- # - :splice_move
205
- #
206
- # Try to move data with splice.
207
- #
208
- # If splice is used, try to move pages from the source to the destination instead of copying. See
209
- # documentation of SPLICE_F_MOVE in splice(2) man page.
210
- #
211
- # - :splice_nonblock
212
- #
213
- # Don't block on the pipe when copying data with splice
214
- #
215
- # Makes the operations on the pipe non-blocking (if the pipe is full or empty). See SPLICE_F_NONBLOCK in
216
- # the splice(2) man page.
217
- #
218
- # @return [Integer] actual number of bytes copied or -errno on error
219
- #
220
- def copy_to(dst, *flags)
221
- Libfuse.fuse_buf_copy(dst, self, flags)
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
246
- end
247
-
248
- attach_function :fuse_buf_size, [FuseBufVec.by_ref], :size_t
249
- attach_function :fuse_buf_copy, [FuseBufVec.by_ref, FuseBufVec.by_ref, :fuse_buf_copy_flags], :ssize_t
250
-
251
- class << self
252
- # @!visibility private
253
- # @!method fuse_buf_size
254
- # @!method fuse_buf_copy
255
- end
256
- end
257
- end