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