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.
- checksums.yaml +7 -0
- data/.yardopts +1 -0
- data/README.md +100 -0
- data/lib/ffi/accessors.rb +145 -0
- data/lib/ffi/devt.rb +30 -0
- data/lib/ffi/flock.rb +47 -0
- data/lib/ffi/gnu_extensions.rb +115 -0
- data/lib/ffi/libfuse/ackbar.rb +112 -0
- data/lib/ffi/libfuse/adapter/context.rb +37 -0
- data/lib/ffi/libfuse/adapter/debug.rb +89 -0
- data/lib/ffi/libfuse/adapter/fuse2_compat.rb +91 -0
- data/lib/ffi/libfuse/adapter/fuse3_support.rb +87 -0
- data/lib/ffi/libfuse/adapter/interrupt.rb +37 -0
- data/lib/ffi/libfuse/adapter/pathname.rb +23 -0
- data/lib/ffi/libfuse/adapter/ruby.rb +334 -0
- data/lib/ffi/libfuse/adapter/safe.rb +58 -0
- data/lib/ffi/libfuse/adapter/thread_local_context.rb +36 -0
- data/lib/ffi/libfuse/adapter.rb +79 -0
- data/lib/ffi/libfuse/callbacks.rb +61 -0
- data/lib/ffi/libfuse/fuse2.rb +159 -0
- data/lib/ffi/libfuse/fuse3.rb +162 -0
- data/lib/ffi/libfuse/fuse_args.rb +166 -0
- data/lib/ffi/libfuse/fuse_buffer.rb +155 -0
- data/lib/ffi/libfuse/fuse_callbacks.rb +48 -0
- data/lib/ffi/libfuse/fuse_cmdline_opts.rb +44 -0
- data/lib/ffi/libfuse/fuse_common.rb +249 -0
- data/lib/ffi/libfuse/fuse_config.rb +205 -0
- data/lib/ffi/libfuse/fuse_conn_info.rb +211 -0
- data/lib/ffi/libfuse/fuse_context.rb +79 -0
- data/lib/ffi/libfuse/fuse_file_info.rb +100 -0
- data/lib/ffi/libfuse/fuse_loop_config.rb +43 -0
- data/lib/ffi/libfuse/fuse_operations.rb +870 -0
- data/lib/ffi/libfuse/fuse_opt.rb +54 -0
- data/lib/ffi/libfuse/fuse_poll_handle.rb +59 -0
- data/lib/ffi/libfuse/fuse_version.rb +43 -0
- data/lib/ffi/libfuse/job_pool.rb +53 -0
- data/lib/ffi/libfuse/main.rb +200 -0
- data/lib/ffi/libfuse/test/operations.rb +56 -0
- data/lib/ffi/libfuse/test.rb +3 -0
- data/lib/ffi/libfuse/thread_pool.rb +147 -0
- data/lib/ffi/libfuse/version.rb +8 -0
- data/lib/ffi/libfuse.rb +24 -0
- data/lib/ffi/ruby_object.rb +95 -0
- data/lib/ffi/stat/constants.rb +29 -0
- data/lib/ffi/stat/native.rb +50 -0
- data/lib/ffi/stat/time_spec.rb +137 -0
- data/lib/ffi/stat.rb +96 -0
- data/lib/ffi/stat_vfs.rb +81 -0
- data/lib/ffi/struct_array.rb +39 -0
- data/lib/ffi/struct_wrapper.rb +100 -0
- data/sample/memory_fs.rb +189 -0
- data/sample/no_fs.rb +69 -0
- 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
|