@zenfs/core 0.1.0 → 0.2.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.
Files changed (44) hide show
  1. package/dist/ApiError.d.ts +51 -14
  2. package/dist/ApiError.js +60 -34
  3. package/dist/FileIndex.d.ts +32 -35
  4. package/dist/FileIndex.js +93 -109
  5. package/dist/backends/AsyncMirror.d.ts +42 -43
  6. package/dist/backends/AsyncMirror.js +146 -133
  7. package/dist/backends/AsyncStore.d.ts +29 -28
  8. package/dist/backends/AsyncStore.js +139 -189
  9. package/dist/backends/InMemory.d.ts +16 -13
  10. package/dist/backends/InMemory.js +29 -14
  11. package/dist/backends/Locked.d.ts +8 -28
  12. package/dist/backends/Locked.js +44 -148
  13. package/dist/backends/OverlayFS.d.ts +26 -34
  14. package/dist/backends/OverlayFS.js +208 -371
  15. package/dist/backends/SyncStore.d.ts +54 -72
  16. package/dist/backends/SyncStore.js +159 -161
  17. package/dist/backends/backend.d.ts +45 -29
  18. package/dist/backends/backend.js +83 -13
  19. package/dist/backends/index.d.ts +6 -7
  20. package/dist/backends/index.js +5 -6
  21. package/dist/browser.min.js +5 -7
  22. package/dist/browser.min.js.map +4 -4
  23. package/dist/emulation/callbacks.d.ts +36 -67
  24. package/dist/emulation/callbacks.js +90 -46
  25. package/dist/emulation/constants.js +1 -1
  26. package/dist/emulation/promises.d.ts +228 -129
  27. package/dist/emulation/promises.js +414 -172
  28. package/dist/emulation/shared.d.ts +10 -10
  29. package/dist/emulation/shared.js +18 -20
  30. package/dist/emulation/sync.d.ts +25 -25
  31. package/dist/emulation/sync.js +187 -73
  32. package/dist/file.d.ts +166 -170
  33. package/dist/file.js +199 -218
  34. package/dist/filesystem.d.ts +68 -241
  35. package/dist/filesystem.js +59 -383
  36. package/dist/index.d.ts +7 -44
  37. package/dist/index.js +13 -52
  38. package/dist/inode.d.ts +37 -28
  39. package/dist/inode.js +123 -65
  40. package/dist/stats.d.ts +21 -19
  41. package/dist/stats.js +35 -56
  42. package/dist/utils.d.ts +26 -9
  43. package/dist/utils.js +73 -102
  44. package/package.json +4 -3
@@ -1,30 +1,15 @@
1
- var _a;
2
- import { BaseFileSystem } from '../filesystem.js';
1
+ import { FileSystem } from '../filesystem.js';
3
2
  import { ApiError, ErrorCode } from '../ApiError.js';
4
- import { FileFlag, ActionType, PreloadFile } from '../file.js';
3
+ import { FileFlag, PreloadFile } from '../file.js';
5
4
  import { Stats } from '../stats.js';
6
5
  import LockedFS from './Locked.js';
7
- import { resolve, dirname } from '../emulation/path.js';
6
+ import { dirname } from '../emulation/path.js';
8
7
  import { Cred } from '../cred.js';
9
- import { CreateBackend } from './backend.js';
10
8
  import { decode, encode } from '../utils.js';
11
9
  /**
12
10
  * @internal
13
11
  */
14
- const deletionLogPath = '/.deletedFiles.log';
15
- /**
16
- * Given a read-only mode, makes it writable.
17
- * @internal
18
- */
19
- function makeModeWritable(mode) {
20
- return 0o222 | mode;
21
- }
22
- /**
23
- * @internal
24
- */
25
- function getFlag(f) {
26
- return FileFlag.getFileFlag(f);
27
- }
12
+ const deletionLogPath = '/.deleted';
28
13
  /**
29
14
  * Overlays a RO file to make it writable.
30
15
  */
@@ -36,12 +21,12 @@ class OverlayFile extends PreloadFile {
36
21
  if (!this.isDirty()) {
37
22
  return;
38
23
  }
39
- await this._fs._syncAsync(this);
24
+ await this.fs.sync(this.path, this.buffer, this.stats);
40
25
  this.resetDirty();
41
26
  }
42
27
  syncSync() {
43
28
  if (this.isDirty()) {
44
- this._fs._syncSync(this);
29
+ this.fs.syncSync(this.path, this.buffer, this.stats);
45
30
  this.resetDirty();
46
31
  }
47
32
  }
@@ -53,37 +38,41 @@ class OverlayFile extends PreloadFile {
53
38
  }
54
39
  }
