@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.
Files changed (44) hide show
  1. package/dist/ApiError.d.ts +51 -14
  2. package/dist/ApiError.js +60 -34
  3. package/dist/FileIndex.d.ts +32 -35
  4. package/dist/FileIndex.js +93 -109
  5. package/dist/backends/AsyncMirror.d.ts +42 -43
  6. package/dist/backends/AsyncMirror.js +146 -133
  7. package/dist/backends/AsyncStore.d.ts +29 -28
  8. package/dist/backends/AsyncStore.js +139 -189
  9. package/dist/backends/InMemory.d.ts +16 -13
  10. package/dist/backends/InMemory.js +29 -14
  11. package/dist/backends/Locked.d.ts +8 -28
  12. package/dist/backends/Locked.js +44 -148
  13. package/dist/backends/OverlayFS.d.ts +26 -34
  14. package/dist/backends/OverlayFS.js +208 -371
  15. package/dist/backends/SyncStore.d.ts +54 -72
  16. package/dist/backends/SyncStore.js +159 -161
  17. package/dist/backends/backend.d.ts +45 -29
  18. package/dist/backends/backend.js +83 -13
  19. package/dist/backends/index.d.ts +6 -7
  20. package/dist/backends/index.js +5 -6
  21. package/dist/browser.min.js +5 -7
  22. package/dist/browser.min.js.map +4 -4
  23. package/dist/emulation/callbacks.d.ts +36 -67
  24. package/dist/emulation/callbacks.js +90 -46
  25. package/dist/emulation/constants.js +1 -1
  26. package/dist/emulation/promises.d.ts +228 -129
  27. package/dist/emulation/promises.js +414 -172
  28. package/dist/emulation/shared.d.ts +10 -10
  29. package/dist/emulation/shared.js +18 -20
  30. package/dist/emulation/sync.d.ts +25 -25
  31. package/dist/emulation/sync.js +187 -73
  32. package/dist/file.d.ts +166 -170
  33. package/dist/file.js +199 -218
  34. package/dist/filesystem.d.ts +68 -241
  35. package/dist/filesystem.js +59 -383
  36. package/dist/index.d.ts +7 -44
  37. package/dist/index.js +13 -52
  38. package/dist/inode.d.ts +37 -28
  39. package/dist/inode.js +123 -65
  40. package/dist/stats.d.ts +21 -19
  41. package/dist/stats.js +35 -56
  42. package/dist/utils.d.ts +26 -9
  43. package/dist/utils.js +73 -102
  44. 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 { FileFlag, PreloadFile } from '../file.js';
