@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.
- package/dist/backends/{overlay.d.ts → cow.d.ts} +79 -41
- package/dist/backends/{overlay.js → cow.js} +131 -196
- package/dist/backends/index.d.ts +1 -1
- package/dist/backends/index.js +1 -1
- package/dist/backends/port/fs.js +1 -1
- package/dist/backends/single_buffer.js +4 -4
- package/dist/backends/store/fs.js +3 -3
- package/dist/index.d.ts +1 -7
- package/dist/index.js +2 -0
- package/dist/internal/devices.js +1 -1
- package/dist/internal/file.js +7 -7
- package/dist/mixins/async.js +21 -25
- package/dist/mixins/shared.d.ts +2 -2
- package/dist/polyfills.d.ts +0 -4
- package/dist/polyfills.js +16 -13
- package/dist/vfs/async.js +42 -19
- package/dist/vfs/promises.d.ts +7 -13
- package/dist/vfs/promises.js +55 -58
- package/dist/vfs/shared.js +2 -2
- package/dist/vfs/streams.d.ts +2 -2
- package/dist/vfs/streams.js +24 -18
- package/dist/vfs/sync.js +5 -5
- package/package.json +3 -3
- package/tests/common/mutex.test.ts +1 -1
- package/tests/fetch/server.js +1 -1
- package/tests/fs/directory.test.ts +11 -59
- package/tests/fs/errors.test.ts +1 -1
- package/tests/fs/stat.test.ts +2 -6
- package/tests/fs/streams.test.ts +71 -66
- package/tests/setup/cow.ts +13 -0
- package/tests/tsconfig.json +1 -4
- package/types/README.md +1 -0
- package/types/readable-stream.d.ts +17 -0
- package/tests/setup/_overlay.ts +0 -7
|
@@ -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
|
|
8
|
+
* Configuration options for CoW.
|
|
8
9
|
* @category Backends and Configuration
|
|
9
10
|
*/
|
|
10
|
-
export interface
|
|
11
|
-
/**
|
|
12
|
-
|
|
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
|
-
*
|
|
46
|
+
* Parse a journal from a string
|
|
17
47
|
*/
|
|
18
|
-
|
|
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
|
-
*
|
|
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
|
|
29
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
105
|
-
|
|
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:
|
|
146
|
+
readonly create: (options: CopyOnWriteOptions) => CopyOnWriteFS;
|
|
117
147
|
};
|
|
118
|
-
type
|
|
119
|
-
export interface
|
|
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
|
|
55
|
+
import { LazyFile } from '../internal/file.js';
|
|
56
56
|
import { FileSystem } from '../internal/filesystem.js';
|
|
57
|
-
import {
|
|
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
|
-
|
|
61
|
-
const
|
|
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
|
-
*
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
267
|
+
this.journal.add('delete', path);
|
|
258
268
|
}
|
|
259
269
|
}
|
|
260
270
|
unlinkSync(path) {
|
|
261
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
311
|
+
this.journal.add('delete', path);
|
|
307
312
|
}
|
|
308
313
|
async mkdir(path, mode, options) {
|
|
309
|
-
this.
|
|
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.
|
|
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.
|
|
328
|
-
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
399
|
-
this.
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
547
|
-
|
|
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
|
|
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
|
|
491
|
+
export const CopyOnWrite = _CopyOnWrite;
|
|
492
|
+
/**
|
|
493
|
+
* @deprecated Use `CopyOnWrite`
|
|
494
|
+
* @category Backends and Configuration
|
|
495
|
+
* @internal @hidden
|
|
496
|
+
*/
|
|
497
|
+
export const Overlay = _CopyOnWrite;
|
package/dist/backends/index.d.ts
CHANGED