@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
@@ -46,16 +46,17 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
46
46
  /* eslint-disable @typescript-eslint/no-redundant-type-constituents */
47
47
  import { Buffer } from 'buffer';
48
48
  import { Errno, ErrnoError } from '../error.js';
49
- import { isAppendable, isExclusive, isReadable, isTruncating, isWriteable, parseFlag } from '../file.js';
49
+ import { flagToMode, isAppendable, isExclusive, isReadable, isTruncating, isWriteable, parseFlag } from '../file.js';
50
50
  import '../polyfills.js';
51
51
  import { BigIntStats } from '../stats.js';
52
52
  import { normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
53
53
  import * as constants from './constants.js';
54
54
  import { Dir, Dirent } from './dir.js';
55
55
  import { dirname, join, parse } from './path.js';
56
- import { _statfs, cred, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js';
56
+ import { _statfs, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js';
57
+ import { credentials } from '../credentials.js';
57
58
  import { ReadStream, WriteStream } from './streams.js';
58
- import { FSWatcher } from './watchers.js';
59
+ import { FSWatcher, emitChange } from './watchers.js';
59
60
  export * as constants from './constants.js';
60
61
  export class FileHandle {
61
62
  constructor(fdOrFile) {
@@ -66,19 +67,21 @@ export class FileHandle {
66
67
  /**
67
68
  * Asynchronous fchown(2) - Change ownership of a file.
68
69
  */
69
- chown(uid, gid) {
70
- return this.file.chown(uid, gid);
70
+ async chown(uid, gid) {
71
+ await this.file.chown(uid, gid);
72
+ emitChange('change', this.file.path);
71
73
  }
72
74
  /**
73
75
  * Asynchronous fchmod(2) - Change permissions of a file.
74
76
  * @param mode A file mode. If a string is passed, it is parsed as an octal integer.
75
77
  */
76
- chmod(mode) {
78
+ async chmod(mode) {
77
79
  const numMode = normalizeMode(mode, -1);
78
80
  if (numMode < 0) {
79
81
  throw new ErrnoError(Errno.EINVAL, 'Invalid mode.');
80
82
  }
81
- return this.file.chmod(numMode);
83
+ await this.file.chmod(numMode);
84
+ emitChange('change', this.file.path);
82
85
  }
83
86
  /**
84
87
  * Asynchronous fdatasync(2) - synchronize a file's in-core state with storage device.
@@ -96,20 +99,22 @@ export class FileHandle {
96
99
  * Asynchronous ftruncate(2) - Truncate a file to a specified length.
97
100
  * @param len If not specified, defaults to `0`.
98
101
  */
99
- truncate(len) {
102
+ async truncate(len) {
100
103
  len || (len = 0);
101
104
  if (len < 0) {
102
105
  throw new ErrnoError(Errno.EINVAL);
103
106
  }
104
- return this.file.truncate(len);
107
+ await this.file.truncate(len);
108
+ emitChange('change', this.file.path);
105
109
  }
106
110
  /**
107
111
  * Asynchronously change file timestamps of the file.
108
112
  * @param atime The last access time. If a string is provided, it will be coerced to number.
109
113
  * @param mtime The last modified time. If a string is provided, it will be coerced to number.
110
114
  */
111
- utimes(atime, mtime) {
112
- return this.file.utimes(normalizeTime(atime), normalizeTime(mtime));
115
+ async utimes(atime, mtime) {
116
+ await this.file.utimes(normalizeTime(atime), normalizeTime(mtime));
117
+ emitChange('change', this.file.path);
113
118
  }
114
119
  /**
115
120
  * Asynchronously append data to a file, creating the file if it does not exist. The underlying file will _not_ be closed automatically.
@@ -132,6 +137,7 @@ export class FileHandle {
132
137
  }
133
138
  const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : data;
134
139
  await this.file.write(encodedData, 0, encodedData.length);
140
+ emitChange('change', this.file.path);
135
141
  }
136
142
  /**
137
143
  * Asynchronously reads data from the file.
@@ -207,6 +213,9 @@ export class FileHandle {
207
213
  }
208
214
  async stat(opts) {
209
215
  const stats = await this.file.stat();
216
+ if (!stats.hasAccess(constants.R_OK, credentials)) {
217
+ throw ErrnoError.With('EACCES', this.file.path, 'stat');
218
+ }
210
219
  return opts?.bigint ? new BigIntStats(stats) : stats;
211
220
  }
212
221
  async write(data, posOrOff, lenOrEnc, position) {
@@ -228,6 +237,7 @@ export class FileHandle {
228
237
  }
229
238
  position ?? (position = this.file.position);
230
239
  const bytesWritten = await this.file.write(buffer, offset, length, position);
240
+ emitChange('change', this.file.path);
231
241
  return { buffer, bytesWritten };
232
242
  }
233
243
  /**
@@ -252,6 +262,7 @@ export class FileHandle {
252
262
  }
253
263
  const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : data;
254
264
  await this.file.write(encodedData, 0, encodedData.length, 0);
265
+ emitChange('change', this.file.path);
255
266
  }
256
267
  /**
257
268
  * Asynchronous close(2) - close a `FileHandle`.
@@ -346,13 +357,18 @@ export async function rename(oldPath, newPath) {
346
357
  newPath = normalizePath(newPath);
347
358
  const src = resolveMount(oldPath);
348
359
  const dst = resolveMount(newPath);
360
+ if (!(await stat(dirname(oldPath))).hasAccess(constants.W_OK, credentials)) {
361
+ throw ErrnoError.With('EACCES', oldPath, 'rename');
362
+ }
349
363
  try {
350
364
  if (src.mountPoint == dst.mountPoint) {
351
- await src.fs.rename(src.path, dst.path, cred);
365
+ await src.fs.rename(src.path, dst.path);
366
+ emitChange('rename', oldPath.toString());
352
367
  return;
353
368
  }
354
369
  await writeFile(newPath, await readFile(oldPath));
355
370
  await unlink(oldPath);
371
+ emitChange('rename', oldPath.toString());
356
372
  }
357
373
  catch (e) {
358
374
  throw fixError(e, { [src.path]: oldPath, [dst.path]: newPath });
@@ -366,7 +382,7 @@ rename;
366
382
  export async function exists(path) {
367
383
  try {
368
384
  const { fs, path: resolved } = resolveMount(await realpath(path));
369
- return await fs.exists(resolved, cred);
385
+ return await fs.exists(resolved);
370
386
  }
371
387
  catch (e) {
372
388
  if (e instanceof ErrnoError && e.code == 'ENOENT') {
@@ -379,7 +395,10 @@ export async function stat(path, options) {
379
395
  path = normalizePath(path);
380
396
  const { fs, path: resolved } = resolveMount((await exists(path)) ? await realpath(path) : path);
381
397
  try {
382
- const stats = await fs.stat(resolved, cred);
398
+ const stats = await fs.stat(resolved);
399
+ if (!stats.hasAccess(constants.R_OK, credentials)) {
400
+ throw ErrnoError.With('EACCES', path, 'stat');
401
+ }
383
402
  return options?.bigint ? new BigIntStats(stats) : stats;
384
403
  }
385
404
  catch (e) {
@@ -391,7 +410,7 @@ export async function lstat(path, options) {
391
410
  path = normalizePath(path);
392
411
  const { fs, path: resolved } = resolveMount(path);
393
412
  try {
394
- const stats = await fs.stat(resolved, cred);
413
+ const stats = await fs.stat(resolved);
395
414
  return options?.bigint ? new BigIntStats(stats) : stats;
396
415
  }
397
416
  catch (e) {
@@ -430,7 +449,11 @@ export async function unlink(path) {
430
449
  path = normalizePath(path);
431
450
  const { fs, path: resolved } = resolveMount(path);
432
451
  try {
433
- await fs.unlink(resolved, cred);
452
+ if (!(await fs.stat(resolved)).hasAccess(constants.W_OK, credentials)) {
453
+ throw ErrnoError.With('EACCES', resolved, 'unlink');
454
+ }
455
+ await fs.unlink(resolved);
456
+ emitChange('rename', path.toString());
434
457
  }
435
458
  catch (e) {
436
459
  throw fixError(e, { [resolved]: path });
@@ -446,33 +469,39 @@ async function _open(path, _flag, _mode = 0o644, resolveSymlinks) {
446
469
  const mode = normalizeMode(_mode, 0o644), flag = parseFlag(_flag);
447
470
  path = resolveSymlinks && (await exists(path)) ? await realpath(path) : path;
448
471
  const { fs, path: resolved } = resolveMount(path);
449
- if (!(await fs.exists(resolved, cred))) {
472
+ const stats = await fs.stat(resolved).catch(() => null);
473
+ if (!stats) {
450
474
  if ((!isWriteable(flag) && !isAppendable(flag)) || flag == 'r+') {
451
475
  throw ErrnoError.With('ENOENT', path, '_open');
452
476
  }
453
477
  // Create the file
454
- const parentStats = await fs.stat(dirname(resolved), cred);
455
- if (parentStats && !parentStats.isDirectory()) {
478
+ const parentStats = await fs.stat(dirname(resolved));
479
+ if (!parentStats.hasAccess(constants.W_OK, credentials)) {
480
+ throw ErrnoError.With('EACCES', dirname(path), '_open');
481
+ }
482
+ if (!parentStats.isDirectory()) {
456
483
  throw ErrnoError.With('ENOTDIR', dirname(path), '_open');
457
484
  }
458
- return new FileHandle(await fs.createFile(resolved, flag, mode, cred));
485
+ return new FileHandle(await fs.createFile(resolved, flag, mode));
486
+ }
487
+ if (!stats.hasAccess(flagToMode(flag), credentials)) {
488
+ throw ErrnoError.With('EACCES', path, '_open');
459
489
  }
460
490
  if (isExclusive(flag)) {
461
491
  throw ErrnoError.With('EEXIST', path, '_open');
462
492
  }
463
- if (!isTruncating(flag)) {
464
- return new FileHandle(await fs.openFile(resolved, flag, cred));
465
- }
493
+ const handle = new FileHandle(await fs.openFile(resolved, flag));
466
494
  /*
467
495
  In a previous implementation, we deleted the file and
468
496
  re-created it. However, this created a race condition if another
469
497
  asynchronous request was trying to read the file, as the file
470
498
  would not exist for a small period of time.
471
499
  */
472
- const file = await fs.openFile(resolved, flag, cred);
473
- await file.truncate(0);
474
- await file.sync();
475
- return new FileHandle(file);
500
+ if (isTruncating(flag)) {
501
+ await handle.truncate(0);
502
+ await handle.sync();
503
+ }
504
+ return handle;
476
505
  }
477
506
  /**
478
507
  * Asynchronous file open.
@@ -581,7 +610,11 @@ export async function rmdir(path) {
581
610
  path = (await exists(path)) ? await realpath(path) : path;
582
611
  const { fs, path: resolved } = resolveMount(path);
583
612
  try {
584
- await fs.rmdir(resolved, cred);
613
+ if (!(await fs.stat(resolved)).hasAccess(constants.W_OK, credentials)) {
614
+ throw ErrnoError.With('EACCES', resolved, 'rmdir');
615
+ }
616
+ await fs.rmdir(resolved);
617
+ emitChange('rename', path.toString());
585
618
  }
586
619
  catch (e) {
587
620
  throw fixError(e, { [resolved]: path });
@@ -597,15 +630,24 @@ export async function mkdir(path, options) {
597
630
  const errorPaths = { [resolved]: path };
598
631
  try {
599
632
  if (!options?.recursive) {
600
- await fs.mkdir(resolved, mode, cred);
633
+ if (!(await fs.stat(dirname(resolved))).hasAccess(constants.W_OK, credentials)) {
634
+ throw ErrnoError.With('EACCES', dirname(resolved), 'mkdir');
635
+ }
636
+ await fs.mkdir(resolved, mode);
637
+ emitChange('rename', path.toString());
638
+ return;
601
639
  }
602
640
  const dirs = [];
603
- for (let dir = resolved, origDir = path; !(await fs.exists(dir, cred)); dir = dirname(dir), origDir = dirname(origDir)) {
641
+ for (let dir = resolved, origDir = path; !(await fs.exists(dir)); dir = dirname(dir), origDir = dirname(origDir)) {
604
642
  dirs.unshift(dir);
605
643
  errorPaths[dir] = origDir;
606
644
  }
607
645
  for (const dir of dirs) {
608
- await fs.mkdir(dir, mode, cred);
646
+ if (!(await fs.stat(dirname(dir))).hasAccess(constants.W_OK, credentials)) {
647
+ throw ErrnoError.With('EACCES', dirname(dir), 'mkdir');
648
+ }
649
+ await fs.mkdir(dir, mode);
650
+ emitChange('rename', dir);
609
651
  }
610
652
  return dirs[0];
611
653
  }
@@ -616,11 +658,14 @@ export async function mkdir(path, options) {
616
658
  mkdir;
617
659
  export async function readdir(path, options) {
618
660
  path = normalizePath(path);
661
+ if (!(await stat(path)).hasAccess(constants.R_OK, credentials)) {
662
+ throw ErrnoError.With('EACCES', path, 'readdir');
663
+ }
619
664
  path = (await exists(path)) ? await realpath(path) : path;
620
665
  const { fs, path: resolved } = resolveMount(path);
621
666
  let entries;
622
667
  try {
623
- entries = await fs.readdir(resolved, cred);
668
+ entries = await fs.readdir(resolved);
624
669
  }
625
670
  catch (e) {
626
671
  throw fixError(e, { [resolved]: path });
@@ -645,18 +690,31 @@ readdir;
645
690
  // SYMLINK METHODS
646
691
  /**
647
692
  * `link`.
648
- * @param existing
649
- * @param newpath
693
+ * @param targetPath
694
+ * @param linkPath
650
695
  */
651
- export async function link(existing, newpath) {
652
- existing = normalizePath(existing);
653
- newpath = normalizePath(newpath);
654
- const { fs, path: resolved } = resolveMount(newpath);
696
+ export async function link(targetPath, linkPath) {
697
+ targetPath = normalizePath(targetPath);
698
+ if (!(await stat(dirname(targetPath))).hasAccess(constants.R_OK, credentials)) {
699
+ throw ErrnoError.With('EACCES', dirname(targetPath), 'link');
700
+ }
701
+ linkPath = normalizePath(linkPath);
702
+ if (!(await stat(dirname(linkPath))).hasAccess(constants.W_OK, credentials)) {
703
+ throw ErrnoError.With('EACCES', dirname(linkPath), 'link');
704
+ }
705
+ const { fs, path } = resolveMount(targetPath);
706
+ const link = resolveMount(linkPath);
707
+ if (fs != link.fs) {
708
+ throw ErrnoError.With('EXDEV', linkPath, 'link');
709
+ }
655
710
  try {
656
- return await fs.link(existing, newpath, cred);
711
+ if (!(await fs.stat(path)).hasAccess(constants.W_OK, credentials)) {
712
+ throw ErrnoError.With('EACCES', path, 'link');
713
+ }
714
+ return await fs.link(path, link.path);
657
715
  }
658
716
  catch (e) {
659
- throw fixError(e, { [resolved]: newpath });
717
+ throw fixError(e, { [link.path]: linkPath, [path]: targetPath });
660
718
  }
661
719
  }
662
720
  link;
@@ -840,7 +898,7 @@ export async function realpath(path, options) {
840
898
  const lpath = join(dir == '/' ? '/' : await realpath(dir), base);
841
899
  const { fs, path: resolvedPath, mountPoint } = resolveMount(lpath);
842
900
  try {
843
- const stats = await fs.stat(resolvedPath, cred);
901
+ const stats = await fs.stat(resolvedPath);
844
902
  if (!stats.isSymbolicLink()) {
845
903
  return lpath;
846
904
  }
@@ -854,7 +912,7 @@ realpath;
854
912
  export function watch(filename, options = {}) {
855
913
  return {
856
914
  [Symbol.asyncIterator]() {
857
- const watcher = new FSWatcher(typeof options != 'string' ? options : { encoding: options });
915
+ const watcher = new FSWatcher(filename.toString(), typeof options != 'string' ? options : { encoding: options });
858
916
  function withDone(done) {
859
917
  return function () {
860
918
  const event = Promise.withResolvers();
@@ -880,7 +938,7 @@ watch;
880
938
  */
881
939
  export async function access(path, mode = constants.F_OK) {
882
940
  const stats = await stat(path);
883
- if (!stats.hasAccess(mode, cred)) {
941
+ if (!stats.hasAccess(mode, credentials)) {
884
942
  throw new ErrnoError(Errno.EACCES);
885
943
  }
886
944
  }
@@ -936,6 +994,7 @@ export async function copyFile(src, dest, mode) {
936
994
  throw new ErrnoError(Errno.EEXIST, 'Destination file already exists.', dest, 'copyFile');
937
995
  }
938
996
  await writeFile(dest, await readFile(src));
997
+ emitChange('rename', dest.toString());
939
998
  }
940
999
  copyFile;
941
1000
  /**
@@ -947,9 +1006,7 @@ copyFile;
947
1006
  */
948
1007
  export async function opendir(path, options) {
949
1008
  path = normalizePath(path);
950
- const dir = new Dir(path);
951
- await dir._loadEntries();
952
- return dir;
1009
+ return new Dir(path);
953
1010
  }
954
1011
  opendir;
955
1012
  /**
@@ -1,11 +1,8 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
2
  import type { BigIntStatsFs, StatsFs } from 'node:fs';
3
- import type { Cred } from '../cred.js';
4
3
  import type { File } from '../file.js';
5
4
  import type { FileSystem } from '../filesystem.js';
6
5
  import { type AbsolutePath } from './path.js';
7
- export declare let cred: Cred;
8
- export declare function setCred(val: Cred): void;
9
6
  export declare const fdMap: Map<number, File>;
10
7
  export declare function file2fd(file: File): number;
11
8
  export declare function fd2file(fd: number): File;
@@ -1,15 +1,9 @@
1
1
  // Utilities and shared data
2
2
  import { InMemory } from '../backends/memory.js';
3
- import { rootCred } from '../cred.js';
4
3
  import { Errno, ErrnoError } from '../error.js';
5
4
  import { size_max } from '../inode.js';
6
5
  import { normalizePath } from '../utils.js';
7
6
  import { resolve } from './path.js';
8
- // credentials
9
- export let cred = rootCred;
10
- export function setCred(val) {
11
- cred = val;
12
- }
13
7
  // descriptors
14
8
  export const fdMap = new Map();
15
9
  let nextFd = 100;
@@ -197,7 +197,6 @@ export declare function rmdirSync(path: fs.PathLike): void;
197
197
  * Synchronous `mkdir`.
198
198
  * @param path
199
199
  * @param mode defaults to o777
200
- * @todo Implement recursion
201
200
  */
202
201
  export declare function mkdirSync(path: fs.PathLike, options: fs.MakeDirectoryOptions & {
203
202
  recursive: true;
@@ -230,10 +229,10 @@ export declare function readdirSync(path: fs.PathLike, options?: (fs.ObjectEncod
230
229
  }) | BufferEncoding | null): string[] | Buffer[];
231
230
  /**
232
231
  * Synchronous `link`.
233
- * @param existing
234
- * @param newpath
232
+ * @param targetPath
233
+ * @param linkPath
235
234
  */
236
- export declare function linkSync(existing: fs.PathLike, newpath: fs.PathLike): void;
235
+ export declare function linkSync(targetPath: fs.PathLike, linkPath: fs.PathLike): void;
237
236
  /**
238
237
  * Synchronous `symlink`.
239
238
  * @param target target path