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