ffi-libfuse 0.0.1.pre
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.
- checksums.yaml +7 -0
- data/.yardopts +1 -0
- data/README.md +100 -0
- data/lib/ffi/accessors.rb +145 -0
- data/lib/ffi/devt.rb +30 -0
- data/lib/ffi/flock.rb +47 -0
- data/lib/ffi/gnu_extensions.rb +115 -0
- data/lib/ffi/libfuse/ackbar.rb +112 -0
- data/lib/ffi/libfuse/adapter/context.rb +37 -0
- data/lib/ffi/libfuse/adapter/debug.rb +89 -0
- data/lib/ffi/libfuse/adapter/fuse2_compat.rb +91 -0
- data/lib/ffi/libfuse/adapter/fuse3_support.rb +87 -0
- data/lib/ffi/libfuse/adapter/interrupt.rb +37 -0
- data/lib/ffi/libfuse/adapter/pathname.rb +23 -0
- data/lib/ffi/libfuse/adapter/ruby.rb +334 -0
- data/lib/ffi/libfuse/adapter/safe.rb +58 -0
- data/lib/ffi/libfuse/adapter/thread_local_context.rb +36 -0
- data/lib/ffi/libfuse/adapter.rb +79 -0
- data/lib/ffi/libfuse/callbacks.rb +61 -0
- data/lib/ffi/libfuse/fuse2.rb +159 -0
- data/lib/ffi/libfuse/fuse3.rb +162 -0
- data/lib/ffi/libfuse/fuse_args.rb +166 -0
- data/lib/ffi/libfuse/fuse_buffer.rb +155 -0
- data/lib/ffi/libfuse/fuse_callbacks.rb +48 -0
- data/lib/ffi/libfuse/fuse_cmdline_opts.rb +44 -0
- data/lib/ffi/libfuse/fuse_common.rb +249 -0
- data/lib/ffi/libfuse/fuse_config.rb +205 -0
- data/lib/ffi/libfuse/fuse_conn_info.rb +211 -0
- data/lib/ffi/libfuse/fuse_context.rb +79 -0
- data/lib/ffi/libfuse/fuse_file_info.rb +100 -0
- data/lib/ffi/libfuse/fuse_loop_config.rb +43 -0
- data/lib/ffi/libfuse/fuse_operations.rb +870 -0
- data/lib/ffi/libfuse/fuse_opt.rb +54 -0
- data/lib/ffi/libfuse/fuse_poll_handle.rb +59 -0
- data/lib/ffi/libfuse/fuse_version.rb +43 -0
- data/lib/ffi/libfuse/job_pool.rb +53 -0
- data/lib/ffi/libfuse/main.rb +200 -0
- data/lib/ffi/libfuse/test/operations.rb +56 -0
- data/lib/ffi/libfuse/test.rb +3 -0
- data/lib/ffi/libfuse/thread_pool.rb +147 -0
- data/lib/ffi/libfuse/version.rb +8 -0
- data/lib/ffi/libfuse.rb +24 -0
- data/lib/ffi/ruby_object.rb +95 -0
- data/lib/ffi/stat/constants.rb +29 -0
- data/lib/ffi/stat/native.rb +50 -0
- data/lib/ffi/stat/time_spec.rb +137 -0
- data/lib/ffi/stat.rb +96 -0
- data/lib/ffi/stat_vfs.rb +81 -0
- data/lib/ffi/struct_array.rb +39 -0
- data/lib/ffi/struct_wrapper.rb +100 -0
- data/sample/memory_fs.rb +189 -0
- data/sample/no_fs.rb +69 -0
- metadata +165 -0
data/lib/ffi/stat.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'struct_wrapper'
|
4
|
+
require_relative 'stat/native'
|
5
|
+
require_relative 'stat/constants'
|
6
|
+
|
7
|
+
module FFI
|
8
|
+
# Ruby representation of stat.h struct
|
9
|
+
class Stat
|
10
|
+
class << self
|
11
|
+
# @return [Stat] Newly allocated stat representing a regular file - see {Stat#file}
|
12
|
+
def file(**fields)
|
13
|
+
new.file(**fields)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [Stat] Newly allocated stat representing a directory - see {Stat#dir}
|
17
|
+
def dir(**fields)
|
18
|
+
new.dir(**fields)
|
19
|
+
end
|
20
|
+
alias directory dir
|
21
|
+
end
|
22
|
+
|
23
|
+
# We need to be a StructWrapper because of clash with #size
|
24
|
+
include StructWrapper
|
25
|
+
|
26
|
+
native_struct(Native)
|
27
|
+
|
28
|
+
# @!attribute [rw] mode
|
29
|
+
# @return [Integer] file mode (type | perms)
|
30
|
+
|
31
|
+
# @!attribute [rw] size
|
32
|
+
# @return [Integer] size of file in bytes
|
33
|
+
|
34
|
+
# @!attribute [rw] nlink
|
35
|
+
# @return [Integer] number of links
|
36
|
+
|
37
|
+
# @!attribute [rw] uid
|
38
|
+
# @return [Integer] owner user id
|
39
|
+
|
40
|
+
# @!attribute [rw] gid
|
41
|
+
# @return [Integer] owner group id
|
42
|
+
|
43
|
+
int_members = Native
|
44
|
+
.members
|
45
|
+
.select { |m| m.to_s.start_with?('st_') && !m.to_s.end_with?('timespec') }
|
46
|
+
.map { |m| m[3..].to_sym }
|
47
|
+
|
48
|
+
ffi_attr_accessor(*int_members, format: 'st_%s')
|
49
|
+
|
50
|
+
# @!attribute [rw] atime
|
51
|
+
# @return [Time] time of last access
|
52
|
+
|
53
|
+
# @!attribute [rw] mtime
|
54
|
+
# @return [Time] time of last modification
|
55
|
+
|
56
|
+
# @!attribute [rw] ctime
|
57
|
+
# @return [Time] time of last status change
|
58
|
+
|
59
|
+
time_members = Native
|
60
|
+
.members
|
61
|
+
.select { |m| m.to_s.start_with?('st_') && m.to_s.end_with?('timespec') }
|
62
|
+
.map { |m| m[3..-5].to_sym }
|
63
|
+
|
64
|
+
ffi_attr_reader(*time_members, format: 'st_%sspec', &:time)
|
65
|
+
|
66
|
+
ffi_attr_writer(*time_members, format: 'st_%sspec', simple: false) do |sec, nsec = 0|
|
67
|
+
t = self[__method__[0..-2].to_sym]
|
68
|
+
t.set_time(sec, nsec)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Fill content for a regular file
|
72
|
+
# @param [Integer] mode
|
73
|
+
# @param [Integer] size
|
74
|
+
# @param [Integer] uid
|
75
|
+
# @param [Integer] gid
|
76
|
+
# @param [Hash] args additional system specific stat fields
|
77
|
+
# @return [self]
|
78
|
+
def file(mode:, size:, uid: Process.uid, gid: Process.gid, **args)
|
79
|
+
mode = ((S_IFREG & S_IFMT) | (mode & 0o777))
|
80
|
+
fill(mode: mode, size: size, uid: uid, gid: gid, **args)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Fill content for a directory
|
84
|
+
# @param [Integer] mode
|
85
|
+
# @param [Integer] nlink
|
86
|
+
# @param [Integer] uid
|
87
|
+
# @param [Integer] gid
|
88
|
+
# @param [Hash] args additional system specific stat fields
|
89
|
+
# @return [self]
|
90
|
+
def dir(mode:, nlink: 1, uid: Process.uid, gid: Process.gid, **args)
|
91
|
+
mode = ((S_IFDIR & S_IFMT) | (mode & 0o777))
|
92
|
+
fill(mode: mode, uid: uid, gid: gid, nlink: nlink, **args)
|
93
|
+
end
|
94
|
+
alias directory dir
|
95
|
+
end
|
96
|
+
end
|
data/lib/ffi/stat_vfs.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ffi'
|
4
|
+
require_relative('accessors')
|
5
|
+
|
6
|
+
module FFI
|
7
|
+
# Represents Statvfs for use with {Libfuse::FuseOperations#statfs} callback
|
8
|
+
class StatVfs < Struct
|
9
|
+
include Accessors
|
10
|
+
|
11
|
+
case Platform::NAME
|
12
|
+
when /darwin/
|
13
|
+
layout(
|
14
|
+
f_bsize: :ulong,
|
15
|
+
f_frsize: :ulong,
|
16
|
+
f_blocks: :uint,
|
17
|
+
f_bfree: :uint,
|
18
|
+
f_bavail: :uint,
|
19
|
+
f_files: :uint,
|
20
|
+
f_ffree: :uint,
|
21
|
+
f_favail: :uint,
|
22
|
+
f_fsid: :ulong,
|
23
|
+
f_flag: :ulong,
|
24
|
+
f_namemax: :ulong
|
25
|
+
)
|
26
|
+
when /linux/
|
27
|
+
layout(
|
28
|
+
:f_bsize, :ulong,
|
29
|
+
:f_frsize, :ulong,
|
30
|
+
:f_blocks, :uint64,
|
31
|
+
:f_bfree, :uint64,
|
32
|
+
:f_bavail, :uint64,
|
33
|
+
:f_files, :uint64,
|
34
|
+
:f_ffree, :uint64,
|
35
|
+
:f_favail, :uint64,
|
36
|
+
:f_fsid, :ulong,
|
37
|
+
:f_flag, :ulong,
|
38
|
+
:f_namemax, :ulong,
|
39
|
+
:f_spare, [:int, 6]
|
40
|
+
)
|
41
|
+
else
|
42
|
+
raise NotImplementedError, "FFI::StatVfs not implemented for FFI::Platform #{Platform::NAME}"
|
43
|
+
end
|
44
|
+
|
45
|
+
# @!attribute [rw] bsize
|
46
|
+
# @return [Integer] Filesystem block size
|
47
|
+
|
48
|
+
# @!attribute [rw] frsize
|
49
|
+
# @return [Integer] Fragment size
|
50
|
+
|
51
|
+
# @!attribute [rw] blocks
|
52
|
+
# @return [Integer] Size of fs in frsize units
|
53
|
+
|
54
|
+
# @!attribute [rw] bfree
|
55
|
+
# @return [Integer] Number of free blocks
|
56
|
+
|
57
|
+
# @!attribute [rw] bavail
|
58
|
+
# @return [Integer] Number of free blocks for unprivileged users
|
59
|
+
|
60
|
+
# @!attribute [rw] files
|
61
|
+
# @return [Integer] Number of inodes
|
62
|
+
|
63
|
+
# @!attribute [rw] ffree
|
64
|
+
# @return [Integer] Number of free inodes
|
65
|
+
|
66
|
+
# @!attribute [rw] favail
|
67
|
+
# @return [Integer] Number of free inodes for unprivileged users
|
68
|
+
|
69
|
+
# @!attribute [rw] fsid
|
70
|
+
# @return [Integer] Filesystem ID
|
71
|
+
|
72
|
+
# @!attribute [rw] flag
|
73
|
+
# @return [Integer] Mount flags
|
74
|
+
|
75
|
+
# @!attribute [rw] namemax
|
76
|
+
# @return [Integer] Maximum filename length
|
77
|
+
|
78
|
+
int_members = members.select { |m| m =~ /^f_/ }.map { |m| m[2..].to_sym }
|
79
|
+
ffi_attr_accessor(*int_members, format: 'f_%s')
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ffi'
|
4
|
+
|
5
|
+
module FFI
|
6
|
+
# Module to extend struct classes that are present in callbacks as fixed length arrays
|
7
|
+
module StructArray
|
8
|
+
# Generate a one way converter for a fixed size array of struct
|
9
|
+
# @return [DataConverter]
|
10
|
+
def array(size)
|
11
|
+
ArrayConverter.new(self, size)
|
12
|
+
end
|
13
|
+
|
14
|
+
# @!visibility private
|
15
|
+
# Helper to handle callbacks containing fixed length array of struct
|
16
|
+
class ArrayConverter
|
17
|
+
include DataConverter
|
18
|
+
|
19
|
+
def initialize(type, size)
|
20
|
+
@type = type
|
21
|
+
@size = size
|
22
|
+
end
|
23
|
+
|
24
|
+
def native_type(_type = nil)
|
25
|
+
FFI::Type::POINTER
|
26
|
+
end
|
27
|
+
|
28
|
+
def from_native(ptr, _ctx)
|
29
|
+
return [] if ptr.null?
|
30
|
+
|
31
|
+
Array.new(@size) { |i| @type.new(ptr + (i * @type.size)) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_native(ary, _ctx)
|
35
|
+
raise NotImplementedError, "#{self.class.name} Cannot convert #{ary} to pointer"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ffi'
|
4
|
+
require_relative 'accessors'
|
5
|
+
|
6
|
+
module FFI
|
7
|
+
# Helper to wrap structs with ugly names and attribute clashes with FFI::Struct (eg size)
|
8
|
+
module StructWrapper
|
9
|
+
# @!visibility private
|
10
|
+
class ByReference < StructByReference
|
11
|
+
def initialize(wrapper_class)
|
12
|
+
super(wrapper_class.native_struct)
|
13
|
+
@wrapper_class = wrapper_class
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_native(value, ctx)
|
17
|
+
return Pointer::NULL if value.nil?
|
18
|
+
|
19
|
+
value = value.native if value.is_a?(StructWrapper)
|
20
|
+
super(value, ctx)
|
21
|
+
end
|
22
|
+
|
23
|
+
def from_native(value, ctx)
|
24
|
+
return nil if value.null?
|
25
|
+
|
26
|
+
native = super(value, ctx)
|
27
|
+
@wrapper_class.new(native)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Additional class methods for StructWrapper
|
32
|
+
module ClassMethods
|
33
|
+
# @!visibility private
|
34
|
+
def from_native(value, _)
|
35
|
+
new(value)
|
36
|
+
end
|
37
|
+
|
38
|
+
# @!visibility private
|
39
|
+
def to_native(value, _)
|
40
|
+
case value
|
41
|
+
when native_type
|
42
|
+
value
|
43
|
+
when self
|
44
|
+
value.native
|
45
|
+
else
|
46
|
+
new.fill(value).native
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# @!visibility private
|
51
|
+
def native_struct(struct_class = nil)
|
52
|
+
if struct_class
|
53
|
+
native_type(struct_class.by_value)
|
54
|
+
@struct_class = struct_class
|
55
|
+
end
|
56
|
+
@struct_class
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [Type] represents a pointer to the wrapped struct
|
60
|
+
def by_ref
|
61
|
+
@by_ref ||= ByReference.new(self)
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [Type] represents passing wrapped struct by value
|
65
|
+
def by_value
|
66
|
+
self
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class << self
|
71
|
+
# @!visibility private
|
72
|
+
def included(mod)
|
73
|
+
mod.extend(DataConverter)
|
74
|
+
mod.extend(ClassMethods)
|
75
|
+
mod.include(Accessors)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# @!parse extend ClassMethods
|
80
|
+
# @!parse include Accessors
|
81
|
+
|
82
|
+
# @!visibility private
|
83
|
+
attr_reader :native
|
84
|
+
|
85
|
+
# @!visibility private
|
86
|
+
def initialize(native = self.class.native_struct.new)
|
87
|
+
@native = native
|
88
|
+
end
|
89
|
+
|
90
|
+
# Get attribute
|
91
|
+
def [](member_or_attr)
|
92
|
+
@native[self.class.ffi_attr_readers.fetch(member_or_attr, member_or_attr)]
|
93
|
+
end
|
94
|
+
|
95
|
+
# Set attribute
|
96
|
+
def []=(member_or_attr, val)
|
97
|
+
@native[self.class.ffi_attr_writers.fetch(member_or_attr, member_or_attr)] = val
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/sample/memory_fs.rb
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'ffi/libfuse'
|
5
|
+
|
6
|
+
# A simple in-memory filesystem defined with hashes.
|
7
|
+
#
|
8
|
+
# It is writable to the user that mounted it may create and edit files within it
|
9
|
+
#
|
10
|
+
# === Usage
|
11
|
+
# root = Memory.new(files: { 'hello' => { 'world.txt' => 'Hello World'}})
|
12
|
+
# root.mkdir("/hello")
|
13
|
+
# root.("/hello/world","Hello World!\n")
|
14
|
+
# root.write("/hello/everybody","Hello Everyone!\n")
|
15
|
+
#
|
16
|
+
# Libfuse::fuse_main($0,ARGV,operations: root)
|
17
|
+
#
|
18
|
+
#
|
19
|
+
class MemoryFS
|
20
|
+
# @return [Hash<String,Object>] list of file objects by path
|
21
|
+
attr_reader :root
|
22
|
+
|
23
|
+
include FFI::Libfuse::Adapter::Fuse3Support
|
24
|
+
include FFI::Libfuse::Adapter::Ruby
|
25
|
+
include FFI::Libfuse::Adapter::Pathname
|
26
|
+
|
27
|
+
File = Struct.new(:mode, :content, :ctime, :atime, :mtime) do
|
28
|
+
def dig(*_args)
|
29
|
+
raise Errno::ENOTDIR
|
30
|
+
end
|
31
|
+
|
32
|
+
def fill_stat(stat = FFI::Stat.new)
|
33
|
+
stat.file(mode: mode, ctime: ctime, atime: atime, mtime: mtime, size: content.size)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# rubocop:disable Lint/StructNewOverride
|
38
|
+
Dir = Struct.new(:mode, :entries, :ctime, :atime, :mtime) do
|
39
|
+
def dig(*args)
|
40
|
+
entries.dig(*args)
|
41
|
+
end
|
42
|
+
|
43
|
+
def fill_stat(stat = FFI::Stat.new)
|
44
|
+
stat.directory(mode: mode, ctime: ctime, atime: atime, mtime: mtime)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
# rubocop:enable Lint/StructNewOverride
|
48
|
+
|
49
|
+
def initialize(files: {}, max_size: 100_000, max_files: 1_000)
|
50
|
+
now = Time.now
|
51
|
+
@root = Dir.new(0x755, {}, now, now, now)
|
52
|
+
@total_size = 0
|
53
|
+
@total_files = 1
|
54
|
+
@max_size = max_size
|
55
|
+
@max_files = max_files
|
56
|
+
|
57
|
+
build(files)
|
58
|
+
end
|
59
|
+
|
60
|
+
def build(files, path = ::Pathname.new('/'))
|
61
|
+
files.each_pair do |basename, content|
|
62
|
+
raise 'Initial file keys must be String' unless basename.is_a?(String)
|
63
|
+
raise 'Initial file keys must not contain path separators' if basename =~ %r{[/\\]}
|
64
|
+
|
65
|
+
entry_path = path + basename
|
66
|
+
case content
|
67
|
+
when String
|
68
|
+
create(entry_path, 0x644)
|
69
|
+
write(entry_path, content, 0)
|
70
|
+
when Hash
|
71
|
+
mkdir(entry_path, 0x755)
|
72
|
+
build(content, entry_path)
|
73
|
+
else
|
74
|
+
raise 'Initial files must be String or Hash'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def fuse_version
|
80
|
+
'MemoryFS: Version x.y.z'
|
81
|
+
end
|
82
|
+
|
83
|
+
def fuse_traps
|
84
|
+
{
|
85
|
+
HUP: -> { reload }
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
def statfs(_path, statfs_buf)
|
90
|
+
blocks = @total_size / 1_000
|
91
|
+
statfs_buf.bsize = 1 # block size (in Kb)
|
92
|
+
statfs_buf.frsize = 1 # fragment size pretty much always bsize
|
93
|
+
statfs_buf.blocks = @max_size
|
94
|
+
statfs_buf.bfree = @max_size - blocks
|
95
|
+
statfs_buf.bavail = @max_size - blocks
|
96
|
+
statfs_buf.files = @max_files
|
97
|
+
statfs_buf.ffree = @max_files - @total_files
|
98
|
+
statfs_buf.favail = @max_files - @total_files
|
99
|
+
0
|
100
|
+
end
|
101
|
+
|
102
|
+
def getattr(path, stat_buf)
|
103
|
+
entry = find(path)
|
104
|
+
return -Errno::ENOENT::Errno unless entry
|
105
|
+
|
106
|
+
entry.fill_stat(stat_buf)
|
107
|
+
0
|
108
|
+
end
|
109
|
+
|
110
|
+
def readdir(path, _offset, _ffi)
|
111
|
+
%w[. ..].each { |d| yield(d, nil) }
|
112
|
+
dir = find(path)
|
113
|
+
dir.entries.each_pair { |k, e| yield(k, e.fill_stat) }
|
114
|
+
end
|
115
|
+
|
116
|
+
def create(path, mode, _ffi)
|
117
|
+
dir_entries = find(path.dirname).entries
|
118
|
+
now = Time.now
|
119
|
+
dir_entries[path.basename.to_s] = File.new(mode, String.new, now, now, now)
|
120
|
+
@total_files += 1
|
121
|
+
0
|
122
|
+
end
|
123
|
+
|
124
|
+
# op[:read] = [:pointer, :size_t, :off_t, FuseFileInfo.by_ref]
|
125
|
+
def read(path, len, off, _ffi)
|
126
|
+
file = find(path)
|
127
|
+
file.atime = Time.now.utc
|
128
|
+
FFI::Libfuse::ThreadPool.busy
|
129
|
+
sleep 0.5
|
130
|
+
file.content[off, len]
|
131
|
+
end
|
132
|
+
|
133
|
+
# write(const char* path, char *buf, size_t size, off_t offset, struct fuse_file_info* fi)
|
134
|
+
def write(path, data, offset, _ffi)
|
135
|
+
file = find(path)
|
136
|
+
content = file.content
|
137
|
+
@total_size -= content.size
|
138
|
+
content[offset, data.length] = data
|
139
|
+
@total_size += content.size
|
140
|
+
file.mtime = Time.now.utc
|
141
|
+
end
|
142
|
+
|
143
|
+
def truncate(path, size)
|
144
|
+
file = find(path)
|
145
|
+
@total_size -= file.content.size
|
146
|
+
file.content[size..-1] = ''
|
147
|
+
file.mtime = Time.now.utc
|
148
|
+
@total_size += file.content.size
|
149
|
+
0
|
150
|
+
end
|
151
|
+
|
152
|
+
def unlink(path)
|
153
|
+
dir = find(path.dirname)
|
154
|
+
deleted = dir.entries.delete(path.basename.to_s)
|
155
|
+
@total_files -= 1
|
156
|
+
@total_size -= deleted.content.size if deleted.is_a?(File)
|
157
|
+
0
|
158
|
+
end
|
159
|
+
|
160
|
+
def mkdir(path, mode)
|
161
|
+
entries = find(path.dirname).entries
|
162
|
+
now = Time.now
|
163
|
+
entries[path.basename.to_s] = Dir.new(mode, {}, now, now, now)
|
164
|
+
end
|
165
|
+
|
166
|
+
def rmdir(path)
|
167
|
+
dir = find(path)
|
168
|
+
raise Errno::ENOTDIR unless dir.is_a?(Dir)
|
169
|
+
raise Errno::ENOTEMPTY unless dir.entries.empty?
|
170
|
+
|
171
|
+
find(path.dirname).entries.delete(path.basename.to_s)
|
172
|
+
0
|
173
|
+
end
|
174
|
+
|
175
|
+
def utimens(path, atime, mtime)
|
176
|
+
entry = find(path)
|
177
|
+
entry.atime = atime if atime
|
178
|
+
entry.mtime = mtime if mtime
|
179
|
+
0
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
|
184
|
+
def find(path)
|
185
|
+
path.root? ? root : root.dig(*path.to_s.split('/')[1..])
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
exit(FFI::Libfuse.fuse_main($0, *ARGV, operations: MemoryFS.new)) if __FILE__ == $0
|