Module: RFuse

Defined in:
lib/rfuse.rb,
lib/rfuse/stat.rb,
lib/rfuse/flock.rb,
lib/rfuse/rfuse.rb,
lib/rfuse/statvfs.rb,
lib/rfuse/version.rb,
lib/rfuse/gem_version.rb

Overview

Ruby FUSE (Filesystem in USErspace) binding

Defined Under Namespace

Modules: Adapter Classes: Error, Filler, Flock, Fuse, FuseDelegator, Stat, StatVfs

Constant Summary collapse

VERSION =
"2.0.0"

Class Method Summary collapse

Class Method Details

.main(argv = ARGV.dup, extra_options = [], extra_options_usage = nil, device = nil, exec = File.basename($PROGRAM_NAME)) {|options, argv| ... } ⇒ Object

Convenience method to launch a fuse filesystem, with nice usage messages and default signal traps

Examples:


class MyFuse < Fuse
   def initialize(myfs,*argv)
      @myfs = myfs # my filesystem local option value
      super(*argv)
   end
   # ... implementations for filesystem methods ...
end

class MyFSClass  # not a subclass of Fuse, FuseDelegator used
   def initialize(myfs)
    @myfs = myfs # my filesystem local option value
   end
   # ...
end

MY_OPTIONS = [ :myfs ]
OPTION_USAGE = "  -o myfs=VAL how to use the myfs option"
DEVICE_USAGE = "how to use device arg"

# Normally from the command line...
ARGV = [ "some/device", "/mnt/point", "-h", "-o", "debug,myfs=aValue" ]

RFuse.main(ARGV, MY_OPTIONS, OPTION_USAGE, DEVICE_USAGE, $0) do |options, argv|

    # options ==
       { :device => "some/device",
         :mountpoint => "/mnt/point",
         :help => true,
         :debug => true,
         :myfs => "aValue"
       }

    # ... validate options...
    raise RFuse::Error, "Bad option" unless options[:myfs]

    # return the filesystem class to be initialised by RFuse
    MyFuse
    # or
    MyFSClass

    # OR take full control over initialisation yourself and return the object
    MyFuse.new(options[:myfs],*argv)
    # or
    MyFSClass.new(options[:myfs])

end

Parameters:

  • argv (Array<String>) (defaults to: ARGV.dup)

    command line arguments

  • extra_options (Array<Symbol>) (defaults to: [])

    list of additional options

  • extra_options_usage (String) (defaults to: nil)

    describing additional option usage

  • device (String) (defaults to: nil)

    a description of the device field

  • exec (String) (defaults to: File.basename($PROGRAM_NAME))

    the executable file

Yield Parameters:

Yield Returns:

  • (Class<Fuse>)

    a subclass of Fuse that implements your filesystem. Will receive .new(*extra_options,*argv)

  • (Class)

    a class that implements FuseDelegator::FUSE_METHODS. Will receive .new(*extra_options) and the resulting instance sent with *argv to FuseDelegator

  • (Fuse)

    Your initialised (and therefore already mounted) filesystem

  • (Object)

    An object that implements the Fuse methods, to be passed with *argv to FuseDelegator

  • (Error)

    raise Error with appropriate message for invalid options

Since:

  • 1.1.0



199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/rfuse.rb', line 199

def self.main(argv = ARGV.dup, extra_options = [], extra_options_usage = nil, device = nil, exec = File.basename($PROGRAM_NAME))
  begin
    fuse_help = ['Filesystem options:',extra_options_usage,device,''].compact.join("\n")

    options = parse_options(argv, *extra_options, desc: extra_options_usage && fuse_help, exec: exec)

    unless options[:mountpoint]
      warn "rfuse: failed, no mountpoint provided"
      puts usage(device,exec)
      return nil
    end

    fs = yield options, argv

    raise Error, 'no filesystem provided' unless fs

    # create can pass the extra options to a constructor so order and existence is important.
    fs_options = extra_options.each_with_object({}) { |k,h| h[k] = options.delete(k) }
    fuse = create(argv: argv, fs: fs, options: fs_options)

    return nil if options[:show_help] || options[:show_version]

    raise Error, "filesystem #{fs} not mounted" unless fuse&.mounted?

    fuse.run(**options)
  rescue Error => e
    # These errors are produced generally because the user passed bad arguments etc..
    puts usage(device, exec) unless options[:help]
    warn "rfuse failed: #{e.message}"
    nil
  rescue StandardError => e
    # These are other errors related the yield block
    warn e.message
    warn e.backtrace.join("\n")
  end
