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
@@ -3,13 +3,17 @@
3
3
  require_relative 'fuse_version'
4
4
  require_relative 'fuse_opt'
5
5
  require_relative '../ruby_object'
6
+ require_relative '../boolean_int'
7
+ require_relative '../accessors'
6
8
 
7
9
  module FFI
8
10
  # Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
9
11
  module Libfuse
10
12
  # struct fuse_args
11
13
  class FuseArgs < FFI::Struct
12
- layout :argc, :int, :argv, :pointer, :allocated, :int
14
+ include FFI::Accessors
15
+
16
+ layout :argc, :int, :argv, :pointer, :allocated, :bool_int
13
17
 
14
18
  # Create a fuse_args struct from command line options
15
19
  # @param [Array<String>] argv command line args
@@ -34,33 +38,17 @@ module FFI
34
38
  @arg_vector[argv.size].put_pointer(0, FFI::Pointer::NULL)
35
39
  self[:argv] = @arg_vector
36
40
  self[:argc] = argv.size
37
- self[:allocated] = 0
41
+ self[:allocated] = false # ie libfuse did not allocate
38
42
  self
39
43
  end
40
44
 
41
- # @!attribute [r] argc
42
- # @return [Integer] count of args
43
- def argc
44
- self[:argc]
45
- end
46
-
47
45
  # @!attribute [r] argv
48
46
  # @return [Array<String>] list of args
49
- def argv
47
+ ffi_attr_reader(:argv) do
50
48
  # noinspection RubyResolve
51
49
  self[:argv].get_array_of_pointer(0, argc).map(&:read_string)
52
50
  end
53
51
 
54
- # @!visibility private
55
- def allocated
56
- self[:allocated]
57
- end
58
-
59
- # @!visibility private
60
- def inspect
61
- "#{self.class.name} - #{%i[argc argv allocated].to_h { |m| [m, send(m)] }}"
62
- end
63
-
64
52
  # Add an arg to this arg list
65
53
  # @param [String] arg
66
54
  def add(arg)
@@ -121,7 +109,8 @@ module FFI
121
109
  # - :error an error, alternatively raise {Error}
122
110
  # - :keep retain the current argument for further processing
123
111
  # - :handled,:discard remove the current argument from further processing
124
- # @return [nil|self] nil on error otherwise self
112
+ # @raise Error if an error is raised during parsing
113
+ # @return [self]
125
114
  def parse!(opts, data = nil, ignore: %i[non_option unmatched], &block)
126
115
  ignore ||= []
127
116
 
@@ -140,7 +129,9 @@ module FFI
140
129
  end
141
130
 
142
131
  fop = fuse_opt_proc(symbols, bool_opts, param_opts, ignore, &block)
143
- Libfuse.fuse_opt_parse(self, data, int_opts, fop).zero? ? self : nil
132
+ raise Error unless Libfuse.fuse_opt_parse(self, data, int_opts, fop).zero?
133
+
134
+ self
144
135
  end
145
136
 
146
137
  private
@@ -148,6 +139,8 @@ module FFI
148
139
  # Valid return values from parse! block
149
140
  FUSE_OPT_PROC_RETURN = { error: -1, keep: 1, handled: 0, discard: 0 }.freeze
150
141
 
142
+ ffi_attr_reader(:argc, :allocated?)
143
+
151
144
  def fuse_opt_proc(symbols, bool_opts, param_opts, ignore, &block)
152
145
  proc do |data, arg, key, out|
