@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,11 +1,12 @@
|
|
|
1
1
|
import { dirname, basename, join, resolve, sep } from '../emulation/path.js';
|
|
2
2
|
import { ApiError, ErrorCode } from '../ApiError.js';
|
|
3
3
|
import { W_OK, R_OK } from '../emulation/constants.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import Inode from '../inode.js';
|
|
4
|
+
import { PreloadFile } from '../file.js';
|
|
5
|
+
import { SyncFileSystem } from '../filesystem.js';
|
|
6
|
+
import { randomIno, Inode } from '../inode.js';
|
|
7
7
|
import { FileType } from '../stats.js';
|
|
8
|
-
import {
|
|
8
|
+
import { decodeDirListing, encode, encodeDirListing } from '../utils.js';
|
|
9
|
+
import { rootIno } from '../inode.js';
|
|
9
10
|
/**
|
|
10
11
|
* A simple RW transaction for simple synchronous key-value stores.
|
|
11
12
|
*/
|
|
@@ -16,24 +17,24 @@ export class SimpleSyncRWTransaction {
|
|
|
16
17
|
* Stores data in the keys we modify prior to modifying them.
|
|
17
18
|
* Allows us to roll back commits.
|
|
18
19
|
*/
|
|
19
|
-
this.originalData =
|
|
20
|
+
this.originalData = new Map();
|
|
20
21
|
/**
|
|
21
22
|
* List of keys modified in this transaction, if any.
|
|
22
23
|
*/
|
|
23
|
-
this.modifiedKeys =
|
|
24
|
+
this.modifiedKeys = new Set();
|
|
24
25
|
}
|
|
25
|
-
get(
|
|
26
|
-
const val = this.store.get(
|
|
27
|
-
this.stashOldValue(
|
|
26
|
+
get(ino) {
|
|
27
|
+
const val = this.store.get(ino);
|
|
28
|
+
this.stashOldValue(ino, val);
|
|
28
29
|
return val;
|
|
29
30
|
}
|
|
30
|
-
put(
|
|
31
|
-
this.markModified(
|
|
32
|
-
return this.store.put(
|
|
31
|
+
put(ino, data, overwrite) {
|
|
32
|
+
this.markModified(ino);
|
|
33
|
+
return this.store.put(ino, data, overwrite);
|
|
33
34
|
}
|
|
34
|
-
|
|
35
|
-
this.markModified(
|
|
36
|
-
this.store.
|
|
35
|
+
remove(ino) {
|
|
36
|
+
this.markModified(ino);
|
|
37
|
+
this.store.remove(ino);
|
|
37
38
|
}
|
|
38
39
|
commit() {
|
|
39
40
|
/* NOP */
|
|
@@ -41,10 +42,10 @@ export class SimpleSyncRWTransaction {
|
|
|
41
42
|
abort() {
|
|
42
43
|
// Rollback old values.
|
|
43
44
|
for (const key of this.modifiedKeys) {
|
|
44
|
-
const value = this.originalData
|
|
45
|
+
const value = this.originalData.get(key);
|
|
45
46
|
if (!value) {
|
|
46
47
|
// Key didn't exist.
|
|
47
|
-
this.store.
|
|
48
|
+
this.store.remove(key);
|
|
48
49
|
}
|
|
49
50
|
else {
|
|
50
51
|
// Key existed. Store old value.
|
|
@@ -52,44 +53,45 @@ export class SimpleSyncRWTransaction {
|
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
|
-
_has(key) {
|
|
56
|
-
return Object.prototype.hasOwnProperty.call(this.originalData, key);
|
|
57
|
-
}
|
|
58
56
|
/**
|
|
59
57
|
* Stashes given key value pair into `originalData` if it doesn't already
|
|
60
58
|
* exist. Allows us to stash values the program is requesting anyway to
|
|
61
59
|
* prevent needless `get` requests if the program modifies the data later
|
|
62
60
|
* on during the transaction.
|
|
63
61
|
*/
|
|
64
|
-
stashOldValue(
|
|
62
|
+
stashOldValue(ino, value) {
|
|
65
63
|
// Keep only the earliest value in the transaction.
|
|
66
|
-
if (!this.
|
|
67
|
-
this.originalData
|
|
64
|
+
if (!this.originalData.has(ino)) {
|
|
65
|
+
this.originalData.set(ino, value);
|
|
68
66
|
}
|
|
69
67
|
}
|
|
70
68
|
/**
|
|
71
69
|
* Marks the given key as modified, and stashes its value if it has not been
|
|
72
70
|
* stashed already.
|
|
73
71
|
*/
|
|
74
|
-
markModified(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
this.originalData[key] = this.store.get(key);
|
|
79
|
-
}
|
|
72
|
+
markModified(ino) {
|
|
73
|
+
this.modifiedKeys.add(ino);
|
|
74
|
+
if (!this.originalData.has(ino)) {
|
|
75
|
+
this.originalData.set(ino, this.store.get(ino));
|
|
80
76
|
}
|
|
81
77
|
}
|
|
82
78
|
}
|
|
83
|
-
export class
|
|
79
|
+
export class SyncStoreFile extends PreloadFile {
|
|
84
80
|
constructor(_fs, _path, _flag, _stat, contents) {
|
|
85
81
|
super(_fs, _path, _flag, _stat, contents);
|
|
86
82
|
}
|
|
83
|
+
async sync() {
|
|
84
|
+
this.syncSync();
|
|
85
|
+
}
|
|
87
86
|
syncSync() {
|
|
88
87
|
if (this.isDirty()) {
|
|
89
|
-
this.
|
|
88
|
+
this.fs.syncSync(this.path, this._buffer, this.stats);
|
|
90
89
|
this.resetDirty();
|
|
91
90
|
}
|
|
92
91
|
}
|
|
92
|
+
async close() {
|
|
93
|
+
this.closeSync();
|
|
94
|
+
}
|
|
93
95
|
closeSync() {
|
|
94
96
|
this.syncSync();
|
|
95
97
|
}
|
|
@@ -103,30 +105,22 @@ export class SyncKeyValueFile extends PreloadFile {
|
|
|
103
105
|
* @todo Introduce Node ID caching.
|
|
104
106
|
* @todo Check modes.
|
|
105
107
|
*/
|
|
106
|
-
export class
|
|
107
|
-
static isAvailable() {
|
|
108
|
-
return true;
|
|
109
|
-
}
|
|
108
|
+
export class SyncStoreFileSystem extends SyncFileSystem {
|
|
110
109
|
constructor(options) {
|
|
111
110
|
super();
|
|
112
111
|
this.store = options.store;
|
|
113
112
|
// INVARIANT: Ensure that the root exists.
|
|
114
113
|
this.makeRootDirectory();
|
|
115
114
|
}
|
|
116
|
-
|
|
117
|
-
return
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
supportsProps() {
|
|
126
|
-
return true;
|
|
127
|
-
}
|
|
128
|
-
supportsSynch() {
|
|
129
|
-
return true;
|
|
115
|
+
get metadata() {
|
|
116
|
+
return {
|
|
117
|
+
name: this.store.name,
|
|
118
|
+
readonly: false,
|
|
119
|
+
supportsProperties: true,
|
|
120
|
+
synchronous: true,
|
|
121
|
+
freeSpace: 0,
|
|
122
|
+
totalSpace: 0,
|
|
123
|
+
};
|
|
130
124
|
}
|
|
131
125
|
/**
|
|
132
126
|
* Delete all contents stored in the file system.
|
|
@@ -136,29 +130,23 @@ export class SyncKeyValueFileSystem extends SynchronousFileSystem {
|
|
|
136
130
|
// INVARIANT: Root always exists.
|
|
137
131
|
this.makeRootDirectory();
|
|
138
132
|
}
|
|
139
|
-
accessSync(p, mode, cred) {
|
|
140
|
-
const tx = this.store.beginTransaction('readonly'), node = this.findINode(tx, p);
|
|
141
|
-
if (!node.toStats().hasAccess(mode, cred)) {
|
|
142
|
-
throw ApiError.EACCES(p);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
133
|
renameSync(oldPath, newPath, cred) {
|
|
146
134
|
const tx = this.store.beginTransaction('readwrite'), oldParent = dirname(oldPath), oldName = basename(oldPath), newParent = dirname(newPath), newName = basename(newPath),
|
|
147
135
|
// Remove oldPath from parent's directory listing.
|
|
148
|
-
oldDirNode = this.findINode(tx, oldParent), oldDirList = this.getDirListing(tx,
|
|
136
|
+
oldDirNode = this.findINode(tx, oldParent), oldDirList = this.getDirListing(tx, oldDirNode, oldParent);
|
|
149
137
|
if (!oldDirNode.toStats().hasAccess(W_OK, cred)) {
|
|
150
138
|
throw ApiError.EACCES(oldPath);
|
|
151
139
|
}
|
|
152
140
|
if (!oldDirList[oldName]) {
|
|
153
141
|
throw ApiError.ENOENT(oldPath);
|
|
154
142
|
}
|
|
155
|
-
const
|
|
143
|
+
const ino = oldDirList[oldName];
|
|
156
144
|
delete oldDirList[oldName];
|
|
157
145
|
// Invariant: Can't move a folder inside itself.
|
|
158
146
|
// This funny little hack ensures that the check passes only if oldPath
|
|
159
147
|
// is a subpath of newParent. We append '/' to avoid matching folders that
|
|
160
148
|
// are a substring of the bottom-most folder in the path.
|
|
161
|
-
if ((newParent + '/').indexOf(oldPath + '/')
|
|
149
|
+
if ((newParent + '/').indexOf(oldPath + '/') == 0) {
|
|
162
150
|
throw new ApiError(ErrorCode.EBUSY, oldParent);
|
|
163
151
|
}
|
|
164
152
|
// Add newPath to parent's directory listing.
|
|
@@ -171,15 +159,15 @@ export class SyncKeyValueFileSystem extends SynchronousFileSystem {
|
|
|
171
159
|
}
|
|
172
160
|
else {
|
|
173
161
|
newDirNode = this.findINode(tx, newParent);
|
|
174
|
-
newDirList = this.getDirListing(tx,
|
|
162
|
+
newDirList = this.getDirListing(tx, newDirNode, newParent);
|
|
175
163
|
}
|
|
176
164
|
if (newDirList[newName]) {
|
|
177
165
|
// If it's a file, delete it.
|
|
178
|
-
const newNameNode = this.getINode(tx,
|
|
179
|
-
if (newNameNode.isFile()) {
|
|
166
|
+
const newNameNode = this.getINode(tx, newDirList[newName], newPath);
|
|
167
|
+
if (newNameNode.toStats().isFile()) {
|
|
180
168
|
try {
|
|
181
|
-
tx.
|
|
182
|
-
tx.
|
|
169
|
+
tx.remove(newNameNode.ino);
|
|
170
|
+
tx.remove(newDirList[newName]);
|
|
183
171
|
}
|
|
184
172
|
catch (e) {
|
|
185
173
|
tx.abort();
|
|
@@ -191,11 +179,11 @@ export class SyncKeyValueFileSystem extends SynchronousFileSystem {
|
|
|
191
179
|
throw ApiError.EPERM(newPath);
|
|
192
180
|
}
|
|
193
181
|
}
|
|
194
|
-
newDirList[newName] =
|
|
182
|
+
newDirList[newName] = ino;
|
|
195
183
|
// Commit the two changed directory listings.
|
|
196
184
|
try {
|
|
197
|
-
tx.put(oldDirNode.
|
|
198
|
-
tx.put(newDirNode.
|
|
185
|
+
tx.put(oldDirNode.ino, encodeDirListing(oldDirList), true);
|
|
186
|
+
tx.put(newDirNode.ino, encodeDirListing(newDirList), true);
|
|
199
187
|
}
|
|
200
188
|
catch (e) {
|
|
201
189
|
tx.abort();
|
|
@@ -212,19 +200,18 @@ export class SyncKeyValueFileSystem extends SynchronousFileSystem {
|
|
|
212
200
|
return stats;
|
|
213
201
|
}
|
|
214
202
|
createFileSync(p, flag, mode, cred) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
return new SyncKeyValueFile(this, p, flag, newFile.toStats(), data);
|
|
203
|
+
this.commitNewFile(p, FileType.FILE, mode, cred);
|
|
204
|
+
return this.openFileSync(p, flag, cred);
|
|
218
205
|
}
|
|
219
206
|
openFileSync(p, flag, cred) {
|
|
220
|
-
const tx = this.store.beginTransaction('readonly'), node = this.findINode(tx, p), data = tx.get(node.
|
|
221
|
-
if (!node.toStats().hasAccess(flag.
|
|
207
|
+
const tx = this.store.beginTransaction('readonly'), node = this.findINode(tx, p), data = tx.get(node.ino);
|
|
208
|
+
if (!node.toStats().hasAccess(flag.mode, cred)) {
|
|
222
209
|
throw ApiError.EACCES(p);
|
|
223
210
|
}
|
|
224
|
-
if (data ===
|
|
211
|
+
if (data === null) {
|
|
225
212
|
throw ApiError.ENOENT(p);
|
|
226
213
|
}
|
|
227
|
-
return new
|
|
214
|
+
return new SyncStoreFile(this, p, flag, node.toStats(), data);
|
|
228
215
|
}
|
|
229
216
|
unlinkSync(p, cred) {
|
|
230
217
|
this.removeEntry(p, false, cred);
|
|
@@ -239,8 +226,7 @@ export class SyncKeyValueFileSystem extends SynchronousFileSystem {
|
|
|
239
226
|
}
|
|
240
227
|
}
|
|
241
228
|
mkdirSync(p, mode, cred) {
|
|
242
|
-
|
|
243
|
-
this.commitNewFile(tx, p, FileType.DIRECTORY, mode, cred, data);
|
|
229
|
+
this.commitNewFile(p, FileType.DIRECTORY, mode, cred, encode('{}'));
|
|
244
230
|
}
|
|
245
231
|
readdirSync(p, cred) {
|
|
246
232
|
const tx = this.store.beginTransaction('readonly');
|
|
@@ -248,28 +234,20 @@ export class SyncKeyValueFileSystem extends SynchronousFileSystem {
|
|
|
248
234
|
if (!node.toStats().hasAccess(R_OK, cred)) {
|
|
249
235
|
throw ApiError.EACCES(p);
|
|
250
236
|
}
|
|
251
|
-
return Object.keys(this.getDirListing(tx,
|
|
252
|
-
}
|
|
253
|
-
chmodSync(p, mode, cred) {
|
|
254
|
-
const fd = this.openFileSync(p, FileFlag.getFileFlag('r+'), cred);
|
|
255
|
-
fd.chmodSync(mode);
|
|
237
|
+
return Object.keys(this.getDirListing(tx, node, p));
|
|
256
238
|
}
|
|
257
|
-
|
|
258
|
-
const fd = this.openFileSync(p, FileFlag.getFileFlag('r+'), cred);
|
|
259
|
-
fd.chownSync(new_uid, new_gid);
|
|
260
|
-
}
|
|
261
|
-
_syncSync(p, data, stats) {
|
|
239
|
+
syncSync(p, data, stats) {
|
|
262
240
|
// @todo Ensure mtime updates properly, and use that to determine if a data
|
|
263
241
|
// update is required.
|
|
264
242
|
const tx = this.store.beginTransaction('readwrite'),
|
|
265
243
|
// We use the _findInode helper because we actually need the INode id.
|
|
266
|
-
fileInodeId = this._findINode(tx, dirname(p), basename(p)), fileInode = this.getINode(tx,
|
|
244
|
+
fileInodeId = this._findINode(tx, dirname(p), basename(p)), fileInode = this.getINode(tx, fileInodeId, p), inodeChanged = fileInode.update(stats);
|
|
267
245
|
try {
|
|
268
246
|
// Sync data.
|
|
269
|
-
tx.put(fileInode.
|
|
247
|
+
tx.put(fileInode.ino, data, true);
|
|
270
248
|
// Sync metadata.
|
|
271
249
|
if (inodeChanged) {
|
|
272
|
-
tx.put(fileInodeId, fileInode.
|
|
250
|
+
tx.put(fileInodeId, fileInode.data, true);
|
|
273
251
|
}
|
|
274
252
|
}
|
|
275
253
|
catch (e) {
|
|
@@ -278,22 +256,47 @@ export class SyncKeyValueFileSystem extends SynchronousFileSystem {
|
|
|
278
256
|
}
|
|
279
257
|
tx.commit();
|
|
280
258
|
}
|
|
259
|
+
linkSync(existing, newpath, cred) {
|
|
260
|
+
const tx = this.store.beginTransaction('readwrite'), existingDir = dirname(existing), existingDirNode = this.findINode(tx, existingDir);
|
|
261
|
+
if (!existingDirNode.toStats().hasAccess(R_OK, cred)) {
|
|
262
|
+
throw ApiError.EACCES(existingDir);
|
|
263
|
+
}
|
|
264
|
+
const newDir = dirname(newpath), newDirNode = this.findINode(tx, newDir), newListing = this.getDirListing(tx, newDirNode, newDir);
|
|
265
|
+
if (!newDirNode.toStats().hasAccess(W_OK, cred)) {
|
|
266
|
+
throw ApiError.EACCES(newDir);
|
|
267
|
+
}
|
|
268
|
+
const ino = this._findINode(tx, existingDir, basename(existing));
|
|
269
|
+
const node = this.getINode(tx, ino, existing);
|
|
270
|
+
if (!node.toStats().hasAccess(W_OK, cred)) {
|
|
271
|
+
throw ApiError.EACCES(newpath);
|
|
272
|
+
}
|
|
273
|
+
node.nlink++;
|
|
274
|
+
newListing[basename(newpath)] = ino;
|
|
275
|
+
try {
|
|
276
|
+
tx.put(ino, node.data, true);
|
|
277
|
+
tx.put(newDirNode.ino, encodeDirListing(newListing), true);
|
|
278
|
+
}
|
|
279
|
+
catch (e) {
|
|
280
|
+
tx.abort();
|
|
281
|
+
throw e;
|
|
282
|
+
}
|
|
283
|
+
tx.commit();
|
|
284
|
+
}
|
|
281
285
|
/**
|
|
282
286
|
* Checks if the root directory exists. Creates it if it doesn't.
|
|
283
287
|
*/
|
|
284
288
|
makeRootDirectory() {
|
|
285
289
|
const tx = this.store.beginTransaction('readwrite');
|
|
286
|
-
if (tx.get(
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
290
|
+
if (tx.get(rootIno)) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
// Create new inode, mode o777, owned by root:root
|
|
294
|
+
const inode = new Inode();
|
|
295
|
+
inode.mode = 0o777 | FileType.DIRECTORY;
|
|
296
|
+
// If the root doesn't exist, the first random ID shouldn't exist either.
|
|
297
|
+
tx.put(inode.ino, encode('{}'), false);
|
|
298
|
+
tx.put(rootIno, inode.data, false);
|
|
299
|
+
tx.commit();
|
|
297
300
|
}
|
|
298
301
|
/**
|
|
299
302
|
* Helper function for findINode.
|
|
@@ -308,30 +311,24 @@ export class SyncKeyValueFileSystem extends SynchronousFileSystem {
|
|
|
308
311
|
throw new ApiError(ErrorCode.EIO, 'Infinite loop detected while finding inode', currentPath);
|
|
309
312
|
}
|
|
310
313
|
visited.add(currentPath);
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
if (dirList[filename]) {
|
|
316
|
-
return dirList[filename];
|
|
317
|
-
}
|
|
318
|
-
else {
|
|
314
|
+
if (parent != '/') {
|
|
315
|
+
const ino = this._findINode(tx, dirname(parent), basename(parent), visited);
|
|
316
|
+
const dir = this.getDirListing(tx, this.getINode(tx, ino, parent + sep + filename), parent);
|
|
317
|
+
if (!(filename in dir)) {
|
|
319
318
|
throw ApiError.ENOENT(resolve(parent, filename));
|
|
320
319
|
}
|
|
321
|
-
|
|
322
|
-
if (parent === '/') {
|
|
323
|
-
if (filename === '') {
|
|
324
|
-
// Return the root's ID.
|
|
325
|
-
return ROOT_NODE_ID;
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
// Find the item in the root node.
|
|
329
|
-
return readDirectory(this.getINode(tx, parent, ROOT_NODE_ID));
|
|
330
|
-
}
|
|
320
|
+
return dir[filename];
|
|
331
321
|
}
|
|
332
|
-
|
|
333
|
-
|
|
322
|
+
if (filename != '') {
|
|
323
|
+
// Find the item in the root node.
|
|
324
|
+
const dir = this.getDirListing(tx, this.getINode(tx, rootIno, parent), parent);
|
|
325
|
+
if (!(filename in dir)) {
|
|
326
|
+
throw ApiError.ENOENT(resolve(parent, filename));
|
|
327
|
+
}
|
|
328
|
+
return dir[filename];
|
|
334
329
|
}
|
|
330
|
+
// Return the root's ID.
|
|
331
|
+
return rootIno;
|
|
335
332
|
}
|
|
336
333
|
/**
|
|
337
334
|
* Finds the Inode of the given path.
|
|
@@ -340,7 +337,8 @@ export class SyncKeyValueFileSystem extends SynchronousFileSystem {
|
|
|
340
337
|
* @todo memoize/cache
|
|
341
338
|
*/
|
|
342
339
|
findINode(tx, p) {
|
|
343
|
-
|
|
340
|
+
const ino = this._findINode(tx, dirname(p), basename(p));
|
|
341
|
+
return this.getINode(tx, ino, p);
|
|
344
342
|
}
|
|
345
343
|
/**
|
|
346
344
|
* Given the ID of a node, retrieves the corresponding Inode.
|
|
@@ -348,26 +346,25 @@ export class SyncKeyValueFileSystem extends SynchronousFileSystem {
|
|
|
348
346
|
* @param p The corresponding path to the file (used for error messages).
|
|
349
347
|
* @param id The ID to look up.
|
|
350
348
|
*/
|
|
351
|
-
getINode(tx,
|
|
352
|
-
const
|
|
353
|
-
if (
|
|
349
|
+
getINode(tx, id, p) {
|
|
350
|
+
const data = tx.get(id);
|
|
351
|
+
if (!data) {
|
|
354
352
|
throw ApiError.ENOENT(p);
|
|
355
353
|
}
|
|
356
|
-
return Inode.
|
|
354
|
+
return new Inode(data.buffer);
|
|
357
355
|
}
|
|
358
356
|
/**
|
|
359
|
-
* Given the Inode of a directory, retrieves the corresponding directory
|
|
360
|
-
* listing.
|
|
357
|
+
* Given the Inode of a directory, retrieves the corresponding directory listing.
|
|
361
358
|
*/
|
|
362
|
-
getDirListing(tx,
|
|
363
|
-
if (!inode.isDirectory()) {
|
|
359
|
+
getDirListing(tx, inode, p) {
|
|
360
|
+
if (!inode.toStats().isDirectory()) {
|
|
364
361
|
throw ApiError.ENOTDIR(p);
|
|
365
362
|
}
|
|
366
|
-
const data = tx.get(inode.
|
|
367
|
-
if (data
|
|
363
|
+
const data = tx.get(inode.ino);
|
|
364
|
+
if (!data) {
|
|
368
365
|
throw ApiError.ENOENT(p);
|
|
369
366
|
}
|
|
370
|
-
return
|
|
367
|
+
return decodeDirListing(data);
|
|
371
368
|
}
|
|
372
369
|
/**
|
|
373
370
|
* Creates a new node under a random ID. Retries 5 times before giving up in
|
|
@@ -376,12 +373,12 @@ export class SyncKeyValueFileSystem extends SynchronousFileSystem {
|
|
|
376
373
|
*/
|
|
377
374
|
addNewNode(tx, data) {
|
|
378
375
|
const retries = 0;
|
|
379
|
-
let
|
|
376
|
+
let ino;
|
|
380
377
|
while (retries < 5) {
|
|
381
378
|
try {
|
|
382
|
-
|
|
383
|
-
tx.put(
|
|
384
|
-
return
|
|
379
|
+
ino = randomIno();
|
|
380
|
+
tx.put(ino, data, false);
|
|
381
|
+
return ino;
|
|
385
382
|
}
|
|
386
383
|
catch (e) {
|
|
387
384
|
// Ignore and reroll.
|
|
@@ -390,8 +387,7 @@ export class SyncKeyValueFileSystem extends SynchronousFileSystem {
|
|
|
390
387
|
throw new ApiError(ErrorCode.EIO, 'Unable to commit data to key-value store.');
|
|
391
388
|
}
|
|
392
389
|
/**
|
|
393
|
-
* Commits a new file (well, a FILE or a DIRECTORY) to the file system with
|
|
394
|
-
* the given mode.
|
|
390
|
+
* Commits a new file (well, a FILE or a DIRECTORY) to the file system with the given mode.
|
|
395
391
|
* Note: This will commit the transaction.
|
|
396
392
|
* @param p The path to the new file.
|
|
397
393
|
* @param type The type of the new file.
|
|
@@ -399,15 +395,16 @@ export class SyncKeyValueFileSystem extends SynchronousFileSystem {
|
|
|
399
395
|
* @param data The data to store at the file's data node.
|
|
400
396
|
* @return The Inode for the new file.
|
|
401
397
|
*/
|
|
402
|
-
commitNewFile(
|
|
403
|
-
const parentDir = dirname(p), fname = basename(p), parentNode = this.findINode(tx, parentDir), dirListing = this.getDirListing(tx,
|
|
398
|
+
commitNewFile(p, type, mode, cred, data = new Uint8Array()) {
|
|
399
|
+
const tx = this.store.beginTransaction('readwrite'), parentDir = dirname(p), fname = basename(p), parentNode = this.findINode(tx, parentDir), dirListing = this.getDirListing(tx, parentNode, parentDir);
|
|
404
400
|
//Check that the creater has correct access
|
|
405
|
-
if (!parentNode.toStats().hasAccess(
|
|
401
|
+
if (!parentNode.toStats().hasAccess(W_OK, cred)) {
|
|
406
402
|
throw ApiError.EACCES(p);
|
|
407
403
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
404
|
+
/* Invariant: The root always exists.
|
|
405
|
+
If we don't check this prior to taking steps below,
|
|
406
|
+
we will create a file with name '' in root should p == '/'.
|
|
407
|
+
*/
|
|
411
408
|
if (p === '/') {
|
|
412
409
|
throw ApiError.EEXIST(p);
|
|
413
410
|
}
|
|
@@ -415,16 +412,17 @@ export class SyncKeyValueFileSystem extends SynchronousFileSystem {
|
|
|
415
412
|
if (dirListing[fname]) {
|
|
416
413
|
throw ApiError.EEXIST(p);
|
|
417
414
|
}
|
|
418
|
-
|
|
415
|
+
const fileNode = new Inode();
|
|
419
416
|
try {
|
|
420
417
|
// Commit data.
|
|
421
|
-
|
|
422
|
-
fileNode =
|
|
423
|
-
|
|
424
|
-
|
|
418
|
+
fileNode.ino = this.addNewNode(tx, data);
|
|
419
|
+
fileNode.size = data.length;
|
|
420
|
+
fileNode.mode = mode | type;
|
|
421
|
+
fileNode.uid = cred.uid;
|
|
422
|
+
fileNode.gid = cred.gid;
|
|
425
423
|
// Update and commit parent directory listing.
|
|
426
|
-
dirListing[fname] =
|
|
427
|
-
tx.put(parentNode.
|
|
424
|
+
dirListing[fname] = this.addNewNode(tx, fileNode.data);
|
|
425
|
+
tx.put(parentNode.ino, encodeDirListing(dirListing), true);
|
|
428
426
|
}
|
|
429
427
|
catch (e) {
|
|
430
428
|
tx.abort();
|
|
@@ -440,31 +438,31 @@ export class SyncKeyValueFileSystem extends SynchronousFileSystem {
|
|
|
440
438
|
* @todo Update mtime.
|
|
441
439
|
*/
|
|
442
440
|
removeEntry(p, isDir, cred) {
|
|
443
|
-
const tx = this.store.beginTransaction('readwrite'), parent = dirname(p), parentNode = this.findINode(tx, parent), parentListing = this.getDirListing(tx,
|
|
444
|
-
if (!
|
|
441
|
+
const tx = this.store.beginTransaction('readwrite'), parent = dirname(p), parentNode = this.findINode(tx, parent), parentListing = this.getDirListing(tx, parentNode, parent), fileName = basename(p), fileIno = parentListing[fileName];
|
|
442
|
+
if (!fileIno) {
|
|
445
443
|
throw ApiError.ENOENT(p);
|
|
446
444
|
}
|
|
447
|
-
const fileNodeId = parentListing[fileName];
|
|
448
445
|
// Get file inode.
|
|
449
|
-
const fileNode = this.getINode(tx,
|
|
446
|
+
const fileNode = this.getINode(tx, fileIno, p);
|
|
450
447
|
if (!fileNode.toStats().hasAccess(W_OK, cred)) {
|
|
451
448
|
throw ApiError.EACCES(p);
|
|
452
449
|
}
|
|
453
450
|
// Remove from directory listing of parent.
|
|
454
451
|
delete parentListing[fileName];
|
|
455
|
-
if (!isDir && fileNode.isDirectory()) {
|
|
452
|
+
if (!isDir && fileNode.toStats().isDirectory()) {
|
|
456
453
|
throw ApiError.EISDIR(p);
|
|
457
454
|
}
|
|
458
|
-
|
|
455
|
+
if (isDir && !fileNode.toStats().isDirectory()) {
|
|
459
456
|
throw ApiError.ENOTDIR(p);
|
|
460
457
|
}
|
|
461
458
|
try {
|
|
462
|
-
// Delete data.
|
|
463
|
-
tx.del(fileNode.id);
|
|
464
|
-
// Delete node.
|
|
465
|
-
tx.del(fileNodeId);
|
|
466
459
|
// Update directory listing.
|
|
467
|
-
tx.put(parentNode.
|
|
460
|
+
tx.put(parentNode.ino, encodeDirListing(parentListing), true);
|
|
461
|
+
if (--fileNode.nlink < 1) {
|
|
462
|
+
// remove file
|
|
463
|
+
tx.remove(fileNode.ino);
|
|
464
|
+
tx.remove(fileIno);
|
|
465
|
+
}
|
|
468
466
|
}
|
|
469
467
|
catch (e) {
|
|
470
468
|
tx.abort();
|