ffi-libfuse 0.0.1.pre
Sign up to get free protection for your applications and to get access to all the features.
- 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
|