@zenfs/core 1.10.1 → 1.10.3

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.
@@ -1,42 +1,75 @@
1
1
  import type { File } from '../internal/file.js';
2
2
  import type { CreationOptions, UsageInfo } from '../internal/filesystem.js';
3
- import type { Stats } from '../stats.js';
4
3
  import type { InodeLike } from '../internal/inode.js';
4
+ import type { Stats } from '../stats.js';
5
5
  import { FileSystem } from '../internal/filesystem.js';
6
+ import { EventEmitter } from 'eventemitter3';
6
7
  /**
7
- * Configuration options for OverlayFS instances.
8
+ * Configuration options for CoW.
8
9
  * @category Backends and Configuration
9
10
  */
10
- export interface OverlayOptions {
11
- /**
12
- * The file system to write modified files to.
13
- */
11
+ export interface CopyOnWriteOptions {
12
+ /** The file system that initially populates this file system. */
13
+ readable: FileSystem;
14
+ /** The file system to write modified files to. */
14
15
  writable: FileSystem;
16
+ /** @see {@link Journal} */
17
+ journal?: Journal;
18
+ }
19
+ /**
20
+ * @hidden @deprecated use `CopyOnWriteOptions`
21
+ */
22
+ export type OverlayOptions = CopyOnWriteOptions;
23
+ declare const journalOperations: readonly ["delete"];
24
+ /**
25
+ * @internal
26
+ */
27
+ export type JournalOperation = (typeof journalOperations)[number];
28
+ /**
29
+ * @internal
30
+ */
31
+ export interface JournalEntry {
32
+ path: string;
33
+ op: JournalOperation;
34
+ }
35
+ /**
36
+ * Tracks various operations for the CoW backend
37
+ * @internal
38
+ */
39
+ export declare class Journal extends EventEmitter<{
40
+ update: [op: JournalOperation, path: string];
41
+ delete: [path: string];
42
+ }> {
43
+ protected entries: JournalEntry[];
44
+ toString(): string;
15
45
  /**
16
- * The file system that initially populates this file system.
46
+ * Parse a journal from a string
17
47
  */
18
- readable: FileSystem;
48
+ fromString(value: string): this;
49
+ add(op: JournalOperation, path: string): void;
50
+ has(op: JournalOperation, path: string): boolean;
51
+ isDeleted(path: string): boolean;
19
52
  }
20
53
  /**
21
- * OverlayFS makes a read-only filesystem writable by storing writes on a second, writable file system.
22
- * Deletes are persisted via metadata stored on the writable file system.
23
- *
24
- * This class contains no locking whatsoever. It is mutexed to prevent races.
25
- *
54
+ * Using a readable file system as a base, writes are done to a writable file system.
26
55
  * @internal
27
56
  */
28
- export declare class OverlayFS extends FileSystem {
29
- ready(): Promise<void>;
30
- readonly writable: FileSystem;
57
+ export declare class CopyOnWriteFS extends FileSystem {
58
+ /** The file system that initially populates this file system. */
31
59
  readonly readable: FileSystem;
32
- private _isInitialized;
33
- private _deletedFiles;
34
- private _deleteLog;
35
- private _deleteLogUpdatePending;
36
- private _deleteLogUpdateNeeded;
37
- private _deleteLogError?;
38
- private _ready;
39
- constructor({ writable, readable }: OverlayOptions);
60
+ /** The file system to write modified files to. */
61
+ readonly writable: FileSystem;
62
+ /** The journal to use for persisting deletions */
63
+ readonly journal: Journal;
64
+ ready(): Promise<void>;
65
+ constructor(
66
+ /** The file system that initially populates this file system. */
67
+ readable: FileSystem,
68
+ /** The file system to write modified files to. */
69
+ writable: FileSystem,
70
+ /** The journal to use for persisting deletions */
71
+ journal?: Journal);
72
+ isDeleted(path: string): boolean;
40
73
  /**
41
74
  * @todo Consider trying to track information on the writable as well
42
75
  */
@@ -47,13 +80,6 @@ export declare class OverlayFS extends FileSystem {
47
80
  readSync(path: string, buffer: Uint8Array, offset: number, end: number): void;
48
81
  write(path: string, buffer: Uint8Array, offset: number): Promise<void>;
49
82
  writeSync(path: string, buffer: Uint8Array, offset: number): void;
50
- /**
51
- * Called once to load up metadata stored on the writable file system.
52
- * @internal
53
- */
54
- _initialize(): Promise<void>;
55
- getDeletionLog(): string;
56
- restoreDeletionLog(log: string): Promise<void>;
57
83
  rename(oldPath: string, newPath: string): Promise<void>;
58
84
  renameSync(oldPath: string, newPath: string): void;
59
85
  stat(path: string): Promise<Stats>;
@@ -72,11 +98,6 @@ export declare class OverlayFS extends FileSystem {
72
98
  mkdirSync(path: string, mode: number, options: CreationOptions): void;
73
99
  readdir(path: string): Promise<string[]>;
74
100
  readdirSync(path: string): string[];
75
- private deletePath;
76
- private updateLog;
77
- private _reparseDeletionLog;
78
- private checkInitialized;
79
- private checkPath;
80
101
  /**
81
102
  * Create the needed parent directories on the writable storage should they not exist.
82
103
  * Use modes from the read-only storage.
@@ -101,8 +122,13 @@ export declare class OverlayFS extends FileSystem {
101
122
  private copyToWritableSync;
102
123
  private copyToWritable;
103
124
  }
104
- declare const _Overlay: {
105
- readonly name: "Overlay";
125
+ /**
126
+ * @hidden @deprecated use `CopyOnWriteFS`
127
+ */
128
+ export declare class OverlayFS extends CopyOnWriteFS {
129
+ }
130
+ declare const _CopyOnWrite: {
131
+ readonly name: "CopyOnWrite";
106
132
  readonly options: {
107
133
  readonly writable: {
108
134
  readonly type: "object";
@@ -112,11 +138,15 @@ declare const _Overlay: {
112
138
  readonly type: "object";
113
139
  readonly required: true;
114
140
  };
141
+ readonly journal: {
142
+ readonly type: "object";
143
+ readonly required: false;
144
+ };
115
145
  };
116
- readonly create: (options: OverlayOptions) => OverlayFS;
146
+ readonly create: (options: CopyOnWriteOptions) => CopyOnWriteFS;
117
147
  };
118
- type _Overlay = typeof _Overlay;
119
- export interface Overlay extends _Overlay {
148
+ type _CopyOnWrite = typeof _CopyOnWrite;
149
+ export interface CopyOnWrite extends _CopyOnWrite {
120
150
  }
121
151
  /**
122
152
  * Overlay makes a read-only filesystem writable by storing writes on a second, writable file system.
@@ -124,5 +154,13 @@ export interface Overlay extends _Overlay {
124
154
  * @category Backends and Configuration
125
155
  * @internal
126
156
  */
157
+ export declare const CopyOnWrite: CopyOnWrite;
158
+ export interface Overlay extends _CopyOnWrite {
159
+ }
160
+ /**
161
+ * @deprecated Use `CopyOnWrite`
162
+ * @category Backends and Configuration
163
+ * @internal @hidden
164
+ */
127
165
  export declare const Overlay: Overlay;
128
166
  export {};
@@ -52,43 +52,100 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
52
52
  });
53
53
  import { canary } from 'utilium';
54
54
  import { Errno, ErrnoError } from '../internal/error.js';
55
- import { LazyFile, parseFlag } from '../internal/file.js';
55
+ import { LazyFile } from '../internal/file.js';
56
56
  import { FileSystem } from '../internal/filesystem.js';
57
- import { crit, err, info } from '../internal/log.js';
58
- import { decodeUTF8, encodeUTF8 } from '../utils.js';
57
+ import { debug, err, warn } from '../internal/log.js';
59
58
  import { dirname, join } from '../vfs/path.js';
60
- /** @internal */
61
- const deletionLogPath = '/.deleted';
59
+ import { EventEmitter } from 'eventemitter3';
60
+ const journalOperations = ['delete'];
61
+ /** Because TS doesn't work right w/o it */
62
+ function isJournalOp(op) {
63
+ return journalOperations.includes(op);
64
+ }
65
+ const maxOpLength = Math.max(...journalOperations.map(op => op.length));
66
+ const journalMagicString = '#journal@v0\n';
62
67
  /**
63
- * OverlayFS makes a read-only filesystem writable by storing writes on a second, writable file system.
64
- * Deletes are persisted via metadata stored on the writable file system.
65
- *
66
- * This class contains no locking whatsoever. It is mutexed to prevent races.
67
- *
68
+ * Tracks various operations for the CoW backend
68
69
  * @internal
69
70
  */
70
- export class OverlayFS extends FileSystem {
71
+ export class Journal extends EventEmitter {
72
+ constructor() {
73
+ super(...arguments);
74
+ this.entries = [];
75
+ }
76
+ toString() {
77
+ return journalMagicString + this.entries.map(entry => `${entry.op.padEnd(maxOpLength)} ${entry.path}`).join('\n');
78
+ }
79
+ /**
80
+ * Parse a journal from a string
81
+ */
82
+ fromString(value) {
83
+ if (!value.startsWith(journalMagicString))
84
+ throw err(new ErrnoError(Errno.EINVAL, 'Invalid journal contents, refusing to parse'));
85
+ for (const line of value.split('\n')) {
86
+ if (line.startsWith('#'))
87
+ continue; // ignore comments
88
+ const [op, path] = line.split(/\s+/);
89
+ if (!isJournalOp(op)) {
90
+ warn('Unknown operation in journal (skipping): ' + op);
91
+ continue;
92
+ }
93
+ this.entries.push({ op, path });
94
+ }
95
+ return this;
96
+ }
97
+ add(op, path) {
98
+ this.entries.push({ op, path });
99
+ this.emit('update', op, path);
100
+ this.emit(op, path);
101
+ }
102
+ has(op, path) {
103
+ const test = JSON.stringify({ op, path });
104
+ for (const entry of this.entries)
105
+ if (JSON.stringify(entry) === test)
106
+ return true;
107
+ return false;
108
+ }
109
+ isDeleted(path) {
110
+ let deleted = false;
111
+ for (const entry of this.entries) {
112
+ if (entry.path != path)
113
+ continue;
114
+ switch (entry.op) {
115
+ case 'delete':
116
+ deleted = true;
117
+ }
118
+ }
119
+ return deleted;
120
+ }
121
+ }
122
+ /**
123
+ * Using a readable file system as a base, writes are done to a writable file system.
124
+ * @internal
125
+ */
126
+ export class CopyOnWriteFS extends FileSystem {
71
127
  async ready() {
72
128
  await this.readable.ready();
73
129
  await this.writable.ready();
74
- await this._ready;
75
130
  }
76
- constructor({ writable, readable }) {
131
+ constructor(
132
+ /** The file system that initially populates this file system. */
133
+ readable,
134
+ /** The file system to write modified files to. */
135
+ writable,
136
+ /** The journal to use for persisting deletions */
137
+ journal = new Journal()) {
77
138
  super(0x62756c6c, readable.name);
78
- this._isInitialized = false;
79
- this._deletedFiles = new Set();
80
- this._deleteLog = '';
81
- // If 'true', we have scheduled a delete log update.
82
- this._deleteLogUpdatePending = false;
83
- // If 'true', a delete log update is needed after the scheduled delete log
84
- // update finishes.
85
- this._deleteLogUpdateNeeded = false;
86
- this.writable = writable;
87
139
  this.readable = readable;
88
- if (this.writable.attributes.has('no_write')) {
140
+ this.writable = writable;
141
+ this.journal = journal;
142
+ if (writable.attributes.has('no_write')) {
89
143
  throw err(new ErrnoError(Errno.EINVAL, 'Writable file system can not be written to'));
90
144
  }
91
- this._ready = this._initialize();
145
+ readable.attributes.set('no_write');
146
+ }
147
+ isDeleted(path) {
148
+ return this.journal.isDeleted(path);
92
149
  }
93
150
  /**
94
151
  * @todo Consider trying to track information on the writable as well
@@ -120,74 +177,35 @@ export class OverlayFS extends FileSystem {
120
177
  this.copyForWriteSync(path);
121
178
  return this.writable.writeSync(path, buffer, offset);
122
179
  }
123
- /**
124
- * Called once to load up metadata stored on the writable file system.
125
- * @internal
126
- */
127
- async _initialize() {
128
- if (this._isInitialized) {
129
- return;
130
- }
131
- // Read deletion log, process into metadata.
132
- try {
133
- const file = await this.writable.openFile(deletionLogPath, parseFlag('r'));
134
- const { size } = await file.stat();
135
- const { buffer } = await file.read(new Uint8Array(size));
136
- this._deleteLog = decodeUTF8(buffer);
137
- }
138
- catch (error) {
139
- if (error.errno !== Errno.ENOENT)
140
- throw err(error);
141
- info('Overlay does not have a deletion log');
142
- }
143
- this._isInitialized = true;
144
- this._reparseDeletionLog();
145
- }
146
- getDeletionLog() {
147
- return this._deleteLog;
148
- }
149
- async restoreDeletionLog(log) {
150
- this._deleteLog = log;
151
- this._reparseDeletionLog();
152
- await this.updateLog('');
153
- }
154
180
  async rename(oldPath, newPath) {
155
- this.checkInitialized();
156
- this.checkPath(oldPath);
157
- this.checkPath(newPath);
158
181
  await this.copyForWrite(oldPath);
159
182
  try {
160
183
  await this.writable.rename(oldPath, newPath);
161
184
  }
162
185
  catch {
163
- if (this._deletedFiles.has(oldPath)) {
186
+ if (this.isDeleted(oldPath)) {
164
187
  throw ErrnoError.With('ENOENT', oldPath, 'rename');
165
188
  }
166
189
  }
167
190
  }
168
191
  renameSync(oldPath, newPath) {
169
- this.checkInitialized();
170
- this.checkPath(oldPath);
171
- this.checkPath(newPath);
172
192
  this.copyForWriteSync(oldPath);
173
193
  try {
174
194
  this.writable.renameSync(oldPath, newPath);
175
195
  }
176
196
  catch {
177
- if (this._deletedFiles.has(oldPath)) {
197
+ if (this.isDeleted(oldPath)) {
178
198
  throw ErrnoError.With('ENOENT', oldPath, 'rename');
179
199
  }
180
200
  }
181
201
  }
182
202
  async stat(path) {
183
- this.checkInitialized();
184
203
  try {
185
204
  return await this.writable.stat(path);
186
205
  }
187
206
  catch {
188
- if (this._deletedFiles.has(path)) {
207
+ if (this.isDeleted(path))
189
208
  throw ErrnoError.With('ENOENT', path, 'stat');
190
- }
191
209
  const oldStat = await this.readable.stat(path);
192
210
  // Make the oldStat's mode writable.
193
211
  oldStat.mode |= 0o222;
@@ -195,14 +213,12 @@ export class OverlayFS extends FileSystem {
195
213
  }
196
214
  }
197
215
  statSync(path) {
198
- this.checkInitialized();
199
216
  try {
200
217
  return this.writable.statSync(path);
201
218
  }
202
219
  catch {
203
- if (this._deletedFiles.has(path)) {
220
+ if (this.isDeleted(path))
204
221
  throw ErrnoError.With('ENOENT', path, 'stat');
205
- }
206
222
  const oldStat = this.readable.statSync(path);
207
223
  // Make the oldStat's mode writable.
208
224
  oldStat.mode |= 0o222;
@@ -224,28 +240,22 @@ export class OverlayFS extends FileSystem {
224
240
  return new LazyFile(this, path, flag, stats);
225
241
  }
226
242
  async createFile(path, flag, mode, options) {
227
- this.checkInitialized();
228
243
  await this.writable.createFile(path, flag, mode, options);
229
244
  return this.openFile(path, flag);
230
245
  }
231
246
  createFileSync(path, flag, mode, options) {
232
- this.checkInitialized();
233
247
  this.writable.createFileSync(path, flag, mode, options);
234
248
  return this.openFileSync(path, flag);
235
249
  }
236
250
  async link(srcpath, dstpath) {
237
- this.checkInitialized();
238
251
  await this.copyForWrite(srcpath);
239
252
  await this.writable.link(srcpath, dstpath);
240
253
  }
241
254
  linkSync(srcpath, dstpath) {
242
- this.checkInitialized();
243
255
  this.copyForWriteSync(srcpath);
244
256
  this.writable.linkSync(srcpath, dstpath);
245
257
  }
246
258
  async unlink(path) {
247
- this.checkInitialized();
248
- this.checkPath(path);
249
259
  if (!(await this.exists(path))) {
250
260
  throw ErrnoError.With('ENOENT', path, 'unlink');
251
261
  }
@@ -254,25 +264,21 @@ export class OverlayFS extends FileSystem {
254
264
  }
255
265
  // if it still exists add to the delete log
256
266
  if (await this.exists(path)) {
257
- await this.deletePath(path);
267
+ this.journal.add('delete', path);
258
268
  }
259
269
  }
260
270
  unlinkSync(path) {
261
- this.checkInitialized();
262
- this.checkPath(path);
263
- if (!this.existsSync(path)) {
271
+ if (!this.existsSync(path))
264
272
  throw ErrnoError.With('ENOENT', path, 'unlink');
265
- }
266
273
  if (this.writable.existsSync(path)) {
267
274
  this.writable.unlinkSync(path);
268
275
  }
269
276
  // if it still exists add to the delete log
270
277
  if (this.existsSync(path)) {
271
- void this.deletePath(path);
278
+ this.journal.add('delete', path);
272
279
  }
273
280
  }
274
281
  async rmdir(path) {
275
- this.checkInitialized();
276
282
  if (!(await this.exists(path))) {
277
283
  throw ErrnoError.With('ENOENT', path, 'rmdir');
278
284
  }
@@ -286,10 +292,9 @@ export class OverlayFS extends FileSystem {
286
292
  if ((await this.readdir(path)).length) {
287
293
  throw ErrnoError.With('ENOTEMPTY', path, 'rmdir');
288
294
  }
289
- await this.deletePath(path);
295
+ this.journal.add('delete', path);
290
296
  }
291
297
  rmdirSync(path) {
292
- this.checkInitialized();
293
298
  if (!this.existsSync(path)) {
294
299
  throw ErrnoError.With('ENOENT', path, 'rmdir');
295
300
  }
@@ -303,137 +308,55 @@ export class OverlayFS extends FileSystem {
303
308
  if (this.readdirSync(path).length) {
304
309
  throw ErrnoError.With('ENOTEMPTY', path, 'rmdir');
305
310
  }
306
- void this.deletePath(path);
311
+ this.journal.add('delete', path);
307
312
  }
308
313
  async mkdir(path, mode, options) {
309
- this.checkInitialized();
310
- if (await this.exists(path)) {
314
+ if (await this.exists(path))
311
315
  throw ErrnoError.With('EEXIST', path, 'mkdir');
312
- }
313
- // The below will throw should any of the parent directories fail to exist on _writable.
314
316
  await this.createParentDirectories(path);
315
317
  await this.writable.mkdir(path, mode, options);
316
318
  }
317
319
  mkdirSync(path, mode, options) {
318
- this.checkInitialized();
319
- if (this.existsSync(path)) {
320
+ if (this.existsSync(path))
320
321
  throw ErrnoError.With('EEXIST', path, 'mkdir');
321
- }
322
- // The below will throw should any of the parent directories fail to exist on _writable.
323
322
  this.createParentDirectoriesSync(path);
324
323
  this.writable.mkdirSync(path, mode, options);
325
324
  }
326
325
  async readdir(path) {
327
- this.checkInitialized();
328
- // Readdir in both, check delete log on RO file system's listing, merge, return.
329
- const contents = [];
330
- try {
331
- contents.push(...(await this.writable.readdir(path)));
332
- }
333
- catch {
334
- // NOP.
335
- }
336
- try {
337
- contents.push(...(await this.readable.readdir(path)).filter((fPath) => !this._deletedFiles.has(`${path}/${fPath}`)));
338
- }
339
- catch {
340
- // NOP.
341
- }
342
- const seenMap = {};
343
- return contents.filter((path) => {
344
- const result = !seenMap[path];
345
- seenMap[path] = true;
346
- return result;
347
- });
348
- }
349
- readdirSync(path) {
350
- this.checkInitialized();
351
- // Readdir in both, check delete log on RO file system's listing, merge, return.
352
- let contents = [];
353
- try {
354
- contents = contents.concat(this.writable.readdirSync(path));
355
- }
356
- catch {
357
- // NOP.
358
- }
359
- try {
360
- contents = contents.concat(this.readable.readdirSync(path).filter((fPath) => !this._deletedFiles.has(`${path}/${fPath}`)));
361
- }
362
- catch {
363
- // NOP.
364
- }
365
- const seenMap = {};
366
- return contents.filter((path) => {
367
- const result = !seenMap[path];
368
- seenMap[path] = true;
369
- return result;
370
- });
371
- }
372
- async deletePath(path) {
373
- this._deletedFiles.add(path);
374
- await this.updateLog(`d${path}\n`);
375
- }
376
- async updateLog(addition) {
377
- this._deleteLog += addition;
378
- if (this._deleteLogUpdatePending) {
379
- this._deleteLogUpdateNeeded = true;
380
- return;
381
- }
382
- this._deleteLogUpdatePending = true;
383
- const log = await this.writable.openFile(deletionLogPath, parseFlag('w'));
384
- try {
385
- await log.write(encodeUTF8(this._deleteLog));
386
- if (this._deleteLogUpdateNeeded) {
387
- this._deleteLogUpdateNeeded = false;
388
- await this.updateLog('');
326
+ if (this.isDeleted(path))
327
+ throw ErrnoError.With('ENOENT', path, 'readdir');
328
+ const entries = await this.writable.readdir(path);
329
+ if (await this.writable.exists(path))
330
+ for (const entry of await this.writable.readdir(path)) {
331
+ if (!entries.includes(entry))
332
+ entries.push(entry);
389
333
  }
390
- }
391
- catch (e) {
392
- this._deleteLogError = e;
393
- }
394
- finally {
395
- this._deleteLogUpdatePending = false;
396
- }
334
+ return entries.filter(entry => !this.isDeleted(join(path, entry)));
397
335
  }
398
- _reparseDeletionLog() {
399
- this._deletedFiles.clear();
400
- for (const entry of this._deleteLog.split('\n')) {
401
- if (!entry.startsWith('d')) {
402
- continue;
336
+ readdirSync(path) {
337
+ if (this.isDeleted(path))
338
+ throw ErrnoError.With('ENOENT', path, 'readdir');
339
+ const entries = this.writable.readdirSync(path);
340
+ if (this.writable.existsSync(path))
341
+ for (const entry of this.writable.readdirSync(path)) {
342
+ if (!entries.includes(entry))
343
+ entries.push(entry);
403
344
  }
404
- // If the log entry begins w/ 'd', it's a deletion.
405
- this._deletedFiles.add(entry.slice(1));
406
- }
407
- }
408
- checkInitialized() {
409
- if (!this._isInitialized) {
410
- throw crit(new ErrnoError(Errno.EPERM, 'Overlay is not initialized'), { fs: this });
411
- }
412
- if (!this._deleteLogError) {
413
- return;
414
- }
415
- const error = this._deleteLogError;
416
- delete this._deleteLogError;
417
- throw error;
418
- }
419
- checkPath(path) {
420
- if (path == deletionLogPath) {
421
- throw ErrnoError.With('EPERM', path, 'checkPath');
422
- }
345
+ return entries.filter(entry => !this.isDeleted(join(path, entry)));
423
346
  }
424
347
  /**
425
348
  * Create the needed parent directories on the writable storage should they not exist.
426
349
  * Use modes from the read-only storage.
427
350
  */
428
351
  createParentDirectoriesSync(path) {
429
- let parent = dirname(path);
430
352
  const toCreate = [];
431
353
  const silence = canary(ErrnoError.With('EDEADLK', path));
432
- while (!this.writable.existsSync(parent)) {
354
+ for (let parent = dirname(path); !this.writable.existsSync(parent); parent = dirname(parent)) {
433
355
  toCreate.push(parent);
434
- parent = dirname(parent);
435
356
  }
436
357
  silence();
358
+ if (toCreate.length)
359
+ debug('COW: Creating parent directories: ' + toCreate.join(', '));
437
360
  for (const path of toCreate.reverse()) {
438
361
  const { uid, gid, mode } = this.statSync(path);
439
362
  this.writable.mkdirSync(path, mode, { uid, gid });
@@ -444,14 +367,14 @@ export class OverlayFS extends FileSystem {
444
367
  * Use modes from the read-only storage.
445
368
  */
446
369
  async createParentDirectories(path) {
447
- let parent = dirname(path);
448
370
  const toCreate = [];
449
371
  const silence = canary(ErrnoError.With('EDEADLK', path));
450
- while (!(await this.writable.exists(parent))) {
372
+ for (let parent = dirname(path); !(await this.writable.exists(parent)); parent = dirname(parent)) {
451
373
  toCreate.push(parent);
452
- parent = dirname(parent);
453
374
  }
454
375
  silence();
376
+ if (toCreate.length)
377
+ debug('COW: Creating parent directories: ' + toCreate.join(', '));
455
378
  for (const path of toCreate.reverse()) {
456
379
  const { uid, gid, mode } = await this.stat(path);
457
380
  await this.writable.mkdir(path, mode, { uid, gid });
@@ -543,14 +466,20 @@ export class OverlayFS extends FileSystem {
543
466
  }
544
467
  }
545
468
  }
546
- const _Overlay = {
547
- name: 'Overlay',
469
+ /**
470
+ * @hidden @deprecated use `CopyOnWriteFS`
471
+ */
472
+ export class OverlayFS extends CopyOnWriteFS {
473
+ }
474
+ const _CopyOnWrite = {
475
+ name: 'CopyOnWrite',
548
476
  options: {
549
477
  writable: { type: 'object', required: true },
550
478
  readable: { type: 'object', required: true },
479
+ journal: { type: 'object', required: false },
551
480
  },
552
481
  create(options) {
553
- return new OverlayFS(options);
482
+ return new CopyOnWriteFS(options.readable, options.writable, options.journal);
554
483
  },
555
484
  };
556
485
  /**
@@ -559,4 +488,10 @@ const _Overlay = {
559
488
  * @category Backends and Configuration
560
489
  * @internal
561
490
  */
562
- export const Overlay = _Overlay;
491
+ export const CopyOnWrite = _CopyOnWrite;
492
+ /**
493
+ * @deprecated Use `CopyOnWrite`
494
+ * @category Backends and Configuration
495
+ * @internal @hidden
496
+ */
497
+ export const Overlay = _CopyOnWrite;
@@ -1,7 +1,7 @@
1
1
  export * from './backend.js';
2
+ export * from './cow.js';
2
3
  export * from './fetch.js';
3
4
  export * from './memory.js';
4
- export * from './overlay.js';
5
5
  export * from './passthrough.js';
6
6
  export * from './port/fs.js';
7
7
  export * from './single_buffer.js';