ffi-libfuse 0.3.4 → 0.4.0
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/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
|