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.
data/lib/rfuse.rb CHANGED
@@ -1,645 +1,430 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'fcntl'
2
- require 'rfuse/version'
3
- require 'rfuse/rfuse'
4
- require 'rfuse/compat'
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
- # @private
10
- # Used by listxattr
11
- def self.packxattr(xattrs)
12
- case xattrs
13
- when Array
14
- xattrs.join("\000").concat("\000")
15
- when String
16
- #assume already \0 separated list of keys
17
- xattrs
18
- else
19
- raise Error, ":listxattr must return Array or String, got #{xattrs.inspect}"
20
- end
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
- # Parse mount arguments and options
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
- if argv.include?("-d")
87
- result[:debug] = true
88
- end
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
- opt_index = ( argv.find_index("-o") || -1 ) + 1
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
- if opt_index > 1 && opt_index < argv.length
93
- options = argv[opt_index].split(",")
253
+ class Fuse
94
254
 
95
- options.delete_if() do |opt|
96
- opt.strip!
255
+ attr_reader :mountpoint
256
+ alias mountname mountpoint
97
257
 
98
- opt,value = opt.split("=",2)
99
- value = true unless value
100
- opt_sym = opt.to_sym
101
- result[opt_sym] = value
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
- #result of delete if
104
- local_opts.include?(opt_sym)
105
- end
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
- if options.empty?
108
- argv.slice!(opt_index - 1,2)
109
- else
110
- argv[opt_index] = options.join(",")
111
- end
112
- end
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
- result
284
+ def loop
285
+ run([] ,single_thread: true, foreground: true)
115
286
  end
116
287
 
117
- # Generate a usage string
118
- #
119
- # @param [String] device a description of how the device field should be used
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
- # Convenience method to launch a fuse filesystem, with nice usage messages and default signal traps
294
+ # Set traps
127
295
  #
128
- # @param [Array<String>] argv command line arguments
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
- # @yieldparam [Hash<Symbol,String>] options See {.parse_options}
135
- # @yieldparam [Array<String>] argv Cleaned argv See {.parse_options}
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
- # @yieldreturn [Class<Fuse>]
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
- # @yieldreturn [Class]
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
- # @yieldreturn [Fuse]
145
- # Your initialised (and therefore already mounted) filesystem
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
- # @yieldreturn [Object]
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
- # class MyFuse < Fuse
158
- # def initialize(myfs,*argv)
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
- # class MyFSClass # not a subclass of Fuse, FuseDelegator used
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 self.main(argv=ARGV,extra_options=[],extra_options_usage=nil,device=nil,exec=File.basename($0))
205
-
206
- options = parse_options(argv,*extra_options)
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
- # @private
260
- # Helper to create a {Fuse} from variety of #{.main} yield results
261
- def self.create(fs, argv=[], options = {}, extra_options = [])
262
- if fs.kind_of?(Fuse)
263
- fs
264
- elsif fs.is_a?(Class)
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
- public
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
- #This class is useful to make your filesystem implementation
451
- #debuggable and testable without needing to mount an actual filesystem
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
- class Filler
563
- # @private
564
- def to_s
565
- "Filler::[]"
566
- end
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
- class FileInfo
570
- # @private
571
- def to_s
572
- "FileInfo::fh->#{fh}"
573
- end
383
+ # @private
384
+ def to_s
385
+ "#{self.class.name}::#{@fuse_delegate}"
574
386
  end
575
387
 
576
- # Helper class to return from :getattr method
577
- class Stat
578
- # Format mask
579
- S_IFMT = 0170000
580
- # Directory
581
- S_IFDIR = 0040000
582
- # Character device
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
- # Helper class to return from :statfs (eg for df output)
627
- # All attributes are Integers and default to 0
628
- class StatVfs
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
- # @return [Integer]
631
- attr_accessor :f_bsize,:f_frsize,:f_blocks,:f_bfree,:f_bavail
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
- # @return [Integer]
634
- attr_accessor :f_files,:f_ffree,:f_favail,:f_fsid,:f_flag,:f_namemax
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
- # values can be symbols or strings but drop the pointless f_ prefix
637
- def initialize(values={ })
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
- end #Module RFuse
425
+
426
+ # Remove Kernel:open etc..
427
+ FFI::Libfuse::FuseOperations.fuse_callbacks.each { |c| undef_method(c) rescue nil }
428
+ end
429
+
430
+ end