153
146
  key = symbols[key]
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'fuse_version'
4
+ require_relative '../struct_wrapper'
5
+
6
+ module FFI
7
+ # Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
8
+ module Libfuse
9
+ bitmask :fuse_buf_flags, [:is_fd, 1, :fd_seek, :fd_retry]
10
+
11
+ #
12
+ # Single io buffer
13
+ #
14
+ # Generic io 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
18
+ # @!visibility private
19
+ # Native FuseBuf layout
20
+ class Native < FFI::Struct
21
+ layout(
22
+ size: :size_t, # Size of io in bytes
23
+ flags: FFI::Libfuse.find_type(:fuse_buf_flags), # Buffer flags
24
+ mem: :pointer, # Memory pointer - used if :is_fd flag is not set
25
+ fd: :int, # File descriptor - used if :is_fd is set
26
+ pos: :off_t # File position - used if :fd_seek flag is set.
27
+ )
28
+ end
29
+
30
+ include StructWrapper
31
+ native_struct(Native)
32
+
33
+ ffi_attr_reader(:mem, :fd, :pos)
34
+ ffi_attr_accessor(:size)
35
+
36
+ # @!attribute [r] mem
37
+ # @return [FFI::Pointer] the memory in the buffer
38
+
39
+ alias memory mem
40
+
41
+ # @!attribute [r] fd
42
+ # @return [Integer] the file descriptor number
43
+
44
+ alias file_descriptor fd
45
+
46
+ # @attribute [rw] size
47
+ # @return [Integer] the size of the buffer
48
+
49
+ # @return [Boolean] true if this a memory buffer
50
+ def mem?
51
+ !fd?
52
+ end
53
+
54
+ # @return [Boolean] true if this is a file descriptor buffer
55
+ def fd?
56
+ self[:flags].include?(:is_fd)
57
+ end
58
+ alias file_descriptor? fd?
59
+
60
+ # @overload fill(str:)
61
+ # Create a memory buffer from str
62
+ # @param [String,#to_s] str
63
+ #
64
+ # @overload fill(size:)
65
+ # Allocate an empty memory buffer of size bytes
66
+ # @param [Integer] size
67
+ #
68
+ # @overload fill(mem:, size: mem.size)
69
+ # Set the buffer to contain the previously allocated memory
70
+ # @param [FFI::Pointer] mem
71
+ # @param [Integer] size <= mem.size
72
+ #
73
+ # @overload fill(fd:, fd_retry: false, size:, pos: 0)
74
+ # Fill as a FileDescriptor buffer
75
+ # @param [Integer] fd File descriptor
76
+ # @param [Boolean] fd_retry
77
+ # Retry operations on file descriptor
78
+ #
79
+ # If this flag is set then retry operation on file descriptor until size bytes have been copied or an error
80
+ # or EOF is detected.
81
+ #
82
+ # @param [Integer] size
83
+ # number of bytes to read from fd
84
+ # @param [nil, Integer] pos
85
+ # If set then used to seek to the given offset before performing operation on file descriptor.
86
+ # @return [self]
87
+ def fill(
88
+ str: nil,
89
+ mem: str ? FFI::MemoryPointer.from_string(str.to_s) : FFI::Pointer::NULL, size: mem.null? ? 0 : mem.size,
90
+ fd: -1, fd_retry: false, pos: nil # rubocop:disable Naming/MethodParameterName
91
+
92
+ )
93
+
94
+ # Allocate size bytes if we've been given a null pointer
95
+ mem = FFI::MemoryPointer.new(:char, size, true) if fd == -1 && mem.null? && size.positive?
96
+
97
+ mem.autorelease = to_ptr.autorelease? unless mem.null?
98
+
99
+ self[:size] = size
100
+ self[:mem] = mem
101
+ self[:fd] = fd
102
+ flags = []
103
+ flags << :is_fd if fd != -1
104
+ flags << :fd_seek if pos
105
+ flags << :fd_retry if fd_retry
106
+ self[:flags] = flags
107
+ self[:pos] = pos || 0
108
+ self
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,228 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'fuse_version'
4
+ require_relative 'io'
5
+ require_relative 'fuse_buf'
6
+ require_relative '../accessors'
7
+
8
+ module FFI
9
+ # Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
10
+ module Libfuse
11
+ #
12
+ # Data buffer vector
13
+ #
14
+ # An list of io buffers, each containing a memory pointer or a file descriptor.
15
+ #
16
+ class FuseBufVec < FFI::Struct
17
+ include Accessors
18
+
19
+ layout(
20
+ count: :size_t,
21
+ idx: :size_t,
22
+ off: :size_t,
23
+ # buf is treated as a variable length array of FuseBuf at size +1 following the struct.
24
+ buf: [FuseBuf::Native, 1] # struct fuse_buf[1]
25
+ )
26
+
27
+ ffi_attr_reader(:count, :idx, :off)
28
+
29
+ # @!attribute [r] count
30
+ # @return [Integer] the number of buffers in the array
31
+
32
+ # @!attribute [r] idx
33
+ # @return [Integer] index of current buffer within the array
34
+
35
+ alias index idx
36
+
37
+ # @!attribute [r] off
38
+ # @return [Integer] current offset within the current buffer
39
+
40
+ alias offset off
41
+
42
+ # Create and initialise from a ruby object that quacks like {::File}, {::IO}, or {::String}
43
+ #
44
+ # @param [Object] io
45
+ #
46
+ # * Integer file descriptor or File like object that returns one via :fileno
47
+ # * Otherwise something to pass to {IO.read}(io, size, offset) to create a memory based buffer
48
+ #
49
+ # @param [Integer] size
50
+ # @param [Integer] offset
51
+ # @return [FuseBufVec]
52
+ #
53
+ # @note The returned object's memory is not auto-released, and thus suitable for use with
54
+ # {FuseOperations#read_buf} where the buffers are cleared by libfuse library..
55
+ def self.create(io, size, offset = nil)
56
+ fd = io.respond_to?(:fileno) ? io.fileno : io
57
+ return init(autorelease: false, size: size, fd: fd, pos: offset || 0) if fd.is_a?(Integer)
58
+
59
+ init(autorelease: false, str: Libfuse::IO.read(io, size, offset))
60
+ end
61
+
62
+ # Create and initialise a new FuseBufVec
63
+ #
64
+ # @param [Boolean] autorelease should the struct be freed on GC
65
+ #
66
+ # Use false only if this object is going to be passed to the C library side. eg. {FuseOperations#read_buf}
67
+ # @param [Hash<Symbol>] buf_options options for configuring the initial buffer (see {FuseBuf#fill})
68
+ # @return [FuseBufVec]
69
+ def self.init(autorelease: true, count: 1, **buf_options)
70
+ bufvec_ptr = FFI::MemoryPointer.new(:uchar, FuseBufVec.size + (FuseBuf::Native.size * (count - 1)), true)
71
+ bufvec_ptr.autorelease = autorelease
72
+ bufvec = new(bufvec_ptr)
73
+ bufvec[:count] = count
74
+ bufvec[:idx] = 0
75
+ bufvec[:off] = 0
76
+ bufvec.buffers[0].fill(**buf_options) unless buf_options.empty?
77
+ bufvec
78
+ end
79
+
80
+ # @return [Integer] total size of io in a fuse buffer vector (ie the size of all the fuse buffers)
81
+ def buf_size
82
+ Libfuse.fuse_buf_size(self)
83
+ end
84
+
85
+ # Set {index}/{offset} for reading/writing from pos
86
+ # @param [Integer] pos
87
+ # @return [self]
88
+ # @raise [Errno::ERANGE] if seek past end of file
89
+ def seek(pos)
90
+ buffers.each_with_index do |b, i|
91
+ if pos < b.size
92
+ self[:idx] = i
93
+ self[:off] = pos
94
+ return self
95
+ else
96
+ pos -= b.size
97
+ end
98
+ end
99
+ raise Errno::ERANGE
100
+ end
101
+
102
+ # Copy data from this set of buffers to another set
103
+ #
104
+ # @param [FuseBufVec] dst destination buffers
105
+ # @param [Array<Symbol>] flags Buffer copy flags
106
+ #
107
+ # - :no_splice
108
+ # Don't use splice(2)
109
+ #
110
+ # Always fall back to using read and write instead of splice(2) to copy io from one file descriptor to
111
+ # another.
112
+ #
113
+ # If this flag is not set, then only fall back if splice is unavailable.
114
+ #
115
+ # - :force_splice
116
+ #
117
+ # Always use splice(2) to copy io from one file descriptor to another. If splice is not available, return
118
+ # -EINVAL.
119
+ #
120
+ # - :splice_move
121
+ #
122
+ # Try to move io with splice.
123
+ #
124
+ # If splice is used, try to move pages from the source to the destination instead of copying. See
125
+ # documentation of SPLICE_F_MOVE in splice(2) man page.
126
+ #
127
+ # - :splice_nonblock
128
+ #
129
+ # Don't block on the pipe when copying io with splice
130
+ #
131
+ # Makes the operations on the pipe non-blocking (if the pipe is full or empty). See SPLICE_F_NONBLOCK in
132
+ # the splice(2) man page.
133
+ #
134
+ # @return [Integer] actual number of bytes copied or -errno on error
135
+ #
136
+ def copy_to(dst, *flags)
137
+ Libfuse.fuse_buf_copy(dst, self, flags)
138
+ end
139
+
140
+ # Copy direct to file descriptor
141
+ # @param [Integer] fileno a file descriptor
142
+ # @param [nil, Integer] offset if non nil will first seek to offset
143
+ # @param [Array<Symbol>] flags see {copy_to}
144
+ # @return [Integer] number of bytes copied
145
+ def copy_to_fd(fileno, offset = nil, *flags)
146
+ dst = self.class.init(size: buf_size, fd: fileno, pos: offset)
147
+ copy_to(dst, *flags)
148
+ end
149
+
150
+ # Copy to string via a temporary buffer
151
+ # @param [Array<Symbol>] flags see {copy_to}
152
+ # @return [String] the extracted data
153
+ def copy_to_str(*flags)
154
+ dst = self.class.init(size: buf_size)
155
+ copied = copy_to(dst, *flags)
156
+ dst.buffers.first.memory.read_string(copied)
157
+ end
158
+
159
+ # Copy from another set of buffers to this one
160
+ # @param [FuseBufVec] src source buffers
161
+ # @param [Array<Symbol>] flags
162
+ # @return [Integer] number of bytes written
163
+ # @see copy_to
164
+ def copy_from(src, *flags)
165
+ Libfuse.fuse_buf_copy(self, src, flags)
166
+ end
167
+
168
+ # Store ourself into a pointer location as received by {FuseOperations#read_buf}
169
+ # @param [FFI::Pointer<FuseBufVec>] bufp
170
+ # @return [void]
171
+ def store_to(bufp)
172
+ bufp.write_pointer(to_ptr)
173
+ end
174
+
175
+ # Write data from these buffers to another object
176
+ #
177
+ # @overload copy_to_io(io, *flags)
178
+ # @overload copy_to_io(io, offset = nil, *flags)
179
+ # @param [Object] io one of
180
+ #
181
+ # * another {FuseBufVec} via io.{seek}(offset) and {copy_to}(io, *flags)
182
+ # * an {::Integer} file descriptor to write via {copy_to_fd}(io, offset, *flags)
183
+ # * a {::File} like object that returns a file descriptor via :fileno used as above
184
+ # * an {::IO} like object that accepts a string data as {IO.write}(io, {copy_to_str}(*flags), offset)
185
+ #
186
+ # @param [nil, Integer] offset position in io to begin writing at, or nil if io is already positioned
187
+ # @param [Array<Symbol>] flags see {copy_to}
188
+ #
189
+ # @return [Integer] number of bytes written
190
+ # @raise [Errno::EBADF] if io is not a valid target
191
+ def copy_to_io(io, offset = nil, *flags)
192
+ if offset.is_a?(Symbol)
193
+ flags.unshift(offset)
194
+ offset = nil
195
+ end
196
+
197
+ if io.is_a?(FuseBufVec)
198
+ io.seek(offset) if offset
199
+ return copy_to(io, *flags)
200
+ end
201
+
202
+ fd = (io.respond_to?(:fileno) ? io.fileno : io)
203
+ return copy_to_fd(fd, offset || 0, *flags) if fd.is_a?(Integer)
204
+
205
+ Libfuse::IO.write(io, copy_to_str(*flags), offset)
206
+ end
207
+
208
+ # @return [Array<FuseBuf>] list of buffers
209
+ def buffers
210
+ @buffers ||= Array.new(count) do |i|
211
+ native = i.zero? ? self[:buf].first : FuseBuf::Native.new(self[:buf].to_ptr + (i * FuseBuf::Native.size))
212
+ FuseBuf.new(native)
213
+ end.freeze
214
+ end
215
+ end
216
+
217
+ bitmask :fuse_buf_copy_flags, [:no_splice, 1, :force_splice, 2, :splice_move, 4, :splice_nonblock, 8]
218
+ attach_function :fuse_buf_copy, [FuseBufVec.by_ref, FuseBufVec.by_ref, :fuse_buf_copy_flags], :ssize_t
219
+ attach_function :fuse_buf_size, [FuseBufVec.by_ref], :size_t
220
+
221
+ class << self
222
+ # @!visibility private
223
+ # @!method fuse_buf_size
224
+ # @!method fuse_buf_copy
225
+ # @!method fuse_buf_size
226
+ end
227
+ end
228
+ end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../accessors'
4
+ require_relative '../boolean_int'
4
5
  require_relative 'fuse_loop_config'
