ffi-libfuse 0.0.1.pre

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 (53) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +1 -0
  3. data/README.md +100 -0
  4. data/lib/ffi/accessors.rb +145 -0
  5. data/lib/ffi/devt.rb +30 -0
  6. data/lib/ffi/flock.rb +47 -0
  7. data/lib/ffi/gnu_extensions.rb +115 -0
  8. data/lib/ffi/libfuse/ackbar.rb +112 -0
  9. data/lib/ffi/libfuse/adapter/context.rb +37 -0
  10. data/lib/ffi/libfuse/adapter/debug.rb +89 -0
  11. data/lib/ffi/libfuse/adapter/fuse2_compat.rb +91 -0
  12. data/lib/ffi/libfuse/adapter/fuse3_support.rb +87 -0
  13. data/lib/ffi/libfuse/adapter/interrupt.rb +37 -0
  14. data/lib/ffi/libfuse/adapter/pathname.rb +23 -0
  15. data/lib/ffi/libfuse/adapter/ruby.rb +334 -0
  16. data/lib/ffi/libfuse/adapter/safe.rb +58 -0
  17. data/lib/ffi/libfuse/adapter/thread_local_context.rb +36 -0
  18. data/lib/ffi/libfuse/adapter.rb +79 -0
  19. data/lib/ffi/libfuse/callbacks.rb +61 -0
  20. data/lib/ffi/libfuse/fuse2.rb +159 -0
  21. data/lib/ffi/libfuse/fuse3.rb +162 -0
  22. data/lib/ffi/libfuse/fuse_args.rb +166 -0
  23. data/lib/ffi/libfuse/fuse_buffer.rb +155 -0
  24. data/lib/ffi/libfuse/fuse_callbacks.rb +48 -0
  25. data/lib/ffi/libfuse/fuse_cmdline_opts.rb +44 -0
  26. data/lib/ffi/libfuse/fuse_common.rb +249 -0
  27. data/lib/ffi/libfuse/fuse_config.rb +205 -0
  28. data/lib/ffi/libfuse/fuse_conn_info.rb +211 -0
  29. data/lib/ffi/libfuse/fuse_context.rb +79 -0
  30. data/lib/ffi/libfuse/fuse_file_info.rb +100 -0
  31. data/lib/ffi/libfuse/fuse_loop_config.rb +43 -0
  32. data/lib/ffi/libfuse/fuse_operations.rb +870 -0
  33. data/lib/ffi/libfuse/fuse_opt.rb +54 -0
  34. data/lib/ffi/libfuse/fuse_poll_handle.rb +59 -0
  35. data/lib/ffi/libfuse/fuse_version.rb +43 -0
  36. data/lib/ffi/libfuse/job_pool.rb +53 -0
  37. data/lib/ffi/libfuse/main.rb +200 -0
  38. data/lib/ffi/libfuse/test/operations.rb +56 -0
  39. data/lib/ffi/libfuse/test.rb +3 -0
  40. data/lib/ffi/libfuse/thread_pool.rb +147 -0
  41. data/lib/ffi/libfuse/version.rb +8 -0
  42. data/lib/ffi/libfuse.rb +24 -0
  43. data/lib/ffi/ruby_object.rb +95 -0
  44. data/lib/ffi/stat/constants.rb +29 -0
  45. data/lib/ffi/stat/native.rb +50 -0
  46. data/lib/ffi/stat/time_spec.rb +137 -0
  47. data/lib/ffi/stat.rb +96 -0
  48. data/lib/ffi/stat_vfs.rb +81 -0
  49. data/lib/ffi/struct_array.rb +39 -0
  50. data/lib/ffi/struct_wrapper.rb +100 -0
  51. data/sample/memory_fs.rb +189 -0
  52. data/sample/no_fs.rb +69 -0
  53. metadata +165 -0
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+
5
+ module FFI
6
+ module Libfuse
7
+ module Adapter
8
+ # Wrapper module to convert first path argument of callback methods to a {::Pathname}
9
+ module Pathname
10
+ # @!visibility private
11
+ def fuse_wrappers(*wrappers)
12
+ wrappers << {
13
+ wrapper: proc { |_fuse_method, path, *args, &b| b.call(::Pathname.new(path), *args) },
14
+ excludes: %i[init destroy]
15
+ }
16
+ return wrappers unless defined?(super)
17
+
18
+ super(*wrappers)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,334 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'safe'
4
+ require_relative 'debug'
5
+
6
+ module FFI
7
+ module Libfuse
8
+ module Adapter
9
+ # Wrapper module to give more natural ruby signatures to the fuse callbacks, in particular to avoid dealing with
10
+ # FFI Pointers
11
+ #
12
+ # @note includes {Debug} and {Safe}
13
+ module Ruby
14
+ # adapter module prepended to the including module
15
+ # @!visibility private
16
+ # rubocop:disable Metrics/ModuleLength
17
+ module Shim
18
+ include Adapter
19
+
20
+ # Fuse3 support outer module can override this
21
+ def fuse3_compat?
22
+ FUSE_MAJOR_VERSION >= 3
23
+ end
24
+
25
+ def write(*args)
26
+ *pre, path, buf, size, offset, info = args
27
+ super(*pre, path, buf.read_bytes(size), offset, info)
28
+ size
29
+ end
30
+
31
+ def read(*args)
32
+ *pre, path, buf, size, offset, info = args
33
+ res = super(*pre, path, size, offset, info)
34
+
35
+ return -Errno::ERANGE::Errno unless res.size <= size
36
+
37
+ buf.write_bytes(res)
38
+ res.size
39
+ end
40
+
41
+ def readlink(*args)
42
+ *pre, path, buf, size = args
43
+ link = super(*pre, path)
44
+ buf.write_bytes(link, [0..size])
45
+ 0
46
+ end
47
+
48
+ # rubocop:disable Metrics/AbcSize
49
+
50
+ # changes args (removes buf and filler), processes return, changes block
51
+ # *pre, path, buf, filler, offset, fuse_file_info, flag = nil
52
+ def readdir(*args)
53
+ flag_arg = args.pop if fuse3_compat?
54
+ *pre, buf, filler, offset, fuse_file_info = args
55
+ args = pre + [offset, fuse_file_info]
56
+ args << flag_arg if fuse3_compat?
57
+ buffer_available = true
58
+ super(*args) do |name, stat, buffered = false, flag = 0|
59
+ raise StopIteration unless buffer_available
60
+
61
+ offset = buffered if buffered.is_a?(Integer)
62
+ offset += 1 if buffered && !buffered.is_a?(Integer) # auto-track offsets
63
+ stat = Stat.new.fill(stat) if stat && !stat.is_a?(Stat)
64
+ filler_args = [buf, name, stat, offset]
65
+ filler_args << flag if fuse3_compat?
66
+ buffer_available = filler.call(*filler_args).zero?
67
+ end
68
+ end
69
+ # rubocop:enable Metrics/AbcSize
70
+
71
+ def setxattr(*args)
72
+ # fuse converts the void* data buffer to a const char* null terminated string
73
+ # which libfuse reads directly, so size is irrelevant
74
+ *pre, path, name, data, _size, flags = args
75
+ super(*pre, path, name, data, flags)
76
+ end
77
+
78
+ def getxattr(*args)
79
+ *pre, path, name, buf, size = args
80
+ res = super(*pre, path, name)
81
+
82
+ return -Errno::ENODATA::Errno unless res
83
+
84
+ res = res.to_s
85
+
86
+ return res.size if size.zero?
87
+ return -Errno::ERANGE::Errno if res.size > size
88
+
89
+ buf.write_bytes(res)
90
+ res.size
91
+ end
92
+
93
+ def listxattr(*args)
94
+ *pre, path, buf, size = args
95
+ res = super(*pre, path)
96
+ res.reduce(0) do |offset, name|
97
+ name = name.to_s
98
+ unless size.zero?
99
+ return -Errno::ERANGE::Errno if offset + name.size >= size
100
+
101
+ buf.put_string(offset, name) # put string includes the NUL terminator
102
+ end
103
+ offset + name.size + 1
104
+ end
105
+ end
106
+
107
+ def read_buf(*args)
108
+ *pre, path, bufp, size, offset, fuse_file_info = args
109
+ buf = FuseBufVec.new
110
+ super(*pre, path, buf, size, offset, fuse_file_info)
111
+ bufp.put_pointer(0, buf)
112
+ 0
113
+ end
114
+
115
+ # extract atime, mtime from times array, convert from FFI::Stat::TimeSpec to ruby Time
116
+ def utimens(*args)
117
+ ffi = args.pop if fuse3_compat?
118
+ *pre, path, times = args
119
+
120
+ # Empty times means set both to current time
121
+ times = [Stat::TimeSpec.now, Stat::TimeSpec.now] unless times&.size == 2
122
+
123
+ # If both times are set to UTIME_NOW, make sure they get the same value!
124
+ now = times.any?(&:now?) && Time.now
125
+ atime, mtime = times.map { |t| t.time(now) }
126
+
127
+ args = pre + [path, atime, mtime]
128
+ args << ffi if fuse3_compat?
129
+ super(*args)
130
+ 0
131
+ end
132
+
133
+ # accept a filehandle object as result of these methods
134
+ # keep a reference to the filehandle until corresponding release
135
+ %i[create open opendir].each do |fuse_method|
136
+ define_method(fuse_method) do |*args|
137
+ fh = super(*args)
138
+ store_filehandle(args.last, fh)
139
+ 0
140
+ end
141
+ end
142
+
143
+ %i[release release_dir].each do |fuse_method|
144
+ define_method(fuse_method) do |*args|
145
+ super(*args)
146
+ ensure
147
+ release_filehandle(args.last)
148
+ end
149
+ end
150
+
151
+ private
152
+
153
+ def store_filehandle(ffi, filehandle)
154
+ return unless filehandle
155
+
156
+ @filehandles ||= {}
157
+ @filehandles[ffi]
158
+ ffi.fh = filehandle
159
+ end
160
+
161
+ def release_filehandle(ffi)
162
+ return unless ffi.fh
163
+
164
+ @filehandles.delete(ffi.fh.object_id)
165
+ end
166
+ end
167
+ # rubocop:enable Metrics/ModuleLength
168
+
169
+ # @!group FUSE Callbacks
170
+
171
+ # @!method open(path,fuse_file_info)
172
+ # File open
173
+ # @abstract
174
+ # @param [String] path
175
+ # @param [FuseFileInfo] fuse_file_info
176
+ # @return [Object] file handle (available to future operations in fuse_file_info.fh)
177
+ # @see FuseOperations#open
178
+
179
+ # @!method create(path,mode,fuse_file_info)
180
+ # File creation
181
+ # @abstract
182
+ # @param [String] path
183
+ # @param [Integer] mode
184
+ # @param [FuseFileInfo] fuse_file_info
185
+ # @return [Object] file handle (available to future operations in fuse_file_info.fh)
186
+ # @see FuseOperations#create
187
+
188
+ # @!method opendir(path,fuse_file_info)
189
+ # Directory open
190
+ # @abstract
191
+ # @param [String] path
192
+ # @param [FuseFileInfo] fuse_file_info
193
+ # @return [Object] directory handle (available to future operations in fuse_file_info.fh)
194
+ # @see FuseOperations#opendir
195
+
196
+ # @!method write(path,data,offset,info)
197
+ # Write file data
198
+ # @abstract
199
+ # @param [String] path
200
+ # @param [String] data
201
+ # @param [Integer] offset
202
+ # @param [FuseFileInfo] info
203
+ # @return [void]
204
+ # @see FuseOperations#write
205
+
206
+ # @!method read(path,size,offset,info)
207
+ # @abstract
208
+ # Read file data
209
+ #
210
+ # @param [String] path
211
+ # @param [Integer] size
212
+ # @param [Integer] offset
213
+ # @param [FuseFileInfo] info
214
+ #
215
+ # @return [String] the data, expected to be exactly size bytes, except if EOF
216
+ # @see FuseOperations#read
217
+
218
+ # @!method readlink(path)
219
+ # @abstract
220
+ # Resolve target of a symbolic link
221
+ # @param [String] path
222
+ # @return [String] the link target
223
+ # @see FuseOperations#readlink
224
+
225
+ # @!method readdir(path,offset,fuse_file_info,&filler)
226
+ # @abstract
227
+ # List directory entries
228
+ #
229
+ # The filesystem may choose between two modes of operation:
230
+ #
231
+ # 1) The readdir implementation ignores the offset parameter yielding only name, stat pairs for each entry.
232
+ # The yield will always return true so the whole directory is read in a single readdir operation.
233
+ #
234
+ # 2) The readdir implementation keeps track of the offsets of the directory entries. It uses the offset
235
+ # parameter and always yields buffered=true to the filler function. When the buffer is full, or an error
236
+ # occurs, yielding to the filler function will return false. Subsequent yields will raise StopIteration
237
+ #
238
+ # @param [String] path
239
+ # @param [Integer] offset the starting offset (inclusive!)
240
+ #
241
+ # this is either 0 for a new operation, or the last value previously yielded to the filler function
242
+ # (when it returned false to indicate the buffer was full). Type 2 implementations should therefore include
243
+ #
244
+ # @param [FuseFileInfo] fuse_file_info
245
+ #
246
+ # @raise [SystemCallError] an appropriate Errno value
247
+ # @return [void]
248
+ #
249
+ # @yieldparam [String] name the name of a directory entry
250
+ # @yieldparam [Stat|nil] stat the directory entry stat
251
+ #
252
+ # Note sending nil values will cause Fuse to issue #getattr operations for each entry
253
+ #
254
+ # @yieldparam [Integer|Boolean] offset (optional - default false)
255
+ #
256
+ # integer value will be used as offset. The last value yielded (with false return) will be used
257
+ # for the next readdir call
258
+ #
259
+ # otherwise truthy to indicate support for restart from monotonically increasing offset
260
+ #
261
+ # false to indicate type 1 operation - full listing
262
+ #
263
+ # @yieldreturn [Boolean]
264
+ #
265
+ # * true if buffer accepted the directory entry
266
+ # * false on first time buffer is full. StopIteration will be raised on subsequent yields
267
+ #
268
+ # @see FuseOperations#readdir
269
+
270
+ # @!method getxattr(path,name)
271
+ # @abstract
272
+ # Get extended attribute
273
+ # @param [String] path
274
+ # @param [String] name the attribute name
275
+ # @return [nil|String] the attribute value or nil if it does not exist
276
+ # @see FuseOperations#getxattr
277
+
278
+ # @!method listxattr(path,name)
279
+ # @abstract
280
+ # List extended attributes
281
+ # @param [String] path
282
+ # @return [Array<String>] list of xattribute names
283
+ # @see FuseOperations#listxattr
284
+
285
+ # @!method setxattr(path, name, data, flags)
286
+ # @abstract
287
+ # Set extended attribute
288
+ # @param [String] path
289
+ # @param [String] name
290
+ # @param [String] data
291
+ # @param [Symbol|Integer] flags 0, :xattr_create, :xattr_replace
292
+ # @return [void]
293
+ # @raise [Errno::EEXIST] for :xattr_create and name already exists
294
+ # @raise [Errno::ENODATA] for :xattr_replace and name does not already exist
295
+ # @see FuseOperations#setxattr
296
+
297
+ # @!method read_buf(path,buffers,size,offset,fuse_file_info)
298
+ # @abstract
299
+ # Read through fuse data buffers
300
+ # @param [String] path
301
+ # @param [FuseBufVec] buffers
302
+ # @param [Integer] size
303
+ # @param [Integer] offset
304
+ # @param [FuseFileInfo] fuse_file_info
305
+ # @return [void]
306
+ # @see FuseOperations#read_buf
307
+
308
+ # @!method utimens(path,atime,mtime,fuse_file_info=nil)
309
+ # @abstract
310
+ # Change the access and/or modification times of a file with nanosecond resolution
311
+ #
312
+ # @param [String] path
313
+ # @param [Time|nil] atime if set the file's new access time (in UTC)
314
+ # @param [Time|nil] mtime if set the file's new modification time (in UTC)
315
+ # @param [FuseFileInfo] fuse_file_info (since Fuse3)
316
+ # @return [void]
317
+ # @see FuseOperations#utimens
318
+ #
319
+ # @note
320
+ # Either atime or mtime can be nil corresponding to utimensat(2) receiving UTIME_OMIT.
321
+ # The special value UTIME_NOW passed from Fuse is automatically set to the current time
322
+
323
+ # @!endgroup
324
+
325
+ # @!visibility private
326
+ def self.included(mod)
327
+ mod.prepend(Shim)
328
+ mod.include(Safe)
329
+ mod.include(Debug)
330
+ end
331
+ end
332
+ end
333
+ end
334
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FFI
4
+ module Libfuse
5
+ module Adapter
6
+ # Safe callbacks convert return values into integer responses, and rescues errors
7
+ #
8
+ # Applies to all callbacks except :init, :destroy
9
+ module Safe
10
+ # @!visibility private
11
+ def fuse_wrappers(*wrappers)
12
+ wrappers << {
13
+ wrapper: proc { |fm, *args, **_, &b| Safe.safe_callback(fm, *args, &b) },
14
+ excludes: %i[init destroy]
15
+ }
16
+ return wrappers unless defined?(super)
17
+
18
+ super(*wrappers)
19
+ end
20
+
21
+ # Callbacks that are expected to return meaningful positive integers
22
+ MEANINGFUL_RETURN = %i[read write write_buf lseek copy_file_range getxattr listxattr].freeze
23
+
24
+ module_function
25
+
26
+ # Process the results of yielding *args for the fuse_method callback
27
+ #
28
+ # @yieldreturn [SystemCallError] expected callback errors rescued to return equivalent -ve errno value
29
+ # @yieldreturn [StandardError,ScriptError] unexpected callback errors are rescued
30
+ # to return -Errno::ENOTRECOVERABLE after emitting backtrace to #warn
31
+ #
32
+ # @yieldreturn [Integer]
33
+ #
34
+ # * -ve values returned directly
35
+ # * +ve values returned directly for fuse_methods in {MEANINGFUL_RETURN} list
36
+ # * otherwise returns 0
37
+ #
38
+ # @yieldreturn [Object] always returns 0 if no exception is raised
39
+ #
40
+ def safe_callback(fuse_method, *args)
41
+ result = yield(*args)
42
+
43
+ return 0 unless result.is_a?(Integer)
44
+ return 0 unless result.negative? || MEANINGFUL_RETURN.include?(fuse_method)
45
+
46
+ result
47
+ rescue SystemCallError => e
48
+ -e.errno
49
+ rescue StandardError, ScriptError => e
50
+ # rubocop:disable Layout/LineLength
51
+ warn "FFI::libfuse error in callback #{fuse_method}: #{e.class.name}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
52
+ # rubocop:enable Layout/LineLength
53
+ -Errno::ENOTRECOVERABLE::Errno
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../fuse_context'
4
+
5
+ module FFI
6
+ module Libfuse
7
+ module Adapter
8
+ # Injects a wrapper via #{FuseCallbacks#fuse_wrappers} make the current {FuseContext} object available to
9
+ # callbacks (except :destroy) via thread local variable :fuse_context
10
+ module ThreadLocalContext
11
+ # @!visibility private
12
+ def fuse_wrappers(*wrappers)
13
+ wrappers.unshift(
14
+ {
15
+ wrapper: proc { |_fm, *args, **_, &b| self.class.thread_local_context(*args, &b) },
16
+ excludes: %i[destroy]
17
+ }
18
+ )
19
+ return wrappers unless defined?(super)
20
+
21
+ super(*wrappers)
22
+ end
23
+
24
+ module_function
25
+
26
+ # Stores {FuseContext} in thread local variable :fuse_context before yielding
27
+ def thread_local_context(*args)
28
+ Thread.current[:fuse_context] = FuseContext.get
29
+ yield(*args)
30
+ ensure
31
+ Thread.current[:fuse_context] = nil
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end