@zenfs/core 1.2.9 → 1.3.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 (57) hide show
  1. package/dist/backends/fetch.js +1 -1
  2. package/dist/backends/memory.d.ts +1 -2
  3. package/dist/backends/overlay.js +2 -3
  4. package/dist/backends/port/fs.d.ts +2 -15
  5. package/dist/backends/store/fs.d.ts +17 -28
  6. package/dist/backends/store/fs.js +169 -191
  7. package/dist/backends/store/simple.d.ts +20 -21
  8. package/dist/backends/store/simple.js +24 -24
  9. package/dist/backends/store/store.d.ts +22 -23
  10. package/dist/backends/store/store.js +6 -6
  11. package/dist/config.d.ts +8 -0
  12. package/dist/config.js +2 -1
  13. package/dist/devices.d.ts +2 -3
  14. package/dist/emulation/cache.d.ts +40 -31
  15. package/dist/emulation/cache.js +62 -53
  16. package/dist/emulation/index.d.ts +1 -1
  17. package/dist/emulation/index.js +1 -1
  18. package/dist/emulation/promises.js +18 -17
  19. package/dist/emulation/shared.d.ts +6 -0
  20. package/dist/emulation/shared.js +13 -1
  21. package/dist/emulation/sync.d.ts +1 -1
  22. package/dist/emulation/sync.js +16 -15
  23. package/dist/file.d.ts +3 -15
  24. package/dist/file.js +7 -19
  25. package/dist/inode.d.ts +4 -13
  26. package/dist/inode.js +22 -29
  27. package/dist/mixins/async.d.ts +15 -10
  28. package/dist/mixins/async.js +3 -1
  29. package/dist/stats.js +30 -5
  30. package/dist/utils.d.ts +5 -7
  31. package/dist/utils.js +11 -20
  32. package/package.json +1 -1
  33. package/src/backends/fetch.ts +1 -1
  34. package/src/backends/memory.ts +1 -2
  35. package/src/backends/overlay.ts +2 -2
  36. package/src/backends/store/fs.ts +187 -220
  37. package/src/backends/store/simple.ts +36 -37
  38. package/src/backends/store/store.ts +25 -26
  39. package/src/config.ts +11 -1
  40. package/src/devices.ts +2 -3
  41. package/src/emulation/cache.ts +68 -60
  42. package/src/emulation/index.ts +1 -1
  43. package/src/emulation/promises.ts +20 -19
  44. package/src/emulation/shared.ts +13 -1
  45. package/src/emulation/sync.ts +16 -15
  46. package/src/file.ts +9 -21
  47. package/src/inode.ts +10 -31
  48. package/src/mixins/async.ts +27 -24
  49. package/src/stats.ts +47 -5
  50. package/src/utils.ts +11 -23
  51. package/tests/fs/dir.test.ts +21 -31
  52. package/tests/fs/directory.test.ts +6 -4
  53. package/tests/fs/links.test.ts +9 -2
  54. package/tests/fs/permissions.test.ts +2 -2
  55. package/tests/fs/stat.test.ts +42 -0
  56. package/tests/fs/times.test.ts +28 -28
  57. package/tests/setup/cow+fetch.ts +4 -2
@@ -46,13 +46,14 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
46
46
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
47
47
  });
48
48
  import { credentials } from '../../credentials.js';
49
- import { S_IFDIR, S_IFREG } from '../../emulation/constants.js';
50
- import { basename, dirname, join, resolve } from '../../emulation/path.js';
49
+ import { S_IFDIR, S_IFREG, S_ISGID, S_ISUID } from '../../emulation/constants.js';
50
+ import { basename, dirname, parse, resolve } from '../../emulation/path.js';
51
51
  import { Errno, ErrnoError } from '../../error.js';
52
52
  import { PreloadFile } from '../../file.js';
53
53
  import { FileSystem } from '../../filesystem.js';
54
- import { Inode, randomIno, rootIno } from '../../inode.js';
55
- import { decodeDirListing, encodeUTF8, encodeDirListing } from '../../utils.js';
54
+ import { Inode, rootIno } from '../../inode.js';
55
+ import { decodeDirListing, encodeDirListing, encodeUTF8, randomBigInt } from '../../utils.js';
56
+ import { serialize } from 'utilium';
56
57
  const maxInodeAllocTries = 5;
57
58
  /**
58
59
  * A file system which uses a key-value store.
@@ -106,40 +107,40 @@ export class StoreFS extends FileSystem {
106
107
  const env_1 = { stack: [], error: void 0, hasError: false };
107
108
  try {
108
109
  const tx = __addDisposableResource(env_1, this.store.transaction(), true);
109
- const oldParent = dirname(oldPath), oldName = basename(oldPath), newParent = dirname(newPath), newName = basename(newPath),
110
+ const _old = parse(oldPath), _new = parse(newPath),
110
111
  // Remove oldPath from parent's directory listing.
111
- oldDirNode = await this.findINode(tx, oldParent), oldDirList = await this.getDirListing(tx, oldDirNode, oldParent);
112
- if (!oldDirList[oldName]) {
112
+ oldDirNode = await this.findInode(tx, _old.dir, 'rename'), oldDirList = decodeDirListing(await this.get(tx, oldDirNode.data, _old.dir, 'rename'));
113
+ if (!oldDirList[_old.base]) {
113
114
  throw ErrnoError.With('ENOENT', oldPath, 'rename');
114
115
  }
115
- const nodeId = oldDirList[oldName];
116
- delete oldDirList[oldName];
116
+ const ino = oldDirList[_old.base];
117
+ delete oldDirList[_old.base];
117
118
  /*
118
119
  Can't move a folder inside itself.
119
- This ensures that the check passes only if `oldPath` is a subpath of `newParent`.
120
+ This ensures that the check passes only if `oldPath` is a subpath of `_new.dir`.
120
121
  We append '/' to avoid matching folders that are a substring of the bottom-most folder in the path.
121
122
  */
