ffi-libfuse 0.3.4 → 0.4.1
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/CHANGELOG.md +29 -0
- data/README.md +1 -1
- data/lib/ffi/accessors.rb +419 -93
- data/lib/ffi/boolean_int.rb +1 -1
- data/lib/ffi/devt.rb +36 -10
- data/lib/ffi/flock.rb +31 -27
- data/lib/ffi/libfuse/adapter/context.rb +1 -1
- data/lib/ffi/libfuse/adapter/debug.rb +54 -16
- data/lib/ffi/libfuse/adapter/fuse2_compat.rb +43 -26
- data/lib/ffi/libfuse/adapter/fuse3_support.rb +7 -8
- data/lib/ffi/libfuse/adapter/interrupt.rb +1 -1
- data/lib/ffi/libfuse/adapter/pathname.rb +1 -1
- data/lib/ffi/libfuse/adapter/ruby.rb +211 -160
- data/lib/ffi/libfuse/adapter/safe.rb +70 -22
- data/lib/ffi/libfuse/callbacks.rb +2 -1
- data/lib/ffi/libfuse/filesystem/accounting.rb +1 -1
- data/lib/ffi/libfuse/filesystem/mapped_files.rb +33 -7
- data/lib/ffi/libfuse/filesystem/pass_through_dir.rb +0 -1
- data/lib/ffi/libfuse/filesystem/virtual_dir.rb +294 -127
- data/lib/ffi/libfuse/filesystem/virtual_file.rb +85 -79
- data/lib/ffi/libfuse/filesystem/virtual_fs.rb +34 -15
- data/lib/ffi/libfuse/filesystem/virtual_link.rb +60 -0
- data/lib/ffi/libfuse/filesystem/virtual_node.rb +104 -87
- data/lib/ffi/libfuse/filesystem.rb +1 -1
- data/lib/ffi/libfuse/fuse2.rb +3 -2
- data/lib/ffi/libfuse/fuse3.rb +6 -6
- data/lib/ffi/libfuse/fuse_args.rb +14 -21
- data/lib/ffi/libfuse/fuse_buf.rb +112 -0
- data/lib/ffi/libfuse/fuse_buf_vec.rb +228 -0
- data/lib/ffi/libfuse/fuse_cmdline_opts.rb +19 -16
- data/lib/ffi/libfuse/fuse_common.rb +10 -4
- data/lib/ffi/libfuse/fuse_config.rb +35 -23
- data/lib/ffi/libfuse/fuse_conn_info.rb +1 -1
- data/lib/ffi/libfuse/fuse_context.rb +2 -1
- data/lib/ffi/libfuse/fuse_loop_config.rb +68 -20
- data/lib/ffi/libfuse/fuse_operations.rb +86 -41
- data/lib/ffi/libfuse/gem_helper.rb +2 -9
- data/lib/ffi/libfuse/io.rb +56 -0
- data/lib/ffi/libfuse/main.rb +33 -26
- data/lib/ffi/libfuse/test_helper.rb +67 -61
- data/lib/ffi/libfuse/version.rb +1 -1
- data/lib/ffi/libfuse.rb +1 -1
- data/lib/ffi/stat/native.rb +4 -4
- data/lib/ffi/stat.rb +35 -12
- data/lib/ffi/stat_vfs.rb +1 -2
- data/lib/ffi/struct_array.rb +2 -1
- data/lib/ffi/struct_wrapper.rb +6 -4
- data/sample/hello_fs.rb +1 -1
- metadata +6 -3
- data/lib/ffi/libfuse/fuse_buffer.rb +0 -257
@@ -9,9 +9,6 @@ module FFI
|
|
9
9
|
module Libfuse
|
10
10
|
# Can be included test classes to assist with running/debugging filesystems
|
11
11
|
module TestHelper
|
12
|
-
# rubocop:disable Metrics/AbcSize
|
13
|
-
# rubocop:disable Metrics/MethodLength
|
14
|
-
|
15
12
|
# Runs the fuse loop on a pre configured fuse filesystem
|
16
13
|
# @param [FuseOperations] operations
|
17
14
|
# @param [Array<String>] args to pass to {FFI::Libfuse::Main.fuse_create}
|
@@ -19,7 +16,7 @@ module FFI
|
|
19
16
|
# @yield [mnt]
|
20
17
|
# caller can execute and test file operations using mnt and ruby File/Dir etc
|
21
18
|
# the block is run in a forked process and is successful unless an exception is raised
|
22
|
-
# @yieldparam [String] mnt the temporary
|
19
|
+
# @yieldparam [String] mnt the temporary directory used as the mount point
|
23
20
|
# @raise [Error] if unexpected state is found during operations
|
24
21
|
# @return [void]
|
25
22
|
def with_fuse(operations, *args, **options)
|
@@ -34,34 +31,14 @@ module FFI
|
|
34
31
|
yield mnt
|
35
32
|
end
|
36
33
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
# Rake owns INT
|
41
|
-
fuse.default_traps.delete(:TERM)
|
42
|
-
fuse.default_traps.delete(:INT)
|
43
|
-
|
44
|
-
raise FFI::Libfuse::Error, 'fuse object is not mounted?' unless fuse.mounted?
|
45
|
-
|
46
|
-
t = Thread.new { fuse.run(foreground: true, **options) }
|
47
|
-
|
48
|
-
# TODO: Work out why waitpid2 hangs on mac unless the process has already finished
|
49
|
-
sleep 10 if mac_fuse?
|
50
|
-
|
51
|
-
_pid, block_status = Process.waitpid2(fpid)
|
52
|
-
block_exit = block_status.exitstatus
|
53
|
-
fuse.exit('fuse_helper')&.join
|
54
|
-
run_result = t.value
|
55
|
-
|
56
|
-
raise FFI::Libfuse::Error, 'fuse is still mounted after fuse.exit' if fuse.mounted?
|
57
|
-
raise FFI::Libfuse::Error, "forked file operations failed with #{block_exit}" unless block_exit.zero?
|
58
|
-
raise FFI::Libfuse::Error, "fuse run failed #{run_result}" unless run_result.zero?
|
34
|
+
run_fuse(mnt, *args, operations: operations, **options) do
|
35
|
+
# TODO: Work out why waitpid2 hangs on mac unless the process has already finished
|
36
|
+
sleep 10 if mac_fuse?
|
59
37
|
|
60
|
-
|
61
|
-
|
38
|
+
_pid, block_status = Process.waitpid2(fpid)
|
39
|
+
block_exit = block_status.exitstatus
|
40
|
+
raise FFI::Libfuse::Error, "forked file operations failed with #{block_exit}" unless block_exit.zero?
|
62
41
|
end
|
63
|
-
|
64
|
-
true
|
65
42
|
end
|
66
43
|
end
|
67
44
|
|
@@ -77,42 +54,28 @@ module FFI
|
|
77
54
|
# @note if the filesystem is configured to daemonize then no output will be captured
|
78
55
|
def run_filesystem(filesystem, *args, env: {})
|
79
56
|
fsname = File.basename(filesystem)
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
Bundler.with_unbundled_env do
|
84
|
-
Open3.capture3(env, 'bundle', 'exec', filesystem.to_s, mnt, "-ofsname=#{fsname}", *args, binmode: true)
|
85
|
-
end
|
86
|
-
else
|
87
|
-
Open3.capture3(env, filesystem.to_s, mnt, "-ofsname=#{fsname}", *args, binmode: true)
|
88
|
-
end
|
89
|
-
end
|
57
|
+
|
58
|
+
t, err = safe_fuse do |mnt|
|
59
|
+
t = Thread.new { open3_filesystem(args, env, filesystem, fsname, mnt) }
|
90
60
|
sleep 1
|
91
61
|
|
92
62
|
begin
|
93
|
-
if block_given?
|
94
|
-
raise Error, "#{fsname} not mounted at #{mnt}" unless mounted?(mnt, fsname)
|
95
|
-
|
96
|
-
yield mnt
|
97
|
-
end
|
98
|
-
# rubocop:disable Lint/RescueException
|
99
|
-
# Minitest::Assertion and other test assertion classes are not derived from StandardError
|
100
|
-
rescue Exception => _err
|
101
|
-
# rubocop:enable Lint/RescueException
|
102
|
-
unmount(mnt) if mounted?(mnt)
|
103
|
-
o, e, _s = t.value
|
104
|
-
warn "Errors\n#{e}" unless e.empty?
|
105
|
-
warn "Output\n#{o}" unless o.empty?
|
106
|
-
raise
|
107
|
-
end
|
63
|
+
raise Error, "#{fsname} not mounted at #{mnt}" if block_given? && !mounted?(mnt, fsname)
|
108
64
|
|
109
|
-
|
110
|
-
|
111
|
-
|
65
|
+
yield mnt if block_given?
|
66
|
+
[t, nil]
|
67
|
+
rescue Minitest::Assertion, StandardError => e
|
68
|
+
[t, e]
|
69
|
+
end
|
112
70
|
end
|
71
|
+
|
72
|
+
o, e, s = t.value
|
73
|
+
return [o, e, s.exitstatus] unless err
|
74
|
+
|
75
|
+
warn "Errors\n#{e}" unless e.empty?
|
76
|
+
warn "Output\n#{o}" unless o.empty?
|
77
|
+
raise err
|
113
78
|
end
|
114
|
-
# rubocop:enable Metrics/AbcSize
|
115
|
-
# rubocop:enable Metrics/MethodLength
|
116
79
|
|
117
80
|
def mounted?(mnt, _filesystem = '.*')
|
118
81
|
type, prefix = mac_fuse? ? %w[macfuse /private] : %w[fuse]
|
@@ -124,7 +87,7 @@ module FFI
|
|
124
87
|
if mac_fuse?
|
125
88
|
system("diskutil unmount force #{mnt} >/dev/null 2>&1")
|
126
89
|
else
|
127
|
-
system("fusermount -zu #{mnt} >/dev/null 2>&1")
|
90
|
+
system("fusermount#{FUSE_MAJOR_VERSION == 3 ? '3' : ''} -zu #{mnt} >/dev/null 2>&1")
|
128
91
|
end
|
129
92
|
end
|
130
93
|
|
@@ -140,6 +103,49 @@ module FFI
|
|
140
103
|
def mac_fuse?
|
141
104
|
FFI::Platform::IS_MAC
|
142
105
|
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def open3_filesystem(args, env, filesystem, fsname, mnt)
|
110
|
+
if ENV['BUNDLER_GEMFILE']
|
111
|
+
Open3.capture3(env, 'bundle', 'exec', filesystem.to_s, mnt, "-ofsname=#{fsname}", *args, binmode: true)
|
112
|
+
else
|
113
|
+
Open3.capture3(env, filesystem.to_s, mnt, "-ofsname=#{fsname}", *args, binmode: true)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def run_fuse(mnt, *args, operations:, **options)
|
118
|
+
fuse = mount_fuse(mnt, *args, operations: operations)
|
119
|
+
begin
|
120
|
+
t = Thread.new do
|
121
|
+
Thread.current.name = 'Fuse Run'
|
122
|
+
# Rake owns INT
|
123
|
+
fuse.run(foreground: true, traps: { INT: nil, TERM: nil }, **options)
|
124
|
+
end
|
125
|
+
|
126
|
+
yield
|
127
|
+
ensure
|
128
|
+
fuse.exit('fuse_helper')&.join
|
129
|
+
run_result = t.value
|
130
|
+
|
131
|
+
raise FFI::Libfuse::Error, 'fuse is still mounted after fuse.exit' if fuse.mounted?
|
132
|
+
raise FFI::Libfuse::Error, "fuse run failed #{run_result}" unless run_result.zero?
|
133
|
+
|
134
|
+
if !mac_fuse? && mounted?(mnt)
|
135
|
+
raise FFI::Libfuse::Error, "OS reports fuse is still mounted at #{mnt} after fuse.exit"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def mount_fuse(mnt, *args, operations:)
|
141
|
+
operations.fuse_debug(args.include?('-d')) if operations.respond_to?(:fuse_debug)
|
142
|
+
|
143
|
+
fuse = FFI::Libfuse::Main.fuse_create(mnt, *args, operations: operations)
|
144
|
+
raise FFI::Libfuse::Error, 'No fuse object returned from fuse_create' unless fuse
|
145
|
+
raise FFI::Libfuse::Error, 'fuse object is not mounted?' unless fuse.mounted?
|
146
|
+
|
147
|
+
fuse
|
148
|
+
end
|
143
149
|
end
|
144
150
|
end
|
145
151
|
end
|
data/lib/ffi/libfuse/version.rb
CHANGED
data/lib/ffi/libfuse.rb
CHANGED
@@ -11,7 +11,7 @@ require_relative 'devt'
|
|
11
11
|
module FFI
|
12
12
|
# Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
|
13
13
|
module Libfuse
|
14
|
-
# Filesystems can raise this error to indicate
|
14
|
+
# Filesystems can raise this error to indicate misconfiguration issues etc...
|
15
15
|
class Error < StandardError; end
|
16
16
|
|
17
17
|
# Opinionated default args for {.main}.
|
data/lib/ffi/stat/native.rb
CHANGED
@@ -27,14 +27,14 @@ module FFI
|
|
27
27
|
:unused, [:long, 3]
|
28
28
|
|
29
29
|
[['', :string], ['l', :string], ['f', :int]].each do |(prefix, ftype)|
|
30
|
-
native_func = "native_#{prefix}stat"
|
31
|
-
lib_func = "#{prefix}stat"
|
30
|
+
native_func = :"native_#{prefix}stat"
|
31
|
+
lib_func = :"#{prefix}stat"
|
32
32
|
begin
|
33
33
|
::FFI::Stat.attach_function native_func, lib_func, [ftype, by_ref], :int
|
34
34
|
rescue FFI::NotFoundError
|
35
35
|
# gLibc 2.31 (Ubuntu focal) does not export these functions, it maps them to __xstat variants
|
36
|
-
native_xfunc = "native_#{prefix}xstat"
|
37
|
-
lib_xfunc = "__#{prefix}xstat"
|
36
|
+
native_xfunc = :"native_#{prefix}xstat"
|
37
|
+
lib_xfunc = :"__#{prefix}xstat"
|
38
38
|
::FFI::Stat.attach_function native_xfunc, lib_xfunc, [:int, ftype, by_ref], :int
|
39
39
|
# 1 is 64 bit versions of struct stat, 3 is 32 bit
|
40
40
|
::FFI::Stat.define_singleton_method(native_func) { |file, buf| send(native_xfunc, 1, file, buf) }
|
data/lib/ffi/stat.rb
CHANGED
@@ -34,9 +34,9 @@ module FFI
|
|
34
34
|
int_members = Native
|
35
35
|
.members
|
36
36
|
.select { |m| m.to_s.start_with?('st_') && !m.to_s.end_with?('timespec') }
|
37
|
-
.
|
37
|
+
.to_h { |m| [:"#{m[3..]}", m] }
|
38
38
|
|
39
|
-
ffi_attr_accessor(
|
39
|
+
ffi_attr_accessor(**int_members)
|
40
40
|
|
41
41
|
# @!attribute [rw] atime
|
42
42
|
# @return [Time] time of last access
|
@@ -47,12 +47,13 @@ module FFI
|
|
47
47
|
# @!attribute [rw] ctime
|
48
48
|
# @return [Time] time of last status change
|
49
49
|
|
50
|
-
time_members = Native.members.select { |m| m.to_s =~ /^st_.*timespec$/ }.
|
50
|
+
time_members = Native.members.select { |m| m.to_s =~ /^st_.*timespec$/ }.to_h { |m| [:"#{m[3..-5]}", m] }
|
51
51
|
|
52
|
-
ffi_attr_reader(
|
52
|
+
ffi_attr_reader(**time_members, &:time)
|
53
53
|
|
54
|
-
|
55
|
-
|
54
|
+
ffi_attr_writer_method(**time_members) do |sec, nsec = 0|
|
55
|
+
_attr, member = ffi_attr_writer_member(__method__)
|
56
|
+
self[member].set_time(sec, nsec)
|
56
57
|
end
|
57
58
|
|
58
59
|
# Fill content for a regular file
|
@@ -62,9 +63,9 @@ module FFI
|
|
62
63
|
# @param [Integer] gid
|
63
64
|
# @param [Hash] args additional system specific stat fields
|
64
65
|
# @return [self]
|
65
|
-
def file(mode:, size:, uid: Process.uid, gid: Process.gid, **args)
|
66
|
+
def file(mode:, size:, nlink: 1, uid: Process.uid, gid: Process.gid, **args)
|
66
67
|
mode = ((S_IFREG & S_IFMT) | (mode & 0o777))
|
67
|
-
fill(mode: mode, size: size, uid: uid, gid: gid, **args)
|
68
|
+
fill(mode: mode, size: size, nlink: nlink, uid: uid, gid: gid, **args)
|
68
69
|
end
|
69
70
|
|
70
71
|
# Fill content for a directory
|
@@ -80,6 +81,18 @@ module FFI
|
|
80
81
|
end
|
81
82
|
alias directory dir
|
82
83
|
|
84
|
+
# Fill content for a symbolic link
|
85
|
+
# @param [Integer] size length of the target name (including null terminator)
|
86
|
+
# @param [Integer] mode
|
87
|
+
# @param [Integer] uid
|
88
|
+
# @param [Integer] gid
|
89
|
+
# @param [Hash] args additional system specific stat fields
|
90
|
+
# @return [self]
|
91
|
+
def symlink(size:, mode: 0o777, nlink: 1, uid: Process.uid, gid: Process.gid, **args)
|
92
|
+
mode = ((S_IFLNK & S_IFMT) | (mode & 0o777))
|
93
|
+
fill(mode: mode, nlink: nlink, size: size, uid: uid, gid: gid, **args)
|
94
|
+
end
|
95
|
+
|
83
96
|
# Fill attributes from file (using native LIBC calls)
|
84
97
|
# @param [Integer|:to_s] file descriptor or a file path
|
85
98
|
# @param [Boolean] follow links
|
@@ -123,7 +136,7 @@ module FFI
|
|
123
136
|
# @param [Integer] mask (see umask)
|
124
137
|
# @param [Hash] overrides see {fill}
|
125
138
|
# @return self
|
126
|
-
def mask(mask =
|
139
|
+
def mask(mask = S_ISUID, **overrides)
|
127
140
|
fill(mode: mode & (~mask), **overrides)
|
128
141
|
end
|
129
142
|
|
@@ -147,17 +160,27 @@ module FFI
|
|
147
160
|
mode & S_ISVTX != 0
|
148
161
|
end
|
149
162
|
|
163
|
+
def symlink?
|
164
|
+
mode & S_IFLNK != 0
|
165
|
+
end
|
166
|
+
|
150
167
|
class << self
|
151
|
-
# @!method file(
|
168
|
+
# @!method file(**fields)
|
152
169
|
# @return [Stat]
|
153
170
|
# @raise [SystemCallError]
|
154
171
|
# @see Stat#file
|
155
172
|
|
156
|
-
# @!method dir(
|
173
|
+
# @!method dir(**fields)
|
157
174
|
# @return [Stat]
|
158
175
|
# @raise [SystemCallError]
|
159
176
|
# @see Stat#dir
|
160
|
-
|
177
|
+
|
178
|
+
# @!method symlink(**fields)
|
179
|
+
# @return [Stat]
|
180
|
+
# @raise [SystemCallError]
|
181
|
+
# @see Stat#symlink
|
182
|
+
|
183
|
+
%i[file dir symlink].each { |m| define_method(m) { |stat = new, **args| stat.send(m, **args) } }
|
161
184
|
alias directory dir
|
162
185
|
|
163
186
|
# @!method from(file, stat = new(), follow: false)
|
data/lib/ffi/stat_vfs.rb
CHANGED
@@ -75,8 +75,7 @@ module FFI
|
|
75
75
|
# @!attribute [rw] namemax
|
76
76
|
# @return [Integer] Maximum filename length
|
77
77
|
|
78
|
-
|
79
|
-
ffi_attr_accessor(*int_members, format: 'f_%s')
|
78
|
+
ffi_attr_accessor(**members.grep(/^f_/).to_h { |m| [m[2..].to_sym, m] })
|
80
79
|
|
81
80
|
extend FFI::Library
|
82
81
|
ffi_lib FFI::Library::LIBC
|
data/lib/ffi/struct_array.rb
CHANGED
@@ -10,6 +10,7 @@ module FFI
|
|
10
10
|
def array(size)
|
11
11
|
ArrayConverter.new(self, size)
|
12
12
|
end
|
13
|
+
alias [] array
|
13
14
|
|
14
15
|
# @!visibility private
|
15
16
|
# Helper to handle callbacks containing fixed length array of struct
|
@@ -28,7 +29,7 @@ module FFI
|
|
28
29
|
def from_native(ptr, _ctx)
|
29
30
|
return [] if ptr.null?
|
30
31
|
|
31
|
-
Array.new(@size) { |i| @type.new(ptr + (i * @type.size)) }
|
32
|
+
Array.new(@size) { |i| @type.new(ptr + (i * @type.size)) }.freeze
|
32
33
|
end
|
33
34
|
|
34
35
|
def to_native(ary, _ctx)
|
data/lib/ffi/struct_wrapper.rb
CHANGED
@@ -27,13 +27,13 @@ module FFI
|
|
27
27
|
return Pointer::NULL if value.nil?
|
28
28
|
|
29
29
|
value = value.native if value.is_a?(StructWrapper)
|
30
|
-
super
|
30
|
+
super
|
31
31
|
end
|
32
32
|
|
33
33
|
def from_native(value, ctx)
|
34
34
|
return nil if value.null?
|
35
35
|
|
36
|
-
native = super
|
36
|
+
native = super
|
37
37
|
@wrapper_class.new(native)
|
38
38
|
end
|
39
39
|
end
|
@@ -100,12 +100,14 @@ module FFI
|
|
100
100
|
|
101
101
|
# Get attribute
|
102
102
|
def [](member_or_attr)
|
103
|
-
|
103
|
+
_attr, member = ffi_attr_reader_member(member_or_attr, member_or_attr)
|
104
|
+
@native[member]
|
104
105
|
end
|
105
106
|
|
106
107
|
# Set attribute
|
107
108
|
def []=(member_or_attr, val)
|
108
|
-
|
109
|
+
_attr, member = ffi_attr_writer_member(member_or_attr, member_or_attr)
|
110
|
+
@native[member] = val
|
109
111
|
end
|
110
112
|
|
111
113
|
# Pass unimplemented methods on to {#native} underlying struct
|
data/sample/hello_fs.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ffi-libfuse
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Grant Gardner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-10-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ffi
|
@@ -161,11 +161,13 @@ files:
|
|
161
161
|
- lib/ffi/libfuse/filesystem/virtual_dir.rb
|
162
162
|
- lib/ffi/libfuse/filesystem/virtual_file.rb
|
163
163
|
- lib/ffi/libfuse/filesystem/virtual_fs.rb
|
164
|
+
- lib/ffi/libfuse/filesystem/virtual_link.rb
|
164
165
|
- lib/ffi/libfuse/filesystem/virtual_node.rb
|
165
166
|
- lib/ffi/libfuse/fuse2.rb
|
166
167
|
- lib/ffi/libfuse/fuse3.rb
|
167
168
|
- lib/ffi/libfuse/fuse_args.rb
|
168
|
-
- lib/ffi/libfuse/
|
169
|
+
- lib/ffi/libfuse/fuse_buf.rb
|
170
|
+
- lib/ffi/libfuse/fuse_buf_vec.rb
|
169
171
|
- lib/ffi/libfuse/fuse_callbacks.rb
|
170
172
|
- lib/ffi/libfuse/fuse_cmdline_opts.rb
|
171
173
|
- lib/ffi/libfuse/fuse_common.rb
|
@@ -180,6 +182,7 @@ files:
|
|
180
182
|
- lib/ffi/libfuse/fuse_version.rb
|
181
183
|
- lib/ffi/libfuse/gem_helper.rb
|
182
184
|
- lib/ffi/libfuse/gem_version.rb
|
185
|
+
- lib/ffi/libfuse/io.rb
|
183
186
|
- lib/ffi/libfuse/job_pool.rb
|
184
187
|
- lib/ffi/libfuse/main.rb
|
185
188
|
- lib/ffi/libfuse/test_helper.rb
|
@@ -1,257 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'fuse_version'
|
4
|
-
|
5
|
-
module FFI
|
6
|
-
# Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
|
7
|
-
module Libfuse
|
8
|
-
bitmask :fuse_buf_flags, [:is_fd, 1, :fd_seek, :fd_retry]
|
9
|
-
bitmask :fuse_buf_copy_flags, [:no_splice, 1, :force_splice, 2, :splice_move, 4, :splice_nonblock, 8]
|
10
|
-
|
11
|
-
#
|
12
|
-
# Single data buffer
|
13
|
-
#
|
14
|
-
# Generic data buffer for I/O, extended attributes, etc...Data may be supplied as a memory pointer or as a file
|
15
|
-
# descriptor
|
16
|
-
#
|
17
|
-
class FuseBuf < FFI::Struct
|
18
|
-
layout(
|
19
|
-
size: :size_t, # Size of data in bytes
|
20
|
-
flags: :fuse_buf_flags, # Buffer flags
|
21
|
-
mem: :pointer, # Memory pointer - used if :is_fd flag is not set
|
22
|
-
fd: :int, # File descriptor - used if :is_fd is set
|
23
|
-
pos: :off_t # File position - used if :fd_seek flag is set.
|
24
|
-
)
|
25
|
-
|
26
|
-
# rubocop:disable Naming/MethodParameterName
|
27
|
-
|
28
|
-
# @!attribute [r] mem
|
29
|
-
# @return [FFI::Pointer] the memory in the buffer
|
30
|
-
def mem
|
31
|
-
self[:mem]
|
32
|
-
end
|
33
|
-
alias memory mem
|
34
|
-
|
35
|
-
# @!attribute [r] fd
|
36
|
-
# @return [Integer] the file descriptor number
|
37
|
-
def fd
|
38
|
-
self[:fd]
|
39
|
-
end
|
40
|
-
alias file_descriptor fd
|
41
|
-
|
42
|
-
# @return [Boolean] true if this a memory buffer
|
43
|
-
def mem?
|
44
|
-
!fd?
|
45
|
-
end
|
46
|
-
|
47
|
-
# @return [Boolean] true if this is a file descriptor buffer
|
48
|
-
def fd?
|
49
|
-
self[:flags].include?(:is_fd)
|
50
|
-
end
|
51
|
-
alias file_descriptor? fd?
|
52
|
-
|
53
|
-
# Resize mem to smaller than initially allocated
|
54
|
-
# @param [Integer] new_size
|
55
|
-
# @return [void]
|
56
|
-
def resize(new_size)
|
57
|
-
self[:size] = new_size
|
58
|
-
end
|
59
|
-
|
60
|
-
# @overload fill(size:, mem:, auto_release)
|
61
|
-
# Fill as a Memory buffer
|
62
|
-
# @param [Integer] size Size of data in bytes
|
63
|
-
# @param [FFI::Pointer] mem Memory pointer allocated to size if required
|
64
|
-
# @param [Boolean] autorelease
|
65
|
-
#
|
66
|
-
# @overload fill(fd:, fd_retry:, pos:)
|
67
|
-
# Fill as a FileDescriptor buffer
|
68
|
-
# @param [Integer] fd File descriptor
|
69
|
-
# @param [Boolean] fd_retry
|
70
|
-
# Retry operations on file descriptor
|
71
|
-
#
|
72
|
-
# If this flag is set then retry operation on file descriptor until .size bytes have been copied or an error
|
73
|
-
# or EOF is detected.
|
74
|
-
#
|
75
|
-
# @param [Integer] pos
|
76
|
-
# If > 0 then used to seek to the given offset before performing operation on file descriptor.
|
77
|
-
# @return [self]
|
78
|
-
def fill(mem: FFI::Pointer::NULL, size: mem.null? ? 0 : mem.size, fd: -1, fd_retry: false, pos: 0)
|
79
|
-
mem = FFI::MemoryPointer.new(:char, size, true) if fd == -1 && mem.null? && size.positive?
|
80
|
-
mem.autorelease = to_ptr.autorelease? unless mem.null?
|
81
|
-
|
82
|
-
self[:size] = size
|
83
|
-
self[:mem] = mem
|
84
|
-
self[:fd] = fd
|
85
|
-
flags = []
|
86
|
-
flags << :is_fd if fd != -1
|
87
|
-
flags << :fd_seek if pos.positive?
|
88
|
-
flags << :fd_retry if fd_retry
|
89
|
-
self[:flags] = flags
|
90
|
-
self[:pos] = pos
|
91
|
-
self
|
92
|
-
end
|
93
|
-
end
|
94
|
-
# rubocop:enable Naming/MethodParameterName
|
95
|
-
|
96
|
-
#
|
97
|
-
# Data buffer vector
|
98
|
-
#
|
99
|
-
# An array of data buffers, each containing a memory pointer or a file descriptor.
|
100
|
-
#
|
101
|
-
# Allocate dynamically to add more than one buffer.
|
102
|
-
#
|
103
|
-
class FuseBufVec < FFI::Struct
|
104
|
-
layout(
|
105
|
-
count: :size_t,
|
106
|
-
idx: :size_t,
|
107
|
-
off: :size_t,
|
108
|
-
# but is treated as a variable length array of FuseBuf at size +1 following the struct.
|
109
|
-
buf: [FuseBuf, 1] # struct fuse_buf[1]
|
110
|
-
)
|
111
|
-
|
112
|
-
# @!attribute [r] count
|
113
|
-
# @return [Integer] the number of buffers in the array
|
114
|
-
def count
|
115
|
-
self[:count]
|
116
|
-
end
|
117
|
-
|
118
|
-
# @!attribute [r] index
|
119
|
-
# @return [Integer] index of current buffer within the array
|
120
|
-
def index
|
121
|
-
self[:idx]
|
122
|
-
end
|
123
|
-
alias idx index
|
124
|
-
|
125
|
-
# @!attribute [r] offset
|
126
|
-
# @return [Integer] current offset within the current buffer
|
127
|
-
def offset
|
128
|
-
self[:off]
|
129
|
-
end
|
130
|
-
alias off offset
|
131
|
-
|
132
|
-
# @!attribute [r] buffers
|
133
|
-
# @return [Array<FuseBuf>] array of buffers
|
134
|
-
def buffers
|
135
|
-
@buffers ||= Array.new(count) do |i|
|
136
|
-
next self[:buf].first if i.zero?
|
137
|
-
|
138
|
-
FuseBuf.new(self[:buf].to_ptr + (i * FuseBuf.size))
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
# @!attribute [r] current
|
143
|
-
# @return [FuseBuf] the current buffer
|
144
|
-
def current
|
145
|
-
return self[:buf].first if index.zero?
|
146
|
-
|
147
|
-
FuseBuf.new(self[:buf].to_ptr + (index * FuseBuf.size))
|
148
|
-
end
|
149
|
-
|
150
|
-
# Create and initialise a new FuseBufVec
|
151
|
-
# @param [Boolean] autorelease should the struct be freed on GC (default NO!!!)
|
152
|
-
# @param [Hash] buf_options options for configuring the initial buffer. See {#init}
|
153
|
-
# @yield(buf,index)
|
154
|
-
# @yieldparam [FuseBuf] buf
|
155
|
-
# @yieldparam [Integer] index
|
156
|
-
# @yieldreturn [void]
|
157
|
-
# @return [FuseBufVec]
|
158
|
-
def self.init(autorelease: true, count: 1, **buf_options)
|
159
|
-
bufvec_ptr = FFI::MemoryPointer.new(:uchar, FuseBufVec.size + (FuseBuf.size * (count - 1)), true)
|
160
|
-
bufvec_ptr.autorelease = autorelease
|
161
|
-
bufvec = new(bufvec_ptr)
|
162
|
-
bufvec[:count] = count
|
163
|
-
bufvec.init(**buf_options)
|
164
|
-
|
165
|
-
buffers.each_with_index { |b, i| yield i, b } if block_given?
|
166
|
-
bufvec
|
167
|
-
end
|
168
|
-
|
169
|
-
# Set and initialise a specific buffer
|
170
|
-
#
|
171
|
-
# See fuse_common.h FUSE_BUFVEC_INIT macro
|
172
|
-
# @param [Integer] index the index of the buffer
|
173
|
-
# @param [Hash<Symbol,Object>] buf_options see {FuseBuf#fill}
|
174
|
-
# @return [FuseBuf] the initial buffer
|
175
|
-
def init(index: 0, **buf_options)
|
176
|
-
self[:idx] = index
|
177
|
-
self[:off] = 0
|
178
|
-
current.fill(**buf_options) unless buf_options.empty?
|
179
|
-
self
|
180
|
-
end
|
181
|
-
|
182
|
-
# Would pref this to be called #size but clashes with FFI::Struct, might need StructWrapper
|
183
|
-
# @return [Integer] total size of data in a fuse buffer vector
|
184
|
-
def buf_size
|
185
|
-
Libfuse.fuse_buf_size(self)
|
186
|
-
end
|
187
|
-
|
188
|
-
# Copy data from one buffer vector to another
|
189
|
-
# @param [FuseBufVec] dst Destination buffer vector
|
190
|
-
# @param [Array<Symbol>] flags Buffer copy flags
|
191
|
-
# - :no_splice
|
192
|
-
# Don't use splice(2)
|
193
|
-
#
|
194
|
-
# Always fall back to using read and write instead of splice(2) to copy data from one file descriptor to
|
195
|
-
# another.
|
196
|
-
#
|
197
|
-
# If this flag is not set, then only fall back if splice is unavailable.
|
198
|
-
#
|
199
|
-
# - :force_splice
|
200
|
-
#
|
201
|
-
# Always use splice(2) to copy data from one file descriptor to another. If splice is not available, return
|
202
|
-
# -EINVAL.
|
203
|
-
#
|
204
|
-
# - :splice_move
|
205
|
-
#
|
206
|
-
# Try to move data with splice.
|
207
|
-
#
|
208
|
-
# If splice is used, try to move pages from the source to the destination instead of copying. See
|
209
|
-
# documentation of SPLICE_F_MOVE in splice(2) man page.
|
210
|
-
#
|
211
|
-
# - :splice_nonblock
|
212
|
-
#
|
213
|
-
# Don't block on the pipe when copying data with splice
|
214
|
-
#
|
215
|
-
# Makes the operations on the pipe non-blocking (if the pipe is full or empty). See SPLICE_F_NONBLOCK in
|
216
|
-
# the splice(2) man page.
|
217
|
-
#
|
218
|
-
# @return [Integer] actual number of bytes copied or -errno on error
|
219
|
-
#
|
220
|
-
def copy_to(dst, *flags)
|
221
|
-
Libfuse.fuse_buf_copy(dst, self, flags)
|
222
|
-
end
|
223
|
-
|
224
|
-
# Copy our data direct to file descriptor
|
225
|
-
# @param [Integer] fileno a file descriptor
|
226
|
-
# @param [Integer] offset
|
227
|
-
# @param [Array<Symbol>] flags - see {copy_to}
|
228
|
-
# @return [Integer] number of bytes copied
|
229
|
-
def copy_to_fd(fileno, offset = 0, *flags)
|
230
|
-
dst = self.class.init(size: buf_size, fd: fileno, pos: offset)
|
231
|
-
copy_to(dst, *flags)
|
232
|
-
end
|
233
|
-
|
234
|
-
# Copy to string via a temporary buffer
|
235
|
-
# @param [Array<Symbol>] flags - see {copy_to}
|
236
|
-
# @return [String] the extracted data
|
237
|
-
def copy_to_str(*flags)
|
238
|
-
dst = FuseBufVec.init(size: buf_size)
|
239
|
-
copied = copy_to(dst, *flags)
|
240
|
-
dst.current.memory.read_string(copied)
|
241
|
-
end
|
242
|
-
|
243
|
-
def copy_from(src, *flags)
|
244
|
-
Libfuse.fuse_buf_copy(self, src, flags)
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
attach_function :fuse_buf_size, [FuseBufVec.by_ref], :size_t
|
249
|
-
attach_function :fuse_buf_copy, [FuseBufVec.by_ref, FuseBufVec.by_ref, :fuse_buf_copy_flags], :ssize_t
|
250
|
-
|
251
|
-
class << self
|
252
|
-
# @!visibility private
|
253
|
-
# @!method fuse_buf_size
|
254
|
-
# @!method fuse_buf_copy
|
255
|
-
end
|
256
|
-
end
|
257
|
-
end
|