Class: FFI::Libfuse::Filesystem::VirtualFS

Inherits:
Object
  • Object
show all
Includes:
Adapter::Context, Adapter::Debug, Adapter::Fuse2Compat, Utils
Defined in:
lib/ffi/libfuse/filesystem/virtual_fs.rb

Overview

A configurable main Filesystem that delegates FUSE Callbacks to another filesystem

This class registers support for all available fuse callbacks, subject to the options below.

Delegate filesystems like VirtualDir may raise ENOTSUP to indicate a callback is not handled at runtime although the behaviour of C libfuse varies in this regard.

It is writable to the user that mounted it may create and edit files within it

Filesystem options

Passed by -o options to FFI::Libfuse.main

Examples:

class MyFS < FFI::Libfuse::Filesystem::VirtualFS
  def fuse_configure
    build({ 'hello' => { 'world.txt' => 'Hello World'}})
    mkdir("/hello")
    create("/hello/world.txt").write("Hello World!\n")
    create("/hello/everybody").write("Hello Everyone!\n")
    symlink("/hello/link","everybody")`
  end
end

exit(FFI::Libfuse.fuse_main(operations: MyFS.new))

Constant Summary

Constants included from Adapter::Fuse2Compat

Adapter::Fuse2Compat::FUSE_CONFIG_FLAGS, Adapter::Fuse2Compat::FUSE_CONFIG_ONLY_ATTRIBUTES

Constants included from Adapter::Debug

Adapter::Debug::DEFAULT_FORMAT

Instance Attribute Summary collapse

Fuse Configuration collapse

Instance Method Summary collapse

Methods included from Adapter::Debug

debug, #debug?, debug_callback, #debug_config, debug_error, debug_format, error_message

Methods included from Adapter::Safe

#default_errno, safe_callback

Methods included from Adapter::Context

fuse_context, thread_local_context

Methods included from Utils

#directory?, #empty_dir?, #empty_file?, #exists?, #file?, #mkdir_p, #stat

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args, &block) ⇒ Object (private)

Passes FUSE Callbacks on to the #root filesystem



190
191
192
193
194
195
196
197
# File 'lib/ffi/libfuse/filesystem/virtual_fs.rb', line 190

def method_missing(method, *args, &block)
  return @root.public_send(method, *args, &block) if @root.respond_to?(method)

  # This is not always reliable but better than raising NoMethodError
  return -Errno::ENOTSUP::Errno if FuseOperations.fuse_callbacks.include?(method)

  super
end

Instance Attribute Details

#optionsHash{Symbol => String,Boolean} (readonly)

Returns custom options captured as defined by #fuse_options.

Returns:

  • (Hash{Symbol => String,Boolean})

    custom options captured as defined by #fuse_options



61
62
63
# File 'lib/ffi/libfuse/filesystem/virtual_fs.rb', line 61

def options
  @options
end

#rootObject (readonly)

Returns the root filesystem that quacks like a FFI::Libfuse::FuseOperations.

Returns:



58
59
60
# File 'lib/ffi/libfuse/filesystem/virtual_fs.rb', line 58

def root
  @root
end

Instance Method Details

#accountingAccounting|:max_space=, :max_nodes=

Returns an accumulator of filesystem statistics used to consume the max_space and max_nodes options.

Returns:

  • (Accounting|:max_space=, :max_nodes=)

    an accumulator of filesystem statistics used to consume the max_space and max_nodes options



65
66
67
# File 'lib/ffi/libfuse/filesystem/virtual_fs.rb', line 65

def accounting
  @accounting ||= Accounting.new
end

#build(files) ⇒ Object

Adds files directly to the filesystem according to the content implementing one of the methods below

  • :each_pair is treated as a subdir of files
  • :readdir (eg PassThroughDir) is treated as a directory- sent via mkdir
  • :getattr (eg PassThroughFile) is treated as a file - sent via create
  • :to_str (eg String ) is created a default file, with the string sent via write

Parameters:

  • Hash<String,:each_pair, (Hash<String,:each_pair, :readdir,:getattr .:to_str] files map of paths to content generated)

    :readdir,:getattr .:to_str] files map of paths to content generated



84
85
86
87
88
89
90
91
92
93
94
# File 'lib/ffi/libfuse/filesystem/virtual_fs.rb', line 84

def build(files, base_path = Pathname.new('/'))
  files.each_pair do |path, content|
    path = (base_path + path).cleanpath
    @root.mkdir_p(path.dirname) unless path.dirname == base_path

    rt = %i[each_pair readdir getattr to_str].detect { |m| content.respond_to?(m) }
    raise "Unsupported initial content for #{self.class.name}: #{content.class.name}- #{content}" unless rt

    send("build_#{rt}", content, path)
  end
end

#fuse_configure(root = VirtualDir.new(accounting: accounting).mkdir('/')) ⇒ Object

Subclasses can override the no-arg method and call super to pass in a different root.

Parameters:

  • root (FuseOperations) (defaults to: VirtualDir.new(accounting: accounting).mkdir('/'))

    the root filesystem



71
72
73
# File 'lib/ffi/libfuse/filesystem/virtual_fs.rb', line 71

def fuse_configure(root = VirtualDir.new(accounting: accounting).mkdir('/'))
  @root = root
end

#fuse_helpObject

Subclasses can override this method to add descriptions for additional options



140
141
142
143
144
145
146
# File 'lib/ffi/libfuse/filesystem/virtual_fs.rb', line 140

def fuse_help
  <<~END_HELP
    #{Accounting::HELP}
    #{self.class.name} options:
        -o no_buf              always use read, write instead of read_buf, write_buf
  END_HELP
end

#fuse_options(args, opts = {}, &block) ⇒ Object

Default fuse options Subclasses can override this method and call super with the additional options: @yield(key, value, **args) Called for each matching key in opts

Parameters:

  • opts (Hash) (defaults to: {})

    additional options to parse into the #options attribute

See Also:



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/ffi/libfuse/filesystem/virtual_fs.rb', line 121

def fuse_options(args, opts = {}, &block)
  @options = {}
  opts = opts.merge({ 'no_buf' => :no_buf }).merge(Accounting::OPTIONS)
  args.parse!(opts) do |key:, value:, **kwargs|
    case key
    when *Accounting::OPTIONS.values.uniq
      next accounting.fuse_opt_proc(key: key, value: value)
    when :no_buf
      @no_buf = true
    else
      next block.call(key, value, **kwargs) if block

      options[key] = value
    end
    :handled
  end
end

#fuse_respond_to?(method) ⇒ Boolean

Respond to all FUSE Callbacks except deprecated, noting that ..

  • :read_buf, :write_buf can be excluded by the 'no_buf' mount option
  • :access already has a libfuse mount option (default_permissions)
  • :create falls back to :mknod on ENOSYS (as raised by FFI::Libfuse::Filesystem::VirtualDir)
  • :copy_file_range can raise ENOTSUP to trigger glibc to fallback to inefficient copy

Returns:

  • (Boolean)


106
107
108
109
110
111
112
113
# File 'lib/ffi/libfuse/filesystem/virtual_fs.rb', line 106

def fuse_respond_to?(method)
  case method
  when :read_buf, :write_buf
    !no_buf
  else
    true
  end
end

#fuse_versionObject

Subclasses can override to produce a nice version string for -V



149
150
151
# File 'lib/ffi/libfuse/filesystem/virtual_fs.rb', line 149

def fuse_version
  self.class.name
end

#use_inoBoolean

Configure whether entries in this filesystem provide useful inode values in #gettattr and #readdir

Defaults to true since default Dir, File, Link all use FFI::Libfuse::Filesystem::VirtualNode which uses Ruby object id as the inode value.

Subclasses should override to false if some sub-filesystems will not provide inode values.

Returns:

  • (Boolean)


165
166
167
# File 'lib/ffi/libfuse/filesystem/virtual_fs.rb', line 165

def use_ino
  true
end