@zenfs/core 0.17.1 → 0.18.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 (69) hide show
  1. package/dist/backends/backend.d.ts +2 -3
  2. package/dist/backends/fetch.js +2 -2
  3. package/dist/backends/file_index.d.ts +14 -15
  4. package/dist/backends/file_index.js +3 -9
  5. package/dist/backends/overlay.d.ts +21 -22
  6. package/dist/backends/overlay.js +111 -114
  7. package/dist/backends/port/fs.d.ts +21 -22
  8. package/dist/backends/port/fs.js +23 -23
  9. package/dist/backends/store/fs.d.ts +20 -21
  10. package/dist/backends/store/fs.js +70 -138
  11. package/dist/browser.min.js +4 -4
  12. package/dist/browser.min.js.map +4 -4
  13. package/dist/config.js +2 -2
  14. package/dist/{cred.d.ts → credentials.d.ts} +3 -2
  15. package/dist/credentials.js +16 -0
  16. package/dist/emulation/async.d.ts +19 -4
  17. package/dist/emulation/async.js +55 -8
  18. package/dist/emulation/dir.d.ts +4 -7
  19. package/dist/emulation/dir.js +16 -24
  20. package/dist/emulation/promises.d.ts +3 -3
  21. package/dist/emulation/promises.js +103 -46
  22. package/dist/emulation/shared.d.ts +0 -3
  23. package/dist/emulation/shared.js +0 -6
  24. package/dist/emulation/sync.d.ts +3 -4
  25. package/dist/emulation/sync.js +107 -65
  26. package/dist/emulation/watchers.d.ts +40 -3
  27. package/dist/emulation/watchers.js +115 -9
  28. package/dist/error.d.ts +1 -1
  29. package/dist/error.js +1 -1
  30. package/dist/filesystem.d.ts +20 -21
  31. package/dist/filesystem.js +4 -4
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.js +1 -1
  34. package/dist/mixins/async.d.ts +13 -14
  35. package/dist/mixins/async.js +45 -47
  36. package/dist/mixins/mutexed.d.ts +1 -1
  37. package/dist/mixins/mutexed.js +61 -53
  38. package/dist/mixins/readonly.d.ts +12 -13
  39. package/dist/mixins/readonly.js +12 -12
  40. package/dist/mixins/sync.js +20 -20
  41. package/dist/stats.d.ts +12 -5
  42. package/dist/stats.js +11 -2
  43. package/dist/utils.d.ts +3 -4
  44. package/dist/utils.js +7 -17
  45. package/package.json +2 -2
  46. package/src/backends/backend.ts +2 -3
  47. package/src/backends/fetch.ts +2 -2
  48. package/src/backends/file_index.ts +3 -12
  49. package/src/backends/overlay.ts +112 -116
  50. package/src/backends/port/fs.ts +25 -26
  51. package/src/backends/store/fs.ts +72 -151
  52. package/src/config.ts +3 -2
  53. package/src/{cred.ts → credentials.ts} +11 -2
  54. package/src/emulation/async.ts +72 -16
  55. package/src/emulation/dir.ts +21 -29
  56. package/src/emulation/promises.ts +107 -46
  57. package/src/emulation/shared.ts +0 -8
  58. package/src/emulation/sync.ts +109 -66
  59. package/src/emulation/watchers.ts +140 -10
  60. package/src/error.ts +1 -1
  61. package/src/filesystem.ts +22 -23
  62. package/src/index.ts +1 -1
  63. package/src/mixins/async.ts +54 -55
  64. package/src/mixins/mutexed.ts +62 -55
  65. package/src/mixins/readonly.ts +24 -25
  66. package/src/mixins/sync.ts +21 -22
  67. package/src/stats.ts +15 -5
  68. package/src/utils.ts +9 -26
  69. package/dist/cred.js +0 -8
@@ -1,8 +1,8 @@
1
- import type { Cred } from '../../cred.js';
2
- import { R_OK, S_IFDIR, S_IFREG, W_OK } from '../../emulation/constants.js';
1
+ import { credentials } from '../../credentials.js';
2
+ import { S_IFDIR, S_IFREG } from '../../emulation/constants.js';
3
3
  import { basename, dirname, join, resolve } from '../../emulation/path.js';
4
4
  import { Errno, ErrnoError } from '../../error.js';
5
- import { PreloadFile, flagToMode } from '../../file.js';
5
+ import { PreloadFile } from '../../file.js';
6
6
  import { FileSystem, type FileSystemMetadata } from '../../filesystem.js';
