ffi-libfuse 0.4.0 → 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 +7 -0
- data/lib/ffi/accessors.rb +418 -106
- data/lib/ffi/devt.rb +33 -7
- data/lib/ffi/flock.rb +31 -27
- data/lib/ffi/libfuse/adapter/context.rb +1 -1
- data/lib/ffi/libfuse/adapter/debug.rb +1 -1
- data/lib/ffi/libfuse/adapter/fuse2_compat.rb +7 -7
- data/lib/ffi/libfuse/adapter/fuse3_support.rb +7 -7
- 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 +1 -1
- data/lib/ffi/libfuse/adapter/safe.rb +1 -1
- data/lib/ffi/libfuse/filesystem/virtual_dir.rb +2 -2
- data/lib/ffi/libfuse/filesystem/virtual_file.rb +1 -1
- data/lib/ffi/libfuse/filesystem/virtual_link.rb +1 -1
- data/lib/ffi/libfuse/fuse3.rb +5 -5
- data/lib/ffi/libfuse/fuse_args.rb +9 -19
- data/lib/ffi/libfuse/fuse_cmdline_opts.rb +19 -16
- data/lib/ffi/libfuse/fuse_config.rb +25 -22
- 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/main.rb +7 -3
- data/lib/ffi/libfuse/test_helper.rb +4 -6
- data/lib/ffi/libfuse/version.rb +1 -1
- data/lib/ffi/stat.rb +16 -9
- data/lib/ffi/stat_vfs.rb +1 -2
- data/lib/ffi/struct_wrapper.rb +6 -4
- metadata +2 -2
data/lib/ffi/flock.rb
CHANGED
@@ -18,32 +18,36 @@ module FFI
|
|
18
18
|
|
19
19
|
include(Accessors)
|
20
20
|
|
21
|
-
layout(
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
21
|
+
layout(
|
22
|
+
# @!attribute [r] type
|
23
|
+
# @return [Symbol] lock type, :rdlck, :wrlck, :unlck
|
24
|
+
l_type: Enums::LockType,
|
25
|
+
|
26
|
+
# @!attribute [r] whence
|
27
|
+
# @return [Symbol] specifies what the offset is relative to, one of :seek_set, :seek_cur or :seek_end
|
28
|
+
# corresponding to the whence argument to fseek(2) or lseek(2),
|
29
|
+
l_whence: Enums::SeekWhenceShort,
|
30
|
+
|
31
|
+
# @!attribute [r] start
|
32
|
+
# @return [Integer] the offset of the start of the region to which the lock applies, and is given in bytes
|
33
|
+
# relative to the point specified by #{whence} member.
|
34
|
+
l_start: :off_t,
|
35
|
+
|
36
|
+
# @!attribute [r] len
|
37
|
+
# @return [Integer] the length of the region to be locked.
|
38
|
+
#
|
39
|
+
# A value of 0 means the region extends to the end of the file.
|
40
|
+
l_len: :off_t,
|
41
|
+
|
42
|
+
# @!attribute [r] pid
|
43
|
+
# @return [Integer] the process ID (see Process Creation Concepts) of the process holding the lock.
|
44
|
+
# It is filled in by calling fcntl with the F_GETLK command, but is ignored when making a lock. If the
|
45
|
+
# conflicting lock is an open file description lock (see Open File Description Locks), then this field will be
|
46
|
+
# set to -1.
|
47
|
+
l_pid: :pid_t
|
48
|
+
)
|
49
|
+
|
50
|
+
# Strip leading 'l_' to make attribute names
|
51
|
+
ffi_attr_reader(**members.to_h { |m| [m[2..].to_sym, m] })
|
48
52
|
end
|
49
53
|
end
|
@@ -12,27 +12,27 @@ module FFI
|
|
12
12
|
if FUSE_MAJOR_VERSION == 2
|
13
13
|
# @!visibility private
|
14
14
|
def getattr(path, stat, fuse_file_info = nil)
|
15
|
-
super
|
15
|
+
super
|
16
16
|
end
|
17
17
|
|
18
18
|
def truncate(path, size, fuse_file_info = nil)
|
19
|
-
super
|
19
|
+
super
|
20
20
|
end
|
21
21
|
|
22
22
|
def init(fuse_conn_info, fuse_config = nil)
|
23
|
-
super
|
23
|
+
super
|
24
24
|
end
|
25
25
|
|
26
26
|
def chown(path, uid, gid, fuse_file_info = nil)
|
27
|
-
super
|
27
|
+
super
|
28
28
|
end
|
29
29
|
|
30
30
|
def chmod(path, mode, fuse_file_info = nil)
|
31
|
-
super
|
31
|
+
super
|
32
32
|
end
|
33
33
|
|
34
34
|
def utimens(path, times, fuse_file_info = nil)
|
35
|
-
super
|
35
|
+
super
|
36
36
|
end
|
37
37
|
|
38
38
|
def readdir(path, buffer, filler, offset, fuse_file_info, fuse_readdir_flag = 0)
|
@@ -45,7 +45,7 @@ module FFI
|
|
45
45
|
# fgetattr and ftruncate already fallback to the respective basic method
|
46
46
|
return false if %i[getdir fgetattr ftruncate].include?(fuse_method)
|
47
47
|
|
48
|
-
super
|
48
|
+
super
|
49
49
|
end
|
50
50
|
|
51
51
|
def fuse_options(args)
|
@@ -24,24 +24,24 @@ module FFI
|
|
24
24
|
fi = args.pop
|
25
25
|
return fgetattr(*args, fi) if fi && fuse_super_respond_to?(:fgetattr)
|
26
26
|
|
27
|
-
super
|
27
|
+
super
|
28
28
|
end
|
29
29
|
|
30
30
|
def truncate(*args)
|
31
31
|
fi = args.pop
|
32
32
|
return ftruncate(*args, fi) if fi && fuse_super_respond_to?(:ftruncate)
|
33
33
|
|
34
|
-
super
|
34
|
+
super
|
35
35
|
end
|
36
36
|
|
37
37
|
def chown(*args)
|
38
38
|
args.pop
|
39
|
-
super
|
39
|
+
super
|
40
40
|
end
|
41
41
|
|
42
42
|
def chmod(*args)
|
43
43
|
args.pop
|
44
|
-
super
|
44
|
+
super
|
45
45
|
end
|
46
46
|
|
47
47
|
# TODO: Fuse3 deprecated flag utime_omit_ok - which meant that UTIME_OMIT and UTIME_NOW are passed through
|
@@ -50,14 +50,14 @@ module FFI
|
|
50
50
|
# but there is no way to handle OMIT
|
51
51
|
def utimens(*args)
|
52
52
|
args.pop
|
53
|
-
super
|
53
|
+
super if defined?(super)
|
54
54
|
end
|
55
55
|
|
56
56
|
def init(*args)
|
57
57
|
args.pop
|
58
58
|
|
59
59
|
# TODO: populate FuseConfig with output from fuse_flags/FuseConnInfo where appropriate
|
60
|
-
super
|
60
|
+
super
|
61
61
|
end
|
62
62
|
|
63
63
|
def readdir(*args, &block)
|
@@ -71,7 +71,7 @@ module FFI
|
|
71
71
|
proc { |buf, name, stat, off| a.call(buf, name, stat, off, 0) }
|
72
72
|
end
|
73
73
|
|
74
|
-
super
|
74
|
+
super
|
75
75
|
end
|
76
76
|
|
77
77
|
def fuse_respond_to(fuse_callback)
|
@@ -338,7 +338,7 @@ module FFI
|
|
338
338
|
|
339
339
|
# Calls super if defined and storing result to protect from GC until {#destroy}
|
340
340
|
def init(*args)
|
341
|
-
o = super
|
341
|
+
o = super if fuse_super_respond_to?(:init)
|
342
342
|
handles << o if o
|
343
343
|
end
|
344
344
|
|
@@ -41,7 +41,7 @@ module FFI
|
|
41
41
|
|
42
42
|
def initialize(accounting: Accounting.new)
|
43
43
|
@entries = {}
|
44
|
-
super
|
44
|
+
super
|
45
45
|
end
|
46
46
|
|
47
47
|
# @!endgroup
|
@@ -157,7 +157,7 @@ module FFI
|
|
157
157
|
# TODO: Strictly should understand setgid and sticky bits of this dir's mode when creating new files
|
158
158
|
new_file = file ? file.call(name) : new_file(name)
|
159
159
|
if entry_fuse_respond_to?(new_file, :create)
|
160
|
-
new_file.
|
160
|
+
new_file.create('/', mode, ffi)
|
161
161
|
else
|
162
162
|
# TODO: generate a sensible device number
|
163
163
|
entry_send(new_file, :mknod, '/', mode, 0)
|
data/lib/ffi/libfuse/fuse3.rb
CHANGED
@@ -58,12 +58,12 @@ module FFI
|
|
58
58
|
cmdline_opts = FuseCmdlineOpts.new
|
59
59
|
raise Error unless Libfuse.fuse_parse_cmdline3(args, cmdline_opts).zero?
|
60
60
|
|
61
|
-
handler&.fuse_debug(cmdline_opts.debug) if handler.respond_to?(:fuse_debug)
|
61
|
+
handler&.fuse_debug(cmdline_opts.debug?) if handler.respond_to?(:fuse_debug)
|
62
62
|
|
63
63
|
# mimics fuse_main which exits after printing version info, even if -h
|
64
|
-
if cmdline_opts.show_version
|
64
|
+
if cmdline_opts.show_version?
|
65
65
|
show_version(handler)
|
66
|
-
elsif cmdline_opts.show_help
|
66
|
+
elsif cmdline_opts.show_help?
|
67
67
|
show_help(args, handler)
|
68
68
|
end
|
69
69
|
|
@@ -145,8 +145,8 @@ module FFI
|
|
145
145
|
|
146
146
|
private
|
147
147
|
|
148
|
-
def native_fuse_loop_mt(
|
149
|
-
Libfuse.fuse_loop_mt3(@fuse, FuseLoopConfig.
|
148
|
+
def native_fuse_loop_mt(**options)
|
149
|
+
Libfuse.fuse_loop_mt3(@fuse, FuseLoopConfig.create(**options))
|
150
150
|
end
|
151
151
|
|
152
152
|
def unmount
|
@@ -3,13 +3,17 @@
|
|
3
3
|
require_relative 'fuse_version'
|
4
4
|
require_relative 'fuse_opt'
|
5
5
|
require_relative '../ruby_object'
|
6
|
+
require_relative '../boolean_int'
|
7
|
+
require_relative '../accessors'
|
6
8
|
|
7
9
|
module FFI
|
8
10
|
# Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
|
9
11
|
module Libfuse
|
10
12
|
# struct fuse_args
|
11
13
|
class FuseArgs < FFI::Struct
|
12
|
-
|
14
|
+
include FFI::Accessors
|
15
|
+
|
16
|
+
layout :argc, :int, :argv, :pointer, :allocated, :bool_int
|
13
17
|
|
14
18
|
# Create a fuse_args struct from command line options
|
15
19
|
# @param [Array<String>] argv command line args
|
@@ -34,33 +38,17 @@ module FFI
|
|
34
38
|
@arg_vector[argv.size].put_pointer(0, FFI::Pointer::NULL)
|
35
39
|
self[:argv] = @arg_vector
|
36
40
|
self[:argc] = argv.size
|
37
|
-
self[:allocated] =
|
41
|
+
self[:allocated] = false # ie libfuse did not allocate
|
38
42
|
self
|
39
43
|
end
|
40
44
|
|
41
|
-
# @!attribute [r] argc
|
42
|
-
# @return [Integer] count of args
|
43
|
-
def argc
|
44
|
-
self[:argc]
|
45
|
-
end
|
46
|
-
|
47
45
|
# @!attribute [r] argv
|
48
46
|
# @return [Array<String>] list of args
|
49
|
-
|
47
|
+
ffi_attr_reader(:argv) do
|
50
48
|
# noinspection RubyResolve
|
51
49
|
self[:argv].get_array_of_pointer(0, argc).map(&:read_string)
|
52
50
|
end
|
53
51
|
|
54
|
-
# @!visibility private
|
55
|
-
def allocated
|
56
|
-
self[:allocated]
|
57
|
-
end
|
58
|
-
|
59
|
-
# @!visibility private
|
60
|
-
def inspect
|
61
|
-
"#{self.class.name} - #{%i[argc argv allocated].to_h { |m| [m, send(m)] }}"
|
62
|
-
end
|
63
|
-
|
64
52
|
# Add an arg to this arg list
|
65
53
|
# @param [String] arg
|
66
54
|
def add(arg)
|
@@ -151,6 +139,8 @@ module FFI
|
|
151
139
|
# Valid return values from parse! block
|
152
140
|
FUSE_OPT_PROC_RETURN = { error: -1, keep: 1, handled: 0, discard: 0 }.freeze
|
153
141
|
|
142
|
+
ffi_attr_reader(:argc, :allocated?)
|
143
|
+
|
154
144
|
def fuse_opt_proc(symbols, bool_opts, param_opts, ignore, &block)
|
155
145
|
proc do |data, arg, key, out|
|
156
146
|
key = symbols[key]
|
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../accessors'
|
4
|
+
require_relative '../boolean_int'
|
4
5
|
require_relative 'fuse_loop_config'
|
5
6
|
module FFI
|
6
7
|
module Libfuse
|
7
|
-
#
|
8
8
|
# struct fuse_cmdline_opts {
|
9
9
|
# int singlethread;
|
10
10
|
# int foreground;
|
@@ -16,29 +16,32 @@ module FFI
|
|
16
16
|
# int clone_fd;
|
17
17
|
# unsigned int max_idle_threads;
|
18
18
|
# };
|
19
|
+
|
20
|
+
# Command line options
|
19
21
|
# @!visibility private
|
20
22
|
class FuseCmdlineOpts < FFI::Struct
|
21
23
|
include(FFI::Accessors)
|
22
24
|
|
23
|
-
|
24
|
-
single_thread: :
|
25
|
-
foreground: :
|
26
|
-
debug: :
|
27
|
-
nodefault_subtype: :
|
25
|
+
spec = {
|
26
|
+
single_thread: :bool_int,
|
27
|
+
foreground: :bool_int,
|
28
|
+
debug: :bool_int,
|
29
|
+
nodefault_subtype: :bool_int,
|
28
30
|
mountpoint: :string,
|
29
|
-
show_version: :
|
30
|
-
show_help: :
|
31
|
-
clone_fd: :
|
32
|
-
max_idle_threads: :
|
33
|
-
|
31
|
+
show_version: :bool_int,
|
32
|
+
show_help: :bool_int,
|
33
|
+
clone_fd: :bool_int,
|
34
|
+
max_idle_threads: :uint
|
35
|
+
}
|
36
|
+
spec[:max_threads] = :uint if FUSE_MINOR_VERSION >= 12
|
37
|
+
|
38
|
+
layout(spec)
|
34
39
|
|
35
|
-
|
36
|
-
ffi_attr_reader(
|
37
|
-
:clone_fd) do |v|
|
38
|
-
v != 0
|
39
|
-
end
|
40
|
+
bool, = spec.partition { |_, v| v == :bool_int }
|
41
|
+
ffi_attr_reader(*bool.map { |k, _| "#{k}?" })
|
40
42
|
|
41
43
|
ffi_attr_reader(:max_idle_threads, :mountpoint)
|
44
|
+
ffi_attr_reader(:max_threads) if FUSE_MINOR_VERSION >= 12
|
42
45
|
end
|
43
46
|
end
|
44
47
|
end
|
@@ -10,7 +10,7 @@ module FFI
|
|
10
10
|
# This structure is initialized from the arguments passed to fuse_new(), and then passed to the file system's init()
|
11
11
|
# handler which should ensure that the configuration is compatible with the file system implementation.
|
12
12
|
#
|
13
|
-
# Some options can only be set via the filesystem init method (use_ino etc..) because the filesystem either
|
13
|
+
# Some options can only be set via the filesystem init method (:use_ino etc..) because the filesystem either
|
14
14
|
# supports them or it doesn't.
|
15
15
|
class FuseConfig < FFI::Struct
|
16
16
|
include FFI::Accessors
|
@@ -40,7 +40,7 @@ module FFI
|
|
40
40
|
# @!attribute [rw] negative_timeout
|
41
41
|
# The timeout in seconds for which a negative lookup will be cached.
|
42
42
|
#
|
43
|
-
# This means, that if file did not exist (lookup
|
43
|
+
# This means, that if file did not exist (lookup returned ENOENT), the lookup will only be redone after the
|
44
44
|
# timeout, and the file/directory will be assumed to not exist until then. A value of zero means that
|
45
45
|
# negative lookups are not cached.
|
46
46
|
#
|
@@ -55,7 +55,7 @@ module FFI
|
|
55
55
|
# @return [Float]
|
56
56
|
attr_timeout: :double,
|
57
57
|
|
58
|
-
# @!attribute [rw] intr
|
58
|
+
# @!attribute [rw] intr?
|
59
59
|
# Allow requests to be interrupted
|
60
60
|
# @return [Boolean]
|
61
61
|
intr: :bool_int,
|
@@ -80,7 +80,7 @@ module FFI
|
|
80
80
|
# @return [Integer]
|
81
81
|
remember: :int,
|
82
82
|
|
83
|
-
# @!attribute [rw] hard_remove
|
83
|
+
# @!attribute [rw] hard_remove?
|
84
84
|
# should open files be removed immediately
|
85
85
|
#
|
86
86
|
# The default behavior is that if an open file is deleted, the file is renamed to a hidden file
|
@@ -95,7 +95,7 @@ module FFI
|
|
95
95
|
# @return [Boolean]
|
96
96
|
hard_remove: :bool_int,
|
97
97
|
|
98
|
-
# @!attribute [rw] use_ino
|
98
|
+
# @!attribute [rw] use_ino?
|
99
99
|
# use filesystem provided inode values
|
100
100
|
#
|
101
101
|
# Honor the st_ino field in the functions getattr() and fill_dir(). This value is used to fill in the st_ino
|
@@ -109,8 +109,8 @@ module FFI
|
|
109
109
|
# @return [Boolean]
|
110
110
|
use_ino: :bool_int,
|
111
111
|
|
112
|
-
# @!attribute [rw] readdir_ino
|
113
|
-
# generate inodes for readdir even if {#use_ino} is set
|
112
|
+
# @!attribute [rw] readdir_ino?
|
113
|
+
# generate inodes for readdir even if {#use_ino?} is set
|
114
114
|
#
|
115
115
|
# If use_ino option is not given, still try to fill in the d_ino field in readdir(2). If the name was
|
116
116
|
# previously looked up, and is still in the cache, the inode number found there will be used. Otherwise it
|
@@ -118,7 +118,7 @@ module FFI
|
|
118
118
|
# @return [Boolean]
|
119
119
|
readdir_ino: :bool_int,
|
120
120
|
|
121
|
-
# @!attribute [rw] direct_io
|
121
|
+
# @!attribute [rw] direct_io?
|
122
122
|
# disables the use of kernel page cache (file content cache) in the kernel for this filesystem.
|
123
123
|
#
|
124
124
|
# This has several affects:
|
@@ -135,13 +135,13 @@ module FFI
|
|
135
135
|
# @return [Boolean]
|
136
136
|
direct_io: :bool_int,
|
137
137
|
|
138
|
-
# @!attribute [rw] kernel_cache
|
138
|
+
# @!attribute [rw] kernel_cache?
|
139
139
|
# disables flushing the cache of the file contents on every open(2).
|
140
140
|
#
|
141
141
|
# This should only be enabled on filesystem where the file data is never changed externally (not through the
|
142
142
|
# mounted FUSE filesystem). Thus it is not suitable for network filesystem and other intermediate filesystem.
|
143
143
|
#
|
144
|
-
# **Note**: if neither this option or {#direct_io} is specified data is still cached after the open(2),
|
144
|
+
# **Note**: if neither this option or {#direct_io?} is specified data is still cached after the open(2),
|
145
145
|
# so a read(2) system call will not always initiate a read operation.
|
146
146
|
#
|
147
147
|
# Internally, enabling this option causes fuse to set {FuseFileInfo#keep_cache} overwriting any value that was
|
@@ -149,7 +149,7 @@ module FFI
|
|
149
149
|
# @return [Boolean]
|
150
150
|
kernel_cache: :bool_int,
|
151
151
|
|
152
|
-
# @!attribute [rw] auto_cache
|
152
|
+
# @!attribute [rw] auto_cache?
|
153
153
|
# invalidate cached data on open based on changes in file attributes
|
154
154
|
#
|
155
155
|
# This option is an alternative to `kernel_cache`. Instead of unconditionally keeping cached data, the cached
|
@@ -165,7 +165,7 @@ module FFI
|
|
165
165
|
ac_attr_timeout_set: :bool_int,
|
166
166
|
ac_attr_timeout: :double,
|
167
167
|
|
168
|
-
# @!attribute [rw] nullpath_ok
|
168
|
+
# @!attribute [rw] nullpath_ok?
|
169
169
|
# operations on open files and directories are ok to receive nil paths
|
170
170
|
#
|
171
171
|
# If this option is given the file-system handlers for the following operations will not receive path
|
@@ -185,21 +185,24 @@ module FFI
|
|
185
185
|
layout(spec)
|
186
186
|
|
187
187
|
# Find the attrs that have a corresponding setter (prefix set_ or suffix _set
|
188
|
+
# map attr => setter
|
188
189
|
setters = spec.keys
|
189
|
-
.map { |k| [k
|
190
|
-
.reject { |(s, a)| s == a }
|
190
|
+
.map { |k| [k.to_s.sub(/^set_/, '').sub(/_set$/, '').to_sym, k] }
|
191
|
+
.reject { |(s, a)| s == a }
|
192
|
+
.to_h
|
191
193
|
|
192
|
-
setters.
|
193
|
-
|
194
|
+
ffi_attr_reader_method(*setters.keys) do
|
195
|
+
self[setters[__method__]] ? self[__method__] : nil
|
196
|
+
end
|
194
197
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
end
|
198
|
+
ffi_attr_writer_method(*setters.keys) do |val|
|
199
|
+
self[setters[__method__]] = !val.nil?
|
200
|
+
self[__method__] = val || 0
|
199
201
|
end
|
200
202
|
|
201
|
-
|
202
|
-
|
203
|
+
ffi_attr_reader(:show_help?, :debug?)
|
204
|
+
remaining = (spec.keys - setters.keys - setters.values - %i[show_help modules debug])
|
205
|
+
ffi_attr_accessor(*remaining.map { |a| spec[a] == :bool_int ? "#{a}?" : a })
|
203
206
|
end
|
204
207
|
end
|
205
208
|
end
|
@@ -326,7 +326,7 @@ module FFI
|
|
326
326
|
# or unwanted
|
327
327
|
# @return [Array<Symbol>]
|
328
328
|
# @see capable
|
329
|
-
|
329
|
+
ffi_attr_reader_method(:want) do |*caps, **h|
|
330
330
|
next self[:want] if caps.empty? && h.empty?
|
331
331
|
|
332
332
|
h.merge!(caps.pop) if caps.last.is_a?(Hash)
|
@@ -15,7 +15,8 @@ module FFI
|
|
15
15
|
base[:umask] = :mode_t if FUSE_VERSION >= 28
|
16
16
|
layout base
|
17
17
|
|
18
|
-
|
18
|
+
# Define readers, safe from null access
|
19
|
+
ffi_attr_reader_method(*members) do
|
19
20
|
m = __method__
|
20
21
|
|
21
22
|
# Use overrides if they are available, or the default context if the underlying memory is invalid
|