@zenfs/core 0.0.1

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