7
7
  import { type Ino, Inode, randomIno, rootIno } from '../../inode.js';
8
8
  import type { FileType, Stats } from '../../stats.js';
@@ -64,7 +64,7 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
64
64
  /**
65
65
  * @todo Make rename compatible with the cache.
66
66
  */
67
- public async rename(oldPath: string, newPath: string, cred: Cred): Promise<void> {
67
+ public async rename(oldPath: string, newPath: string): Promise<void> {
68
68
  await using tx = this.store.transaction();
69
69
  const oldParent = dirname(oldPath),
70
70
  oldName = basename(oldPath),
@@ -74,10 +74,6 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
74
74
  oldDirNode = await this.findINode(tx, oldParent),
75
75
  oldDirList = await this.getDirListing(tx, oldDirNode, oldParent);
76
76
 
77
- if (!oldDirNode.toStats().hasAccess(W_OK, cred)) {
78
- throw ErrnoError.With('EACCES', oldPath, 'rename');
79
- }
80
-
81
77
  if (!oldDirList[oldName]) {
82
78
  throw ErrnoError.With('ENOENT', oldPath, 'rename');
83
79
  }
@@ -105,15 +101,13 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
105
101
  }
106
102
 
107
103
  if (newDirList[newName]) {
108
- // If it's a file, delete it.
104
+ // If it's a file, delete it, if it's a directory, throw a permissions error.
109
105
  const newNameNode = await this.getINode(tx, newDirList[newName], newPath);
110
- if (newNameNode.toStats().isFile()) {
111
- await tx.remove(newNameNode.ino);
112
- await tx.remove(newDirList[newName]);
113
- } else {
114
- // If it's a directory, throw a permissions error.
106
+ if (!newNameNode.toStats().isFile()) {
115
107
  throw ErrnoError.With('EPERM', newPath, 'rename');
116
108
  }
109
+ await tx.remove(newNameNode.ino);
110
+ await tx.remove(newDirList[newName]);
117
111
  }
118
112
  newDirList[newName] = nodeId;
119
113
  // Commit the two changed directory listings.
@@ -122,7 +116,7 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
122
116
  await tx.commit();
123
117
  }
124
118
 
125
- public renameSync(oldPath: string, newPath: string, cred: Cred): void {
119
+ public renameSync(oldPath: string, newPath: string): void {
126
120
  using tx = this.store.transaction();
127
121
  const oldParent = dirname(oldPath),
128
122
  oldName = basename(oldPath),
@@ -132,10 +126,6 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
132
126
  oldDirNode = this.findINodeSync(tx, oldParent),
133
127
  oldDirList = this.getDirListingSync(tx, oldDirNode, oldParent);
134
128
 
135
- if (!oldDirNode.toStats().hasAccess(W_OK, cred)) {
136
- throw ErrnoError.With('EACCES', oldPath, 'rename');
137
- }
138
-
139
129
  if (!oldDirList[oldName]) {
140
130
  throw ErrnoError.With('ENOENT', oldPath, 'rename');
141
131
  }
@@ -163,15 +153,13 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
163
153
  }
164
154
 
165
155
  if (newDirList[newName]) {
166
- // If it's a file, delete it.
156
+ // If it's a file, delete it, if it's a directory, throw a permissions error.
167
157
  const newNameNode = this.getINodeSync(tx, newDirList[newName], newPath);
168
- if (newNameNode.toStats().isFile()) {
169
- tx.removeSync(newNameNode.ino);
170
- tx.removeSync(newDirList[newName]);
171
- } else {
172
- // If it's a directory, throw a permissions error.
158
+ if (!newNameNode.toStats().isFile()) {
173
159
  throw ErrnoError.With('EPERM', newPath, 'rename');
174
160
  }
161
+ tx.removeSync(newNameNode.ino);
162
+ tx.removeSync(newDirList[newName]);
175
163
  }
176
164
  newDirList[newName] = ino;
177
165
 
@@ -181,114 +169,92 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
181
169
  tx.commitSync();
182
170
  }
183
171
 
184
- public async stat(path: string, cred: Cred): Promise<Stats> {
172
+ public async stat(path: string): Promise<Stats> {
185
173
  await using tx = this.store.transaction();
186
174
  const inode = await this.findINode(tx, path);
187
175
  if (!inode) {
188
176
  throw ErrnoError.With('ENOENT', path, 'stat');
189
177
  }
190
- const stats = inode.toStats();
191
- if (!stats.hasAccess(R_OK, cred)) {
192
- throw ErrnoError.With('EACCES', path, 'stat');
193
- }
194
- return stats;
178
+ return inode.toStats();
195
179
  }
196
180
 
197
- public statSync(path: string, cred: Cred): Stats {
181
+ public statSync(path: string): Stats {
198
182
  using tx = this.store.transaction();
199
183
  // Get the inode to the item, convert it into a Stats object.
200
- const stats = this.findINodeSync(tx, path).toStats();
201
- if (!stats.hasAccess(R_OK, cred)) {
202
- throw ErrnoError.With('EACCES', path, 'stat');
203
- }
204
- return stats;
184
+ return this.findINodeSync(tx, path).toStats();
205
185
  }
206
186
 
207
- public async createFile(path: string, flag: string, mode: number, cred: Cred): Promise<PreloadFile<this>> {
208
- const node = await this.commitNew(path, S_IFREG, mode, cred, new Uint8Array(0));
187
+ public async createFile(path: string, flag: string, mode: number): Promise<PreloadFile<this>> {
188
+ const node = await this.commitNew(path, S_IFREG, mode, new Uint8Array(0));
209
189
  return new PreloadFile(this, path, flag, node.toStats(), new Uint8Array(0));
210
190
  }
211
191
 
212
- public createFileSync(path: string, flag: string, mode: number, cred: Cred): PreloadFile<this> {
213
- this.commitNewSync(path, S_IFREG, mode, cred);
214
- return this.openFileSync(path, flag, cred);
192
+ public createFileSync(path: string, flag: string, mode: number): PreloadFile<this> {
193
+ this.commitNewSync(path, S_IFREG, mode);
194
+ return this.openFileSync(path, flag);
215
195
  }
216
196
 
217
- public async openFile(path: string, flag: string, cred: Cred): Promise<PreloadFile<this>> {
197
+ public async openFile(path: string, flag: string): Promise<PreloadFile<this>> {
218
198
  await using tx = this.store.transaction();
219
199
  const node = await this.findINode(tx, path),
220
200
  data = await tx.get(node.ino);
221
- if (!node.toStats().hasAccess(flagToMode(flag), cred)) {
222
- throw ErrnoError.With('EACCES', path, 'openFile');
223
- }
224
201
  if (!data) {
225
202
  throw ErrnoError.With('ENOENT', path, 'openFile');
226
203
  }
227
204
  return new PreloadFile(this, path, flag, node.toStats(), data);
228
205
  }
229
206
 
230
- public openFileSync(path: string, flag: string, cred: Cred): PreloadFile<this> {
207
+ public openFileSync(path: string, flag: string): PreloadFile<this> {
231
208
  using tx = this.store.transaction();
232
209
  const node = this.findINodeSync(tx, path),
233
210
  data = tx.getSync(node.ino);
234
- if (!node.toStats().hasAccess(flagToMode(flag), cred)) {
235
- throw ErrnoError.With('EACCES', path, 'openFile');
236
- }
237
211
  if (!data) {
238
212
  throw ErrnoError.With('ENOENT', path, 'openFile');
239
213
  }
240
214
  return new PreloadFile(this, path, flag, node.toStats(), data);
241
215
  }
242
216
 
243
- public async unlink(path: string, cred: Cred): Promise<void> {
244
- return this.remove(path, false, cred);
217
+ public async unlink(path: string): Promise<void> {
218
+ return this.remove(path, false);
245
219
  }
246
220
 
247
- public unlinkSync(path: string, cred: Cred): void {
248
- this.removeSync(path, false, cred);
221
+ public unlinkSync(path: string): void {
222
+ this.removeSync(path, false);
249
223
  }
250
224
 
251
- public async rmdir(path: string, cred: Cred): Promise<void> {
225
+ public async rmdir(path: string): Promise<void> {
252
226
  // Check first if directory is empty.
253
- const list = await this.readdir(path, cred);
254
- if (list.length > 0) {
227
+ if ((await this.readdir(path)).length) {
255
228
  throw ErrnoError.With('ENOTEMPTY', path, 'rmdir');
256
229
  }
257
- await this.remove(path, true, cred);
230
+ await this.remove(path, true);
258
231
  }
259
232
 
260
- public rmdirSync(path: string, cred: Cred): void {
233
+ public rmdirSync(path: string): void {
261
234
  // Check first if directory is empty.
262
- if (this.readdirSync(path, cred).length > 0) {
235
+ if (this.readdirSync(path).length) {
263
236
  throw ErrnoError.With('ENOTEMPTY', path, 'rmdir');
264
- } else {
265
- this.removeSync(path, true, cred);
266
237
  }
238
+ this.removeSync(path, true);
267
239
  }
268
240
 
269
- public async mkdir(path: string, mode: number, cred: Cred): Promise<void> {
270
- await this.commitNew(path, S_IFDIR, mode, cred, encode('{}'));
241
+ public async mkdir(path: string, mode: number): Promise<void> {
242
+ await this.commitNew(path, S_IFDIR, mode, encode('{}'));
271
243
  }
272
244
 
273
- public mkdirSync(path: string, mode: number, cred: Cred): void {
274
- this.commitNewSync(path, S_IFDIR, mode, cred, encode('{}'));
245
+ public mkdirSync(path: string, mode: number): void {
246
+ this.commitNewSync(path, S_IFDIR, mode, encode('{}'));
275
247
  }
276
248
 
277
- public async readdir(path: string, cred: Cred): Promise<string[]> {
249
+ public async readdir(path: string): Promise<string[]> {
278
250
  await using tx = this.store.transaction();
279
251
  const node = await this.findINode(tx, path);
280
- if (!node.toStats().hasAccess(R_OK, cred)) {
281
- throw ErrnoError.With('EACCES', path, 'readdur');
282
- }
283
252
  return Object.keys(await this.getDirListing(tx, node, path));
284
253
  }
285
254
 
286
- public readdirSync(path: string, cred: Cred): string[] {
255
+ public readdirSync(path: string): string[] {
287
256
  using tx = this.store.transaction();
288
257
  const node = this.findINodeSync(tx, path);
289
- if (!node.toStats().hasAccess(R_OK, cred)) {
290
- throw ErrnoError.With('EACCES', path, 'readdir');
291
- }
292
258
  return Object.keys(this.getDirListingSync(tx, node, path));
293
259
  }
294
260
 
@@ -334,66 +300,39 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
334
300
  tx.commitSync();
335
301
  }
336
302
 
337
- public async link(existing: string, newpath: string, cred: Cred): Promise<void> {
303
+ public async link(target: string, link: string): Promise<void> {
338
304
  await using tx = this.store.transaction();
339
- const existingDir: string = dirname(existing),
340
- existingDirNode = await this.findINode(tx, existingDir);
341
305
 
342
- if (!existingDirNode.toStats().hasAccess(R_OK, cred)) {
343
- throw ErrnoError.With('EACCES', existingDir, 'link');
344
- }
345
-
346
- const newDir: string = dirname(newpath),
306
+ const newDir: string = dirname(link),
347
307
  newDirNode = await this.findINode(tx, newDir),
348
- newListing = await this.getDirListing(tx, newDirNode, newDir);
349
-
350
- if (!newDirNode.toStats().hasAccess(W_OK, cred)) {
351
- throw ErrnoError.With('EACCES', newDir, 'link');
352
- }
308
+ listing = await this.getDirListing(tx, newDirNode, newDir);
353
309
 
354
- const ino = await this._findINode(tx, existingDir, basename(existing));
355
- const node = await this.getINode(tx, ino, existing);
356
-
357
- if (!node.toStats().hasAccess(W_OK, cred)) {
358
- throw ErrnoError.With('EACCES', newpath, 'link');
359
- }
310
+ const ino = await this._findINode(tx, dirname(target), basename(target));
311
+ const node = await this.getINode(tx, ino, target);
360
312
 
361
313
  node.nlink++;
362
- newListing[basename(newpath)] = ino;
314
+ listing[basename(link)] = ino;
363
315
 
364
316
  tx.setSync(ino, node.data);
365
- tx.setSync(newDirNode.ino, encodeDirListing(newListing));
317
+ tx.setSync(newDirNode.ino, encodeDirListing(listing));
366
318
  tx.commitSync();
367
319
  }
368
320
 
369
- public linkSync(existing: string, newpath: string, cred: Cred): void {
321
+ public linkSync(target: string, link: string): void {
370
322
  using tx = this.store.transaction();
371
- const existingDir: string = dirname(existing),
372
- existingDirNode = this.findINodeSync(tx, existingDir);
373
-
374
- if (!existingDirNode.toStats().hasAccess(R_OK, cred)) {
375
- throw ErrnoError.With('EACCES', existingDir, 'link');
376
- }
377
323
 
378
- const newDir: string = dirname(newpath),
324
+ const newDir: string = dirname(link),
379
325
  newDirNode = this.findINodeSync(tx, newDir),
380
- newListing = this.getDirListingSync(tx, newDirNode, newDir);
326
+ listing = this.getDirListingSync(tx, newDirNode, newDir);
381
327
 
382
- if (!newDirNode.toStats().hasAccess(W_OK, cred)) {
383
- throw ErrnoError.With('EACCES', newDir, 'link');
384
- }
385
-
386
- const ino = this._findINodeSync(tx, existingDir, basename(existing));
387
- const node = this.getINodeSync(tx, ino, existing);
328
+ const ino = this._findINodeSync(tx, dirname(target), basename(target));
329
+ const node = this.getINodeSync(tx, ino, target);
388
330
 
389
- if (!node.toStats().hasAccess(W_OK, cred)) {
390
- throw ErrnoError.With('EACCES', newpath, 'link');
391
- }
392
331
  node.nlink++;
393
- newListing[basename(newpath)] = ino;
332
+ listing[basename(link)] = ino;
394
333
 
395
334
  tx.setSync(ino, node.data);
396
- tx.setSync(newDirNode.ino, encodeDirListing(newListing));
335
+ tx.setSync(newDirNode.ino, encodeDirListing(listing));
397
336
  tx.commitSync();
398
337
  }
399
338
 
@@ -616,16 +555,11 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
616
555
  * @param cred The UID/GID to create the file with
617
556
  * @param data The data to store at the file's data node.
618
557
  */
619
- private async commitNew(path: string, type: FileType, mode: number, cred: Cred, data: Uint8Array): Promise<Inode> {
558
+ private async commitNew(path: string, type: FileType, mode: number, data: Uint8Array): Promise<Inode> {
620
559
  await using tx = this.store.transaction();
621
560
  const parentPath = dirname(path),
622
561
  parent = await this.findINode(tx, parentPath);
623
562
 
624
- //Check that the creater has correct access
625
- if (!parent.toStats().hasAccess(W_OK, cred)) {
626
- throw ErrnoError.With('EACCES', path, 'commitNewFile');
627
- }
628
-
629
563
  const fname = basename(path),
630
564
  listing = await this.getDirListing(tx, parent, parentPath);
631
565
 
@@ -635,21 +569,21 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
635
569
  we will create a file with name '' in root should path == '/'.
636
570
  */
637
571
  if (path === '/') {
638
- throw ErrnoError.With('EEXIST', path, 'commitNewFile');
572
+ throw ErrnoError.With('EEXIST', path, 'commitNew');
639
573
  }
640
574
 
641
575
  // Check if file already exists.
642
576
  if (listing[fname]) {
643
577
  await tx.abort();
644
- throw ErrnoError.With('EEXIST', path, 'commitNewFile');
578
+ throw ErrnoError.With('EEXIST', path, 'commitNew');
645
579
  }
646
580
 
647
581
  // Commit data.
648
582
  const inode = new Inode();
649
583
  inode.ino = await this.addNew(tx, data, path);
650
584
  inode.mode = mode | type;
651
- inode.uid = cred.uid;
652
- inode.gid = cred.gid;
585
+ inode.uid = credentials.uid;
586
+ inode.gid = credentials.gid;
653
587
  inode.size = data.length;
654
588
 
655
589
  // Update and commit parent directory listing.
@@ -668,16 +602,11 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
668
602
  * @param data The data to store at the file's data node.
669
603
  * @return The Inode for the new file.
670
604
  */
671
- protected commitNewSync(path: string, type: FileType, mode: number, cred: Cred, data: Uint8Array = new Uint8Array()): Inode {
605
+ protected commitNewSync(path: string, type: FileType, mode: number, data: Uint8Array = new Uint8Array()): Inode {
672
606
  using tx = this.store.transaction();
673
607
  const parentPath = dirname(path),
674
608
  parent = this.findINodeSync(tx, parentPath);
675
609
 
676
- //Check that the creater has correct access
677
- if (!parent.toStats().hasAccess(W_OK, cred)) {
678
- throw ErrnoError.With('EACCES', path, 'commitNewFile');
679
- }
680
-
681
610
  const fname = basename(path),
682
611
  listing = this.getDirListingSync(tx, parent, parentPath);
683
612
 
@@ -687,12 +616,12 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
687
616
  we will create a file with name '' in root should p == '/'.
688
617
  */
689
618
  if (path === '/') {
690
- throw ErrnoError.With('EEXIST', path, 'commitNewFile');
619
+ throw ErrnoError.With('EEXIST', path, 'commitNew');
691
620
  }
692
621
 
693
622
  // Check if file already exists.
694
623
  if (listing[fname]) {
695
- throw ErrnoError.With('EEXIST', path, 'commitNewFile');
624
+ throw ErrnoError.With('EEXIST', path, 'commitNew');
696
625
  }
697
626
 
698
627
  // Commit data.
@@ -700,8 +629,8 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
700
629
  node.ino = this.addNewSync(tx, data, path);
701
630
  node.size = data.length;
702
631
  node.mode = mode | type;
703
- node.uid = cred.uid;
704
- node.gid = cred.gid;
632
+ node.uid = credentials.uid;
633
+ node.gid = credentials.gid;
705
634
  // Update and commit parent directory listing.
706
635
  listing[fname] = this.addNewSync(tx, node.data, path);
707
636
  tx.setSync(parent.ino, encodeDirListing(listing));
@@ -715,7 +644,7 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
715
644
  * @param isDir Does the path belong to a directory, or a file?
716
645
  * @todo Update mtime.
717
646
  */
718
- private async remove(path: string, isDir: boolean, cred: Cred): Promise<void> {
647
+ private async remove(path: string, isDir: boolean): Promise<void> {
719
648
  await using tx = this.store.transaction();
720
649
  const parent: string = dirname(path),
721
650
  parentNode = await this.findINode(tx, parent),
@@ -723,7 +652,7 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
723
652
  fileName: string = basename(path);
724
653
 
725
654
  if (!listing[fileName]) {
726
- throw ErrnoError.With('ENOENT', path, 'removeEntry');
655
+ throw ErrnoError.With('ENOENT', path, 'remove');
727
656
  }
728
657
 
729
658
  const fileIno = listing[fileName];
@@ -731,19 +660,15 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
731
660
  // Get file inode.
732
661
  const fileNode = await this.getINode(tx, fileIno, path);
733
662
 
734
- if (!fileNode.toStats().hasAccess(W_OK, cred)) {
735
- throw ErrnoError.With('EACCES', path, 'removeEntry');
736
- }
737
-
738
663
  // Remove from directory listing of parent.
739
664
  delete listing[fileName];
740
665
 
741
666
  if (!isDir && fileNode.toStats().isDirectory()) {
742
- throw ErrnoError.With('EISDIR', path, 'removeEntry');
667
+ throw ErrnoError.With('EISDIR', path, 'remove');
743
668
  }
744
669
 
745
670
  if (isDir && !fileNode.toStats().isDirectory()) {
746
- throw ErrnoError.With('ENOTDIR', path, 'removeEntry');
671
+ throw ErrnoError.With('ENOTDIR', path, 'remove');
747
672
  }
748
673
 
749
674
  await tx.set(parentNode.ino, encodeDirListing(listing));
@@ -764,7 +689,7 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
764
689
  * @param isDir Does the path belong to a directory, or a file?
765
690
  * @todo Update mtime.
766
691
  */
767
- protected removeSync(path: string, isDir: boolean, cred: Cred): void {
692
+ protected removeSync(path: string, isDir: boolean): void {
768
693
  using tx = this.store.transaction();
769
694
  const parent: string = dirname(path),
770
695
  parentNode = this.findINodeSync(tx, parent),
@@ -773,25 +698,21 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
773
698
  fileIno: Ino = listing[fileName];
774
699
 
775
700
  if (!fileIno) {
776
- throw ErrnoError.With('ENOENT', path, 'removeEntry');
701
+ throw ErrnoError.With('ENOENT', path, 'remove');
777
702
  }
778
703
 
779
704
  // Get file inode.
780
705
  const fileNode = this.getINodeSync(tx, fileIno, path);
781
706
 
782
- if (!fileNode.toStats().hasAccess(W_OK, cred)) {
783
- throw ErrnoError.With('EACCES', path, 'removeEntry');
784
- }
785
-
786
707
  // Remove from directory listing of parent.
787
708
  delete listing[fileName];
788
709
 
789
710
  if (!isDir && fileNode.toStats().isDirectory()) {
790
- throw ErrnoError.With('EISDIR', path, 'removeEntry');
711
+ throw ErrnoError.With('EISDIR', path, 'remove');
791
712
  }
792
713
 
793
714
  if (isDir && !fileNode.toStats().isDirectory()) {
794
- throw ErrnoError.With('ENOTDIR', path, 'removeEntry');
715
+ throw ErrnoError.With('ENOTDIR', path, 'remove');
795
716
  }
796
717
 
797
718
  // Update directory listing.
package/src/config.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import type { Backend, BackendConfiguration, FilesystemOf, SharedConfig } from './backends/backend.js';
2
2
  import { checkOptions, isBackend, isBackendConfig } from './backends/backend.js';
3
+ import { credentials } from './credentials.js';
3
4
  import * as fs from './emulation/index.js';
4
5
  import type { AbsolutePath } from './emulation/path.js';
5
- import { setCred, type MountObject } from './emulation/shared.js';
6
+ import type { MountObject } from './emulation/shared.js';
6
7
  import { Errno, ErrnoError } from './error.js';
7
8
  import { FileSystem } from './filesystem.js';
8
9
 
@@ -107,7 +108,7 @@ export async function configure<T extends ConfigMounts>(config: Partial<Configur
107
108
  const uid = 'uid' in config ? config.uid || 0 : 0;
108
109
  const gid = 'gid' in config ? config.gid || 0 : 0;
109
110
 
110
- setCred({ uid, gid, suid: uid, sgid: gid, euid: uid, egid: gid });
111
+ Object.assign(credentials, { uid, gid, suid: uid, sgid: gid, euid: uid, egid: gid });
111
112
 
112
113
  if (!config.mounts) {
113
114
  return;
@@ -3,7 +3,7 @@
3
3
  * Similar to Linux's cred struct.
4
4
  * @see https://github.com/torvalds/linux/blob/master/include/linux/cred.h
5
5
  */
6
- export interface Cred {
6
+ export interface Credentials {
7
7
  uid: number;
8
8
  gid: number;
9
9
  suid: number;
@@ -12,7 +12,16 @@ export interface Cred {
12
12
  egid: number;
13
13
  }
14
14
 
15
- export const rootCred: Cred = {
15
+ export const credentials: Credentials = {
16
+ uid: 0,
17
+ gid: 0,
18
+ suid: 0,
19
+ sgid: 0,
20
+ euid: 0,
21
+ egid: 0,
22
+ };
23
+
24
+ export const rootCredentials: Credentials = {
16
25
  uid: 0,
17
26
  gid: 0,
18
27
  suid: 0,
@@ -3,14 +3,14 @@ import type * as fs from 'node:fs';
3
3
  import { Errno, ErrnoError } from '../error.js';
4
4
  import type { FileContents } from '../filesystem.js';
5
5
  import { BigIntStats, type Stats } from '../stats.js';
6
- import { normalizeMode, type Callback } from '../utils.js';
6
+ import { normalizeMode, normalizePath, type Callback } from '../utils.js';
7
7
  import { R_OK } from './constants.js';
8
8
  import type { Dirent } from './dir.js';
9
9
  import type { Dir } from './dir.js';
10
10
  import * as promises from './promises.js';
11
11
  import { fd2file } from './shared.js';
12
12
  import { ReadStream, WriteStream } from './streams.js';
13
- import { FSWatcher } from './watchers.js';
13
+ import { FSWatcher, StatWatcher } from './watchers.js';
14
14
 
15
15
  const nop = () => {};
16
16
 
@@ -673,36 +673,92 @@ export function access(path: fs.PathLike, cbMode: number | Callback, cb: Callbac
673
673
  }
674
674
  access satisfies Omit<typeof fs.access, '__promisify__'>;
675
675
 
676
+ const statWatchers: Map<string, { watcher: StatWatcher; listeners: Set<(curr: Stats, prev: Stats) => void> }> = new Map();
677
+
676
678
  /**
677
- * @todo Implement
679
+ * Watch for changes on a file. The callback listener will be called each time the file is accessed.
680
+ *
681
+ * The `options` argument may be omitted. If provided, it should be an object with a `persistent` boolean and an `interval` number specifying the polling interval in milliseconds.
682
+ *
683
+ * When a change is detected, the `listener` callback is called with the current and previous `Stats` objects.
684
+ *
685
+ * @param path The path to the file to watch.
686
+ * @param options Optional options object specifying `persistent` and `interval`.
687
+ * @param listener The callback listener to be called when the file changes.
678
688
  */
679
689
  export function watchFile(path: fs.PathLike, listener: (curr: Stats, prev: Stats) => void): void;
680
690
  export function watchFile(path: fs.PathLike, options: { persistent?: boolean; interval?: number }, listener: (curr: Stats, prev: Stats) => void): void;
681
691
  export function watchFile(
682
692
  path: fs.PathLike,
683
693
  optsListener: { persistent?: boolean; interval?: number } | ((curr: Stats, prev: Stats) => void),
684
- listener: (curr: Stats, prev: Stats) => void = nop
694
+ listener?: (curr: Stats, prev: Stats) => void
685
695
  ): void {
686
- throw ErrnoError.With('ENOSYS', path.toString(), 'watchFile');
696
+ const normalizedPath = normalizePath(path.toString());
697
+ const options: { persistent?: boolean; interval?: number } = typeof optsListener != 'function' ? optsListener : {};
698
+
699
+ if (typeof optsListener === 'function') {
700
+ listener = optsListener;
701
+ }
702
+
703
+ if (!listener) {
704
+ throw new ErrnoError(Errno.EINVAL, 'No listener specified', path.toString(), 'watchFile');
705
+ }
706
+
707
+ if (statWatchers.has(normalizedPath)) {
708
+ const entry = statWatchers.get(normalizedPath);
709
+ if (entry) {
710
+ entry.listeners.add(listener);
711
+ }
712
+ return;
713
+ }
714
+
715
+ const watcher = new StatWatcher(normalizedPath, options);
716
+ watcher.on('change', (curr: Stats, prev: Stats) => {
717
+ const entry = statWatchers.get(normalizedPath);
718
+ if (!entry) {
719
+ return;
720
+ }
721
+ for (const listener of entry.listeners) {
722
+ listener(curr, prev);
723
+ }
724
+ });
725
+ statWatchers.set(normalizedPath, { watcher, listeners: new Set() });
687
726
  }
688
727
  watchFile satisfies Omit<typeof fs.watchFile, '__promisify__'>;
689
728
 
690
729
  /**
691
- * @todo Implement
730
+ * Stop watching for changes on a file.
731
+ *
732
+ * If the `listener` is specified, only that particular listener is removed.
733
+ * If no `listener` is specified, all listeners are removed, and the file is no longer watched.
734
+ *
735
+ * @param path The path to the file to stop watching.
736
+ * @param listener Optional listener to remove.
692
737
  */
693
738
  export function unwatchFile(path: fs.PathLike, listener: (curr: Stats, prev: Stats) => void = nop): void {
694
- throw ErrnoError.With('ENOSYS', path.toString(), 'unwatchFile');
739
+ const normalizedPath = normalizePath(path.toString());
740
+
741
+ const entry = statWatchers.get(normalizedPath);
742
+ if (entry) {
743
+ if (listener && listener !== nop) {
744
+ entry.listeners.delete(listener);
745
+ } else {
746
+ // If no listener is specified, remove all listeners
747
+ entry.listeners.clear();
748
+ }
749
+ if (entry.listeners.size === 0) {
750
+ // No more listeners, stop the watcher
751
+ entry.watcher.stop();
752
+ statWatchers.delete(normalizedPath);
753
+ }
754
+ }
695
755
  }
696
756
  unwatchFile satisfies Omit<typeof fs.unwatchFile, '__promisify__'>;
697
757
 
698
- export function watch(path: fs.PathLike, listener?: (event: string, filename: string) => any): fs.FSWatcher;
699
- export function watch(path: fs.PathLike, options: { persistent?: boolean }, listener?: (event: string, filename: string) => any): fs.FSWatcher;
700
- export function watch(
701
- path: fs.PathLike,
702
- options?: fs.WatchOptions | ((event: string, filename: string) => any),
703
- listener?: (event: string, filename: string) => any
704
- ): fs.FSWatcher {
705
- const watcher = new FSWatcher<string>(typeof options == 'object' ? options : {});
758
+ export function watch(path: fs.PathLike, listener?: (event: string, filename: string) => any): FSWatcher;
759
+ export function watch(path: fs.PathLike, options: { persistent?: boolean }, listener?: (event: string, filename: string) => any): FSWatcher;
760
+ export function watch(path: fs.PathLike, options?: fs.WatchOptions | ((event: string, filename: string) => any), listener?: (event: string, filename: string) => any): FSWatcher {
761
+ const watcher = new FSWatcher<string>(normalizePath(path), typeof options == 'object' ? options : {});
706
762
  listener = typeof options == 'function' ? options : listener;
707
763
  watcher.on('change', listener || nop);
708
764
  return watcher;
@@ -770,7 +826,7 @@ export function createReadStream(path: fs.PathLike, _options?: BufferEncoding |
770
826
  handle
771
827
  ?.close()
772
828
  .then(() => callback(error))
773
- .catch(callback);
829
+ .catch(nop);
774
830
  },
775
831
  });
776
832