55
40
  /**
56
- * *INTERNAL, DO NOT USE DIRECTLY!*
41
+ * OverlayFS makes a read-only filesystem writable by storing writes on a second, writable file system.
42
+ * Deletes are persisted via metadata stored on the writable file system.
57
43
  *
58
- * Core OverlayFS class that contains no locking whatsoever. We wrap these objects
59
- * in a LockedFS to prevent races.
44
+ * This class contains no locking whatsoever. It is wrapped in a LockedFS to prevent races.
45
+ *
46
+ * @internal
60
47
  */
61
- export class UnlockedOverlayFS extends BaseFileSystem {
62
- static isAvailable() {
63
- return true;
48
+ export class UnlockedOverlayFS extends FileSystem {
49
+ async ready() {
50
+ await this._readable.ready();
51
+ await this._writable.ready();
52
+ await this._ready;
53
+ return this;
64
54
  }
65
55
  constructor({ writable, readable }) {
66
56
  super();
67
57
  this._isInitialized = false;
68
- this._deletedFiles = {};
58
+ this._deletedFiles = new Set();
69
59
  this._deleteLog = '';
70
60
  // If 'true', we have scheduled a delete log update.
71
61
  this._deleteLogUpdatePending = false;
72
62
  // If 'true', a delete log update is needed after the scheduled delete log
73
63
  // update finishes.
74
64
  this._deleteLogUpdateNeeded = false;
75
- // If there was an error updating the delete log...
76
- this._deleteLogError = null;
77
65
  this._writable = writable;
78
66
  this._readable = readable;
79
67
  if (this._writable.metadata.readonly) {
80
68
  throw new ApiError(ErrorCode.EINVAL, 'Writable file system must be writable.');
81
69
  }
70
+ this._ready = this._initialize();
82
71
  }
83
72
  get metadata() {
84
73
  return {
85
74
  ...super.metadata,
86
- name: OverlayFS.Name,
75
+ name: OverlayFS.name,
87
76
  synchronous: this._readable.metadata.synchronous && this._writable.metadata.synchronous,
88
77
  supportsProperties: this._readable.metadata.supportsProperties && this._writable.metadata.supportsProperties,
89
78
  };
@@ -94,30 +83,30 @@ export class UnlockedOverlayFS extends BaseFileSystem {
94
83
  writable: this._writable,
95
84
  };
96
85
  }
97
- async _syncAsync(file) {
98
- const stats = file.getStats();
99
- await this.createParentDirectoriesAsync(file.getPath(), stats.getCred(0, 0));
100
- return this._writable.writeFile(file.getPath(), file.getBuffer(), getFlag('w'), stats.mode, stats.getCred(0, 0));
86
+ async sync(path, data, stats) {
87
+ const cred = stats.getCred(0, 0);
88
+ await this.createParentDirectories(path, cred);
89
+ await this._writable.sync(path, data, stats);
101
90
  }
102
- _syncSync(file) {
103
- const stats = file.getStats();
104
- this.createParentDirectories(file.getPath(), stats.getCred(0, 0));
105
- this._writable.writeFileSync(file.getPath(), file.getBuffer(), getFlag('w'), stats.mode, stats.getCred(0, 0));
91
+ syncSync(path, data, stats) {
92
+ const cred = stats.getCred(0, 0);
93
+ this.createParentDirectoriesSync(path, cred);
94
+ this._writable.syncSync(path, data, stats);
106
95
  }
107
96
  /**
108
- * **INTERNAL METHOD**
109
- *
110
97
  * Called once to load up metadata stored on the writable file system.
98
+ * @internal
111
99
  */
112
100
  async _initialize() {
113
- // if we're already initialized, immediately invoke the callback
114
101
  if (this._isInitialized) {
115
102
  return;
116
103
  }
117
104
  // Read deletion log, process into metadata.
118
105
  try {
119
- const data = await this._writable.readFile(deletionLogPath, getFlag('r'), Cred.Root);
120
- this._deleteLog = decode(data);
106
+ const file = await this._writable.openFile(deletionLogPath, FileFlag.FromString('r'), Cred.Root);
107
+ const { size } = await file.stat();
108
+ const { buffer } = await file.read(new Uint8Array(size));
109
+ this._deleteLog = decode(buffer);
121
110
  }
122
111
  catch (err) {
123
112
  if (err.errno !== ErrorCode.ENOENT) {
@@ -139,108 +128,26 @@ export class UnlockedOverlayFS extends BaseFileSystem {
139
128
  this.checkInitialized();
140
129
  this.checkPath(oldPath);
141
130
  this.checkPath(newPath);
142
- if (oldPath === deletionLogPath || newPath === deletionLogPath) {
143
- throw ApiError.EPERM('Cannot rename deletion log.');
144
- }
145
- // Write newPath using oldPath's contents, delete oldPath.
146
- const oldStats = await this.stat(oldPath, cred);
147
- if (oldStats.isDirectory()) {
148
- // Optimization: Don't bother moving if old === new.
149
- if (oldPath === newPath) {
150
- return;
151
- }
152
- let mode = 0o777;
153
- if (await this.exists(newPath, cred)) {
154
- const stats = await this.stat(newPath, cred);
155
- mode = stats.mode;
156
- if (stats.isDirectory()) {
157
- if ((await this.readdir(newPath, cred)).length > 0) {
158
- throw ApiError.ENOTEMPTY(newPath);
159
- }
160
- }
161
- else {
162
- throw ApiError.ENOTDIR(newPath);
163
- }
164
- }
165
- // Take care of writable first. Move any files there, or create an empty directory
166
- // if it doesn't exist.
167
- if (await this._writable.exists(oldPath, cred)) {
168
- await this._writable.rename(oldPath, newPath, cred);
169
- }
170
- else if (!(await this._writable.exists(newPath, cred))) {
171
- await this._writable.mkdir(newPath, mode, cred);
172
- }
173
- // Need to move *every file/folder* currently stored on readable to its new location
174
- // on writable.
175
- if (await this._readable.exists(oldPath, cred)) {
176
- for (const name of await this._readable.readdir(oldPath, cred)) {
177
- // Recursion! Should work for any nested files / folders.
178
- await this.rename(resolve(oldPath, name), resolve(newPath, name), cred);
179
- }
180
- }
131
+ try {
132
+ await this._writable.rename(oldPath, newPath, cred);
181
133
  }
182
- else {
183
- if ((await this.exists(newPath, cred)) && (await this.stat(newPath, cred)).isDirectory()) {
184
- throw ApiError.EISDIR(newPath);
134
+ catch (e) {
135
+ if (this._deletedFiles.has(oldPath)) {
136
+ throw ApiError.ENOENT(oldPath);
185
137
  }
186
- await this.writeFile(newPath, await this.readFile(oldPath, getFlag('r'), cred), getFlag('w'), oldStats.mode, cred);
187
- }
188
- if (oldPath !== newPath && (await this.exists(oldPath, cred))) {
189
- await this.unlink(oldPath, cred);
190
138
  }
191
139
  }
192
140
  renameSync(oldPath, newPath, cred) {
193
141
  this.checkInitialized();
194
142
  this.checkPath(oldPath);
195
143
  this.checkPath(newPath);
196
- if (oldPath === deletionLogPath || newPath === deletionLogPath) {
197
- throw ApiError.EPERM('Cannot rename deletion log.');
198
- }
199
- // Write newPath using oldPath's contents, delete oldPath.
200
- const oldStats = this.statSync(oldPath, cred);
201
- if (oldStats.isDirectory()) {
202
- // Optimization: Don't bother moving if old === new.
203
- if (oldPath === newPath) {
204
- return;
205
- }
206
- let mode = 0o777;
207
- if (this.existsSync(newPath, cred)) {
208
- const stats = this.statSync(newPath, cred);
209
- mode = stats.mode;
210
- if (stats.isDirectory()) {
211
- if (this.readdirSync(newPath, cred).length > 0) {
212
- throw ApiError.ENOTEMPTY(newPath);
213
- }
214
- }
215
- else {
216
- throw ApiError.ENOTDIR(newPath);
217
- }
218
- }
219
- // Take care of writable first. Move any files there, or create an empty directory
220
- // if it doesn't exist.
221
- if (this._writable.existsSync(oldPath, cred)) {
222
- this._writable.renameSync(oldPath, newPath, cred);
223
- }
224
- else if (!this._writable.existsSync(newPath, cred)) {
225
- this._writable.mkdirSync(newPath, mode, cred);
226
- }
227
- // Need to move *every file/folder* currently stored on readable to its new location
228
- // on writable.
229
- if (this._readable.existsSync(oldPath, cred)) {
230
- this._readable.readdirSync(oldPath, cred).forEach(name => {
231
- // Recursion! Should work for any nested files / folders.
232
- this.renameSync(resolve(oldPath, name), resolve(newPath, name), cred);
233
- });
234
- }
144
+ try {
145
+ this._writable.renameSync(oldPath, newPath, cred);
235
146
  }
236
- else {
237
- if (this.existsSync(newPath, cred) && this.statSync(newPath, cred).isDirectory()) {
238
- throw ApiError.EISDIR(newPath);
147
+ catch (e) {
148
+ if (this._deletedFiles.has(oldPath)) {
149
+ throw ApiError.ENOENT(oldPath);
239
150
  }
240
- this.writeFileSync(newPath, this.readFileSync(oldPath, getFlag('r'), cred), getFlag('w'), oldStats.mode, cred);
241
- }
242
- if (oldPath !== newPath && this.existsSync(oldPath, cred)) {
243
- this.unlinkSync(oldPath, cred);
244
151
  }
245
152
  }
246
153
  async stat(p, cred) {
@@ -249,13 +156,13 @@ export class UnlockedOverlayFS extends BaseFileSystem {
249
156
  return this._writable.stat(p, cred);
250
157
  }
251
158
  catch (e) {
252
- if (this._deletedFiles[p]) {
159
+ if (this._deletedFiles.has(p)) {
253
160
  throw ApiError.ENOENT(p);
254
161
  }
255
162
  const oldStat = Stats.clone(await this._readable.stat(p, cred));
256
163
  // Make the oldStat's mode writable. Preserve the topmost part of the
257
164
  // mode, which specifies if it is a file or a directory.
258
- oldStat.mode = makeModeWritable(oldStat.mode);
165
+ oldStat.mode |= 0o222;
259
166
  return oldStat;
260
167
  }
261
168
  }
@@ -265,183 +172,136 @@ export class UnlockedOverlayFS extends BaseFileSystem {
265
172
  return this._writable.statSync(p, cred);
266
173
  }
267
174
  catch (e) {
268
- if (this._deletedFiles[p]) {
175
+ if (this._deletedFiles.has(p)) {
269
176
  throw ApiError.ENOENT(p);
270
177
  }
271
178
  const oldStat = Stats.clone(this._readable.statSync(p, cred));
272
179
  // Make the oldStat's mode writable. Preserve the topmost part of the
273
180
  // mode, which specifies if it is a file or a directory.
274
- oldStat.mode = makeModeWritable(oldStat.mode);
181
+ oldStat.mode |= 0o222;
275
182
  return oldStat;
276
183
  }
277
184
  }
278
- async open(p, flag, mode, cred) {
279
- this.checkInitialized();
280
- this.checkPath(p);
281
- if (p === deletionLogPath) {
282
- throw ApiError.EPERM('Cannot open deletion log.');
283
- }
284
- if (await this.exists(p, cred)) {
285
- switch (flag.pathExistsAction()) {
286
- case ActionType.TRUNCATE_FILE:
287
- await this.createParentDirectoriesAsync(p, cred);
288
- return this._writable.open(p, flag, mode, cred);
289
- case ActionType.NOP:
290
- if (await this._writable.exists(p, cred)) {
291
- return this._writable.open(p, flag, mode, cred);
292
- }
293
- else {
294
- // Create an OverlayFile.
295
- const buf = await this._readable.readFile(p, getFlag('r'), cred);
296
- const stats = Stats.clone(await this._readable.stat(p, cred));
297
- stats.mode = mode;
298
- return new OverlayFile(this, p, flag, stats, buf);
299
- }
300
- default:
301
- throw ApiError.EEXIST(p);
302
- }
185
+ async openFile(path, flag, cred) {
186
+ if (await this._writable.exists(path, cred)) {
187
+ return this._writable.openFile(path, flag, cred);
303
188
  }
304
- else {
305
- switch (flag.pathNotExistsAction()) {
306
- case ActionType.CREATE_FILE:
307
- await this.createParentDirectoriesAsync(p, cred);
308
- return this._writable.open(p, flag, mode, cred);
309
- default:
310
- throw ApiError.ENOENT(p);
311
- }
189
+ // Create an OverlayFile.
190
+ const file = await this._readable.openFile(path, FileFlag.FromString('r'), cred);
191
+ const stats = Stats.clone(await file.stat());
192
+ const { buffer } = await file.read(new Uint8Array(stats.size));
193
+ return new OverlayFile(this, path, flag, stats, buffer);
194
+ }
195
+ openFileSync(path, flag, cred) {
196
+ if (this._writable.existsSync(path, cred)) {
197
+ return this._writable.openFileSync(path, flag, cred);
312
198
  }
199
+ // Create an OverlayFile.
200
+ const file = this._readable.openFileSync(path, FileFlag.FromString('r'), cred);
201
+ const stats = Stats.clone(file.statSync());
202
+ const data = new Uint8Array(stats.size);
203
+ file.readSync(data);
204
+ return new OverlayFile(this, path, flag, stats, data);
313
205
  }
314
- openSync(p, flag, mode, cred) {
206
+ async createFile(path, flag, mode, cred) {
315
207
  this.checkInitialized();
316
- this.checkPath(p);
317
- if (p === deletionLogPath) {
318
- throw ApiError.EPERM('Cannot open deletion log.');
319
- }
320
- if (this.existsSync(p, cred)) {
321
- switch (flag.pathExistsAction()) {
322
- case ActionType.TRUNCATE_FILE:
323
- this.createParentDirectories(p, cred);
324
- return this._writable.openSync(p, flag, mode, cred);
325
- case ActionType.NOP:
326
- if (this._writable.existsSync(p, cred)) {
327
- return this._writable.openSync(p, flag, mode, cred);
328
- }
329
- else {
330
- // Create an OverlayFile.
331
- const buf = this._readable.readFileSync(p, getFlag('r'), cred);
332
- const stats = Stats.clone(this._readable.statSync(p, cred));
333
- stats.mode = mode;
334
- return new OverlayFile(this, p, flag, stats, buf);
335
- }
336
- default:
337
- throw ApiError.EEXIST(p);
338
- }
339
- }
340
- else {
341
- switch (flag.pathNotExistsAction()) {
342
- case ActionType.CREATE_FILE:
343
- this.createParentDirectories(p, cred);
344
- return this._writable.openSync(p, flag, mode, cred);
345
- default:
346
- throw ApiError.ENOENT(p);
347
- }
348
- }
208
+ await this._writable.createFile(path, flag, mode, cred);
209
+ return this.openFile(path, flag, cred);
210
+ }
211
+ createFileSync(path, flag, mode, cred) {
212
+ this.checkInitialized();
213
+ this._writable.createFileSync(path, flag, mode, cred);
214
+ return this.openFileSync(path, flag, cred);
215
+ }
216
+ async link(srcpath, dstpath, cred) {
217
+ this.checkInitialized();
218
+ await this._writable.link(srcpath, dstpath, cred);
219
+ }
220
+ linkSync(srcpath, dstpath, cred) {
221
+ this.checkInitialized();
222
+ this._writable.linkSync(srcpath, dstpath, cred);
349
223
  }
350
224
  async unlink(p, cred) {
351
225
  this.checkInitialized();
352
226
  this.checkPath(p);
353
- if (await this.exists(p, cred)) {
354
- if (await this._writable.exists(p, cred)) {
355
- await this._writable.unlink(p, cred);
356
- }
357
- // if it still exists add to the delete log
358
- if (await this.exists(p, cred)) {
359
- this.deletePath(p, cred);
360
- }
361
- }
362
- else {
227
+ if (!(await this.exists(p, cred))) {
363
228
  throw ApiError.ENOENT(p);
364
229
  }
230
+ if (await this._writable.exists(p, cred)) {
231
+ await this._writable.unlink(p, cred);
232
+ }
233
+ // if it still exists add to the delete log
234
+ if (await this.exists(p, cred)) {
235
+ this.deletePath(p, cred);
236
+ }
365
237
  }
366
238
  unlinkSync(p, cred) {
367
239
  this.checkInitialized();
368
240
  this.checkPath(p);
369
- if (this.existsSync(p, cred)) {
370
- if (this._writable.existsSync(p, cred)) {
371
- this._writable.unlinkSync(p, cred);
372
- }
373
- // if it still exists add to the delete log
374
- if (this.existsSync(p, cred)) {
375
- this.deletePath(p, cred);
376
- }
377
- }
378
- else {
241
+ if (!this.existsSync(p, cred)) {
379
242
  throw ApiError.ENOENT(p);
380
243
  }
244
+ if (this._writable.existsSync(p, cred)) {
245
+ this._writable.unlinkSync(p, cred);
246
+ }
247
+ // if it still exists add to the delete log
248
+ if (this.existsSync(p, cred)) {
249
+ this.deletePath(p, cred);
250
+ }
381
251
  }
382
252
  async rmdir(p, cred) {
383
253
  this.checkInitialized();
254
+ if (!(await this.exists(p, cred))) {
255
+ throw ApiError.ENOENT(p);
256
+ }
257
+ if (await this._writable.exists(p, cred)) {
258
+ await this._writable.rmdir(p, cred);
259
+ }
384
260
  if (await this.exists(p, cred)) {
385
- if (await this._writable.exists(p, cred)) {
386
- await this._writable.rmdir(p, cred);
261
+ // Check if directory is empty.
262
+ if ((await this.readdir(p, cred)).length > 0) {
263
+ throw ApiError.ENOTEMPTY(p);
387
264
  }
388
- if (await this.exists(p, cred)) {
389
- // Check if directory is empty.
390
- if ((await this.readdir(p, cred)).length > 0) {
391
- throw ApiError.ENOTEMPTY(p);
392
- }
393
- else {
394
- this.deletePath(p, cred);
395
- }
265
+ else {
266
+ this.deletePath(p, cred);
396
267
  }
397
268
  }
398
- else {
399
- throw ApiError.ENOENT(p);
400
- }
401
269
  }
402
270
  rmdirSync(p, cred) {
403
271
  this.checkInitialized();
272
+ if (!this.existsSync(p, cred)) {
273
+ throw ApiError.ENOENT(p);
274
+ }
275
+ if (this._writable.existsSync(p, cred)) {
276
+ this._writable.rmdirSync(p, cred);
277
+ }
404
278
  if (this.existsSync(p, cred)) {
405
- if (this._writable.existsSync(p, cred)) {
406
- this._writable.rmdirSync(p, cred);
279
+ // Check if directory is empty.
280
+ if (this.readdirSync(p, cred).length > 0) {
281
+ throw ApiError.ENOTEMPTY(p);
407
282
  }
408
- if (this.existsSync(p, cred)) {
409
- // Check if directory is empty.
410
- if (this.readdirSync(p, cred).length > 0) {
411
- throw ApiError.ENOTEMPTY(p);
412
- }
413
- else {
414
- this.deletePath(p, cred);
415
- }
283
+ else {
284
+ this.deletePath(p, cred);
416
285
  }
417
286
  }
418
- else {
419
- throw ApiError.ENOENT(p);
420
- }
421
287
  }
422
288
  async mkdir(p, mode, cred) {
423
289
  this.checkInitialized();
424
290
  if (await this.exists(p, cred)) {
425
291
  throw ApiError.EEXIST(p);
426
292
  }
427
- else {
428
- // The below will throw should any of the parent directories fail to exist
429
- // on _writable.
430
- await this.createParentDirectoriesAsync(p, cred);
431
- await this._writable.mkdir(p, mode, cred);
432
- }
293
+ // The below will throw should any of the parent directories fail to exist on _writable.
294
+ await this.createParentDirectories(p, cred);
295
+ await this._writable.mkdir(p, mode, cred);
433
296
  }
434
297
  mkdirSync(p, mode, cred) {
435
298
  this.checkInitialized();
436
299
  if (this.existsSync(p, cred)) {
437
300
  throw ApiError.EEXIST(p);
438
301
  }
439
- else {
440
- // The below will throw should any of the parent directories fail to exist
441
- // on _writable.
442
- this.createParentDirectories(p, cred);
443
- this._writable.mkdirSync(p, mode, cred);
444
- }
302
+ // The below will throw should any of the parent directories fail to exist on _writable.
303
+ this.createParentDirectoriesSync(p, cred);
304
+ this._writable.mkdirSync(p, mode, cred);
445
305
  }
446
306
  async readdir(p, cred) {
447
307
  this.checkInitialized();
@@ -452,13 +312,13 @@ export class UnlockedOverlayFS extends BaseFileSystem {
452
312
  // Readdir in both, check delete log on RO file system's listing, merge, return.
453
313
  let contents = [];
454
314
  try {
455
- contents = contents.concat(await this._writable.readdir(p, cred));
315
+ contents.push(...(await this._writable.readdir(p, cred)));
456
316
  }
457
317
  catch (e) {
458
318
  // NOP.
459
319
  }
460
320
  try {
461
- contents = contents.concat((await this._readable.readdir(p, cred)).filter((fPath) => !this._deletedFiles[`${p}/${fPath}`]));
321
+ contents.push(...(await this._readable.readdir(p, cred)).filter((fPath) => !this._deletedFiles.has(`${p}/${fPath}`)));
462
322
  }
463
323
  catch (e) {
464
324
  // NOP.
@@ -485,7 +345,7 @@ export class UnlockedOverlayFS extends BaseFileSystem {
485
345
  // NOP.
486
346
  }
487
347
  try {
488
- contents = contents.concat(this._readable.readdirSync(p, cred).filter((fPath) => !this._deletedFiles[`${p}/${fPath}`]));
348
+ contents = contents.concat(this._readable.readdirSync(p, cred).filter((fPath) => !this._deletedFiles.has(`${p}/${fPath}`)));
489
349
  }
490
350
  catch (e) {
491
351
  // NOP.
@@ -497,98 +357,63 @@ export class UnlockedOverlayFS extends BaseFileSystem {
497
357
  return result;
498
358
  });
499
359
  }
500
- async exists(p, cred) {
501
- this.checkInitialized();
502
- return (await this._writable.exists(p, cred)) || ((await this._readable.exists(p, cred)) && this._deletedFiles[p] !== true);
503
- }
504
- existsSync(p, cred) {
505
- this.checkInitialized();
506
- return this._writable.existsSync(p, cred) || (this._readable.existsSync(p, cred) && this._deletedFiles[p] !== true);
507
- }
508
- async chmod(p, mode, cred) {
509
- this.checkInitialized();
510
- await this.operateOnWritableAsync(p, cred);
511
- await this._writable.chmod(p, mode, cred);
512
- }
513
- chmodSync(p, mode, cred) {
514
- this.checkInitialized();
515
- this.operateOnWritable(p, cred);
516
- this._writable.chmodSync(p, mode, cred);
517
- }
518
- async chown(p, new_uid, new_gid, cred) {
519
- this.checkInitialized();
520
- await this.operateOnWritableAsync(p, cred);
521
- await this._writable.chown(p, new_uid, new_gid, cred);
522
- }
523
- chownSync(p, new_uid, new_gid, cred) {
524
- this.checkInitialized();
525
- this.operateOnWritable(p, cred);
526
- this._writable.chownSync(p, new_uid, new_gid, cred);
527
- }
528
- async utimes(p, atime, mtime, cred) {
529
- this.checkInitialized();
530
- await this.operateOnWritableAsync(p, cred);
531
- await this._writable.utimes(p, atime, mtime, cred);
532
- }
533
- utimesSync(p, atime, mtime, cred) {
534
- this.checkInitialized();
535
- this.operateOnWritable(p, cred);
536
- this._writable.utimesSync(p, atime, mtime, cred);
537
- }
538
360
  deletePath(p, cred) {
539
- this._deletedFiles[p] = true;
361
+ this._deletedFiles.add(p);
540
362
  this.updateLog(`d${p}\n`, cred);
541
363
  }
542
- updateLog(addition, cred) {
364
+ async updateLog(addition, cred) {
543
365
  this._deleteLog += addition;
544
366
  if (this._deleteLogUpdatePending) {
545
367
  this._deleteLogUpdateNeeded = true;
368
+ return;
546
369
  }
547
- else {
548
- this._deleteLogUpdatePending = true;
549
- this._writable
550
- .writeFile(deletionLogPath, encode(this._deleteLog), FileFlag.getFileFlag('w'), 0o644, cred)
551
- .then(() => {
552
- if (this._deleteLogUpdateNeeded) {
553
- this._deleteLogUpdateNeeded = false;
554
- this.updateLog('', cred);
555
- }
556
- })
557
- .catch(e => {
558
- this._deleteLogError = e;
559
- })
560
- .finally(() => {
561
- this._deleteLogUpdatePending = false;
562
- });
370
+ this._deleteLogUpdatePending = true;
371
+ const log = await this._writable.openFile(deletionLogPath, FileFlag.FromString('w'), cred);
372
+ try {
373
+ await log.write(encode(this._deleteLog));
374
+ if (this._deleteLogUpdateNeeded) {
375
+ this._deleteLogUpdateNeeded = false;
376
+ this.updateLog('', cred);
377
+ }
378
+ }
379
+ catch (e) {
380
+ this._deleteLogError = e;
381
+ }
382
+ finally {
383
+ this._deleteLogUpdatePending = false;
563
384
  }
564
385
  }
565
386
  _reparseDeletionLog() {
566
- this._deletedFiles = {};
567
- this._deleteLog.split('\n').forEach((path) => {
387
+ this._deletedFiles.clear();
388
+ for (const entry of this._deleteLog.split('\n')) {
389
+ if (!entry.startsWith('d')) {
390
+ continue;
391
+ }
568
392
  // If the log entry begins w/ 'd', it's a deletion.
569
- this._deletedFiles[path.slice(1)] = path.slice(0, 1) === 'd';
570
- });
393
+ this._deletedFiles.add(entry.slice(1));
394
+ }
571
395
  }
572
396
  checkInitialized() {
573
397
  if (!this._isInitialized) {
574
398
  throw new ApiError(ErrorCode.EPERM, 'OverlayFS is not initialized. Please initialize OverlayFS using its initialize() method before using it.');
575
399
  }
576
- else if (this._deleteLogError !== null) {
577
- const e = this._deleteLogError;
578
- this._deleteLogError = null;
579
- throw e;
400
+ if (!this._deleteLogError) {
401
+ return;
580
402
  }
403
+ const error = this._deleteLogError;
404
+ this._deleteLogError = null;
405
+ throw error;
581
406
  }
582
- checkPath(p) {
583
- if (p === deletionLogPath) {
584
- throw ApiError.EPERM(p);
407
+ checkPath(path) {
408
+ if (path == deletionLogPath) {
409
+ throw ApiError.EPERM(path);
585
410
  }
586
411
  }
587
412
  /**
588
413
  * With the given path, create the needed parent directories on the writable storage
589
414
  * should they not exist. Use modes from the read-only storage.
590
415
  */
591
- createParentDirectories(p, cred) {
416
+ createParentDirectoriesSync(p, cred) {
592
417
  let parent = dirname(p), toCreate = [];
593
418
  while (!this._writable.existsSync(parent, cred)) {
594
419
  toCreate.push(parent);
@@ -599,7 +424,7 @@ export class UnlockedOverlayFS extends BaseFileSystem {
599
424
  this._writable.mkdirSync(p, this.statSync(p, cred).mode, cred);
600
425
  }
601
426
  }
602
- async createParentDirectoriesAsync(p, cred) {
427
+ async createParentDirectories(p, cred) {
603
428
  let parent = dirname(p), toCreate = [];
604
429
  while (!(await this._writable.exists(parent, cred))) {
605
430
  toCreate.push(parent);
@@ -623,7 +448,7 @@ export class UnlockedOverlayFS extends BaseFileSystem {
623
448
  if (!this._writable.existsSync(p, cred)) {
624
449
  // File is on readable storage. Copy to writable storage before
625
450
  // changing its mode.
626
- this.copyToWritable(p, cred);
451
+ this.copyToWritableSync(p, cred);
627
452
  }
628
453
  }
629
454
  async operateOnWritableAsync(p, cred) {
@@ -631,30 +456,40 @@ export class UnlockedOverlayFS extends BaseFileSystem {
631
456
  throw ApiError.ENOENT(p);
632
457
  }
633
458
  if (!(await this._writable.exists(p, cred))) {
634
- return this.copyToWritableAsync(p, cred);
459
+ return this.copyToWritable(p, cred);
635
460
  }
636
461
  }
637
462
  /**
638
463
  * Copy from readable to writable storage.
639
464
  * PRECONDITION: File does not exist on writable storage.
640
465
  */
641
- copyToWritable(p, cred) {
642
- const pStats = this.statSync(p, cred);
643
- if (pStats.isDirectory()) {
644
- this._writable.mkdirSync(p, pStats.mode, cred);
645
- }
646
- else {
647
- this.writeFileSync(p, this._readable.readFileSync(p, getFlag('r'), cred), getFlag('w'), pStats.mode, cred);
648
- }
649
- }
650
- async copyToWritableAsync(p, cred) {
651
- const pStats = await this.stat(p, cred);
652
- if (pStats.isDirectory()) {
653
- await this._writable.mkdir(p, pStats.mode, cred);
466
+ copyToWritableSync(p, cred) {
467
+ const stats = this.statSync(p, cred);
468
+ if (stats.isDirectory()) {
469
+ this._writable.mkdirSync(p, stats.mode, cred);
470
+ return;
654
471
  }
655
- else {
656
- await this.writeFile(p, await this._readable.readFile(p, getFlag('r'), cred), getFlag('w'), pStats.mode, cred);
472
+ const data = new Uint8Array(stats.size);
473
+ const readable = this._readable.openFileSync(p, FileFlag.FromString('r'), cred);
474
+ readable.readSync(data);
475
+ readable.closeSync();
476
+ const writable = this._writable.openFileSync(p, FileFlag.FromString('w'), cred);
477
+ writable.writeSync(data);
478
+ writable.closeSync();
479
+ }
480
+ async copyToWritable(p, cred) {
481
+ const stats = await this.stat(p, cred);
482
+ if (stats.isDirectory()) {
483
+ await this._writable.mkdir(p, stats.mode, cred);
484
+ return;
657
485
  }
486
+ const data = new Uint8Array(stats.size);
487
+ const readable = await this._readable.openFile(p, FileFlag.FromString('r'), cred);
488
+ await readable.read(data);
489
+ await readable.close();
490
+ const writable = await this._writable.openFile(p, FileFlag.FromString('w'), cred);
491
+ await writable.write(data);
492
+ await writable.close();
658
493
  }
659
494
  }
660
495
  /**
@@ -663,15 +498,15 @@ export class UnlockedOverlayFS extends BaseFileSystem {
663
498
  * file system.
664
499
  */
665
500
  export class OverlayFS extends LockedFS {
666
- static isAvailable() {
667
- return UnlockedOverlayFS.isAvailable();
501
+ async ready() {
502
+ await super.ready();
503
+ return this;
668
504
  }
669
505
  /**
670
506
  * @param options The options to initialize the OverlayFS with
671
507
  */
672
508
  constructor(options) {
673
509
  super(new UnlockedOverlayFS(options));
674
- this._ready = this._initialize();
675
510
  }
676
511
  getOverlayedFileSystems() {
677
512
  return super.fs.getOverlayedFileSystems();
@@ -685,21 +520,23 @@ export class OverlayFS extends LockedFS {
685
520
  unwrap() {
686
521
  return super.fs;
687
522
  }
688
- async _initialize() {
689
- await super.fs._initialize();
690
- return this;
691
- }
692
523
  }
693
- _a = OverlayFS;
694
- OverlayFS.Name = 'OverlayFS';
695
- OverlayFS.Create = CreateBackend.bind(_a);
696
- OverlayFS.Options = {
697
- writable: {
698
- type: 'object',
699
- description: 'The file system to write modified files to.',
524
+ export const Overlay = {
525
+ name: 'Overlay',
526
+ options: {
527
+ writable: {
528
+ type: 'object',
529
+ description: 'The file system to write modified files to.',
530
+ },
531
+ readable: {
532
+ type: 'object',
533
+ description: 'The file system that initially populates this file system.',
534
+ },
535
+ },
536
+ isAvailable() {
537
+ return true;
700
538
  },
701
- readable: {
702
- type: 'object',
703
- description: 'The file system that initially populates this file system.',
539
+ create(options) {
540
+ return new OverlayFS(options);
704
541
  },
705
542
  };