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