@zenfs/core 1.3.6 → 1.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.
- package/dist/backends/memory.d.ts +4 -4
- package/dist/backends/memory.js +4 -4
- package/dist/backends/overlay.d.ts +5 -2
- package/dist/backends/overlay.js +7 -10
- package/dist/backends/port/fs.js +1 -4
- package/dist/config.js +4 -8
- package/dist/context.d.ts +32 -0
- package/dist/context.js +23 -0
- package/dist/credentials.d.ts +5 -5
- package/dist/credentials.js +10 -6
- package/dist/emulation/async.d.ts +90 -89
- package/dist/emulation/async.js +76 -75
- package/dist/emulation/dir.d.ts +3 -1
- package/dist/emulation/dir.js +6 -7
- package/dist/emulation/index.d.ts +1 -1
- package/dist/emulation/index.js +1 -1
- package/dist/emulation/promises.d.ts +50 -48
- package/dist/emulation/promises.js +78 -77
- package/dist/emulation/shared.d.ts +35 -8
- package/dist/emulation/shared.js +37 -11
- package/dist/emulation/sync.d.ts +63 -62
- package/dist/emulation/sync.js +72 -73
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/stats.d.ts +2 -1
- package/dist/stats.js +5 -4
- package/package.json +3 -5
- package/scripts/test.js +78 -17
- package/tests/assignment.ts +1 -1
- package/tests/common/context.test.ts +19 -0
- package/tests/{devices.test.ts → common/devices.test.ts} +3 -3
- package/tests/{handle.test.ts → common/handle.test.ts} +1 -1
- package/tests/common/mounts.test.ts +36 -0
- package/tests/{mutex.test.ts → common/mutex.test.ts} +3 -3
- package/tests/common/path.test.ts +34 -0
- package/tests/common.ts +4 -3
- package/tests/fs/dir.test.ts +11 -11
- package/tests/fs/directory.test.ts +17 -17
- package/tests/fs/errors.test.ts +29 -39
- package/tests/fs/watch.test.ts +2 -2
- package/tests/setup/context.ts +9 -0
- package/tests/setup/cow+fetch.ts +1 -1
- package/tests/setup/memory.ts +1 -1
- package/tests/{setup/common.ts → setup.ts} +6 -5
- package/src/backends/backend.ts +0 -161
- package/src/backends/fetch.ts +0 -180
- package/src/backends/file_index.ts +0 -206
- package/src/backends/memory.ts +0 -45
- package/src/backends/overlay.ts +0 -560
- package/src/backends/port/fs.ts +0 -329
- package/src/backends/port/readme.md +0 -54
- package/src/backends/port/rpc.ts +0 -167
- package/src/backends/readme.md +0 -3
- package/src/backends/store/fs.ts +0 -667
- package/src/backends/store/readme.md +0 -9
- package/src/backends/store/simple.ts +0 -154
- package/src/backends/store/store.ts +0 -189
- package/src/config.ts +0 -227
- package/src/credentials.ts +0 -49
- package/src/devices.ts +0 -521
- package/src/emulation/async.ts +0 -834
- package/src/emulation/cache.ts +0 -86
- package/src/emulation/config.ts +0 -21
- package/src/emulation/constants.ts +0 -182
- package/src/emulation/dir.ts +0 -138
- package/src/emulation/index.ts +0 -8
- package/src/emulation/path.ts +0 -440
- package/src/emulation/promises.ts +0 -1140
- package/src/emulation/shared.ts +0 -172
- package/src/emulation/streams.ts +0 -34
- package/src/emulation/sync.ts +0 -863
- package/src/emulation/watchers.ts +0 -194
- package/src/error.ts +0 -307
- package/src/file.ts +0 -631
- package/src/filesystem.ts +0 -174
- package/src/index.ts +0 -35
- package/src/inode.ts +0 -128
- package/src/mixins/async.ts +0 -230
- package/src/mixins/index.ts +0 -5
- package/src/mixins/mutexed.ts +0 -257
- package/src/mixins/readonly.ts +0 -96
- package/src/mixins/shared.ts +0 -25
- package/src/mixins/sync.ts +0 -58
- package/src/polyfills.ts +0 -21
- package/src/stats.ts +0 -405
- package/src/utils.ts +0 -276
- package/tests/mounts.test.ts +0 -18
- package/tests/path.test.ts +0 -34
package/src/backends/overlay.ts
DELETED
|
@@ -1,560 +0,0 @@
|
|
|
1
|
-
import { dirname } from '../emulation/path.js';
|
|
2
|
-
import { Errno, ErrnoError } from '../error.js';
|
|
3
|
-
import type { File } from '../file.js';
|
|
4
|
-
import { PreloadFile, parseFlag } from '../file.js';
|
|
5
|
-
import type { FileSystemMetadata } from '../filesystem.js';
|
|
6
|
-
import { FileSystem } from '../filesystem.js';
|
|
7
|
-
import { Mutexed } from '../mixins/mutexed.js';
|
|
8
|
-
import type { Stats } from '../stats.js';
|
|
9
|
-
import { decodeUTF8, encodeUTF8 } from '../utils.js';
|
|
10
|
-
import type { Backend } from './backend.js';
|
|
11
|
-
/** @internal */
|
|
12
|
-
const deletionLogPath = '/.deleted';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Configuration options for OverlayFS instances.
|
|
16
|
-
*/
|
|
17
|
-
export interface OverlayOptions {
|
|
18
|
-
/**
|
|
19
|
-
* The file system to write modified files to.
|
|
20
|
-
*/
|
|
21
|
-
writable: FileSystem;
|
|
22
|
-
/**
|
|
23
|
-
* The file system that initially populates this file system.
|
|
24
|
-
*/
|
|
25
|
-
readable: FileSystem;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* OverlayFS makes a read-only filesystem writable by storing writes on a second, writable file system.
|
|
30
|
-
* Deletes are persisted via metadata stored on the writable file system.
|
|
31
|
-
*
|
|
32
|
-
* This class contains no locking whatsoever. It is mutexed to prevent races.
|
|
33
|
-
*
|
|
34
|
-
* @internal
|
|
35
|
-
*/
|
|
36
|
-
export class UnmutexedOverlayFS extends FileSystem {
|
|
37
|
-
async ready(): Promise<void> {
|
|
38
|
-
await this.readable.ready();
|
|
39
|
-
await this.writable.ready();
|
|
40
|
-
await this._ready;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
public readonly writable: FileSystem;
|
|
44
|
-
public readonly readable: FileSystem;
|
|
45
|
-
private _isInitialized: boolean = false;
|
|
46
|
-
private _deletedFiles: Set<string> = new Set();
|
|
47
|
-
private _deleteLog: string = '';
|
|
48
|
-
// If 'true', we have scheduled a delete log update.
|
|
49
|
-
private _deleteLogUpdatePending: boolean = false;
|
|
50
|
-
// If 'true', a delete log update is needed after the scheduled delete log
|
|
51
|
-
// update finishes.
|
|
52
|
-
private _deleteLogUpdateNeeded: boolean = false;
|
|
53
|
-
// If there was an error updating the delete log...
|
|
54
|
-
private _deleteLogError?: ErrnoError;
|
|
55
|
-
|
|
56
|
-
private _ready: Promise<void>;
|
|
57
|
-
|
|
58
|
-
public constructor({ writable, readable }: OverlayOptions) {
|
|
59
|
-
super();
|
|
60
|
-
this.writable = writable;
|
|
61
|
-
this.readable = readable;
|
|
62
|
-
if (this.writable.metadata().readonly) {
|
|
63
|
-
throw new ErrnoError(Errno.EINVAL, 'Writable file system must be writable.');
|
|
64
|
-
}
|
|
65
|
-
this._ready = this._initialize();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
public metadata(): FileSystemMetadata {
|
|
69
|
-
return {
|
|
70
|
-
...super.metadata(),
|
|
71
|
-
name: OverlayFS.name,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
public async sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void> {
|
|
76
|
-
await this.copyForWrite(path);
|
|
77
|
-
if (!(await this.writable.exists(path))) {
|
|
78
|
-
await this.writable.createFile(path, 'w', 0o644);
|
|
79
|
-
}
|
|
80
|
-
await this.writable.sync(path, data, stats);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
public syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void {
|
|
84
|
-
this.copyForWriteSync(path);
|
|
85
|
-
this.writable.syncSync(path, data, stats);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Called once to load up metadata stored on the writable file system.
|
|
90
|
-
* @internal
|
|
91
|
-
*/
|
|
92
|
-
public async _initialize(): Promise<void> {
|
|
93
|
-
if (this._isInitialized) {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Read deletion log, process into metadata.
|
|
98
|
-
try {
|
|
99
|
-
const file = await this.writable.openFile(deletionLogPath, parseFlag('r'));
|
|
100
|
-
const { size } = await file.stat();
|
|
101
|
-
const { buffer } = await file.read(new Uint8Array(size));
|
|
102
|
-
this._deleteLog = decodeUTF8(buffer);
|
|
103
|
-
} catch (err) {
|
|
104
|
-
if ((err as ErrnoError).errno !== Errno.ENOENT) {
|
|
105
|
-
throw err;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
this._isInitialized = true;
|
|
109
|
-
this._reparseDeletionLog();
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
public getDeletionLog(): string {
|
|
113
|
-
return this._deleteLog;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
public async restoreDeletionLog(log: string): Promise<void> {
|
|
117
|
-
this._deleteLog = log;
|
|
118
|
-
this._reparseDeletionLog();
|
|
119
|
-
await this.updateLog('');
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
public async rename(oldPath: string, newPath: string): Promise<void> {
|
|
123
|
-
this.checkInitialized();
|
|
124
|
-
this.checkPath(oldPath);
|
|
125
|
-
this.checkPath(newPath);
|
|
126
|
-
|
|
127
|
-
await this.copyForWrite(oldPath);
|
|
128
|
-
|
|
129
|
-
try {
|
|
130
|
-
await this.writable.rename(oldPath, newPath);
|
|
131
|
-
} catch {
|
|
132
|
-
if (this._deletedFiles.has(oldPath)) {
|
|
133
|
-
throw ErrnoError.With('ENOENT', oldPath, 'rename');
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
public renameSync(oldPath: string, newPath: string): void {
|
|
139
|
-
this.checkInitialized();
|
|
140
|
-
this.checkPath(oldPath);
|
|
141
|
-
this.checkPath(newPath);
|
|
142
|
-
|
|
143
|
-
this.copyForWriteSync(oldPath);
|
|
144
|
-
|
|
145
|
-
try {
|
|
146
|
-
this.writable.renameSync(oldPath, newPath);
|
|
147
|
-
} catch {
|
|
148
|
-
if (this._deletedFiles.has(oldPath)) {
|
|
149
|
-
throw ErrnoError.With('ENOENT', oldPath, 'rename');
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
public async stat(path: string): Promise<Stats> {
|
|
155
|
-
this.checkInitialized();
|
|
156
|
-
try {
|
|
157
|
-
return await this.writable.stat(path);
|
|
158
|
-
} catch {
|
|
159
|
-
if (this._deletedFiles.has(path)) {
|
|
160
|
-
throw ErrnoError.With('ENOENT', path, 'stat');
|
|
161
|
-
}
|
|
162
|
-
const oldStat = await this.readable.stat(path);
|
|
163
|
-
// Make the oldStat's mode writable.
|
|
164
|
-
oldStat.mode |= 0o222;
|
|
165
|
-
return oldStat;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
public statSync(path: string): Stats {
|
|
170
|
-
this.checkInitialized();
|
|
171
|
-
try {
|
|
172
|
-
return this.writable.statSync(path);
|
|
173
|
-
} catch {
|
|
174
|
-
if (this._deletedFiles.has(path)) {
|
|
175
|
-
throw ErrnoError.With('ENOENT', path, 'stat');
|
|
176
|
-
}
|
|
177
|
-
const oldStat = this.readable.statSync(path);
|
|
178
|
-
// Make the oldStat's mode writable.
|
|
179
|
-
oldStat.mode |= 0o222;
|
|
180
|
-
return oldStat;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
public async openFile(path: string, flag: string): Promise<File> {
|
|
185
|
-
if (await this.writable.exists(path)) {
|
|
186
|
-
return this.writable.openFile(path, flag);
|
|
187
|
-
}
|
|
188
|
-
// Create an OverlayFile.
|
|
189
|
-
const file = await this.readable.openFile(path, parseFlag('r'));
|
|
190
|
-
const stats = await file.stat();
|
|
191
|
-
const { buffer } = await file.read(new Uint8Array(stats.size));
|
|
192
|
-
return new PreloadFile(this, path, flag, stats, buffer);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
public openFileSync(path: string, flag: string): File {
|
|
196
|
-
if (this.writable.existsSync(path)) {
|
|
197
|
-
return this.writable.openFileSync(path, flag);
|
|
198
|
-
}
|
|
199
|
-
// Create an OverlayFile.
|
|
200
|
-
const file = this.readable.openFileSync(path, parseFlag('r'));
|
|
201
|
-
const stats = file.statSync();
|
|
202
|
-
const data = new Uint8Array(stats.size);
|
|
203
|
-
file.readSync(data);
|
|
204
|
-
return new PreloadFile(this, path, flag, stats, data);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
public async createFile(path: string, flag: string, mode: number): Promise<File> {
|
|
208
|
-
this.checkInitialized();
|
|
209
|
-
await this.writable.createFile(path, flag, mode);
|
|
210
|
-
return this.openFile(path, flag);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
public createFileSync(path: string, flag: string, mode: number): File {
|
|
214
|
-
this.checkInitialized();
|
|
215
|
-
this.writable.createFileSync(path, flag, mode);
|
|
216
|
-
return this.openFileSync(path, flag);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
public async link(srcpath: string, dstpath: string): Promise<void> {
|
|
220
|
-
this.checkInitialized();
|
|
221
|
-
await this.copyForWrite(srcpath);
|
|
222
|
-
await this.writable.link(srcpath, dstpath);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
public linkSync(srcpath: string, dstpath: string): void {
|
|
226
|
-
this.checkInitialized();
|
|
227
|
-
this.copyForWriteSync(srcpath);
|
|
228
|
-
this.writable.linkSync(srcpath, dstpath);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
public async unlink(path: string): Promise<void> {
|
|
232
|
-
this.checkInitialized();
|
|
233
|
-
this.checkPath(path);
|
|
234
|
-
if (!(await this.exists(path))) {
|
|
235
|
-
throw ErrnoError.With('ENOENT', path, 'unlink');
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (await this.writable.exists(path)) {
|
|
239
|
-
await this.writable.unlink(path);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// if it still exists add to the delete log
|
|
243
|
-
if (await this.exists(path)) {
|
|
244
|
-
await this.deletePath(path);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
public unlinkSync(path: string): void {
|
|
249
|
-
this.checkInitialized();
|
|
250
|
-
this.checkPath(path);
|
|
251
|
-
if (!this.existsSync(path)) {
|
|
252
|
-
throw ErrnoError.With('ENOENT', path, 'unlink');
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (this.writable.existsSync(path)) {
|
|
256
|
-
this.writable.unlinkSync(path);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// if it still exists add to the delete log
|
|
260
|
-
if (this.existsSync(path)) {
|
|
261
|
-
void this.deletePath(path);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
public async rmdir(path: string): Promise<void> {
|
|
266
|
-
this.checkInitialized();
|
|
267
|
-
if (!(await this.exists(path))) {
|
|
268
|
-
throw ErrnoError.With('ENOENT', path, 'rmdir');
|
|
269
|
-
}
|
|
270
|
-
if (await this.writable.exists(path)) {
|
|
271
|
-
await this.writable.rmdir(path);
|
|
272
|
-
}
|
|
273
|
-
if (!(await this.exists(path))) {
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
// Check if directory is empty.
|
|
277
|
-
if ((await this.readdir(path)).length) {
|
|
278
|
-
throw ErrnoError.With('ENOTEMPTY', path, 'rmdir');
|
|
279
|
-
}
|
|
280
|
-
await this.deletePath(path);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
public rmdirSync(path: string): void {
|
|
284
|
-
this.checkInitialized();
|
|
285
|
-
if (!this.existsSync(path)) {
|
|
286
|
-
throw ErrnoError.With('ENOENT', path, 'rmdir');
|
|
287
|
-
}
|
|
288
|
-
if (this.writable.existsSync(path)) {
|
|
289
|
-
this.writable.rmdirSync(path);
|
|
290
|
-
}
|
|
291
|
-
if (!this.existsSync(path)) {
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
// Check if directory is empty.
|
|
295
|
-
if (this.readdirSync(path).length) {
|
|
296
|
-
throw ErrnoError.With('ENOTEMPTY', path, 'rmdir');
|
|
297
|
-
}
|
|
298
|
-
void this.deletePath(path);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
public async mkdir(path: string, mode: number): Promise<void> {
|
|
302
|
-
this.checkInitialized();
|
|
303
|
-
if (await this.exists(path)) {
|
|
304
|
-
throw ErrnoError.With('EEXIST', path, 'mkdir');
|
|
305
|
-
}
|
|
306
|
-
// The below will throw should any of the parent directories fail to exist on _writable.
|
|
307
|
-
await this.createParentDirectories(path);
|
|
308
|
-
await this.writable.mkdir(path, mode);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
public mkdirSync(path: string, mode: number): void {
|
|
312
|
-
this.checkInitialized();
|
|
313
|
-
if (this.existsSync(path)) {
|
|
314
|
-
throw ErrnoError.With('EEXIST', path, 'mkdir');
|
|
315
|
-
}
|
|
316
|
-
// The below will throw should any of the parent directories fail to exist on _writable.
|
|
317
|
-
this.createParentDirectoriesSync(path);
|
|
318
|
-
this.writable.mkdirSync(path, mode);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
public async readdir(path: string): Promise<string[]> {
|
|
322
|
-
this.checkInitialized();
|
|
323
|
-
|
|
324
|
-
// Readdir in both, check delete log on RO file system's listing, merge, return.
|
|
325
|
-
const contents: string[] = [];
|
|
326
|
-
try {
|
|
327
|
-
contents.push(...(await this.writable.readdir(path)));
|
|
328
|
-
} catch {
|
|
329
|
-
// NOP.
|
|
330
|
-
}
|
|
331
|
-
try {
|
|
332
|
-
contents.push(...(await this.readable.readdir(path)).filter((fPath: string) => !this._deletedFiles.has(`${path}/${fPath}`)));
|
|
333
|
-
} catch {
|
|
334
|
-
// NOP.
|
|
335
|
-
}
|
|
336
|
-
const seenMap: { [name: string]: boolean } = {};
|
|
337
|
-
return contents.filter((path: string) => {
|
|
338
|
-
const result = !seenMap[path];
|
|
339
|
-
seenMap[path] = true;
|
|
340
|
-
return result;
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
public readdirSync(path: string): string[] {
|
|
345
|
-
this.checkInitialized();
|
|
346
|
-
|
|
347
|
-
// Readdir in both, check delete log on RO file system's listing, merge, return.
|
|
348
|
-
let contents: string[] = [];
|
|
349
|
-
try {
|
|
350
|
-
contents = contents.concat(this.writable.readdirSync(path));
|
|
351
|
-
} catch {
|
|
352
|
-
// NOP.
|
|
353
|
-
}
|
|
354
|
-
try {
|
|
355
|
-
contents = contents.concat(this.readable.readdirSync(path).filter((fPath: string) => !this._deletedFiles.has(`${path}/${fPath}`)));
|
|
356
|
-
} catch {
|
|
357
|
-
// NOP.
|
|
358
|
-
}
|
|
359
|
-
const seenMap: { [name: string]: boolean } = {};
|
|
360
|
-
return contents.filter((path: string) => {
|
|
361
|
-
const result = !seenMap[path];
|
|
362
|
-
seenMap[path] = true;
|
|
363
|
-
return result;
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
private async deletePath(path: string): Promise<void> {
|
|
368
|
-
this._deletedFiles.add(path);
|
|
369
|
-
await this.updateLog(`d${path}\n`);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
private async updateLog(addition: string) {
|
|
373
|
-
this._deleteLog += addition;
|
|
374
|
-
if (this._deleteLogUpdatePending) {
|
|
375
|
-
this._deleteLogUpdateNeeded = true;
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
this._deleteLogUpdatePending = true;
|
|
379
|
-
const log = await this.writable.openFile(deletionLogPath, parseFlag('w'));
|
|
380
|
-
try {
|
|
381
|
-
await log.write(encodeUTF8(this._deleteLog));
|
|
382
|
-
if (this._deleteLogUpdateNeeded) {
|
|
383
|
-
this._deleteLogUpdateNeeded = false;
|
|
384
|
-
await this.updateLog('');
|
|
385
|
-
}
|
|
386
|
-
} catch (e) {
|
|
387
|
-
this._deleteLogError = e as ErrnoError;
|
|
388
|
-
} finally {
|
|
389
|
-
this._deleteLogUpdatePending = false;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
private _reparseDeletionLog(): void {
|
|
394
|
-
this._deletedFiles.clear();
|
|
395
|
-
for (const entry of this._deleteLog.split('\n')) {
|
|
396
|
-
if (!entry.startsWith('d')) {
|
|
397
|
-
continue;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// If the log entry begins w/ 'd', it's a deletion.
|
|
401
|
-
|
|
402
|
-
this._deletedFiles.add(entry.slice(1));
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
private checkInitialized(): void {
|
|
407
|
-
if (!this._isInitialized) {
|
|
408
|
-
throw new ErrnoError(Errno.EPERM, 'OverlayFS is not initialized. Please initialize OverlayFS using its initialize() method before using it.');
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
if (!this._deleteLogError) {
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
const error = this._deleteLogError;
|
|
416
|
-
delete this._deleteLogError;
|
|
417
|
-
throw error;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
private checkPath(path: string): void {
|
|
421
|
-
if (path == deletionLogPath) {
|
|
422
|
-
throw ErrnoError.With('EPERM', path, 'checkPath');
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
/**
|
|
427
|
-
* Create the needed parent directories on the writable storage should they not exist.
|
|
428
|
-
* Use modes from the read-only storage.
|
|
429
|
-
*/
|
|
430
|
-
private createParentDirectoriesSync(path: string): void {
|
|
431
|
-
let parent = dirname(path);
|
|
432
|
-
const toCreate: string[] = [];
|
|
433
|
-
while (!this.writable.existsSync(parent)) {
|
|
434
|
-
toCreate.push(parent);
|
|
435
|
-
parent = dirname(parent);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
for (const path of toCreate.reverse()) {
|
|
439
|
-
this.writable.mkdirSync(path, this.statSync(path).mode);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* Create the needed parent directories on the writable storage should they not exist.
|
|
445
|
-
* Use modes from the read-only storage.
|
|
446
|
-
*/
|
|
447
|
-
private async createParentDirectories(path: string): Promise<void> {
|
|
448
|
-
let parent = dirname(path);
|
|
449
|
-
const toCreate: string[] = [];
|
|
450
|
-
while (!(await this.writable.exists(parent))) {
|
|
451
|
-
toCreate.push(parent);
|
|
452
|
-
parent = dirname(parent);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
for (const path of toCreate.reverse()) {
|
|
456
|
-
const stats = await this.stat(path);
|
|
457
|
-
await this.writable.mkdir(path, stats.mode);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
/**
|
|
462
|
-
* Helper function:
|
|
463
|
-
* - Ensures p is on writable before proceeding. Throws an error if it doesn't exist.
|
|
464
|
-
* - Calls f to perform operation on writable.
|
|
465
|
-
*/
|
|
466
|
-
private copyForWriteSync(path: string): void {
|
|
467
|
-
if (!this.existsSync(path)) {
|
|
468
|
-
throw ErrnoError.With('ENOENT', path, 'copyForWrite');
|
|
469
|
-
}
|
|
470
|
-
if (!this.writable.existsSync(dirname(path))) {
|
|
471
|
-
this.createParentDirectoriesSync(path);
|
|
472
|
-
}
|
|
473
|
-
if (!this.writable.existsSync(path)) {
|
|
474
|
-
this.copyToWritableSync(path);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
private async copyForWrite(path: string): Promise<void> {
|
|
479
|
-
if (!(await this.exists(path))) {
|
|
480
|
-
throw ErrnoError.With('ENOENT', path, 'copyForWrite');
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
if (!(await this.writable.exists(dirname(path)))) {
|
|
484
|
-
await this.createParentDirectories(path);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
if (!(await this.writable.exists(path))) {
|
|
488
|
-
return this.copyToWritable(path);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
/**
|
|
493
|
-
* Copy from readable to writable storage.
|
|
494
|
-
* PRECONDITION: File does not exist on writable storage.
|
|
495
|
-
*/
|
|
496
|
-
private copyToWritableSync(path: string): void {
|
|
497
|
-
const stats = this.statSync(path);
|
|
498
|
-
if (stats.isDirectory()) {
|
|
499
|
-
this.writable.mkdirSync(path, stats.mode);
|
|
500
|
-
return;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
const data = new Uint8Array(stats.size);
|
|
504
|
-
using readable = this.readable.openFileSync(path, 'r');
|
|
505
|
-
readable.readSync(data);
|
|
506
|
-
using writable = this.writable.createFileSync(path, 'w', stats.mode | 0o222);
|
|
507
|
-
writable.writeSync(data);
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
private async copyToWritable(path: string): Promise<void> {
|
|
511
|
-
const stats = await this.stat(path);
|
|
512
|
-
if (stats.isDirectory()) {
|
|
513
|
-
await this.writable.mkdir(path, stats.mode);
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
const data = new Uint8Array(stats.size);
|
|
518
|
-
await using readable = await this.readable.openFile(path, 'r');
|
|
519
|
-
await readable.read(data);
|
|
520
|
-
await using writable = await this.writable.createFile(path, 'w', stats.mode | 0o222);
|
|
521
|
-
await writable.write(data);
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
/**
|
|
526
|
-
* OverlayFS makes a read-only filesystem writable by storing writes on a second,
|
|
527
|
-
* writable file system. Deletes are persisted via metadata stored on the writable
|
|
528
|
-
* file system.
|
|
529
|
-
* @internal
|
|
530
|
-
*/
|
|
531
|
-
export class OverlayFS extends Mutexed(UnmutexedOverlayFS) {}
|
|
532
|
-
|
|
533
|
-
const _Overlay = {
|
|
534
|
-
name: 'Overlay',
|
|
535
|
-
|
|
536
|
-
options: {
|
|
537
|
-
writable: {
|
|
538
|
-
type: 'object',
|
|
539
|
-
required: true,
|
|
540
|
-
description: 'The file system to write modified files to.',
|
|
541
|
-
},
|
|
542
|
-
readable: {
|
|
543
|
-
type: 'object',
|
|
544
|
-
required: true,
|
|
545
|
-
description: 'The file system that initially populates this file system.',
|
|
546
|
-
},
|
|
547
|
-
},
|
|
548
|
-
|
|
549
|
-
isAvailable(): boolean {
|
|
550
|
-
return true;
|
|
551
|
-
},
|
|
552
|
-
|
|
553
|
-
create(options: OverlayOptions) {
|
|
554
|
-
return new OverlayFS(options);
|
|
555
|
-
},
|
|
556
|
-
} as const satisfies Backend<OverlayFS, OverlayOptions>;
|
|
557
|
-
type _Overlay = typeof _Overlay;
|
|
558
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
559
|
-
export interface Overlay extends _Overlay {}
|
|
560
|
-
export const Overlay: Overlay = _Overlay;
|