ffi-libfuse 0.0.1.pre

Sign up to get free protection for your applications and to get access to all the features.
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