5
6
  module FFI
6
7
  module Libfuse
7
- #
8
8
  # struct fuse_cmdline_opts {
9
9
  # int singlethread;
10
10
  # int foreground;
@@ -16,29 +16,32 @@ module FFI
16
16
  # int clone_fd;
17
17
  # unsigned int max_idle_threads;
18
18
  # };
19
+
20
+ # Command line options
19
21
  # @!visibility private
20
22
  class FuseCmdlineOpts < FFI::Struct
21
23
  include(FFI::Accessors)
22
24
 
23
- layout(
24
- single_thread: :int,
25
- foreground: :int,
26
- debug: :int,
27
- nodefault_subtype: :int,
25
+ spec = {
26
+ single_thread: :bool_int,
27
+ foreground: :bool_int,
28
+ debug: :bool_int,
29
+ nodefault_subtype: :bool_int,
28
30
  mountpoint: :string,
29
- show_version: :int,
30
- show_help: :int,
31
- clone_fd: :int,
32
- max_idle_threads: :int
33
- )
31
+ show_version: :bool_int,
32
+ show_help: :bool_int,
33
+ clone_fd: :bool_int,
34
+ max_idle_threads: :uint
35
+ }
36
+ spec[:max_threads] = :uint if FUSE_MINOR_VERSION >= 12
37
+
38
+ layout(spec)
34
39
 
