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