ffi-libfuse 0.0.1.pre
Sign up to get free protection for your applications and to get access to all the features.
- 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
|