ffi-libfuse 0.1.0.rc20220550 → 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +1 -1
- data/CHANGELOG.md +60 -0
- data/lib/ffi/flock.rb +7 -5
- data/lib/ffi/libfuse/adapter/fuse3_support.rb +7 -3
- data/lib/ffi/libfuse/adapter/interrupt.rb +1 -1
- data/lib/ffi/libfuse/adapter/safe.rb +11 -10
- data/lib/ffi/libfuse/filesystem/utils.rb +2 -2
- data/lib/ffi/libfuse/filesystem/virtual_fs.rb +8 -0
- data/lib/ffi/libfuse/fuse2.rb +11 -3
- data/lib/ffi/libfuse/fuse3.rb +17 -7
- data/lib/ffi/libfuse/fuse_args.rb +4 -2
- data/lib/ffi/libfuse/fuse_common.rb +9 -4
- data/lib/ffi/libfuse/fuse_operations.rb +14 -2
- data/lib/ffi/libfuse/fuse_opt.rb +1 -1
- data/lib/ffi/libfuse/gem_version.rb +54 -0
- data/lib/ffi/libfuse/main.rb +21 -16
- data/lib/ffi/libfuse/test_helper.rb +145 -0
- data/lib/ffi/libfuse/version.rb +1 -1
- data/lib/ffi/libfuse.rb +2 -2
- data/lib/ffi/ruby_object.rb +3 -0
- metadata +9 -9
- data/CHANGES.md +0 -14
- data/lib/ffi/libfuse/test/operations.rb +0 -56
- data/lib/ffi/libfuse/test.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 688ea85b8e5ee83a4baae3055c14026e65e929c2e22a8d9a2d0e983584e8bd7b
|
4
|
+
data.tar.gz: 552a053bea357d44ccf6fa2fcc12d311116d66320e113d7de7acb929a8ac539e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca1a1f2b2b9573c523583f0ee9d8fa05fecbbaf4ba36fbe73b4db5090626b8fc984091880677b4654caacda25d6e073b0a679b57539f6faecfd4c62f5bc45257
|
7
|
+
data.tar.gz: f8cb9a7d82ac0be86249066b9b69d2cfe03be3e6eb028881b16e715766b2a0f9dbba46ba2478310be75677e2b8c188f0b26235bbfa491dc42d9dff0ca904840e
|
data/.yardopts
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## [0.3.3](https://github.com/lwoggardner/ffi-libfuse/compare/v0.0.1...v0.3.3) (2023-01-07)
|
4
|
+
|
5
|
+
|
6
|
+
### ⚠ BREAKING CHANGES
|
7
|
+
|
8
|
+
* Support downstream RFuse/RFuseFS
|
9
|
+
|
10
|
+
### Features
|
11
|
+
|
12
|
+
* FFI::Libfuse::Filesystem - base filesystems ([5b19005](https://github.com/lwoggardner/ffi-libfuse/commit/5b19005c4b1ff2237b85c4854f481ea6e3625c62))
|
13
|
+
|
14
|
+
|
15
|
+
### Code Refactoring
|
16
|
+
|
17
|
+
* Support downstream RFuse/RFuseFS ([e6b3fb5](https://github.com/lwoggardner/ffi-libfuse/commit/e6b3fb552b8881dbf28f014617b7412f2542aaa3))
|
18
|
+
|
19
|
+
|
20
|
+
### Miscellaneous Chores
|
21
|
+
|
22
|
+
* **github:** release 0.3.3 ([b54a56f](https://github.com/lwoggardner/ffi-libfuse/commit/b54a56f3f93f15c7684aa2cb2c2dd38c9d033e7f))
|
23
|
+
|
24
|
+
## [0.3.2](https://github.com/lwoggardner/ffi-libfuse/compare/v0.3.0...v0.3.2) (2023-01-07)
|
25
|
+
|
26
|
+
|
27
|
+
### Miscellaneous Chores
|
28
|
+
|
29
|
+
* fix release-please action ([fd55030](https://github.com/lwoggardner/ffi-libfuse/commit/fd550301248ebd9616da457ef8c2d88a7e55f819))
|
30
|
+
|
31
|
+
## [0.3.0](https://github.com/lwoggardner/ffi-libfuse/compare/v0.2.1...v0.3.0) (2023-01-07)
|
32
|
+
|
33
|
+
|
34
|
+
### Miscellaneous Chores
|
35
|
+
|
36
|
+
* fix release-please action ([57d43e9](https://github.com/lwoggardner/ffi-libfuse/commit/57d43e9cac552b1b36469092a6278058893cadc4))
|
37
|
+
|
38
|
+
## [0.2.1](https://github.com/lwoggardner/ffi-libfuse/compare/v0.1.0...v0.2.1) (2023-01-07)
|
39
|
+
|
40
|
+
### Miscellaneous Chores
|
41
|
+
|
42
|
+
* release 0.2.1 ([bb0ef37](https://github.com/lwoggardner/ffi-libfuse/commit/bb0ef37c05a41c6b51a14e5cae292b2d7b75ef1c))
|
43
|
+
|
44
|
+
## [0.1.0](https://github.com/lwoggardner/ffi-libfuse/compare/v0.0.1...v0.1.0) (2023-01-07)
|
45
|
+
|
46
|
+
### ⚠ BREAKING CHANGES
|
47
|
+
|
48
|
+
* Support downstream RFuse/RFuseFS
|
49
|
+
* Changed option parsing.
|
50
|
+
|
51
|
+
{FFI::Libfuse::Main#fuse_options} takes a FuseArgs parameter and fuse_opt_proc is not used
|
52
|
+
|
53
|
+
### Features
|
54
|
+
|
55
|
+
* FFI::Libfuse::Filesystem - base filesystems ([5b19005](https://github.com/lwoggardner/ffi-libfuse/commit/5b19005c4b1ff2237b85c4854f481ea6e3625c62))
|
56
|
+
|
57
|
+
### Code Refactoring
|
58
|
+
|
59
|
+
* Support downstream RFuse/RFuseFS ([e6b3fb5](https://github.com/lwoggardner/ffi-libfuse/commit/e6b3fb552b8881dbf28f014617b7412f2542aaa3))
|
60
|
+
* Test on OSX with macFuse
|
data/lib/ffi/flock.rb
CHANGED
@@ -12,18 +12,20 @@ module FFI
|
|
12
12
|
SeekWhenceShort = enum :short, seek_whence
|
13
13
|
SeekWhence = enum :int, seek_whence
|
14
14
|
|
15
|
-
LockType = enum :short, %i[
|
16
|
-
LockCmd = enum :int, [:
|
15
|
+
LockType = enum :short, %i[rdlck wrlck unlck]
|
16
|
+
LockCmd = enum :int, [:getlk, 5, :setlk, 6, :setlkw, 7]
|
17
17
|
end
|
18
18
|
|
19
19
|
include(Accessors)
|
20
20
|
|
21
|
-
layout(
|
21
|
+
layout(l_type: Enums::LockType, l_whence: Enums::SeekWhenceShort, l_start: :off_t, l_len: :off_t, l_pid: :pid_t)
|
22
22
|
|
23
|
-
|
23
|
+
l_members = members.grep(/^l_/).map { |m| m[2..].to_sym }
|
24
|
+
|
25
|
+
ffi_attr_reader(*l_members, format: 'l_%s')
|
24
26
|
|
25
27
|
# @!attribute [r] type
|
26
|
-
# @return [Symbol] lock type, :
|
28
|
+
# @return [Symbol] lock type, :rdlck, :wrlck, :unlck
|
27
29
|
|
28
30
|
# @!attribute [r] whence
|
29
31
|
# @return [Symbol] specifies what the offset is relative to, one of :seek_set, :seek_cur or :seek_end
|
@@ -22,14 +22,14 @@ module FFI
|
|
22
22
|
|
23
23
|
def getattr(*args)
|
24
24
|
fi = args.pop
|
25
|
-
return fgetattr(*args, fi) if fi &&
|
25
|
+
return fgetattr(*args, fi) if fi && fuse_super_respond_to?(:fgetattr)
|
26
26
|
|
27
27
|
super(*args)
|
28
28
|
end
|
29
29
|
|
30
30
|
def truncate(*args)
|
31
31
|
fi = args.pop
|
32
|
-
return ftruncate(*args, fi) if fi &&
|
32
|
+
return ftruncate(*args, fi) if fi && fuse_super_respond_to?(:ftruncate)
|
33
33
|
|
34
34
|
super(*args)
|
35
35
|
end
|
@@ -50,7 +50,7 @@ module FFI
|
|
50
50
|
# but there is no way to handle OMIT
|
51
51
|
def utimens(*args)
|
52
52
|
args.pop
|
53
|
-
super(*args)
|
53
|
+
super(*args) if defined?(super)
|
54
54
|
end
|
55
55
|
|
56
56
|
def init(*args)
|
@@ -73,6 +73,10 @@ module FFI
|
|
73
73
|
|
74
74
|
super(*args, &block)
|
75
75
|
end
|
76
|
+
|
77
|
+
def fuse_respond_to(fuse_callback)
|
78
|
+
super || (%i[truncate getattr].include?(fuse_callback) && fuse_super_respond_to?("f#{fuse_callback}"))
|
79
|
+
end
|
76
80
|
end
|
77
81
|
|
78
82
|
# @!visibility private
|
@@ -10,7 +10,7 @@ module FFI
|
|
10
10
|
# @!visibility private
|
11
11
|
def fuse_wrappers(*wrappers)
|
12
12
|
wrappers << {
|
13
|
-
wrapper: proc { |fm, *args, **_, &b| Safe.safe_callback(fm, *args, &b) },
|
13
|
+
wrapper: proc { |fm, *args, **_, &b| Safe.safe_callback(fm, *args, default_errno: default_errno, &b) },
|
14
14
|
excludes: %i[init destroy]
|
15
15
|
}
|
16
16
|
return wrappers unless defined?(super)
|
@@ -18,8 +18,10 @@ module FFI
|
|
18
18
|
super(*wrappers)
|
19
19
|
end
|
20
20
|
|
21
|
-
#
|
22
|
-
|
21
|
+
# @return [Integer] the default errno. ENOTRECOVERABLE unless overridden
|
22
|
+
def default_errno
|
23
|
+
defined?(super) ? super : Errno::ENOTRECOVERABLE::Errno
|
24
|
+
end
|
23
25
|
|
24
26
|
module_function
|
25
27
|
|
@@ -27,30 +29,29 @@ module FFI
|
|
27
29
|
#
|
28
30
|
# @yieldreturn [SystemCallError] expected callback errors rescued to return equivalent -ve errno value
|
29
31
|
# @yieldreturn [StandardError,ScriptError] unexpected callback errors are rescued
|
30
|
-
# to return -
|
32
|
+
# to return -ve {default_errno} after emitting backtrace to #warn
|
31
33
|
#
|
32
34
|
# @yieldreturn [Integer]
|
33
35
|
#
|
34
36
|
# * -ve values returned directly
|
35
|
-
# * +ve values returned directly for fuse_methods in {MEANINGFUL_RETURN} list
|
37
|
+
# * +ve values returned directly for fuse_methods in {FuseOperations.MEANINGFUL_RETURN} list
|
36
38
|
# * otherwise returns 0
|
37
39
|
#
|
38
40
|
# @yieldreturn [Object] always returns 0 if no exception is raised
|
39
41
|
#
|
40
|
-
def safe_callback(fuse_method, *args)
|
42
|
+
def safe_callback(fuse_method, *args, default_errno: Errno::ENOTRECOVERABLE::Errno)
|
41
43
|
result = yield(*args)
|
42
44
|
|
43
|
-
return
|
44
|
-
return 0 unless result.negative? || MEANINGFUL_RETURN.include?(fuse_method)
|
45
|
+
return result.to_i if FuseOperations.meaningful_return?(fuse_method)
|
45
46
|
|
46
|
-
|
47
|
+
0
|
47
48
|
rescue SystemCallError => e
|
48
49
|
-e.errno
|
49
50
|
rescue StandardError, ScriptError => e
|
50
51
|
# rubocop:disable Layout/LineLength
|
51
52
|
warn ["FFI::Libfuse error in #{fuse_method}", *e.backtrace.reverse, "#{e.class.name}:#{e.message}"].join("\n\t")
|
52
53
|
# rubocop:enable Layout/LineLength
|
53
|
-
-
|
54
|
+
-default_errno.abs
|
54
55
|
end
|
55
56
|
end
|
56
57
|
end
|
@@ -19,7 +19,7 @@ module FFI
|
|
19
19
|
# @param [Integer] mode permissions for any dirs that need to be created
|
20
20
|
# @yieldparam [String] the path component being created
|
21
21
|
# @yieldreturn [FuseOperations] optionally a filesystem to mount at path, if the path did not previously exist
|
22
|
-
def mkdir_p(path, mode = (
|
22
|
+
def mkdir_p(path, mode = (~FuseContext.get.umask & 0o0777), &mount_fs)
|
23
23
|
return if root?(path) # nothing to make
|
24
24
|
|
25
25
|
path.to_s.split('/')[1..].inject('') do |base_path, sub_dir|
|
@@ -71,7 +71,7 @@ module FFI
|
|
71
71
|
# @return [Boolean] File exists at path and has zero size
|
72
72
|
def empty_file?(path)
|
73
73
|
s = stat(path)
|
74
|
-
(s&.file? && s.size.zero?) || false
|
74
|
+
(s&.file? && s.size.zero?) || false # rubocop:disable Style/ZeroLengthPredicate
|
75
75
|
end
|
76
76
|
|
77
77
|
# Check if a directory is empty
|
@@ -182,6 +182,14 @@ module FFI
|
|
182
182
|
end
|
183
183
|
|
184
184
|
attr_reader :no_buf
|
185
|
+
|
186
|
+
# This class does not implement any fuse methods, ensure they are passed to method missing.
|
187
|
+
# eg Kernel.open
|
188
|
+
FFI::Libfuse::FuseOperations.fuse_callbacks.each do |c|
|
189
|
+
undef_method(c)
|
190
|
+
rescue StandardError
|
191
|
+
nil
|
192
|
+
end
|
185
193
|
end
|
186
194
|
end
|
187
195
|
end
|
data/lib/ffi/libfuse/fuse2.rb
CHANGED
@@ -115,9 +115,7 @@ module FFI
|
|
115
115
|
@fuse = nil if @fuse&.null?
|
116
116
|
end
|
117
117
|
ensure
|
118
|
-
|
119
|
-
# as it's weakref will have been GC'd
|
120
|
-
ObjectSpace.define_finalizer(self, self.class.finalize_fuse(@fuse, @mountpoint, @ch))
|
118
|
+
define_finalizer
|
121
119
|
end
|
122
120
|
|
123
121
|
# [IO] /dev/fuse file descriptor for use with IO.select
|
@@ -150,6 +148,16 @@ module FFI
|
|
150
148
|
c = @ch
|
151
149
|
@ch = nil
|
152
150
|
Libfuse.fuse_unmount2(mountpoint, c)
|
151
|
+
ensure
|
152
|
+
# Can't unmount twice
|
153
|
+
define_finalizer
|
154
|
+
end
|
155
|
+
|
156
|
+
def define_finalizer
|
157
|
+
# if we unmount/destroy in the finalizer then the private_data object cannot be used in destory
|
158
|
+
# as it's weakref will have been GC'd
|
159
|
+
ObjectSpace.undefine_finalizer(self)
|
160
|
+
ObjectSpace.define_finalizer(self, self.class.finalize_fuse(@fuse, @mountpoint, @ch))
|
153
161
|
end
|
154
162
|
end
|
155
163
|
|
data/lib/ffi/libfuse/fuse3.rb
CHANGED
@@ -87,10 +87,10 @@ module FFI
|
|
87
87
|
$stdout.puts "\n#{handler.fuse_help}" if handler.respond_to?(:fuse_help)
|
88
88
|
end
|
89
89
|
|
90
|
-
def finalize_fuse(fuse)
|
90
|
+
def finalize_fuse(fuse, mounted)
|
91
91
|
proc do
|
92
92
|
if fuse
|
93
|
-
Libfuse.fuse_unmount3(fuse)
|
93
|
+
Libfuse.fuse_unmount3(fuse) if mounted
|
94
94
|
Libfuse.fuse_destroy(fuse)
|
95
95
|
end
|
96
96
|
end
|
@@ -101,7 +101,7 @@ module FFI
|
|
101
101
|
|
102
102
|
# Have we requested an unmount (note not actually checking if OS sees the fs as mounted)
|
103
103
|
def mounted?
|
104
|
-
session && !fuse_exited?
|
104
|
+
session && !fuse_exited? && @mounted
|
105
105
|
end
|
106
106
|
|
107
107
|
def initialize(mountpoint, args, operations, private_data)
|
@@ -119,9 +119,7 @@ module FFI
|
|
119
119
|
|
120
120
|
@mounted = @fuse && Libfuse.fuse_mount3(@fuse, @mountpoint).zero?
|
121
121
|
ensure
|
122
|
-
|
123
|
-
# as it's weakref will have been GC'd
|
124
|
-
ObjectSpace.define_finalizer(self, self.class.finalize_fuse(@fuse))
|
122
|
+
define_finalizer
|
125
123
|
end
|
126
124
|
|
127
125
|
def fuse_exited?
|
@@ -152,7 +150,19 @@ module FFI
|
|
152
150
|
end
|
153
151
|
|
154
152
|
def unmount
|
155
|
-
|
153
|
+
return unless @mounted && @fuse && !@fuse.null?
|
154
|
+
|
155
|
+
Libfuse.fuse_unmount3(@fuse)
|
156
|
+
@mounted = false
|
157
|
+
ensure
|
158
|
+
define_finalizer
|
159
|
+
end
|
160
|
+
|
161
|
+
def define_finalizer
|
162
|
+
# if we unmount/destroy in the finalizer then the private_data object cannot be used in destroy
|
163
|
+
# as it's weakref will have been GC'd
|
164
|
+
ObjectSpace.undefine_finalizer(self)
|
165
|
+
ObjectSpace.define_finalizer(self, self.class.finalize_fuse(@fuse, @mounted))
|
156
166
|
end
|
157
167
|
end
|
158
168
|
|
@@ -14,11 +14,13 @@ module FFI
|
|
14
14
|
# Create a fuse_args struct from command line options
|
15
15
|
# @param [Array<String>] argv command line args
|
16
16
|
#
|
17
|
-
# first arg is expected to be program name
|
17
|
+
# first arg is expected to be program name and is ignored by fuse_opt_parse
|
18
|
+
# it is handled specially only in fuse_parse_cmdline (ie no subtype is given)
|
18
19
|
# @return [FuseArgs]
|
19
20
|
# @example
|
20
21
|
# FFI::Libfuse::FuseArgs.create($0,*ARGV)
|
21
22
|
def self.create(*argv)
|
23
|
+
argv.unshift('ffi-libfuse') if argv.empty? || argv[0].start_with?('-')
|
22
24
|
new.fill(*argv)
|
23
25
|
end
|
24
26
|
|
@@ -162,7 +164,7 @@ module FFI
|
|
162
164
|
[opt, arg[opt.rstrip.length..].lstrip]
|
163
165
|
else
|
164
166
|
warn "FuseOptProc error - Cannot match option for #{arg}"
|
165
|
-
next
|
167
|
+
next FUSE_OPT_PROC_RETURN.fetch(:error)
|
166
168
|
end
|
167
169
|
|
168
170
|
safe_opt_proc(key: key, value: value, match: match, data: data, out: out, &block)
|
@@ -100,7 +100,7 @@ module FFI
|
|
100
100
|
# @param [Boolean] foreground
|
101
101
|
# @param [Boolean] single_thread
|
102
102
|
def run_native(foreground: true, single_thread: true, **options)
|
103
|
-
raise 'Cannot run
|
103
|
+
raise 'Cannot run daemonized native multi-thread fuse_loop' if !single_thread && !foreground
|
104
104
|
|
105
105
|
clear_default_traps
|
106
106
|
(se = session) && Libfuse.fuse_set_signal_handlers(se)
|
@@ -165,7 +165,7 @@ module FFI
|
|
165
165
|
# @api private
|
166
166
|
# Ruby implementation of single threaded fuse loop
|
167
167
|
def fuse_loop(**_options)
|
168
|
-
|
168
|
+
safe_fuse_process until fuse_exited?
|
169
169
|
end
|
170
170
|
|
171
171
|
# @api private
|
@@ -179,11 +179,17 @@ module FFI
|
|
179
179
|
ThreadPool.new(name: 'FuseThread', max_idle: max_idle_threads.to_i, max_active: max_threads&.to_i) do
|
180
180
|
raise StopIteration if fuse_exited?
|
181
181
|
|
182
|
-
|
182
|
+
safe_fuse_process
|
183
183
|
end.join
|
184
184
|
end
|
185
185
|
|
186
186
|
# @!visibility private
|
187
|
+
|
188
|
+
def safe_fuse_process
|
189
|
+
# sometimes we get null on unmount, and exit needs a chance to finish to avoid hangs
|
190
|
+
fuse_process || (sleep(0.1) && false)
|
191
|
+
end
|
192
|
+
|
187
193
|
def teardown
|
188
194
|
return unless @fuse
|
189
195
|
|
@@ -209,7 +215,6 @@ module FFI
|
|
209
215
|
sleep 0.2 if mac_fuse?
|
210
216
|
|
211
217
|
Libfuse.fuse_exit(@fuse)
|
212
|
-
|
213
218
|
true
|
214
219
|
end
|
215
220
|
end
|
@@ -33,7 +33,10 @@ module FFI
|
|
33
33
|
bitmask :lock_op, [:lock_sh, 0, :lock_ex, 2, :lock_nb, 4, :lock_un, 8]
|
34
34
|
bitmask :falloc_mode, %i[keep_size punch_hole no_hide_stale collapse_range zero_range insert_range unshare_range]
|
35
35
|
bitmask :flags_mask, %i[nullpath_ok nopath utime_omit_ok] if FUSE_MAJOR_VERSION < 3
|
36
|
-
|
36
|
+
|
37
|
+
# @!visibility private
|
38
|
+
XAttr = enum :xattr, [:xattr_create, 1, :xattr_replace]
|
39
|
+
|
37
40
|
callback :fill_dir_t, fill_dir_t_args, :int
|
38
41
|
|
39
42
|
# The file system operations as specified in libfuse.
|
@@ -47,6 +50,14 @@ module FFI
|
|
47
50
|
class FuseOperations < FFI::Struct
|
48
51
|
include FuseCallbacks
|
49
52
|
|
53
|
+
# Callbacks that are expected to return meaningful positive integers
|
54
|
+
MEANINGFUL_RETURN = %i[read write write_buf lseek copy_file_range getxattr listxattr].freeze
|
55
|
+
|
56
|
+
# @return [Boolean] true if fuse_callback expects a meaningful integer return
|
57
|
+
def self.meaningful_return?(fuse_callback)
|
58
|
+
MEANINGFUL_RETURN.include?(fuse_callback)
|
59
|
+
end
|
60
|
+
|
50
61
|
# Container to dynamically build up the operations layout which is dependent on the loaded libfuse version
|
51
62
|
op = {}
|
52
63
|
|
@@ -213,6 +224,7 @@ module FFI
|
|
213
224
|
op[:truncate] = [:off_t]
|
214
225
|
op[:truncate] << FuseFileInfo.by_ref if FUSE_MAJOR_VERSION >= 3
|
215
226
|
|
227
|
+
# Not directly implemented see utimens
|
216
228
|
# int (*utime) (const char *, struct utimbuf *);
|
217
229
|
op[:utime] = [:pointer] if FUSE_MAJOR_VERSION < 3
|
218
230
|
|
@@ -568,7 +580,7 @@ module FFI
|
|
568
580
|
# @param [String] path
|
569
581
|
# @param [FuseFileInfo] fuse_file_info
|
570
582
|
# For checking lock ownership, the 'fuse_file_info->owner' argument must be used.
|
571
|
-
# @param [Symbol] cmd either :
|
583
|
+
# @param [Symbol] cmd either :getlck, :setlck or :setlkw.
|
572
584
|
# @param [Flock] flock
|
573
585
|
# For the meaning of fields in 'struct flock' see the man page for fcntl(2). The whence field will always
|
574
586
|
# be set to :seek_set.
|
data/lib/ffi/libfuse/fuse_opt.rb
CHANGED
@@ -19,7 +19,7 @@ module FFI
|
|
19
19
|
def fill(template, value)
|
20
20
|
str_ptr = FFI::MemoryPointer.from_string(template)
|
21
21
|
self[:template] = str_ptr
|
22
|
-
self[:offset] = (2**(
|
22
|
+
self[:offset] = (2**(FFI::Type::INT.size * 8)) - 1 # -(1U) in a LONG!!
|
23
23
|
self[:value] = value.to_i
|
24
24
|
self
|
25
25
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'version'
|
4
|
+
|
5
|
+
module FFI
|
6
|
+
# Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
|
7
|
+
module Libfuse
|
8
|
+
# @!visibility private
|
9
|
+
|
10
|
+
SEMVER_TAG_REGEX = /^v\d+\.\d+\.\d+/.freeze
|
11
|
+
|
12
|
+
# branches the format is refs/heads/<branch_name>,
|
13
|
+
# tags it is refs/tags/<tag_name>.
|
14
|
+
# for pull requests it is refs/pull/<pr_number>/merge,
|
15
|
+
GIT_REF_TYPES = { 'heads' => :branch, 'tags' => :tag, 'pull' => :pull }.freeze
|
16
|
+
|
17
|
+
def self.git_ref(env: ENV)
|
18
|
+
ref = env.fetch('GIT_REF') do
|
19
|
+
# get branch ref, or detached head ref to tag
|
20
|
+
`git symbolic-ref HEAD 2>/dev/null || git name-rev HEAD | awk '{ gsub(/[\\^~@].*$/,"",$2); printf("refs/%s\\n",$2)}'`.strip # rubocop:disable Layout/LineLength
|
21
|
+
rescue StandardError
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
|
25
|
+
return [ref, nil] unless ref&.start_with?('refs/')
|
26
|
+
|
27
|
+
_refs, ref_type, ref_name = ref.split('/', 3)
|
28
|
+
[ref_name, GIT_REF_TYPES[ref_type]]
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.gem_version(main_branch: 'main', version: VERSION, env: ENV)
|
32
|
+
ref_name, ref_type = git_ref(env: env)
|
33
|
+
|
34
|
+
version =
|
35
|
+
case ref_type
|
36
|
+
when :branch
|
37
|
+
ref_name == main_branch ? [version] : [version, ref_name]
|
38
|
+
when :tag
|
39
|
+
SEMVER_TAG_REGEX.match?(ref_name) ? [ref_name[1..]] : [version, ref_name]
|
40
|
+
when :pull
|
41
|
+
pr_number, merge, _rest = ref_name.split('/')
|
42
|
+
# GITHUB_BASE_REF The name of the base ref or target branch of the pull request in a workflow run
|
43
|
+
base_ref = env.fetch('GIT_BASE_REF', 'undefined')
|
44
|
+
[version, base_ref, "#{merge}#{pr_number}"]
|
45
|
+
else
|
46
|
+
[version, 'pre', ref_name]
|
47
|
+
end.select { |p| p && !p.empty? }.join('.').tr('//_-', '')
|
48
|
+
|
49
|
+
[version, ref_name, ref_type]
|
50
|
+
end
|
51
|
+
|
52
|
+
GEM_VERSION, GIT_REF_NAME, GIT_REF_TYPE = gem_version
|
53
|
+
end
|
54
|
+
end
|
data/lib/ffi/libfuse/main.rb
CHANGED
@@ -9,6 +9,19 @@ module FFI
|
|
9
9
|
# Controls the main run loop for a FUSE filesystem
|
10
10
|
module Main
|
11
11
|
class << self
|
12
|
+
# Builds default argument list for #{fuse_main} regardless of being called directly or from mount.fuse3
|
13
|
+
#
|
14
|
+
# @param [Array<String>] extra_args additional arguments to add to $0 and *ARGV
|
15
|
+
# @return [Array<String>]
|
16
|
+
# @see https://github.com/libfuse/libfuse/issues/621
|
17
|
+
def default_args(*extra_args)
|
18
|
+
args = ARGV.dup
|
19
|
+
|
20
|
+
# If called from mount.fuse3 we already have a 'source' argument which should go at args[0]
|
21
|
+
args.unshift($0) unless args.size >= 2 && args[0..1].all? { |a| !a.start_with?('-') }
|
22
|
+
args.concat(extra_args)
|
23
|
+
end
|
24
|
+
|
12
25
|
# Main function of FUSE
|
13
26
|
#
|
14
27
|
# This function:
|
@@ -31,7 +44,7 @@ module FFI
|
|
31
44
|
# any data to be made available to the {FuseOperations#init} callback
|
32
45
|
#
|
33
46
|
# @return [Integer] suitable for process exit code
|
34
|
-
def fuse_main(*argv, operations:, args: argv.any? ? argv :
|
47
|
+
def fuse_main(*argv, operations:, args: argv.any? ? argv : default_args, private_data: nil)
|
35
48
|
run_args = fuse_parse_cmdline(args: args, handler: operations)
|
36
49
|
return 2 unless run_args
|
37
50
|
|
@@ -63,7 +76,7 @@ module FFI
|
|
63
76
|
# - parses standard fuse mount options
|
64
77
|
#
|
65
78
|
# @param [Array<String>] argv mount.fuse arguments
|
66
|
-
# expects progname,
|
79
|
+
# expects progname, mountpoint, options....
|
67
80
|
# @param [FuseArgs] args
|
68
81
|
# alternatively constructed args
|
69
82
|
# @param [Object] handler
|
@@ -75,7 +88,7 @@ module FFI
|
|
75
88
|
# * show_version [Boolean]: -v or --version
|
76
89
|
# * debug [Boolean]: -d
|
77
90
|
# * others are options to pass to {FuseCommon#run}
|
78
|
-
def fuse_parse_cmdline(*argv, args: argv, handler: nil)
|
91
|
+
def fuse_parse_cmdline(*argv, args: argv.any? ? argv : default_args, handler: nil)
|
79
92
|
args = fuse_init_args(args)
|
80
93
|
|
81
94
|
# Parse args and print cmdline help
|
@@ -94,16 +107,15 @@ module FFI
|
|
94
107
|
|
95
108
|
# @return [FuseCommon|nil] the mounted filesystem or nil if not mounted
|
96
109
|
def fuse_create(mountpoint, *argv, operations:, args: nil, private_data: nil)
|
97
|
-
args = fuse_init_args(args || argv
|
110
|
+
args = fuse_init_args(args || argv)
|
98
111
|
|
99
112
|
operations = FuseOperations.new(delegate: operations) unless operations.is_a?(FuseOperations)
|
100
113
|
|
101
|
-
fuse = Fuse.new(mountpoint, args, operations, private_data)
|
114
|
+
fuse = Fuse.new(mountpoint.to_s, args, operations, private_data)
|
102
115
|
fuse if fuse.mounted?
|
103
116
|
end
|
104
117
|
|
105
118
|
# @!visibility private
|
106
|
-
|
107
119
|
def fuse_configure(operations:, show_help: false, show_version: false, **_)
|
108
120
|
return true unless operations.respond_to?(:fuse_configure) && !show_help && !show_version
|
109
121
|
|
@@ -120,20 +132,15 @@ module FFI
|
|
120
132
|
end
|
121
133
|
end
|
122
134
|
|
123
|
-
#
|
135
|
+
# @!visibility private
|
124
136
|
def version
|
125
137
|
"#{name}: #{VERSION}"
|
126
138
|
end
|
127
139
|
|
140
|
+
private
|
141
|
+
|
128
142
|
def fuse_init_args(args)
|
129
143
|
if args.is_a?(Array)
|
130
|
-
args = args.map(&:to_s) # handle mountpoint as Pathname etc..
|
131
|
-
|
132
|
-
# https://github.com/libfuse/libfuse/issues/621 handle "source" field sent from /etc/fstab via mount.fuse3
|
133
|
-
# if arg[1] and arg[2] are both non option fields then replace arg1 with -ofsname=<arg1>
|
134
|
-
unless args.size <= 2 || args[1]&.start_with?('-') || args[2]&.start_with?('-')
|
135
|
-
args[1] = "-ofsname=#{args[1]}"
|
136
|
-
end
|
137
144
|
warn "FuseArgs: #{args.join(' ')}" if args.include?('-d')
|
138
145
|
args = FuseArgs.create(*args)
|
139
146
|
end
|
@@ -143,8 +150,6 @@ module FFI
|
|
143
150
|
raise ArgumentError "fuse main args: must be Array<String> or #{FuseArgs.class.name}"
|
144
151
|
end
|
145
152
|
|
146
|
-
private
|
147
|
-
|
148
153
|
def parse_run_options(args, run_args)
|
149
154
|
args.parse!(RUN_OPTIONS) do |key:, value:, **|
|
150
155
|
run_args[key] = value
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../libfuse'
|
4
|
+
require 'open3'
|
5
|
+
require 'sys-filesystem'
|
6
|
+
|
7
|
+
# utilities for running tests with fuse filesystems
|
8
|
+
module FFI
|
9
|
+
module Libfuse
|
10
|
+
# Can be included test classes to assist with running/debugging filesystems
|
11
|
+
module TestHelper
|
12
|
+
# rubocop:disable Metrics/AbcSize
|
13
|
+
# rubocop:disable Metrics/MethodLength
|
14
|
+
|
15
|
+
# Runs the fuse loop on a pre configured fuse filesystem
|
16
|
+
# @param [FuseOperations] operations
|
17
|
+
# @param [Array<String>] args to pass to {FFI::Libfuse::Main.fuse_create}
|
18
|
+
# @param [Hash] options to pass to {FFI::Libfuse::FuseCommon.run}
|
19
|
+
# @yield [mnt]
|
20
|
+
# caller can execute and test file operations using mnt and ruby File/Dir etc
|
21
|
+
# the block is run in a forked process and is successful unless an exception is raised
|
22
|
+
# @yieldparam [String] mnt the temporary direct used as the mount point
|
23
|
+
# @raise [Error] if unexpected state is found during operations
|
24
|
+
# @return [void]
|
25
|
+
def with_fuse(operations, *args, **options)
|
26
|
+
raise ArgumentError, 'Needs block' unless block_given?
|
27
|
+
|
28
|
+
# ignore MacOS special files
|
29
|
+
args << '-onoappledouble,noapplexattr' if mac_fuse?
|
30
|
+
safe_fuse do |mnt|
|
31
|
+
# Start the fork before loading fuse (for MacOS)
|
32
|
+
fpid = Process.fork do
|
33
|
+
sleep 2.5 # Give fuse a chance to start
|
34
|
+
yield mnt
|
35
|
+
end
|
36
|
+
|
37
|
+
fuse = FFI::Libfuse::Main.fuse_create(mnt, *args, operations: operations)
|
38
|
+
raise FFI::Libfuse::Error, 'No fuse object returned from fuse_create' unless fuse
|
39
|
+
|
40
|
+
# Rake owns INT
|
41
|
+
fuse.default_traps.delete(:TERM)
|
42
|
+
fuse.default_traps.delete(:INT)
|
43
|
+
|
44
|
+
raise FFI::Libfuse::Error, 'fuse object is not mounted?' unless fuse.mounted?
|
45
|
+
|
46
|
+
t = Thread.new { fuse.run(foreground: true, **options) }
|
47
|
+
|
48
|
+
# TODO: Work out why waitpid2 hangs on mac unless the process has already finished
|
49
|
+
sleep 10 if mac_fuse?
|
50
|
+
|
51
|
+
_pid, block_status = Process.waitpid2(fpid)
|
52
|
+
block_exit = block_status.exitstatus
|
53
|
+
fuse.exit('fuse_helper')&.join
|
54
|
+
run_result = t.value
|
55
|
+
|
56
|
+
raise FFI::Libfuse::Error, 'fuse is still mounted after fuse.exit' if fuse.mounted?
|
57
|
+
raise FFI::Libfuse::Error, "forked file operations failed with #{block_exit}" unless block_exit.zero?
|
58
|
+
raise FFI::Libfuse::Error, "fuse run failed #{run_result}" unless run_result.zero?
|
59
|
+
|
60
|
+
if !mac_fuse? && mounted?(mnt)
|
61
|
+
raise FFI::Libfuse::Error, "OS reports fuse is still mounted at #{mnt} after fuse.exit"
|
62
|
+
end
|
63
|
+
|
64
|
+
true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Runs a filesystem in a separate process
|
69
|
+
# @param [String] filesystem path to filesystem executable
|
70
|
+
# @param [Array<String>] args to pass the filesystem
|
71
|
+
# @param [Hash<String,String>] env environment to run the filesystem under
|
72
|
+
# @yield [mnt]
|
73
|
+
# caller can execute and test file operations using mnt and ruby File/Dir etc
|
74
|
+
# @yieldparam [String] mnt the temporary direct used as the mount point
|
75
|
+
# @raise [Error] if unexpected state is found during operations
|
76
|
+
# @return [Array] stdout, stderr, exit code as captured by Open3.capture3
|
77
|
+
# @note if the filesystem is configured to daemonize then no output will be captured
|
78
|
+
def run_filesystem(filesystem, *args, env: {})
|
79
|
+
fsname = File.basename(filesystem)
|
80
|
+
safe_fuse do |mnt|
|
81
|
+
t = Thread.new do
|
82
|
+
if defined?(Bundler)
|
83
|
+
Bundler.with_unbundled_env do
|
84
|
+
Open3.capture3(env, 'bundle', 'exec', filesystem.to_s, mnt, "-ofsname=#{fsname}", *args, binmode: true)
|
85
|
+
end
|
86
|
+
else
|
87
|
+
Open3.capture3(env, filesystem.to_s, mnt, "-ofsname=#{fsname}", *args, binmode: true)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
sleep 1
|
91
|
+
|
92
|
+
begin
|
93
|
+
if block_given?
|
94
|
+
raise Error, "#{fsname} not mounted at #{mnt}" unless mounted?(mnt, fsname)
|
95
|
+
|
96
|
+
yield mnt
|
97
|
+
end
|
98
|
+
# rubocop:disable Lint/RescueException
|
99
|
+
# Minitest::Assertion and other test assertion classes are not derived from StandardError
|
100
|
+
rescue Exception => _err
|
101
|
+
# rubocop:enable Lint/RescueException
|
102
|
+
unmount(mnt) if mounted?(mnt)
|
103
|
+
o, e, _s = t.value
|
104
|
+
warn "Errors\n#{e}" unless e.empty?
|
105
|
+
warn "Output\n#{o}" unless o.empty?
|
106
|
+
raise
|
107
|
+
end
|
108
|
+
|
109
|
+
unmount(mnt) if mounted?(mnt)
|
110
|
+
o, e, s = t.value
|
111
|
+
[o, e, s.exitstatus]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
# rubocop:enable Metrics/AbcSize
|
115
|
+
# rubocop:enable Metrics/MethodLength
|
116
|
+
|
117
|
+
def mounted?(mnt, _filesystem = '.*')
|
118
|
+
type, prefix = mac_fuse? ? %w[macfuse /private] : %w[fuse]
|
119
|
+
mounts = Sys::Filesystem.mounts.select { |m| m.mount_type == type }
|
120
|
+
mounts.detect { |m| m.mount_point == "#{prefix}#{mnt}" }
|
121
|
+
end
|
122
|
+
|
123
|
+
def unmount(mnt)
|
124
|
+
if mac_fuse?
|
125
|
+
system("diskutil unmount force #{mnt} >/dev/null 2>&1")
|
126
|
+
else
|
127
|
+
system("fusermount -zu #{mnt} >/dev/null 2>&1")
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def safe_fuse
|
132
|
+
Dir.mktmpdir('ffi-libfuse-spec') do |mountpoint|
|
133
|
+
yield mountpoint
|
134
|
+
ensure
|
135
|
+
# Attempt to force unmount.
|
136
|
+
unmount(mountpoint) if mounted?(mountpoint)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def mac_fuse?
|
141
|
+
FFI::Platform::IS_MAC
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
data/lib/ffi/libfuse/version.rb
CHANGED
data/lib/ffi/libfuse.rb
CHANGED
@@ -19,12 +19,12 @@ module FFI
|
|
19
19
|
# Filesystems that want full control (eg to take advantage of multi-threaded operations) should call
|
20
20
|
# {Main.fuse_main} instead
|
21
21
|
# @note These may change between major versions
|
22
|
-
DEFAULT_ARGS = [
|
22
|
+
DEFAULT_ARGS = %w[-s -odefault_permissions].freeze
|
23
23
|
|
24
24
|
class << self
|
25
25
|
# Filesystem entry point
|
26
26
|
# @see Main.fuse_main
|
27
|
-
def fuse_main(*argv, operations:, args: argv.any? ? argv : DEFAULT_ARGS, private_data: nil)
|
27
|
+
def fuse_main(*argv, operations:, args: argv.any? ? argv : Main.default_args(*DEFAULT_ARGS), private_data: nil)
|
28
28
|
Main.fuse_main(args: args, operations: operations, private_data: private_data) || -1
|
29
29
|
end
|
30
30
|
alias main fuse_main
|
data/lib/ffi/ruby_object.rb
CHANGED
@@ -32,6 +32,7 @@ module FFI
|
|
32
32
|
return nil if object_id.zero?
|
33
33
|
|
34
34
|
_ptr, obj = RubyObject.cache[object_id]
|
35
|
+
obj = obj.__getobj__ if obj.is_a?(WeakRef)
|
35
36
|
obj
|
36
37
|
end
|
37
38
|
end
|
@@ -66,6 +67,8 @@ module FFI
|
|
66
67
|
raise TypeError, "No RubyObject stored at #{ptr.address}" unless cache.key?(ptr.address.object_id)
|
67
68
|
|
68
69
|
_ptr, obj = cache[ptr.get(:long, 0)]
|
70
|
+
# unwrap as the object gets used
|
71
|
+
obj = obj.__getobj__ if obj.is_a?(WeakRef)
|
69
72
|
obj
|
70
73
|
end
|
71
74
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ffi-libfuse
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Grant Gardner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-01-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ffi
|
@@ -130,7 +130,7 @@ extensions: []
|
|
130
130
|
extra_rdoc_files: []
|
131
131
|
files:
|
132
132
|
- ".yardopts"
|
133
|
-
-
|
133
|
+
- CHANGELOG.md
|
134
134
|
- LICENSE
|
135
135
|
- README.md
|
136
136
|
- lib/ffi/accessors.rb
|
@@ -178,10 +178,10 @@ files:
|
|
178
178
|
- lib/ffi/libfuse/fuse_opt.rb
|
179
179
|
- lib/ffi/libfuse/fuse_poll_handle.rb
|
180
180
|
- lib/ffi/libfuse/fuse_version.rb
|
181
|
+
- lib/ffi/libfuse/gem_version.rb
|
181
182
|
- lib/ffi/libfuse/job_pool.rb
|
182
183
|
- lib/ffi/libfuse/main.rb
|
183
|
-
- lib/ffi/libfuse/
|
184
|
-
- lib/ffi/libfuse/test/operations.rb
|
184
|
+
- lib/ffi/libfuse/test_helper.rb
|
185
185
|
- lib/ffi/libfuse/thread_pool.rb
|
186
186
|
- lib/ffi/libfuse/version.rb
|
187
187
|
- lib/ffi/ruby_object.rb
|
@@ -200,7 +200,7 @@ homepage:
|
|
200
200
|
licenses:
|
201
201
|
- MIT
|
202
202
|
metadata:
|
203
|
-
source_code_uri:
|
203
|
+
source_code_uri: https://github.com/lwoggardner/ffi-libfuse
|
204
204
|
rubygems_mfa_required: 'true'
|
205
205
|
post_install_message:
|
206
206
|
rdoc_options: []
|
@@ -213,11 +213,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
213
213
|
version: 2.7.0
|
214
214
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
215
215
|
requirements:
|
216
|
-
- - "
|
216
|
+
- - ">="
|
217
217
|
- !ruby/object:Gem::Version
|
218
|
-
version:
|
218
|
+
version: '0'
|
219
219
|
requirements: []
|
220
|
-
rubygems_version: 3.1.
|
220
|
+
rubygems_version: 3.1.6
|
221
221
|
signing_key:
|
222
222
|
specification_version: 4
|
223
223
|
summary: FFI Bindings for Libfuse
|
data/CHANGES.md
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
0.1.0 / 2022-04
|
2
|
-
------------------
|
3
|
-
|
4
|
-
#### BREAKING changes
|
5
|
-
* Changed option parsing.
|
6
|
-
|
7
|
-
{FFI::Libfuse::Main#fuse_options} now takes a FuseArgs parameter and fuse_opt_proc is not used
|
8
|
-
|
9
|
-
#### New Features
|
10
|
-
* Implemented helper filesystems in {FFI::Libfuse::Filesystem}
|
11
|
-
|
12
|
-
#### Fixes
|
13
|
-
* Test on OSX with macFuse
|
14
|
-
* Lots
|
@@ -1,56 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../fuse_operations'
|
4
|
-
|
5
|
-
module FFI
|
6
|
-
module Libfuse
|
7
|
-
module Test
|
8
|
-
# A FuseOperations that holds callback procs in a Hash rather than FFI objects and allows for direct invocation of
|
9
|
-
# callback methods
|
10
|
-
# @!parse FuseOperations
|
11
|
-
class Operations
|
12
|
-
include FuseCallbacks
|
13
|
-
|
14
|
-
def initialize(delegate:, fuse_wrappers: [])
|
15
|
-
@callbacks = {}
|
16
|
-
initialize_callbacks(delegate: delegate, wrappers: fuse_wrappers)
|
17
|
-
end
|
18
|
-
|
19
|
-
# @!visibility private
|
20
|
-
def [](member)
|
21
|
-
@callbacks[member]
|
22
|
-
end
|
23
|
-
|
24
|
-
# @!visibility private
|
25
|
-
def []=(member, value)
|
26
|
-
@callbacks[member] = value
|
27
|
-
end
|
28
|
-
|
29
|
-
# @!visibility private
|
30
|
-
def members
|
31
|
-
FuseOperations.members
|
32
|
-
end
|
33
|
-
|
34
|
-
private
|
35
|
-
|
36
|
-
# Allow the fuse operations to be called directly - useful for testing
|
37
|
-
# @todo some fancy wrapper to convert tests using Fuse2 signatures when Fuse3 is the loaded library
|
38
|
-
# and vice-versa
|
39
|
-
def method_missing(method, *args)
|
40
|
-
callback = callback?(method) && self[method]
|
41
|
-
return super unless callback
|
42
|
-
|
43
|
-
callback.call(*args)
|
44
|
-
end
|
45
|
-
|
46
|
-
def respond_to_missing?(method, _private = false)
|
47
|
-
self[method] && callback?(method)
|
48
|
-
end
|
49
|
-
|
50
|
-
def callback?(method)
|
51
|
-
callback_members.include?(method)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
data/lib/ffi/libfuse/test.rb
DELETED