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