35
- # int to booleans
36
- ffi_attr_reader(:single_thread, :foreground, :debug, :nodefault_subtype, :show_version, :show_help,
37
- :clone_fd) do |v|
38
- v != 0
39
- end
40
+ bool, = spec.partition { |_, v| v == :bool_int }
41
+ ffi_attr_reader(*bool.map { |k, _| "#{k}?" })
40
42
 
41
43
  ffi_attr_reader(:max_idle_threads, :mountpoint)
44
+ ffi_attr_reader(:max_threads) if FUSE_MINOR_VERSION >= 12
42
45
  end
43
46
  end
44
47
  end
@@ -52,11 +52,13 @@ module FFI
52
52
  teardown
53
53
  end
54
54
 
55
- # @api private
56
55
  # @param [Boolean] foreground
57
56
  # @param [Boolean] single_thread
58
- # @param [Hash<String,Proc>] traps see {Ackbar.trap}
59
- #
57
+ # @param [Hash<String,Proc|nil>] traps as per Signal.trap
58
+ # these are merged over {default_traps} for INT, HUP, TERM that unmount and exit filesystem. A nil
59
+ # value for these default signals will leave any existing signal handle in place.
60
+ # @param [Integer] remember fuse cache timeout
61
+ # @api private
60
62
  # Implement fuse loop in ruby