122
- if ((newParent + '/').indexOf(oldPath + '/') === 0) {
123
- throw new ErrnoError(Errno.EBUSY, oldParent);
123
+ if ((_new.dir + '/').indexOf(oldPath + '/') === 0) {
124
+ throw new ErrnoError(Errno.EBUSY, _old.dir);
124
125
  }
125
126
  // Add newPath to parent's directory listing.
126
- const sameParent = newParent === oldParent;
127
- // Prevent us from re-grabbing the same directory listing, which still contains `oldName.`
128
- const newDirNode = sameParent ? oldDirNode : await this.findINode(tx, newParent);
129
- const newDirList = sameParent ? oldDirList : await this.getDirListing(tx, newDirNode, newParent);
130
- if (newDirList[newName]) {
127
+ const sameParent = _new.dir == _old.dir;
128
+ // Prevent us from re-grabbing the same directory listing, which still contains `old_path.base.`
129
+ const newDirNode = sameParent ? oldDirNode : await this.findInode(tx, _new.dir, 'rename');
130
+ const newDirList = sameParent ? oldDirList : decodeDirListing(await this.get(tx, newDirNode.data, _new.dir, 'rename'));
131
+ if (newDirList[_new.base]) {
131
132
  // If it's a file, delete it, if it's a directory, throw a permissions error.
132
- const newNameNode = await this.getINode(tx, newDirList[newName], newPath);
133
- if (!newNameNode.toStats().isFile()) {
133
+ const existing = new Inode(await this.get(tx, newDirList[_new.base], newPath, 'rename'));
134
+ if (!existing.toStats().isFile()) {
134
135
  throw ErrnoError.With('EPERM', newPath, 'rename');
135
136
  }
136
- await tx.remove(newNameNode.ino);
137
- await tx.remove(newDirList[newName]);
137
+ await tx.remove(existing.data);
138
+ await tx.remove(newDirList[_new.base]);
138
139
  }
139
- newDirList[newName] = nodeId;
140
+ newDirList[_new.base] = ino;
140
141
  // Commit the two changed directory listings.
141
- await tx.set(oldDirNode.ino, encodeDirListing(oldDirList));
142
- await tx.set(newDirNode.ino, encodeDirListing(newDirList));
142
+ await tx.set(oldDirNode.data, encodeDirListing(oldDirList));
143
+ await tx.set(newDirNode.data, encodeDirListing(newDirList));
143
144
  await tx.commit();
144
145
  }
145
146
  catch (e_1) {
@@ -156,40 +157,40 @@ export class StoreFS extends FileSystem {
156
157
  const env_2 = { stack: [], error: void 0, hasError: false };
157
158
  try {
158
159
  const tx = __addDisposableResource(env_2, this.store.transaction(), false);
159
- const oldParent = dirname(oldPath), oldName = basename(oldPath), newParent = dirname(newPath), newName = basename(newPath),
160
+ const _old = parse(oldPath), _new = parse(newPath),
160
161
  // Remove oldPath from parent's directory listing.
161
- oldDirNode = this.findINodeSync(tx, oldParent), oldDirList = this.getDirListingSync(tx, oldDirNode, oldParent);
162
- if (!oldDirList[oldName]) {
162
+ oldDirNode = this.findInodeSync(tx, _old.dir, 'rename'), oldDirList = decodeDirListing(this.getSync(tx, oldDirNode.data, _old.dir, 'rename'));
163
+ if (!oldDirList[_old.base]) {
163
164
  throw ErrnoError.With('ENOENT', oldPath, 'rename');
164
165
  }
165
- const ino = oldDirList[oldName];
166
- delete oldDirList[oldName];
166
+ const ino = oldDirList[_old.base];
167
+ delete oldDirList[_old.base];
167
168
  /*
168
169
  Can't move a folder inside itself.
169
- This ensures that the check passes only if `oldPath` is a subpath of `newParent`.
170
+ This ensures that the check passes only if `oldPath` is a subpath of `_new.dir`.
170
171
  We append '/' to avoid matching folders that are a substring of the bottom-most folder in the path.
171
172
  */
172
- if ((newParent + '/').indexOf(oldPath + '/') == 0) {
173
- throw new ErrnoError(Errno.EBUSY, oldParent);
173
+ if ((_new.dir + '/').indexOf(oldPath + '/') == 0) {
174
+ throw new ErrnoError(Errno.EBUSY, _old.dir);
174
175
  }
175
176
  // Add newPath to parent's directory listing.
176
- const sameParent = newParent === oldParent;
177
- // Prevent us from re-grabbing the same directory listing, which still contains `oldName.`
178
- const newDirNode = sameParent ? oldDirNode : this.findINodeSync(tx, newParent);
179
- const newDirList = sameParent ? oldDirList : this.getDirListingSync(tx, newDirNode, newParent);
180
- if (newDirList[newName]) {
177
+ const sameParent = _new.dir === _old.dir;
178
+ // Prevent us from re-grabbing the same directory listing, which still contains `old_path.base.`
179
+ const newDirNode = sameParent ? oldDirNode : this.findInodeSync(tx, _new.dir, 'rename');
180
+ const newDirList = sameParent ? oldDirList : decodeDirListing(this.getSync(tx, newDirNode.data, _new.dir, 'rename'));
181
+ if (newDirList[_new.base]) {
181
182
  // If it's a file, delete it, if it's a directory, throw a permissions error.
182
- const newNameNode = this.getINodeSync(tx, newDirList[newName], newPath);
183
- if (!newNameNode.toStats().isFile()) {
183
+ const existing = new Inode(this.getSync(tx, newDirList[_new.base], newPath, 'rename'));
184
+ if (!existing.toStats().isFile()) {
184
185
  throw ErrnoError.With('EPERM', newPath, 'rename');
185
186
  }
186
- tx.removeSync(newNameNode.ino);
187
- tx.removeSync(newDirList[newName]);
187
+ tx.removeSync(existing.data);
188
+ tx.removeSync(newDirList[_new.base]);
188
189
  }
189
- newDirList[newName] = ino;
190
+ newDirList[_new.base] = ino;
190
191
  // Commit the two changed directory listings.
191
- tx.setSync(oldDirNode.ino, encodeDirListing(oldDirList));
192
- tx.setSync(newDirNode.ino, encodeDirListing(newDirList));
192
+ tx.setSync(oldDirNode.data, encodeDirListing(oldDirList));
193
+ tx.setSync(newDirNode.data, encodeDirListing(newDirList));
193
194
  tx.commitSync();
194
195
  }
195
196
  catch (e_2) {
@@ -204,7 +205,7 @@ export class StoreFS extends FileSystem {
204
205
  const env_3 = { stack: [], error: void 0, hasError: false };
205
206
  try {
206
207
  const tx = __addDisposableResource(env_3, this.store.transaction(), true);
207
- return (await this.findINode(tx, path)).toStats();
208
+ return (await this.findInode(tx, path, 'stat')).toStats();
208
209
  }
209
210
  catch (e_3) {
210
211
  env_3.error = e_3;
@@ -220,7 +221,7 @@ export class StoreFS extends FileSystem {
220
221
  const env_4 = { stack: [], error: void 0, hasError: false };
221
222
  try {
222
223
  const tx = __addDisposableResource(env_4, this.store.transaction(), false);
223
- return this.findINodeSync(tx, path).toStats();
224
+ return this.findInodeSync(tx, path, 'stat').toStats();
224
225
  }
225
226
  catch (e_4) {
226
227
  env_4.error = e_4;
@@ -231,21 +232,19 @@ export class StoreFS extends FileSystem {
231
232
  }
232
233
  }
233
234
  async createFile(path, flag, mode) {
234
- const node = await this.commitNew(path, S_IFREG, mode, new Uint8Array(0));
235
- return new PreloadFile(this, path, flag, node.toStats(), new Uint8Array(0));
235
+ const node = await this.commitNew(path, S_IFREG, mode, new Uint8Array(), 'createFile');
236
+ return new PreloadFile(this, path, flag, node.toStats(), new Uint8Array());
236
237
  }
237
238
  createFileSync(path, flag, mode) {
238
- this.commitNewSync(path, S_IFREG, mode);
239
- return this.openFileSync(path, flag);
239
+ const node = this.commitNewSync(path, S_IFREG, mode, new Uint8Array(), 'createFile');
240
+ return new PreloadFile(this, path, flag, node.toStats(), new Uint8Array());
240
241
  }
241
242
  async openFile(path, flag) {
242
243
  const env_5 = { stack: [], error: void 0, hasError: false };
243
244
  try {
244
245
  const tx = __addDisposableResource(env_5, this.store.transaction(), true);
245
- const node = await this.findINode(tx, path), data = await tx.get(node.ino);
246
- if (!data) {
247
- throw ErrnoError.With('ENOENT', path, 'openFile');
248
- }
246
+ const node = await this.findInode(tx, path, 'openFile');
247
+ const data = await this.get(tx, node.data, path, 'openFile');
249
248
  return new PreloadFile(this, path, flag, node.toStats(), data);
250
249
  }
251
250
  catch (e_5) {
@@ -262,10 +261,8 @@ export class StoreFS extends FileSystem {
262
261
  const env_6 = { stack: [], error: void 0, hasError: false };
263
262
  try {
264
263
  const tx = __addDisposableResource(env_6, this.store.transaction(), false);
265
- const node = this.findINodeSync(tx, path), data = tx.getSync(node.ino);
266
- if (!data) {
267
- throw ErrnoError.With('ENOENT', path, 'openFile');
268
- }
264
+ const node = this.findInodeSync(tx, path, 'openFile');
265
+ const data = this.getSync(tx, node.data, path, 'openFile');
269
266
  return new PreloadFile(this, path, flag, node.toStats(), data);
270
267
  }
271
268
  catch (e_6) {
@@ -277,35 +274,35 @@ export class StoreFS extends FileSystem {
277
274
  }
278
275
  }
279
276
  async unlink(path) {
280
- return this.remove(path, false);
277
+ return this.remove(path, false, 'unlink');
281
278
  }
282
279
  unlinkSync(path) {
283
- this.removeSync(path, false);
280
+ this.removeSync(path, false, 'unlink');
284
281
  }
285
282
  async rmdir(path) {
286
283
  if ((await this.readdir(path)).length) {
287
284
  throw ErrnoError.With('ENOTEMPTY', path, 'rmdir');
288
285
  }
289
- await this.remove(path, true);
286
+ await this.remove(path, true, 'rmdir');
290
287
  }
291
288
  rmdirSync(path) {
292
289
  if (this.readdirSync(path).length) {
293
290
  throw ErrnoError.With('ENOTEMPTY', path, 'rmdir');
294
291
  }
295
- this.removeSync(path, true);
292
+ this.removeSync(path, true, 'rmdir');
296
293
  }
297
294
  async mkdir(path, mode) {
298
- await this.commitNew(path, S_IFDIR, mode, encodeUTF8('{}'));
295
+ await this.commitNew(path, S_IFDIR, mode, encodeUTF8('{}'), 'mkdir');
299
296
  }
300
297
  mkdirSync(path, mode) {
301
- this.commitNewSync(path, S_IFDIR, mode, encodeUTF8('{}'));
298
+ this.commitNewSync(path, S_IFDIR, mode, encodeUTF8('{}'), 'mkdir');
302
299
  }
303
300
  async readdir(path) {
304
301
  const env_7 = { stack: [], error: void 0, hasError: false };
305
302
  try {
306
303
  const tx = __addDisposableResource(env_7, this.store.transaction(), true);
307
- const node = await this.findINode(tx, path);
308
- return Object.keys(await this.getDirListing(tx, node, path));
304
+ const node = await this.findInode(tx, path, 'readdir');
305
+ return Object.keys(decodeDirListing(await this.get(tx, node.data, path, 'readdir')));
309
306
  }
310
307
  catch (e_7) {
311
308
  env_7.error = e_7;
@@ -321,8 +318,8 @@ export class StoreFS extends FileSystem {
321
318
  const env_8 = { stack: [], error: void 0, hasError: false };
322
319
  try {
323
320
  const tx = __addDisposableResource(env_8, this.store.transaction(), false);
324
- const node = this.findINodeSync(tx, path);
325
- return Object.keys(this.getDirListingSync(tx, node, path));
321
+ const node = this.findInodeSync(tx, path, 'readdir');
322
+ return Object.keys(decodeDirListing(this.getSync(tx, node.data, path, 'readdir')));
326
323
  }
327
324
  catch (e_8) {
328
325
  env_8.error = e_8;
@@ -341,12 +338,12 @@ export class StoreFS extends FileSystem {
341
338
  try {
342
339
  const tx = __addDisposableResource(env_9, this.store.transaction(), true);
343
340
  // We use _findInode because we actually need the INode id.
344
- const fileInodeId = await this._findINode(tx, dirname(path), basename(path)), fileInode = await this.getINode(tx, fileInodeId, path), inodeChanged = fileInode.update(stats);
341
+ const fileInodeId = await this._findInode(tx, path, 'sync'), fileInode = new Inode(await this.get(tx, fileInodeId, path, 'sync')), inodeChanged = fileInode.update(stats);
345
342
  // Sync data.
346
- await tx.set(fileInode.ino, data);
343
+ await tx.set(fileInode.data, data);
347
344
  // Sync metadata.
348
345
  if (inodeChanged) {
349
- await tx.set(fileInodeId, fileInode.data);
346
+ await tx.set(fileInodeId, serialize(fileInode));
350
347
  }
351
348
  await tx.commit();
352
349
  }
@@ -369,12 +366,12 @@ export class StoreFS extends FileSystem {
369
366
  try {
370
367
  const tx = __addDisposableResource(env_10, this.store.transaction(), false);
371
368
  // We use _findInode because we actually need the INode id.
372
- const fileInodeId = this._findINodeSync(tx, dirname(path), basename(path)), fileInode = this.getINodeSync(tx, fileInodeId, path), inodeChanged = fileInode.update(stats);
369
+ const fileInodeId = this._findInodeSync(tx, path, 'sync'), fileInode = new Inode(this.getSync(tx, fileInodeId, path, 'sync')), inodeChanged = fileInode.update(stats);
373
370
  // Sync data.
374
- tx.setSync(fileInode.ino, data);
371
+ tx.setSync(fileInode.data, data);
375
372
  // Sync metadata.
376
373
  if (inodeChanged) {
377
- tx.setSync(fileInodeId, fileInode.data);
374
+ tx.setSync(fileInodeId, serialize(fileInode));
378
375
  }
379
376
  tx.commitSync();
380
377
  }
@@ -390,13 +387,13 @@ export class StoreFS extends FileSystem {
390
387
  const env_11 = { stack: [], error: void 0, hasError: false };
391
388
  try {
392
389
  const tx = __addDisposableResource(env_11, this.store.transaction(), true);
393
- const newDir = dirname(link), newDirNode = await this.findINode(tx, newDir), listing = await this.getDirListing(tx, newDirNode, newDir);
394
- const ino = await this._findINode(tx, dirname(target), basename(target));
395
- const node = await this.getINode(tx, ino, target);
390
+ const newDir = dirname(link), newDirNode = await this.findInode(tx, newDir, 'link'), listing = decodeDirListing(await this.get(tx, newDirNode.data, newDir, 'link'));
391
+ const ino = await this._findInode(tx, target, 'link');
392
+ const node = new Inode(await this.get(tx, ino, target, 'link'));
396
393
  node.nlink++;
397
394
  listing[basename(link)] = ino;
398
- tx.setSync(ino, node.data);
399
- tx.setSync(newDirNode.ino, encodeDirListing(listing));
395
+ tx.setSync(ino, serialize(node));
396
+ tx.setSync(newDirNode.data, encodeDirListing(listing));
400
397
  tx.commitSync();
401
398
  }
402
399
  catch (e_11) {
@@ -413,13 +410,13 @@ export class StoreFS extends FileSystem {
413
410
  const env_12 = { stack: [], error: void 0, hasError: false };
414
411
  try {
415
412
  const tx = __addDisposableResource(env_12, this.store.transaction(), false);
416
- const newDir = dirname(link), newDirNode = this.findINodeSync(tx, newDir), listing = this.getDirListingSync(tx, newDirNode, newDir);
417
- const ino = this._findINodeSync(tx, dirname(target), basename(target));
418
- const node = this.getINodeSync(tx, ino, target);
413
+ const newDir = dirname(link), newDirNode = this.findInodeSync(tx, newDir, 'link'), listing = decodeDirListing(this.getSync(tx, newDirNode.data, newDir, 'link'));
414
+ const ino = this._findInodeSync(tx, target, 'link');
415
+ const node = new Inode(this.getSync(tx, ino, target, 'link'));
419
416
  node.nlink++;
420
417
  listing[basename(link)] = ino;
421
- tx.setSync(ino, node.data);
422
- tx.setSync(newDirNode.ino, encodeDirListing(listing));
418
+ tx.setSync(ino, serialize(node));
419
+ tx.setSync(newDirNode.data, encodeDirListing(listing));
423
420
  tx.commitSync();
424
421
  }
425
422
  catch (e_12) {
@@ -442,10 +439,11 @@ export class StoreFS extends FileSystem {
442
439
  }
443
440
  // Create new inode. o777, owned by root:root
444
441
  const inode = new Inode();
442
+ inode.ino = rootIno;
445
443
  inode.mode = 0o777 | S_IFDIR;
446
444
  // If the root doesn't exist, the first random ID shouldn't exist either.
447
- await tx.set(inode.ino, encodeUTF8('{}'));
448
- await tx.set(rootIno, inode.data);
445
+ await tx.set(inode.data, encodeUTF8('{}'));
446
+ await tx.set(rootIno, serialize(inode));
449
447
  await tx.commit();
450
448
  }
451
449
  catch (e_13) {
@@ -470,10 +468,11 @@ export class StoreFS extends FileSystem {
470
468
  }
471
469
  // Create new inode, mode o777, owned by root:root
472
470
  const inode = new Inode();
471
+ inode.ino = rootIno;
473
472
  inode.mode = 0o777 | S_IFDIR;
474
473
  // If the root doesn't exist, the first random ID shouldn't exist either.
475
- tx.setSync(inode.ino, encodeUTF8('{}'));
476
- tx.setSync(rootIno, inode.data);
474
+ tx.setSync(inode.data, encodeUTF8('{}'));
475
+ tx.setSync(rootIno, serialize(inode));
477
476
  tx.commitSync();
478
477
  }
479
478
  catch (e_14) {
@@ -490,19 +489,19 @@ export class StoreFS extends FileSystem {
490
489
  * @param filename The filename of the inode we are attempting to find, minus
491
490
  * the parent.
492
491
  */
493
- async _findINode(tx, parent, filename, visited = new Set()) {
494
- const currentPath = join(parent, filename);
495
- if (visited.has(currentPath)) {
496
- throw new ErrnoError(Errno.EIO, 'Infinite loop detected while finding inode', currentPath);
492
+ async _findInode(tx, path, syscall, visited = new Set()) {
493
+ if (visited.has(path)) {
494
+ throw new ErrnoError(Errno.EIO, 'Infinite loop detected while finding inode', path);
497
495
  }
498
- visited.add(currentPath);
499
- if (parent == '/' && filename === '') {
496
+ visited.add(path);
497
+ if (path == '/') {
500
498
  return rootIno;
501
499
  }
502
- const inode = parent == '/' ? await this.getINode(tx, rootIno, parent) : await this.findINode(tx, parent, visited);
503
- const dirList = await this.getDirListing(tx, inode, parent);
500
+ const { dir: parent, base: filename } = parse(path);
501
+ const inode = parent == '/' ? new Inode(await this.get(tx, rootIno, parent, syscall)) : await this.findInode(tx, parent, syscall, visited);
502
+ const dirList = decodeDirListing(await this.get(tx, inode.data, parent, syscall));
504
503
  if (!(filename in dirList)) {
505
- throw ErrnoError.With('ENOENT', resolve(parent, filename), '_findINode');
504
+ throw ErrnoError.With('ENOENT', resolve(parent, filename), syscall);
506
505
  }
507
506
  return dirList[filename];
508
507
  }
@@ -513,19 +512,19 @@ export class StoreFS extends FileSystem {
513
512
  * the parent.
514
513
  * @return string The ID of the file's inode in the file system.
515
514
  */
516
- _findINodeSync(tx, parent, filename, visited = new Set()) {
517
- const currentPath = join(parent, filename);
518
- if (visited.has(currentPath)) {
519
- throw new ErrnoError(Errno.EIO, 'Infinite loop detected while finding inode', currentPath);
515
+ _findInodeSync(tx, path, syscall, visited = new Set()) {
516
+ if (visited.has(path)) {
517
+ throw new ErrnoError(Errno.EIO, 'Infinite loop detected while finding inode', path);
520
518
  }
521
- visited.add(currentPath);
522
- if (parent == '/' && filename === '') {
519
+ visited.add(path);
520
+ if (path == '/') {
523
521
  return rootIno;
524
522
  }
525
- const inode = parent == '/' ? this.getINodeSync(tx, rootIno, parent) : this.findINodeSync(tx, parent, visited);
526
- const dir = this.getDirListingSync(tx, inode, parent);
523
+ const { dir: parent, base: filename } = parse(path);
524
+ const inode = parent == '/' ? new Inode(this.getSync(tx, rootIno, parent, syscall)) : this.findInodeSync(tx, parent, syscall, visited);
525
+ const dir = decodeDirListing(this.getSync(tx, inode.data, parent, syscall));
527
526
  if (!(filename in dir)) {
528
- throw ErrnoError.With('ENOENT', resolve(parent, filename), '_findINode');
527
+ throw ErrnoError.With('ENOENT', resolve(parent, filename), syscall);
529
528
  }
530
529
  return dir[filename];
531
530
  }
@@ -534,9 +533,9 @@ export class StoreFS extends FileSystem {
534
533
  * @param path The path to look up.
535
534
  * @todo memoize/cache
536
535
  */
537
- async findINode(tx, path, visited = new Set()) {
538
- const id = await this._findINode(tx, dirname(path), basename(path), visited);
539
- return this.getINode(tx, id, path);
536
+ async findInode(tx, path, syscall, visited = new Set()) {
537
+ const ino = await this._findInode(tx, path, syscall, visited);
538
+ return new Inode(await this.get(tx, ino, path, syscall));
540
539
  }
541
540
  /**
542
541
  * Finds the Inode of `path`.
@@ -544,92 +543,64 @@ export class StoreFS extends FileSystem {
544
543
  * @return The Inode of the path p.
545
544
  * @todo memoize/cache
546
545
  */
547
- findINodeSync(tx, path, visited = new Set()) {
548
- const ino = this._findINodeSync(tx, dirname(path), basename(path), visited);
549
- return this.getINodeSync(tx, ino, path);
546
+ findInodeSync(tx, path, syscall, visited = new Set()) {
547
+ const ino = this._findInodeSync(tx, path, syscall, visited);
548
+ return new Inode(this.getSync(tx, ino, path, syscall));
550
549
  }
551
550
  /**
552
- * Given the ID of a node, retrieves the corresponding Inode.
551
+ * Given an ID, retrieves the corresponding data.
553
552
  * @param tx The transaction to use.
554
553
  * @param path The corresponding path to the file (used for error messages).
555
554
  * @param id The ID to look up.
556
555
  */
557
- async getINode(tx, id, path) {
556
+ async get(tx, id, path, syscall) {
558
557
  const data = await tx.get(id);
559
558
  if (!data) {
560
- throw ErrnoError.With('ENOENT', path, 'getINode');
559
+ throw ErrnoError.With('ENOENT', path, syscall);
561
560
  }
562
- return new Inode(data.buffer);
561
+ return data;
563
562
  }
564
563
  /**
565
- * Given the ID of a node, retrieves the corresponding Inode.
564
+ * Given an ID, retrieves the corresponding data.
566
565
  * @param tx The transaction to use.
567
566
  * @param path The corresponding path to the file (used for error messages).
568
567
  * @param id The ID to look up.
569
568
  */
570
- getINodeSync(tx, id, path) {
569
+ getSync(tx, id, path, syscall) {
571
570
  const data = tx.getSync(id);
572
571
  if (!data) {
573
- throw ErrnoError.With('ENOENT', path, 'getINode');
574
- }
575
- const inode = new Inode(data.buffer);
576
- return inode;
577
- }
578
- /**
579
- * Given the Inode of a directory, retrieves the corresponding directory
580
- * listing.
581
- */
582
- async getDirListing(tx, inode, path) {
583
- const data = await tx.get(inode.ino);
584
- /*
585
- Occurs when data is undefined,or corresponds to something other than a directory listing.
586
- The latter should never occur unless the file system is corrupted.
587
- */
588
- if (!data) {
589
- throw ErrnoError.With('ENOENT', path, 'getDirListing');
590
- }
591
- return decodeDirListing(data);
592
- }
593
- /**
594
- * Given the Inode of a directory, retrieves the corresponding directory listing.
595
- */
596
- getDirListingSync(tx, inode, p) {
597
- const data = tx.getSync(inode.ino);
598
- if (!data) {
599
- throw ErrnoError.With('ENOENT', p, 'getDirListing');
572
+ throw ErrnoError.With('ENOENT', path, syscall);
600
573
  }
601
- return decodeDirListing(data);
574
+ return data;
602
575
  }
603
576
  /**
604
577
  * Adds a new node under a random ID. Retries before giving up in
605
- * the exceedingly unlikely chance that we try to reuse a random ino.
578
+ * the exceedingly unlikely chance that we try to reuse a random id.
606
579
  */
607
- async addNew(tx, data, path) {
580
+ async allocNew(tx, path, syscall) {
608
581
  for (let i = 0; i < maxInodeAllocTries; i++) {
609
- const ino = randomIno();
582
+ const ino = randomBigInt();
610
583
  if (await tx.get(ino)) {
611
584
  continue;
612
585
  }
613
- await tx.set(ino, data);
614
586
  return ino;
615
587
  }
616
- throw new ErrnoError(Errno.ENOSPC, 'No inode IDs available', path, 'addNewNode');
588
+ throw new ErrnoError(Errno.ENOSPC, 'No IDs available', path, syscall);
617
589
  }
618
590
  /**
619
591
  * Creates a new node under a random ID. Retries before giving up in
620
- * the exceedingly unlikely chance that we try to reuse a random ino.
592
+ * the exceedingly unlikely chance that we try to reuse a random id.
621
593
  * @return The ino that the data was stored under.
622
594
  */
623
- addNewSync(tx, data, path) {
595
+ allocNewSync(tx, path, syscall) {
624
596
  for (let i = 0; i < maxInodeAllocTries; i++) {
625
- const ino = randomIno();
597
+ const ino = randomBigInt();
626
598
  if (tx.getSync(ino)) {
627
599
  continue;
628
600
  }
629
- tx.setSync(ino, data);
630
601
  return ino;
631
602
  }
632
- throw new ErrnoError(Errno.ENOSPC, 'No inode IDs available', path, 'addNewNode');
603
+ throw new ErrnoError(Errno.ENOSPC, 'No IDs available', path, syscall);
633
604
  }
634
605
  /**
635
606
  * Commits a new file (well, a FILE or a DIRECTORY) to the file system with `mode`.
@@ -639,7 +610,7 @@ export class StoreFS extends FileSystem {
639
610
  * @param mode The mode to create the new file with.
640
611
  * @param data The data to store at the file's data node.
641
612
  */
642
- async commitNew(path, type, mode, data) {
613
+ async commitNew(path, type, mode, data, syscall) {
643
614
  const env_15 = { stack: [], error: void 0, hasError: false };
644
615
  try {
645
616
  /*
@@ -648,26 +619,29 @@ export class StoreFS extends FileSystem {
648
619
  we will create a file with name '' in root if path is '/'.
649
620
  */
650
621
  if (path == '/') {
651
- throw ErrnoError.With('EEXIST', path, 'commitNew');
622
+ throw ErrnoError.With('EEXIST', path, syscall);
652
623
  }
653
624
  const tx = __addDisposableResource(env_15, this.store.transaction(), true);
654
- const parentPath = dirname(path), parent = await this.findINode(tx, parentPath);
655
- const fname = basename(path), listing = await this.getDirListing(tx, parent, parentPath);
625
+ const { dir: parentPath, base: fname } = parse(path);
626
+ const parent = await this.findInode(tx, parentPath, syscall);
627
+ const listing = decodeDirListing(await this.get(tx, parent.data, parentPath, syscall));
656
628
  // Check if file already exists.
657
629
  if (listing[fname]) {
658
- await tx.abort();
659
- throw ErrnoError.With('EEXIST', path, 'commitNew');
630
+ throw ErrnoError.With('EEXIST', path, syscall);
660
631
  }
661
632
  // Commit data.
662
633
  const inode = new Inode();
663
- inode.ino = await this.addNew(tx, data, path);
634
+ inode.ino = await this.allocNew(tx, path, syscall);
635
+ inode.data = await this.allocNew(tx, path, syscall);
664
636
  inode.mode = mode | type;
665
- inode.uid = credentials.uid;
666
- inode.gid = credentials.gid;
637
+ inode.uid = parent.mode & S_ISUID ? parent.uid : credentials.uid;
638
+ inode.gid = parent.mode & S_ISGID ? parent.gid : credentials.gid;
667
639
  inode.size = data.length;
640
+ await tx.set(inode.ino, serialize(inode));
641
+ await tx.set(inode.data, data);
668
642
  // Update and commit parent directory listing.
669
- listing[fname] = await this.addNew(tx, inode.data, path);
670
- await tx.set(parent.ino, encodeDirListing(listing));
643
+ listing[fname] = inode.ino;
644
+ await tx.set(parent.data, encodeDirListing(listing));
671
645
  await tx.commit();
672
646
  return inode;
673
647
  }
@@ -690,7 +664,7 @@ export class StoreFS extends FileSystem {
690
664
  * @param data The data to store at the file's data node.
691
665
  * @return The Inode for the new file.
692
666
  */
693
- commitNewSync(path, type, mode, data = new Uint8Array()) {
667
+ commitNewSync(path, type, mode, data, syscall) {
694
668
  const env_16 = { stack: [], error: void 0, hasError: false };
695
669
  try {
696
670
  /*
@@ -699,27 +673,31 @@ export class StoreFS extends FileSystem {
699
673
  we will create a file with name '' in root if path is '/'.
700
674
  */
701
675
  if (path == '/') {
702
- throw ErrnoError.With('EEXIST', path, 'commitNew');
676
+ throw ErrnoError.With('EEXIST', path, syscall);
703
677
  }
704
678
  const tx = __addDisposableResource(env_16, this.store.transaction(), false);
705
- const parentPath = dirname(path), parent = this.findINodeSync(tx, parentPath);
706
- const fname = basename(path), listing = this.getDirListingSync(tx, parent, parentPath);
679
+ const { dir: parentPath, base: fname } = parse(path);
680
+ const parent = this.findInodeSync(tx, parentPath, syscall);
681
+ const listing = decodeDirListing(this.getSync(tx, parent.data, parentPath, syscall));
707
682
  // Check if file already exists.
708
683
  if (listing[fname]) {
709
- throw ErrnoError.With('EEXIST', path, 'commitNew');
684
+ throw ErrnoError.With('EEXIST', path, syscall);
710
685
  }
711
686
  // Commit data.
712
- const node = new Inode();
713
- node.ino = this.addNewSync(tx, data, path);
714
- node.size = data.length;
715
- node.mode = mode | type;
716
- node.uid = credentials.uid;
717
- node.gid = credentials.gid;
687
+ const inode = new Inode();
688
+ inode.ino = this.allocNewSync(tx, path, syscall);
689
+ inode.data = this.allocNewSync(tx, path, syscall);
690
+ inode.size = data.length;
691
+ inode.mode = mode | type;
692
+ inode.uid = parent.mode & S_ISUID ? parent.uid : credentials.uid;
693
+ inode.gid = parent.mode & S_ISGID ? parent.gid : credentials.gid;
718
694
  // Update and commit parent directory listing.
719
- listing[fname] = this.addNewSync(tx, node.data, path);
720
- tx.setSync(parent.ino, encodeDirListing(listing));
695
+ tx.setSync(inode.ino, serialize(inode));
696
+ tx.setSync(inode.data, data);
697
+ listing[fname] = inode.ino;
698
+ tx.setSync(parent.data, encodeDirListing(listing));
721
699
  tx.commitSync();
722
- return node;
700
+ return inode;
723
701
  }
724
702
  catch (e_16) {
725
703
  env_16.error = e_16;
@@ -735,26 +713,26 @@ export class StoreFS extends FileSystem {
735
713
  * @param isDir Does the path belong to a directory, or a file?
736
714
  * @todo Update mtime.
737
715
  */
738
- async remove(path, isDir) {
716
+ async remove(path, isDir, syscall) {
739
717
  const env_17 = { stack: [], error: void 0, hasError: false };
740
718
  try {
741
719
  const tx = __addDisposableResource(env_17, this.store.transaction(), true);
742
- const parent = dirname(path), parentNode = await this.findINode(tx, parent), listing = await this.getDirListing(tx, parentNode, parent), fileName = basename(path);
720
+ const { dir: parent, base: fileName } = parse(path), parentNode = await this.findInode(tx, parent, syscall), listing = decodeDirListing(await this.get(tx, parentNode.data, parent, syscall));
743
721
  if (!listing[fileName]) {
744
722
  throw ErrnoError.With('ENOENT', path, 'remove');
745
723
  }
746
724
  const fileIno = listing[fileName];
747
725
  // Get file inode.
748
- const fileNode = await this.getINode(tx, fileIno, path);
726
+ const fileNode = new Inode(await this.get(tx, fileIno, path, syscall));
749
727
  // Remove from directory listing of parent.
750
728
  delete listing[fileName];
751
729
  if (!isDir && fileNode.toStats().isDirectory()) {
752
730
  throw ErrnoError.With('EISDIR', path, 'remove');
753
731
  }
754
- await tx.set(parentNode.ino, encodeDirListing(listing));
732
+ await tx.set(parentNode.data, encodeDirListing(listing));
755
733
  if (--fileNode.nlink < 1) {
756
734
  // remove file
757
- await tx.remove(fileNode.ino);
735
+ await tx.remove(fileNode.data);
758
736
  await tx.remove(fileIno);
759
737
  }
760
738
  // Success.
@@ -776,26 +754,26 @@ export class StoreFS extends FileSystem {
776
754
  * @param isDir Does the path belong to a directory, or a file?
777
755
  * @todo Update mtime.
778
756
  */
779
- removeSync(path, isDir) {
757
+ removeSync(path, isDir, syscall) {
780
758
  const env_18 = { stack: [], error: void 0, hasError: false };
781
759
  try {
782
760
  const tx = __addDisposableResource(env_18, this.store.transaction(), false);
783
- const parent = dirname(path), parentNode = this.findINodeSync(tx, parent), listing = this.getDirListingSync(tx, parentNode, parent), fileName = basename(path), fileIno = listing[fileName];
761
+ const { dir: parent, base: fileName } = parse(path), parentNode = this.findInodeSync(tx, parent, syscall), listing = decodeDirListing(this.getSync(tx, parentNode.data, parent, syscall)), fileIno = listing[fileName];
784
762
  if (!fileIno) {
785
763
  throw ErrnoError.With('ENOENT', path, 'remove');
786
764
  }
787
765
  // Get file inode.
788
- const fileNode = this.getINodeSync(tx, fileIno, path);
766
+ const fileNode = new Inode(this.getSync(tx, fileIno, path, syscall));
789
767
  // Remove from directory listing of parent.
790
768
  delete listing[fileName];
791
769
  if (!isDir && fileNode.toStats().isDirectory()) {
792
770
  throw ErrnoError.With('EISDIR', path, 'remove');
793
771
  }
794
772
  // Update directory listing.
795
- tx.setSync(parentNode.ino, encodeDirListing(listing));
773
+ tx.setSync(parentNode.data, encodeDirListing(listing));
796
774
  if (--fileNode.nlink < 1) {
797
775
  // remove file
798
- tx.removeSync(fileNode.ino);
776
+ tx.removeSync(fileNode.data);
799
777
  tx.removeSync(fileIno);
800
778
  }
801
779
  // Success.