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