Class: FFI::Libfuse::Filesystem::VirtualDir
- Inherits:
-
VirtualNode
- Object
- VirtualNode
- FFI::Libfuse::Filesystem::VirtualDir
- Includes:
- Utils
- Defined in:
- lib/ffi/libfuse/filesystem/virtual_dir.rb
Overview
A Filesystem of Filesystems
Implements a simple Hash based directory of sub filesystems.
FUSE Callbacks
If path is root ('/') then the operation applies to this directory itself
If the path is a simple basename (with leading slash and no others) then the operation applies to an entry in this directory. The operation is handled by the directory and then passed on to the entry itself (with path = '/')
Otherwise it is passed on to the next entry via #path_method
Constraints
- Expects to be wrapped by Adapter::Safe
- Passes on FUSE Callbacks to sub filesystems agnostic of FFI::Libfuse::FUSE_MAJOR_VERSION. Sub-filesystems should use Adapter::Fuse2Compat or Adapter::Fuse3Support as required
Instance Attribute Summary collapse
-
#entries ⇒ Hash<String,FuseOperations>
readonly
Our directory entries.
Attributes included from Ruby::VirtualNode
#accounting, #virtual_stat, #virtual_xattr
FUSE Callbacks collapse
-
#create(path, mode = FuseContext.get.mask(0o644), ffi = nil) {|String| ... } ⇒ Object
For our entries, creates a new file.
-
#getattr(path, stat_buf = nil, ffi = nil) ⇒ Object
For the root path provides this directory's stat information, otherwise passes on to the next filesystem.
-
#link(from_path, to_path) {|existing| ... } ⇒ Object
Create a new hard link in this filesystem.
-
#mkdir(path, mode = FuseContext.get.mask(0o777)) {|String| ... } ⇒ Object
Creates a new directory entry in this directory.
-
#new_dir(_name) ⇒ FuseOperations
Method for creating a new directory, called from mkdir.
-
#new_file(_name) ⇒ FuseOperations
Method for creating a new file.
- #new_link(from_path, replacing) ⇒ FuseOperations
- #new_symlink(_name) ⇒ Object
-
#open(path, *args) ⇒ Object?
Safely passes on file open to next filesystem.
-
#opendir(path, ffi) ⇒ self, ...
Safely handles directory open to next filesystem.
-
#readdir(path, buf, filler, offset, ffi, *flag) ⇒ Object
If path is root fills the directory from the keys in #entries.
-
#release(path, *args) ⇒ Object
Safely handle file release.
-
#releasedir(path, *args) ⇒ Object
Safely handles directory release.
- #rename(from_path, to_path) ⇒ Object
-
#rmdir(path) ⇒ Object
For root path validates we are empty and removes a node link from Ruby::VirtualNode#accounting For our entries, passes on the call to the entry (with path='/') and then removes the entry.
- #same_filesystem_method(callback, from_path, to_path, rescue_notsup: false) ⇒ Object
-
#symlink(target, path) ⇒ Object
Creates a new symbolic link in this directory.
-
#unlink(path) {|entry| ... } ⇒ Object
For our entries validates the entry exists and calls unlink('/') on it to do any cleanup before removing the entry from our entries list.
Instance Method Summary collapse
-
#initialize(accounting: Accounting.new) ⇒ VirtualDir
constructor
A new instance of VirtualDir.
-
#path_method(callback, *args, notsup: Errno::ENOTSUP, block: nil) {|entry_key, entry| ... } ⇒ Object
Finds the path argument of the callback and splits it into an entry in this directory and a remaining path.
Methods included from Utils
#directory?, #empty_dir?, #empty_file?, #exists?, #file?, #mkdir_p, #stat
Methods included from Ruby::VirtualNode
#chmod, #chown, #getxattr, #init_node, #listxattr, #statfs, #utimens
Constructor Details
#initialize(accounting: Accounting.new) ⇒ VirtualDir
Returns a new instance of VirtualDir.
42 43 44 45 |
# File 'lib/ffi/libfuse/filesystem/virtual_dir.rb', line 42 def initialize(accounting: Accounting.new) @entries = {} super end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method, *args, &block) ⇒ Object (private)
395 396 397 398 399 |
# File 'lib/ffi/libfuse/filesystem/virtual_dir.rb', line 395 def method_missing(method, *args, &block) return super unless FuseOperations.path_callbacks.include?(method) path_method(method, *args, block: block) end |
Instance Attribute Details
#entries ⇒ Hash<String,FuseOperations> (readonly)
Returns our directory entries.
40 41 42 |
# File 'lib/ffi/libfuse/filesystem/virtual_dir.rb', line 40 def entries @entries end |
Instance Method Details
#create(path, mode = FuseContext.get.mask(0o644), ffi = nil) {|String| ... } ⇒ Object
For our entries, creates a new file
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/ffi/libfuse/filesystem/virtual_dir.rb', line 149 def create(path, mode = FuseContext.get.mask(0o644), ffi = nil, &file) raise Errno::EISDIR if root?(path) # fuselib will fallback to mknod on ENOSYS on a case by case basis path_method(__method__, path, mode, ffi, notsup: Errno::ENOSYS, block: file) do |name, existing| raise Errno::EISDIR if dir_entry?(existing) raise Errno::EEXIST if existing # TODO: Strictly should understand setgid and sticky bits of this dir's mode when creating new files new_file = file ? file.call(name) : new_file(name) if entry_fuse_respond_to?(new_file, :create) new_file.create('/', mode, ffi) else # TODO: generate a sensible device number entry_send(new_file, :mknod, '/', mode, 0) entry_send(new_file, :open, '/', ffi) end entries[name] = new_file end end |
#getattr(path, stat_buf = nil, ffi = nil) ⇒ Object
For the root path provides this directory's stat information, otherwise passes on to the next filesystem
52 53 54 55 56 57 58 59 |
# File 'lib/ffi/libfuse/filesystem/virtual_dir.rb', line 52 def getattr(path, stat_buf = nil, ffi = nil) if root?(path) stat_buf&.directory(nlink: entries.size + 2, **virtual_stat) return self end path_method(__method__, path, stat_buf, ffi, notsup: Errno::ENOSYS) end |
#link(from_path, to_path) {|existing| ... } ⇒ Object
Create a new hard link in this filesystem
215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/ffi/libfuse/filesystem/virtual_dir.rb', line 215 def link(from_path, to_path, &linker) # Can't link to a directory raise Errno::EISDIR if root?(to_path) raise Errno::ENOSYS unless from_path || linker same_filesystem_method(__method__, from_path, to_path) do linker ||= proc { |replacing| new_link(from_path, replacing) } path_method(__method__, from_path, to_path, block: linker) do |link_name, existing| linked_entry = linker.call(existing) entries[link_name] = linked_entry end end end |
#mkdir(path, mode = FuseContext.get.mask(0o777)) {|String| ... } ⇒ Object
Creates a new directory entry in this directory
184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/ffi/libfuse/filesystem/virtual_dir.rb', line 184 def mkdir(path, mode = FuseContext.get.mask(0o777), &dir) return init_node(mode) if root?(path) path_method(__method__, path, mode, block: dir) do |dir_name, existing| raise Errno::EEXIST if existing new_dir = dir ? dir.call(dir_name) : new_dir(dir_name) entry_send(new_dir, :mkdir, '/', mode) entries[dir_name] = new_dir end end |
#new_dir(_name) ⇒ FuseOperations
Method for creating a new directory, called from mkdir
199 200 201 |
# File 'lib/ffi/libfuse/filesystem/virtual_dir.rb', line 199 def new_dir(_name) VirtualDir.new(accounting: accounting) end |
#new_file(_name) ⇒ FuseOperations
Method for creating a new file
173 174 175 |
# File 'lib/ffi/libfuse/filesystem/virtual_dir.rb', line 173 def new_file(_name) VirtualFile.new(accounting: accounting) end |
#new_link(from_path, replacing) ⇒ FuseOperations
Called from within ##link
Uses ##getattr(from_path) to find the filesystem object at from_path.
Calls ##link(nil, '/') on this object to signal that a new link has been created to it.
Filesystem objects that do not support linking should raise Errno::EPERM
if the object should not be hard
linked (eg directories)
237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/ffi/libfuse/filesystem/virtual_dir.rb', line 237 def new_link(from_path, replacing) raise Errno::EEXIST if replacing linked_entry = getattr(from_path) # the linked entry itself must represent a filesystem inode raise Errno::EPERM unless entry_fuse_respond_to?(linked_entry, :getattr) entry_send(linked_entry, :link, nil, '/') linked_entry end |
#new_symlink(_name) ⇒ Object
352 353 354 |
# File 'lib/ffi/libfuse/filesystem/virtual_dir.rb', line 352 def new_symlink(_name) VirtualLink.new(accounting: accounting) end |
#open(path, *args) ⇒ Object?
Safely passes on file open to next filesystem
66 67 68 69 70 |
# File 'lib/ffi/libfuse/filesystem/virtual_dir.rb', line 66 def open(path, *args) raise Errno::EISDIR if root?(path) path_method(__method__, path, *args, notsup: nil) end |
#opendir(path, ffi) ⇒ self, ...
Safely handles directory open to next filesystem
87 88 89 90 91 |
# File 'lib/ffi/libfuse/filesystem/virtual_dir.rb', line 87 def opendir(path, ffi) return (ffi.fh = self) if root?(path) path_method(__method__, path, ffi, notsup: nil) end |
#path_method(callback, *args, notsup: Errno::ENOTSUP, block: nil) {|entry_key, entry| ... } ⇒ Object
Finds the path argument of the callback and splits it into an entry in this directory and a remaining path
If a block is given and there is no remaining path (ie our entry) the block is called and its value returned
If the path is not our entry, the callback is passed on to the sub filesystem entry with the remaining path
If the path is our entry, but not block is provided, the callback is passed to our entry with a path of '/'
@yield(entry_key, entry)
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 |
# File 'lib/ffi/libfuse/filesystem/virtual_dir.rb', line 376 def path_method(callback, *args, notsup: Errno::ENOTSUP, block: nil) # Inside path_method _read_arg_method, path_arg_method, next_arg_method = FuseOperations.path_arg_methods(callback) path = args.send(path_arg_method) entry_key, next_path = entry_path(path) our_entry = root?(next_path) return yield entry_key, entries[entry_key] if block_given? && our_entry # Pass to our entry args.send(next_arg_method, next_path) notdir = Errno::ENOTDIR unless our_entry entry_send(entries[entry_key], callback, *args, notsup: notsup, notdir: notdir, &block) end |
#readdir(path, buf, filler, offset, ffi, *flag) ⇒ Object
If path is root fills the directory from the keys in #entries
If ffi.fh is itself a filesystem then try to call its :readdir directly
Otherwise passes to the next filesystem in path
109 110 111 112 113 114 115 |
# File 'lib/ffi/libfuse/filesystem/virtual_dir.rb', line 109 def readdir(path, buf, filler, offset, ffi, *flag) return %w[. ..].concat(entries.keys).each(&Adapter::Ruby::ReaddirFiller.new(buf, filler)) if root?(path) return ffi.fh.readdir('/', buf, filler, offset, ffi, *flag) if dir_entry?(ffi.fh) path_method(:readdir, path, buf, filler, offset, ffi, *flag, notsup: Errno::ENOTDIR) end |
#release(path, *args) ⇒ Object
Safely handle file release
Passes on to next filesystem, rescuing ENOTSUP or ENOSYS
76 77 78 79 80 |
# File 'lib/ffi/libfuse/filesystem/virtual_dir.rb', line 76 def release(path, *args) raise Errno::EISDIR if root?(path) path_method(__method__, path, *args, notsup: nil) end |
#releasedir(path, *args) ⇒ Object
Safely handles directory release
Does nothing for the root path
Otherwise safely passes on to next filesystem, rescuing ENOTSUP or ENOSYS
98 99 100 101 102 |
# File 'lib/ffi/libfuse/filesystem/virtual_dir.rb', line 98 def releasedir(path, *args) return if root?(path) path_method(__method__, path, *args, notsup: nil) end |
#rename(from_path, to_path) ⇒ Object
As per POSIX raname(2) silently succeeds if from_path and to_path are hard links to the
Rename is handled via ##link and ##unlink using their respective block arguments to handle validation and retrieve the object at from_path. Intermediate directory filesystems are only required to pass on the block, while the final directory target of from_path and to_path must call these blocks as this class does.
If to_path is being replaced the existing entry will be signaled via ##unlink('/'), or ##rmdir('/') same filesystem object (ie without unlinking from_path)
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 |
# File 'lib/ffi/libfuse/filesystem/virtual_dir.rb', line 288 def rename(from_path, to_path) return if from_path == to_path raise Errno::EINVAL if root?(from_path) same_filesystem_method(__method__, from_path, to_path, rescue_notsup: true) do # Can't rename into a subdirectory of itself raise Errno::EINVAL if to_path.start_with?("#{from_path}/") # POSIX rename(2) requires to silently abandon, without unlinking from_path, # if the inodes at from_path and to_path are the same object (ie hard linked to each other)) catch :same_hard_link do link(nil, to_path) do |replacing| check_rename_unlink(from_path) unlink(from_path) do |source| raise Errno::ENOENT unless source throw :same_hard_link if source.equal?(replacing) rename_cleanup_overwritten(replacing) end end end end end |
#rmdir(path) ⇒ Object
For root path validates we are empty and removes a node link from Ruby::VirtualNode#accounting For our entries, passes on the call to the entry (with path='/') and then removes the entry.
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/ffi/libfuse/filesystem/virtual_dir.rb', line 122 def rmdir(path) if root?(path) raise Errno::ENOTEMPTY unless entries.empty? accounting.adjust(0, -1) return end path_method(__method__, path) do |entry_key, dir| raise Errno::ENOENT unless dir raise Errno::ENOTDIR unless dir_entry?(dir) entry_send(dir, :rmdir, '/') entries.delete(entry_key) dir end end |
#same_filesystem_method(callback, from_path, to_path, rescue_notsup: false) ⇒ Object
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 |
# File 'lib/ffi/libfuse/filesystem/virtual_dir.rb', line 316 def same_filesystem_method(callback, from_path, to_path, rescue_notsup: false) return yield unless from_path # no from_path to traverse to_dir, next_to_path = entry_path(to_path) return yield if root?(next_to_path) # target is our entry, no more directories to traverse from_dir, next_from_path = entry_path(from_path) return yield if from_dir != to_dir # from and to in different directories, we need to handle it ourself # try traverse into sub-fs, which must itself be a directory begin entry_send( entries[to_dir], callback, next_from_path, next_to_path, notsup: Errno::ENOSYS, notdir: Errno::ENOTDIR, rescue_notsup: rescue_notsup ) rescue Errno::ENOSYS, Errno::ENOTSUP raise unless rescue_notsup yield end end |
#symlink(target, path) ⇒ Object
Creates a new symbolic link in this directory
342 343 344 345 346 347 348 349 350 |
# File 'lib/ffi/libfuse/filesystem/virtual_dir.rb', line 342 def symlink(target, path) path_method(__method__, target, path) do |link_name, existing| raise Errno::EEXIST if existing new_link = new_symlink(link_name) entry_send(new_link, :symlink, target, '/') entries[link_name] = new_link end end |
#unlink(path) {|entry| ... } ⇒ Object
For our entries validates the entry exists and calls unlink('/') on it to do any cleanup before removing the entry from our entries list.
If a block is supplied (eg ##rename) it will be called before the entry is deleted
@yield(file_name, entry)
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
# File 'lib/ffi/libfuse/filesystem/virtual_dir.rb', line 260 def unlink(path, &rename) raise Errno::EISDIR if root?(path) path_method(__method__, path, block: rename) do |entry_key, entry| if rename rename.call(entry) elsif entry entry_send(entry, :unlink, '/') else raise Errno::ENOENT end entries.delete(entry_key) end end |