ffi-libfuse 0.3.4 → 0.4.0
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 +22 -0
- data/README.md +1 -1
- data/lib/ffi/accessors.rb +21 -7
- data/lib/ffi/boolean_int.rb +1 -1
- data/lib/ffi/devt.rb +3 -3
- data/lib/ffi/libfuse/adapter/debug.rb +53 -15
- data/lib/ffi/libfuse/adapter/fuse2_compat.rb +38 -21
- data/lib/ffi/libfuse/adapter/fuse3_support.rb +0 -1
- data/lib/ffi/libfuse/adapter/ruby.rb +210 -159
- data/lib/ffi/libfuse/adapter/safe.rb +69 -21
- 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 +293 -126
- 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 +1 -1
- data/lib/ffi/libfuse/fuse_args.rb +5 -2
- data/lib/ffi/libfuse/fuse_buf.rb +112 -0
- data/lib/ffi/libfuse/fuse_buf_vec.rb +228 -0
- data/lib/ffi/libfuse/fuse_common.rb +10 -4
- data/lib/ffi/libfuse/fuse_config.rb +16 -7
- 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 +27 -24
- data/lib/ffi/libfuse/test_helper.rb +68 -60
- 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 +19 -3
- data/lib/ffi/struct_array.rb +2 -1
- data/sample/hello_fs.rb +1 -1
- metadata +6 -3
- data/lib/ffi/libfuse/fuse_buffer.rb +0 -257
@@ -14,6 +14,11 @@ module FFI
|
|
14
14
|
#
|
15
15
|
# Implements callbacks satisfying {Adapter::Ruby} which is automatically included.
|
16
16
|
module MappedFiles
|
17
|
+
# @!visibility private
|
18
|
+
def self.included(mod)
|
19
|
+
mod.prepend(Adapter::Ruby::Prepend)
|
20
|
+
end
|
21
|
+
|
17
22
|
# Do we have ffi-xattr to handle extended attributes in real files
|
18
23
|
HAS_XATTR =
|
19
24
|
begin
|
@@ -34,7 +39,7 @@ module FFI
|
|
34
39
|
# @return [String] mapped_path in an underlying filesystem
|
35
40
|
#
|
36
41
|
# Fuse callbacks are fulfilled using Ruby's native File methods called on this path
|
37
|
-
# @return [String, Adapter::Ruby
|
42
|
+
# @return [String, Adapter::Ruby] mapped_path, filesystem
|
38
43
|
#
|
39
44
|
# If an optional filesystem value is returned fuse callbacks will be passed on to this filesystem with the
|
40
45
|
# mapped_path and other callback args unchanged
|
@@ -78,6 +83,26 @@ module FFI
|
|
78
83
|
path_method(__method__, path, ffi) { |rp| File.open(rp, ffi.flags) }
|
79
84
|
end
|
80
85
|
|
86
|
+
# implemented to allow for virtual files within the mapped fs, but we just rely om the result of open
|
87
|
+
def read(path, size, offset, ffi)
|
88
|
+
path_method(__method__, path, size, offset, ffi) { |_rp| nil }
|
89
|
+
end
|
90
|
+
|
91
|
+
# implemented to allow for virtual files within the mapped fs, but we just rely om the result of open
|
92
|
+
def read_buf(path, size, offset, ffi)
|
93
|
+
path_method(__method__, path, size, offset, ffi, error: nil) { |_rp| nil }
|
94
|
+
end
|
95
|
+
|
96
|
+
# implemented to allow for virtual files within the mapped fs, but we just rely om the result of open
|
97
|
+
def write(path, size, offset, ffi)
|
98
|
+
path_method(__method__, path, size, offset, ffi) { |_rp| nil }
|
99
|
+
end
|
100
|
+
|
101
|
+
# implemented to allow for virtual files within the mapped fs, but we just rely om the result of open
|
102
|
+
def write_buf(path, offset, ffi, &buffer)
|
103
|
+
path_method(__method__, path, offset, ffi, block: buffer, error: nil) { |_rp| nil }
|
104
|
+
end
|
105
|
+
|
81
106
|
# Truncates the file handle (or the real file)
|
82
107
|
def truncate(path, size, ffi = nil)
|
83
108
|
return ffi.fh.truncate(size) if ffi&.fh
|
@@ -122,18 +147,19 @@ module FFI
|
|
122
147
|
end
|
123
148
|
# @!endgroup
|
124
149
|
|
125
|
-
# @!visibility private
|
126
|
-
def self.included(mod)
|
127
|
-
mod.prepend(Adapter::Ruby::Prepend)
|
128
|
-
end
|
129
|
-
|
130
150
|
private
|
131
151
|
|
132
152
|
def path_method(callback, path, *args, error: Errno::ENOENT, block: nil)
|
133
153
|
rp, fs = map_path(path)
|
154
|
+
|
134
155
|
raise error if error && !rp
|
156
|
+
return nil unless rp
|
157
|
+
return yield(rp) unless fs
|
158
|
+
|
159
|
+
return fs.send(callback, rp, *args, &block) if fs.respond_to?(callback)
|
160
|
+
raise error if error
|
135
161
|
|
136
|
-
|
162
|
+
nil
|
137
163
|
end
|
138
164
|
end
|
139
165
|
end
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require_relative 'accounting'
|
4
4
|
require_relative 'virtual_node'
|
5
5
|
require_relative 'virtual_file'
|
6
|
+
require_relative 'virtual_link'
|
6
7
|
require_relative 'pass_through_file'
|
7
8
|
require_relative 'pass_through_dir'
|
8
9
|
require_relative 'mapped_dir'
|
@@ -12,7 +13,7 @@ module FFI
|
|
12
13
|
module Filesystem
|
13
14
|
# A Filesystem of Filesystems
|
14
15
|
#
|
15
|
-
# Implements a
|
16
|
+
# Implements a simple Hash based directory of sub filesystems.
|
16
17
|
#
|
17
18
|
# FUSE Callbacks
|
18
19
|
# ===
|
@@ -40,24 +41,21 @@ module FFI
|
|
40
41
|
|
41
42
|
def initialize(accounting: Accounting.new)
|
42
43
|
@entries = {}
|
43
|
-
@mounted = false
|
44
44
|
super(accounting: accounting)
|
45
45
|
end
|
46
46
|
|
47
|
-
# @return [Boolean] true if this dir been mounted
|
48
|
-
def mounted?
|
49
|
-
@mounted
|
50
|
-
end
|
51
|
-
|
52
47
|
# @!endgroup
|
53
48
|
|
54
49
|
# @!group FUSE Callbacks
|
55
50
|
|
56
51
|
# For the root path provides this directory's stat information, otherwise passes on to the next filesystem
|
57
|
-
def getattr(path, stat_buf = nil,
|
58
|
-
|
52
|
+
def getattr(path, stat_buf = nil, ffi = nil)
|
53
|
+
if root?(path)
|
54
|
+
stat_buf&.directory(nlink: entries.size + 2, **virtual_stat)
|
55
|
+
return self
|
56
|
+
end
|
59
57
|
|
60
|
-
|
58
|
+
path_method(__method__, path, stat_buf, ffi, notsup: Errno::ENOSYS)
|
61
59
|
end
|
62
60
|
|
63
61
|
# Safely passes on file open to next filesystem
|
@@ -69,8 +67,6 @@ module FFI
|
|
69
67
|
raise Errno::EISDIR if root?(path)
|
70
68
|
|
71
69
|
path_method(__method__, path, *args, notsup: nil)
|
72
|
-
rescue Errno::ENOTSUP, Errno::ENOSYS
|
73
|
-
nil
|
74
70
|
end
|
75
71
|
|
76
72
|
# Safely handle file release
|
@@ -81,8 +77,6 @@ module FFI
|
|
81
77
|
raise Errno::EISDIR if root?(path)
|
82
78
|
|
83
79
|
path_method(__method__, path, *args, notsup: nil)
|
84
|
-
rescue Errno::ENOTSUP, Errno::ENOSYS
|
85
|
-
# do nothing
|
86
80
|
end
|
87
81
|
|
88
82
|
# Safely handles directory open to next filesystem
|
@@ -91,11 +85,9 @@ module FFI
|
|
91
85
|
# @return [Object] the result of {#path_method} for all other paths
|
92
86
|
# @return [nil] for sub-filesystems that do not implement this callback or raise ENOTSUP or ENOSYS
|
93
87
|
def opendir(path, ffi)
|
94
|
-
return
|
88
|
+
return (ffi.fh = self) if root?(path)
|
95
89
|
|
96
|
-
ffi
|
97
|
-
rescue Errno::ENOTSUP, Errno::ENOSYS
|
98
|
-
nil
|
90
|
+
path_method(__method__, path, ffi, notsup: nil)
|
99
91
|
end
|
100
92
|
|
101
93
|
# Safely handles directory release
|
@@ -104,9 +96,9 @@ module FFI
|
|
104
96
|
#
|
105
97
|
# Otherwise safely passes on to next filesystem, rescuing ENOTSUP or ENOSYS
|
106
98
|
def releasedir(path, *args)
|
107
|
-
|
108
|
-
|
109
|
-
|
99
|
+
return if root?(path)
|
100
|
+
|
101
|
+
path_method(__method__, path, *args, notsup: nil)
|
110
102
|
end
|
111
103
|
|
112
104
|
# If path is root fills the directory from the keys in {#entries}
|
@@ -117,14 +109,13 @@ module FFI
|
|
117
109
|
def readdir(path, buf, filler, offset, ffi, *flag)
|
118
110
|
return %w[. ..].concat(entries.keys).each(&Adapter::Ruby::ReaddirFiller.new(buf, filler)) if root?(path)
|
119
111
|
|
120
|
-
return ffi.fh.readdir('/', buf, filler, offset, ffi, *flag) if
|
112
|
+
return ffi.fh.readdir('/', buf, filler, offset, ffi, *flag) if dir_entry?(ffi.fh)
|
121
113
|
|
122
|
-
path_method(:readdir, path, buf, filler, offset, ffi, *flag
|
114
|
+
path_method(:readdir, path, buf, filler, offset, ffi, *flag, notsup: Errno::ENOTDIR)
|
123
115
|
end
|
124
116
|
|
125
117
|
# For root path validates we are empty and removes a node link from {#accounting}
|
126
|
-
# For our entries, passes on the call to the entry (with path='/') and then removes the entry.
|
127
|
-
# :destroy will be called on the deleted entry
|
118
|
+
# For our entries, passes on the call to the entry (with path='/') and then removes the entry.
|
128
119
|
# @raise [Errno::ENOTEMPTY] if path is root and our entries list is not empty
|
129
120
|
# @raise [Errno::ENOENT] if the entry does not exist
|
130
121
|
# @raise [Errno::ENOTDIR] if the entry does not respond to :readdir (ie: is not a directory)
|
@@ -136,144 +127,275 @@ module FFI
|
|
136
127
|
return
|
137
128
|
end
|
138
129
|
|
139
|
-
|
140
|
-
|
130
|
+
path_method(__method__, path) do |entry_key, dir|
|
131
|
+
raise Errno::ENOENT unless dir
|
132
|
+
raise Errno::ENOTDIR unless dir_entry?(dir)
|
141
133
|
|
142
|
-
|
143
|
-
raise Errno::ENOENT unless dir
|
144
|
-
raise Errno::ENOTDIR unless entry_fuse_respond_to?(dir, :readdir)
|
134
|
+
entry_send(dir, :rmdir, '/')
|
145
135
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
entry_send(dir, :destroy, init_results.delete(entry_key)) if dir && mounted?
|
150
|
-
end
|
151
|
-
|
152
|
-
# For our entries, validates the entry exists and is not a directory, then passes on unlink (with path = '/')
|
153
|
-
# and finally deletes.
|
154
|
-
# @raise [Errno:EISDIR] if the request entry responds to :readdir
|
155
|
-
def unlink(path)
|
156
|
-
entry_key = entry_key(path)
|
157
|
-
return path_method(__method__, path) unless entry_key
|
158
|
-
|
159
|
-
entry = entries[entry_key]
|
160
|
-
raise Errno::ENOENT unless entry
|
161
|
-
raise Errno::EISDIR if entry_fuse_respond_to?(entry, :readdir)
|
162
|
-
|
163
|
-
entry_send(entry, :unlink, '/')
|
164
|
-
entries.delete(entry_key) && true
|
136
|
+
entries.delete(entry_key)
|
137
|
+
dir
|
138
|
+
end
|
165
139
|
end
|
166
140
|
|
167
141
|
# For our entries, creates a new file
|
168
142
|
# @raise [Errno::EISDIR] if the entry exists and responds_to?(:readdir)
|
169
143
|
# @raise [Errno::EEXIST] if the entry exists
|
170
|
-
# @yield []
|
171
|
-
# @yieldreturn [
|
144
|
+
# @yield [String] filename the name of the file in this directory
|
145
|
+
# @yieldreturn [:getattr] something that quacks with the FUSE Callbacks of a regular file
|
172
146
|
#
|
173
147
|
# :create or :mknod + :open will be attempted with path = '/' on this file
|
174
|
-
# @return
|
148
|
+
# @return the result of the supplied block, or if not given a new {VirtualFile}
|
175
149
|
def create(path, mode = FuseContext.get.mask(0o644), ffi = nil, &file)
|
176
|
-
|
150
|
+
raise Errno::EISDIR if root?(path)
|
177
151
|
|
178
152
|
# fuselib will fallback to mknod on ENOSYS on a case by case basis
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
153
|
+
path_method(__method__, path, mode, ffi, notsup: Errno::ENOSYS, block: file) do |name, existing|
|
154
|
+
raise Errno::EISDIR if dir_entry?(existing)
|
155
|
+
raise Errno::EEXIST if existing
|
156
|
+
|
157
|
+
# TODO: Strictly should understand setgid and sticky bits of this dir's mode when creating new files
|
158
|
+
new_file = file ? file.call(name) : new_file(name)
|
159
|
+
if entry_fuse_respond_to?(new_file, :create)
|
160
|
+
new_file.public_send(:create, '/', mode, ffi)
|
161
|
+
else
|
162
|
+
# TODO: generate a sensible device number
|
163
|
+
entry_send(new_file, :mknod, '/', mode, 0)
|
164
|
+
entry_send(new_file, :open, '/', ffi)
|
165
|
+
end
|
166
|
+
entries[name] = new_file
|
193
167
|
end
|
194
|
-
|
168
|
+
end
|
169
|
+
|
170
|
+
# Method for creating a new file
|
171
|
+
# @param [String] _name
|
172
|
+
# @return [FuseOperations] something representing a regular file
|
173
|
+
def new_file(_name)
|
174
|
+
VirtualFile.new(accounting: accounting)
|
195
175
|
end
|
196
176
|
|
197
177
|
# Creates a new directory entry in this directory
|
198
178
|
# @param [String] path
|
199
179
|
# @param [Integer] mode
|
200
|
-
# @yield []
|
180
|
+
# @yield [String] name the name of the directory in this filesystem
|
201
181
|
# @yieldreturn [Object] something that quacks with the FUSE Callbacks representing a directory
|
202
|
-
# @return
|
182
|
+
# @return the result of the block if given, otherwise the newly created sub {VirtualDir}
|
203
183
|
# @raise [Errno::EEXIST] if the entry already exists at path
|
204
184
|
def mkdir(path, mode = FuseContext.get.mask(0o777), &dir)
|
205
185
|
return init_node(mode) if root?(path)
|
206
186
|
|
207
|
-
|
208
|
-
|
187
|
+
path_method(__method__, path, mode, block: dir) do |dir_name, existing|
|
188
|
+
raise Errno::EEXIST if existing
|
209
189
|
|
210
|
-
|
211
|
-
|
190
|
+
new_dir = dir ? dir.call(dir_name) : new_dir(dir_name)
|
191
|
+
entry_send(new_dir, :mkdir, '/', mode)
|
192
|
+
entries[dir_name] = new_dir
|
193
|
+
end
|
194
|
+
end
|
212
195
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
196
|
+
# Method for creating a new directory, called from mkdir
|
197
|
+
# @param [String] _name
|
198
|
+
# @return [FuseOperations] something representing a directory
|
199
|
+
def new_dir(_name)
|
200
|
+
VirtualDir.new(accounting: accounting)
|
217
201
|
end
|
218
202
|
|
219
|
-
#
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
203
|
+
# Create a new hard link in this filesystem
|
204
|
+
#
|
205
|
+
# @param [String, nil] from_path
|
206
|
+
# @param [String] to_path
|
207
|
+
# @yield [existing]
|
208
|
+
# Used to retrieve the filesystem object at from_path to be linked at to_path
|
209
|
+
#
|
210
|
+
# If not supplied, a proc wrapping #{new_link} is created and used or passed on to sub-filesystems
|
211
|
+
# @yieldparam [FuseOperations] existing the object currently at to_path
|
212
|
+
# @yieldreturn [FuseOperations] an object representing an inode to be linked at to_path
|
213
|
+
# @raise [Errno::EISDIR] if this object is trying to be added as a link (since you can't hard link directories)
|
214
|
+
# @see new_link
|
215
|
+
def link(from_path, to_path, &linker)
|
216
|
+
# Can't link to a directory
|
217
|
+
raise Errno::EISDIR if root?(to_path)
|
218
|
+
raise Errno::ENOSYS unless from_path || linker
|
219
|
+
|
220
|
+
same_filesystem_method(__method__, from_path, to_path) do
|
221
|
+
linker ||= proc { |replacing| new_link(from_path, replacing) }
|
222
|
+
path_method(__method__, from_path, to_path, block: linker) do |link_name, existing|
|
223
|
+
linked_entry = linker.call(existing)
|
224
|
+
entries[link_name] = linked_entry
|
225
|
+
end
|
226
|
+
end
|
226
227
|
end
|
227
228
|
|
228
|
-
#
|
229
|
-
|
230
|
-
|
231
|
-
|
229
|
+
# Called from within #{link}
|
230
|
+
# Uses #{getattr}(from_path) to find the filesystem object at from_path.
|
231
|
+
# Calls #{link}(nil, '/') on this object to signal that a new link has been created to it.
|
232
|
+
# Filesystem objects that do not support linking should raise `Errno::EPERM` if the object should not be hard
|
233
|
+
# linked (eg directories)
|
234
|
+
# @return [FuseOperations]
|
235
|
+
# @raise Errno::EXIST if there is an existing object to replace
|
236
|
+
# @raise Errno::EPERM if the object at from_path is not a filesystem (does not itself respond to #getattr)
|
237
|
+
def new_link(from_path, replacing)
|
238
|
+
raise Errno::EEXIST if replacing
|
239
|
+
|
240
|
+
linked_entry = getattr(from_path)
|
241
|
+
|
242
|
+
# the linked entry itself must represent a filesystem inode
|
243
|
+
raise Errno::EPERM unless entry_fuse_respond_to?(linked_entry, :getattr)
|
244
|
+
|
245
|
+
entry_send(linked_entry, :link, nil, '/')
|
246
|
+
linked_entry
|
247
|
+
end
|
248
|
+
|
249
|
+
# For our entries validates the entry exists and calls unlink('/') on it to do any cleanup
|
250
|
+
# before removing the entry from our entries list.
|
251
|
+
#
|
252
|
+
# If a block is supplied (eg #{rename}) it will be called before the entry is deleted
|
253
|
+
#
|
254
|
+
# @raise [Errno:EISDIR] if we are unlinking ourself (use rmdir instead)
|
255
|
+
# @raise [Errno::ENOENT] if the entry does not exist at path (and no block is provided)
|
256
|
+
# @return the unlinked filesystem object
|
257
|
+
# @yield(file_name, entry)
|
258
|
+
# @yieldparam [FuseOperations] entry a filesystem like object representing the file being unlinked
|
259
|
+
# @yieldreturn [void]
|
260
|
+
def unlink(path, &rename)
|
261
|
+
raise Errno::EISDIR if root?(path)
|
262
|
+
|
263
|
+
path_method(__method__, path, block: rename) do |entry_key, entry|
|
264
|
+
if rename
|
265
|
+
rename.call(entry)
|
266
|
+
elsif entry
|
267
|
+
entry_send(entry, :unlink, '/')
|
268
|
+
else
|
269
|
+
raise Errno::ENOENT
|
270
|
+
end
|
271
|
+
|
272
|
+
entries.delete(entry_key)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# Rename is handled via #{link} and #{unlink} using their respective block arguments to handle validation
|
277
|
+
# and retrieve the object at from_path. Intermediate directory filesystems are only required to pass on the
|
278
|
+
# block, while the final directory target of from_path and to_path must call these blocks as this class does.
|
279
|
+
#
|
280
|
+
# If to_path is being replaced the existing entry will be signaled via #{unlink}('/'), or #{rmdir}('/')
|
281
|
+
# @raise Errno::EINVAL if trying to rename the root object OR from_path is a directory prefix of to_path
|
282
|
+
# @raise Errno::ENOENT if the filesystem at from_path does not exist
|
283
|
+
# @raise Errno::ENOSYS if the filesystem at from_path or directory of to_path does not support rename
|
284
|
+
# @raise Errno::EEXIST if the filesystem at to_path already exists and is not a symlink
|
285
|
+
# @see POSIX rename(2)
|
286
|
+
# @note As per POSIX raname(2) silently succeeds if from_path and to_path are hard links to the
|
287
|
+
# same filesystem object (ie without unlinking from_path)
|
288
|
+
def rename(from_path, to_path)
|
289
|
+
return if from_path == to_path
|
290
|
+
raise Errno::EINVAL if root?(from_path)
|
291
|
+
|
292
|
+
same_filesystem_method(__method__, from_path, to_path, rescue_notsup: true) do
|
293
|
+
# Can't rename into a subdirectory of itself
|
294
|
+
raise Errno::EINVAL if to_path.start_with?("#{from_path}/")
|
295
|
+
|
296
|
+
# POSIX rename(2) requires to silently abandon, without unlinking from_path,
|
297
|
+
# if the inodes at from_path and to_path are the same object (ie hard linked to each other))
|
298
|
+
catch :same_hard_link do
|
299
|
+
link(nil, to_path) do |replacing|
|
300
|
+
check_rename_unlink(from_path)
|
301
|
+
unlink(from_path) do |source|
|
302
|
+
raise Errno::ENOENT unless source
|
303
|
+
|
304
|
+
throw :same_hard_link if source.equal?(replacing)
|
305
|
+
rename_cleanup_overwritten(replacing)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
# Common between {#link} and {#rename} are callbacks that might have different semantics
|
313
|
+
# if called within the same sub-filesystem.
|
314
|
+
# While from_path and to_path have a common top level directory, we pass the callback on
|
315
|
+
# to the entry at that directory
|
316
|
+
def same_filesystem_method(callback, from_path, to_path, rescue_notsup: false)
|
317
|
+
return yield unless from_path # no from_path to traverse
|
318
|
+
|
319
|
+
to_dir, next_to_path = entry_path(to_path)
|
320
|
+
return yield if root?(next_to_path) # target is our entry, no more directories to traverse
|
321
|
+
|
322
|
+
from_dir, next_from_path = entry_path(from_path)
|
323
|
+
return yield if from_dir != to_dir # from and to in different directories, we need to handle it ourself
|
324
|
+
|
325
|
+
# try traverse into sub-fs, which must itself be a directory
|
326
|
+
begin
|
327
|
+
entry_send(
|
328
|
+
entries[to_dir], callback,
|
329
|
+
next_from_path, next_to_path,
|
330
|
+
notsup: Errno::ENOSYS, notdir: Errno::ENOTDIR, rescue_notsup: rescue_notsup
|
331
|
+
)
|
332
|
+
rescue Errno::ENOSYS, Errno::ENOTSUP
|
333
|
+
raise unless rescue_notsup
|
334
|
+
|
335
|
+
yield
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
# Creates a new symbolic link in this directory
|
340
|
+
# @param [String] target - an absolute path for the operating system or relative to path
|
341
|
+
# @param [String] path - the path to create the link at
|
342
|
+
def symlink(target, path)
|
343
|
+
path_method(__method__, target, path) do |link_name, existing|
|
344
|
+
raise Errno::EEXIST if existing
|
345
|
+
|
346
|
+
new_link = new_symlink(link_name)
|
347
|
+
entry_send(new_link, :symlink, target, '/')
|
348
|
+
entries[link_name] = new_link
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
def new_symlink(_name)
|
353
|
+
VirtualLink.new(accounting: accounting)
|
232
354
|
end
|
233
355
|
|
234
356
|
# @!endgroup
|
235
357
|
|
236
|
-
#
|
237
|
-
#
|
358
|
+
# Finds the path argument of the callback and splits it into an entry in this directory and a remaining path
|
359
|
+
#
|
360
|
+
# If a block is given and there is no remaining path (ie our entry) the block is called and its value returned
|
361
|
+
#
|
362
|
+
# If the path is not our entry, the callback is passed on to the sub filesystem entry with the remaining path
|
363
|
+
#
|
364
|
+
# If the path is our entry, but not block is provided, the callback is passed to our entry with a path of '/'
|
365
|
+
#
|
238
366
|
# @param [Symbol] callback a FUSE Callback
|
239
|
-
# @param [
|
240
|
-
# @param [
|
241
|
-
# @param [Proc]
|
242
|
-
# @param [Class<SystemCallError>] notsup
|
367
|
+
# @param [Array] args callback arguments (first argument is typically 'path')
|
368
|
+
# @param [Errno] notsup an error to raise if this callback is not supported by our entry
|
369
|
+
# @param [Proc] block optional block to keep passing down. See {#mkdir}, {#create}, {#link}
|
243
370
|
# @raise [Errno:ENOENT] if the next entry does not exist
|
371
|
+
# @raise [Errno::ENOTDIR] if the next entry must be a directory, but does not respond to :raaddir
|
244
372
|
# @raise [SystemCallError] error from notsup if the next entry does not respond to ths callback
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
373
|
+
# @yield(entry_key, entry)
|
374
|
+
# @yieldparam [String,nil] entry_key the name of the entry in this directory or nil, if path is '/'
|
375
|
+
# @yieldparam [FuseOperations,nil] entry the filesystem object currently stored at entry_key
|
376
|
+
def path_method(callback, *args, notsup: Errno::ENOTSUP, block: nil)
|
377
|
+
# Inside path_method
|
378
|
+
_read_arg_method, path_arg_method, next_arg_method = FuseOperations.path_arg_methods(callback)
|
379
|
+
path = args.send(path_arg_method)
|
251
380
|
|
252
|
-
|
381
|
+
entry_key, next_path = entry_path(path)
|
382
|
+
our_entry = root?(next_path)
|
253
383
|
|
254
|
-
|
255
|
-
return unless responds || notsup
|
256
|
-
raise notsup unless responds
|
384
|
+
return yield entry_key, entries[entry_key] if block_given? && our_entry
|
257
385
|
|
258
|
-
|
259
|
-
|
260
|
-
end
|
386
|
+
# Pass to our entry
|
387
|
+
args.send(next_arg_method, next_path)
|
261
388
|
|
262
|
-
|
263
|
-
|
264
|
-
entry.public_send(callback, next_path, *args, &invoke)
|
389
|
+
notdir = Errno::ENOTDIR unless our_entry
|
390
|
+
entry_send(entries[entry_key], callback, *args, notsup: notsup, notdir: notdir, &block)
|
265
391
|
end
|
266
392
|
|
267
393
|
private
|
268
394
|
|
269
|
-
|
270
|
-
|
271
|
-
def method_missing(method, *args, &invoke)
|
395
|
+
def method_missing(method, *args, &block)
|
272
396
|
return super unless FuseOperations.path_callbacks.include?(method)
|
273
397
|
|
274
|
-
|
275
|
-
|
276
|
-
path_method(method, *args, &invoke)
|
398
|
+
path_method(method, *args, block: block)
|
277
399
|
end
|
278
400
|
|
279
401
|
def respond_to_missing?(method, inc_private = false)
|
@@ -282,23 +404,68 @@ module FFI
|
|
282
404
|
super
|
283
405
|
end
|
284
406
|
|
285
|
-
|
286
|
-
|
287
|
-
|
407
|
+
# Split path into an entry key and remaining path
|
408
|
+
# @param [:to_s] path
|
409
|
+
# @return [nil] if path is root (or nil)
|
410
|
+
# @return [Array<String, String] entry_key and '/' if path refers to an entry in this directory
|
411
|
+
# @return [Array<String, String>] entry key and remaining path when path refers to an entry in a sub-directory
|
412
|
+
def entry_path(path)
|
413
|
+
return nil unless path
|
414
|
+
|
415
|
+
path = path.to_s
|
416
|
+
return nil if root?(path)
|
417
|
+
|
418
|
+
# Fuse paths always start with a leading slash and never have a trailing slash
|
419
|
+
sep_index = path.index('/', 1)
|
420
|
+
|
421
|
+
return [path[1..], '/'] unless sep_index
|
288
422
|
|
289
|
-
|
290
|
-
init_result = entry_fuse_respond_to?(dir, :init) ? dir.init(*init_args) : nil
|
291
|
-
init_results[name] = init_result if init_result
|
423
|
+
[path[1..sep_index - 1], path[sep_index..]]
|
292
424
|
end
|
293
425
|
|
294
426
|
def entry_fuse_respond_to?(entry_fs, method)
|
295
427
|
entry_fs.respond_to?(:fuse_respond_to?) ? entry_fs.fuse_respond_to?(method) : entry_fs.respond_to?(method)
|
296
428
|
end
|
297
429
|
|
298
|
-
def
|
299
|
-
|
430
|
+
def dir_entry?(entry)
|
431
|
+
entry_fuse_respond_to?(entry, :readdir)
|
432
|
+
end
|
433
|
+
|
434
|
+
def entry_send(entry, callback, *args, notsup: nil, notdir: nil, rescue_notsup: notsup.nil?, &blk)
|
435
|
+
raise Errno::ENOENT unless entry
|
436
|
+
raise notdir if notdir && !dir_entry?(entry)
|
437
|
+
|
438
|
+
responds = entry_fuse_respond_to?(entry, callback)
|
439
|
+
return unless responds || notsup
|
440
|
+
raise notsup unless responds
|
441
|
+
|
442
|
+
entry.public_send(callback, *args, &blk)
|
443
|
+
rescue Errno::ENOTSUP, Errno::ENOSYS
|
444
|
+
raise unless rescue_notsup
|
445
|
+
|
446
|
+
nil
|
447
|
+
end
|
448
|
+
|
449
|
+
def check_rename_unlink(from_path)
|
450
|
+
# Safety check that the unlink proc is passed through to the final directory
|
451
|
+
# to explicitly support our rename proc.
|
452
|
+
rename_support = false
|
453
|
+
unlink("#{from_path}.__unlink_rename__") do |source|
|
454
|
+
rename_support = source.nil?
|
455
|
+
end
|
456
|
+
raise Errno::ENOSYS, 'rename via unlink not supported' unless rename_support
|
457
|
+
end
|
458
|
+
|
459
|
+
# Cleanup the object being overwritten, including potentially raising SystemCallError
|
460
|
+
# to prevent the rename going ahead
|
461
|
+
def rename_cleanup_overwritten(replacing)
|
462
|
+
return unless replacing
|
463
|
+
|
464
|
+
entry_send(replacing, dir_entry?(replacing) ? :rmdir : :unlink, '/')
|
465
|
+
end
|
300
466
|
|
301
|
-
|
467
|
+
def root?(path)
|
468
|
+
path ? super : true
|
302
469
|
end
|
303
470
|
end
|
304
471
|
end
|