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