rfuse 1.2.3.rc20201019.81 → 2.0.0.ffilibfuse

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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