5
- import { SynchronousFileSystem } from '../filesystem.js';
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 { decode, encode, randomUUID, ROOT_NODE_ID } from '../utils.js';
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(key) {
26
- const val = this.store.get(key);
27
- this.stashOldValue(key, val);
26
+ get(ino) {
27
+ const val = this.store.get(ino);
28
+ this.stashOldValue(ino, val);
28
29
  return val;
29
30
  }
30
- put(key, data, overwrite) {
31
- this.markModified(key);
32
- return this.store.put(key, data, overwrite);
31
+ put(ino, data, overwrite) {
32
+ this.markModified(ino);
33
+ return this.store.put(ino, data, overwrite);
33
34
  }
34
- del(key) {
35
- this.markModified(key);
36
- this.store.del(key);
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[key];
45
+ const value = this.originalData.get(key);
45
46
  if (!value) {
46
47
  // Key didn't exist.
47
- this.store.del(key);
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(key, value) {
62
+ stashOldValue(ino, value) {
65
63
  // Keep only the earliest value in the transaction.
66
- if (!this._has(key)) {
67
- this.originalData[key] = value;
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(key) {
75
- if (this.modifiedKeys.indexOf(key) === -1) {
76
- this.modifiedKeys.push(key);
77
- if (!this._has(key)) {
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 SyncKeyValueFile extends PreloadFile {
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._fs._syncSync(this.getPath(), this.getBuffer(), this.getStats());
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 SyncKeyValueFileSystem extends SynchronousFileSystem {
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
- getName() {
117
- return this.store.name();
118
- }
119
- isReadOnly() {
120
- return false;
121
- }
122
- supportsSymlinks() {
123
- return false;
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, oldParent, oldDirNode);
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 nodeId = oldDirList[oldName];
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 + '/') === 0) {
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, newParent, newDirNode);
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, newPath, newDirList[newName]);
179
- if (newNameNode.isFile()) {
166
+ const newNameNode = this.getINode(tx, newDirList[newName], newPath);
167
+ if (newNameNode.toStats().isFile()) {
180
168
  try {
181
- tx.del(newNameNode.id);
182
- tx.del(newDirList[newName]);
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] = nodeId;
182
+ newDirList[newName] = ino;
195
183
  // Commit the two changed directory listings.
196
184
  try {
197
- tx.put(oldDirNode.id, encode(JSON.stringify(oldDirList)), true);
198
- tx.put(newDirNode.id, encode(JSON.stringify(newDirList)), true);
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
- const tx = this.store.beginTransaction('readwrite'), data = new Uint8Array(0), newFile = this.commitNewFile(tx, p, FileType.FILE, mode, cred, data);
216
- // Open the file.
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.id);
221
- if (!node.toStats().hasAccess(flag.getMode(), cred)) {
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 === undefined) {
211
+ if (data === null) {
225
212
  throw ApiError.ENOENT(p);
226
213
  }
227
- return new SyncKeyValueFile(this, p, flag, node.toStats(), data);
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
- const tx = this.store.beginTransaction('readwrite'), data = encode('{}');
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, p, node));
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
- chownSync(p, new_uid, new_gid, cred) {
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, p, fileInodeId), inodeChanged = fileInode.update(stats);
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.id, data, true);
247
+ tx.put(fileInode.ino, data, true);
270
248
  // Sync metadata.
271
249
  if (inodeChanged) {
272
- tx.put(fileInodeId, fileInode.serialize(), true);
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(ROOT_NODE_ID) === undefined) {
287
- // Create new inode.
288
- const currTime = new Date().getTime(),
289
- // Mode 0666, owned by root:root
290
- dirInode = new Inode(randomUUID(), 4096, 511 | FileType.DIRECTORY, currTime, currTime, currTime, 0, 0);
291
- // If the root doesn't exist, the first random ID shouldn't exist,
292
- // either.
293
- tx.put(dirInode.id, encode('{}'), false);
294
- tx.put(ROOT_NODE_ID, dirInode.serialize(), false);
295
- tx.commit();
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
- const readDirectory = (inode) => {
312
- // Get the root's directory listing.
313
- const dirList = this.getDirListing(tx, parent, inode);
314
- // Get the file's ID.
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
- else {
333
- return readDirectory(this.getINode(tx, parent + sep + filename, this._findINode(tx, dirname(parent), basename(parent), visited)));
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
- return this.getINode(tx, p, this._findINode(tx, dirname(p), basename(p)));
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, p, id) {
352
- const inode = tx.get(id);
353
- if (inode === undefined) {
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.Deserialize(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, p, inode) {
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.id);
367
- if (data === undefined) {
363
+ const data = tx.get(inode.ino);
364
+ if (!data) {
368
365
  throw ApiError.ENOENT(p);
369
366
  }
370
- return JSON.parse(decode(data));
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 currId;
376
+ let ino;
380
377
  while (retries < 5) {
381
378
  try {
382
- currId = randomUUID();
383
- tx.put(currId, data, false);
384
- return currId;
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(tx, p, type, mode, cred, data) {
403
- const parentDir = dirname(p), fname = basename(p), parentNode = this.findINode(tx, parentDir), dirListing = this.getDirListing(tx, parentDir, parentNode), currTime = new Date().getTime();
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(0b0100 /* Write */, cred)) {
401
+ if (!parentNode.toStats().hasAccess(W_OK, cred)) {
406
402
  throw ApiError.EACCES(p);
407
403
  }
408
- // Invariant: The root always exists.
409
- // If we don't check this prior to taking steps below, we will create a
410
- // file with name '' in root should p == '/'.
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
- let fileNode;
415
+ const fileNode = new Inode();
419
416
  try {
420
417
  // Commit data.
421
- const dataId = this.addNewNode(tx, data);
422
- fileNode = new Inode(dataId, data.length, mode | type, currTime, currTime, currTime, cred.uid, cred.gid);
423
- // Commit file node.
424
- const fileNodeId = this.addNewNode(tx, fileNode.serialize());
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] = fileNodeId;
427
- tx.put(parentNode.id, encode(JSON.stringify(dirListing)), true);
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, parent, parentNode), fileName = basename(p);
444
- if (!parentListing[fileName]) {
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, p, fileNodeId);
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
- else if (isDir && !fileNode.isDirectory()) {
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.id, encode(JSON.stringify(parentListing)), true);
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();