@zenfs/core 2.5.7 → 2.6.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.
- package/dist/backends/cow.d.ts +13 -0
- package/dist/backends/cow.js +48 -11
- package/dist/backends/single_buffer.d.ts +2 -2
- package/dist/backends/store/fs.d.ts +6 -6
- package/dist/backends/store/fs.js +14 -10
- package/dist/internal/devices.d.ts +6 -6
- package/dist/internal/filesystem.d.ts +4 -4
- package/dist/internal/index_fs.d.ts +4 -4
- package/dist/internal/index_fs.js +6 -8
- package/dist/internal/inode.js +3 -0
- package/dist/node/async.js +1 -1
- package/dist/node/dir.d.ts +3 -1
- package/dist/node/dir.js +23 -5
- package/dist/node/promises.d.ts +4 -2
- package/dist/node/promises.js +130 -89
- package/dist/node/streams.d.ts +4 -0
- package/dist/node/streams.js +9 -10
- package/dist/node/sync.js +71 -40
- package/dist/utils.d.ts +8 -1
- package/dist/utils.js +26 -7
- package/dist/vfs/async.js +224 -85
- package/dist/vfs/file.d.ts +13 -7
- package/dist/vfs/file.js +661 -357
- package/dist/vfs/shared.d.ts +1 -0
- package/dist/vfs/shared.js +19 -3
- package/dist/vfs/sync.js +214 -85
- package/dist/vfs/vcache.d.ts +66 -0
- package/dist/vfs/vcache.js +249 -0
- package/dist/vfs/vnode.d.ts +66 -0
- package/dist/vfs/vnode.js +200 -0
- package/dist/vfs/watchers.js +4 -2
- package/dist/vfs/xattr.js +2 -2
- package/eslint.shared.js +1 -1
- package/package.json +6 -3
- package/scripts/make-index.js +7 -1
- package/scripts/test.js +28 -21
- package/tests/backend/port.test.ts +3 -3
- package/tests/backend/store.test.ts +49 -0
- package/tests/common/inode.test.ts +8 -0
- package/tests/common.ts +62 -0
- package/tests/fetch/fetch.ts +6 -0
- package/tests/fetch/run.sh +2 -2
- package/tests/fetch/server.js +3 -3
- package/tests/fs/append.test.ts +5 -5
- package/tests/fs/concurrency.test.ts +52 -0
- package/tests/fs/dir.test.ts +13 -15
- package/tests/fs/directory.test.ts +32 -31
- package/tests/fs/errors.test.ts +151 -31
- package/tests/fs/exists.test.ts +4 -4
- package/tests/fs/links.test.ts +4 -11
- package/tests/fs/open.test.ts +4 -4
- package/tests/fs/permissions.test.ts +15 -15
- package/tests/fs/read.test.ts +9 -9
- package/tests/fs/readFile.test.ts +9 -9
- package/tests/fs/rename.test.ts +8 -8
- package/tests/fs/scaling.test.ts +4 -4
- package/tests/fs/stat.test.ts +20 -20
- package/tests/fs/streams.test.ts +9 -6
- package/tests/fs/tempdir.test.ts +33 -10
- package/tests/fs/times.test.ts +11 -11
- package/tests/fs/truncate.test.ts +6 -6
- package/tests/fs/watch.test.ts +107 -53
- package/tests/fs/write.test.ts +10 -10
- package/tests/fs/xattr.test.ts +3 -3
- package/tests/native/run.sh +3 -0
- package/tests/native/setup.ts +230 -0
- package/types/readable-stream.d.ts +2 -2
package/dist/vfs/shared.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export declare function mount(this: V_Context, mountPoint: string, fs: FileSyste
|
|
|
23
23
|
/**
|
|
24
24
|
* Unmounts the file system at `mountPoint`.
|
|
25
25
|
* @category Backends and Configuration
|
|
26
|
+
* @todo [BREAKING] make this `async` and `await` vcache sync
|
|
26
27
|
*/
|
|
27
28
|
export declare function umount(this: V_Context, mountPoint: string): void;
|
|
28
29
|
/**
|
package/dist/vfs/shared.js
CHANGED
|
@@ -9,6 +9,7 @@ import { credentialsAllowRoot } from '../internal/credentials.js';
|
|
|
9
9
|
import { withExceptionContext } from '../internal/error.js';
|
|
10
10
|
import { join, resolve } from '../path.js';
|
|
11
11
|
import { normalizePath } from '../utils.js';
|
|
12
|
+
import { caches, VCache } from './vcache.js';
|
|
12
13
|
/**
|
|
13
14
|
* The map of mount points
|
|
14
15
|
* @category Backends and Configuration
|
|
@@ -30,23 +31,38 @@ export function mount(mountPoint, fs) {
|
|
|
30
31
|
throw err(withErrno('EINVAL', 'Mount point is already in use: ' + mountPoint));
|
|
31
32
|
fs._mountPoint = mountPoint;
|
|
32
33
|
mounts.set(mountPoint, fs);
|
|
34
|
+
if (!caches.has(fs.uuid))
|
|
35
|
+
caches.set(fs.uuid, new VCache(fs));
|
|
33
36
|
info(`Mounted ${fs.name} on ${mountPoint}`);
|
|
34
37
|
debug(`${fs.name} attributes: ${[...fs.attributes].map(([k, v]) => (v !== undefined && v !== null ? k + '=' + v : k)).join(', ')}`);
|
|
35
38
|
}
|
|
36
39
|
/**
|
|
37
40
|
* Unmounts the file system at `mountPoint`.
|
|
38
41
|
* @category Backends and Configuration
|
|
42
|
+
* @todo [BREAKING] make this `async` and `await` vcache sync
|
|
39
43
|
*/
|
|
40
44
|
export function umount(mountPoint) {
|
|
41
45
|
if (mountPoint[0] != '/')
|
|
42
46
|
mountPoint = '/' + mountPoint;
|
|
43
47
|
mountPoint = resolve.call(this, mountPoint);
|
|
44
|
-
|
|
48
|
+
const fs = mounts.get(mountPoint);
|
|
49
|
+
if (!fs) {
|
|
45
50
|
warn(mountPoint + ' is already unmounted');
|
|
46
51
|
return;
|
|
47
52
|
}
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
const _done = () => {
|
|
54
|
+
caches.delete(fs.uuid);
|
|
55
|
+
mounts.delete(mountPoint);
|
|
56
|
+
notice('Unmounted ' + mountPoint);
|
|
57
|
+
};
|
|
58
|
+
try {
|
|
59
|
+
caches.get(fs.uuid)?.syncSync();
|
|
60
|
+
_done();
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
warn(`Could not flush to ${mountPoint} synchronously. Unmount is being deferred.`);
|
|
64
|
+
void caches.get(fs.uuid)?.sync().then(_done);
|
|
65
|
+
}
|
|
50
66
|
}
|
|
51
67
|
/**
|
|
52
68
|
* Gets the internal `FileSystem` for the path, then returns it along with the path relative to the FS' root
|
package/dist/vfs/sync.js
CHANGED
|
@@ -1,10 +1,64 @@
|
|
|
1
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
|
+
if (value !== null && value !== void 0) {
|
|
3
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
4
|
+
var dispose, inner;
|
|
5
|
+
if (async) {
|
|
6
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
7
|
+
dispose = value[Symbol.asyncDispose];
|
|
8
|
+
}
|
|
9
|
+
if (dispose === void 0) {
|
|
10
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
11
|
+
dispose = value[Symbol.dispose];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
15
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
16
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
17
|
+
}
|
|
18
|
+
else if (async) {
|
|
19
|
+
env.stack.push({ async: true });
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
24
|
+
return function (env) {
|
|
25
|
+
function fail(e) {
|
|
26
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
27
|
+
env.hasError = true;
|
|
28
|
+
}
|
|
29
|
+
var r, s = 0;
|
|
30
|
+
function next() {
|
|
31
|
+
while (r = env.stack.pop()) {
|
|
32
|
+
try {
|
|
33
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
34
|
+
if (r.dispose) {
|
|
35
|
+
var result = r.dispose.call(r.value);
|
|
36
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
37
|
+
}
|
|
38
|
+
else s |= 1;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
fail(e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
45
|
+
if (env.hasError) throw env.error;
|
|
46
|
+
}
|
|
47
|
+
return next();
|
|
48
|
+
};
|
|
49
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
+
var e = new Error(message);
|
|
51
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
+
});
|
|
1
53
|
import { setUVMessage, UV } from 'kerium';
|
|
2
54
|
import { decodeUTF8 } from 'utilium';
|
|
3
55
|
import * as constants from '../constants.js';
|
|
4
56
|
import { contextOf } from '../internal/contexts.js';
|
|
57
|
+
import { wrap } from '../internal/error.js';
|
|
5
58
|
import { hasAccess, isDirectory, isSymbolicLink } from '../internal/inode.js';
|
|
6
59
|
import { basename, dirname, join, parse, resolve as resolvePath } from '../path.js';
|
|
7
60
|
import { normalizeMode, normalizePath } from '../utils.js';
|
|
61
|
+
import { cacheOf, lockPathSync } from './vcache.js';
|
|
8
62
|
import { checkAccess } from './config.js';
|
|
9
63
|
import { Dirent, ifToDt } from './dir.js';
|
|
10
64
|
import { Handle } from './file.js';
|
|
@@ -23,8 +77,8 @@ export function resolve($, path, preserveSymlinks, extra) {
|
|
|
23
77
|
that means we don't need to perform any resolution for parent directories. */
|
|
24
78
|
try {
|
|
25
79
|
const resolved = resolveMount(path, $);
|
|
26
|
-
// Stat it to make sure it exists
|
|
27
|
-
const stats = resolved.fs.statSync(resolved.path);
|
|
80
|
+
// Stat it to make sure it exists. The vnode cache takes precedence since it may have unsynced changes
|
|
81
|
+
const stats = cacheOf(resolved.fs).get(resolved.path)?.inode ?? resolved.fs.statSync(resolved.path);
|
|
28
82
|
if (!isSymbolicLink(stats) || preserveSymlinks) {
|
|
29
83
|
return { ...resolved, fullPath: path, stats };
|
|
30
84
|
}
|
|
@@ -42,7 +96,7 @@ export function resolve($, path, preserveSymlinks, extra) {
|
|
|
42
96
|
const resolved = resolveMount(maybePath, $);
|
|
43
97
|
let stats;
|
|
44
98
|
try {
|
|
45
|
-
stats = resolved.fs.statSync(resolved.path);
|
|
99
|
+
stats = cacheOf(resolved.fs).get(resolved.path)?.inode ?? resolved.fs.statSync(resolved.path);
|
|
46
100
|
}
|
|
47
101
|
catch (e) {
|
|
48
102
|
if (e.code === 'ENOENT')
|
|
@@ -72,45 +126,57 @@ export function open(path, opt) {
|
|
|
72
126
|
// nothing
|
|
73
127
|
}
|
|
74
128
|
if (!stats) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
129
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
130
|
+
try {
|
|
131
|
+
if (!(flag & constants.O_CREAT)) {
|
|
132
|
+
throw UV('ENOENT', 'open', path);
|
|
133
|
+
}
|
|
134
|
+
// Create the file
|
|
135
|
+
const parentPath = dirname(resolved);
|
|
136
|
+
const parentStats = fs.statSync(parentPath);
|
|
137
|
+
if (checkAccess && !hasAccess(this, parentStats, constants.W_OK)) {
|
|
138
|
+
throw UV('EACCES', 'open', path);
|
|
139
|
+
}
|
|
140
|
+
if (!isDirectory(parentStats)) {
|
|
141
|
+
throw UV('ENOTDIR', 'open', path);
|
|
142
|
+
}
|
|
143
|
+
if (!opt.allowDirectory && mode & constants.S_IFDIR)
|
|
144
|
+
throw UV('EISDIR', 'open', path);
|
|
145
|
+
// Serialize entry creation with other operations on the parent directory
|
|
146
|
+
const _ = __addDisposableResource(env_1, lockPathSync(fs, parentPath, 'rw', parentStats), false);
|
|
147
|
+
const { euid: uid, egid: gid } = contextOf(this).credentials;
|
|
148
|
+
const inode = fs.createFileSync(resolved, {
|
|
149
|
+
mode,
|
|
150
|
+
uid: parentStats.mode & constants.S_ISUID ? parentStats.uid : uid,
|
|
151
|
+
gid: parentStats.mode & constants.S_ISGID ? parentStats.gid : gid,
|
|
152
|
+
});
|
|
153
|
+
// A new entry in the parent directory, which is a 'rename' event
|
|
154
|
+
emitChange(this, 'rename', path);
|
|
155
|
+
return new Handle(this, path, resolved, flag, cacheOf(fs).ref(resolved, inode));
|
|
82
156
|
}
|
|
83
|
-
|
|
84
|
-
|
|
157
|
+
catch (e_1) {
|
|
158
|
+
env_1.error = e_1;
|
|
159
|
+
env_1.hasError = true;
|
|
85
160
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (checkAccess && !hasAccess(this, parentStats, constants.W_OK)) {
|
|
89
|
-
throw UV('EACCES', 'open', path);
|
|
161
|
+
finally {
|
|
162
|
+
__disposeResources(env_1);
|
|
90
163
|
}
|
|
91
|
-
const { euid: uid, egid: gid } = contextOf(this).credentials;
|
|
92
|
-
const inode = fs.createFileSync(resolved, {
|
|
93
|
-
mode,
|
|
94
|
-
uid: parentStats.mode & constants.S_ISUID ? parentStats.uid : uid,
|
|
95
|
-
gid: parentStats.mode & constants.S_ISGID ? parentStats.gid : gid,
|
|
96
|
-
});
|
|
97
|
-
return new Handle(this, path, fs, resolved, flag, inode);
|
|
98
164
|
}
|
|
99
165
|
if (checkAccess && (!hasAccess(this, stats, mode) || !hasAccess(this, stats, flags.toMode(flag)))) {
|
|
100
166
|
throw UV('EACCES', 'open', path);
|
|
101
167
|
}
|
|
102
168
|
if (flag & constants.O_EXCL)
|
|
103
169
|
throw UV('EEXIST', 'open', path);
|
|
104
|
-
const file = new Handle(this, path, fs, resolved, flag, stats);
|
|
105
170
|
if (!opt.allowDirectory && stats.mode & constants.S_IFDIR)
|
|
106
171
|
throw UV('EISDIR', 'open', path);
|
|
172
|
+
const file = new Handle(this, path, resolved, flag, cacheOf(fs).ref(resolved, stats));
|
|
107
173
|
if (flag & constants.O_TRUNC)
|
|
108
174
|
file.truncateSync(0);
|
|
109
175
|
return file;
|
|
110
176
|
}
|
|
111
177
|
export function readlink(path) {
|
|
112
178
|
path = normalizePath(path);
|
|
113
|
-
const { fs, stats, path: resolved } = resolve(this, path, true);
|
|
179
|
+
const { fs, stats, path: resolved } = resolve(this, path, true, { syscall: 'readlink' });
|
|
114
180
|
if (!stats)
|
|
115
181
|
throw UV('ENOENT', 'readlink', path);
|
|
116
182
|
if (checkAccess && !hasAccess(this, stats, constants.R_OK))
|
|
@@ -128,15 +194,26 @@ export function mkdir(path, options = {}) {
|
|
|
128
194
|
const { euid: uid, egid: gid } = contextOf(this).credentials;
|
|
129
195
|
const { mode = 0o777, recursive } = options;
|
|
130
196
|
const __create = (path, resolved, parent) => {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
197
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
198
|
+
try {
|
|
199
|
+
if (checkAccess && !hasAccess(this, parent, constants.W_OK))
|
|
200
|
+
throw UV('EACCES', 'mkdir', dirname(path));
|
|
201
|
+
const _ = __addDisposableResource(env_2, lockPathSync(fs, dirname(resolved), 'rw', parent), false);
|
|
202
|
+
const inode = wrap(fs, 'mkdirSync', { path, syscall: 'mkdir' })(resolved, {
|
|
203
|
+
mode,
|
|
204
|
+
uid: parent.mode & constants.S_ISUID ? parent.uid : uid,
|
|
205
|
+
gid: parent.mode & constants.S_ISGID ? parent.gid : gid,
|
|
206
|
+
});
|
|
207
|
+
emitChange(this, 'rename', path);
|
|
208
|
+
return inode;
|
|
209
|
+
}
|
|
210
|
+
catch (e_2) {
|
|
211
|
+
env_2.error = e_2;
|
|
212
|
+
env_2.hasError = true;
|
|
213
|
+
}
|
|
214
|
+
finally {
|
|
215
|
+
__disposeResources(env_2);
|
|
216
|
+
}
|
|
140
217
|
};
|
|
141
218
|
if (!recursive) {
|
|
142
219
|
__create(path, resolved, fs.statSync(dirname(resolved)));
|
|
@@ -157,12 +234,27 @@ export function mkdir(path, options = {}) {
|
|
|
157
234
|
export function readdir(path, options = {}) {
|
|
158
235
|
path = normalizePath(path);
|
|
159
236
|
const { fs, path: resolved } = resolve(this, path);
|
|
160
|
-
|
|
237
|
+
// Node reports `readdir` failures as `scandir`
|
|
238
|
+
const stats = cacheOf(fs).get(resolved)?.inode ?? wrap(fs, 'statSync', { path, syscall: 'scandir' })(resolved);
|
|
161
239
|
if (checkAccess && !hasAccess(this, stats, constants.R_OK))
|
|
162
|
-
throw UV('EACCES', '
|
|
240
|
+
throw UV('EACCES', 'scandir', path);
|
|
163
241
|
if (!isDirectory(stats))
|
|
164
|
-
throw UV('ENOTDIR', '
|
|
165
|
-
|
|
242
|
+
throw UV('ENOTDIR', 'scandir', path);
|
|
243
|
+
let entries;
|
|
244
|
+
{
|
|
245
|
+
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
246
|
+
try {
|
|
247
|
+
const _ = __addDisposableResource(env_3, lockPathSync(fs, resolved, 'ro', stats), false);
|
|
248
|
+
entries = fs.readdirSync(resolved);
|
|
249
|
+
}
|
|
250
|
+
catch (e_3) {
|
|
251
|
+
env_3.error = e_3;
|
|
252
|
+
env_3.hasError = true;
|
|
253
|
+
}
|
|
254
|
+
finally {
|
|
255
|
+
__disposeResources(env_3);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
166
258
|
// Iterate over entries and handle recursive case if needed
|
|
167
259
|
const values = [];
|
|
168
260
|
const addEntry = (entry) => {
|
|
@@ -192,58 +284,94 @@ export function readdir(path, options = {}) {
|
|
|
192
284
|
return values;
|
|
193
285
|
}
|
|
194
286
|
export function rename(oldPath, newPath) {
|
|
195
|
-
|
|
196
|
-
newPath = normalizePath(newPath);
|
|
197
|
-
const $ex = { syscall: 'rename', path: oldPath, dest: newPath };
|
|
198
|
-
const src = resolve(this, oldPath, true, $ex);
|
|
199
|
-
const dst = resolveMount(newPath, this, $ex);
|
|
200
|
-
if (src.fs.uuid !== dst.fs.uuid)
|
|
201
|
-
throw UV('EXDEV', $ex);
|
|
202
|
-
if (dst.path.startsWith(src.path + '/'))
|
|
203
|
-
throw UV('EBUSY', $ex);
|
|
204
|
-
if (!src.stats)
|
|
205
|
-
throw UV('ENOENT', $ex);
|
|
206
|
-
const fs = src.fs;
|
|
207
|
-
const oldParent = fs.statSync(dirname(src.path));
|
|
208
|
-
const newParent = fs.statSync(dirname(dst.path));
|
|
209
|
-
let newStats;
|
|
287
|
+
const env_4 = { stack: [], error: void 0, hasError: false };
|
|
210
288
|
try {
|
|
211
|
-
|
|
289
|
+
oldPath = normalizePath(oldPath);
|
|
290
|
+
newPath = normalizePath(newPath);
|
|
291
|
+
const $ex = { syscall: 'rename', path: oldPath, dest: newPath };
|
|
292
|
+
const src = resolve(this, oldPath, true, $ex);
|
|
293
|
+
const dst = resolveMount(newPath, this, $ex);
|
|
294
|
+
if (src.fs.uuid !== dst.fs.uuid)
|
|
295
|
+
throw UV('EXDEV', $ex);
|
|
296
|
+
// A directory can not be moved inside itself
|
|
297
|
+
if (dst.path.startsWith(src.path + '/'))
|
|
298
|
+
throw UV('EINVAL', $ex);
|
|
299
|
+
if (!src.stats)
|
|
300
|
+
throw UV('ENOENT', $ex);
|
|
301
|
+
const fs = src.fs;
|
|
302
|
+
const oldParent = fs.statSync(dirname(src.path));
|
|
303
|
+
const newParent = fs.statSync(dirname(dst.path));
|
|
304
|
+
let newStats;
|
|
305
|
+
try {
|
|
306
|
+
newStats = fs.statSync(dst.path);
|
|
307
|
+
}
|
|
308
|
+
catch (e) {
|
|
309
|
+
if (e.code != 'ENOENT')
|
|
310
|
+
throw e;
|
|
311
|
+
}
|
|
312
|
+
if (checkAccess && (!hasAccess(this, oldParent, constants.R_OK) || !hasAccess(this, newParent, constants.W_OK)))
|
|
313
|
+
throw UV('EACCES', $ex);
|
|
314
|
+
if (newStats && !isDirectory(src.stats) && isDirectory(newStats))
|
|
315
|
+
throw UV('EISDIR', $ex);
|
|
316
|
+
if (newStats && isDirectory(src.stats) && !isDirectory(newStats))
|
|
317
|
+
throw UV('ENOTDIR', $ex);
|
|
318
|
+
const srcDir = dirname(src.path);
|
|
319
|
+
const dstDir = dirname(dst.path);
|
|
320
|
+
// Lock both parent directories, ordered by inode number to avoid ABBA deadlocks (like Linux's `lock_two_nondirectories`)
|
|
321
|
+
const parents = [
|
|
322
|
+
[srcDir, oldParent],
|
|
323
|
+
[dstDir, newParent],
|
|
324
|
+
];
|
|
325
|
+
if (oldParent.ino > newParent.ino)
|
|
326
|
+
parents.reverse();
|
|
327
|
+
const _first = __addDisposableResource(env_4, lockPathSync(fs, parents[0][0], 'rw', parents[0][1]), false);
|
|
328
|
+
const _second = __addDisposableResource(env_4, oldParent.ino == newParent.ino ? null : lockPathSync(fs, parents[1][0], 'rw', parents[1][1]), false);
|
|
329
|
+
src.fs.renameSync(src.path, dst.path);
|
|
330
|
+
cacheOf(fs).rename(src.path, dst.path);
|
|
331
|
+
// Both names change which entries exist, so both are 'rename' events
|
|
332
|
+
emitChange(this, 'rename', oldPath);
|
|
333
|
+
emitChange(this, 'rename', newPath);
|
|
212
334
|
}
|
|
213
|
-
catch (
|
|
214
|
-
|
|
215
|
-
|
|
335
|
+
catch (e_4) {
|
|
336
|
+
env_4.error = e_4;
|
|
337
|
+
env_4.hasError = true;
|
|
338
|
+
}
|
|
339
|
+
finally {
|
|
340
|
+
__disposeResources(env_4);
|
|
216
341
|
}
|
|
217
|
-
if (checkAccess && (!hasAccess(this, oldParent, constants.R_OK) || !hasAccess(this, newParent, constants.W_OK)))
|
|
218
|
-
throw UV('EACCES', $ex);
|
|
219
|
-
if (newStats && !isDirectory(src.stats) && isDirectory(newStats))
|
|
220
|
-
throw UV('EISDIR', $ex);
|
|
221
|
-
if (newStats && isDirectory(src.stats) && !isDirectory(newStats))
|
|
222
|
-
throw UV('ENOTDIR', $ex);
|
|
223
|
-
src.fs.renameSync(src.path, dst.path);
|
|
224
|
-
emitChange(this, 'rename', oldPath);
|
|
225
|
-
emitChange(this, 'change', newPath);
|
|
226
342
|
}
|
|
227
343
|
export function link(target, link) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
344
|
+
const env_5 = { stack: [], error: void 0, hasError: false };
|
|
345
|
+
try {
|
|
346
|
+
target = normalizePath(target);
|
|
347
|
+
link = normalizePath(link);
|
|
348
|
+
const $ex = { syscall: 'link', path: target, dest: link };
|
|
349
|
+
const { fs, path: resolved } = resolveMount(target, this, $ex);
|
|
350
|
+
const dst = resolveMount(link, this, $ex);
|
|
351
|
+
if (fs.uuid !== dst.fs.uuid)
|
|
352
|
+
throw UV('EXDEV', $ex);
|
|
353
|
+
const stats = fs.statSync(resolved);
|
|
354
|
+
if (checkAccess) {
|
|
355
|
+
if (!hasAccess(this, stats, constants.R_OK))
|
|
356
|
+
throw UV('EACCES', $ex);
|
|
357
|
+
const dirStats = fs.statSync(dirname(resolved));
|
|
358
|
+
if (!hasAccess(this, dirStats, constants.R_OK))
|
|
359
|
+
throw UV('EACCES', $ex);
|
|
360
|
+
const destStats = fs.statSync(dirname(dst.path));
|
|
361
|
+
if (!hasAccess(this, destStats, constants.W_OK))
|
|
362
|
+
throw UV('EACCES', $ex);
|
|
363
|
+
}
|
|
364
|
+
const _ = __addDisposableResource(env_5, lockPathSync(fs, dirname(dst.path), 'rw'), false);
|
|
365
|
+
fs.linkSync(resolved, dst.path);
|
|
366
|
+
cacheOf(fs).link(resolved, dst.path);
|
|
367
|
+
}
|
|
368
|
+
catch (e_5) {
|
|
369
|
+
env_5.error = e_5;
|
|
370
|
+
env_5.hasError = true;
|
|
371
|
+
}
|
|
372
|
+
finally {
|
|
373
|
+
__disposeResources(env_5);
|
|
245
374
|
}
|
|
246
|
-
return fs.linkSync(resolved, dst.path);
|
|
247
375
|
}
|
|
248
376
|
export function stat(path, lstat) {
|
|
249
377
|
path = normalizePath.call(this, path);
|
|
@@ -255,7 +383,8 @@ export function stat(path, lstat) {
|
|
|
255
383
|
const { base, dir } = parse(path);
|
|
256
384
|
const { fs, path: parent } = resolve(this, dir, false, extra);
|
|
257
385
|
try {
|
|
258
|
-
|
|
386
|
+
const target = base ? join(parent, base) : parent;
|
|
387
|
+
stats = cacheOf(fs).get(target)?.inode ?? fs.statSync(target);
|
|
259
388
|
}
|
|
260
389
|
catch (e) {
|
|
261
390
|
setUVMessage(Object.assign(e, extra));
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { LockMode, LockRelease } from 'kerium/locks';
|
|
2
|
+
import type { FileSystem } from '../internal/filesystem.js';
|
|
3
|
+
import type { InodeLike } from '../internal/inode.js';
|
|
4
|
+
import { VNode } from './vnode.js';
|
|
5
|
+
/**
|
|
6
|
+
* The VFS-level cache of vnodes for a single file system. Modeled after Linux's dcache/icache.
|
|
7
|
+
*
|
|
8
|
+
* Vnodes are keyed by inode number, so hard links share a vnode.
|
|
9
|
+
* A vnode stays cached while it is referenced or dirty, and is evicted otherwise.
|
|
10
|
+
* @category VFS
|
|
11
|
+
* @todo Add LRU or something to limit memory usage
|
|
12
|
+
*/
|
|
13
|
+
export declare class VCache {
|
|
14
|
+
readonly fs: FileSystem;
|
|
15
|
+
protected byIno: Map<number, VNode>;
|
|
16
|
+
protected byPath: Map<string, VNode>;
|
|
17
|
+
constructor(fs: FileSystem);
|
|
18
|
+
/** Get the vnode for a path, if one is cached. Note paths are relative to the FS root, not the VFS root */
|
|
19
|
+
get(path: string): VNode | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Get or create the vnode for `path`, incrementing its reference count.
|
|
22
|
+
* Callers must `unref` the vnode when done with it.
|
|
23
|
+
*/
|
|
24
|
+
ref(path: string, inode: InodeLike): VNode;
|
|
25
|
+
/** Release a reference to a vnode, evicting it if it is unreferenced and clean */
|
|
26
|
+
unref(node: VNode): void;
|
|
27
|
+
protected evict(node: VNode): void;
|
|
28
|
+
/** Update cached paths for a rename. Handles directories, whose descendants' paths change too */
|
|
29
|
+
rename(oldPath: string, newPath: string): void;
|
|
30
|
+
/** Add a path for a newly created hard link, if the target is cached */
|
|
31
|
+
link(targetPath: string, linkPath: string): void;
|
|
32
|
+
/** Remove a path for an unlink/rmdir. The vnode is kept if other paths (hard links) refer to it */
|
|
33
|
+
remove(path: string): void;
|
|
34
|
+
/** Sync all dirty vnodes to the backend */
|
|
35
|
+
sync(): Promise<void>;
|
|
36
|
+
/** Sync all dirty vnodes to the backend synchronously */
|
|
37
|
+
syncSync(): void;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* All vnode caches, keyed by file system UUID.
|
|
41
|
+
* Keyed by UUID rather than the `FileSystem` object because `resolveMount`
|
|
42
|
+
* wraps file systems in a new exception-context proxy on every call.
|
|
43
|
+
* @category VFS
|
|
44
|
+
* @internal
|
|
45
|
+
*/
|
|
46
|
+
export declare const caches: Map<`${string}-${string}-${string}-${string}-${string}`, VCache>;
|
|
47
|
+
/**
|
|
48
|
+
* Get the vnode cache for a file system, creating it if needed.
|
|
49
|
+
* @category VFS
|
|
50
|
+
* @internal
|
|
51
|
+
*/
|
|
52
|
+
export declare function cacheOf(fs: FileSystem): VCache;
|
|
53
|
+
/**
|
|
54
|
+
* Ref and lock the vnode for a path, usually a parent directory during a namespace operation.
|
|
55
|
+
* The returned release function unlocks and unrefs the vnode.
|
|
56
|
+
* If `inode` is not provided and the vnode is not cached, the backend is `stat`ed.
|
|
57
|
+
* @category VFS
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
export declare function lockPath(fs: FileSystem, path: string, mode: LockMode, inode?: InodeLike): Promise<LockRelease>;
|
|
61
|
+
/**
|
|
62
|
+
* Synchronous version of `lockPath`.
|
|
63
|
+
* @category VFS
|
|
64
|
+
* @internal
|
|
65
|
+
*/
|
|
66
|
+
export declare function lockPathSync(fs: FileSystem, path: string, mode: LockMode, inode?: InodeLike): LockRelease;
|