61
63
  #
62
64
  # Pros:
@@ -71,6 +73,7 @@ module FFI
71
73
  # * clone_fd is ignored
72
74
  # * filesystem interrupts probably can't work
73
75
  def run_ruby(foreground: true, single_thread: true, traps: {}, remember: false, **options)
76
+ traps = default_traps.merge(traps).keep_if { |_, v| v }
74
77
  Ackbar.trap(default_traps.merge(traps)) do |signals|
75
78
  daemonize unless foreground
76
79
 
@@ -112,7 +115,9 @@ module FFI
112
115
  end
113
116
 
114
117
  # Ruby implementation of fuse default traps
115
- # @see Ackbar
118
+ #
119
+ # * INT, HUP, TERM, TSTP to unmount and exit filesystem
120
+ # * PIPE is ignored
116
121
  def default_traps
117
122
  exproc = ->(signame) { exit(signame) }
118
123
  @default_traps ||= { INT: exproc, HUP: exproc, TERM: exproc, TSTP: exproc, PIPE: 'IGNORE' }
@@ -190,6 +195,7 @@ module FFI
190
195
  fuse_process || (sleep(0.1) && false)
191
196
  end
192
197
 
198
+ # @!visibility private
193
199
  def teardown
194
200
  return unless @fuse
195
201
 
