ffi-libfuse 0.0.1.pre
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 +7 -0
- data/.yardopts +1 -0
- data/README.md +100 -0
- data/lib/ffi/accessors.rb +145 -0
- data/lib/ffi/devt.rb +30 -0
- data/lib/ffi/flock.rb +47 -0
- data/lib/ffi/gnu_extensions.rb +115 -0
- data/lib/ffi/libfuse/ackbar.rb +112 -0
- data/lib/ffi/libfuse/adapter/context.rb +37 -0
- data/lib/ffi/libfuse/adapter/debug.rb +89 -0
- data/lib/ffi/libfuse/adapter/fuse2_compat.rb +91 -0
- data/lib/ffi/libfuse/adapter/fuse3_support.rb +87 -0
- data/lib/ffi/libfuse/adapter/interrupt.rb +37 -0
- data/lib/ffi/libfuse/adapter/pathname.rb +23 -0
- data/lib/ffi/libfuse/adapter/ruby.rb +334 -0
- data/lib/ffi/libfuse/adapter/safe.rb +58 -0
- data/lib/ffi/libfuse/adapter/thread_local_context.rb +36 -0
- data/lib/ffi/libfuse/adapter.rb +79 -0
- data/lib/ffi/libfuse/callbacks.rb +61 -0
- data/lib/ffi/libfuse/fuse2.rb +159 -0
- data/lib/ffi/libfuse/fuse3.rb +162 -0
- data/lib/ffi/libfuse/fuse_args.rb +166 -0
- data/lib/ffi/libfuse/fuse_buffer.rb +155 -0
- data/lib/ffi/libfuse/fuse_callbacks.rb +48 -0
- data/lib/ffi/libfuse/fuse_cmdline_opts.rb +44 -0
- data/lib/ffi/libfuse/fuse_common.rb +249 -0
- data/lib/ffi/libfuse/fuse_config.rb +205 -0
- data/lib/ffi/libfuse/fuse_conn_info.rb +211 -0
- data/lib/ffi/libfuse/fuse_context.rb +79 -0
- data/lib/ffi/libfuse/fuse_file_info.rb +100 -0
- data/lib/ffi/libfuse/fuse_loop_config.rb +43 -0
- data/lib/ffi/libfuse/fuse_operations.rb +870 -0
- data/lib/ffi/libfuse/fuse_opt.rb +54 -0
- data/lib/ffi/libfuse/fuse_poll_handle.rb +59 -0
- data/lib/ffi/libfuse/fuse_version.rb +43 -0
- data/lib/ffi/libfuse/job_pool.rb +53 -0
- data/lib/ffi/libfuse/main.rb +200 -0
- data/lib/ffi/libfuse/test/operations.rb +56 -0
- data/lib/ffi/libfuse/test.rb +3 -0
- data/lib/ffi/libfuse/thread_pool.rb +147 -0
- data/lib/ffi/libfuse/version.rb +8 -0
- data/lib/ffi/libfuse.rb +24 -0
- data/lib/ffi/ruby_object.rb +95 -0
- data/lib/ffi/stat/constants.rb +29 -0
- data/lib/ffi/stat/native.rb +50 -0
- data/lib/ffi/stat/time_spec.rb +137 -0
- data/lib/ffi/stat.rb +96 -0
- data/lib/ffi/stat_vfs.rb +81 -0
- data/lib/ffi/struct_array.rb +39 -0
- data/lib/ffi/struct_wrapper.rb +100 -0
- data/sample/memory_fs.rb +189 -0
- data/sample/no_fs.rb +69 -0
- metadata +165 -0
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'fuse_version'
|
4
|
+
require_relative 'fuse_opt'
|
5
|
+
require_relative '../ruby_object'
|
6
|
+
|
7
|
+
module FFI
|
8
|
+
# Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
|
9
|
+
module Libfuse
|
10
|
+
# struct fuse_args
|
11
|
+
class FuseArgs < FFI::Struct
|
12
|
+
layout :argc, :int, :argv, :pointer, :allocated, :int
|
13
|
+
|
14
|
+
# Create an fuse_args struct from command line options
|
15
|
+
# @param [Array<String>] argv command line args
|
16
|
+
# args[0] is expected to be program name
|
17
|
+
# @return [FuseArgs]
|
18
|
+
def self.create(*argv)
|
19
|
+
new.fill(*argv)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @!visibility private
|
23
|
+
# noinspection RubyResolve
|
24
|
+
def fill(*argv)
|
25
|
+
# Keep the args allocated while in scope
|
26
|
+
@arg_vector = FFI::MemoryPointer.new(:pointer, argv.size + 1)
|
27
|
+
@argv_strings = argv.map { |k| FFI::MemoryPointer.from_string(k.to_s) }
|
28
|
+
@arg_vector.write_array_of_pointer(@argv_strings)
|
29
|
+
@arg_vector[argv.size].put_pointer(0, FFI::Pointer::NULL)
|
30
|
+
self[:argv] = @arg_vector
|
31
|
+
self[:argc] = argv.size
|
32
|
+
self[:allocated] = 0
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
# @!attribute [r] argc
|
37
|
+
# @return [Integer] count of args
|
38
|
+
def argc
|
39
|
+
self[:argc]
|
40
|
+
end
|
41
|
+
|
42
|
+
# @!attribute [r] argv
|
43
|
+
# @return [Array<String>]
|
44
|
+
def argv
|
45
|
+
# noinspection RubyResolve
|
46
|
+
self[:argv].get_array_of_pointer(0, argc).map(&:read_string)
|
47
|
+
end
|
48
|
+
|
49
|
+
# @!visibility private
|
50
|
+
def allocated
|
51
|
+
self[:allocated]
|
52
|
+
end
|
53
|
+
|
54
|
+
# @!visibility private
|
55
|
+
def inspect
|
56
|
+
"#{self.class.name} - #{%i[argc argv allocated].map { |m| [m, send(m)] }.to_h}"
|
57
|
+
end
|
58
|
+
|
59
|
+
# Add an arg to this arg list
|
60
|
+
# @param [String] arg
|
61
|
+
def add(arg)
|
62
|
+
Libfuse.fuse_opt_add_arg(self, arg)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Insert arg at pos in this struct via fuse_opt_insert_arg
|
66
|
+
# @param [Integer] pos index to insert arg at
|
67
|
+
# @param [String] arg
|
68
|
+
def insert(pos, arg)
|
69
|
+
Libfuse.fuse_opt_insert_arg(self, pos, arg)
|
70
|
+
end
|
71
|
+
|
72
|
+
#
|
73
|
+
# Option parsing function
|
74
|
+
#
|
75
|
+
# @param [Hash<String,Symbol>] opts option schema
|
76
|
+
#
|
77
|
+
# hash keys are a String template to match arguments against
|
78
|
+
#
|
79
|
+
# 1. "-x", "-foo", "--foo", "--foo-bar", etc. These match only themselves. Invalid values are "--" and anything
|
80
|
+
# beginning with "-o"
|
81
|
+
# 2. "foo", "foo-bar", etc. These match "-ofoo", "-ofoo-bar" or the relevant option in a comma separated option
|
82
|
+
# list
|
83
|
+
# 3. "bar=", "--foo=", etc. These are variations of 1) and 2) which have a parameter
|
84
|
+
# 4. '%' Formats Not Supported (or needed for Ruby!)
|
85
|
+
# 5. "-x ", etc. Matches either "-xparam" or "-x param" as two separate arguments
|
86
|
+
# 6. '%' Formats Not Supported
|
87
|
+
#
|
88
|
+
# hash values are the Symbol sent to the block for a matching argument
|
89
|
+
#
|
90
|
+
# - :keep Argument is not passed to block, but behave as if the block returns :keep
|
91
|
+
# - :discard Argument is not passed to block, but behave as if the block returns :discard
|
92
|
+
# - any other value is yielded as 'key' property on matching argument
|
93
|
+
#
|
94
|
+
# @param [Object] data an optional object that will be passed thru to the block
|
95
|
+
#
|
96
|
+
# @yieldparam [Object] data
|
97
|
+
# @yieldparam [String] arg is the whole argument or option including the parameter if exists.
|
98
|
+
#
|
99
|
+
# A two-argument option ("-x foo") is always converted to single argument option of the form "-xfoo" before this
|
100
|
+
# function is called.
|
101
|
+
#
|
102
|
+
# Options of the form '-ofoo' are yielded without the '-o' prefix.
|
103
|
+
#
|
104
|
+
# @yieldparam [Symbol] key determines why the processing function was called
|
105
|
+
#
|
106
|
+
# - :unmatched for arguments that *do not match* any supplied option
|
107
|
+
# - :non_option for non-option arguments (after -- or not beginning with -)
|
108
|
+
# - with appropriate value from opts hash for a matching argument
|
109
|
+
#
|
110
|
+
# @yieldparam [FuseArgs] outargs can {add} or {insert} additional args as required
|
111
|
+
#
|
112
|
+
# eg. if one arg implies another
|
113
|
+
#
|
114
|
+
# @yieldreturn [Symbol] the argument action
|
115
|
+
#
|
116
|
+
# - :error an error
|
117
|
+
# - :keep the current argument (to pass on further)
|
118
|
+
# - :handled,:discard success and discard the current argument (ie because it has been handled)
|
119
|
+
#
|
120
|
+
# @return [nil|FuseArgs] nil on error, self on success
|
121
|
+
def parse!(opts, data = nil, &block)
|
122
|
+
# turn option value symbols into integers including special negative values from fuse_opt.h
|
123
|
+
symbols = opts.values.uniq + %i[discard keep non_option unmatched]
|
124
|
+
|
125
|
+
int_opts = opts.transform_values do |v|
|
126
|
+
%i[discard keep].include?(v) ? symbols.rindex(v) - symbols.size : symbols.index(v)
|
127
|
+
end
|
128
|
+
|
129
|
+
fop = proc { |d, arg, key, outargs| fuse_opt_proc(d, arg, symbols[key], outargs, &block) }
|
130
|
+
result = Libfuse.fuse_opt_parse(self, data, int_opts, fop)
|
131
|
+
|
132
|
+
result.zero? ? self : nil
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
# Valid return values from parse! block
|
138
|
+
FUSE_OPT_PROC_RETURN = { error: -1, keep: 1, handled: 0, discard: 0 }.freeze
|
139
|
+
|
140
|
+
def fuse_opt_proc(data, arg, key, out, &block)
|
141
|
+
res = block.call(data, arg, key, out)
|
142
|
+
res.is_a?(Integer) ? res : FUSE_OPT_PROC_RETURN.fetch(res)
|
143
|
+
rescue KeyError => e
|
144
|
+
warn "FuseOptProc error - Unknown result #{e.key}"
|
145
|
+
-1
|
146
|
+
rescue StandardError => e
|
147
|
+
warn "FuseOptProc error - #{e.class.name}:#{e.message}"
|
148
|
+
-1
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs);
|
153
|
+
callback :fuse_opt_proc_t, [RubyObject, :string, :int, FuseArgs.by_ref], :int
|
154
|
+
|
155
|
+
attach_function :fuse_opt_parse, [FuseArgs.by_ref, RubyObject, FuseOpt::OptList, :fuse_opt_proc_t], :int
|
156
|
+
attach_function :fuse_opt_add_arg, [FuseArgs.by_ref, :string], :int
|
157
|
+
attach_function :fuse_opt_insert_arg, [FuseArgs.by_ref, :int, :string], :int
|
158
|
+
|
159
|
+
class << self
|
160
|
+
# @!visibility private
|
161
|
+
# @!method fuse_opt_parse
|
162
|
+
# @!method fuse_opt_add_arg
|
163
|
+
# @!method fuse_opt_insert_arg
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'fuse_version'
|
4
|
+
|
5
|
+
module FFI
|
6
|
+
# Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
|
7
|
+
module Libfuse
|
8
|
+
bitmask :fuse_buf_flags, [:is_fd, 1, :fd_seek, :fd_retry]
|
9
|
+
bitmask :fuse_buf_copy_flags, [:no_splice, 1, :force_splice, 2, :splice_move, 4, :splice_nonblock, 8]
|
10
|
+
|
11
|
+
#
|
12
|
+
# Single data buffer
|
13
|
+
#
|
14
|
+
# Generic data buffer for I/O, extended attributes, etc...Data may be supplied as a memory pointer or as a file
|
15
|
+
# descriptor
|
16
|
+
#
|
17
|
+
# @todo define helper methods to create buffers pointing to file_descriptors or allocated memory
|
18
|
+
class FuseBuf < FFI::Struct
|
19
|
+
layout(
|
20
|
+
size: :size_t, # Size of data in bytes
|
21
|
+
flags: :fuse_buf_flags, # Buffer flags
|
22
|
+
mem: :pointer, # Memory pointer - used if :is_fd flag is not set
|
23
|
+
fd: :int, # File descriptor - used if :is_fd is set
|
24
|
+
pos: :off_t # File position - used if :fd_seek flag is set.
|
25
|
+
)
|
26
|
+
|
27
|
+
# rubocop:disable Naming/MethodParameterName
|
28
|
+
|
29
|
+
# @param [Integer] size Size of data in bytes
|
30
|
+
# @param [Integer] fd File descriptor
|
31
|
+
# @param [FFI::Pointer] mem Memory pointer
|
32
|
+
# @param [Boolean] fd_retry
|
33
|
+
# Retry operation on file descriptor
|
34
|
+
#
|
35
|
+
# If this flag is set then retry operation on file descriptor until .size bytes have been copied or an error or
|
36
|
+
# EOF is detected.
|
37
|
+
#
|
38
|
+
# @param [Integer] pos
|
39
|
+
# If > 0 then used to seek to the given offset before performing operation on file descriptor.
|
40
|
+
# @return [self]
|
41
|
+
def fill(size:, mem: FFI::Pointer::NULL, fd: -1, fd_retry: false, pos: 0)
|
42
|
+
self[:size] = size
|
43
|
+
self[:mem] = mem
|
44
|
+
self[:fd] = fd
|
45
|
+
flags = []
|
46
|
+
flags << :is_fd if fd != -1
|
47
|
+
flags << :fd_seek if pos.positive?
|
48
|
+
flags << :fd_retry if fd_retry
|
49
|
+
self[:flags] = flags
|
50
|
+
self[:pos] = pos
|
51
|
+
self
|
52
|
+
end
|
53
|
+
end
|
54
|
+
# rubocop:enable Naming/MethodParameterName
|
55
|
+
|
56
|
+
#
|
57
|
+
# Data buffer vector
|
58
|
+
#
|
59
|
+
# An array of data buffers, each containing a memory pointer or a file descriptor.
|
60
|
+
#
|
61
|
+
# Allocate dynamically to add more than one buffer.
|
62
|
+
#
|
63
|
+
# @todo find a use for {FuseOperations#read_buf} and implement necessary helpers
|
64
|
+
class FuseBufVec < FFI::Struct
|
65
|
+
layout(
|
66
|
+
count: :size_t,
|
67
|
+
idx: :size_t,
|
68
|
+
off: :size_t,
|
69
|
+
buf: :pointer
|
70
|
+
)
|
71
|
+
# @!attribute [r] count
|
72
|
+
# @todo implement
|
73
|
+
# @return [Integer] the number of buffers in the array
|
74
|
+
|
75
|
+
# @!attribute [r] index
|
76
|
+
# @todo implement
|
77
|
+
# @return [Integer] index of current buffer within the array
|
78
|
+
|
79
|
+
# @!attribute [r] offset
|
80
|
+
# @todo implement
|
81
|
+
# @return [Integer] current offset within the current buffer
|
82
|
+
|
83
|
+
# @!attribute [r] buffers
|
84
|
+
# @todo implement
|
85
|
+
# @return [Array<FuseBuf>] array of buffers
|
86
|
+
|
87
|
+
# @see #init
|
88
|
+
def self.init(**buf_options)
|
89
|
+
new.init(**buf_options)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Allocate a vector containing a single buffer
|
93
|
+
#
|
94
|
+
# See fuse_common.h FUSE_BUFVEC_INIT macro
|
95
|
+
# @param [Hash<Symbol,Object>] buf_options see {FuseBuf.fill}
|
96
|
+
def init(**buf_options)
|
97
|
+
self[:count] = 1
|
98
|
+
self[:idx] = 0
|
99
|
+
self[:off] = 0
|
100
|
+
self[:buf] = FuseBuf.new.fill(**buf_options), to_ptr
|
101
|
+
self
|
102
|
+
end
|
103
|
+
|
104
|
+
# @return [Integer] total size of data in a fuse buffer vector
|
105
|
+
def buf_size
|
106
|
+
Libfuse.fuse_buf_size(self)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Copy data from one buffer vector to another
|
110
|
+
# @param [FuseBufVec] dst Destination buffer vector
|
111
|
+
# @param [Array<Symbol>] flags Buffer copy flags
|
112
|
+
# - :no_splice
|
113
|
+
# Don't use splice(2)
|
114
|
+
#
|
115
|
+
# Always fall back to using read and write instead of splice(2) to copy data from one file descriptor to
|
116
|
+
# another.
|
117
|
+
#
|
118
|
+
# If this flag is not set, then only fall back if splice is unavailable.
|
119
|
+
#
|
120
|
+
# - :force_splice
|
121
|
+
#
|
122
|
+
# Always use splice(2) to copy data from one file descriptor to another. If splice is not available, return
|
123
|
+
# -EINVAL.
|
124
|
+
#
|
125
|
+
# - :splice_move
|
126
|
+
#
|
127
|
+
# Try to move data with splice.
|
128
|
+
#
|
129
|
+
# If splice is used, try to move pages from the source to the destination instead of copying. See
|
130
|
+
# documentation of SPLICE_F_MOVE in splice(2) man page.
|
131
|
+
#
|
132
|
+
# - :splice_nonblock
|
133
|
+
#
|
134
|
+
# Don't block on the pipe when copying data with splice
|
135
|
+
#
|
136
|
+
# Makes the operations on the pipe non-blocking (if the pipe is full or empty). See SPLICE_F_NONBLOCK in
|
137
|
+
# the splice(2) man page.
|
138
|
+
#
|
139
|
+
# @return [Integer] actual number of bytes copied or -errno on error
|
140
|
+
#
|
141
|
+
def copy_to(dst, *flags)
|
142
|
+
Libfuse.fuse_buf_copy(dst, self, flags)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
attach_function :fuse_buf_size, [FuseBufVec.by_ref], :size_t
|
147
|
+
attach_function :fuse_buf_copy, [FuseBufVec.by_ref, FuseBufVec.by_ref, :fuse_buf_copy_flags], :ssize_t
|
148
|
+
|
149
|
+
class << self
|
150
|
+
# @!visibility private
|
151
|
+
# @!method fuse_buf_size
|
152
|
+
# @!method fuse_buf_copy
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'callbacks'
|
4
|
+
|
5
|
+
module FFI
|
6
|
+
module Libfuse
|
7
|
+
# Methods to register callbacks and wrappers
|
8
|
+
module FuseCallbacks
|
9
|
+
include Callbacks
|
10
|
+
|
11
|
+
# @!group Configuration
|
12
|
+
|
13
|
+
# @!method fuse_wrappers(*wrappers)
|
14
|
+
# @abstract
|
15
|
+
# Wrappers change the behaviour/signature of the abstract fuse callback methods
|
16
|
+
#
|
17
|
+
# @param [Array] wrappers
|
18
|
+
# An initial list of wrappers
|
19
|
+
# @return [Array] the final list of wrappers.
|
20
|
+
# Implementations should append or prepend to the input wrappers as appropriate
|
21
|
+
#
|
22
|
+
# See {register} for what constitutes a valid wrapper
|
23
|
+
|
24
|
+
# @!method fuse_respond_to?(fuse_method)
|
25
|
+
# @abstract
|
26
|
+
# @param [Symbol] fuse_method a fuse callback method
|
27
|
+
# @return [Boolean] true if the fuse method should be registered
|
28
|
+
|
29
|
+
# @!endgroup
|
30
|
+
private
|
31
|
+
|
32
|
+
def initialize_callbacks(delegate:, wrappers: [])
|
33
|
+
wrappers = delegate.fuse_wrappers(*wrappers) if delegate.respond_to?(:fuse_wrappers)
|
34
|
+
super(callback_members, delegate: delegate, wrappers: wrappers)
|
35
|
+
end
|
36
|
+
|
37
|
+
def respond_to_callback?(method, delegate)
|
38
|
+
return delegate.fuse_respond_to?(method) if delegate.respond_to?(:fuse_respond_to?)
|
39
|
+
|
40
|
+
super
|
41
|
+
end
|
42
|
+
|
43
|
+
def callback_members
|
44
|
+
members - [:flags]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../accessors'
|
4
|
+
require_relative 'fuse_loop_config'
|
5
|
+
module FFI
|
6
|
+
module Libfuse
|
7
|
+
#
|
8
|
+
# struct fuse_cmdline_opts {
|
9
|
+
# int singlethread;
|
10
|
+
# int foreground;
|
11
|
+
# int debug;
|
12
|
+
# int nodefault_subtype;
|
13
|
+
# char *mountpoint;
|
14
|
+
# int show_version;
|
15
|
+
# int show_help;
|
16
|
+
# int clone_fd;
|
17
|
+
# unsigned int max_idle_threads;
|
18
|
+
# };
|
19
|
+
# @!visibility private
|
20
|
+
class FuseCmdlineOpts < FFI::Struct
|
21
|
+
include(FFI::Accessors)
|
22
|
+
|
23
|
+
layout(
|
24
|
+
single_thread: :int,
|
25
|
+
foreground: :int,
|
26
|
+
debug: :int,
|
27
|
+
nodefault_subtype: :int,
|
28
|
+
mountpoint: :string,
|
29
|
+
show_version: :int,
|
30
|
+
show_help: :int,
|
31
|
+
clone_fd: :int,
|
32
|
+
max_idle_threads: :int
|
33
|
+
)
|
34
|
+
|
35
|
+
# int to booleans
|
36
|
+
ffi_attr_reader(:single_thread, :foreground, :debug, :nodefault_subtype, :show_version, :show_help,
|
37
|
+
:clone_fd) do |v|
|
38
|
+
v != 0
|
39
|
+
end
|
40
|
+
|
41
|
+
ffi_attr_reader(:max_idle_threads, :mountpoint)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,249 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'fuse_version'
|
4
|
+
require_relative 'thread_pool'
|
5
|
+
require_relative 'ackbar'
|
6
|
+
|
7
|
+
module FFI
|
8
|
+
# Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
|
9
|
+
module Libfuse
|
10
|
+
typedef :pointer, :fuse
|
11
|
+
typedef :pointer, :session
|
12
|
+
|
13
|
+
attach_function :fuse_get_session, [:fuse], :session
|
14
|
+
attach_function :fuse_set_signal_handlers, [:session], :int
|
15
|
+
attach_function :fuse_remove_signal_handlers, [:session], :void
|
16
|
+
attach_function :fuse_loop, [:fuse], :int, blocking: false
|
17
|
+
attach_function :fuse_clean_cache, [:fuse], :int
|
18
|
+
attach_function :fuse_exit, [:fuse], :void
|
19
|
+
attach_function :fuse_destroy, [:fuse], :void
|
20
|
+
attach_function :fuse_daemonize, [:int], :int
|
21
|
+
|
22
|
+
class << self
|
23
|
+
# @!visibility private
|
24
|
+
# @!method fuse_get_session(fuse)
|
25
|
+
# @!method fuse_set_signal_handlers(session)
|
26
|
+
# @!method fuse_remove_signal_handlers(session)
|
27
|
+
# @!method fuse_loop(fuse)
|
28
|
+
# @!method fuse_clean_cache(fuse)
|
29
|
+
# @!method fuse_exit(fuse)
|
30
|
+
# @!method fuse_destroy(fuse) void
|
31
|
+
# @!method fuse_daemonize(foreground) int
|
32
|
+
end
|
33
|
+
|
34
|
+
# @abstract Base class for Fuse, which itself is just a constant pointing to Fuse2 or Fuse3
|
35
|
+
# @note This class documents the Ruby re-implementation of native fuse functions. It will not generally be used
|
36
|
+
# directly.
|
37
|
+
class FuseCommon
|
38
|
+
# Run the mounted filesystem until exit
|
39
|
+
# @param [Boolean] native should we run the C native fuse_loop functions or the ruby implementation
|
40
|
+
# @param [Hash<Symbol,Object>] options passed to {run_native} or {run_ruby}
|
41
|
+
# @return [Integer] an exit code for the fuse process (0 for success)
|
42
|
+
def run(native: false, **options)
|
43
|
+
return false unless mounted?
|
44
|
+
|
45
|
+
if native
|
46
|
+
run_native(**options)
|
47
|
+
else
|
48
|
+
run_ruby(**options)
|
49
|
+
end
|
50
|
+
rescue Errno => e
|
51
|
+
-e.errno
|
52
|
+
rescue StandardError => e
|
53
|
+
warn e
|
54
|
+
warn e.backtrace.join("\n")
|
55
|
+
-1
|
56
|
+
ensure
|
57
|
+
teardown
|
58
|
+
end
|
59
|
+
|
60
|
+
# @api private
|
61
|
+
# @param [Boolean] foreground
|
62
|
+
# @param [Boolean] single_thread
|
63
|
+
# @param [Hash<String,Proc>] traps see {Ackbar.trap}
|
64
|
+
#
|
65
|
+
# Implement fuse loop in ruby
|
66
|
+
#
|
67
|
+
# Pros:
|
68
|
+
#
|
69
|
+
# * multi-threading works
|
70
|
+
# * can set max_threads @see https://github.com/libfuse/libfuse/issues/203
|
71
|
+
# * can send signals to the FS (eg to reload)
|
72
|
+
# * daemonize works
|
73
|
+
#
|
74
|
+
# Cons:
|
75
|
+
#
|
76
|
+
# * clone_fd is ignored
|
77
|
+
# * filesystem interrupts probably can't work
|
78
|
+
def run_ruby(foreground: true, single_thread: true, traps: {}, **options)
|
79
|
+
Ackbar.trap(default_traps.merge(traps)) do |signals|
|
80
|
+
daemonize unless foreground
|
81
|
+
|
82
|
+
if single_thread
|
83
|
+
fuse_loop(signals: signals, **options)
|
84
|
+
else
|
85
|
+
fuse_loop_mt(signals: signals, **options)
|
86
|
+
end
|
87
|
+
0
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Running fuse loop natively
|
92
|
+
#
|
93
|
+
# Pros
|
94
|
+
#
|
95
|
+
# * clone_fd will work
|
96
|
+
# * filesystem interrupts may work
|
97
|
+
#
|
98
|
+
# Cons
|
99
|
+
#
|
100
|
+
# * multi-threading will create a new ruby thread for every callback
|
101
|
+
# * cannot daemonize multi-threaded (hangs) TODO: Why - pthread_lock?, GVL?
|
102
|
+
# * cannot pass signals to the filesystem
|
103
|
+
#
|
104
|
+
# @api private
|
105
|
+
# @param [Boolean] foreground
|
106
|
+
# @param [Boolean] single_thread
|
107
|
+
#
|
108
|
+
def run_native(foreground: true, single_thread: true, **options)
|
109
|
+
if !single_thread && !foreground
|
110
|
+
warn 'Cannot run native multi-thread fuse_loop when daemonized. Using single_thread mode'
|
111
|
+
single_thread = true
|
112
|
+
end
|
113
|
+
|
114
|
+
clear_default_traps
|
115
|
+
(se = session) && Libfuse.fuse_set_signal_handlers(se)
|
116
|
+
|
117
|
+
Libfuse.fuse_daemonize(foreground ? 1 : 0)
|
118
|
+
|
119
|
+
if single_thread
|
120
|
+
Libfuse.fuse_loop(@fuse)
|
121
|
+
else
|
122
|
+
native_fuse_loop_mt(**options)
|
123
|
+
end
|
124
|
+
ensure
|
125
|
+
(se = session) && Libfuse.fuse_remove_signal_handlers(se)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Tell the processing loop to stop and force unmount the filesystem which is unfortunately required to make
|
129
|
+
# the processing threads, which are mostly blocked on io reads from /dev/fuse fd, to exit.
|
130
|
+
def exit
|
131
|
+
Libfuse.fuse_exit(@fuse) if @fuse
|
132
|
+
# Force threads blocked reading on #io to finish
|
133
|
+
unmount
|
134
|
+
end
|
135
|
+
|
136
|
+
# Ruby implementation of fuse default traps
|
137
|
+
# @see Ackbar
|
138
|
+
def default_traps
|
139
|
+
@default_traps ||= { INT: -> { exit }, HUP: -> { exit }, TERM: -> { exit }, PIPE: 'IGNORE' }
|
140
|
+
end
|
141
|
+
|
142
|
+
# @api private
|
143
|
+
# Ruby implementation of fuse_daemonize which does not work under MRI probably due to the way ruby needs to
|
144
|
+
# understand native threads.
|
145
|
+
def daemonize
|
146
|
+
raise 'Cannot daemonize without support for fork' unless Process.respond_to?(:fork)
|
147
|
+
|
148
|
+
# pipe to wait for fork
|
149
|
+
pr, pw = ::IO.pipe
|
150
|
+
|
151
|
+
if Process.fork
|
152
|
+
# rubocop:disable Style/RescueModifier
|
153
|
+
status = pr.read(1).unpack1('c') rescue 1
|
154
|
+
Kernel.exit!(status)
|
155
|
+
# rubocop:enable Style/RescueModifier
|
156
|
+
end
|
157
|
+
|
158
|
+
begin
|
159
|
+
Process.setsid
|
160
|
+
|
161
|
+
Dir.chdir '/'
|
162
|
+
|
163
|
+
# close/redirect file descriptors
|
164
|
+
$stdin.close
|
165
|
+
|
166
|
+
[$stdout, $stderr].each { |io| io.reopen('/dev/null', 'w') }
|
167
|
+
|
168
|
+
pw.write([0].pack('c'))
|
169
|
+
ensure
|
170
|
+
pw.close
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# @api private
|
175
|
+
# Keep track of time until next cache clean for the caching performed by libfuse itself
|
176
|
+
def fuse_clean_cache
|
177
|
+
now = Time.now
|
178
|
+
@next_cache_clean ||= now
|
179
|
+
return @next_cache_clean - now unless now >= @next_cache_clean
|
180
|
+
|
181
|
+
delay = Libfuse.fuse_clean_cache(@fuse)
|
182
|
+
@next_cache_clean = now + delay
|
183
|
+
delay
|
184
|
+
end
|
185
|
+
|
186
|
+
# @api private
|
187
|
+
# Ruby implementation of single threaded fuse loop
|
188
|
+
def fuse_loop(signals:, remember: false, **_options)
|
189
|
+
loop do
|
190
|
+
break unless mounted?
|
191
|
+
|
192
|
+
timeout = remember ? fuse_clean_cache : nil
|
193
|
+
|
194
|
+
ready, _ignore_writable, errors = ::IO.select([io, signals.pr], [], [io], timeout)
|
195
|
+
|
196
|
+
next unless ready || errors
|
197
|
+
|
198
|
+
raise 'FUSE error' unless errors.empty?
|
199
|
+
|
200
|
+
break if ready.include?(io) && !process
|
201
|
+
|
202
|
+
break if ready.include?(signals.pr) && !signals.next
|
203
|
+
rescue Errno::EBADF
|
204
|
+
raise if mounted? # This will occur on exit
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# @api private
|
209
|
+
# Ruby implementation of multi threaded fuse loop
|
210
|
+
#
|
211
|
+
# We cannot simulate the clone_fd behaviour of fuse_loop_mt as the required fd is not exposed in the low level
|
212
|
+
# fuse api.
|
213
|
+
#
|
214
|
+
# @see ThreadPool ThreadPool for the mechanism that controls creation and termination of worker threads
|
215
|
+
def fuse_loop_mt(signals:, max_idle_threads: 10, max_threads: nil, remember: false, **_options)
|
216
|
+
# Monitor for signals (and cache cleaning if required)
|
217
|
+
|
218
|
+
signals.monitor { remember ? fuse_clean_cache : nil }
|
219
|
+
ThreadPool.new(name: 'FuseThread', max_idle: max_idle_threads.to_i, max_active: max_threads) { process }.join
|
220
|
+
end
|
221
|
+
|
222
|
+
# @!visibility private
|
223
|
+
def teardown
|
224
|
+
return unless @fuse
|
225
|
+
|
226
|
+
unmount
|
227
|
+
Libfuse.fuse_destroy(@fuse)
|
228
|
+
ObjectSpace.undefine_finalizer(self)
|
229
|
+
@fuse = nil
|
230
|
+
end
|
231
|
+
|
232
|
+
# @!visibility private
|
233
|
+
def session
|
234
|
+
return nil unless @fuse
|
235
|
+
|
236
|
+
@session ||= Libfuse.fuse_get_session(@fuse)
|
237
|
+
@session.null? ? nil : @session
|
238
|
+
end
|
239
|
+
|
240
|
+
# Allow fuse to handle default signals
|
241
|
+
def clear_default_traps
|
242
|
+
%i[INT HUP TERM PIPE].each do |sig|
|
243
|
+
prev = Signal.trap(sig, 'SYSTEM_DEFAULT')
|
244
|
+
Signal.trap(sig, prev) unless prev == 'DEFAULT'
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|