sample/memory_fs.rb in ffi-libfuse-0.0.1.rctest12 vs sample/memory_fs.rb in ffi-libfuse-0.1.0.rc20220550

- old
+ new

@@ -1,189 +1,13 @@ #!/usr/bin/env ruby # frozen_string_literal: true require 'ffi/libfuse' +require 'ffi/libfuse/filesystem/virtual_fs' # A simple in-memory filesystem defined with hashes. -# -# It is writable to the user that mounted it may create and edit files within it -# -# === Usage -# root = Memory.new(files: { 'hello' => { 'world.txt' => 'Hello World'}}) -# root.mkdir("/hello") -# root.("/hello/world","Hello World!\n") -# root.write("/hello/everybody","Hello Everyone!\n") -# -# Libfuse::fuse_main($0,ARGV,operations: root) -# -# -class MemoryFS - # @return [Hash<String,Object>] list of file objects by path - attr_reader :root +class MemoryFS < FFI::Libfuse::Filesystem::VirtualFS; end - include FFI::Libfuse::Adapter::Fuse3Support - include FFI::Libfuse::Adapter::Ruby - include FFI::Libfuse::Adapter::Pathname +# Set this to test multi-threading etc... +main_class = ENV.fetch('MEMORY_FS_SKIP_DEFAULT_ARGS', 'N') == 'Y' ? FFI::Libfuse::Main : FFI::Libfuse - File = Struct.new(:mode, :content, :ctime, :atime, :mtime) do - def dig(*_args) - raise Errno::ENOTDIR - end - - def fill_stat(stat = FFI::Stat.new) - stat.file(mode: mode, ctime: ctime, atime: atime, mtime: mtime, size: content.size) - end - end - - # rubocop:disable Lint/StructNewOverride - Dir = Struct.new(:mode, :entries, :ctime, :atime, :mtime) do - def dig(*args) - entries.dig(*args) - end - - def fill_stat(stat = FFI::Stat.new) - stat.directory(mode: mode, ctime: ctime, atime: atime, mtime: mtime) - end - end - # rubocop:enable Lint/StructNewOverride - - def initialize(files: {}, max_size: 100_000, max_files: 1_000) - now = Time.now - @root = Dir.new(0x755, {}, now, now, now) - @total_size = 0 - @total_files = 1 - @max_size = max_size - @max_files = max_files - - build(files) - end - - def build(files, path = ::Pathname.new('/')) - files.each_pair do |basename, content| - raise 'Initial file keys must be String' unless basename.is_a?(String) - raise 'Initial file keys must not contain path separators' if basename =~ %r{[/\\]} - - entry_path = path + basename - case content - when String - create(entry_path, 0x644) - write(entry_path, content, 0) - when Hash - mkdir(entry_path, 0x755) - build(content, entry_path) - else - raise 'Initial files must be String or Hash' - end - end - end - - def fuse_version - 'MemoryFS: Version x.y.z' - end - - def fuse_traps - { - HUP: -> { reload } - } - end - - def statfs(_path, statfs_buf) - blocks = @total_size / 1_000 - statfs_buf.bsize = 1 # block size (in Kb) - statfs_buf.frsize = 1 # fragment size pretty much always bsize - statfs_buf.blocks = @max_size - statfs_buf.bfree = @max_size - blocks - statfs_buf.bavail = @max_size - blocks - statfs_buf.files = @max_files - statfs_buf.ffree = @max_files - @total_files - statfs_buf.favail = @max_files - @total_files - 0 - end - - def getattr(path, stat_buf) - entry = find(path) - return -Errno::ENOENT::Errno unless entry - - entry.fill_stat(stat_buf) - 0 - end - - def readdir(path, _offset, _ffi) - %w[. ..].each { |d| yield(d, nil) } - dir = find(path) - dir.entries.each_pair { |k, e| yield(k, e.fill_stat) } - end - - def create(path, mode, _ffi) - dir_entries = find(path.dirname).entries - now = Time.now - dir_entries[path.basename.to_s] = File.new(mode, String.new, now, now, now) - @total_files += 1 - 0 - end - - # op[:read] = [:pointer, :size_t, :off_t, FuseFileInfo.by_ref] - def read(path, len, off, _ffi) - file = find(path) - file.atime = Time.now.utc - FFI::Libfuse::ThreadPool.busy - sleep 0.5 - file.content[off, len] - end - - # write(const char* path, char *buf, size_t size, off_t offset, struct fuse_file_info* fi) - def write(path, data, offset, _ffi) - file = find(path) - content = file.content - @total_size -= content.size - content[offset, data.length] = data - @total_size += content.size - file.mtime = Time.now.utc - end - - def truncate(path, size) - file = find(path) - @total_size -= file.content.size - file.content[size..-1] = '' - file.mtime = Time.now.utc - @total_size += file.content.size - 0 - end - - def unlink(path) - dir = find(path.dirname) - deleted = dir.entries.delete(path.basename.to_s) - @total_files -= 1 - @total_size -= deleted.content.size if deleted.is_a?(File) - 0 - end - - def mkdir(path, mode) - entries = find(path.dirname).entries - now = Time.now - entries[path.basename.to_s] = Dir.new(mode, {}, now, now, now) - end - - def rmdir(path) - dir = find(path) - raise Errno::ENOTDIR unless dir.is_a?(Dir) - raise Errno::ENOTEMPTY unless dir.entries.empty? - - find(path.dirname).entries.delete(path.basename.to_s) - 0 - end - - def utimens(path, atime, mtime) - entry = find(path) - entry.atime = atime if atime - entry.mtime = mtime if mtime - 0 - end - - private - - def find(path) - path.root? ? root : root.dig(*path.to_s.split('/')[1..]) - end -end - -exit(FFI::Libfuse.fuse_main($0, *ARGV, operations: MemoryFS.new)) if __FILE__ == $0 +exit(main_class.fuse_main(operations: MemoryFS.new)) if __FILE__ == $0