@zenfs/core 0.0.12 → 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 +52 -15
- package/dist/ApiError.js +77 -50
- 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 +154 -147
- package/dist/backends/AsyncStore.d.ts +29 -28
- package/dist/backends/AsyncStore.js +375 -482
- package/dist/backends/FolderAdapter.js +8 -19
- 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 +74 -224
- package/dist/backends/OverlayFS.d.ts +26 -34
- package/dist/backends/OverlayFS.js +303 -511
- 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 +21 -6
- package/dist/browser.min.js.map +4 -4
- package/dist/emulation/callbacks.d.ts +119 -113
- package/dist/emulation/callbacks.js +129 -92
- package/dist/emulation/constants.js +1 -1
- package/dist/emulation/dir.d.ts +55 -0
- package/dist/emulation/dir.js +104 -0
- package/dist/emulation/fs.d.ts +1 -2
- package/dist/emulation/fs.js +0 -1
- package/dist/emulation/index.d.ts +3 -0
- package/dist/emulation/index.js +3 -0
- package/dist/emulation/promises.d.ts +265 -145
- package/dist/emulation/promises.js +526 -383
- package/dist/emulation/shared.d.ts +20 -6
- package/dist/emulation/shared.js +22 -23
- package/dist/emulation/streams.d.ts +102 -0
- package/dist/emulation/streams.js +55 -0
- package/dist/emulation/sync.d.ts +98 -69
- package/dist/emulation/sync.js +280 -133
- package/dist/file.d.ts +175 -173
- package/dist/file.js +257 -273
- package/dist/filesystem.d.ts +71 -244
- package/dist/filesystem.js +67 -472
- package/dist/index.d.ts +7 -44
- package/dist/index.js +22 -75
- package/dist/inode.d.ts +37 -28
- package/dist/inode.js +123 -65
- package/dist/stats.d.ts +91 -36
- package/dist/stats.js +138 -110
- package/dist/utils.d.ts +26 -13
- package/dist/utils.js +79 -107
- package/package.json +7 -4
- package/readme.md +2 -40
|
@@ -1,142 +1,80 @@
|
|
|
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
1
|
import { dirname, basename, join, resolve } from '../emulation/path.js';
|
|
11
2
|
import { ApiError, ErrorCode } from '../ApiError.js';
|
|
12
3
|
import { W_OK, R_OK } from '../emulation/constants.js';
|
|
13
|
-
import { PreloadFile
|
|
14
|
-
import {
|
|
15
|
-
import Inode from '../inode.js';
|
|
4
|
+
import { PreloadFile } from '../file.js';
|
|
5
|
+
import { AsyncFileSystem } from '../filesystem.js';
|
|
6
|
+
import { randomIno, Inode } from '../inode.js';
|
|
16
7
|
import { FileType } from '../stats.js';
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
this.prev = null;
|
|
23
|
-
this.next = null;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
// Adapted from https://chrisrng.svbtle.com/lru-cache-in-javascript
|
|
8
|
+
import { encode, decodeDirListing, encodeDirListing } from '../utils.js';
|
|
9
|
+
import { rootIno } from '../inode.js';
|
|
10
|
+
/**
|
|
11
|
+
* Last Recently Used cache
|
|
12
|
+
*/
|
|
27
13
|
class LRUCache {
|
|
28
14
|
constructor(limit) {
|
|
29
15
|
this.limit = limit;
|
|
30
|
-
this.
|
|
31
|
-
this.map = {};
|
|
32
|
-
this.head = null;
|
|
33
|
-
this.tail = null;
|
|
16
|
+
this.cache = [];
|
|
34
17
|
}
|
|
35
|
-
/**
|
|
36
|
-
* Change or add a new value in the cache
|
|
37
|
-
* We overwrite the entry if it already exists
|
|
38
|
-
*/
|
|
39
18
|
set(key, value) {
|
|
40
|
-
const
|
|
41
|
-
if (
|
|
42
|
-
this.
|
|
43
|
-
this.remove(node.key);
|
|
19
|
+
const existingIndex = this.cache.findIndex(node => node.key === key);
|
|
20
|
+
if (existingIndex != -1) {
|
|
21
|
+
this.cache.splice(existingIndex, 1);
|
|
44
22
|
}
|
|
45
|
-
else {
|
|
46
|
-
|
|
47
|
-
delete this.map[this.tail.key];
|
|
48
|
-
this.size--;
|
|
49
|
-
this.tail = this.tail.prev;
|
|
50
|
-
this.tail.next = null;
|
|
51
|
-
}
|
|
23
|
+
else if (this.cache.length >= this.limit) {
|
|
24
|
+
this.cache.shift();
|
|
52
25
|
}
|
|
53
|
-
this.
|
|
26
|
+
this.cache.push({ key, value });
|
|
54
27
|
}
|
|
55
|
-
/* Retrieve a single entry from the cache */
|
|
56
28
|
get(key) {
|
|
57
|
-
|
|
58
|
-
const value = this.map[key].value;
|
|
59
|
-
const node = new LRUNode(key, value);
|
|
60
|
-
this.remove(key);
|
|
61
|
-
this.setHead(node);
|
|
62
|
-
return value;
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
/* Remove a single entry from the cache */
|
|
69
|
-
remove(key) {
|
|
70
|
-
const node = this.map[key];
|
|
29
|
+
const node = this.cache.find(n => n.key === key);
|
|
71
30
|
if (!node) {
|
|
72
31
|
return;
|
|
73
32
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
else {
|
|
78
|
-
this.head = node.next;
|
|
79
|
-
}
|
|
80
|
-
if (node.next !== null) {
|
|
81
|
-
node.next.prev = node.prev;
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
this.tail = node.prev;
|
|
85
|
-
}
|
|
86
|
-
delete this.map[key];
|
|
87
|
-
this.size--;
|
|
33
|
+
// Move the accessed item to the end of the cache (most recently used)
|
|
34
|
+
this.set(key, node.value);
|
|
35
|
+
return node.value;
|
|
88
36
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
this.head = null;
|
|
94
|
-
this.tail = null;
|
|
95
|
-
}
|
|
96
|
-
setHead(node) {
|
|
97
|
-
node.next = this.head;
|
|
98
|
-
node.prev = null;
|
|
99
|
-
if (this.head !== null) {
|
|
100
|
-
this.head.prev = node;
|
|
101
|
-
}
|
|
102
|
-
this.head = node;
|
|
103
|
-
if (this.tail === null) {
|
|
104
|
-
this.tail = node;
|
|
37
|
+
remove(key) {
|
|
38
|
+
const index = this.cache.findIndex(node => node.key === key);
|
|
39
|
+
if (index !== -1) {
|
|
40
|
+
this.cache.splice(index, 1);
|
|
105
41
|
}
|
|
106
|
-
|
|
107
|
-
|
|
42
|
+
}
|
|
43
|
+
reset() {
|
|
44
|
+
this.cache = [];
|
|
108
45
|
}
|
|
109
46
|
}
|
|
110
|
-
export class
|
|
47
|
+
export class AsyncFile extends PreloadFile {
|
|
111
48
|
constructor(_fs, _path, _flag, _stat, contents) {
|
|
112
49
|
super(_fs, _path, _flag, _stat, contents);
|
|
113
50
|
}
|
|
114
|
-
sync() {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
51
|
+
async sync() {
|
|
52
|
+
if (!this.isDirty()) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
await this.fs.sync(this.path, this._buffer, this.stats);
|
|
56
|
+
this.resetDirty();
|
|
57
|
+
}
|
|
58
|
+
syncSync() {
|
|
59
|
+
throw new ApiError(ErrorCode.ENOTSUP);
|
|
60
|
+
}
|
|
61
|
+
async close() {
|
|
62
|
+
this.sync();
|
|
122
63
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
this.sync();
|
|
126
|
-
});
|
|
64
|
+
closeSync() {
|
|
65
|
+
throw new ApiError(ErrorCode.ENOTSUP);
|
|
127
66
|
}
|
|
128
67
|
}
|
|
129
68
|
/**
|
|
130
69
|
* An "Asynchronous key-value file system". Stores data to/retrieves data from
|
|
131
70
|
* an underlying asynchronous key-value store.
|
|
132
71
|
*/
|
|
133
|
-
export class
|
|
134
|
-
|
|
135
|
-
return
|
|
72
|
+
export class AsyncStoreFileSystem extends AsyncFileSystem {
|
|
73
|
+
ready() {
|
|
74
|
+
return this._ready;
|
|
136
75
|
}
|
|
137
76
|
constructor(cacheSize) {
|
|
138
77
|
super();
|
|
139
|
-
this._cache = null;
|
|
140
78
|
if (cacheSize > 0) {
|
|
141
79
|
this._cache = new LRUCache(cacheSize);
|
|
142
80
|
}
|
|
@@ -145,245 +83,212 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
145
83
|
* Initializes the file system. Typically called by subclasses' async
|
|
146
84
|
* constructors.
|
|
147
85
|
*/
|
|
148
|
-
init(store) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
yield this.makeRootDirectory();
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
getName() {
|
|
156
|
-
return this.store.name();
|
|
157
|
-
}
|
|
158
|
-
isReadOnly() {
|
|
159
|
-
return false;
|
|
160
|
-
}
|
|
161
|
-
supportsSymlinks() {
|
|
162
|
-
return false;
|
|
163
|
-
}
|
|
164
|
-
supportsProps() {
|
|
165
|
-
return true;
|
|
166
|
-
}
|
|
167
|
-
supportsSynch() {
|
|
168
|
-
return false;
|
|
86
|
+
async init(store) {
|
|
87
|
+
this.store = store;
|
|
88
|
+
// INVARIANT: Ensure that the root exists.
|
|
89
|
+
await this.makeRootDirectory();
|
|
169
90
|
}
|
|
170
91
|
/**
|
|
171
92
|
* Delete all contents stored in the file system.
|
|
172
93
|
*/
|
|
173
|
-
empty() {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
yield this.makeRootDirectory();
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
access(p, mode, cred) {
|
|
184
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
185
|
-
const tx = this.store.beginTransaction('readonly');
|
|
186
|
-
const inode = yield this.findINode(tx, p);
|
|
187
|
-
if (!inode) {
|
|
188
|
-
throw ApiError.ENOENT(p);
|
|
189
|
-
}
|
|
190
|
-
if (!inode.toStats().hasAccess(mode, cred)) {
|
|
191
|
-
throw ApiError.EACCES(p);
|
|
192
|
-
}
|
|
193
|
-
});
|
|
94
|
+
async empty() {
|
|
95
|
+
if (this._cache) {
|
|
96
|
+
this._cache.reset();
|
|
97
|
+
}
|
|
98
|
+
await this.store.clear();
|
|
99
|
+
// INVARIANT: Root always exists.
|
|
100
|
+
await this.makeRootDirectory();
|
|
194
101
|
}
|
|
195
102
|
/**
|
|
196
103
|
* @todo Make rename compatible with the cache.
|
|
197
104
|
*/
|
|
198
|
-
rename(oldPath, newPath, cred) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
105
|
+
async rename(oldPath, newPath, cred) {
|
|
106
|
+
const c = this._cache;
|
|
107
|
+
if (this._cache) {
|
|
108
|
+
// Clear and disable cache during renaming process.
|
|
109
|
+
this._cache = null;
|
|
110
|
+
c.reset();
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const tx = this.store.beginTransaction('readwrite'), oldParent = dirname(oldPath), oldName = basename(oldPath), newParent = dirname(newPath), newName = basename(newPath),
|
|
114
|
+
// Remove oldPath from parent's directory listing.
|
|
115
|
+
oldDirNode = await this.findINode(tx, oldParent), oldDirList = await this.getDirListing(tx, oldDirNode, oldParent);
|
|
116
|
+
if (!oldDirNode.toStats().hasAccess(W_OK, cred)) {
|
|
117
|
+
throw ApiError.EACCES(oldPath);
|
|
118
|
+
}
|
|
119
|
+
if (!oldDirList[oldName]) {
|
|
120
|
+
throw ApiError.ENOENT(oldPath);
|
|
121
|
+
}
|
|
122
|
+
const nodeId = oldDirList[oldName];
|
|
123
|
+
delete oldDirList[oldName];
|
|
124
|
+
// Invariant: Can't move a folder inside itself.
|
|
125
|
+
// This funny little hack ensures that the check passes only if oldPath
|
|
126
|
+
// is a subpath of newParent. We append '/' to avoid matching folders that
|
|
127
|
+
// are a substring of the bottom-most folder in the path.
|
|
128
|
+
if ((newParent + '/').indexOf(oldPath + '/') === 0) {
|
|
129
|
+
throw new ApiError(ErrorCode.EBUSY, oldParent);
|
|
130
|
+
}
|
|
131
|
+
// Add newPath to parent's directory listing.
|
|
132
|
+
let newDirNode, newDirList;
|
|
133
|
+
if (newParent === oldParent) {
|
|
134
|
+
// Prevent us from re-grabbing the same directory listing, which still
|
|
135
|
+
// contains oldName.
|
|
136
|
+
newDirNode = oldDirNode;
|
|
137
|
+
newDirList = oldDirList;
|
|
205
138
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
delete oldDirList[oldName];
|
|
218
|
-
// Invariant: Can't move a folder inside itself.
|
|
219
|
-
// This funny little hack ensures that the check passes only if oldPath
|
|
220
|
-
// is a subpath of newParent. We append '/' to avoid matching folders that
|
|
221
|
-
// are a substring of the bottom-most folder in the path.
|
|
222
|
-
if ((newParent + '/').indexOf(oldPath + '/') === 0) {
|
|
223
|
-
throw new ApiError(ErrorCode.EBUSY, oldParent);
|
|
224
|
-
}
|
|
225
|
-
// Add newPath to parent's directory listing.
|
|
226
|
-
let newDirNode, newDirList;
|
|
227
|
-
if (newParent === oldParent) {
|
|
228
|
-
// Prevent us from re-grabbing the same directory listing, which still
|
|
229
|
-
// contains oldName.
|
|
230
|
-
newDirNode = oldDirNode;
|
|
231
|
-
newDirList = oldDirList;
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
newDirNode = yield this.findINode(tx, newParent);
|
|
235
|
-
newDirList = yield this.getDirListing(tx, newParent, newDirNode);
|
|
236
|
-
}
|
|
237
|
-
if (newDirList[newName]) {
|
|
238
|
-
// If it's a file, delete it.
|
|
239
|
-
const newNameNode = yield this.getINode(tx, newPath, newDirList[newName]);
|
|
240
|
-
if (newNameNode.isFile()) {
|
|
241
|
-
try {
|
|
242
|
-
yield tx.del(newNameNode.id);
|
|
243
|
-
yield tx.del(newDirList[newName]);
|
|
244
|
-
}
|
|
245
|
-
catch (e) {
|
|
246
|
-
yield tx.abort();
|
|
247
|
-
throw e;
|
|
248
|
-
}
|
|
139
|
+
else {
|
|
140
|
+
newDirNode = await this.findINode(tx, newParent);
|
|
141
|
+
newDirList = await this.getDirListing(tx, newDirNode, newParent);
|
|
142
|
+
}
|
|
143
|
+
if (newDirList[newName]) {
|
|
144
|
+
// If it's a file, delete it.
|
|
145
|
+
const newNameNode = await this.getINode(tx, newDirList[newName], newPath);
|
|
146
|
+
if (newNameNode.toStats().isFile()) {
|
|
147
|
+
try {
|
|
148
|
+
await tx.remove(newNameNode.ino);
|
|
149
|
+
await tx.remove(newDirList[newName]);
|
|
249
150
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
throw
|
|
151
|
+
catch (e) {
|
|
152
|
+
await tx.abort();
|
|
153
|
+
throw e;
|
|
253
154
|
}
|
|
254
155
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
yield tx.put(oldDirNode.id, encode(JSON.stringify(oldDirList)), true);
|
|
259
|
-
yield tx.put(newDirNode.id, encode(JSON.stringify(newDirList)), true);
|
|
260
|
-
}
|
|
261
|
-
catch (e) {
|
|
262
|
-
yield tx.abort();
|
|
263
|
-
throw e;
|
|
264
|
-
}
|
|
265
|
-
yield tx.commit();
|
|
266
|
-
}
|
|
267
|
-
finally {
|
|
268
|
-
if (c) {
|
|
269
|
-
this._cache = c;
|
|
156
|
+
else {
|
|
157
|
+
// If it's a directory, throw a permissions error.
|
|
158
|
+
throw ApiError.EPERM(newPath);
|
|
270
159
|
}
|
|
271
160
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
stat(p, cred) {
|
|
275
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
276
|
-
const tx = this.store.beginTransaction('readonly');
|
|
277
|
-
const inode = yield this.findINode(tx, p);
|
|
278
|
-
const stats = inode.toStats();
|
|
279
|
-
if (!stats.hasAccess(R_OK, cred)) {
|
|
280
|
-
throw ApiError.EACCES(p);
|
|
281
|
-
}
|
|
282
|
-
return stats;
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
createFile(p, flag, mode, cred) {
|
|
286
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
287
|
-
const tx = this.store.beginTransaction('readwrite'), data = new Uint8Array(0), newFile = yield this.commitNewFile(tx, p, FileType.FILE, mode, cred, data);
|
|
288
|
-
// Open the file.
|
|
289
|
-
return new AsyncKeyValueFile(this, p, flag, newFile.toStats(), data);
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
openFile(p, flag, cred) {
|
|
293
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
294
|
-
const tx = this.store.beginTransaction('readonly'), node = yield this.findINode(tx, p), data = yield tx.get(node.id);
|
|
295
|
-
if (!node.toStats().hasAccess(flag.getMode(), cred)) {
|
|
296
|
-
throw ApiError.EACCES(p);
|
|
297
|
-
}
|
|
298
|
-
if (data === undefined) {
|
|
299
|
-
throw ApiError.ENOENT(p);
|
|
300
|
-
}
|
|
301
|
-
return new AsyncKeyValueFile(this, p, flag, node.toStats(), data);
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
unlink(p, cred) {
|
|
305
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
306
|
-
return this.removeEntry(p, false, cred);
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
rmdir(p, cred) {
|
|
310
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
311
|
-
// Check first if directory is empty.
|
|
312
|
-
const list = yield this.readdir(p, cred);
|
|
313
|
-
if (list.length > 0) {
|
|
314
|
-
throw ApiError.ENOTEMPTY(p);
|
|
315
|
-
}
|
|
316
|
-
yield this.removeEntry(p, true, cred);
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
mkdir(p, mode, cred) {
|
|
320
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
321
|
-
const tx = this.store.beginTransaction('readwrite'), data = encode('{}');
|
|
322
|
-
yield this.commitNewFile(tx, p, FileType.DIRECTORY, mode, cred, data);
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
readdir(p, cred) {
|
|
326
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
327
|
-
const tx = this.store.beginTransaction('readonly');
|
|
328
|
-
const node = yield this.findINode(tx, p);
|
|
329
|
-
if (!node.toStats().hasAccess(R_OK, cred)) {
|
|
330
|
-
throw ApiError.EACCES(p);
|
|
331
|
-
}
|
|
332
|
-
return Object.keys(yield this.getDirListing(tx, p, node));
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
chmod(p, mode, cred) {
|
|
336
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
337
|
-
const fd = yield this.openFile(p, FileFlag.getFileFlag('r+'), cred);
|
|
338
|
-
yield fd.chmod(mode);
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
chown(p, new_uid, new_gid, cred) {
|
|
342
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
343
|
-
const fd = yield this.openFile(p, FileFlag.getFileFlag('r+'), cred);
|
|
344
|
-
yield fd.chown(new_uid, new_gid);
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
_sync(p, data, stats) {
|
|
348
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
349
|
-
// @todo Ensure mtime updates properly, and use that to determine if a data
|
|
350
|
-
// update is required.
|
|
351
|
-
const tx = this.store.beginTransaction('readwrite'),
|
|
352
|
-
// We use the _findInode helper because we actually need the INode id.
|
|
353
|
-
fileInodeId = yield this._findINode(tx, dirname(p), basename(p)), fileInode = yield this.getINode(tx, p, fileInodeId), inodeChanged = fileInode.update(stats);
|
|
161
|
+
newDirList[newName] = nodeId;
|
|
162
|
+
// Commit the two changed directory listings.
|
|
354
163
|
try {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
// Sync metadata.
|
|
358
|
-
if (inodeChanged) {
|
|
359
|
-
yield tx.put(fileInodeId, fileInode.serialize(), true);
|
|
360
|
-
}
|
|
164
|
+
await tx.put(oldDirNode.ino, encodeDirListing(oldDirList), true);
|
|
165
|
+
await tx.put(newDirNode.ino, encodeDirListing(newDirList), true);
|
|
361
166
|
}
|
|
362
167
|
catch (e) {
|
|
363
|
-
|
|
168
|
+
await tx.abort();
|
|
364
169
|
throw e;
|
|
365
170
|
}
|
|
366
|
-
|
|
367
|
-
}
|
|
171
|
+
await tx.commit();
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
if (c) {
|
|
175
|
+
this._cache = c;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async stat(p, cred) {
|
|
180
|
+
const tx = this.store.beginTransaction('readonly');
|
|
181
|
+
const inode = await this.findINode(tx, p);
|
|
182
|
+
if (!inode) {
|
|
183
|
+
throw ApiError.ENOENT(p);
|
|
184
|
+
}
|
|
185
|
+
const stats = inode.toStats();
|
|
186
|
+
if (!stats.hasAccess(R_OK, cred)) {
|
|
187
|
+
throw ApiError.EACCES(p);
|
|
188
|
+
}
|
|
189
|
+
return stats;
|
|
190
|
+
}
|
|
191
|
+
async createFile(p, flag, mode, cred) {
|
|
192
|
+
const tx = this.store.beginTransaction('readwrite'), data = new Uint8Array(0), newFile = await this.commitNewFile(tx, p, FileType.FILE, mode, cred, data);
|
|
193
|
+
// Open the file.
|
|
194
|
+
return new AsyncFile(this, p, flag, newFile.toStats(), data);
|
|
195
|
+
}
|
|
196
|
+
async openFile(p, flag, cred) {
|
|
197
|
+
const tx = this.store.beginTransaction('readonly'), node = await this.findINode(tx, p), data = await tx.get(node.ino);
|
|
198
|
+
if (!node.toStats().hasAccess(flag.mode, cred)) {
|
|
199
|
+
throw ApiError.EACCES(p);
|
|
200
|
+
}
|
|
201
|
+
if (!data) {
|
|
202
|
+
throw ApiError.ENOENT(p);
|
|
203
|
+
}
|
|
204
|
+
return new AsyncFile(this, p, flag, node.toStats(), data);
|
|
205
|
+
}
|
|
206
|
+
async unlink(p, cred) {
|
|
207
|
+
return this.removeEntry(p, false, cred);
|
|
208
|
+
}
|
|
209
|
+
async rmdir(p, cred) {
|
|
210
|
+
// Check first if directory is empty.
|
|
211
|
+
const list = await this.readdir(p, cred);
|
|
212
|
+
if (list.length > 0) {
|
|
213
|
+
throw ApiError.ENOTEMPTY(p);
|
|
214
|
+
}
|
|
215
|
+
await this.removeEntry(p, true, cred);
|
|
216
|
+
}
|
|
217
|
+
async mkdir(p, mode, cred) {
|
|
218
|
+
const tx = this.store.beginTransaction('readwrite'), data = encode('{}');
|
|
219
|
+
await this.commitNewFile(tx, p, FileType.DIRECTORY, mode, cred, data);
|
|
220
|
+
}
|
|
221
|
+
async readdir(p, cred) {
|
|
222
|
+
const tx = this.store.beginTransaction('readonly');
|
|
223
|
+
const node = await this.findINode(tx, p);
|
|
224
|
+
if (!node.toStats().hasAccess(R_OK, cred)) {
|
|
225
|
+
throw ApiError.EACCES(p);
|
|
226
|
+
}
|
|
227
|
+
return Object.keys(await this.getDirListing(tx, node, p));
|
|
368
228
|
}
|
|
369
229
|
/**
|
|
370
|
-
*
|
|
230
|
+
* Updated the inode and data node at the given path
|
|
231
|
+
* @todo Ensure mtime updates properly, and use that to determine if a data update is required.
|
|
371
232
|
*/
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
yield tx.put(dirInode.id, encode('{}'), false);
|
|
383
|
-
yield tx.put(ROOT_NODE_ID, dirInode.serialize(), false);
|
|
384
|
-
yield tx.commit();
|
|
233
|
+
async sync(p, data, stats) {
|
|
234
|
+
const tx = this.store.beginTransaction('readwrite'),
|
|
235
|
+
// We use the _findInode helper because we actually need the INode id.
|
|
236
|
+
fileInodeId = await this._findINode(tx, dirname(p), basename(p)), fileInode = await this.getINode(tx, fileInodeId, p), inodeChanged = fileInode.update(stats);
|
|
237
|
+
try {
|
|
238
|
+
// Sync data.
|
|
239
|
+
await tx.put(fileInode.ino, data, true);
|
|
240
|
+
// Sync metadata.
|
|
241
|
+
if (inodeChanged) {
|
|
242
|
+
await tx.put(fileInodeId, fileInode.data, true);
|
|
385
243
|
}
|
|
386
|
-
}
|
|
244
|
+
}
|
|
245
|
+
catch (e) {
|
|
246
|
+
await tx.abort();
|
|
247
|
+
throw e;
|
|
248
|
+
}
|
|
249
|
+
await tx.commit();
|
|
250
|
+
}
|
|
251
|
+
async link(existing, newpath, cred) {
|
|
252
|
+
const tx = this.store.beginTransaction('readwrite'), existingDir = dirname(existing), existingDirNode = await this.findINode(tx, existingDir);
|
|
253
|
+
if (!existingDirNode.toStats().hasAccess(R_OK, cred)) {
|
|
254
|
+
throw ApiError.EACCES(existingDir);
|
|
255
|
+
}
|
|
256
|
+
const newDir = dirname(newpath), newDirNode = await this.findINode(tx, newDir), newListing = await this.getDirListing(tx, newDirNode, newDir);
|
|
257
|
+
if (!newDirNode.toStats().hasAccess(W_OK, cred)) {
|
|
258
|
+
throw ApiError.EACCES(newDir);
|
|
259
|
+
}
|
|
260
|
+
const ino = await this._findINode(tx, existingDir, basename(existing));
|
|
261
|
+
const node = await this.getINode(tx, ino, existing);
|
|
262
|
+
if (!node.toStats().hasAccess(W_OK, cred)) {
|
|
263
|
+
throw ApiError.EACCES(newpath);
|
|
264
|
+
}
|
|
265
|
+
node.nlink++;
|
|
266
|
+
newListing[basename(newpath)] = ino;
|
|
267
|
+
try {
|
|
268
|
+
tx.put(ino, node.data, true);
|
|
269
|
+
tx.put(newDirNode.ino, encodeDirListing(newListing), true);
|
|
270
|
+
}
|
|
271
|
+
catch (e) {
|
|
272
|
+
tx.abort();
|
|
273
|
+
throw e;
|
|
274
|
+
}
|
|
275
|
+
tx.commit();
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Checks if the root directory exists. Creates it if it doesn't.
|
|
279
|
+
*/
|
|
280
|
+
async makeRootDirectory() {
|
|
281
|
+
const tx = this.store.beginTransaction('readwrite');
|
|
282
|
+
if ((await tx.get(rootIno)) === undefined) {
|
|
283
|
+
// Create new inode. o777, owned by root:root
|
|
284
|
+
const dirInode = new Inode();
|
|
285
|
+
dirInode.mode = 0o777 | FileType.DIRECTORY;
|
|
286
|
+
// If the root doesn't exist, the first random ID shouldn't exist,
|
|
287
|
+
// either.
|
|
288
|
+
await tx.put(dirInode.ino, encode('{}'), false);
|
|
289
|
+
await tx.put(rootIno, dirInode.data, false);
|
|
290
|
+
await tx.commit();
|
|
291
|
+
}
|
|
387
292
|
}
|
|
388
293
|
/**
|
|
389
294
|
* Helper function for findINode.
|
|
@@ -391,48 +296,30 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
391
296
|
* @param filename The filename of the inode we are attempting to find, minus
|
|
392
297
|
* the parent.
|
|
393
298
|
*/
|
|
394
|
-
_findINode(tx, parent, filename, visited = new Set()) {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
return id;
|
|
405
|
-
}
|
|
299
|
+
async _findINode(tx, parent, filename, visited = new Set()) {
|
|
300
|
+
const currentPath = join(parent, filename);
|
|
301
|
+
if (visited.has(currentPath)) {
|
|
302
|
+
throw new ApiError(ErrorCode.EIO, 'Infinite loop detected while finding inode', currentPath);
|
|
303
|
+
}
|
|
304
|
+
visited.add(currentPath);
|
|
305
|
+
if (this._cache) {
|
|
306
|
+
const id = this._cache.get(currentPath);
|
|
307
|
+
if (id) {
|
|
308
|
+
return id;
|
|
406
309
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
return ROOT_NODE_ID;
|
|
414
|
-
}
|
|
415
|
-
else {
|
|
416
|
-
// BASE CASE #2: Find the item in the root node.
|
|
417
|
-
const inode = yield this.getINode(tx, parent, ROOT_NODE_ID);
|
|
418
|
-
const dirList = yield this.getDirListing(tx, parent, inode);
|
|
419
|
-
if (dirList[filename]) {
|
|
420
|
-
const id = dirList[filename];
|
|
421
|
-
if (this._cache) {
|
|
422
|
-
this._cache.set(currentPath, id);
|
|
423
|
-
}
|
|
424
|
-
return id;
|
|
425
|
-
}
|
|
426
|
-
else {
|
|
427
|
-
throw ApiError.ENOENT(resolve(parent, filename));
|
|
428
|
-
}
|
|
310
|
+
}
|
|
311
|
+
if (parent === '/') {
|
|
312
|
+
if (filename === '') {
|
|
313
|
+
// BASE CASE #1: Return the root's ID.
|
|
314
|
+
if (this._cache) {
|
|
315
|
+
this._cache.set(currentPath, rootIno);
|
|
429
316
|
}
|
|
317
|
+
return rootIno;
|
|
430
318
|
}
|
|
431
319
|
else {
|
|
432
|
-
//
|
|
433
|
-
|
|
434
|
-
const
|
|
435
|
-
const dirList = yield this.getDirListing(tx, parent, inode);
|
|
320
|
+
// BASE CASE #2: Find the item in the root node.
|
|
321
|
+
const inode = await this.getINode(tx, rootIno, parent);
|
|
322
|
+
const dirList = await this.getDirListing(tx, inode, parent);
|
|
436
323
|
if (dirList[filename]) {
|
|
437
324
|
const id = dirList[filename];
|
|
438
325
|
if (this._cache) {
|
|
@@ -444,18 +331,32 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
444
331
|
throw ApiError.ENOENT(resolve(parent, filename));
|
|
445
332
|
}
|
|
446
333
|
}
|
|
447
|
-
}
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
// Get the parent directory's INode, and find the file in its directory
|
|
337
|
+
// listing.
|
|
338
|
+
const inode = await this.findINode(tx, parent, visited);
|
|
339
|
+
const dirList = await this.getDirListing(tx, inode, parent);
|
|
340
|
+
if (dirList[filename]) {
|
|
341
|
+
const id = dirList[filename];
|
|
342
|
+
if (this._cache) {
|
|
343
|
+
this._cache.set(currentPath, id);
|
|
344
|
+
}
|
|
345
|
+
return id;
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
throw ApiError.ENOENT(resolve(parent, filename));
|
|
349
|
+
}
|
|
350
|
+
}
|
|
448
351
|
}
|
|
449
352
|
/**
|
|
450
353
|
* Finds the Inode of the given path.
|
|
451
354
|
* @param p The path to look up.
|
|
452
355
|
* @todo memoize/cache
|
|
453
356
|
*/
|
|
454
|
-
findINode(tx, p, visited = new Set()) {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
return this.getINode(tx, p, id);
|
|
458
|
-
});
|
|
357
|
+
async findINode(tx, p, visited = new Set()) {
|
|
358
|
+
const id = await this._findINode(tx, dirname(p), basename(p), visited);
|
|
359
|
+
return this.getINode(tx, id, p);
|
|
459
360
|
}
|
|
460
361
|
/**
|
|
461
362
|
* Given the ID of a node, retrieves the corresponding Inode.
|
|
@@ -463,62 +364,56 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
463
364
|
* @param p The corresponding path to the file (used for error messages).
|
|
464
365
|
* @param id The ID to look up.
|
|
465
366
|
*/
|
|
466
|
-
getINode(tx,
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
return Inode.Deserialize(data);
|
|
473
|
-
});
|
|
367
|
+
async getINode(tx, id, p) {
|
|
368
|
+
const data = await tx.get(id);
|
|
369
|
+
if (!data) {
|
|
370
|
+
throw ApiError.ENOENT(p);
|
|
371
|
+
}
|
|
372
|
+
return new Inode(data.buffer);
|
|
474
373
|
}
|
|
475
374
|
/**
|
|
476
375
|
* Given the Inode of a directory, retrieves the corresponding directory
|
|
477
376
|
* listing.
|
|
478
377
|
*/
|
|
479
|
-
getDirListing(tx,
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
}
|
|
494
|
-
});
|
|
378
|
+
async getDirListing(tx, inode, p) {
|
|
379
|
+
if (!inode.toStats().isDirectory()) {
|
|
380
|
+
throw ApiError.ENOTDIR(p);
|
|
381
|
+
}
|
|
382
|
+
const data = await tx.get(inode.ino);
|
|
383
|
+
if (!data) {
|
|
384
|
+
/*
|
|
385
|
+
Occurs when data is undefined, or corresponds to something other
|
|
386
|
+
than a directory listing. The latter should never occur unless
|
|
387
|
+
the file system is corrupted.
|
|
388
|
+
*/
|
|
389
|
+
throw ApiError.ENOENT(p);
|
|
390
|
+
}
|
|
391
|
+
return decodeDirListing(data);
|
|
495
392
|
}
|
|
496
393
|
/**
|
|
497
394
|
* Adds a new node under a random ID. Retries 5 times before giving up in
|
|
498
|
-
* the exceedingly unlikely chance that we try to reuse a random
|
|
395
|
+
* the exceedingly unlikely chance that we try to reuse a random ino.
|
|
499
396
|
*/
|
|
500
|
-
addNewNode(tx, data) {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
397
|
+
async addNewNode(tx, data) {
|
|
398
|
+
let retries = 0;
|
|
399
|
+
const reroll = async () => {
|
|
400
|
+
if (++retries === 5) {
|
|
401
|
+
// Max retries hit. Return with an error.
|
|
402
|
+
throw new ApiError(ErrorCode.EIO, 'Unable to commit data to key-value store.');
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
// Try again.
|
|
406
|
+
const ino = randomIno();
|
|
407
|
+
const committed = await tx.put(ino, data, false);
|
|
408
|
+
if (!committed) {
|
|
409
|
+
return reroll();
|
|
507
410
|
}
|
|
508
411
|
else {
|
|
509
|
-
|
|
510
|
-
const currId = randomUUID();
|
|
511
|
-
const committed = yield tx.put(currId, data, false);
|
|
512
|
-
if (!committed) {
|
|
513
|
-
return reroll();
|
|
514
|
-
}
|
|
515
|
-
else {
|
|
516
|
-
return currId;
|
|
517
|
-
}
|
|
412
|
+
return ino;
|
|
518
413
|
}
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
return reroll();
|
|
522
417
|
}
|
|
523
418
|
/**
|
|
524
419
|
* Commits a new file (well, a FILE or a DIRECTORY) to the file system with
|
|
@@ -530,41 +425,41 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
530
425
|
* @param cred The UID/GID to create the file with
|
|
531
426
|
* @param data The data to store at the file's data node.
|
|
532
427
|
*/
|
|
533
|
-
commitNewFile(tx, p, type, mode, cred, data) {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
428
|
+
async commitNewFile(tx, p, type, mode, cred, data) {
|
|
429
|
+
const parentDir = dirname(p), fname = basename(p), parentNode = await this.findINode(tx, parentDir), dirListing = await this.getDirListing(tx, parentNode, parentDir);
|
|
430
|
+
//Check that the creater has correct access
|
|
431
|
+
if (!parentNode.toStats().hasAccess(W_OK, cred)) {
|
|
432
|
+
throw ApiError.EACCES(p);
|
|
433
|
+
}
|
|
434
|
+
// Invariant: The root always exists.
|
|
435
|
+
// If we don't check this prior to taking steps below, we will create a
|
|
436
|
+
// file with name '' in root should p == '/'.
|
|
437
|
+
if (p === '/') {
|
|
438
|
+
throw ApiError.EEXIST(p);
|
|
439
|
+
}
|
|
440
|
+
// Check if file already exists.
|
|
441
|
+
if (dirListing[fname]) {
|
|
442
|
+
await tx.abort();
|
|
443
|
+
throw ApiError.EEXIST(p);
|
|
444
|
+
}
|
|
445
|
+
try {
|
|
446
|
+
// Commit data.
|
|
447
|
+
const inode = new Inode();
|
|
448
|
+
inode.ino = await this.addNewNode(tx, data);
|
|
449
|
+
inode.mode = mode | type;
|
|
450
|
+
inode.uid = cred.uid;
|
|
451
|
+
inode.gid = cred.gid;
|
|
452
|
+
inode.size = data.length;
|
|
453
|
+
// Update and commit parent directory listing.
|
|
454
|
+
dirListing[fname] = await this.addNewNode(tx, inode.data);
|
|
455
|
+
await tx.put(parentNode.ino, encodeDirListing(dirListing), true);
|
|
456
|
+
await tx.commit();
|
|
457
|
+
return inode;
|
|
458
|
+
}
|
|
459
|
+
catch (e) {
|
|
460
|
+
tx.abort();
|
|
461
|
+
throw e;
|
|
462
|
+
}
|
|
568
463
|
}
|
|
569
464
|
/**
|
|
570
465
|
* Remove all traces of the given path from the file system.
|
|
@@ -578,43 +473,41 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
578
473
|
* @param isDir Does the path belong to a directory, or a file?
|
|
579
474
|
* @todo Update mtime.
|
|
580
475
|
*/
|
|
581
|
-
removeEntry(p, isDir, cred) {
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
// Update directory listing.
|
|
610
|
-
yield tx.put(parentNode.id, encode(JSON.stringify(parentListing)), true);
|
|
611
|
-
}
|
|
612
|
-
catch (e) {
|
|
613
|
-
yield tx.abort();
|
|
614
|
-
throw e;
|
|
476
|
+
async removeEntry(p, isDir, cred) {
|
|
477
|
+
if (this._cache) {
|
|
478
|
+
this._cache.remove(p);
|
|
479
|
+
}
|
|
480
|
+
const tx = this.store.beginTransaction('readwrite'), parent = dirname(p), parentNode = await this.findINode(tx, parent), parentListing = await this.getDirListing(tx, parentNode, parent), fileName = basename(p);
|
|
481
|
+
if (!parentListing[fileName]) {
|
|
482
|
+
throw ApiError.ENOENT(p);
|
|
483
|
+
}
|
|
484
|
+
const fileIno = parentListing[fileName];
|
|
485
|
+
// Get file inode.
|
|
486
|
+
const fileNode = await this.getINode(tx, fileIno, p);
|
|
487
|
+
if (!fileNode.toStats().hasAccess(W_OK, cred)) {
|
|
488
|
+
throw ApiError.EACCES(p);
|
|
489
|
+
}
|
|
490
|
+
// Remove from directory listing of parent.
|
|
491
|
+
delete parentListing[fileName];
|
|
492
|
+
if (!isDir && fileNode.toStats().isDirectory()) {
|
|
493
|
+
throw ApiError.EISDIR(p);
|
|
494
|
+
}
|
|
495
|
+
if (isDir && !fileNode.toStats().isDirectory()) {
|
|
496
|
+
throw ApiError.ENOTDIR(p);
|
|
497
|
+
}
|
|
498
|
+
try {
|
|
499
|
+
await tx.put(parentNode.ino, encodeDirListing(parentListing), true);
|
|
500
|
+
if (--fileNode.nlink < 1) {
|
|
501
|
+
// remove file
|
|
502
|
+
await tx.remove(fileNode.ino);
|
|
503
|
+
await tx.remove(fileIno);
|
|
615
504
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
505
|
+
}
|
|
506
|
+
catch (e) {
|
|
507
|
+
await tx.abort();
|
|
508
|
+
throw e;
|
|
509
|
+
}
|
|
510
|
+
// Success.
|
|
511
|
+
await tx.commit();
|
|
619
512
|
}
|
|
620
513
|
}
|