ffi-libfuse 0.0.1.pre → 0.1.0.rc20220550
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 +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
|