ffi-libfuse 0.0.1.pre → 0.1.0.rc20220550
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 +4 -4
- data/.yardopts +3 -1
- data/CHANGES.md +14 -0
- data/LICENSE +21 -0
- data/README.md +127 -44
- data/lib/ffi/accessors.rb +6 -6
- data/lib/ffi/boolean_int.rb +27 -0
- data/lib/ffi/devt.rb +23 -0
- data/lib/ffi/encoding.rb +38 -0
- data/lib/ffi/gnu_extensions.rb +1 -1
- data/lib/ffi/libfuse/ackbar.rb +6 -8
- data/lib/ffi/libfuse/adapter/context.rb +12 -10
- data/lib/ffi/libfuse/adapter/fuse2_compat.rb +52 -51
- data/lib/ffi/libfuse/adapter/fuse3_support.rb +0 -1
- data/lib/ffi/libfuse/adapter/ruby.rb +499 -148
- data/lib/ffi/libfuse/adapter/safe.rb +1 -1
- data/lib/ffi/libfuse/adapter.rb +1 -2
- data/lib/ffi/libfuse/callbacks.rb +1 -1
- data/lib/ffi/libfuse/filesystem/accounting.rb +116 -0
- data/lib/ffi/libfuse/filesystem/mapped_dir.rb +74 -0
- data/lib/ffi/libfuse/filesystem/mapped_files.rb +141 -0
- data/lib/ffi/libfuse/filesystem/pass_through_dir.rb +55 -0
- data/lib/ffi/libfuse/filesystem/pass_through_file.rb +45 -0
- data/lib/ffi/libfuse/filesystem/utils.rb +102 -0
- data/lib/ffi/libfuse/filesystem/virtual_dir.rb +306 -0
- data/lib/ffi/libfuse/filesystem/virtual_file.rb +94 -0
- data/lib/ffi/libfuse/filesystem/virtual_fs.rb +188 -0
- data/lib/ffi/libfuse/filesystem/virtual_node.rb +101 -0
- data/lib/ffi/libfuse/filesystem.rb +25 -0
- data/lib/ffi/libfuse/fuse2.rb +21 -21
- data/lib/ffi/libfuse/fuse3.rb +12 -12
- data/lib/ffi/libfuse/fuse_args.rb +69 -34
- data/lib/ffi/libfuse/fuse_buffer.rb +128 -26
- data/lib/ffi/libfuse/fuse_callbacks.rb +1 -5
- data/lib/ffi/libfuse/fuse_common.rb +55 -61
- data/lib/ffi/libfuse/fuse_config.rb +134 -143
- data/lib/ffi/libfuse/fuse_conn_info.rb +310 -134
- data/lib/ffi/libfuse/fuse_context.rb +45 -3
- data/lib/ffi/libfuse/fuse_operations.rb +43 -19
- data/lib/ffi/libfuse/fuse_version.rb +10 -6
- data/lib/ffi/libfuse/main.rb +80 -37
- data/lib/ffi/libfuse/thread_pool.rb +1 -1
- data/lib/ffi/libfuse/version.rb +1 -1
- data/lib/ffi/libfuse.rb +13 -4
- data/lib/ffi/ruby_object.rb +1 -1
- data/lib/ffi/stat/constants.rb +9 -0
- data/lib/ffi/stat/native.rb +36 -6
- data/lib/ffi/stat/time_spec.rb +28 -12
- data/lib/ffi/stat.rb +111 -22
- data/lib/ffi/stat_vfs.rb +59 -1
- data/lib/ffi/struct_wrapper.rb +22 -1
- data/sample/hello_fs.rb +54 -0
- data/sample/memory_fs.rb +5 -181
- data/sample/no_fs.rb +20 -21
- data/sample/pass_through_fs.rb +30 -0
- metadata +66 -7
- data/lib/ffi/libfuse/adapter/thread_local_context.rb +0 -36
data/lib/ffi/stat.rb
CHANGED
@@ -1,28 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'struct_wrapper'
|
4
|
-
require_relative 'stat/native'
|
5
4
|
require_relative 'stat/constants'
|
6
5
|
|
7
6
|
module FFI
|
8
7
|
# Ruby representation of stat.h struct
|
9
8
|
class Stat
|
10
|
-
|
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
|
9
|
+
# Use a StructWrapper because of clash with #size and the ability to attach functions
|
24
10
|
include StructWrapper
|
25
11
|
|
12
|
+
extend FFI::Library
|
13
|
+
ffi_lib FFI::Library::LIBC
|
14
|
+
|
15
|
+
# stat/native will attach functions to Stat
|
16
|
+
require_relative 'stat/native'
|
26
17
|
native_struct(Native)
|
27
18
|
|
28
19
|
# @!attribute [rw] mode
|
@@ -56,16 +47,12 @@ module FFI
|
|
56
47
|
# @!attribute [rw] ctime
|
57
48
|
# @return [Time] time of last status change
|
58
49
|
|
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 }
|
50
|
+
time_members = Native.members.select { |m| m.to_s =~ /^st_.*timespec$/ }.map { |m| m[3..-5].to_sym }
|
63
51
|
|
64
52
|
ffi_attr_reader(*time_members, format: 'st_%sspec', &:time)
|
65
53
|
|
66
54
|
ffi_attr_writer(*time_members, format: 'st_%sspec', simple: false) do |sec, nsec = 0|
|
67
|
-
|
68
|
-
t.set_time(sec, nsec)
|
55
|
+
self[__method__[0..-2].to_sym].set_time(sec, nsec)
|
69
56
|
end
|
70
57
|
|
71
58
|
# Fill content for a regular file
|
@@ -87,10 +74,112 @@ module FFI
|
|
87
74
|
# @param [Integer] gid
|
88
75
|
# @param [Hash] args additional system specific stat fields
|
89
76
|
# @return [self]
|
90
|
-
def dir(mode:, nlink:
|
77
|
+
def dir(mode:, nlink: 3, uid: Process.uid, gid: Process.gid, **args)
|
91
78
|
mode = ((S_IFDIR & S_IFMT) | (mode & 0o777))
|
92
79
|
fill(mode: mode, uid: uid, gid: gid, nlink: nlink, **args)
|
93
80
|
end
|
94
81
|
alias directory dir
|
82
|
+
|
83
|
+
# Fill attributes from file (using native LIBC calls)
|
84
|
+
# @param [Integer|:to_s] file descriptor or a file path
|
85
|
+
# @param [Boolean] follow links
|
86
|
+
# @return [self]
|
87
|
+
def from(file, follow: true)
|
88
|
+
return fstat(file) if file.is_a?(Integer)
|
89
|
+
|
90
|
+
return stat(file.to_s) if follow
|
91
|
+
|
92
|
+
lstat(file.to_s)
|
93
|
+
end
|
94
|
+
|
95
|
+
# @!method stat(path)
|
96
|
+
# Fill attributes from file, following links
|
97
|
+
# @param [:to_s] path a file path
|
98
|
+
# @raise [SystemCallError] on error
|
99
|
+
# @return [self]
|
100
|
+
|
101
|
+
# @!method lstat(path)
|
102
|
+
# Fill attributes from file path, without following links
|
103
|
+
# @param [:to_s] path
|
104
|
+
# @raise [SystemCallError] on error
|
105
|
+
# @return [self]
|
106
|
+
|
107
|
+
# @!method fstat(fileno)
|
108
|
+
# Fill attributes from file descriptor
|
109
|
+
# @param [:to_i] fileno file descriptor
|
110
|
+
# @raise [SystemCallError] on error
|
111
|
+
# @return [self]
|
112
|
+
|
113
|
+
%i[stat lstat fstat].each do |m|
|
114
|
+
define_method(m) do |file|
|
115
|
+
res = self.class.send("native_#{m}", (m == :fstat ? file.to_i : file.to_s), native)
|
116
|
+
raise SystemCallError.new('', FFI::LastError.error) unless res.zero?
|
117
|
+
|
118
|
+
self
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Apply permissions mask to mode
|
123
|
+
# @param [Integer] mask (see umask)
|
124
|
+
# @param [Hash] overrides see {fill}
|
125
|
+
# @return self
|
126
|
+
def mask(mask = 0o4000, **overrides)
|
127
|
+
fill(mode: mode & (~mask), **overrides)
|
128
|
+
end
|
129
|
+
|
130
|
+
def file?
|
131
|
+
mode & S_IFREG != 0
|
132
|
+
end
|
133
|
+
|
134
|
+
def directory?
|
135
|
+
mode & S_IFDIR != 0
|
136
|
+
end
|
137
|
+
|
138
|
+
def setuid?
|
139
|
+
mode & S_ISUID != 0
|
140
|
+
end
|
141
|
+
|
142
|
+
def setgid?
|
143
|
+
mode & S_ISGID != 0
|
144
|
+
end
|
145
|
+
|
146
|
+
def sticky?
|
147
|
+
mode & S_ISVTX != 0
|
148
|
+
end
|
149
|
+
|
150
|
+
class << self
|
151
|
+
# @!method file(stat,**fields)
|
152
|
+
# @return [Stat]
|
153
|
+
# @raise [SystemCallError]
|
154
|
+
# @see Stat#file
|
155
|
+
|
156
|
+
# @!method dir(stat,**fields)
|
157
|
+
# @return [Stat]
|
158
|
+
# @raise [SystemCallError]
|
159
|
+
# @see Stat#dir
|
160
|
+
%i[file dir].each { |m| define_method(m) { |stat = new, **args| stat.send(m, **args) } }
|
161
|
+
alias directory dir
|
162
|
+
|
163
|
+
# @!method from(file, stat = new(), follow: false)
|
164
|
+
# @return [Stat]
|
165
|
+
# @raise [SystemCallError]
|
166
|
+
# @see Stat#from
|
167
|
+
|
168
|
+
# @!method stat(file, stat = new())
|
169
|
+
# @return [Stat]
|
170
|
+
# @raise [SystemCallError]
|
171
|
+
# @see Stat#stat
|
172
|
+
|
173
|
+
# @!method lstat(file, stat = new())
|
174
|
+
# @return [Stat]
|
175
|
+
# @raise [SystemCallError]
|
176
|
+
# @see Stat#lstat
|
177
|
+
|
178
|
+
# @!method fstat(file, stat = new())
|
179
|
+
# @return [Stat]
|
180
|
+
# @raise [SystemCallError]
|
181
|
+
# @see Stat#fstat
|
182
|
+
%i[from stat lstat fstat].each { |m| define_method(m) { |file, stat = new, **args| stat.send(m, file, **args) } }
|
183
|
+
end
|
95
184
|
end
|
96
185
|
end
|
data/lib/ffi/stat_vfs.rb
CHANGED
@@ -75,7 +75,65 @@ module FFI
|
|
75
75
|
# @!attribute [rw] namemax
|
76
76
|
# @return [Integer] Maximum filename length
|
77
77
|
|
78
|
-
int_members = members.
|
78
|
+
int_members = members.grep(/^f_/).map { |m| m[2..].to_sym }
|
79
79
|
ffi_attr_accessor(*int_members, format: 'f_%s')
|
80
|
+
|
81
|
+
extend FFI::Library
|
82
|
+
ffi_lib FFI::Library::LIBC
|
83
|
+
|
84
|
+
attach_function :native_statvfs, :statvfs, [:string, by_ref], :int
|
85
|
+
attach_function :native_fstatvfs, :fstatvfs, [:int, by_ref], :int
|
86
|
+
|
87
|
+
# Fill from native statvfs for path
|
88
|
+
# @param [:to_s] path
|
89
|
+
# @return [self]
|
90
|
+
def statvfs(path)
|
91
|
+
res = self.class.native_statvfs(path.to_s, self)
|
92
|
+
raise SystemCallError.new('', FFI::LastError.errno) unless res.zero?
|
93
|
+
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
# Fill from native fstatvfs for fileno
|
98
|
+
# @param [Integer] fileno
|
99
|
+
# @return [self]
|
100
|
+
def fstatvfs(fileno)
|
101
|
+
res = self.class.native_fstatvfs(fileno, self)
|
102
|
+
raise SystemCallError.new('', FFI::LastError.errno) unless res.zero?
|
103
|
+
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
107
|
+
# File from native LIBC calls for file
|
108
|
+
# @param [Integer|:to_s] file a file descriptor or a file path
|
109
|
+
# @return [self]
|
110
|
+
def from(file)
|
111
|
+
return fstatvfs(file) if file.is_a?(Integer)
|
112
|
+
|
113
|
+
statvfs(file)
|
114
|
+
end
|
115
|
+
|
116
|
+
class << self
|
117
|
+
# @!method from(file)
|
118
|
+
# @return [StatVfs]
|
119
|
+
# @raise [SystemCallError]
|
120
|
+
# @see StatVfs#from
|
121
|
+
|
122
|
+
# @!method statvfs(file)
|
123
|
+
# @return [StatVfs]
|
124
|
+
# @raise [SystemCallError]
|
125
|
+
# @see StatVfs#statvfs
|
126
|
+
|
127
|
+
# @!method fstatvfs(file)
|
128
|
+
# @return [StatVfs]
|
129
|
+
# @raise [SystemCallError]
|
130
|
+
# @see StatVfs#fstatvfs
|
131
|
+
%i[from statvfs fstatvfs].each { |m| define_method(m) { |file, stat = new, **args| stat.send(m, file, **args) } }
|
132
|
+
|
133
|
+
# @!visibility private
|
134
|
+
|
135
|
+
# @!method native_statvfs(path, statvfs_buf)
|
136
|
+
# @!method native_fstatvfs(fd, statvfs_buf)
|
137
|
+
end
|
80
138
|
end
|
81
139
|
end
|
data/lib/ffi/struct_wrapper.rb
CHANGED
@@ -5,6 +5,16 @@ require_relative 'accessors'
|
|
5
5
|
|
6
6
|
module FFI
|
7
7
|
# Helper to wrap structs with ugly names and attribute clashes with FFI::Struct (eg size)
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# class MyStruct
|
11
|
+
# include FFI::StructWrapper
|
12
|
+
# native_struct(MyNativeStruct)
|
13
|
+
#
|
14
|
+
# #!@attribute [rw] field
|
15
|
+
# ffi_attr_accessor :field
|
16
|
+
# end
|
17
|
+
#
|
8
18
|
module StructWrapper
|
9
19
|
# @!visibility private
|
10
20
|
class ByReference < StructByReference
|
@@ -78,8 +88,9 @@ module FFI
|
|
78
88
|
|
79
89
|
# @!parse extend ClassMethods
|
80
90
|
# @!parse include Accessors
|
91
|
+
# @!parse extend Accessors::ClassMethods
|
81
92
|
|
82
|
-
#
|
93
|
+
# @return [FFI::Struct] the underlying native struct
|
83
94
|
attr_reader :native
|
84
95
|
|
85
96
|
# @!visibility private
|
@@ -96,5 +107,15 @@ module FFI
|
|
96
107
|
def []=(member_or_attr, val)
|
97
108
|
@native[self.class.ffi_attr_writers.fetch(member_or_attr, member_or_attr)] = val
|
98
109
|
end
|
110
|
+
|
111
|
+
# Pass unimplemented methods on to {#native} underlying struct
|
112
|
+
def method_missing(method, *args)
|
113
|
+
@native.send(method, *args)
|
114
|
+
end
|
115
|
+
|
116
|
+
# @!visibility private
|
117
|
+
def respond_to_missing?(method, private = false)
|
118
|
+
@native.respond_to?(method, private)
|
119
|
+
end
|
99
120
|
end
|
100
121
|
end
|
data/sample/hello_fs.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'ffi/libfuse'
|
5
|
+
|
6
|
+
# Hello World!
|
7
|
+
class HelloFS
|
8
|
+
include FFI::Libfuse::Adapter::Ruby
|
9
|
+
include FFI::Libfuse::Adapter::Fuse2Compat
|
10
|
+
|
11
|
+
# FUSE Configuration methods
|
12
|
+
|
13
|
+
def fuse_options(args)
|
14
|
+
args.parse!({ 'subject=' => :subject }) do |key:, value:, **|
|
15
|
+
raise FFI::Libfuse::Error, 'subject option must be at least 2 characters' unless value.size >= 2
|
16
|
+
|
17
|
+
@subject = value if key == :subject
|
18
|
+
:handled
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def fuse_help
|
23
|
+
'-o subject=<subject> a target to say hello to'
|
24
|
+
end
|
25
|
+
|
26
|
+
def fuse_configure
|
27
|
+
@subject ||= 'World!'
|
28
|
+
@content = "Hello #{@subject}\n"
|
29
|
+
end
|
30
|
+
|
31
|
+
# FUSE callbacks
|
32
|
+
|
33
|
+
def getattr(path, stat, *_args)
|
34
|
+
case path
|
35
|
+
when '/'
|
36
|
+
stat.directory(mode: 0o550)
|
37
|
+
when '/hello.txt'
|
38
|
+
stat.file(mode: 0o440, size: @content.size)
|
39
|
+
else
|
40
|
+
raise Errno::ENOENT
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def readdir(_path, *_args)
|
45
|
+
yield 'hello.txt'
|
46
|
+
end
|
47
|
+
|
48
|
+
def read(_path, *_args)
|
49
|
+
@content
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Start the file system
|
54
|
+
FFI::Libfuse.fuse_main(operations: HelloFS.new) if __FILE__ == $0
|
data/sample/memory_fs.rb
CHANGED
@@ -2,188 +2,12 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'ffi/libfuse'
|
5
|
+
require 'ffi/libfuse/filesystem/virtual_fs'
|
5
6
|
|
6
7
|
# 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
|
8
|
+
class MemoryFS < FFI::Libfuse::Filesystem::VirtualFS; end
|
22
9
|
|
23
|
-
|
24
|
-
|
25
|
-
include FFI::Libfuse::Adapter::Pathname
|
10
|
+
# Set this to test multi-threading etc...
|
11
|
+
main_class = ENV.fetch('MEMORY_FS_SKIP_DEFAULT_ARGS', 'N') == 'Y' ? FFI::Libfuse::Main : FFI::Libfuse
|
26
12
|
|
27
|
-
|
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
|
13
|
+
exit(main_class.fuse_main(operations: MemoryFS.new)) if __FILE__ == $0
|
data/sample/no_fs.rb
CHANGED
@@ -6,13 +6,21 @@ require 'ffi/libfuse'
|
|
6
6
|
# An empty file system
|
7
7
|
class NoFS
|
8
8
|
include FFI::Libfuse::Adapter::Context
|
9
|
-
include FFI::Libfuse::Adapter::Fuse3Support
|
10
9
|
include FFI::Libfuse::Adapter::Ruby
|
10
|
+
include FFI::Libfuse::Adapter::Fuse3Support # must run outside of Adapter::Ruby
|
11
11
|
|
12
12
|
OPTIONS = { 'log=' => :log }.freeze
|
13
13
|
|
14
|
-
def fuse_options
|
15
|
-
OPTIONS
|
14
|
+
def fuse_options(args)
|
15
|
+
args.parse!(OPTIONS) do |key:, value:, **|
|
16
|
+
case key
|
17
|
+
when :log
|
18
|
+
@logfile = value
|
19
|
+
else
|
20
|
+
next :keep
|
21
|
+
end
|
22
|
+
:handled
|
23
|
+
end
|
16
24
|
end
|
17
25
|
|
18
26
|
def fuse_help
|
@@ -24,46 +32,37 @@ class NoFS
|
|
24
32
|
end
|
25
33
|
|
26
34
|
def fuse_version
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
def fuse_opt_proc(_data, arg, key, _outargs)
|
31
|
-
case key
|
32
|
-
when :log
|
33
|
-
@logfile = arg[4..]
|
34
|
-
return :handled
|
35
|
-
end
|
36
|
-
:keep
|
35
|
+
"NoFS: Version x.y.z. Fuse3Compat=#{fuse3_compat?}"
|
37
36
|
end
|
38
37
|
|
39
|
-
def getattr(
|
38
|
+
def getattr(path, stat)
|
40
39
|
raise Errno::ENOENT unless path == '/'
|
41
40
|
|
42
41
|
stat.directory(mode: 0o555)
|
43
42
|
end
|
44
43
|
|
45
|
-
def readdir(
|
46
|
-
|
44
|
+
def readdir(_path, _offset, _ffi, &block)
|
45
|
+
puts "NOFS Readdir: #{block}"
|
46
|
+
%w[. ..].each(&block)
|
47
47
|
end
|
48
48
|
|
49
49
|
def log
|
50
50
|
@log ||= File.open(@logfile || '/tmp/no_fs.out', 'a')
|
51
51
|
end
|
52
52
|
|
53
|
-
def init(
|
53
|
+
def init(_conn)
|
54
|
+
ctx = FFI::Libfuse::Adapter::Context.fuse_context
|
54
55
|
log.puts("NoFS init ctx- #{ctx.inspect}") if ctx
|
55
|
-
log.puts("NoFS init conn - #{conn.inspect}") if conn && !conn.null?
|
56
|
-
log.puts "NoFS init cfg #{cfg.inspect}" if cfg && !cfg.null?
|
57
56
|
warn 'NoFS: DEBUG enabled' if debug?
|
58
57
|
log.flush
|
59
58
|
'INIT_DATA'
|
60
59
|
end
|
61
60
|
|
62
|
-
def destroy(obj
|
61
|
+
def destroy(obj)
|
63
62
|
# If the fs is not cleanly unmounted the init data will have been GC'd by the time this is called
|
64
63
|
log.puts("NoFS destroy- #{obj.inspect}") if !obj.is_a?(WeakRef) || obj.weakref_alive?
|
65
64
|
log.puts "NoFS destroy- pid=#{Process.pid}"
|
66
65
|
end
|
67
66
|
end
|
68
67
|
|
69
|
-
exit(FFI::Libfuse.fuse_main($0, *ARGV, operations: NoFS.new, private_data: 'MAIN_DATA')) if __FILE__ == $0
|
68
|
+
exit(FFI::Libfuse::Main.fuse_main($0, *ARGV, operations: NoFS.new, private_data: 'MAIN_DATA')) if __FILE__ == $0
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'ffi/libfuse'
|
5
|
+
require 'ffi/libfuse/filesystem/pass_through_dir'
|
6
|
+
|
7
|
+
# Pass Through Filesystem - over a base directory
|
8
|
+
class PassThroughFS < FFI::Libfuse::Filesystem::PassThroughDir
|
9
|
+
def fuse_options(args)
|
10
|
+
args.parse!({ 'base_dir=' => :base_dir }) do |key:, value:, **|
|
11
|
+
next :keep unless key == :base_dir
|
12
|
+
|
13
|
+
raise FFI::Libfuse::Error, "#{value} is not a directory" unless Dir.exist?(value)
|
14
|
+
|
15
|
+
self.base_dir = value
|
16
|
+
:handled
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def fuse_help
|
21
|
+
'-o base_dir=<dir>'
|
22
|
+
end
|
23
|
+
|
24
|
+
def fuse_configure
|
25
|
+
self.base_dir ||= Dir.pwd
|
26
|
+
warn "Using #{self.base_dir} as base directory for file operations" if debug?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
exit(FFI::Libfuse.fuse_main(operations: PassThroughFS.new)) if __FILE__ == $0
|