@@ -10,10 +10,12 @@ module FFI
10
10
  # This structure is initialized from the arguments passed to fuse_new(), and then passed to the file system's init()
11
11
  # handler which should ensure that the configuration is compatible with the file system implementation.
12
12
  #
13
+ # Some options can only be set via the filesystem init method (:use_ino etc..) because the filesystem either
14
+ # supports them or it doesn't.
13
15
  class FuseConfig < FFI::Struct
14
16
  include FFI::Accessors
15
17
 
16
- layout(
18
+ spec =
17
19
  {
18
20
  # @!attribute [r] gid
19
21
  # @return [Integer|nil] if set, this value will be used for the :gid attribute of each file
@@ -38,7 +40,7 @@ module FFI
38
40
  # @!attribute [rw] negative_timeout
39
41
  # The timeout in seconds for which a negative lookup will be cached.
40
42
  #
41
- # This means, that if file did not exist (lookup retuned ENOENT), the lookup will only be redone after the
43
+ # This means, that if file did not exist (lookup returned ENOENT), the lookup will only be redone after the
42
44
  # timeout, and the file/directory will be assumed to not exist until then. A value of zero means that
43
45
  # negative lookups are not cached.
44
46
  #
@@ -53,7 +55,7 @@ module FFI
53
55
  # @return [Float]
54
56
  attr_timeout: :double,
55
57
 
56
- # @!attribute [rw] intr
58
+ # @!attribute [rw] intr?
57
59
  # Allow requests to be interrupted
58
60
  # @return [Boolean]
59
61
  intr: :bool_int,
@@ -78,7 +80,7 @@ module FFI
78
80
  # @return [Integer]
79
81
  remember: :int,
80
82
 
81
- # @!attribute [rw] hard_remove
83
+ # @!attribute [rw] hard_remove?
82
84
  # should open files be removed immediately
83
85
  #
84
86
  # The default behavior is that if an open file is deleted, the file is renamed to a hidden file
@@ -93,7 +95,7 @@ module FFI
93
95
  # @return [Boolean]
94
96
  hard_remove: :bool_int,
95
97
 
96
- # @!attribute [rw] use_ino
98
+ # @!attribute [rw] use_ino?
97
99
  # use filesystem provided inode values
98
100
  #
99
101
  # Honor the st_ino field in the functions getattr() and fill_dir(). This value is used to fill in the st_ino
@@ -107,8 +109,8 @@ module FFI
107
109
  # @return [Boolean]
108
110
  use_ino: :bool_int,
109
111
 
110
- # @!attribute [rw] readdir_ino
111
- # generate inodes for readdir even if {#use_ino} is set
112
+ # @!attribute [rw] readdir_ino?
113
+ # generate inodes for readdir even if {#use_ino?} is set
112
114
  #
113
115
  # If use_ino option is not given, still try to fill in the d_ino field in readdir(2). If the name was
114
116
  # previously looked up, and is still in the cache, the inode number found there will be used. Otherwise it
@@ -116,7 +118,7 @@ module FFI
116
118
  # @return [Boolean]
117
119
  readdir_ino: :bool_int,
118
120
 
119
- # @!attribute [rw] direct_io
121
+ # @!attribute [rw] direct_io?
120
122
  # disables the use of kernel page cache (file content cache) in the kernel for this filesystem.
121
123
  #
122
124
  # This has several affects:
@@ -133,13 +135,13 @@ module FFI
133
135
  # @return [Boolean]
134
136
  direct_io: :bool_int,
135
137
 
136
- # @!attribute [rw] kernel_cache
138
+ # @!attribute [rw] kernel_cache?
137
139
  # disables flushing the cache of the file contents on every open(2).
138
140
  #
139
141
  # This should only be enabled on filesystem where the file data is never changed externally (not through the
140
142
  # mounted FUSE filesystem). Thus it is not suitable for network filesystem and other intermediate filesystem.
141
143
  #
142
- # **Note**: if neither this option or {#direct_io} is specified data is still cached after the open(2),
144
+ # **Note**: if neither this option or {#direct_io?} is specified data is still cached after the open(2),
143
145
  # so a read(2) system call will not always initiate a read operation.
144
146
  #
145
147
  # Internally, enabling this option causes fuse to set {FuseFileInfo#keep_cache} overwriting any value that was
@@ -147,7 +149,7 @@ module FFI
147
149
  # @return [Boolean]
148
150
  kernel_cache: :bool_int,
149
151
 
150
- # @!attribute [rw] auto_cache
152
+ # @!attribute [rw] auto_cache?
151
153
  # invalidate cached data on open based on changes in file attributes
152
154
  #
153
155
  # This option is an alternative to `kernel_cache`. Instead of unconditionally keeping cached data, the cached
@@ -158,12 +160,12 @@ module FFI
158
160
 
159
161
  # @!attribute [rw] ac_attr_timeout
160
162
  # if set the timeout in seconds for which file attributes are cached for the purpose of checking if
161
- # auto_cache should flush the file data on open.
163
+ # auto_cache should flush the file data on open.
162
164
  # @return [Float|nil]
163
165
  ac_attr_timeout_set: :bool_int,
164
166
  ac_attr_timeout: :double,
165
167
 
166
- # @!attribute [rw] nullpath_ok
168
+ # @!attribute [rw] nullpath_ok?
167
169
  # operations on open files and directories are ok to receive nil paths
168
170
  #
169
171
  # If this option is given the file-system handlers for the following operations will not receive path
@@ -179,18 +181,28 @@ module FFI
179
181
  modules: :pointer,
180
182
  debug: :bool_int
181
183
  }
182
- )
183
-
184
- setters = { gid: :set_gid, uid: :set_uid, umask: :set_mode, ac_attr_timeout: :ac_attr_timeout_set }
185
- setters.each do |(attr, setter)|
186
- ffi_attr_reader(attr)
187
- ffi_attr_writer(attr) do |val|
188
- self[setter] = !val.nil?
189
- val || 0
190
- end
184
+
185
+ layout(spec)
186
+
187
+ # Find the attrs that have a corresponding setter (prefix set_ or suffix _set
188
+ # map attr => setter
189
+ setters = spec.keys
190
+ .map { |k| [k.to_s.sub(/^set_/, '').sub(/_set$/, '').to_sym, k] }
191
+ .reject { |(s, a)| s == a }
192
+ .to_h
193
+
194
+ ffi_attr_reader_method(*setters.keys) do
195
+ self[setters[__method__]] ? self[__method__] : nil
196
+ end
197
+
198
+ ffi_attr_writer_method(*setters.keys) do |val|
199
+ self[setters[__method__]] = !val.nil?
200
+ self[__method__] = val || 0
191
201
  end
