ffi-libfuse 0.3.4 → 0.4.1

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