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,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'fuse_version'
|
4
|
+
|
5
|
+
module FFI
|
6
|
+
module Libfuse
|
7
|
+
# Its a trap!
|
8
|
+
#
|
9
|
+
# Implements the self-pipe trick for handling signals in multi-threaded ruby programs
|
10
|
+
class Ackbar
|
11
|
+
# Read side of the self-pipe
|
12
|
+
# @return [IO] for use with IO.select only
|
13
|
+
# @see monitor
|
14
|
+
# @see next
|
15
|
+
attr_reader :pr
|
16
|
+
|
17
|
+
class << self
|
18
|
+
# Run a block with the given traps, restoring the original traps on completion
|
19
|
+
# @param [Hash] traps map of signal to handler as per Signal.trap
|
20
|
+
# @yieldparam [Ackbar] signals
|
21
|
+
# @return [Object] the result of the block
|
22
|
+
def trap(traps, force: false)
|
23
|
+
signals = new(traps, force: force)
|
24
|
+
yield signals
|
25
|
+
ensure
|
26
|
+
signals&.restore
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# @param [Hash<Symbol|String|Integer,String|Proc>] traps
|
31
|
+
# Map of signal or signo to signal handler as per Signal.trap
|
32
|
+
# @param [Boolean] force
|
33
|
+
# If not set traps that are not currently set to 'DEFAULT' will be ignored
|
34
|
+
def initialize(traps, force: false, signal: Signal)
|
35
|
+
@signal = signal
|
36
|
+
@traps = traps.transform_keys { |k| signame(k) }
|
37
|
+
@pr, @pw = ::IO.pipe
|
38
|
+
@monitor = nil
|
39
|
+
@restore = @traps.map { |(sig, handler)| [sig, trap(sig, handler, force: force)] }.to_h
|
40
|
+
end
|
41
|
+
|
42
|
+
# Handle the next available signal on the pipe (without blocking)
|
43
|
+
# @return [Boolean] false if pipe is closed, true if pipe would block
|
44
|
+
# @return [Array<String,Object>] signal name, signal result when a signal has been processed
|
45
|
+
def next
|
46
|
+
signame = signal.signame(@pr.read_nonblock(1).unpack1('c'))
|
47
|
+
t = @traps[signame]
|
48
|
+
[signame, t.arity.zero? ? t.call : t.call(signame)]
|
49
|
+
rescue EOFError
|
50
|
+
# the signal pipe writer is closed - we are exiting.
|
51
|
+
@pr.close
|
52
|
+
false
|
53
|
+
rescue Errno::EWOULDBLOCK, Errno::EAGAIN
|
54
|
+
# oh well...
|
55
|
+
true
|
56
|
+
end
|
57
|
+
|
58
|
+
# Restore traps as they were at #new and close the write side of the pipe
|
59
|
+
def restore
|
60
|
+
@restore.each_pair { |signame, handler| signal.trap(signame, handler) }
|
61
|
+
@restore = nil
|
62
|
+
@pw.close
|
63
|
+
@monitor&.join
|
64
|
+
end
|
65
|
+
alias close restore
|
66
|
+
|
67
|
+
# Start a thread to monitor for signals optionally yields between signals
|
68
|
+
# @param [String] name The name of the monitor thread
|
69
|
+
# @yieldreturn [Integer] timeout in seconds or nil to wait until signal or {restore}
|
70
|
+
# @return [Thread] the monitor thread
|
71
|
+
def monitor(name: 'SignalMonitor')
|
72
|
+
@monitor ||= Thread.new do
|
73
|
+
Thread.current.name = name
|
74
|
+
loop do
|
75
|
+
timeout = block_given? ? yield : nil
|
76
|
+
|
77
|
+
ready, _ignore_writable, _errors = ::IO.select([@pr], [], [], timeout)
|
78
|
+
|
79
|
+
break if ready&.include?(@pr) && !self.next
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# @!visibility private
|
85
|
+
# normalize signal identifiers to things that are keys in Signal.list
|
86
|
+
def signame(sig)
|
87
|
+
sig.is_a?(Integer) ? signal.signame(sig) : sig.to_s.upcase.sub(/^SIG/, '')
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
attr_reader :signal
|
93
|
+
|
94
|
+
def trap(signame, handler, force: false)
|
95
|
+
prev =
|
96
|
+
case handler
|
97
|
+
when String, Symbol
|
98
|
+
signal.trap(signame, handler.to_s)
|
99
|
+
else
|
100
|
+
signal.trap(signame) { |signo| send_signal(signo) }
|
101
|
+
end
|
102
|
+
|
103
|
+
signal.trap(signame, prev) unless force || prev == 'DEFAULT'
|
104
|
+
prev
|
105
|
+
end
|
106
|
+
|
107
|
+
def send_signal(signo)
|
108
|
+
@pw&.write([signo].pack('c')) unless @pw&.closed?
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../fuse_context'
|
4
|
+
|
5
|
+
module FFI
|
6
|
+
module Libfuse
|
7
|
+
module Adapter
|
8
|
+
# Wrapper module to inject {FuseContext} as first arg to each callback method (except :destroy)
|
9
|
+
#
|
10
|
+
# {ThreadLocalContext} may be a less intrusive means to make the context available to callbacks
|
11
|
+
module Context
|
12
|
+
# @!visibility private
|
13
|
+
def fuse_wrappers(*wrappers)
|
14
|
+
wrappers.unshift(
|
15
|
+
{
|
16
|
+
wrapper: proc { |_fm, *args, **_, &b| self.class.context_callback(*args, &b) },
|
17
|
+
excludes: %i[destroy]
|
18
|
+
}
|
19
|
+
)
|
20
|
+
return wrappers unless defined?(super)
|
21
|
+
|
22
|
+
super(*wrappers)
|
23
|
+
end
|
24
|
+
|
25
|
+
module_function
|
26
|
+
|
27
|
+
# @yieldparam [FuseContext] ctx
|
28
|
+
# @yieldparam [Array] *args
|
29
|
+
def context_callback(*args)
|
30
|
+
ctx = FuseContext.get
|
31
|
+
ctx = nil if ctx.null?
|
32
|
+
yield ctx, *args
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FFI
|
4
|
+
module Libfuse
|
5
|
+
module Adapter
|
6
|
+
# Debug callbacks
|
7
|
+
#
|
8
|
+
# When included in a filesystem class, and if debugging is enabled via {Main#fuse_debug}, then installs a wrapper
|
9
|
+
# via #{FuseCallbacks#fuse_wrappers} to log callbacks via #warn
|
10
|
+
module Debug
|
11
|
+
DEFAULT_FORMAT = "%<p>s %<n>s %<t>s %<m>s(%<a>s)\n\t=> %<r>s"
|
12
|
+
|
13
|
+
# @return [Boolean] true if debug is enabled
|
14
|
+
def debug?
|
15
|
+
@debug
|
16
|
+
end
|
17
|
+
|
18
|
+
# Configure debug output
|
19
|
+
# @abstract
|
20
|
+
# @return [Hash<Symbol,String>] options to pass to {debug_callback}
|
21
|
+
def debug_config
|
22
|
+
{}
|
23
|
+
end
|
24
|
+
|
25
|
+
# @!visibility private
|
26
|
+
def fuse_wrappers(*wrappers)
|
27
|
+
conf = { prefix: self.class.name }.merge!(debug_config)
|
28
|
+
# validate config for bad formats, strftime etc
|
29
|
+
Debug.debug_format(:test_debug, [], :result, **conf)
|
30
|
+
wrappers << proc { |fm, *args, &b| Debug.debug_callback(fm, *args, **conf, &b) } if debug?
|
31
|
+
return wrappers unless defined?(super)
|
32
|
+
|
33
|
+
super(*wrappers)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @!visibility private
|
37
|
+
def fuse_debug(enabled)
|
38
|
+
super if defined?(super)
|
39
|
+
@debug = enabled
|
40
|
+
end
|
41
|
+
|
42
|
+
module_function
|
43
|
+
|
44
|
+
# Debug fuse method, args and result
|
45
|
+
# @param [Symbol] fuse_method the callback name
|
46
|
+
# @param [Array] args callback arguments
|
47
|
+
# @param [Hash<Symbol,String>] options see {debug} for defaults
|
48
|
+
# @option options [String] prefix
|
49
|
+
# @option options [String] strftime a date time format
|
50
|
+
# @option options [String] format format string with fields
|
51
|
+
#
|
52
|
+
# * %<n>: Time.now - use strftime option to control time format
|
53
|
+
# * %<t>: Thread name
|
54
|
+
# * %<m>: Fuse method
|
55
|
+
# * %<a>: Comma separate list of arguments
|
56
|
+
# * %<r>: Result of the method call (or any error raised)
|
57
|
+
# * %<p>: The value of prefix option
|
58
|
+
def debug_callback(fuse_method, *args, **options)
|
59
|
+
debug(fuse_method, args, yield(*args), **options)
|
60
|
+
rescue SystemCallError => e
|
61
|
+
# expected behaviour
|
62
|
+
debug(fuse_method, args, "#{e.class.name}(errno=#{e.errno}): #{e.message}", **options)
|
63
|
+
raise
|
64
|
+
rescue StandardError, ScriptError => e
|
65
|
+
# unexpected, debug with backtrace
|
66
|
+
debug(fuse_method, args, (["#{e.class.name}: #{e.message}"] + e.backtrace).join("\n\t"), **options)
|
67
|
+
raise
|
68
|
+
end
|
69
|
+
|
70
|
+
# @!visibility private
|
71
|
+
def debug(fuse_method, args, result, **options)
|
72
|
+
warn debug_format(fuse_method, args, result, **options)
|
73
|
+
result
|
74
|
+
end
|
75
|
+
|
76
|
+
# @!visibility private
|
77
|
+
def debug_format(fuse_method, args, result, prefix: 'DEBUG', strftime: '%FT%T%:z', format: DEFAULT_FORMAT)
|
78
|
+
format(format,
|
79
|
+
p: prefix,
|
80
|
+
n: Time.now.strftime(strftime),
|
81
|
+
t: Thread.current.name || Thread.current,
|
82
|
+
m: fuse_method,
|
83
|
+
a: args.map(&:to_s).join(','),
|
84
|
+
r: result)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FFI
|
4
|
+
module Libfuse
|
5
|
+
module Adapter
|
6
|
+
# Wrapper module to assist filesystem written for Fuse3 to be compatible with Fuse2
|
7
|
+
module Fuse2Compat
|
8
|
+
# @!visibility private
|
9
|
+
# Wrapper shim for fuse methods to ensure compatibility with Fuse2
|
10
|
+
module Fuse2Prepend
|
11
|
+
include Adapter
|
12
|
+
|
13
|
+
def getattr(path, stat, fuse_file_info = nil)
|
14
|
+
super(path, stat, fuse_file_info)
|
15
|
+
end
|
16
|
+
|
17
|
+
def truncate(path, size, fuse_file_info = nil)
|
18
|
+
super(path, size, fuse_file_info)
|
19
|
+
end
|
20
|
+
|
21
|
+
def init(fuse_conn_info, fuse_config = nil)
|
22
|
+
super(fuse_conn_info, fuse_config)
|
23
|
+
end
|
24
|
+
|
25
|
+
def chown(path, uid, gid, fuse_file_info = nil)
|
26
|
+
super(path, uid, gid, fuse_file_info)
|
27
|
+
end
|
28
|
+
|
29
|
+
def chmod(path, mode, fuse_file_info = nil)
|
30
|
+
super(path, mode, fuse_file_info)
|
31
|
+
end
|
32
|
+
|
33
|
+
def utimens(path, atime, mtime, fuse_file_info = nil)
|
34
|
+
super(path, atime, mtime, fuse_file_info)
|
35
|
+
end
|
36
|
+
|
37
|
+
def readdir(path, buffer, filler, offset, fuse_file_info, fuse_readdir_flag = 0)
|
38
|
+
filler = proc { |buf, name, stat, off = 0, _fuse_fill_dir_flag = 0| filler.call(buf, name, stat, off) }
|
39
|
+
super(path, buffer, filler, offset, fuse_file_info, fuse_readdir_flag)
|
40
|
+
end
|
41
|
+
|
42
|
+
def fgetattr(*args)
|
43
|
+
getattr(*args)
|
44
|
+
end
|
45
|
+
|
46
|
+
def ftruncate(*args)
|
47
|
+
truncate(*args)
|
48
|
+
end
|
49
|
+
|
50
|
+
def fuse_respond_to?(fuse_method)
|
51
|
+
fuse_method = fuse_method[1..].to_sym if %i[fgetattr ftruncate].include?(fuse_method)
|
52
|
+
super(fuse_method)
|
53
|
+
end
|
54
|
+
|
55
|
+
def fuse_flags
|
56
|
+
res = defined?(super) ? super : []
|
57
|
+
if respond_to?(:init_fuse_config)
|
58
|
+
fuse_config = FuseConfig.new
|
59
|
+
init_fuse_config(fuse_config, :fuse2)
|
60
|
+
res << :nullpath_ok if fuse_config.nullpath_ok?
|
61
|
+
end
|
62
|
+
|
63
|
+
res
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# @!visibility private
|
68
|
+
module Fuse3Prepend
|
69
|
+
def init(*args)
|
70
|
+
init_fuse_config(args.detect { |a| a.is_a?(FuseConfig) }) if respond_to?(:init_fuse_config)
|
71
|
+
super if defined?(super)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# @!visibility private
|
76
|
+
def self.included(mod)
|
77
|
+
prepend_module = Libfuse.FUSE_MAJOR_VERSION == 2 ? Fuse2Prepend : Fuse3Prepend
|
78
|
+
mod.prepend(prepend_module)
|
79
|
+
end
|
80
|
+
|
81
|
+
# @!method init_fuse_config(fuse_config,compat)
|
82
|
+
# @abstract
|
83
|
+
# Define this method to configure the fuse config object so that under Fuse2 the config options
|
84
|
+
# can be converted to appropriate flags.
|
85
|
+
#
|
86
|
+
# @param [FuseConfig] fuse_config the fuse config object
|
87
|
+
# @param [Symbol] compat either :fuse2 or :fuse3
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FFI
|
4
|
+
module Libfuse
|
5
|
+
module Adapter
|
6
|
+
# Wrapper module to assist filesystem written for Fuse2 to be compatible with Fuse3
|
7
|
+
#
|
8
|
+
# @note there are some deprecated Fuse2 features that have no forwards compatibility in Fuse3
|
9
|
+
#
|
10
|
+
# - flag nopath became fuse_config.nullpath_ok but it is not compatible since chmod/chown etc will now
|
11
|
+
# receive a null path but the filesystem cannot receive the new fuse_file_info arg
|
12
|
+
#
|
13
|
+
module Fuse3Support
|
14
|
+
# The actual module methods that are prepended
|
15
|
+
# @!visibility private
|
16
|
+
module Prepend
|
17
|
+
include Adapter
|
18
|
+
|
19
|
+
# Inner adapters received Fuse2 compatible args
|
20
|
+
def fuse3_compat?
|
21
|
+
false
|
22
|
+
end
|
23
|
+
|
24
|
+
def getattr(*args)
|
25
|
+
fi = args.pop
|
26
|
+
return fgetattr(*args, fi) if fi && respond_to?(:fgetattr)
|
27
|
+
|
28
|
+
super(*args)
|
29
|
+
end
|
30
|
+
|
31
|
+
def truncate(*args)
|
32
|
+
fi = args.pop
|
33
|
+
return ftruncate(*args, fi) if fi && respond_to?(:ftruncate)
|
34
|
+
|
35
|
+
super(*args)
|
36
|
+
end
|
37
|
+
|
38
|
+
def chown(*args)
|
39
|
+
args.pop
|
40
|
+
super(*args)
|
41
|
+
end
|
42
|
+
|
43
|
+
def chmod(*args)
|
44
|
+
args.pop
|
45
|
+
super(*args)
|
46
|
+
end
|
47
|
+
|
48
|
+
# TODO: Fuse3 deprecated flag utime_omit_ok - which meant that UTIME_OMIT and UTIME_NOW are passed through
|
49
|
+
# instead of ????
|
50
|
+
# Strictly if the flag is not set this compat shim should convert utime now values to Time.now
|
51
|
+
# but there is no way to handle OMIT
|
52
|
+
def utimens(*args)
|
53
|
+
args.pop
|
54
|
+
super(*args)
|
55
|
+
end
|
56
|
+
|
57
|
+
def init(*args)
|
58
|
+
args.pop
|
59
|
+
|
60
|
+
# TODO: populate FuseConfig with output from fuse_flags/FuseConnInfo where appropriate
|
61
|
+
super(*args)
|
62
|
+
end
|
63
|
+
|
64
|
+
def readdir(*args, &block)
|
65
|
+
# swallow the flag arg unknown to fuse 2
|
66
|
+
args.pop
|
67
|
+
|
68
|
+
args = args.map do |a|
|
69
|
+
next a unless a.is_a?(FFI::Function)
|
70
|
+
|
71
|
+
# wrap the filler proc for the extra flags argument
|
72
|
+
proc { |buf, name, stat, off| a.call(buf, name, stat, off, 0) }
|
73
|
+
end
|
74
|
+
|
75
|
+
super(*args, &block)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# @!visibility private
|
80
|
+
def self.included(mod)
|
81
|
+
# We prepend our shim module so caller doesn't have to call super
|
82
|
+
mod.prepend(Prepend) if FUSE_MAJOR_VERSION > 2
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../fuse_context'
|
4
|
+
|
5
|
+
module FFI
|
6
|
+
module Libfuse
|
7
|
+
module Adapter
|
8
|
+
# Wrapper module to handle interrupts
|
9
|
+
#
|
10
|
+
# Include this module if you want all requests to check for interruption before processing
|
11
|
+
#
|
12
|
+
# To handle interrupts only for specific callbacks just call {Libfuse.raise_interrupt} or
|
13
|
+
# {Libfuse.fuse_interrupted?} during callback processing rather than including this adapter
|
14
|
+
module Interrupt
|
15
|
+
# @!visibility private
|
16
|
+
def fuse_wrappers(*wrappers)
|
17
|
+
wrappers << {
|
18
|
+
wrapper: proc { |_fuse_method, *args, &b| Interrupt.interrupt_callback(*args, &b) },
|
19
|
+
excludes: %i[init destroy]
|
20
|
+
}
|
21
|
+
return wrappers unless defined?(super)
|
22
|
+
|
23
|
+
super(*wrappers)
|
24
|
+
end
|
25
|
+
|
26
|
+
module_function
|
27
|
+
|
28
|
+
# @raise [Errno::EINTR] if the fuse request is marked as interrupted
|
29
|
+
def interrupt_callback(*args)
|
30
|
+
return -Errno::EINTR::Errno if Libfuse.fuse_interrupted?
|
31
|
+
|
32
|
+
yield(*args)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|