192
202
 
193
- ffi_attr_accessor(*(members - (setters.keys + setters.values)))
203
+ ffi_attr_reader(:show_help?, :debug?)
204
+ remaining = (spec.keys - setters.keys - setters.values - %i[show_help modules debug])
205
+ ffi_attr_accessor(*remaining.map { |a| spec[a] == :bool_int ? "#{a}?" : a })
194
206
  end
195
207
  end
196
208
  end
@@ -326,7 +326,7 @@ module FFI
326
326
  # or unwanted
327
327
  # @return [Array<Symbol>]
328
328
  # @see capable
329
- ffi_attr_reader(:want, simple: false) do |*caps, **h|
329
+ ffi_attr_reader_method(:want) do |*caps, **h|
330
330
  next self[:want] if caps.empty? && h.empty?
331
331
 
332
332
  h.merge!(caps.pop) if caps.last.is_a?(Hash)
@@ -15,7 +15,8 @@ module FFI
15
15
  base[:umask] = :mode_t if FUSE_VERSION >= 28
16
16
  layout base
17
17
 
18
- ffi_attr_reader(*members, simple: false) do
18
+ # Define readers, safe from null access
19
+ ffi_attr_reader_method(*members) do
19
20
  m = __method__
20
21
 
21
22
  # Use overrides if they are available, or the default context if the underlying memory is invalid