ffi-libfuse 0.0.1.pre → 0.1.0.rc20220550
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +3 -1
- data/CHANGES.md +14 -0
- data/LICENSE +21 -0
- data/README.md +127 -44
- data/lib/ffi/accessors.rb +6 -6
- data/lib/ffi/boolean_int.rb +27 -0
- data/lib/ffi/devt.rb +23 -0
- data/lib/ffi/encoding.rb +38 -0
- data/lib/ffi/gnu_extensions.rb +1 -1
- data/lib/ffi/libfuse/ackbar.rb +6 -8
- data/lib/ffi/libfuse/adapter/context.rb +12 -10
- data/lib/ffi/libfuse/adapter/fuse2_compat.rb +52 -51
- data/lib/ffi/libfuse/adapter/fuse3_support.rb +0 -1
- data/lib/ffi/libfuse/adapter/ruby.rb +499 -148
- data/lib/ffi/libfuse/adapter/safe.rb +1 -1
- data/lib/ffi/libfuse/adapter.rb +1 -2
- data/lib/ffi/libfuse/callbacks.rb +1 -1
- data/lib/ffi/libfuse/filesystem/accounting.rb +116 -0
- data/lib/ffi/libfuse/filesystem/mapped_dir.rb +74 -0
- data/lib/ffi/libfuse/filesystem/mapped_files.rb +141 -0
- data/lib/ffi/libfuse/filesystem/pass_through_dir.rb +55 -0
- data/lib/ffi/libfuse/filesystem/pass_through_file.rb +45 -0
- data/lib/ffi/libfuse/filesystem/utils.rb +102 -0
- data/lib/ffi/libfuse/filesystem/virtual_dir.rb +306 -0
- data/lib/ffi/libfuse/filesystem/virtual_file.rb +94 -0
- data/lib/ffi/libfuse/filesystem/virtual_fs.rb +188 -0
- data/lib/ffi/libfuse/filesystem/virtual_node.rb +101 -0
- data/lib/ffi/libfuse/filesystem.rb +25 -0
- data/lib/ffi/libfuse/fuse2.rb +21 -21
- data/lib/ffi/libfuse/fuse3.rb +12 -12
- data/lib/ffi/libfuse/fuse_args.rb +69 -34
- data/lib/ffi/libfuse/fuse_buffer.rb +128 -26
- data/lib/ffi/libfuse/fuse_callbacks.rb +1 -5
- data/lib/ffi/libfuse/fuse_common.rb +55 -61
- data/lib/ffi/libfuse/fuse_config.rb +134 -143
- data/lib/ffi/libfuse/fuse_conn_info.rb +310 -134
- data/lib/ffi/libfuse/fuse_context.rb +45 -3
- data/lib/ffi/libfuse/fuse_operations.rb +43 -19
- data/lib/ffi/libfuse/fuse_version.rb +10 -6
- data/lib/ffi/libfuse/main.rb +80 -37
- data/lib/ffi/libfuse/thread_pool.rb +1 -1
- data/lib/ffi/libfuse/version.rb +1 -1
- data/lib/ffi/libfuse.rb +13 -4
- data/lib/ffi/ruby_object.rb +1 -1
- data/lib/ffi/stat/constants.rb +9 -0
- data/lib/ffi/stat/native.rb +36 -6
- data/lib/ffi/stat/time_spec.rb +28 -12
- data/lib/ffi/stat.rb +111 -22
- data/lib/ffi/stat_vfs.rb +59 -1
- data/lib/ffi/struct_wrapper.rb +22 -1
- data/sample/hello_fs.rb +54 -0
- data/sample/memory_fs.rb +5 -181
- data/sample/no_fs.rb +20 -21
- data/sample/pass_through_fs.rb +30 -0
- metadata +66 -7
- data/lib/ffi/libfuse/adapter/thread_local_context.rb +0 -36
@@ -2,178 +2,379 @@
|
|
2
2
|
|
3
3
|
require_relative 'safe'
|
4
4
|
require_relative 'debug'
|
5
|
+
require 'set'
|
5
6
|
|
6
7
|
module FFI
|
7
8
|
module Libfuse
|
8
9
|
module Adapter
|
9
|
-
#
|
10
|
-
#
|
10
|
+
# This module assists with converting native C libfuse into idiomatic Ruby
|
11
|
+
#
|
12
|
+
# Class Method helpers
|
13
|
+
# ===
|
14
|
+
# These functions deal with the native fuse FFI::Pointers, Buffers etc
|
15
|
+
#
|
16
|
+
# The class {ReaddirFiller} assists with the complexity of the readdir callback
|
17
|
+
#
|
18
|
+
# FUSE Callbacks
|
19
|
+
# ===
|
20
|
+
#
|
21
|
+
# Including this module in a Filesystem module changes the method signatures for callbacks to be more idiomatic
|
22
|
+
# ruby (via prepending {Prepend}). It also includes the type 1 adapters {Context}, {Debug} and {Safe}
|
23
|
+
#
|
24
|
+
# Filesystems that return {::IO} like objects from #{open} need not implement other file io operations
|
25
|
+
# Similarly Filesystems that return {::Dir} like objects from #{opendir} need not implement #{readdir}
|
11
26
|
#
|
12
|
-
# @note includes {Debug} and {Safe}
|
13
27
|
module Ruby
|
14
|
-
#
|
15
|
-
#
|
28
|
+
# Helper class for {FuseOperations#readdir}
|
29
|
+
# @example
|
30
|
+
# def readdir(path, buf, filler, _offset, _ffi, *flags)
|
31
|
+
# rdf = FFI::Adapter::Ruby::ReaddirFiller.new(buf, filler)
|
32
|
+
# %w[. ..].each { |dot_entry| rdf.fill(dot_entry) }
|
33
|
+
# entries.each { |entry| rdf.fill(entry) }
|
34
|
+
# end
|
35
|
+
# @example
|
36
|
+
# # short version
|
37
|
+
# def readdir(path, buf, filler, _offset, _ffi, *flags)
|
38
|
+
# %w[. ..].concat(entries).each(&FFI::Adapter::Ruby::ReaddirFiller.new(buf,filler))
|
39
|
+
# end
|
40
|
+
class ReaddirFiller
|
41
|
+
# @param [FFI::Pointer] buf from #{FuseOperations#readdir}
|
42
|
+
# @param [FFI::Function] filler from #{FuseOperations#readdir}
|
43
|
+
# @param [Boolean] fuse3 does the filler function expect fuse 3 compatibility
|
44
|
+
def initialize(buf, filler, fuse3: FUSE_MAJOR_VERSION >= 3)
|
45
|
+
@buf = buf
|
46
|
+
@filler = filler
|
47
|
+
@stat_buf = nil
|
48
|
+
@fuse3 = fuse3
|
49
|
+
end
|
50
|
+
|
51
|
+
# Fill readdir from a directory handle
|
52
|
+
# @param [#seek, #read, #tell] dir_handle
|
53
|
+
# @param [Integer] offset
|
54
|
+
# @raise [Errno::ENOTSUP] unless dir_handle quacks like ::Dir
|
55
|
+
def readdir_fh(dir_handle, offset = 0)
|
56
|
+
raise Errno::ENOTSUP unless %i[seek read tell].all? { |m| dir_handle.respond_to?(m) }
|
57
|
+
|
58
|
+
dir_handle.seek(offset)
|
59
|
+
loop while (name = dir_handle.read) && fill(name, offset: dir_handle.tell)
|
60
|
+
end
|
61
|
+
|
62
|
+
# @param [String] name a directory entry
|
63
|
+
# @param [FFI::Stat|Hash<Symbol,Integer>|nil] stat or stat fields to fill a {::FFI::Stat}
|
64
|
+
#
|
65
|
+
# Note sending nil values will cause Fuse to issue #getattr operations for each entry
|
66
|
+
#
|
67
|
+
# It is safe to reuse the same {FFI::Stat} object between calls
|
68
|
+
# @param [Integer] offset
|
69
|
+
# @param [Boolean] fill_dir_plus true if stat has full attributes for inode caching
|
70
|
+
# @return [Boolean] true if the buffer accepted the entry
|
71
|
+
# @raise [StopIteration] if called after a previous call returned false
|
72
|
+
def fill(name, stat: nil, offset: 0, fill_dir_plus: false)
|
73
|
+
raise StopIteration unless @buf
|
74
|
+
|
75
|
+
fill_flags = fill_flags(fill_dir_plus: fill_dir_plus)
|
76
|
+
fill_stat = fill_stat(stat)
|
77
|
+
return true if @filler.call(@buf, name, fill_stat, offset, *fill_flags).zero?
|
78
|
+
|
79
|
+
@buf = nil
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [Proc] a proc to pass to something that yields like #{fill}
|
83
|
+
def to_proc
|
84
|
+
proc do |name, stat: nil, offset: 0, fill_dir_plus: false|
|
85
|
+
fill(name, stat: stat, offset: offset, fill_dir_plus: fill_dir_plus)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def fill_flags(fill_dir_plus:)
|
92
|
+
return [] unless @fuse3
|
93
|
+
|
94
|
+
[fill_dir_plus ? :fuse_fill_dir_plus : 0]
|
95
|
+
end
|
96
|
+
|
97
|
+
def fill_stat(from)
|
98
|
+
return from if !from || from.is_a?(::FFI::Stat)
|
99
|
+
|
100
|
+
(@stat_buf ||= ::FFI::Stat.new).fill(from)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
16
104
|
# rubocop:disable Metrics/ModuleLength
|
17
|
-
|
105
|
+
|
106
|
+
# Can be prepended to concrete filesystem implementations to skip duplicate handling of {Debug}, {Safe}
|
107
|
+
#
|
108
|
+
# @note callbacks still expect to be ultimately handled by {Safe}, ie they raise SystemCallError and can
|
109
|
+
# return non Integer results
|
110
|
+
module Prepend
|
18
111
|
include Adapter
|
19
112
|
|
20
|
-
#
|
113
|
+
# Returns true if our we can support fuse_method callback
|
114
|
+
#
|
115
|
+
# ie. when
|
116
|
+
#
|
117
|
+
# * fuse_method is implemented directly by our superclass
|
118
|
+
# * the adapter can handle the callback via
|
119
|
+
# * file or directory handle returned from {#open} or {#opendir}
|
120
|
+
# * fallback to an alternate implementation (eg {#read_buf} -> {#read}, {#write_buf} -> {#write})
|
121
|
+
def fuse_respond_to?(fuse_method)
|
122
|
+
fuse_methods =
|
123
|
+
case fuse_method
|
124
|
+
when :read, :write, :flush, :release
|
125
|
+
%i[open]
|
126
|
+
when :read_buf
|
127
|
+
%i[open read]
|
128
|
+
when :write_buf
|
129
|
+
%i[open write]
|
130
|
+
when :readdir, :releasedir
|
131
|
+
%i[opendir]
|
132
|
+
else
|
133
|
+
[]
|
134
|
+
end
|
135
|
+
fuse_methods << fuse_method
|
136
|
+
|
137
|
+
fuse_methods.any? { |m| super(m) }
|
138
|
+
end
|
139
|
+
|
140
|
+
# Helper to test if path is root
|
141
|
+
def root?(path)
|
142
|
+
path.respond_to?(:root?) ? path.root? : path.to_s == '/'
|
143
|
+
end
|
144
|
+
|
145
|
+
# @!visibility private
|
146
|
+
# Fuse 3 compatibility
|
147
|
+
# @return [Boolean] true if this filesystem is receiving Fuse3 compatible arguments
|
148
|
+
# @see Fuse3Support
|
21
149
|
def fuse3_compat?
|
22
150
|
FUSE_MAJOR_VERSION >= 3
|
23
151
|
end
|
24
152
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
153
|
+
# @!group FUSE Callbacks
|
154
|
+
|
155
|
+
# Writes data to path via
|
156
|
+
#
|
157
|
+
# * super as per {Ruby#write} if defined
|
158
|
+
# * {Ruby.write_fh} on ffi.fh
|
159
|
+
def write(path, buf, size = buf.size, offset = 0, ffi = nil)
|
160
|
+
return Ruby.write_fh(buf, size, offset, ffi&.fh) unless defined?(super)
|
161
|
+
|
162
|
+
Ruby.write_data(buf, size) { |data| super(path, data, offset, ffi) }
|
29
163
|
end
|
30
164
|
|
31
|
-
|
32
|
-
|
33
|
-
|
165
|
+
# Writes data to path with {FuseBuf}s via
|
166
|
+
#
|
167
|
+
# * super directly if defined
|
168
|
+
# * {FuseBufVec#copy_to_fd} if ffi.fh has non-nil :fileno
|
169
|
+
# * {FuseBufVec#copy_to_str} with the result of {Ruby#write}
|
170
|
+
def write_buf(path, bufv, offset, ffi)
|
171
|
+
return super if defined?(super)
|
34
172
|
|
35
|
-
|
173
|
+
fd = ffi&.fh&.fileno
|
174
|
+
return bufv.copy_to_fd(fd, offset) if fd
|
36
175
|
|
37
|
-
|
38
|
-
|
176
|
+
data = bufv.copy_to_str
|
177
|
+
write(path, data, data.size, offset, ffi)
|
39
178
|
end
|
40
179
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
180
|
+
# Flush data to path via
|
181
|
+
#
|
182
|
+
# * super if defined
|
183
|
+
# * :flush on ffi.fh if defined
|
184
|
+
def flush(path, ffi)
|
185
|
+
return super if defined?(super)
|
186
|
+
|
187
|
+
fh = ffi&.fh
|
188
|
+
fh.flush if fh.respond_to?(:flush)
|
46
189
|
end
|
47
190
|
|
48
|
-
#
|
49
|
-
|
50
|
-
#
|
51
|
-
#
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
191
|
+
# Sync data to path via
|
192
|
+
#
|
193
|
+
# * super as per {Ruby#fsync} if defined
|
194
|
+
# * :datasync on ffi.fh if defined and datasync is non-zero
|
195
|
+
# * :fysnc on ffi.fh if defined
|
196
|
+
def fsync(path, datasync, ffi)
|
197
|
+
return super(path, datasync != 0, ffi) if defined?(super)
|
198
|
+
|
199
|
+
fh = ffi&.fh
|
200
|
+
return fh.datasync if datasync && fh.respond_to?(:datasync)
|
201
|
+
|
202
|
+
fh.fsync if fh.respond_to?(:fsync)
|
203
|
+
end
|
204
|
+
|
205
|
+
# Read data from path via
|
206
|
+
#
|
207
|
+
# * super as per {Ruby#read} if defined
|
208
|
+
# * ffi.fh as per {Ruby.read}
|
209
|
+
def read(path, buf, size, offset, ffi)
|
210
|
+
Ruby.read(buf, size, offset) do
|
211
|
+
defined?(super) ? super(path, size, offset, ffi) : ffi&.fh
|
67
212
|
end
|
68
213
|
end
|
69
|
-
# rubocop:enable Metrics/AbcSize
|
70
214
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
215
|
+
# Read data with {FuseBuf}s via
|
216
|
+
#
|
217
|
+
# * super if defined
|
218
|
+
# * ffi.fh.fileno if defined and not nil
|
219
|
+
# * result of {#read}
|
220
|
+
def read_buf(path, bufp, size, offset, ffi)
|
221
|
+
return super if defined?(super)
|
222
|
+
|
223
|
+
Ruby.read_buf(bufp, size, offset) do
|
224
|
+
fh = ffi&.fh
|
225
|
+
fd = fh.fileno if fh.respond_to?(:fileno)
|
226
|
+
next fd if fd
|
227
|
+
|
228
|
+
read(path, nil, size, offset, ffi)
|
229
|
+
end
|
76
230
|
end
|
77
231
|
|
78
|
-
|
79
|
-
|
80
|
-
|
232
|
+
# Read link name from path via super as per {Ruby#readlink}
|
233
|
+
def readlink(path, buf, size)
|
234
|
+
raise Errno::ENOTSUP unless defined?(super)
|
81
235
|
|
82
|
-
|
236
|
+
Ruby.readlink(buf, size) { super(path, size) }
|
237
|
+
end
|
83
238
|
|
84
|
-
|
239
|
+
# Read directory entries via
|
240
|
+
#
|
241
|
+
# * super as per {Ruby#readdir} if defined
|
242
|
+
# * ffi.fh using {ReaddirFiller#readdir_fh}
|
243
|
+
def readdir(path, buf, filler, offset, ffi, flag_arg = nil)
|
244
|
+
rd_filler = ReaddirFiller.new(buf, filler, fuse3: fuse3_compat?)
|
85
245
|
|
86
|
-
|
87
|
-
|
246
|
+
flag_args = {}
|
247
|
+
flag_args[:readdir_plus] = (flag_arg == :fuse_readdir_plus) if fuse3_compat?
|
248
|
+
return super(path, offset, ffi, **flag_args, &rd_filler) if defined?(super)
|
88
249
|
|
89
|
-
|
90
|
-
|
250
|
+
rd_filler.readdir_fh(ffi.fh, offset)
|
251
|
+
rescue StopIteration
|
252
|
+
# do nothing
|
91
253
|
end
|
92
254
|
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
255
|
+
# Set extended attributes via super as per {Ruby#setxattr}
|
256
|
+
def setxattr(path, name, data, _size, flags)
|
257
|
+
raise Errno::ENOTSUP unless defined?(super)
|
100
258
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
end
|
259
|
+
# fuse converts the void* data buffer to a const char* null terminated string
|
260
|
+
# which libfuse reads directly, so size is irrelevant
|
261
|
+
super(path, name, data, flags)
|
105
262
|
end
|
106
263
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
0
|
264
|
+
# Get extended attributes via super as per {Ruby#getxattr}
|
265
|
+
def getxattr(path, name, buf, size)
|
266
|
+
raise Errno::ENOTSUP unless defined?(super)
|
267
|
+
|
268
|
+
Ruby.getxattr(buf, size) { super(path, name) }
|
113
269
|
end
|
114
270
|
|
115
|
-
#
|
116
|
-
def
|
117
|
-
|
118
|
-
*pre, path, times = args
|
271
|
+
# List extended attributes via super as per {Ruby#listxattr}
|
272
|
+
def listxattr(*args)
|
273
|
+
raise Errno::ENOTSUP unless defined?(super)
|
119
274
|
|
120
|
-
|
121
|
-
|
275
|
+
path, buf, size = args
|
276
|
+
Ruby.listxattr(buf, size) { super(path) }
|
277
|
+
end
|
122
278
|
|
123
|
-
|
124
|
-
|
125
|
-
|
279
|
+
# Set file atime, mtime via super as per {Ruby#utimens}
|
280
|
+
def utimens(path, times, *fuse3_args)
|
281
|
+
raise Errno::ENOTSUP unless defined?(super)
|
126
282
|
|
127
|
-
|
128
|
-
|
129
|
-
super(*args)
|
283
|
+
atime, mtime = Stat::TimeSpec.fill_times(times[0, 2], 2).map(&:time)
|
284
|
+
super(path, atime, mtime, *fuse3_args)
|
130
285
|
0
|
131
286
|
end
|
132
287
|
|
133
|
-
#
|
134
|
-
#
|
288
|
+
# @!method create(path, mode, ffi)
|
289
|
+
# Calls super if defined as per {Ruby#create} storing result in ffi.fh and protecting it from GC
|
290
|
+
# until {#release}
|
291
|
+
|
292
|
+
# @!method open(path, ffi)
|
293
|
+
# Calls super if defined as per {Ruby#open} storing result in ffi.fh and protecting it from GC
|
294
|
+
# until {#release}
|
295
|
+
|
296
|
+
# @!method opendir(path, ffi)
|
297
|
+
# Calls super if defined as per {Ruby#opendir} storing result in ffi.fh and protecting it from GC
|
298
|
+
# until {#releasedir}
|
299
|
+
|
135
300
|
%i[create open opendir].each do |fuse_method|
|
136
301
|
define_method(fuse_method) do |*args|
|
137
|
-
fh = super(*args)
|
138
|
-
|
302
|
+
fh = super(*args) if fuse_super_respond_to?(fuse_method)
|
303
|
+
store_handle(args.last, fh)
|
139
304
|
0
|
140
305
|
end
|
141
306
|
end
|
142
307
|
|
143
|
-
|
144
|
-
|
145
|
-
|
308
|
+
# @!method release(path, ffi)
|
309
|
+
# Calls super if defined and allows ffi.fh to be GC'd
|
310
|
+
|
311
|
+
# @!method releasedir(path, ffi)
|
312
|
+
# Calls super if defined and allows ffi.fh to be GC'd
|
313
|
+
|
314
|
+
%i[release releasedir].each do |fuse_method|
|
315
|
+
define_method(fuse_method) do |path, ffi|
|
316
|
+
super(path, ffi) if fuse_super_respond_to?(fuse_method)
|
146
317
|
ensure
|
147
|
-
|
318
|
+
release_handle(ffi)
|
148
319
|
end
|
149
320
|
end
|
150
321
|
|
322
|
+
# Calls super if defined and storing result to protect from GC until {#destroy}
|
323
|
+
def init(*args)
|
324
|
+
o = super(*args) if fuse_super_respond_to?(:init)
|
325
|
+
handles << o if o
|
326
|
+
end
|
327
|
+
|
328
|
+
# Calls super if defined and allows init_obj to be GC'd
|
329
|
+
def destroy(init_obj)
|
330
|
+
super if fuse_super_respond_to?(:destroy)
|
331
|
+
handles.delete(init_obj) if init_obj
|
332
|
+
end
|
333
|
+
|
334
|
+
# @!endgroup
|
151
335
|
private
|
152
336
|
|
153
|
-
def
|
154
|
-
|
337
|
+
def handles
|
338
|
+
@handles ||= Set.new.compare_by_identity
|
339
|
+
end
|
340
|
+
|
341
|
+
def store_handle(ffi, file_handle)
|
342
|
+
return unless file_handle
|
155
343
|
|
156
|
-
|
157
|
-
|
158
|
-
ffi.fh = filehandle
|
344
|
+
handles << file_handle
|
345
|
+
ffi.fh = file_handle
|
159
346
|
end
|
160
347
|
|
161
|
-
def
|
348
|
+
def release_handle(ffi)
|
162
349
|
return unless ffi.fh
|
163
350
|
|
164
|
-
|
351
|
+
handles.delete(ffi.fh)
|
165
352
|
end
|
166
353
|
end
|
167
354
|
# rubocop:enable Metrics/ModuleLength
|
168
355
|
|
169
356
|
# @!group FUSE Callbacks
|
170
357
|
|
358
|
+
# @!method create(path, mode, fuse_file_info)
|
359
|
+
# Create file
|
360
|
+
# @abstract
|
361
|
+
# @param [String] path
|
362
|
+
# @param [FuseFileInfo] fuse_file_info
|
363
|
+
# @return [Object] file handle available to future operations in fuse_file_info.fh
|
364
|
+
|
171
365
|
# @!method open(path,fuse_file_info)
|
172
366
|
# File open
|
173
367
|
# @abstract
|
174
368
|
# @param [String] path
|
175
369
|
# @param [FuseFileInfo] fuse_file_info
|
176
|
-
# @return [Object] file handle
|
370
|
+
# @return [Object] file handle available to future operations in fuse_file_info.fh
|
371
|
+
#
|
372
|
+
# File handles are kept from being GC'd until {FuseOperations#release}
|
373
|
+
#
|
374
|
+
# If the file handle quacks like {::IO} then the file io operations
|
375
|
+
# :read, :write, :flush, :fsync, :release will be invoked on the file handle if not implemented
|
376
|
+
# by the filesystem
|
377
|
+
#
|
177
378
|
# @see FuseOperations#open
|
178
379
|
|
179
380
|
# @!method create(path,mode,fuse_file_info)
|
@@ -194,78 +395,79 @@ module FFI
|
|
194
395
|
# @see FuseOperations#opendir
|
195
396
|
|
196
397
|
# @!method write(path,data,offset,info)
|
197
|
-
# Write file data
|
198
398
|
# @abstract
|
399
|
+
# Write file data. If not implemented will attempt to use info.fh as an IO via :pwrite, or :seek + :write
|
199
400
|
# @param [String] path
|
200
401
|
# @param [String] data
|
201
402
|
# @param [Integer] offset
|
202
403
|
# @param [FuseFileInfo] info
|
203
404
|
# @return [void]
|
405
|
+
# @raise [Errno::ENOTSUP] if not implemented and info.fh does not quack like IO
|
204
406
|
# @see FuseOperations#write
|
205
407
|
|
408
|
+
# @!method write_buf(path,buffers,offset,info)
|
409
|
+
# @abstract
|
410
|
+
# Write file data from buffers
|
411
|
+
# If not implemented, will try to use info.fh,fileno to perform libfuse' file descriptor io, otherwise
|
412
|
+
# the string data is extracted from buffers and passed to #{write}
|
413
|
+
|
206
414
|
# @!method read(path,size,offset,info)
|
207
415
|
# @abstract
|
208
|
-
# Read file data
|
209
|
-
#
|
416
|
+
# Read file data. If not implemented will attempt to use info.fh to perform the read.
|
210
417
|
# @param [String] path
|
211
418
|
# @param [Integer] size
|
212
419
|
# @param [Integer] offset
|
213
420
|
# @param [FuseFileInfo] info
|
214
|
-
#
|
215
421
|
# @return [String] the data, expected to be exactly size bytes, except if EOF
|
422
|
+
# @return [#pread, #read] something that supports :pread, or :seek and :read
|
423
|
+
# @raise [Errno::ENOTSUP] if not implemented, and info.fh does not quack like IO
|
216
424
|
# @see FuseOperations#read
|
425
|
+
# @see FuseOperations#read_buf
|
426
|
+
|
427
|
+
# @!method read_buf(path,buffers,size,offset,info)
|
428
|
+
# @abstract
|
429
|
+
# If not implemented and info.fh has :fileno then libfuse' file descriptor io will be used,
|
430
|
+
# otherwise will use {read} to populate buffers
|
217
431
|
|
218
|
-
# @!method readlink(path)
|
432
|
+
# @!method readlink(path, size)
|
219
433
|
# @abstract
|
220
434
|
# Resolve target of a symbolic link
|
221
435
|
# @param [String] path
|
222
|
-
# @
|
436
|
+
# @param [Integer] size
|
437
|
+
# @return [String] the link target, truncated to size if necessary
|
223
438
|
# @see FuseOperations#readlink
|
224
439
|
|
225
|
-
# @!method readdir(path,offset,fuse_file_info
|
440
|
+
# @!method readdir(path,offset,fuse_file_info, readdir_plus:, &filler)
|
226
441
|
# @abstract
|
227
442
|
# List directory entries
|
228
443
|
#
|
229
|
-
# The filesystem may choose between
|
444
|
+
# The filesystem may choose between three modes of operation:
|
230
445
|
#
|
231
|
-
# 1) The readdir implementation ignores the offset parameter yielding only name,
|
446
|
+
# 1) The readdir implementation ignores the offset parameter yielding only name, and optional stat
|
232
447
|
# The yield will always return true so the whole directory is read in a single readdir operation.
|
233
448
|
#
|
234
449
|
# 2) The readdir implementation keeps track of the offsets of the directory entries. It uses the offset
|
235
|
-
# parameter and
|
236
|
-
# occurs, yielding to the filler function will return false. Subsequent yields will raise
|
450
|
+
# parameter to restart the iteration and yields non-zero offsets for each entry. When the buffer is full, or
|
451
|
+
# an error occurs, yielding to the filler function will return false. Subsequent yields will raise
|
452
|
+
# StopIteration
|
453
|
+
#
|
454
|
+
# 3) Return a Dir like object from {opendir} and do not implement this method. The directory
|
455
|
+
# will be enumerated from offset via calling :seek, :read and :tell on fuse_file_info.fh
|
237
456
|
#
|
238
457
|
# @param [String] path
|
239
458
|
# @param [Integer] offset the starting offset (inclusive!)
|
240
459
|
#
|
241
460
|
# 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).
|
461
|
+
# (when it returned false to indicate the buffer was full).
|
243
462
|
#
|
244
463
|
# @param [FuseFileInfo] fuse_file_info
|
245
|
-
#
|
464
|
+
# @param [Boolean] readdir_plus true if extended readdir is supported (Fuse3 only)
|
246
465
|
# @raise [SystemCallError] an appropriate Errno value
|
247
466
|
# @return [void]
|
248
|
-
#
|
249
|
-
#
|
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
|
-
#
|
467
|
+
# @yield [name,stat:,offset:,fill_dir_plus:]
|
468
|
+
# See {ReaddirFiller#fill}
|
268
469
|
# @see FuseOperations#readdir
|
470
|
+
# @see ReaddirFiller#fill
|
269
471
|
|
270
472
|
# @!method getxattr(path,name)
|
271
473
|
# @abstract
|
@@ -275,7 +477,7 @@ module FFI
|
|
275
477
|
# @return [nil|String] the attribute value or nil if it does not exist
|
276
478
|
# @see FuseOperations#getxattr
|
277
479
|
|
278
|
-
# @!method listxattr(path
|
480
|
+
# @!method listxattr(path)
|
279
481
|
# @abstract
|
280
482
|
# List extended attributes
|
281
483
|
# @param [String] path
|
@@ -294,17 +496,6 @@ module FFI
|
|
294
496
|
# @raise [Errno::ENODATA] for :xattr_replace and name does not already exist
|
295
497
|
# @see FuseOperations#setxattr
|
296
498
|
|
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
499
|
# @!method utimens(path,atime,mtime,fuse_file_info=nil)
|
309
500
|
# @abstract
|
310
501
|
# Change the access and/or modification times of a file with nanosecond resolution
|
@@ -320,13 +511,173 @@ module FFI
|
|
320
511
|
# Either atime or mtime can be nil corresponding to utimensat(2) receiving UTIME_OMIT.
|
321
512
|
# The special value UTIME_NOW passed from Fuse is automatically set to the current time
|
322
513
|
|
514
|
+
# @!method fsync(path, datasync, fuse_file_info)
|
515
|
+
# @abstract
|
516
|
+
# @param [String] path
|
517
|
+
# @param [Boolean] datasync if true only the user data should be flushed, not the meta data.
|
518
|
+
# @param [FuseFileInfo] fuse_file_info
|
519
|
+
|
323
520
|
# @!endgroup
|
324
521
|
|
325
522
|
# @!visibility private
|
326
523
|
def self.included(mod)
|
327
|
-
mod.prepend(
|
328
|
-
mod.include(
|
524
|
+
mod.prepend(Prepend)
|
525
|
+
mod.include(Context)
|
329
526
|
mod.include(Debug)
|
527
|
+
mod.include(Safe)
|
528
|
+
end
|
529
|
+
|
530
|
+
class << self
|
531
|
+
# Helper for implementing {FuseOperations#readlink}
|
532
|
+
# @param [FFI::Pointer] buf
|
533
|
+
# @param [Integer] size
|
534
|
+
# @yield []
|
535
|
+
# @yieldreturn [String] the link name
|
536
|
+
# @raise [Errno::ENOTSUP] if no data is returned
|
537
|
+
# @raise [Errno::ENAMETOOLONG] if data returned is larger than size
|
538
|
+
# @return [void]
|
539
|
+
def readlink(buf, size)
|
540
|
+
link = yield
|
541
|
+
raise Errno::ENOTSUP unless link
|
542
|
+
raise Errno::ENAMETOOLONG unless link.size < size # includes terminating NUL
|
543
|
+
|
544
|
+
buf.put_string(link)
|
545
|
+
0
|
546
|
+
end
|
547
|
+
|
548
|
+
# Helper for implementing {FuseOperations#read}
|
549
|
+
# @param [FFI::Pointer] buf
|
550
|
+
# @param [Integer] size
|
551
|
+
# @param [Integer] offset
|
552
|
+
# @return [Integer] size of data read
|
553
|
+
# @yield []
|
554
|
+
# @yieldreturn [String, #pread, #pwrite] the resulting data or IO like object
|
555
|
+
# @raise [Errno::ENOTSUP] if no data is returned
|
556
|
+
# @raise [Errno::ERANGE] if data return is larger than size
|
557
|
+
# @see data_to_str
|
558
|
+
def read(buf, size, offset = 0)
|
559
|
+
data = yield
|
560
|
+
raise Errno::ENOTSUP unless data
|
561
|
+
|
562
|
+
return data unless buf # called from read_buf
|
563
|
+
|
564
|
+
data = data_to_str(data, size, offset)
|
565
|
+
raise Errno::ERANGE unless data.size <= size
|
566
|
+
|
567
|
+
buf.write_bytes(data)
|
568
|
+
data.size
|
569
|
+
end
|
570
|
+
|
571
|
+
# Helper for implementing {FuseOperations#read_buf}
|
572
|
+
# @param [FFI::Pointer] bufp
|
573
|
+
# @param [Integer] size
|
574
|
+
# @param [Integer] offset
|
575
|
+
# @yield []
|
576
|
+
# @yieldreturn [Integer|:fileno|String,:pread,:pwrite] a file descriptor, String or io like object
|
577
|
+
# @see data_to_bufvec
|
578
|
+
def read_buf(bufp, size, offset)
|
579
|
+
data = yield
|
580
|
+
raise Errno::ENOTSUP unless data
|
581
|
+
|
582
|
+
bufp.write_pointer(data_to_bufvec(data, size, offset).to_ptr)
|
583
|
+
0
|
584
|
+
end
|
585
|
+
|
586
|
+
# Helper to convert input data to a string for use with {FuseOperations#read}
|
587
|
+
# @param [String|:pread|:read] io input data that is a String or quacks like {::IO}
|
588
|
+
# @param [Integer] size
|
589
|
+
# @param [Integer] offset
|
590
|
+
# @return [String] extracted data
|
591
|
+
def data_to_str(io, size, offset)
|
592
|
+
return io if io.is_a?(String)
|
593
|
+
return io.pread(size, offset) if io.respond_to?(:pread)
|
594
|
+
return io.read(size) if io.respond_to?(:read)
|
595
|
+
|
596
|
+
io.to_s
|
597
|
+
end
|
598
|
+
|
599
|
+
# Helper to convert string or IO to {FuseBufVec} for {FuseOperations#read_buf}
|
600
|
+
# @param [Integer|:fileno|String|:pread|:read] data the io like input data or an integer file descriptor
|
601
|
+
# @param [Integer] size
|
602
|
+
# @param [Integer] offset
|
603
|
+
# @return [FuseBufVec]
|
604
|
+
def data_to_bufvec(data, size, offset)
|
605
|
+
data = data.fileno if data.respond_to?(:fileno)
|
606
|
+
return FuseBufVec.init(autorelease: false, size: size, fd: data, pos: offset) if data.is_a?(Integer)
|
607
|
+
|
608
|
+
str = data_to_str(data, size, offset)
|
609
|
+
FuseBufVec.init(autorelease: false, size: str.size, mem: FFI::MemoryPointer.from_string(str))
|
610
|
+
end
|
611
|
+
|
612
|
+
# Helper to implement #{FuseOperations#write}
|
613
|
+
# @param [FFI::Pointer|:to_s] buf
|
614
|
+
# @param [Integer] size
|
615
|
+
# @return [Integer] size
|
616
|
+
# @yield [data]
|
617
|
+
# @yieldparam [String] data extracted from buf
|
618
|
+
# @yieldreturn [void]
|
619
|
+
def write_data(buf, size)
|
620
|
+
data = buf.read_bytes(size) if buf.respond_to?(:read_bytes)
|
621
|
+
data ||= buf.to_s
|
622
|
+
data = data[0..size] if data.size > size
|
623
|
+
yield data
|
624
|
+
size
|
625
|
+
end
|
626
|
+
|
627
|
+
# Helper to write a data buffer to an open file
|
628
|
+
# @param [FFI::Pointer] buf
|
629
|
+
# @param [Integer] size
|
630
|
+
# @param [Integer] offset
|
631
|
+
# @param [:pwrite,:seek,:write] handle an IO like file handle
|
632
|
+
# @return [Integer] size
|
633
|
+
# @raise [Errno::ENOTSUP] if handle is does not quack like an open file
|
634
|
+
def write_fh(buf, size, offset, handle)
|
635
|
+
write_data(buf, size) do |data|
|
636
|
+
if handle.respond_to?(:pwrite)
|
637
|
+
handle.pwrite(data, offset)
|
638
|
+
elsif handle.respond_to?(:write)
|
639
|
+
handle.seek(offset) if handle.respond_to?(:seek)
|
640
|
+
handle.write(data)
|
641
|
+
else
|
642
|
+
raise Errno::ENOTSUP
|
643
|
+
end
|
644
|
+
end
|
645
|
+
end
|
646
|
+
|
647
|
+
# Helper for implementing {FuseOperations#getxattr}
|
648
|
+
#
|
649
|
+
# @param [FFI::Pointer] buf
|
650
|
+
# @param [Integer] size
|
651
|
+
# @yieldreturn [String] the xattr name
|
652
|
+
def getxattr(buf, size)
|
653
|
+
res = yield
|
654
|
+
raise Errno::ENODATA unless res
|
655
|
+
|
656
|
+
res = res.to_s
|
657
|
+
|
658
|
+
return res.size if size.zero?
|
659
|
+
raise Errno::ERANGE if res.size > size
|
660
|
+
|
661
|
+
buf.write_bytes(res)
|
662
|
+
res.size
|
663
|
+
end
|
664
|
+
|
665
|
+
# Helper for implementing {FuseOperations#listxattr}
|
666
|
+
# @param [FFI::Pointer] buf
|
667
|
+
# @param [Integer] size
|
668
|
+
# @yieldreturn [Array<String>] a list of extended attribute names
|
669
|
+
def listxattr(buf, size)
|
670
|
+
res = yield
|
671
|
+
res.reduce(0) do |offset, name|
|
672
|
+
name = name.to_s
|
673
|
+
unless size.zero?
|
674
|
+
raise Errno::ERANGE if offset + name.size >= size
|
675
|
+
|
676
|
+
buf.put_string(offset, name) # put string includes the NUL terminator
|
677
|
+
end
|
678
|
+
offset + name.size + 1
|
679
|
+
end
|
680
|
+
end
|
330
681
|
end
|
331
682
|
end
|
332
683
|
end
|