end

.parse_options(argv, *local_opts, desc: nil, exec: $0) ⇒ Hash<Symbol,String|Boolean>

Parse mount arguments and options

Fuse itself will normalise arguments

mount -t fuse </path/to/fs.rb>#<device> mountpoint [options...]
mount.fuse </path/to/fs.rb>#<device> mountpoint [options...]

both result in the following command execution

/path/to/fs.rb [device] mountpoint [-h] [-d] [-o [opt,optkey=value,...]]

which this method will parse into a Hash with the following special keys

  • :device - the optional mount device, removed from argv if present
  • :mountpoint - required mountpoint
  • :help - if -h was supplied - will print help text (and not mount the filesystem!)
  • :debug - if -d (or -o debug) was supplied - will print debug output from the underlying FUSE library

and any other supplied options.

Examples:

argv = [ "some/device", "/mnt/point", "-h", "-o", "debug,myfs=aValue" ]
options = RFuse.parse_options(argv,:myfs)

# options ==
{ :device => "some/device",
  :mountpoint => "/mnt/point",
  :help => true,
  :debug => true,
  :myfs => "aValue"
}
# and argv ==
[ "/mnt/point","-h","-o","debug" ]

fs = create_filesystem(options)
fuse = RFuse::FuseDelegator.new(fs,*ARGV)

Parameters:

  • argv (Array<String>)

    normalised fuse options

  • local_opts (Array<Symbol>)

    local options if these are found in the argv entry following "-o" they are removed from argv, ie so argv is a clean set of options that can be passed to Fuse or FuseDelegator

Returns:

  • (Hash<Symbol,String|Boolean>)

    the extracted options

Since:

  • 1.0.3



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/rfuse.rb', line 81

def self.parse_options(argv, *local_opts, desc: nil, exec: $0)

  def desc.fuse_help
    self
  end if desc && !desc.empty?

  argv.unshift(exec) unless argv.size >= 2 && argv[0..1].none? { |a| a.start_with?('-') }
  run_args = FFI::Libfuse::Main.fuse_parse_cmdline(*argv, handler: desc)

  run_args[:help] = true if run_args[:show_help] # compatibility

  device_opts = { 'subtype=' => :device, 'fsname=' => :device }
  local_opt_conf = local_opts.each.with_object(device_opts) do |o, conf|
    conf.merge!({ "#{o}=" => o.to_sym, "#{o}" => o.to_sym })
  end

  fuse_args = run_args.delete(:args)

  fuse_args.parse!(local_opt_conf, run_args, **{}) do |key:, value:, data:, **_|
    data[key] = value
    key == :device ? :keep : :discard
  end

  argv.replace(fuse_args.argv)
  # The first arg is actually the device and ignored in future calls to parse opts
  # (eg during fuse_new, but rfuse used to return the mountpoint here.
  argv[0] = run_args[:mountpoint]

  run_args
end

.usage(device = nil, exec = File.basename($PROGRAM_NAME)) ⇒ String

Generate a usage string

Parameters:

  • device (String) (defaults to: nil)

    a description of how the device field should be used

  • exec (String) (defaults to: File.basename($PROGRAM_NAME))

    the executable

Returns:

  • (String)

    the usage string



117
118
119
# File 'lib/rfuse.rb', line 117

def self.usage(device = nil, exec = File.basename($PROGRAM_NAME))
  "Usage:\n     #{exec} #{device} mountpoint [-h] [-d] [-o [opt,optkey=value,...]]\n"
end