ffi-libfuse 0.3.4 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +1 -1
- data/lib/ffi/accessors.rb +21 -7
- data/lib/ffi/boolean_int.rb +1 -1
- data/lib/ffi/devt.rb +3 -3
- data/lib/ffi/libfuse/adapter/debug.rb +53 -15
- data/lib/ffi/libfuse/adapter/fuse2_compat.rb +38 -21
- data/lib/ffi/libfuse/adapter/fuse3_support.rb +0 -1
- data/lib/ffi/libfuse/adapter/ruby.rb +210 -159
- data/lib/ffi/libfuse/adapter/safe.rb +69 -21
- data/lib/ffi/libfuse/callbacks.rb +2 -1
- data/lib/ffi/libfuse/filesystem/accounting.rb +1 -1
- data/lib/ffi/libfuse/filesystem/mapped_files.rb +33 -7
- data/lib/ffi/libfuse/filesystem/pass_through_dir.rb +0 -1
- data/lib/ffi/libfuse/filesystem/virtual_dir.rb +293 -126
- data/lib/ffi/libfuse/filesystem/virtual_file.rb +85 -79
- data/lib/ffi/libfuse/filesystem/virtual_fs.rb +34 -15
- data/lib/ffi/libfuse/filesystem/virtual_link.rb +60 -0
- data/lib/ffi/libfuse/filesystem/virtual_node.rb +104 -87
- data/lib/ffi/libfuse/filesystem.rb +1 -1
- data/lib/ffi/libfuse/fuse2.rb +3 -2
- data/lib/ffi/libfuse/fuse3.rb +1 -1
- data/lib/ffi/libfuse/fuse_args.rb +5 -2
- data/lib/ffi/libfuse/fuse_buf.rb +112 -0
- data/lib/ffi/libfuse/fuse_buf_vec.rb +228 -0
- data/lib/ffi/libfuse/fuse_common.rb +10 -4
- data/lib/ffi/libfuse/fuse_config.rb +16 -7
- data/lib/ffi/libfuse/fuse_operations.rb +86 -41
- data/lib/ffi/libfuse/gem_helper.rb +2 -9
- data/lib/ffi/libfuse/io.rb +56 -0
- data/lib/ffi/libfuse/main.rb +27 -24
- data/lib/ffi/libfuse/test_helper.rb +68 -60
- data/lib/ffi/libfuse/version.rb +1 -1
- data/lib/ffi/libfuse.rb +1 -1
- data/lib/ffi/stat/native.rb +4 -4
- data/lib/ffi/stat.rb +19 -3
- data/lib/ffi/struct_array.rb +2 -1
- data/sample/hello_fs.rb +1 -1
- metadata +6 -3
- data/lib/ffi/libfuse/fuse_buffer.rb +0 -257
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'fuse_version'
|
4
|
+
require_relative '../struct_wrapper'
|
5
|
+
|
6
|
+
module FFI
|
7
|
+
# Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
|
8
|
+
module Libfuse
|
9
|
+
bitmask :fuse_buf_flags, [:is_fd, 1, :fd_seek, :fd_retry]
|
10
|
+
|
11
|
+
#
|
12
|
+
# Single io buffer
|
13
|
+
#
|
14
|
+
# Generic io buffer for I/O, extended attributes, etc...Data may be supplied as a memory pointer or as a file
|
15
|
+
# descriptor
|
16
|
+
#
|
17
|
+
class FuseBuf
|
18
|
+
# @!visibility private
|
19
|
+
# Native FuseBuf layout
|
20
|
+
class Native < FFI::Struct
|
21
|
+
layout(
|
22
|
+
size: :size_t, # Size of io in bytes
|
23
|
+
flags: FFI::Libfuse.find_type(:fuse_buf_flags), # Buffer flags
|
24
|
+
mem: :pointer, # Memory pointer - used if :is_fd flag is not set
|
25
|
+
fd: :int, # File descriptor - used if :is_fd is set
|
26
|
+
pos: :off_t # File position - used if :fd_seek flag is set.
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
include StructWrapper
|
31
|
+
native_struct(Native)
|
32
|
+
|
33
|
+
ffi_attr_reader(:mem, :fd, :pos)
|
34
|
+
ffi_attr_accessor(:size)
|
35
|
+
|
36
|
+
# @!attribute [r] mem
|
37
|
+
# @return [FFI::Pointer] the memory in the buffer
|
38
|
+
|
39
|
+
alias memory mem
|
40
|
+
|
41
|
+
# @!attribute [r] fd
|
42
|
+
# @return [Integer] the file descriptor number
|
43
|
+
|
44
|
+
alias file_descriptor fd
|
45
|
+
|
46
|
+
# @attribute [rw] size
|
47
|
+
# @return [Integer] the size of the buffer
|
48
|
+
|
49
|
+
# @return [Boolean] true if this a memory buffer
|
50
|
+
def mem?
|
51
|
+
!fd?
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [Boolean] true if this is a file descriptor buffer
|
55
|
+
def fd?
|
56
|
+
self[:flags].include?(:is_fd)
|
57
|
+
end
|
58
|
+
alias file_descriptor? fd?
|
59
|
+
|
60
|
+
# @overload fill(str:)
|
61
|
+
# Create a memory buffer from str
|
62
|
+
# @param [String,#to_s] str
|
63
|
+
#
|
64
|
+
# @overload fill(size:)
|
65
|
+
# Allocate an empty memory buffer of size bytes
|
66
|
+
# @param [Integer] size
|
67
|
+
#
|
68
|
+
# @overload fill(mem:, size: mem.size)
|
69
|
+
# Set the buffer to contain the previously allocated memory
|
70
|
+
# @param [FFI::Pointer] mem
|
71
|
+
# @param [Integer] size <= mem.size
|
72
|
+
#
|
73
|
+
# @overload fill(fd:, fd_retry: false, size:, pos: 0)
|
74
|
+
# Fill as a FileDescriptor buffer
|
75
|
+
# @param [Integer] fd File descriptor
|
76
|
+
# @param [Boolean] fd_retry
|
77
|
+
# Retry operations on file descriptor
|
78
|
+
#
|
79
|
+
# If this flag is set then retry operation on file descriptor until size bytes have been copied or an error
|
80
|
+
# or EOF is detected.
|
81
|
+
#
|
82
|
+
# @param [Integer] size
|
83
|
+
# number of bytes to read from fd
|
84
|
+
# @param [nil, Integer] pos
|
85
|
+
# If set then used to seek to the given offset before performing operation on file descriptor.
|
86
|
+
# @return [self]
|
87
|
+
def fill(
|
88
|
+
str: nil,
|
89
|
+
mem: str ? FFI::MemoryPointer.from_string(str.to_s) : FFI::Pointer::NULL, size: mem.null? ? 0 : mem.size,
|
90
|
+
fd: -1, fd_retry: false, pos: nil # rubocop:disable Naming/MethodParameterName
|
91
|
+
|
92
|
+
)
|
93
|
+
|
94
|
+
# Allocate size bytes if we've been given a null pointer
|
95
|
+
mem = FFI::MemoryPointer.new(:char, size, true) if fd == -1 && mem.null? && size.positive?
|
96
|
+
|
97
|
+
mem.autorelease = to_ptr.autorelease? unless mem.null?
|
98
|
+
|
99
|
+
self[:size] = size
|
100
|
+
self[:mem] = mem
|
101
|
+
self[:fd] = fd
|
102
|
+
flags = []
|
103
|
+
flags << :is_fd if fd != -1
|
104
|
+
flags << :fd_seek if pos
|
105
|
+
flags << :fd_retry if fd_retry
|
106
|
+
self[:flags] = flags
|
107
|
+
self[:pos] = pos || 0
|
108
|
+
self
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,228 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'fuse_version'
|
4
|
+
require_relative 'io'
|
5
|
+
require_relative 'fuse_buf'
|
6
|
+
require_relative '../accessors'
|
7
|
+
|
8
|
+
module FFI
|
9
|
+
# Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
|
10
|
+
module Libfuse
|
11
|
+
#
|
12
|
+
# Data buffer vector
|
13
|
+
#
|
14
|
+
# An list of io buffers, each containing a memory pointer or a file descriptor.
|
15
|
+
#
|
16
|
+
class FuseBufVec < FFI::Struct
|
17
|
+
include Accessors
|
18
|
+
|
19
|
+
layout(
|
20
|
+
count: :size_t,
|
21
|
+
idx: :size_t,
|
22
|
+
off: :size_t,
|
23
|
+
# buf is treated as a variable length array of FuseBuf at size +1 following the struct.
|
24
|
+
buf: [FuseBuf::Native, 1] # struct fuse_buf[1]
|
25
|
+
)
|
26
|
+
|
27
|
+
ffi_attr_reader(:count, :idx, :off)
|
28
|
+
|
29
|
+
# @!attribute [r] count
|
30
|
+
# @return [Integer] the number of buffers in the array
|
31
|
+
|
32
|
+
# @!attribute [r] idx
|
33
|
+
# @return [Integer] index of current buffer within the array
|
34
|
+
|
35
|
+
alias index idx
|
36
|
+
|
37
|
+
# @!attribute [r] off
|
38
|
+
# @return [Integer] current offset within the current buffer
|
39
|
+
|
40
|
+
alias offset off
|
41
|
+
|
42
|
+
# Create and initialise from a ruby object that quacks like {::File}, {::IO}, or {::String}
|
43
|
+
#
|
44
|
+
# @param [Object] io
|
45
|
+
#
|
46
|
+
# * Integer file descriptor or File like object that returns one via :fileno
|
47
|
+
# * Otherwise something to pass to {IO.read}(io, size, offset) to create a memory based buffer
|
48
|
+
#
|
49
|
+
# @param [Integer] size
|
50
|
+
# @param [Integer] offset
|
51
|
+
# @return [FuseBufVec]
|
52
|
+
#
|
53
|
+
# @note The returned object's memory is not auto-released, and thus suitable for use with
|
54
|
+
# {FuseOperations#read_buf} where the buffers are cleared by libfuse library..
|
55
|
+
def self.create(io, size, offset = nil)
|
56
|
+
fd = io.respond_to?(:fileno) ? io.fileno : io
|
57
|
+
return init(autorelease: false, size: size, fd: fd, pos: offset || 0) if fd.is_a?(Integer)
|
58
|
+
|
59
|
+
init(autorelease: false, str: Libfuse::IO.read(io, size, offset))
|
60
|
+
end
|
61
|
+
|
62
|
+
# Create and initialise a new FuseBufVec
|
63
|
+
#
|
64
|
+
# @param [Boolean] autorelease should the struct be freed on GC
|
65
|
+
#
|
66
|
+
# Use false only if this object is going to be passed to the C library side. eg. {FuseOperations#read_buf}
|
67
|
+
# @param [Hash<Symbol>] buf_options options for configuring the initial buffer (see {FuseBuf#fill})
|
68
|
+
# @return [FuseBufVec]
|
69
|
+
def self.init(autorelease: true, count: 1, **buf_options)
|
70
|
+
bufvec_ptr = FFI::MemoryPointer.new(:uchar, FuseBufVec.size + (FuseBuf::Native.size * (count - 1)), true)
|
71
|
+
bufvec_ptr.autorelease = autorelease
|
72
|
+
bufvec = new(bufvec_ptr)
|
73
|
+
bufvec[:count] = count
|
74
|
+
bufvec[:idx] = 0
|
75
|
+
bufvec[:off] = 0
|
76
|
+
bufvec.buffers[0].fill(**buf_options) unless buf_options.empty?
|
77
|
+
bufvec
|
78
|
+
end
|
79
|
+
|
80
|
+
# @return [Integer] total size of io in a fuse buffer vector (ie the size of all the fuse buffers)
|
81
|
+
def buf_size
|
82
|
+
Libfuse.fuse_buf_size(self)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Set {index}/{offset} for reading/writing from pos
|
86
|
+
# @param [Integer] pos
|
87
|
+
# @return [self]
|
88
|
+
# @raise [Errno::ERANGE] if seek past end of file
|
89
|
+
def seek(pos)
|
90
|
+
buffers.each_with_index do |b, i|
|
91
|
+
if pos < b.size
|
92
|
+
self[:idx] = i
|
93
|
+
self[:off] = pos
|
94
|
+
return self
|
95
|
+
else
|
96
|
+
pos -= b.size
|
97
|
+
end
|
98
|
+
end
|
99
|
+
raise Errno::ERANGE
|
100
|
+
end
|
101
|
+
|
102
|
+
# Copy data from this set of buffers to another set
|
103
|
+
#
|
104
|
+
# @param [FuseBufVec] dst destination buffers
|
105
|
+
# @param [Array<Symbol>] flags Buffer copy flags
|
106
|
+
#
|
107
|
+
# - :no_splice
|
108
|
+
# Don't use splice(2)
|
109
|
+
#
|
110
|
+
# Always fall back to using read and write instead of splice(2) to copy io from one file descriptor to
|
111
|
+
# another.
|
112
|
+
#
|
113
|
+
# If this flag is not set, then only fall back if splice is unavailable.
|
114
|
+
#
|
115
|
+
# - :force_splice
|
116
|
+
#
|
117
|
+
# Always use splice(2) to copy io from one file descriptor to another. If splice is not available, return
|
118
|
+
# -EINVAL.
|
119
|
+
#
|
120
|
+
# - :splice_move
|
121
|
+
#
|
122
|
+
# Try to move io with splice.
|
123
|
+
#
|
124
|
+
# If splice is used, try to move pages from the source to the destination instead of copying. See
|
125
|
+
# documentation of SPLICE_F_MOVE in splice(2) man page.
|
126
|
+
#
|
127
|
+
# - :splice_nonblock
|
128
|
+
#
|
129
|
+
# Don't block on the pipe when copying io with splice
|
130
|
+
#
|
131
|
+
# Makes the operations on the pipe non-blocking (if the pipe is full or empty). See SPLICE_F_NONBLOCK in
|
132
|
+
# the splice(2) man page.
|
133
|
+
#
|
134
|
+
# @return [Integer] actual number of bytes copied or -errno on error
|
135
|
+
#
|
136
|
+
def copy_to(dst, *flags)
|
137
|
+
Libfuse.fuse_buf_copy(dst, self, flags)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Copy direct to file descriptor
|
141
|
+
# @param [Integer] fileno a file descriptor
|
142
|
+
# @param [nil, Integer] offset if non nil will first seek to offset
|
143
|
+
# @param [Array<Symbol>] flags see {copy_to}
|
144
|
+
# @return [Integer] number of bytes copied
|
145
|
+
def copy_to_fd(fileno, offset = nil, *flags)
|
146
|
+
dst = self.class.init(size: buf_size, fd: fileno, pos: offset)
|
147
|
+
copy_to(dst, *flags)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Copy to string via a temporary buffer
|
151
|
+
# @param [Array<Symbol>] flags see {copy_to}
|
152
|
+
# @return [String] the extracted data
|
153
|
+
def copy_to_str(*flags)
|
154
|
+
dst = self.class.init(size: buf_size)
|
155
|
+
copied = copy_to(dst, *flags)
|
156
|
+
dst.buffers.first.memory.read_string(copied)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Copy from another set of buffers to this one
|
160
|
+
# @param [FuseBufVec] src source buffers
|
161
|
+
# @param [Array<Symbol>] flags
|
162
|
+
# @return [Integer] number of bytes written
|
163
|
+
# @see copy_to
|
164
|
+
def copy_from(src, *flags)
|
165
|
+
Libfuse.fuse_buf_copy(self, src, flags)
|
166
|
+
end
|
167
|
+
|
168
|
+
# Store ourself into a pointer location as received by {FuseOperations#read_buf}
|
169
|
+
# @param [FFI::Pointer<FuseBufVec>] bufp
|
170
|
+
# @return [void]
|
171
|
+
def store_to(bufp)
|
172
|
+
bufp.write_pointer(to_ptr)
|
173
|
+
end
|
174
|
+
|
175
|
+
# Write data from these buffers to another object
|
176
|
+
#
|
177
|
+
# @overload copy_to_io(io, *flags)
|
178
|
+
# @overload copy_to_io(io, offset = nil, *flags)
|
179
|
+
# @param [Object] io one of
|
180
|
+
#
|
181
|
+
# * another {FuseBufVec} via io.{seek}(offset) and {copy_to}(io, *flags)
|
182
|
+
# * an {::Integer} file descriptor to write via {copy_to_fd}(io, offset, *flags)
|
183
|
+
# * a {::File} like object that returns a file descriptor via :fileno used as above
|
184
|
+
# * an {::IO} like object that accepts a string data as {IO.write}(io, {copy_to_str}(*flags), offset)
|
185
|
+
#
|
186
|
+
# @param [nil, Integer] offset position in io to begin writing at, or nil if io is already positioned
|
187
|
+
# @param [Array<Symbol>] flags see {copy_to}
|
188
|
+
#
|
189
|
+
# @return [Integer] number of bytes written
|
190
|
+
# @raise [Errno::EBADF] if io is not a valid target
|
191
|
+
def copy_to_io(io, offset = nil, *flags)
|
192
|
+
if offset.is_a?(Symbol)
|
193
|
+
flags.unshift(offset)
|
194
|
+
offset = nil
|
195
|
+
end
|
196
|
+
|
197
|
+
if io.is_a?(FuseBufVec)
|
198
|
+
io.seek(offset) if offset
|
199
|
+
return copy_to(io, *flags)
|
200
|
+
end
|
201
|
+
|
202
|
+
fd = (io.respond_to?(:fileno) ? io.fileno : io)
|
203
|
+
return copy_to_fd(fd, offset || 0, *flags) if fd.is_a?(Integer)
|
204
|
+
|
205
|
+
Libfuse::IO.write(io, copy_to_str(*flags), offset)
|
206
|
+
end
|
207
|
+
|
208
|
+
# @return [Array<FuseBuf>] list of buffers
|
209
|
+
def buffers
|
210
|
+
@buffers ||= Array.new(count) do |i|
|
211
|
+
native = i.zero? ? self[:buf].first : FuseBuf::Native.new(self[:buf].to_ptr + (i * FuseBuf::Native.size))
|
212
|
+
FuseBuf.new(native)
|
213
|
+
end.freeze
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
bitmask :fuse_buf_copy_flags, [:no_splice, 1, :force_splice, 2, :splice_move, 4, :splice_nonblock, 8]
|
218
|
+
attach_function :fuse_buf_copy, [FuseBufVec.by_ref, FuseBufVec.by_ref, :fuse_buf_copy_flags], :ssize_t
|
219
|
+
attach_function :fuse_buf_size, [FuseBufVec.by_ref], :size_t
|
220
|
+
|
221
|
+
class << self
|
222
|
+
# @!visibility private
|
223
|
+
# @!method fuse_buf_size
|
224
|
+
# @!method fuse_buf_copy
|
225
|
+
# @!method fuse_buf_size
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
@@ -52,11 +52,13 @@ module FFI
|
|
52
52
|
teardown
|
53
53
|
end
|
54
54
|
|
55
|
-
# @api private
|
56
55
|
# @param [Boolean] foreground
|
57
56
|
# @param [Boolean] single_thread
|
58
|
-
# @param [Hash<String,Proc>] traps
|
59
|
-
#
|
57
|
+
# @param [Hash<String,Proc|nil>] traps as per Signal.trap
|
58
|
+
# these are merged over {default_traps} for INT, HUP, TERM that unmount and exit filesystem. A nil
|
59
|
+
# value for these default signals will leave any existing signal handle in place.
|
60
|
+
# @param [Integer] remember fuse cache timeout
|
61
|
+
# @api private
|
60
62
|
# Implement fuse loop in ruby
|
61
63
|
#
|
62
64
|
# Pros:
|
@@ -71,6 +73,7 @@ module FFI
|
|
71
73
|
# * clone_fd is ignored
|
72
74
|
# * filesystem interrupts probably can't work
|
73
75
|
def run_ruby(foreground: true, single_thread: true, traps: {}, remember: false, **options)
|
76
|
+
traps = default_traps.merge(traps).keep_if { |_, v| v }
|
74
77
|
Ackbar.trap(default_traps.merge(traps)) do |signals|
|
75
78
|
daemonize unless foreground
|
76
79
|
|
@@ -112,7 +115,9 @@ module FFI
|
|
112
115
|
end
|
113
116
|
|
114
117
|
# Ruby implementation of fuse default traps
|
115
|
-
#
|
118
|
+
#
|
119
|
+
# * INT, HUP, TERM, TSTP to unmount and exit filesystem
|
120
|
+
# * PIPE is ignored
|
116
121
|
def default_traps
|
117
122
|
exproc = ->(signame) { exit(signame) }
|
118
123
|
@default_traps ||= { INT: exproc, HUP: exproc, TERM: exproc, TSTP: exproc, PIPE: 'IGNORE' }
|
@@ -190,6 +195,7 @@ module FFI
|
|
190
195
|
fuse_process || (sleep(0.1) && false)
|
191
196
|
end
|
192
197
|
|
198
|
+
# @!visibility private
|
193
199
|
def teardown
|
194
200
|
return unless @fuse
|
195
201
|
|
@@ -10,10 +10,12 @@ module FFI
|
|
10
10
|
# This structure is initialized from the arguments passed to fuse_new(), and then passed to the file system's init()
|
11
11
|
# handler which should ensure that the configuration is compatible with the file system implementation.
|
12
12
|
#
|
13
|
+
# Some options can only be set via the filesystem init method (use_ino etc..) because the filesystem either
|
14
|
+
# supports them or it doesn't.
|
13
15
|
class FuseConfig < FFI::Struct
|
14
16
|
include FFI::Accessors
|
15
17
|
|
16
|
-
|
18
|
+
spec =
|
17
19
|
{
|
18
20
|
# @!attribute [r] gid
|
19
21
|
# @return [Integer|nil] if set, this value will be used for the :gid attribute of each file
|
@@ -158,7 +160,7 @@ module FFI
|
|
158
160
|
|
159
161
|
# @!attribute [rw] ac_attr_timeout
|
160
162
|
# if set the timeout in seconds for which file attributes are cached for the purpose of checking if
|
161
|
-
#
|
163
|
+
# auto_cache should flush the file data on open.
|
162
164
|
# @return [Float|nil]
|
163
165
|
ac_attr_timeout_set: :bool_int,
|
164
166
|
ac_attr_timeout: :double,
|
@@ -179,18 +181,25 @@ module FFI
|
|
179
181
|
modules: :pointer,
|
180
182
|
debug: :bool_int
|
181
183
|
}
|
182
|
-
)
|
183
184
|
|
184
|
-
|
185
|
-
|
186
|
-
|
185
|
+
layout(spec)
|
186
|
+
|
187
|
+
# Find the attrs that have a corresponding setter (prefix set_ or suffix _set
|
188
|
+
setters = spec.keys
|
189
|
+
.map { |k| [k, k.to_s.sub(/^set_/, '').sub(/_set$/, '').to_sym] }
|
190
|
+
.reject { |(s, a)| s == a }.to_h
|
191
|
+
|
192
|
+
setters.each do |(setter, attr)|
|
193
|
+
ffi_attr_reader(attr) { |val| self[setter] ? val : nil }
|
194
|
+
|
187
195
|
ffi_attr_writer(attr) do |val|
|
188
196
|
self[setter] = !val.nil?
|
189
197
|
val || 0
|
190
198
|
end
|
191
199
|
end
|
192
200
|
|
193
|
-
|
201
|
+
remaining = (spec.keys - setters.keys - setters.values).map { |a| spec[a] == :bool_int ? "#{a}?" : a }
|
202
|
+
ffi_attr_accessor(*remaining)
|
194
203
|
end
|
195
204
|
end
|
196
205
|
end
|
@@ -1,19 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'fuse_version'
|
4
|
-
require_relative '../ruby_object'
|
5
4
|
require_relative 'fuse_conn_info'
|
6
|
-
require_relative '
|
5
|
+
require_relative 'fuse_config'
|
6
|
+
require_relative 'fuse_buf_vec'
|
7
7
|
require_relative 'fuse_context'
|
8
8
|
require_relative 'fuse_file_info'
|
9
9
|
require_relative 'fuse_poll_handle'
|
10
|
+
require_relative 'fuse_callbacks'
|
11
|
+
require_relative '../ruby_object'
|
10
12
|
require_relative '../stat_vfs'
|
11
13
|
require_relative '../flock'
|
12
|
-
require_relative 'thread_pool'
|
13
14
|
require_relative '../stat'
|
14
|
-
require_relative '../struct_array'
|
15
15
|
require_relative '../encoding'
|
16
|
-
require_relative 'fuse_callbacks'
|
17
16
|
|
18
17
|
module FFI
|
19
18
|
# Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
|
@@ -44,18 +43,80 @@ module FFI
|
|
44
43
|
# All Callback methods are optional, but some are essential for a useful filesystem
|
45
44
|
# e.g. {getattr},{readdir}
|
46
45
|
#
|
47
|
-
# Almost all callback operations take a path which can be of any length and will return 0 for success
|
48
|
-
#
|
46
|
+
# Almost all callback operations take a path which can be of any length and will return 0 for success or a negative
|
47
|
+
# `Errno` value on failure.
|
49
48
|
#
|
50
49
|
class FuseOperations < FFI::Struct
|
51
50
|
include FuseCallbacks
|
52
51
|
|
52
|
+
# Callbacks that have no return value
|
53
|
+
VOID_RETURN = %i[init destroy].freeze
|
54
|
+
|
53
55
|
# Callbacks that are expected to return meaningful positive integers
|
54
56
|
MEANINGFUL_RETURN = %i[read write write_buf lseek copy_file_range getxattr listxattr].freeze
|
55
57
|
|
56
|
-
#
|
57
|
-
|
58
|
-
|
58
|
+
# @!visibility private
|
59
|
+
# Methods to handle the path argument for most {path_callbacks}
|
60
|
+
NODE_PATH_METHODS = %i[first shift unshift].freeze
|
61
|
+
|
62
|
+
# @!visibility private
|
63
|
+
# Methods to handle the path argument for {link}, {symlink} and {rename}
|
64
|
+
LINK_PATH_METHODS = %i[last pop push].freeze
|
65
|
+
|
66
|
+
# @!visibility private
|
67
|
+
CALLBACK_PATH_ARG_METHODS = Hash.new(NODE_PATH_METHODS).merge(
|
68
|
+
{
|
69
|
+
link: LINK_PATH_METHODS,
|
70
|
+
symlink: LINK_PATH_METHODS,
|
71
|
+
rename: LINK_PATH_METHODS
|
72
|
+
}
|
73
|
+
).freeze
|
74
|
+
|
75
|
+
class << self
|
76
|
+
# @return [Boolean] true if fuse_callback expects a meaningful integer return
|
77
|
+
def meaningful_return?(fuse_callback)
|
78
|
+
MEANINGFUL_RETURN.include?(fuse_callback)
|
79
|
+
end
|
80
|
+
|
81
|
+
# @return [Boolean] true if fuse_callback expects a void return
|
82
|
+
def void_return?(fuse_callback)
|
83
|
+
VOID_RETURN.include?(fuse_callback)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Helper to determine how to handle the path argument for a path callback
|
87
|
+
# @param [Symbol] fuse_callback callback method name (must be one of #{path_callbacks})
|
88
|
+
# @return [Symbol, Symbol,Symbol] read, remove, add methods.
|
89
|
+
# [:last, :push, :pop] for :link, :symlink, :rename,
|
90
|
+
# [:first, :shift, :unshift] for everything else
|
91
|
+
# @example
|
92
|
+
# def wrap_callback(fuse_method, *args)
|
93
|
+
# read, remove, add = FFI::Libfuse::FuseOperations.path_arg_methods(fuse_method)
|
94
|
+
# path = args.send(read)
|
95
|
+
# # ... do something with path
|
96
|
+
#
|
97
|
+
# path = args.send(remove)
|
98
|
+
# # ... do something to make an alternate path
|
99
|
+
# args.send(add, adjusted_path)
|
100
|
+
# delegate.send(fuse_methoed, *args)
|
101
|
+
# end
|
102
|
+
def path_arg_methods(fuse_callback)
|
103
|
+
CALLBACK_PATH_ARG_METHODS[fuse_callback]
|
104
|
+
end
|
105
|
+
|
106
|
+
# @return [Set<Symbol>] list of callback methods
|
107
|
+
def fuse_callbacks
|
108
|
+
@fuse_callbacks ||= Set.new(members - [:flags])
|
109
|
+
end
|
110
|
+
|
111
|
+
# @return [Set<Symbol>] list of path callback methods
|
112
|
+
def path_callbacks
|
113
|
+
@path_callbacks ||= fuse_callbacks - VOID_RETURN
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# @!visibility private
|
118
|
+
def fuse_callbacks
|
119
|
+
self.class.fuse_callbacks
|
59
120
|
end
|
60
121
|
|
61
122
|
# Container to dynamically build up the operations layout which is dependent on the loaded libfuse version
|
@@ -151,12 +212,11 @@ module FFI
|
|
151
212
|
# int (*rmdir) (const char *);
|
152
213
|
op[:rmdir] = []
|
153
214
|
|
154
|
-
# @!method symlink(path
|
215
|
+
# @!method symlink(target, path)
|
155
216
|
# @abstract
|
156
217
|
# Create a symbolic link
|
218
|
+
# @param [String] target
|
157
219
|
# @param [String] path
|
158
|
-
# @param [String] target the link target
|
159
|
-
#
|
160
220
|
# @return [Integer] 0 for success or -ve errno
|
161
221
|
|
162
222
|
# int (*symlink) (const char *, const char *);
|
@@ -173,13 +233,13 @@ module FFI
|
|
173
233
|
# int (*rename) (const char *, const char *);
|
174
234
|
op[:rename] = [:fs_string]
|
175
235
|
|
176
|
-
# @!method link(path
|
236
|
+
# @!method link(target, path)
|
177
237
|
# @abstract
|
178
238
|
# Create a hard link to a file
|
179
|
-
# @param [String] path
|
180
239
|
# @param [String] target
|
181
|
-
#
|
240
|
+
# @param [String] path
|
182
241
|
# @return [Integer] 0 for success or -ve errno
|
242
|
+
# @see rename(2)
|
183
243
|
|
184
244
|
# int (*link) (const char *, const char *);
|
185
245
|
op[:link] = [:fs_string]
|
@@ -486,7 +546,6 @@ module FFI
|
|
486
546
|
# fuse3: void *(*init) (struct fuse_conn_info *conn, struct fuse_config *cfg);
|
487
547
|
op[:init] =
|
488
548
|
if FUSE_MAJOR_VERSION >= 3
|
489
|
-
require_relative 'fuse_config'
|
490
549
|
callback([FuseConnInfo.by_ref, FuseConfig.by_ref], RubyObject)
|
491
550
|
else
|
492
551
|
callback([FuseConnInfo.by_ref], RubyObject)
|
@@ -618,7 +677,7 @@ module FFI
|
|
618
677
|
#
|
619
678
|
|
620
679
|
# int (*utimens) (const char *, const struct timespec tv[2]);
|
621
|
-
op[:utimens] = [FFI::Stat::TimeSpec
|
680
|
+
op[:utimens] = [FFI::Stat::TimeSpec[2]]
|
622
681
|
op[:utimens] << FuseFileInfo.by_ref if FUSE_MAJOR_VERSION >= 3
|
623
682
|
|
624
683
|
# @!method bmap(path,blocksize,index)
|
@@ -655,17 +714,19 @@ module FFI
|
|
655
714
|
# release, fsync, readdir, releasedir, fsyncdir, ftruncate, fgetattr, lock, ioctl and poll
|
656
715
|
#
|
657
716
|
# Closely related to flag_nullpath_ok, but if this flag is set then the path will not be calculaged even if
|
658
|
-
# the file
|
659
|
-
# reason.
|
717
|
+
# the file wasn't unlinked. However the path can still be non-NULL if it needs to be calculated for some
|
718
|
+
# other reason.
|
660
719
|
#
|
661
720
|
# - :utime_omit_ok
|
662
721
|
#
|
663
722
|
# Flag indicating that the filesystem accepts special UTIME_NOW and UTIME_OMIT values in its utimens
|
664
723
|
# operation.
|
665
724
|
#
|
666
|
-
# @return [Array
|
725
|
+
# @return [Array<Symbol>] a list of flags to set capabilities
|
667
726
|
# @note Not available in Fuse3
|
668
727
|
# @deprecated in Fuse3 use fuse_config object in {init}
|
728
|
+
|
729
|
+
# flags
|
669
730
|
op[:flags] = :flags_mask if FUSE_MAJOR_VERSION < 3
|
670
731
|
|
671
732
|
if FUSE_VERSION >= 28
|
@@ -717,7 +778,7 @@ module FFI
|
|
717
778
|
# @abstract
|
718
779
|
# Write contents of buffer to an open file
|
719
780
|
#
|
720
|
-
# Similar to the write
|
781
|
+
# Similar to the {write} method, but data is supplied in a generic buffer.
|
721
782
|
# Use {FuseBufVec#copy_to_fd} to copy data to an open file descriptor, or {FuseBufVec#copy_to_str} to extract
|
722
783
|
# string data from the buffer
|
723
784
|
#
|
@@ -734,16 +795,15 @@ module FFI
|
|
734
795
|
# @!method read_buf(path,bufp,size,offset,fuse_file_info)
|
735
796
|
# @abstract
|
736
797
|
#
|
737
|
-
# Similar to the read
|
798
|
+
# Similar to the {read} method, but data is stored and returned in a generic buffer.
|
738
799
|
#
|
739
800
|
# No actual copying of data has to take place, the source file descriptor may simply be stored in the buffer
|
740
801
|
# for later data transfer.
|
741
802
|
#
|
742
803
|
# @param [String] path
|
743
804
|
# @param [FFI::Pointer<FuseBufVec>] bufp
|
744
|
-
# The buffer must be allocated dynamically
|
745
|
-
#
|
746
|
-
# the caller.
|
805
|
+
# The buffer must be allocated dynamically ({FuseBufVec.init})
|
806
|
+
# and stored at the location pointed to by bufp (see {FuseBufVec#store_to}(bufp)).
|
747
807
|
# @param [Integer] size
|
748
808
|
# @param [Integer] offset
|
749
809
|
# @param [FuseFileInfo] fuse_file_info
|
@@ -886,21 +946,6 @@ module FFI
|
|
886
946
|
fuse_flags.concat(delegate.fuse_flags) if delegate.respond_to?(:fuse_flags)
|
887
947
|
send(:[]=, :flags, fuse_flags.uniq)
|
888
948
|
end
|
889
|
-
|
890
|
-
# @!visibility private
|
891
|
-
def fuse_callbacks
|
892
|
-
self.class.fuse_callbacks
|
893
|
-
end
|
894
|
-
|
895
|
-
# @return [Set<Symbol>] list of callback methods
|
896
|
-
def self.fuse_callbacks
|
897
|
-
@fuse_callbacks ||= Set.new(members - [:flags])
|
898
|
-
end
|
899
|
-
|
900
|
-
# @return [Set<Symbol>] list of path callback methods
|
901
|
-
def self.path_callbacks
|
902
|
-
@path_callbacks ||= fuse_callbacks - %i[init destroy]
|
903
|
-
end
|
904
949
|
end
|
905
950
|
end
|
906
951
|
end
|