rfuse 1.2.3 → 2.0.0.ffilibfuse
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +2 -1
- data/{CHANGES.md → CHANGELOG.md} +6 -0
- data/README.md +14 -42
- data/lib/rfuse/flock.rb +31 -0
- data/lib/rfuse/gem_version.rb +20 -0
- data/lib/rfuse/rfuse.rb +318 -0
- data/lib/rfuse/stat.rb +43 -0
- data/lib/rfuse/statvfs.rb +33 -0
- data/lib/rfuse/version.rb +1 -1
- data/lib/rfuse.rb +375 -590
- metadata +28 -30
- data/ext/rfuse/bufferwrapper.c +0 -48
- data/ext/rfuse/bufferwrapper.h +0 -10
- data/ext/rfuse/context.c +0 -80
- data/ext/rfuse/context.h +0 -5
- data/ext/rfuse/extconf.rb +0 -17
- data/ext/rfuse/file_info.c +0 -144
- data/ext/rfuse/file_info.h +0 -13
- data/ext/rfuse/filler.c +0 -61
- data/ext/rfuse/filler.h +0 -15
- data/ext/rfuse/helper.c +0 -97
- data/ext/rfuse/helper.h +0 -15
- data/ext/rfuse/intern_rfuse.c +0 -79
- data/ext/rfuse/intern_rfuse.h +0 -22
- data/ext/rfuse/pollhandle.c +0 -54
- data/ext/rfuse/pollhandle.h +0 -10
- data/ext/rfuse/rfuse.c +0 -2007
- data/ext/rfuse/rfuse.h +0 -3
- data/ext/rfuse/rfuse_mod.c +0 -12
- data/ext/rfuse/ruby-compat.h +0 -39
data/lib/rfuse.rb
CHANGED
@@ -1,645 +1,430 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'fcntl'
|
2
|
-
|
3
|
-
|
4
|
-
|
4
|
+
require_relative 'rfuse/version'
|
5
|
+
require_relative 'rfuse/rfuse'
|
6
|
+
require_relative 'rfuse/compat'
|
7
|
+
require_relative 'rfuse/stat'
|
8
|
+
require_relative 'rfuse/statvfs'
|
9
|
+
require_relative 'rfuse/flock'
|
5
10
|
|
6
11
|
# Ruby FUSE (Filesystem in USErspace) binding
|
7
12
|
module RFuse
|
8
13
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
14
|
+
class Error < StandardError; end
|
15
|
+
|
16
|
+
# @private
|
17
|
+
# Used by listxattr
|
18
|
+
def self.packxattr(xattrs)
|
19
|
+
case xattrs
|
20
|
+
when Array
|
21
|
+
xattrs.join("\000").concat("\000")
|
22
|
+
when String
|
23
|
+
# assume already \0 separated list of keys
|
24
|
+
xattrs
|
25
|
+
else
|
26
|
+
raise Error, ":listxattr must return Array or String, got #{xattrs.inspect}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Parse mount arguments and options
|
31
|
+
#
|
32
|
+
# @param [Array<String>] argv
|
33
|
+
# normalised fuse options
|
34
|
+
#
|
35
|
+
# @param [Array<Symbol>] local_opts local options
|
36
|
+
# if these are found in the argv entry following "-o" they are removed
|
37
|
+
# from argv, ie so argv is a clean set of options that can be passed
|
38
|
+
# to {RFuse::Fuse} or {RFuse::FuseDelegator}
|
39
|
+
#
|
40
|
+
#
|
41
|
+
# @return [Hash<Symbol,String|Boolean>]
|
42
|
+
# the extracted options
|
43
|
+
#
|
44
|
+
# @since 1.0.3
|
45
|
+
#
|
46
|
+
# Fuse itself will normalise arguments
|
47
|
+
#
|
48
|
+
# mount -t fuse </path/to/fs.rb>#<device> mountpoint [options...]
|
49
|
+
# mount.fuse </path/to/fs.rb>#<device> mountpoint [options...]
|
50
|
+
#
|
51
|
+
# both result in the following command execution
|
52
|
+
#
|
53
|
+
# /path/to/fs.rb [device] mountpoint [-h] [-d] [-o [opt,optkey=value,...]]
|
54
|
+
#
|
55
|
+
# which this method will parse into a Hash with the following special keys
|
56
|
+
#
|
57
|
+
# * `:device` - the optional mount device, removed from argv if present
|
58
|
+
# * `:mountpoint` - required mountpoint
|
59
|
+
# * `:help` - if -h was supplied - will print help text (and not mount the filesystem!)
|
60
|
+
# * `:debug` - if -d (or -o debug) was supplied - will print debug output from the underlying FUSE library
|
61
|
+
#
|
62
|
+
# and any other supplied options.
|
63
|
+
#
|
64
|
+
# @example
|
65
|
+
# argv = [ "some/device", "/mnt/point", "-h", "-o", "debug,myfs=aValue" ]
|
66
|
+
# options = RFuse.parse_options(argv,:myfs)
|
67
|
+
#
|
68
|
+
# # options ==
|
69
|
+
# { :device => "some/device",
|
70
|
+
# :mountpoint => "/mnt/point",
|
71
|
+
# :help => true,
|
72
|
+
# :debug => true,
|
73
|
+
# :myfs => "aValue"
|
74
|
+
# }
|
75
|
+
# # and argv ==
|
76
|
+
# [ "/mnt/point","-h","-o","debug" ]
|
77
|
+
#
|
78
|
+
# fs = create_filesystem(options)
|
79
|
+
# fuse = RFuse::FuseDelegator.new(fs,*ARGV)
|
80
|
+
#
|
81
|
+
def self.parse_options(argv, *local_opts, desc: nil, exec: $0)
|
82
|
+
|
83
|
+
def desc.fuse_help
|
84
|
+
self
|
85
|
+
end if desc && !desc.empty?
|
86
|
+
|
87
|
+
argv.unshift(exec) unless argv.size >= 2 && argv[0..1].none? { |a| a.start_with?('-') }
|
88
|
+
run_args = FFI::Libfuse::Main.fuse_parse_cmdline(*argv, handler: desc)
|
89
|
+
|
90
|
+
run_args[:help] = true if run_args[:show_help] # compatibility
|
91
|
+
|
92
|
+
device_opts = { 'subtype=' => :device, 'fsname=' => :device }
|
93
|
+
local_opt_conf = local_opts.each.with_object(device_opts) do |o, conf|
|
94
|
+
conf.merge!({ "#{o}=" => o.to_sym, "#{o}" => o.to_sym })
|
21
95
|
end
|
22
96
|
|
23
|
-
|
24
|
-
#
|
25
|
-
# @param [Array<String>] argv
|
26
|
-
# normalised fuse options
|
27
|
-
#
|
28
|
-
# @param [Array<Symbol>] local_opts local options
|
29
|
-
# if these are found in the argv entry following "-o" they are removed
|
30
|
-
# from argv, ie so argv is a clean set of options that can be passed
|
31
|
-
# to {RFuse::Fuse} or {RFuse::FuseDelegator}
|
32
|
-
#
|
33
|
-
#
|
34
|
-
# @return [Hash<Symbol,String|Boolean>]
|
35
|
-
# the extracted options
|
36
|
-
#
|
37
|
-
# @since 1.0.3
|
38
|
-
#
|
39
|
-
# Fuse itself will normalise arguments
|
40
|
-
#
|
41
|
-
# mount -t fuse </path/to/fs.rb>#<device> mountpoint [options...]
|
42
|
-
# mount.fuse </path/to/fs.rb>#<device> mountpoint [options...]
|
43
|
-
#
|
44
|
-
# both result in the following command execution
|
45
|
-
#
|
46
|
-
# /path/to/fs.rb [device] mountpoint [-h] [-d] [-o [opt,optkey=value,...]]
|
47
|
-
#
|
48
|
-
# which this method will parse into a Hash with the following special keys
|
49
|
-
#
|
50
|
-
# * `:device` - the optional mount device, removed from argv if present
|
51
|
-
# * `:mountpoint` - required mountpoint
|
52
|
-
# * `:help` - if -h was supplied - will print help text (and not mount the filesystem!)
|
53
|
-
# * `:debug` - if -d (or -o debug) was supplied - will print debug output from the underlying FUSE library
|
54
|
-
#
|
55
|
-
# and any other supplied options.
|
56
|
-
#
|
57
|
-
# @example
|
58
|
-
# ARGV = [ "some/device", "/mnt/point", "-h", "-o", "debug,myfs=aValue" ]
|
59
|
-
# options = RFuse.parse_options(ARGV,:myfs)
|
60
|
-
#
|
61
|
-
# # options ==
|
62
|
-
# { :device => "some/device",
|
63
|
-
# :mountpoint => "/mnt/point",
|
64
|
-
# :help => true,
|
65
|
-
# :debug => true,
|
66
|
-
# :myfs => "aValue"
|
67
|
-
# }
|
68
|
-
# # and ARGV ==
|
69
|
-
# [ "/mnt/point","-h","-o","debug" ]
|
70
|
-
#
|
71
|
-
# fs = create_filesystem(options)
|
72
|
-
# fuse = RFuse::FuseDelegator.new(fs,*ARGV)
|
73
|
-
#
|
74
|
-
def self.parse_options(argv,*local_opts)
|
75
|
-
result = Hash.new(nil)
|
76
|
-
|
77
|
-
first_opt_index = (argv.find_index() { |opt| opt =~ /^-.*/ } || argv.length )
|
78
|
-
|
79
|
-
result[:device] = argv.shift if first_opt_index > 1
|
80
|
-
result[:mountpoint] = argv[0] if argv.length > 0
|
81
|
-
|
82
|
-
if argv.include?("-h")
|
83
|
-
result[:help] = true
|
84
|
-
end
|
97
|
+
fuse_args = run_args.delete(:args)
|
85
98
|
|
86
|
-
|
87
|
-
|
88
|
-
|
99
|
+
fuse_args.parse!(local_opt_conf, run_args, **{}) do |key:, value:, data:, **_|
|
100
|
+
data[key] = value
|
101
|
+
key == :device ? :keep : :discard
|
102
|
+
end
|
89
103
|
|
90
|
-
|
104
|
+
argv.replace(fuse_args.argv)
|
105
|
+
# The first arg is actually the device and ignored in future calls to parse opts
|
106
|
+
# (eg during fuse_new, but rfuse used to return the mountpoint here.
|
107
|
+
argv[0] = run_args[:mountpoint]
|
108
|
+
|
109
|
+
run_args
|
110
|
+
end
|
111
|
+
|
112
|
+
# Generate a usage string
|
113
|
+
#
|
114
|
+
# @param [String] device a description of how the device field should be used
|
115
|
+
# @param [String] exec the executable
|
116
|
+
# @return [String] the usage string
|
117
|
+
def self.usage(device = nil, exec = File.basename($PROGRAM_NAME))
|
118
|
+
"Usage:\n #{exec} #{device} mountpoint [-h] [-d] [-o [opt,optkey=value,...]]\n"
|
119
|
+
end
|
120
|
+
|
121
|
+
# Convenience method to launch a fuse filesystem, with nice usage messages and default signal traps
|
122
|
+
#
|
123
|
+
# @param [Array<String>] argv command line arguments
|
124
|
+
# @param [Array<Symbol>] extra_options list of additional options
|
125
|
+
# @param [String] extra_options_usage describing additional option usage
|
126
|
+
# @param [String] device a description of the device field
|
127
|
+
# @param [String] exec the executable file
|
128
|
+
#
|
129
|
+
# @yieldparam [Hash<Symbol,String>] options See {.parse_options}
|
130
|
+
# @yieldparam [Array<String>] argv Cleaned argv See {.parse_options}
|
131
|
+
#
|
132
|
+
# @yieldreturn [Class<Fuse>]
|
133
|
+
# a subclass of {Fuse} that implements your filesystem. Will receive `.new(*extra_options,*argv)`
|
134
|
+
#
|
135
|
+
# @yieldreturn [Class]
|
136
|
+
# a class that implements {FuseDelegator::FUSE_METHODS}. Will receive `.new(*extra_options)`
|
137
|
+
# and the resulting instance sent with `*argv` to {FuseDelegator}
|
138
|
+
#
|
139
|
+
# @yieldreturn [Fuse]
|
140
|
+
# Your initialised (and therefore already mounted) filesystem
|
141
|
+
#
|
142
|
+
# @yieldreturn [Object]
|
143
|
+
# An object that implements the {Fuse} methods, to be passed with `*argv` to {FuseDelegator}
|
144
|
+
#
|
145
|
+
# @yieldreturn [Error]
|
146
|
+
# raise {Error} with appropriate message for invalid options
|
147
|
+
#
|
148
|
+
# @since 1.1.0
|
149
|
+
#
|
150
|
+
# @example
|
151
|
+
#
|
152
|
+
# class MyFuse < Fuse
|
153
|
+
# def initialize(myfs,*argv)
|
154
|
+
# @myfs = myfs # my filesystem local option value
|
155
|
+
# super(*argv)
|
156
|
+
# end
|
157
|
+
# # ... implementations for filesystem methods ...
|
158
|
+
# end
|
159
|
+
#
|
160
|
+
# class MyFSClass # not a subclass of Fuse, FuseDelegator used
|
161
|
+
# def initialize(myfs)
|
162
|
+
# @myfs = myfs # my filesystem local option value
|
163
|
+
# end
|
164
|
+
# # ...
|
165
|
+
# end
|
166
|
+
#
|
167
|
+
# MY_OPTIONS = [ :myfs ]
|
168
|
+
# OPTION_USAGE = " -o myfs=VAL how to use the myfs option"
|
169
|
+
# DEVICE_USAGE = "how to use device arg"
|
170
|
+
#
|
171
|
+
# # Normally from the command line...
|
172
|
+
# ARGV = [ "some/device", "/mnt/point", "-h", "-o", "debug,myfs=aValue" ]
|
173
|
+
#
|
174
|
+
# RFuse.main(ARGV, MY_OPTIONS, OPTION_USAGE, DEVICE_USAGE, $0) do |options, argv|
|
175
|
+
#
|
176
|
+
# # options ==
|
177
|
+
# { :device => "some/device",
|
178
|
+
# :mountpoint => "/mnt/point",
|
179
|
+
# :help => true,
|
180
|
+
# :debug => true,
|
181
|
+
# :myfs => "aValue"
|
182
|
+
# }
|
183
|
+
#
|
184
|
+
# # ... validate options...
|
185
|
+
# raise RFuse::Error, "Bad option" unless options[:myfs]
|
186
|
+
#
|
187
|
+
# # return the filesystem class to be initialised by RFuse
|
188
|
+
# MyFuse
|
189
|
+
# # or
|
190
|
+
# MyFSClass
|
191
|
+
#
|
192
|
+
# # OR take full control over initialisation yourself and return the object
|
193
|
+
# MyFuse.new(options[:myfs],*argv)
|
194
|
+
# # or
|
195
|
+
# MyFSClass.new(options[:myfs])
|
196
|
+
#
|
197
|
+
# end
|
198
|
+
#
|
199
|
+
def self.main(argv = ARGV.dup, extra_options = [], extra_options_usage = nil, device = nil, exec = File.basename($PROGRAM_NAME))
|
200
|
+
begin
|
201
|
+
fuse_help = ['Filesystem options:',extra_options_usage,device,''].compact.join("\n")
|
202
|
+
|
203
|
+
options = parse_options(argv, *extra_options, desc: extra_options_usage && fuse_help, exec: exec)
|
204
|
+
|
205
|
+
unless options[:mountpoint]
|
206
|
+
warn "rfuse: failed, no mountpoint provided"
|
207
|
+
puts usage(device,exec)
|
208
|
+
return nil
|
209
|
+
end
|
210
|
+
|
211
|
+
fs = yield options, argv
|
212
|
+
|
213
|
+
raise Error, 'no filesystem provided' unless fs
|
214
|
+
|
215
|
+
# create can pass the extra options to a constructor so order and existence is important.
|
216
|
+
fs_options = extra_options.each_with_object({}) { |k,h| h[k] = options.delete(k) }
|
217
|
+
fuse = create(argv: argv, fs: fs, options: fs_options)
|
218
|
+
|
219
|
+
return nil if options[:show_help] || options[:show_version]
|
220
|
+
|
221
|
+
raise Error, "filesystem #{fs} not mounted" unless fuse&.mounted?
|
222
|
+
|
223
|
+
fuse.run(**options)
|
224
|
+
rescue Error => e
|
225
|
+
# These errors are produced generally because the user passed bad arguments etc..
|
226
|
+
puts usage(device, exec) unless options[:help]
|
227
|
+
warn "rfuse failed: #{e.message}"
|
228
|
+
nil
|
229
|
+
rescue StandardError => e
|
230
|
+
# These are other errors related the yield block
|
231
|
+
warn e.message
|
232
|
+
warn e.backtrace.join("\n")
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# @private
|
237
|
+
# Helper to create a {Fuse} from variety of #{.main} yield results
|
238
|
+
def self.create(fs:, argv: [], options: {})
|
239
|
+
if fs.is_a?(Fuse)
|
240
|
+
fs
|
241
|
+
elsif fs.is_a?(Class)
|
242
|
+
if Fuse > fs
|
243
|
+
fs.new(*options.values, *argv)
|
244
|
+
else
|
245
|
+
delegate = fs.new(*options.values)
|
246
|
+
FuseDelegator.new(delegate, *argv)
|
247
|
+
end
|
248
|
+
elsif fs
|
249
|
+
FuseDelegator.new(fs, *argv)
|
250
|
+
end
|
251
|
+
end
|
91
252
|
|
92
|
-
|
93
|
-
options = argv[opt_index].split(",")
|
253
|
+
class Fuse
|
94
254
|
|
95
|
-
|
96
|
-
|
255
|
+
attr_reader :mountpoint
|
256
|
+
alias mountname mountpoint
|
97
257
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
258
|
+
def initialize(mountpoint, *argv)
|
259
|
+
@mountpoint = mountpoint
|
260
|
+
@fuse = FFI::Libfuse::Main.fuse_create(mountpoint, *argv, operations: self, private_data: self)
|
261
|
+
end
|
102
262
|
|
103
|
-
|
104
|
-
|
105
|
-
|
263
|
+
# Is the filesystem successfully mounted
|
264
|
+
#
|
265
|
+
# @return [Boolean] true if mounted, false otherwise
|
266
|
+
def mounted?
|
267
|
+
@fuse && @fuse.mounted?
|
268
|
+
end
|
106
269
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
270
|
+
# Convenience method to run a mounted filesystem to completion.
|
271
|
+
#
|
272
|
+
# @param [Array<String|Integer>] signals list of signals to handle.
|
273
|
+
# Default is all available signals. See {#trap_signals}
|
274
|
+
#
|
275
|
+
# @return [void]
|
276
|
+
# @since 1.1.0
|
277
|
+
# @see RFuse.main
|
278
|
+
def run(signals = Signal.list.keys, **run_args)
|
279
|
+
raise Error, 'not mounted' unless @fuse
|
280
|
+
trap_signals(*signals)
|
281
|
+
@fuse.run(traps: @traps, **run_args)
|
282
|
+
end
|
113
283
|
|
114
|
-
|
284
|
+
def loop
|
285
|
+
run([] ,single_thread: true, foreground: true)
|
115
286
|
end
|
116
287
|
|
117
|
-
#
|
118
|
-
|
119
|
-
|
120
|
-
# @param [String] exec the executable
|
121
|
-
# @return [String] the usage string
|
122
|
-
def self.usage(device=nil,exec=File.basename($0))
|
123
|
-
"Usage:\n #{exec} #{device} mountpoint [-h] [-d] [-o [opt,optkey=value,...]]\n"
|
288
|
+
# Stop processing
|
289
|
+
def exit
|
290
|
+
@fuse&.exit&.join
|
124
291
|
end
|
292
|
+
alias :unmount :exit
|
125
293
|
|
126
|
-
#
|
294
|
+
# Set traps
|
127
295
|
#
|
128
|
-
#
|
129
|
-
# @param [Array<Symbol>] extra_options list of additional options
|
130
|
-
# @param [String] extra_options_usage describing additional option usage
|
131
|
-
# @param [String] device a description of the device field
|
132
|
-
# @param [String] exec the executable file
|
296
|
+
# The filesystem supports a signal by providing a `sig<name>` method. eg {#sigint}
|
133
297
|
#
|
134
|
-
#
|
135
|
-
#
|
298
|
+
# The fuse {#loop} is notified of the signal via the self-pipe trick, and calls the corresponding
|
299
|
+
# `sig<name>` method, after any current filesystem operation completes.
|
136
300
|
#
|
137
|
-
#
|
138
|
-
# a subclass of {Fuse} that implements your filesystem. Will receive `.new(*extra_options,*argv)`
|
301
|
+
# This method will not override traps that have previously been set to something other than "DEFAULT"
|
139
302
|
#
|
140
|
-
#
|
141
|
-
# a class that implements {FuseDelegator::FUSE_METHODS}. Will receive `.new(*extra_options)`
|
142
|
-
# and the resulting instance sent with `*argv` to {FuseDelegator}
|
303
|
+
# Note: {Fuse} itself provides {#sigterm} and {#sigint}.
|
143
304
|
#
|
144
|
-
# @
|
145
|
-
#
|
305
|
+
# @param [Array<String>] signames
|
306
|
+
# List of signal names to set traps for, if the filesystem has methods to handle them.
|
307
|
+
# Use `Signal.list.keys` to try all available signals.
|
146
308
|
#
|
147
|
-
# @
|
148
|
-
# An object that implements the {Fuse} methods, to be passed with `*argv` to {FuseDelegator}
|
149
|
-
#
|
150
|
-
# @yieldreturn [Error]
|
151
|
-
# raise {Error} with appropriate message for invalid options
|
309
|
+
# @return [Array<String>] List of signal names that traps have been set for.
|
152
310
|
#
|
153
311
|
# @since 1.1.0
|
154
312
|
#
|
155
313
|
# @example
|
156
|
-
#
|
157
|
-
#
|
158
|
-
#
|
159
|
-
# @myfs = myfs # my filesystem local option value
|
160
|
-
# super(*argv)
|
314
|
+
# class MyFS < Fuse
|
315
|
+
# def sighup()
|
316
|
+
# # do something on HUP signal
|
161
317
|
# end
|
162
|
-
# # ... implementations for filesystem methods ...
|
163
318
|
# end
|
164
319
|
#
|
165
|
-
#
|
166
|
-
# def initialize(myfs)
|
167
|
-
# @myfs = myfs # my filesystem local option value
|
168
|
-
# end
|
169
|
-
# # ...
|
170
|
-
# end
|
171
|
-
#
|
172
|
-
# MY_OPTIONS = [ :myfs ]
|
173
|
-
# OPTION_USAGE = " -o myfs=VAL how to use the myfs option"
|
174
|
-
# DEVICE_USAGE = "how to use device arg"
|
175
|
-
#
|
176
|
-
# # Normally from the command line...
|
177
|
-
# ARGV = [ "some/device", "/mnt/point", "-h", "-o", "debug,myfs=aValue" ]
|
178
|
-
#
|
179
|
-
# RFuse.main(ARGV, MY_OPTIONS, OPTION_USAGE, DEVICE_USAGE, $0) do |options, argv|
|
180
|
-
#
|
181
|
-
# # options ==
|
182
|
-
# { :device => "some/device",
|
183
|
-
# :mountpoint => "/mnt/point",
|
184
|
-
# :help => true,
|
185
|
-
# :debug => true,
|
186
|
-
# :myfs => "aValue"
|
187
|
-
# }
|
188
|
-
#
|
189
|
-
# # ... validate options...
|
190
|
-
# raise RFuse::Error, "Bad option" unless options[:myfs]
|
191
|
-
#
|
192
|
-
# # return the filesystem class to be initialised by RFuse
|
193
|
-
# MyFuse
|
194
|
-
# # or
|
195
|
-
# MyFSClass
|
196
|
-
#
|
197
|
-
# # OR take full control over initialisation yourself and return the object
|
198
|
-
# MyFuse.new(options[:myfs],*argv)
|
199
|
-
# # or
|
200
|
-
# MyFSClass.new(options[:myfs])
|
320
|
+
# fuse = MyFS.new(*args)
|
201
321
|
#
|
322
|
+
# if fuse.mounted?
|
323
|
+
# # Below will result in (effectively) Signal.trap("HUP") { fuse.sighup() }
|
324
|
+
# fuse.trap_signals("HUP","USR1") # ==> ["HUP"]
|
325
|
+
# fuse.loop()
|
202
326
|
# end
|
203
327
|
#
|
204
|
-
def
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
unless options[:mountpoint]
|
209
|
-
$stderr.puts "rfuse: failed, no mountpoint provided"
|
210
|
-
puts usage(device,exec)
|
211
|
-
return nil
|
212
|
-
end
|
213
|
-
|
214
|
-
if options[:help]
|
215
|
-
puts usage(device,exec)
|
216
|
-
# TODO: In Fuse 3.0 this looks like it will move to $stdout
|
217
|
-
help_io = STDERR
|
218
|
-
help_io.puts "Fuse options: (#{FUSE_MAJOR_VERSION}.#{FUSE_MINOR_VERSION})"
|
219
|
-
help_io.puts " -h help - print this help output"
|
220
|
-
help_io.puts " -d |-o debug enable internal FUSE debug output"
|
221
|
-
help_io.puts ""
|
222
|
-
help_io.flush()
|
223
|
-
|
224
|
-
# Cause Fuse kernel Options to print, but don't actually start a filesystem
|
225
|
-
Fuse.new(*argv)
|
226
|
-
|
227
|
-
if extra_options_usage
|
228
|
-
help_io.puts "Filesystem options:"
|
229
|
-
help_io.puts extra_options_usage
|
230
|
-
end
|
231
|
-
|
232
|
-
return nil
|
233
|
-
end
|
234
|
-
|
235
|
-
begin
|
236
|
-
fs = yield options,argv
|
237
|
-
|
238
|
-
raise Error, "no filesystem provided" unless fs
|
239
|
-
|
240
|
-
fuse = create(fs,argv,options,extra_options)
|
241
|
-
|
242
|
-
raise Error, "filesystem #{fs} not mounted" unless fuse && fuse.mounted?
|
243
|
-
|
244
|
-
fuse.run
|
245
|
-
rescue Error => fuse_ex
|
246
|
-
# These errors are produced generally because the user passed bad arguments etc..
|
247
|
-
puts usage(device,exec) unless options[:help]
|
248
|
-
$stderr.puts "rfuse failed: #{fuse_ex.message}"
|
249
|
-
return nil
|
250
|
-
rescue => ex
|
251
|
-
# These are other errors related the yield block
|
252
|
-
$stderr.puts ex.message
|
253
|
-
$stderr.puts ex.backtrace.join("\n")
|
254
|
-
end
|
328
|
+
def trap_signals(*signames)
|
329
|
+
signames = signames.map { |n| n.to_s.upcase }.map { |n| n.start_with?('SIG') ? n[3..-1] : n }
|
330
|
+
signames.keep_if { |n| Signal.list[n] && respond_sigmethod?(sigmethod(n)) }
|
255
331
|
|
332
|
+
@traps ||= {}
|
333
|
+
@traps.merge!(signames.map { |n| [ n, ->() { call_sigmethod(sigmethod(n)) }]}.to_h)
|
334
|
+
@traps.keys
|
256
335
|
end
|
257
336
|
|
337
|
+
private
|
258
338
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
extra_option_values = extra_options.map { |o| options[o] }
|
266
|
-
if Fuse > fs
|
267
|
-
fs.new(*extra_option_values,*argv)
|
268
|
-
else
|
269
|
-
delegate = fs.new(*extra_option_values)
|
270
|
-
FuseDelegator.new(delegate,*argv)
|
271
|
-
end
|
272
|
-
elsif fs
|
273
|
-
FuseDelegator.new(fs,*argv)
|
274
|
-
end
|
339
|
+
def respond_sigmethod?(sigmethod)
|
340
|
+
respond_to?(sigmethod)
|
341
|
+
end
|
342
|
+
|
343
|
+
def sigmethod(signame)
|
344
|
+
"sig#{signame.downcase}".to_sym
|
275
345
|
end
|
276
346
|
|
277
|
-
|
278
|
-
|
279
|
-
class Fuse
|
280
|
-
|
281
|
-
# Convenience method to run a mounted filesystem to completion.
|
282
|
-
#
|
283
|
-
# @param [Array<String|Integer>] signals list of signals to handle.
|
284
|
-
# Default is all available signals. See {#trap_signals}
|
285
|
-
#
|
286
|
-
# @return [void]
|
287
|
-
# @since 1.1.0
|
288
|
-
# @see RFuse.main
|
289
|
-
def run(signals=Signal.list.keys)
|
290
|
-
if mounted?
|
291
|
-
begin
|
292
|
-
traps = trap_signals(*signals)
|
293
|
-
self.loop()
|
294
|
-
ensure
|
295
|
-
traps.each { |t| Signal.trap(t,"DEFAULT") }
|
296
|
-
unmount()
|
297
|
-
end
|
298
|
-
end
|
299
|
-
end
|
300
|
-
|
301
|
-
# Main processing loop
|
302
|
-
#
|
303
|
-
# Use {#exit} to stop processing (or externally call fusermount -u)
|
304
|
-
#
|
305
|
-
# Other ruby threads can continue while loop is running, however
|
306
|
-
# no thread can operate on the filesystem itself (ie with File or Dir methods)
|
307
|
-
#
|
308
|
-
# @return [void]
|
309
|
-
# @raise [RFuse::Error] if already running or not mounted
|
310
|
-
def loop()
|
311
|
-
raise RFuse::Error, "Already running!" if @running
|
312
|
-
raise RFuse::Error, "FUSE not mounted" unless mounted?
|
313
|
-
@running = true
|
314
|
-
while @running do
|
315
|
-
begin
|
316
|
-
ready, ignore, errors = IO.select([@fuse_io,@pr],[],[@fuse_io])
|
317
|
-
|
318
|
-
if ready.include?(@pr)
|
319
|
-
|
320
|
-
signo = @pr.read_nonblock(1).unpack("c")[0]
|
321
|
-
|
322
|
-
# Signal.signame exist in Ruby 2, but returns horrible errors for non-signals in 2.1.0
|
323
|
-
if (signame = Signal.list.invert[signo])
|
324
|
-
call_sigmethod(sigmethod(signame))
|
325
|
-
end
|
326
|
-
|
327
|
-
elsif errors.include?(@fuse_io)
|
328
|
-
|
329
|
-
@running = false
|
330
|
-
raise RFuse::Error, "FUSE error"
|
331
|
-
|
332
|
-
elsif ready.include?(@fuse_io)
|
333
|
-
if process() < 0
|
334
|
-
# Fuse has been unmounted externally
|
335
|
-
# TODO: mounted? should now return false
|
336
|
-
# fuse_exited? is not true...
|
337
|
-
@running = false
|
338
|
-
end
|
339
|
-
end
|
340
|
-
rescue Errno::EWOULDBLOCK, Errno::EAGAIN
|
341
|
-
#oh well...
|
342
|
-
end
|
343
|
-
end
|
344
|
-
end
|
345
|
-
|
346
|
-
# Stop processing {#loop}
|
347
|
-
# eg called from signal handlers, or some other monitoring thread
|
348
|
-
def exit
|
349
|
-
@running = false
|
350
|
-
send_signal(-1)
|
351
|
-
end
|
352
|
-
|
353
|
-
# Set traps
|
354
|
-
#
|
355
|
-
# The filesystem supports a signal by providing a `sig<name>` method. eg {#sigint}
|
356
|
-
#
|
357
|
-
# The fuse {#loop} is notified of the signal via the self-pipe trick, and calls the corresponding
|
358
|
-
# `sig<name>` method, after any current filesystem operation completes.
|
359
|
-
#
|
360
|
-
# This method will not override traps that have previously been set to something other than "DEFAULT"
|
361
|
-
#
|
362
|
-
# Note: {Fuse} itself provides {#sigterm} and {#sigint}.
|
363
|
-
#
|
364
|
-
# @param [Array<String>] signames
|
365
|
-
# List of signal names to set traps for, if the filesystem has methods to handle them.
|
366
|
-
# Use `Signal.list.keys` to try all available signals.
|
367
|
-
#
|
368
|
-
# @return [Array<String>] List of signal names that traps have been set for.
|
369
|
-
#
|
370
|
-
# @since 1.1.0
|
371
|
-
#
|
372
|
-
# @example
|
373
|
-
# class MyFS < Fuse
|
374
|
-
# def sighup()
|
375
|
-
# # do something on HUP signal
|
376
|
-
# end
|
377
|
-
# end
|
378
|
-
#
|
379
|
-
# fuse = MyFS.new(*args)
|
380
|
-
#
|
381
|
-
# if fuse.mounted?
|
382
|
-
# # Below will result in (effectively) Signal.trap("HUP") { fuse.sighup() }
|
383
|
-
# fuse.trap_signals("HUP","USR1") # ==> ["HUP"]
|
384
|
-
# fuse.loop()
|
385
|
-
# end
|
386
|
-
#
|
387
|
-
def trap_signals(*signames)
|
388
|
-
signames.map { |n| n.to_s.upcase }.map { |n| n.start_with?("SIG") ? n[3..-1] : n }.select do |signame|
|
389
|
-
next false unless respond_sigmethod?(sigmethod(signame)) && signo = Signal.list[signame]
|
390
|
-
|
391
|
-
next true if (prev = Signal.trap(signo) { |signo| send_signal(signo) }) == "DEFAULT"
|
392
|
-
|
393
|
-
Signal.trap(signo,prev)
|
394
|
-
false
|
395
|
-
end
|
396
|
-
end
|
397
|
-
|
398
|
-
# Default signal handler to exit on TERM/INT
|
399
|
-
# @return [void]
|
400
|
-
# @see #trap_signals
|
401
|
-
def sigterm
|
402
|
-
@running = false
|
403
|
-
end
|
404
|
-
alias :sigint :sigterm
|
405
|
-
|
406
|
-
private
|
407
|
-
|
408
|
-
# Called by the C iniitialize
|
409
|
-
# afer the filesystem has been mounted successfully
|
410
|
-
def ruby_initialize
|
411
|
-
@running = false
|
412
|
-
|
413
|
-
# Self-pipe for handling signals and exit
|
414
|
-
@pr,@pw = IO.pipe()
|
415
|
-
|
416
|
-
# The FD was created by FUSE so we don't want
|
417
|
-
# ruby to do anything with it during GC
|
418
|
-
@fuse_io = IO.for_fd(fd(),"r",:autoclose => false)
|
419
|
-
end
|
420
|
-
|
421
|
-
# Called by C unmount before doing all the FUSE stuff
|
422
|
-
def ruby_unmount
|
423
|
-
@pr.close if @pr && !@pr.closed?
|
424
|
-
@pw.close if @pw && !@pw.closed?
|
425
|
-
|
426
|
-
# Ideally we want this IO to avoid autoclosing at GC, but
|
427
|
-
# in Ruby 1.8 we have no way to do that. A work around is to close
|
428
|
-
# the IO here. FUSE won't necessarily like that but it is the best
|
429
|
-
# we can do
|
430
|
-
@fuse_io.close() if @fuse_io && !@fuse_io.closed? && @fuse_io.autoclose?
|
431
|
-
end
|
432
|
-
|
433
|
-
def call_sigmethod(sigmethod)
|
434
|
-
send(sigmethod)
|
435
|
-
end
|
436
|
-
|
437
|
-
def respond_sigmethod?(sigmethod)
|
438
|
-
respond_to?(sigmethod)
|
439
|
-
end
|
440
|
-
|
441
|
-
def sigmethod(signame)
|
442
|
-
"sig#{signame.downcase}".to_sym
|
443
|
-
end
|
444
|
-
|
445
|
-
def send_signal(signo)
|
446
|
-
@pw.write([signo].pack("c")) unless !@pw || @pw.closed?
|
447
|
-
end
|
347
|
+
def call_sigmethod(sigmethod)
|
348
|
+
send(sigmethod)
|
448
349
|
end
|
449
350
|
|
450
|
-
|
451
|
-
|
452
|
-
#or inherit from {Fuse}
|
453
|
-
class FuseDelegator < Fuse
|
454
|
-
|
455
|
-
# Available fuse methods -see http://fuse.sourceforge.net/doxygen/structfuse__operations.html
|
456
|
-
# Note :getdir and :utime are deprecated
|
457
|
-
# :ioctl, :poll are not implemented in the C extension
|
458
|
-
FUSE_METHODS = [ :getattr, :readlink, :getdir, :mknod, :mkdir,
|
459
|
-
:unlink, :rmdir, :symlink, :rename, :link,
|
460
|
-
:chmod, :chown, :truncate, :utime, :open,
|
461
|
-
:create, :read, :write, :statfs, :flush,
|
462
|
-
:release, :fsync, :setxattr, :getxattr, :listxattr,:removexattr,
|
463
|
-
:opendir, :readdir, :releasedir, :fsycndir,
|
464
|
-
:init, :destroy, :access, :ftruncate, :fgetattr, :lock,
|
465
|
-
:utimens, :bmap, :ioctl, :poll ]
|
466
|
-
|
467
|
-
# @param [Object] fuse_object your filesystem object that responds to fuse methods
|
468
|
-
# @param [String] mountpoint existing directory where the filesystem will be mounted
|
469
|
-
# @param [String...] options fuse mount options (use "-h" to see a list)
|
470
|
-
#
|
471
|
-
# Create and mount a filesystem
|
472
|
-
#
|
473
|
-
def initialize(fuse_object,mountpoint,*options)
|
474
|
-
@fuse_delegate = fuse_object
|
475
|
-
define_fuse_methods(fuse_object)
|
476
|
-
@debug = false
|
477
|
-
self.debug=$DEBUG
|
478
|
-
super(mountpoint,options)
|
479
|
-
end
|
480
|
-
|
481
|
-
# USR1 sig handler - toggle debugging of fuse methods
|
482
|
-
# @return [void]
|
483
|
-
def sigusr1()
|
484
|
-
self.debug=!@debug
|
485
|
-
end
|
486
|
-
|
487
|
-
# RFuse Debugging status
|
488
|
-
#
|
489
|
-
# @note this is independent of the Fuse kernel module debugging enabled with the "-d" mount option
|
490
|
-
#
|
491
|
-
# @return [Boolean] whether debugging information should be printed to $stderr around each fuse method.
|
492
|
-
# Defaults to $DEBUG
|
493
|
-
# @since 1.1.0
|
494
|
-
# @see #sigusr1
|
495
|
-
def debug?
|
496
|
-
@debug
|
497
|
-
end
|
498
|
-
|
499
|
-
# Set debugging on or off
|
500
|
-
# @param [Boolean] value enable or disable debugging
|
501
|
-
# @return [Boolean] the new debug value
|
502
|
-
# @since 1.1.0
|
503
|
-
def debug=(value)
|
504
|
-
value = value ? true : false
|
505
|
-
if @debug && !value
|
506
|
-
$stderr.puts "=== #{ self }.debug=false"
|
507
|
-
elsif !@debug && value
|
508
|
-
$stderr.puts "=== #{ self }.debug=true"
|
509
|
-
end
|
510
|
-
@debug = value
|
511
|
-
end
|
512
|
-
|
513
|
-
# @private
|
514
|
-
def to_s
|
515
|
-
"#{self.class.name}::#{@fuse_delegate}"
|
516
|
-
end
|
517
|
-
private
|
518
|
-
|
519
|
-
def define_fuse_methods(fuse_object)
|
520
|
-
FUSE_METHODS.each do |method|
|
521
|
-
if fuse_object.respond_to?(method)
|
522
|
-
method_name = method.id2name
|
523
|
-
instance_eval(<<-EOM, "(__FUSE_DELEGATE__)",1)
|
524
|
-
def #{method_name} (*args,&block)
|
525
|
-
begin
|
526
|
-
$stderr.puts "==> \#{ self }.#{ method_name }(\#{args.inspect })" if debug?
|
527
|
-
result = @fuse_delegate.send(:#{method_name},*args,&block)
|
528
|
-
$stderr.puts "<== \#{ self }.#{ method_name }()" if debug?
|
529
|
-
result
|
530
|
-
rescue => ex
|
531
|
-
$@.delete_if{|s| /^\\(__FUSE_DELEGATE__\\):/ =~ s}
|
532
|
-
$stderr.puts(ex.message) unless ex.respond_to?(:errno) || debug?
|
533
|
-
Kernel::raise
|
534
|
-
end
|
535
|
-
end
|
536
|
-
EOM
|
537
|
-
end
|
538
|
-
end
|
539
|
-
end
|
540
|
-
|
541
|
-
def call_sigmethod(sigmethod)
|
542
|
-
$stderr.puts "==> #{ self }.#{ sigmethod }()" if debug?
|
543
|
-
dlg = @fuse_delegate.respond_to?(sigmethod) ? @fuse_delegate : self
|
544
|
-
dlg.send(sigmethod)
|
545
|
-
$stderr.puts "<== #{ self }.#{ sigmethod }()" if debug?
|
546
|
-
end
|
547
|
-
|
548
|
-
def respond_sigmethod?(sigmethod)
|
549
|
-
@fuse_delegate.respond_to?(sigmethod) || self.respond_to?(sigmethod)
|
550
|
-
end
|
551
|
-
|
552
|
-
|
553
|
-
end #class FuseDelegator
|
554
|
-
|
555
|
-
class Context
|
556
|
-
# @private
|
557
|
-
def to_s
|
558
|
-
"Context::u#{uid},g#{gid},p#{pid}"
|
559
|
-
end
|
351
|
+
def self.inherited(klass)
|
352
|
+
klass.include(Adapter) unless klass.ancestors.include?(Adapter)
|
560
353
|
end
|
354
|
+
end
|
561
355
|
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
356
|
+
# This class is useful to make your filesystem implementation
|
357
|
+
# debuggable and testable without needing to mount an actual filesystem
|
358
|
+
# or inherit from {Fuse}
|
359
|
+
class FuseDelegator < Fuse
|
360
|
+
|
361
|
+
# @param [Object] fuse_object your filesystem object that responds to fuse methods
|
362
|
+
# @param [String] mountpoint existing directory where the filesystem will be mounted
|
363
|
+
# @param [String...] options fuse mount options (use "-h" to see a list)
|
364
|
+
#
|
365
|
+
# Create and mount a filesystem
|
366
|
+
#
|
367
|
+
def initialize(fuse_object, mountpoint, *options)
|
368
|
+
@fuse_delegate = fuse_object
|
369
|
+
@debug = $DEBUG
|
370
|
+
super(mountpoint, *options)
|
371
|
+
end
|
372
|
+
|
373
|
+
# USR1 sig handler - toggle debugging of fuse methods
|
374
|
+
# @return [void]
|
375
|
+
def sigusr1
|
376
|
+
@debug = !debug?
|
377
|
+
end
|
378
|
+
|
379
|
+
def debug?
|
380
|
+
@debug
|
567
381
|
end
|
568
382
|
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
"FileInfo::fh->#{fh}"
|
573
|
-
end
|
383
|
+
# @private
|
384
|
+
def to_s
|
385
|
+
"#{self.class.name}::#{@fuse_delegate}"
|
574
386
|
end
|
575
387
|
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
S_IFCHR = 0020000
|
584
|
-
# Block device
|
585
|
-
S_IFBLK = 0060000
|
586
|
-
# Regular file
|
587
|
-
S_IFREG = 0100000
|
588
|
-
# FIFO.
|
589
|
-
S_IFIFO = 0010000
|
590
|
-
# Symbolic link
|
591
|
-
S_IFLNK = 0120000
|
592
|
-
# Socket
|
593
|
-
S_IFSOCK = 0140000
|
594
|
-
|
595
|
-
# @param [Fixnum] mode file permissions
|
596
|
-
# @param [Hash<Symbol,Fixnum>] values initial values for other attributes
|
597
|
-
#
|
598
|
-
# @return [Stat] representing a directory
|
599
|
-
def self.directory(mode=0,values = { })
|
600
|
-
return self.new(S_IFDIR,mode,values)
|
601
|
-
end
|
602
|
-
|
603
|
-
# @param [Fixnum] mode file permissions
|
604
|
-
# @param [Hash<Symbol,Fixnum>] values initial values for other attributes
|
605
|
-
#
|
606
|
-
# @return [Stat] representing a regular file
|
607
|
-
def self.file(mode=0,values = { })
|
608
|
-
return self.new(S_IFREG,mode,values)
|
609
|
-
end
|
610
|
-
|
611
|
-
# @return [Integer] see stat(2)
|
612
|
-
attr_accessor :uid,:gid,:mode,:size, :dev,:ino,:nlink,:rdev,:blksize,:blocks
|
613
|
-
|
614
|
-
# @return [Integer, Time] see stat(2)
|
615
|
-
attr_accessor :atime,:mtime,:ctime
|
616
|
-
|
617
|
-
def initialize(type,permissions,values = { })
|
618
|
-
values[:mode] = ((type & S_IFMT) | (permissions & 07777))
|
619
|
-
@uid,@gid,@size,@mode,@atime,@mtime,@ctime,@dev,@ino,@nlink,@rdev,@blksize,@blocks = Array.new(13,0)
|
620
|
-
values.each_pair do |k,v|
|
621
|
-
instance_variable_set("@#{ k }",v)
|
622
|
-
end
|
623
|
-
end
|
388
|
+
def fuse_respond_to?(fuse_method)
|
389
|
+
return false unless @fuse_delegate.respond_to?(fuse_method)
|
390
|
+
|
391
|
+
m = @fuse_delegate.method(fuse_method)
|
392
|
+
m = m.super_method while m && FFI::Libfuse::Adapter.include?(m.owner)
|
393
|
+
|
394
|
+
m && true
|
624
395
|
end
|
625
396
|
|
626
|
-
|
627
|
-
|
628
|
-
|
397
|
+
def respond_to_missing?(method, private=false)
|
398
|
+
FFI::Libfuse::FuseOperations.fuse_callbacks.include?(method) ? @fuse_delegate.respond_to?(method, private) : super
|
399
|
+
end
|
629
400
|
|
630
|
-
|
631
|
-
|
401
|
+
def method_missing(method_name, *args, &block)
|
402
|
+
return super unless FFI::Libfuse::FuseOperations.fuse_callbacks.include?(method_name) && @fuse_delegate.respond_to?(method_name)
|
403
|
+
begin
|
404
|
+
$stderr.puts "==> \#{ self }.#{method_name}(\#{args.inspect })" if debug?
|
405
|
+
result = @fuse_delegate.send(method_name,*args,&block)
|
406
|
+
$stderr.puts "<== \#{ self }.#{method_name}()" if debug?
|
407
|
+
result
|
408
|
+
rescue => ex
|
409
|
+
$@.delete_if{|s| /^\\(__FUSE_DELEGATE__\\):/ =~ s}
|
410
|
+
$stderr.puts(ex.message) unless ex.respond_to?(:errno) || debug?
|
411
|
+
Kernel::raise
|
412
|
+
end
|
413
|
+
end
|
632
414
|
|
633
|
-
|
634
|
-
|
415
|
+
def call_sigmethod(sigmethod)
|
416
|
+
warn "==> #{self}.#{sigmethod}()" if debug?
|
417
|
+
dlg = @fuse_delegate.respond_to?(sigmethod) ? @fuse_delegate : self
|
418
|
+
dlg.send(sigmethod)
|
419
|
+
warn "<== #{self}.#{sigmethod}()" if debug?
|
420
|
+
end
|
635
421
|
|
636
|
-
|
637
|
-
|
638
|
-
@f_bsize, @f_frsize, @f_blocks, @f_bfree, @f_bavail, @f_files, @f_ffree, @f_favail,@f_fsid, @f_flag,@f_namemax = Array.new(13,0)
|
639
|
-
values.each_pair do |k,v|
|
640
|
-
prefix = k.to_s.start_with?("f_") ? "" : "f_"
|
641
|
-
instance_variable_set("@#{prefix}#{k}",v)
|
642
|
-
end
|
643
|
-
end
|
422
|
+
def respond_sigmethod?(sigmethod)
|
423
|
+
@fuse_delegate.respond_to?(sigmethod) || respond_to?(sigmethod)
|
644
424
|
end
|
645
|
-
|
425
|
+
|
426
|
+
# Remove Kernel:open etc..
|
427
|
+
FFI::Libfuse::FuseOperations.fuse_callbacks.each { |c| undef_method(c) rescue nil }
|
428
|
+
end
|
429
|
+
|
430
|
+
end
|