@zenfs/core 1.11.4 → 2.1.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 (135) hide show
  1. package/dist/backends/backend.d.ts +19 -15
  2. package/dist/backends/backend.js +36 -19
  3. package/dist/backends/cow.d.ts +20 -30
  4. package/dist/backends/cow.js +83 -192
  5. package/dist/backends/fetch.d.ts +1 -0
  6. package/dist/backends/fetch.js +30 -30
  7. package/dist/backends/index.d.ts +1 -1
  8. package/dist/backends/index.js +1 -1
  9. package/dist/backends/memory.d.ts +5 -7
  10. package/dist/backends/memory.js +2 -3
  11. package/dist/backends/passthrough.d.ts +19 -23
  12. package/dist/backends/passthrough.js +98 -288
  13. package/dist/backends/port.d.ts +220 -0
  14. package/dist/backends/port.js +328 -0
  15. package/dist/backends/single_buffer.d.ts +59 -47
  16. package/dist/backends/single_buffer.js +468 -219
  17. package/dist/backends/store/fs.d.ts +25 -35
  18. package/dist/backends/store/fs.js +276 -315
  19. package/dist/backends/store/store.d.ts +10 -15
  20. package/dist/backends/store/store.js +11 -10
  21. package/dist/config.d.ts +3 -12
  22. package/dist/config.js +17 -19
  23. package/dist/context.d.ts +8 -21
  24. package/dist/context.js +33 -10
  25. package/dist/index.d.ts +2 -1
  26. package/dist/index.js +2 -1
  27. package/dist/internal/contexts.d.ts +63 -0
  28. package/dist/internal/contexts.js +15 -0
  29. package/dist/internal/credentials.d.ts +2 -11
  30. package/dist/internal/credentials.js +0 -19
  31. package/dist/internal/devices.d.ts +18 -80
  32. package/dist/internal/devices.js +103 -316
  33. package/dist/internal/error.d.ts +9 -204
  34. package/dist/internal/error.js +19 -288
  35. package/dist/internal/file_index.d.ts +1 -1
  36. package/dist/internal/file_index.js +11 -11
  37. package/dist/internal/filesystem.d.ts +51 -94
  38. package/dist/internal/filesystem.js +21 -20
  39. package/dist/internal/index.d.ts +1 -2
  40. package/dist/internal/index.js +1 -2
  41. package/dist/internal/index_fs.d.ts +12 -30
  42. package/dist/internal/index_fs.js +37 -69
  43. package/dist/internal/inode.d.ts +140 -24
  44. package/dist/internal/inode.js +515 -66
  45. package/dist/mixins/async.js +52 -112
  46. package/dist/mixins/mutexed.d.ts +19 -18
  47. package/dist/mixins/mutexed.js +62 -64
  48. package/dist/mixins/readonly.d.ts +7 -6
  49. package/dist/mixins/readonly.js +24 -18
  50. package/dist/mixins/sync.js +8 -8
  51. package/dist/{vfs/path.d.ts → path.d.ts} +3 -4
  52. package/dist/{vfs/path.js → path.js} +6 -9
  53. package/dist/polyfills.js +1 -1
  54. package/dist/readline.d.ts +134 -0
  55. package/dist/readline.js +623 -0
  56. package/dist/utils.d.ts +9 -37
  57. package/dist/utils.js +17 -85
  58. package/dist/vfs/acl.d.ts +42 -0
  59. package/dist/vfs/acl.js +268 -0
  60. package/dist/vfs/async.d.ts +9 -23
  61. package/dist/vfs/async.js +25 -27
  62. package/dist/vfs/config.d.ts +6 -18
  63. package/dist/vfs/config.js +8 -18
  64. package/dist/vfs/dir.d.ts +3 -3
  65. package/dist/vfs/dir.js +12 -12
  66. package/dist/vfs/file.d.ts +106 -0
  67. package/dist/vfs/file.js +244 -0
  68. package/dist/vfs/flags.d.ts +19 -0
  69. package/dist/vfs/flags.js +62 -0
  70. package/dist/vfs/index.d.ts +4 -10
  71. package/dist/vfs/index.js +4 -13
  72. package/dist/vfs/ioctl.d.ts +88 -0
  73. package/dist/vfs/ioctl.js +409 -0
  74. package/dist/vfs/promises.d.ts +81 -19
  75. package/dist/vfs/promises.js +404 -288
  76. package/dist/vfs/shared.d.ts +7 -37
  77. package/dist/vfs/shared.js +29 -85
  78. package/dist/{stats.d.ts → vfs/stats.d.ts} +14 -28
  79. package/dist/{stats.js → vfs/stats.js} +11 -66
  80. package/dist/vfs/streams.d.ts +1 -0
  81. package/dist/vfs/streams.js +32 -27
  82. package/dist/vfs/sync.d.ts +3 -3
  83. package/dist/vfs/sync.js +263 -260
  84. package/dist/vfs/watchers.d.ts +2 -2
  85. package/dist/vfs/watchers.js +12 -12
  86. package/dist/vfs/xattr.d.ts +116 -0
  87. package/dist/vfs/xattr.js +201 -0
  88. package/package.json +5 -3
  89. package/readme.md +1 -1
  90. package/scripts/test.js +2 -2
  91. package/tests/assignment.ts +1 -1
  92. package/tests/backend/config.worker.js +4 -1
  93. package/tests/backend/fetch.test.ts +3 -0
  94. package/tests/backend/port.test.ts +19 -33
  95. package/tests/backend/remote.worker.js +4 -1
  96. package/tests/backend/single-buffer.test.ts +53 -0
  97. package/tests/backend/single-buffer.worker.js +30 -0
  98. package/tests/common/context.test.ts +3 -3
  99. package/tests/common/handle.test.ts +17 -12
  100. package/tests/common/mutex.test.ts +9 -9
  101. package/tests/common/path.test.ts +1 -1
  102. package/tests/common/readline.test.ts +104 -0
  103. package/tests/common.ts +4 -19
  104. package/tests/fetch/fetch.ts +2 -2
  105. package/tests/fs/append.test.ts +4 -4
  106. package/tests/fs/directory.test.ts +25 -25
  107. package/tests/fs/errors.test.ts +15 -19
  108. package/tests/fs/links.test.ts +4 -3
  109. package/tests/fs/open.test.ts +4 -21
  110. package/tests/fs/permissions.test.ts +14 -18
  111. package/tests/fs/read.test.ts +10 -9
  112. package/tests/fs/readFile.test.ts +10 -26
  113. package/tests/fs/rename.test.ts +4 -9
  114. package/tests/fs/stat.test.ts +8 -8
  115. package/tests/fs/streams.test.ts +2 -11
  116. package/tests/fs/times.test.ts +7 -7
  117. package/tests/fs/truncate.test.ts +8 -36
  118. package/tests/fs/watch.test.ts +10 -10
  119. package/tests/fs/write.test.ts +77 -13
  120. package/tests/fs/xattr.test.ts +85 -0
  121. package/tests/logs.js +22 -0
  122. package/tests/setup/context.ts +1 -1
  123. package/tests/setup/index.ts +3 -3
  124. package/tests/setup/port.ts +7 -1
  125. package/dist/backends/port/fs.d.ts +0 -84
  126. package/dist/backends/port/fs.js +0 -151
  127. package/dist/backends/port/rpc.d.ts +0 -77
  128. package/dist/backends/port/rpc.js +0 -100
  129. package/dist/backends/store/simple.d.ts +0 -20
  130. package/dist/backends/store/simple.js +0 -13
  131. package/dist/internal/file.d.ts +0 -359
  132. package/dist/internal/file.js +0 -751
  133. package/dist/internal/log.d.ts +0 -133
  134. package/dist/internal/log.js +0 -218
  135. package/tests/fs/writeFile.test.ts +0 -70
@@ -51,37 +51,74 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
51
51
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
52
  });
53
53
  import { Buffer } from 'buffer';
54
- import { _throw } from 'utilium';
55
- import { credentials } from '../internal/credentials.js';
56
- import { Errno, ErrnoError } from '../internal/error.js';
57
- import { flagToMode, isAppendable, isExclusive, isReadable, isTruncating, isWriteable, parseFlag } from '../internal/file.js';
54
+ import { Exception, rethrow, setUVMessage, UV } from 'kerium';
55
+ import { decodeUTF8, pick } from 'utilium';
56
+ import { defaultContext } from '../internal/contexts.js';
57
+ import { hasAccess, InodeFlags, isBlockDevice, isCharacterDevice, isDirectory, isSymbolicLink } from '../internal/inode.js';
58
+ import { dirname, join, parse, resolve } from '../path.js';
58
59
  import '../polyfills.js';
59
- import { BigIntStats } from '../stats.js';
60
- import { decodeUTF8, normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
61
- import { config } from './config.js';
60
+ import { createInterface } from '../readline.js';
61
+ import { __assertType, normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
62
+ import { checkAccess } from './config.js';
62
63
  import * as constants from './constants.js';
63
64
  import { Dir, Dirent } from './dir.js';
64
- import { dirname, join, parse, resolve } from './path.js';
65
- import { _statfs, fd2file, fdMap, file2fd, fixError, resolveMount } from './shared.js';
65
+ import { deleteFD, fromFD, SyncHandle, toFD } from './file.js';
66
+ import * as flags from './flags.js';
67
+ import { _statfs, resolveMount } from './shared.js';
68
+ import { _chown, BigIntStats, Stats } from './stats.js';
66
69
  import { ReadStream, WriteStream } from './streams.js';
67
- import { FSWatcher, emitChange } from './watchers.js';
70
+ import { emitChange, FSWatcher } from './watchers.js';
68
71
  export * as constants from './constants.js';
69
72
  export class FileHandle {
70
- constructor(fdOrFile, context) {
73
+ /**
74
+ * Get the current file position.
75
+ *
76
+ * We emulate the following bug mentioned in the Node documentation:
77
+ *
78
+ * On Linux, positional writes don't work when the file is opened in append mode.
79
+ * The kernel ignores the position argument and always appends the data to the end of the file.
80
+ * @returns The current file position.
81
+ */
82
+ get position() {
83
+ return this.flag & constants.O_APPEND ? this.inode.size : this._position;
84
+ }
85
+ set position(value) {
86
+ this._position = value;
87
+ }
88
+ constructor(context, fd) {
71
89
  this.context = context;
72
- const isFile = typeof fdOrFile != 'number';
73
- this.fd = isFile ? file2fd(fdOrFile) : fdOrFile;
74
- this.file = isFile ? fdOrFile : fd2file(fdOrFile);
90
+ this.fd = fd;
91
+ /**
92
+ * Current position
93
+ */
94
+ this._position = 0;
95
+ /**
96
+ * Whether the file has changes which have not been written to the FS
97
+ */
98
+ this.dirty = false;
99
+ /**
100
+ * Whether the file is open or closed
101
+ */
102
+ this.closed = false;
103
+ const sync = fromFD(context, fd);
104
+ Object.assign(this, pick(sync, 'path', 'fs', 'internalPath', 'flag', 'inode'));
105
+ }
106
+ get _isSync() {
107
+ return !!(this.flag & constants.O_SYNC || this.inode.flags & InodeFlags.Sync);
75
108
  }
76
109
  _emitChange() {
77
- var _a, _b, _c;
78
- emitChange(this.context, 'change', this.file.path.slice((_c = (_b = (_a = this.context) === null || _a === void 0 ? void 0 : _a.root) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0));
110
+ emitChange(this.context, 'change', this.path);
79
111
  }
80
112
  /**
81
113
  * Asynchronous fchown(2) - Change ownership of a file.
82
114
  */
83
115
  async chown(uid, gid) {
84
- await this.file.chown(uid, gid);
116
+ if (this.closed)
117
+ throw UV('EBADF', 'chown', this.path);
118
+ this.dirty = true;
119
+ _chown(this.inode, uid, gid);
120
+ if (this._isSync)
121
+ await this.sync();
85
122
  this._emitChange();
86
123
  }
87
124
  /**
@@ -91,31 +128,55 @@ export class FileHandle {
91
128
  async chmod(mode) {
92
129
  const numMode = normalizeMode(mode, -1);
93
130
  if (numMode < 0)
94
- throw new ErrnoError(Errno.EINVAL, 'Invalid mode');
95
- await this.file.chmod(numMode);
131
+ throw UV('EINVAL', 'chmod', this.path);
132
+ if (this.closed)
133
+ throw UV('EBADF', 'chmod', this.path);
134
+ this.dirty = true;
135
+ this.inode.mode = (this.inode.mode & (numMode > constants.S_IFMT ? ~constants.S_IFMT : constants.S_IFMT)) | numMode;
136
+ if (this._isSync || numMode > constants.S_IFMT)
137
+ await this.sync();
96
138
  this._emitChange();
97
139
  }
98
140
  /**
99
141
  * Asynchronous fdatasync(2) - synchronize a file's in-core state with storage device.
100
142
  */
101
143
  datasync() {
102
- return this.file.datasync();
144
+ return this.sync();
103
145
  }
104
146
  /**
105
147
  * Asynchronous fsync(2) - synchronize a file's in-core state with the underlying storage device.
106
148
  */
107
- sync() {
108
- return this.file.sync();
149
+ async sync() {
150
+ if (this.closed)
151
+ throw UV('EBADF', 'sync', this.path);
152
+ if (!this.dirty)
153
+ return;
154
+ if (!this.fs.attributes.has('no_write'))
155
+ await this.fs.touch(this.internalPath, this.inode);
156
+ this.dirty = false;
109
157
  }
110
158
  /**
111
159
  * Asynchronous ftruncate(2) - Truncate a file to a specified length.
112
160
  * @param length If not specified, defaults to `0`.
113
161
  */
114
- async truncate(length) {
115
- length || (length = 0);
162
+ async truncate(length = 0) {
163
+ if (this.closed)
164
+ throw UV('EBADF', 'truncate', this.path);
116
165
  if (length < 0)
117
- throw new ErrnoError(Errno.EINVAL);
118
- await this.file.truncate(length);
166
+ throw UV('EINVAL', 'truncate', this.path);
167
+ if (!(this.flag & constants.O_WRONLY || this.flag & constants.O_RDWR))
168
+ throw UV('EBADF', 'truncate', this.path);
169
+ if (this.fs.attributes.has('readonly'))
170
+ throw UV('EROFS', 'truncate', this.path);
171
+ if (this.inode.flags & InodeFlags.Immutable)
172
+ throw UV('EPERM', 'truncate', this.path);
173
+ this.dirty = true;
174
+ if (!(this.flag & constants.O_WRONLY || this.flag & constants.O_RDWR))
175
+ throw UV('EBADF', 'truncate', this.path);
176
+ this.inode.mtimeMs = Date.now();
177
+ this.inode.size = length;
178
+ if (this._isSync)
179
+ await this.sync();
119
180
  this._emitChange();
120
181
  }
121
182
  /**
@@ -124,7 +185,13 @@ export class FileHandle {
124
185
  * @param mtime The last modified time. If a string is provided, it will be coerced to number.
125
186
  */
126
187
  async utimes(atime, mtime) {
127
- await this.file.utimes(normalizeTime(atime), normalizeTime(mtime));
188
+ if (this.closed)
189
+ throw UV('EBADF', 'utimes', this.path);
190
+ this.dirty = true;
191
+ this.inode.atimeMs = normalizeTime(atime);
192
+ this.inode.mtimeMs = normalizeTime(mtime);
193
+ if (this._isSync)
194
+ await this.sync();
128
195
  this._emitChange();
129
196
  }
130
197
  /**
@@ -138,17 +205,41 @@ export class FileHandle {
138
205
  */
139
206
  async appendFile(data, _options = {}) {
140
207
  const options = normalizeOptions(_options, 'utf8', 'a', 0o644);
141
- const flag = parseFlag(options.flag);
142
- if (!isAppendable(flag)) {
143
- throw new ErrnoError(Errno.EINVAL, 'Flag passed to appendFile must allow for appending');
144
- }
145
- if (typeof data != 'string' && !options.encoding) {
146
- throw new ErrnoError(Errno.EINVAL, 'Encoding not specified');
147
- }
208
+ const flag = flags.parse(options.flag);
209
+ if (!(flag & constants.O_APPEND))
210
+ throw UV('EBADF', 'write', this.path);
148
211
  const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : data;
149
- await this.file.write(encodedData, 0, encodedData.length);
212
+ await this._write(encodedData, 0, encodedData.length);
150
213
  this._emitChange();
151
214
  }
215
+ /**
216
+ * Read data from the file.
217
+ * @param buffer The buffer that the data will be written to.
218
+ * @param offset The offset within the buffer where writing will start.
219
+ * @param length An integer specifying the number of bytes to read.
220
+ * @param position An integer specifying where to begin reading from in the file.
221
+ * If position is unset, data will be read from the current file position.
222
+ */
223
+ async _read(buffer, offset = 0, length = buffer.byteLength - offset, position = this.position) {
224
+ if (this.closed)
225
+ throw UV('EBADF', 'read', this.path);
226
+ if (this.flag & constants.O_WRONLY)
227
+ throw UV('EBADF', 'read', this.path);
228
+ if (!(this.inode.flags & InodeFlags.NoAtime) && !this.fs.attributes.has('no_atime')) {
229
+ this.dirty = true;
230
+ this.inode.atimeMs = Date.now();
231
+ }
232
+ let end = position + length;
233
+ if (!isCharacterDevice(this.inode) && !isBlockDevice(this.inode) && end > this.inode.size) {
234
+ end = position + Math.max(this.inode.size - position, 0);
235
+ }
236
+ this._position = end;
237
+ const uint8 = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
238
+ await this.fs.read(this.internalPath, uint8.subarray(offset, offset + length), position, end);
239
+ if (this._isSync)
240
+ await this.sync();
241
+ return { bytesRead: end - position, buffer };
242
+ }
152
243
  async read(buffer, offset, length, position) {
153
244
  if (typeof offset == 'object' && offset != null) {
154
245
  position = offset.position;
@@ -161,19 +252,19 @@ export class FileHandle {
161
252
  offset = buffer.offset;
162
253
  buffer = buffer.buffer;
163
254
  }
164
- const pos = Number.isSafeInteger(position) ? position : this.file.position;
165
- buffer || (buffer = new Uint8Array((await this.file.stat()).size));
255
+ const pos = Number.isSafeInteger(position) ? position : this.position;
256
+ buffer || (buffer = new Uint8Array(this.inode.size));
166
257
  offset !== null && offset !== void 0 ? offset : (offset = 0);
167
- return this.file.read(buffer, offset, length !== null && length !== void 0 ? length : buffer.byteLength - offset, pos);
258
+ return this._read(buffer, offset, length !== null && length !== void 0 ? length : buffer.byteLength - offset, pos);
168
259
  }
169
260
  async readFile(_options) {
170
261
  const options = normalizeOptions(_options, null, 'r', 0o444);
171
- const flag = parseFlag(options.flag);
172
- if (!isReadable(flag)) {
173
- throw new ErrnoError(Errno.EINVAL, 'Flag passed must allow for reading');
174
- }
262
+ const flag = flags.parse(options.flag);
263
+ if (flag & constants.O_WRONLY)
264
+ throw UV('EBADF', 'read', this.path);
175
265
  const { size } = await this.stat();
176
- const { buffer: data } = await this.file.read(new Uint8Array(size), 0, size, 0);
266
+ const data = new Uint8Array(size);
267
+ await this._read(data, 0, size, 0);
177
268
  const buffer = Buffer.from(data);
178
269
  return options.encoding ? buffer.toString(options.encoding) : buffer;
179
270
  }
@@ -182,23 +273,73 @@ export class FileHandle {
182
273
  * The handle will not be closed automatically.
183
274
  */
184
275
  readableWebStream(options = {}) {
185
- return this.file.streamRead({});
276
+ if (this.closed)
277
+ throw UV('EBADF', 'readableWebStream', this.path);
278
+ return this.fs.streamRead(this.internalPath, options);
186
279
  }
187
280
  /**
188
- * @todo Implement
281
+ * Not part of the Node.js API!
282
+ *
283
+ * Write file data using a `WritableStream`.
284
+ * The handle will not be closed automatically.
285
+ * @internal
286
+ */
287
+ writableWebStream(options = {}) {
288
+ if (this.closed)
289
+ throw UV('EBADF', 'writableWebStream', this.path);
290
+ if (this.inode.flags & InodeFlags.Immutable)
291
+ throw UV('EPERM', 'writableWebStream', this.path);
292
+ return this.fs.streamWrite(this.internalPath, options);
293
+ }
294
+ /**
295
+ * Creates a readline Interface object that allows reading the file line by line
296
+ * @param options Options for creating a read stream
297
+ * @returns A readline interface for reading the file line by line
189
298
  */
190
299
  readLines(options) {
191
- throw ErrnoError.With('ENOSYS', this.file.path, 'FileHandle.readLines');
300
+ if (this.closed || this.flag & constants.O_WRONLY)
301
+ throw UV('EBADF', 'read', this.path);
302
+ return createInterface({ input: this.createReadStream(options), crlfDelay: Infinity });
192
303
  }
193
304
  [Symbol.asyncDispose]() {
194
305
  return this.close();
195
306
  }
196
307
  async stat(opts) {
197
- const stats = await this.file.stat();
198
- if (config.checkAccess && !stats.hasAccess(constants.R_OK, this.context)) {
199
- throw ErrnoError.With('EACCES', this.file.path, 'stat');
200
- }
201
- return (opts === null || opts === void 0 ? void 0 : opts.bigint) ? new BigIntStats(stats) : stats;
308
+ if (this.closed)
309
+ throw UV('EBADF', 'stat', this.path);
310
+ if (checkAccess && !hasAccess(this.context, this.inode, constants.R_OK))
311
+ throw UV('EACCES', 'stat', this.path);
312
+ return (opts === null || opts === void 0 ? void 0 : opts.bigint) ? new BigIntStats(this.inode) : new Stats(this.inode);
313
+ }
314
+ /**
315
+ * Write buffer to the file.
316
+ * @param buffer Uint8Array containing the data to write to the file.
317
+ * @param offset Offset in the buffer to start reading data from.
318
+ * @param length The amount of bytes to write to the file.
319
+ * @param position Offset from the beginning of the file where this data should be written.
320
+ * If position is null, the data will be written at the current position.
321
+ */
322
+ async _write(buffer, offset = 0, length = buffer.byteLength - offset, position = this.position) {
323
+ if (this.closed)
324
+ throw UV('EBADF', 'write', this.path);
325
+ if (this.inode.flags & InodeFlags.Immutable)
326
+ throw UV('EPERM', 'write', this.path);
327
+ if (!(this.flag & constants.O_WRONLY || this.flag & constants.O_RDWR))
328
+ throw UV('EBADF', 'write', this.path);
329
+ if (this.fs.attributes.has('readonly'))
330
+ throw UV('EROFS', 'write', this.path);
331
+ this.dirty = true;
332
+ const end = position + length;
333
+ const slice = buffer.subarray(offset, offset + length);
334
+ if (!isCharacterDevice(this.inode) && !isBlockDevice(this.inode) && end > this.inode.size)
335
+ this.inode.size = end;
336
+ this.inode.mtimeMs = Date.now();
337
+ this.inode.ctimeMs = Date.now();
338
+ this._position = position + slice.byteLength;
339
+ await this.fs.write(this.internalPath, slice, position);
340
+ if (this._isSync)
341
+ await this.sync();
342
+ return slice.byteLength;
202
343
  }
203
344
  /**
204
345
  * Asynchronously writes `string` to the file.
@@ -225,8 +366,8 @@ export class FileHandle {
225
366
  length = typeof lenOrEnc == 'number' ? lenOrEnc : buffer.byteLength;
226
367
  position = typeof position === 'number' ? position : null;
227
368
  }
228
- position !== null && position !== void 0 ? position : (position = this.file.position);
229
- const bytesWritten = await this.file.write(buffer, offset, length, position);
369
+ position !== null && position !== void 0 ? position : (position = this.position);
370
+ const bytesWritten = await this._write(buffer, offset, length, position);
230
371
  this._emitChange();
231
372
  return { buffer: data, bytesWritten };
232
373
  }
@@ -242,23 +383,32 @@ export class FileHandle {
242
383
  */
243
384
  async writeFile(data, _options = {}) {
244
385
  const options = normalizeOptions(_options, 'utf8', 'w', 0o644);
245
- const flag = parseFlag(options.flag);
246
- if (!isWriteable(flag)) {
247
- throw new ErrnoError(Errno.EINVAL, 'Flag passed must allow for writing');
248
- }
249
- if (typeof data != 'string' && !options.encoding) {
250
- throw new ErrnoError(Errno.EINVAL, 'Encoding not specified');
251
- }
386
+ const flag = flags.parse(options.flag);
387
+ if (!(flag & constants.O_WRONLY || flag & constants.O_RDWR))
388
+ throw UV('EBADF', 'writeFile', this.path);
252
389
  const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : data;
253
- await this.file.write(encodedData, 0, encodedData.length, 0);
390
+ await this._write(encodedData, 0, encodedData.length, 0);
254
391
  this._emitChange();
255
392
  }
256
393
  /**
257
394
  * Asynchronous close(2) - close a `FileHandle`.
258
395
  */
259
396
  async close() {
260
- await this.file.close();
261
- fdMap.delete(this.fd);
397
+ if (this.closed)
398
+ throw UV('EBADF', 'close', this.path);
399
+ await this.sync();
400
+ this.dispose();
401
+ deleteFD(this.context, this.fd);
402
+ }
403
+ /**
404
+ * Cleans up. This will *not* sync the file data to the FS
405
+ */
406
+ dispose(force) {
407
+ if (this.closed)
408
+ throw UV('EBADF', 'close', this.path);
409
+ if (this.dirty && !force)
410
+ throw UV('EBUSY', 'close', this.path);
411
+ this.closed = true;
262
412
  }
263
413
  /**
264
414
  * Asynchronous `writev`. Writes from multiple buffers.
@@ -268,7 +418,7 @@ export class FileHandle {
268
418
  */
269
419
  async writev(buffers, position) {
270
420
  if (typeof position == 'number')
271
- this.file.position = position;
421
+ this.position = position;
272
422
  let bytesWritten = 0;
273
423
  for (const buffer of buffers) {
274
424
  bytesWritten += (await this.write(buffer)).bytesWritten;
@@ -283,7 +433,7 @@ export class FileHandle {
283
433
  */
284
434
  async readv(buffers, position) {
285
435
  if (typeof position == 'number')
286
- this.file.position = position;
436
+ this.position = position;
287
437
  let bytesRead = 0;
288
438
  for (const buffer of buffers) {
289
439
  bytesRead += (await this.read(buffer)).bytesRead;
@@ -295,6 +445,8 @@ export class FileHandle {
295
445
  * @param options Options for the readable stream
296
446
  */
297
447
  createReadStream(options = {}) {
448
+ if (this.closed || this.flag & constants.O_WRONLY)
449
+ throw UV('EBADF', 'createReadStream', this.path);
298
450
  return new ReadStream(options, this);
299
451
  }
300
452
  /**
@@ -302,31 +454,44 @@ export class FileHandle {
302
454
  * @param options Options for the writeable stream.
303
455
  */
304
456
  createWriteStream(options = {}) {
457
+ if (this.closed)
458
+ throw UV('EBADF', 'createWriteStream', this.path);
459
+ if (this.inode.flags & InodeFlags.Immutable)
460
+ throw UV('EPERM', 'createWriteStream', this.path);
461
+ if (this.fs.attributes.has('readonly'))
462
+ throw UV('EROFS', 'createWriteStream', this.path);
305
463
  return new WriteStream(options, this);
306
464
  }
307
465
  }
308
466
  export async function rename(oldPath, newPath) {
309
467
  oldPath = normalizePath(oldPath);
468
+ __assertType(oldPath);
310
469
  newPath = normalizePath(newPath);
470
+ __assertType(newPath);
471
+ const $ex = { syscall: 'rename', path: oldPath, dest: newPath };
311
472
  const src = resolveMount(oldPath, this);
312
473
  const dst = resolveMount(newPath, this);
313
- if (config.checkAccess && !(await stat.call(this, dirname(oldPath))).hasAccess(constants.W_OK, this)) {
314
- throw ErrnoError.With('EACCES', oldPath, 'rename');
315
- }
316
- try {
317
- if (src.mountPoint == dst.mountPoint) {
318
- await src.fs.rename(src.path, dst.path);
319
- emitChange(this, 'rename', oldPath.toString());
320
- emitChange(this, 'change', newPath.toString());
321
- return;
322
- }
323
- await writeFile.call(this, newPath, await readFile(oldPath));
324
- await unlink.call(this, oldPath);
325
- emitChange(this, 'rename', oldPath.toString());
326
- }
327
- catch (e) {
328
- throw fixError(e, { [src.path]: oldPath, [dst.path]: newPath });
329
- }
474
+ if (src.fs !== dst.fs)
475
+ throw UV('EXDEV', $ex);
476
+ if (dst.path.startsWith(src.path + '/'))
477
+ throw UV('EBUSY', $ex);
478
+ const parent = (await stat.call(this, dirname(oldPath)).catch(rethrow($ex)));
479
+ const stats = (await stat.call(this, oldPath).catch(rethrow($ex)));
480
+ const newParent = (await stat.call(this, dirname(newPath)).catch(rethrow($ex)));
481
+ const newStats = (await stat.call(this, newPath).catch((e) => {
482
+ if (e.code == 'ENOENT')
483
+ return null;
484
+ throw setUVMessage(Object.assign(e, $ex));
485
+ }));
486
+ if (checkAccess && (!parent.hasAccess(constants.R_OK, this) || !newParent.hasAccess(constants.W_OK, this)))
487
+ throw UV('EACCES', $ex);
488
+ if (newStats && !isDirectory(stats) && isDirectory(newStats))
489
+ throw UV('EISDIR', $ex);
490
+ if (newStats && isDirectory(stats) && !isDirectory(newStats))
491
+ throw UV('ENOTDIR', $ex);
492
+ await src.fs.rename(src.path, dst.path).catch(rethrow($ex));
493
+ emitChange(this, 'rename', oldPath);
494
+ emitChange(this, 'change', newPath);
330
495
  }
331
496
  rename;
332
497
  /**
@@ -338,7 +503,7 @@ export async function exists(path) {
338
503
  return await fs.exists(resolved);
339
504
  }
340
505
  catch (e) {
341
- if (e instanceof ErrnoError && e.code == 'ENOENT') {
506
+ if (e instanceof Exception && e.code == 'ENOENT') {
342
507
  return false;
343
508
  }
344
509
  throw e;
@@ -347,31 +512,23 @@ export async function exists(path) {
347
512
  export async function stat(path, options) {
348
513
  path = normalizePath(path);
349
514
  const { fs, path: resolved } = resolveMount(await realpath.call(this, path), this);
350
- try {
351
- const stats = await fs.stat(resolved);
352
- if (config.checkAccess && !stats.hasAccess(constants.R_OK, this)) {
353
- throw ErrnoError.With('EACCES', resolved, 'stat');
354
- }
355
- return (options === null || options === void 0 ? void 0 : options.bigint) ? new BigIntStats(stats) : stats;
356
- }
357
- catch (e) {
358
- throw fixError(e, { [resolved]: path });
359
- }
515
+ const $ex = { syscall: 'stat', path };
516
+ const stats = await fs.stat(resolved).catch(rethrow($ex));
517
+ if (checkAccess && !hasAccess(this, stats, constants.R_OK))
518
+ throw UV('EACCES', $ex);
519
+ return (options === null || options === void 0 ? void 0 : options.bigint) ? new BigIntStats(stats) : new Stats(stats);
360
520
  }
361
521
  stat;
362
522
  export async function lstat(path, options) {
363
523
  path = normalizePath(path);
364
524
  const { fs, path: resolved } = resolveMount(path, this);
365
- try {
366
- const stats = await fs.stat(resolved);
367
- return (options === null || options === void 0 ? void 0 : options.bigint) ? new BigIntStats(stats) : stats;
368
- }
369
- catch (e) {
370
- throw fixError(e, { [resolved]: path });
371
- }
525
+ const $ex = { syscall: 'lstat', path };
526
+ const stats = await fs.stat(resolved).catch(rethrow($ex));
527
+ if (checkAccess && !hasAccess(this, stats, constants.R_OK))
528
+ throw UV('EACCES', $ex);
529
+ return (options === null || options === void 0 ? void 0 : options.bigint) ? new BigIntStats(stats) : new Stats(stats);
372
530
  }
373
531
  lstat;
374
- // FILE-ONLY METHODS
375
532
  export async function truncate(path, len = 0) {
376
533
  const env_1 = { stack: [], error: void 0, hasError: false };
377
534
  try {
@@ -392,28 +549,14 @@ truncate;
392
549
  export async function unlink(path) {
393
550
  path = normalizePath(path);
394
551
  const { fs, path: resolved } = resolveMount(path, this);
395
- try {
396
- if (config.checkAccess && !(await fs.stat(resolved)).hasAccess(constants.W_OK, this)) {
397
- throw ErrnoError.With('EACCES', resolved, 'unlink');
398
- }
399
- await fs.unlink(resolved);
400
- emitChange(this, 'rename', path.toString());
401
- }
402
- catch (e) {
403
- throw fixError(e, { [resolved]: path });
404
- }
552
+ const $ex = { syscall: 'unlink', path };
553
+ const stats = await fs.stat(resolved).catch(rethrow($ex));
554
+ if (checkAccess && !hasAccess(this, stats, constants.W_OK))
555
+ throw UV('EACCES', $ex);
556
+ await fs.unlink(resolved).catch(rethrow($ex));
557
+ emitChange(this, 'rename', path.toString());
405
558
  }
406
559
  unlink;
407
- /**
408
- * Manually apply setuid/setgid.
409
- */
410
- async function applySetId(file, uid, gid) {
411
- if (file.fs.attributes.has('setid'))
412
- return;
413
- const parent = await file.fs.stat(dirname(file.path));
414
- await file.chown(parent.mode & constants.S_ISUID ? parent.uid : uid, // manually apply setuid/setgid
415
- parent.mode & constants.S_ISGID ? parent.gid : gid);
416
- }
417
560
  /**
418
561
  * Opens a file. This helper handles the complexity of file flags.
419
562
  * @internal
@@ -421,47 +564,43 @@ async function applySetId(file, uid, gid) {
421
564
  async function _open($, path, opt) {
422
565
  var _a;
423
566
  path = normalizePath(path);
424
- const mode = normalizeMode(opt.mode, 0o644), flag = parseFlag(opt.flag);
425
- const { fullPath, fs, path: resolved, stats } = await _resolve($, path.toString(), opt.preserveSymlinks);
567
+ const mode = normalizeMode(opt.mode, 0o644), flag = flags.parse(opt.flag);
568
+ const $ex = { syscall: 'open', path };
569
+ const { fs, path: resolved, stats } = await _resolve($, path.toString(), opt.preserveSymlinks);
426
570
  if (!stats) {
427
- if ((!isWriteable(flag) && !isAppendable(flag)) || flag == 'r+') {
428
- throw ErrnoError.With('ENOENT', fullPath, '_open');
429
- }
571
+ if (!(flag & constants.O_CREAT))
572
+ throw UV('ENOENT', $ex);
430
573
  // Create the file
431
574
  const parentStats = await fs.stat(dirname(resolved));
432
- if (config.checkAccess && !parentStats.hasAccess(constants.W_OK, $)) {
433
- throw ErrnoError.With('EACCES', dirname(fullPath), '_open');
434
- }
435
- if (!parentStats.isDirectory()) {
436
- throw ErrnoError.With('ENOTDIR', dirname(fullPath), '_open');
437
- }
438
- const { euid: uid, egid: gid } = (_a = $ === null || $ === void 0 ? void 0 : $.credentials) !== null && _a !== void 0 ? _a : credentials;
439
- const file = await fs.createFile(resolved, flag, mode, { uid, gid });
440
- await applySetId(file, uid, gid);
441
- return new FileHandle(file, $);
442
- }
443
- if (config.checkAccess && !stats.hasAccess(flagToMode(flag), $)) {
444
- throw ErrnoError.With('EACCES', fullPath, '_open');
445
- }
446
- if (isExclusive(flag)) {
447
- throw ErrnoError.With('EEXIST', fullPath, '_open');
448
- }
449
- const handle = new FileHandle(await fs.openFile(resolved, flag), $);
450
- /*
451
- In a previous implementation, we deleted the file and
452
- re-created it. However, this created a race condition if another
453
- asynchronous request was trying to read the file, as the file
454
- would not exist for a small period of time.
455
- */
456
- if (isTruncating(flag)) {
575
+ if (checkAccess && !hasAccess($, parentStats, constants.W_OK))
576
+ throw UV('EACCES', 'open', dirname(path));
577
+ if (!isDirectory(parentStats))
578
+ throw UV('ENOTDIR', 'open', dirname(path));
579
+ if (!opt.allowDirectory && mode & constants.S_IFDIR)
580
+ throw UV('EISDIR', 'open', path);
581
+ const { euid: uid, egid: gid } = (_a = $ === null || $ === void 0 ? void 0 : $.credentials) !== null && _a !== void 0 ? _a : defaultContext.credentials;
582
+ const inode = await fs.createFile(resolved, {
583
+ mode,
584
+ uid: parentStats.mode & constants.S_ISUID ? parentStats.uid : uid,
585
+ gid: parentStats.mode & constants.S_ISGID ? parentStats.gid : gid,
586
+ });
587
+ return new FileHandle($, toFD(new SyncHandle($, path, fs, resolved, flag, inode)));
588
+ }
589
+ if (checkAccess && !hasAccess($, stats, flags.toMode(flag)))
590
+ throw UV('EACCES', $ex);
591
+ if (flag & constants.O_EXCL)
592
+ throw UV('EEXIST', $ex);
593
+ const handle = new FileHandle($, toFD(new SyncHandle($, path, fs, resolved, flag, stats)));
594
+ if (!opt.allowDirectory && mode & constants.S_IFDIR)
595
+ throw UV('EISDIR', 'open', path);
596
+ if (flag & constants.O_TRUNC)
457
597
  await handle.truncate(0);
458
- }
459
598
  return handle;
460
599
  }
461
600
  /**
462
601
  * Asynchronous file open.
463
- * @see http://www.manpagez.com/man/2/open/
464
- * @param flag Handles the complexity of the various file modes. See its API for more details.
602
+ * @see https://nodejs.org/api/fs.html#fspromisesopenpath-flags-mode
603
+ * @param flag {@link https://nodejs.org/api/fs.html#file-system-flags}
465
604
  * @param mode Mode to use to open the file. Can be ignored if the filesystem doesn't support permissions.
466
605
  */
467
606
  export async function open(path, flag = 'r', mode = 0o644) {
@@ -471,7 +610,7 @@ open;
471
610
  export async function readFile(path, _options) {
472
611
  const env_2 = { stack: [], error: void 0, hasError: false };
473
612
  try {
474
- const options = normalizeOptions(_options, null, 'r', 0o644);
613
+ const options = normalizeOptions(_options, null, 'r', 0o444);
475
614
  const handle = __addDisposableResource(env_2, typeof path == 'object' && 'fd' in path ? path : await open.call(this, path, options.flag, options.mode), true);
476
615
  return await handle.readFile(options);
477
616
  }
@@ -500,9 +639,8 @@ export async function writeFile(path, data, _options) {
500
639
  const options = normalizeOptions(_options, 'utf8', 'w+', 0o644);
501
640
  const handle = __addDisposableResource(env_3, path instanceof FileHandle ? path : await open.call(this, path.toString(), options.flag, options.mode), true);
502
641
  const _data = typeof data == 'string' ? data : data instanceof DataView ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength) : data;
503
- if (typeof _data != 'string' && !(_data instanceof Uint8Array)) {
504
- throw new ErrnoError(Errno.EINVAL, 'The "data" argument must be of type string or an instance of Buffer, TypedArray, or DataView. Received ' + typeof data, handle.file.path, 'writeFile');
505
- }
642
+ if (typeof _data != 'string' && !(_data instanceof Uint8Array))
643
+ throw new TypeError('The "data" argument must be of type string or an instance of Buffer, TypedArray, or DataView. Received ' + typeof data);
506
644
  await handle.writeFile(_data, options);
507
645
  }
508
646
  catch (e_3) {
@@ -526,13 +664,10 @@ export async function appendFile(path, data, _options) {
526
664
  const env_4 = { stack: [], error: void 0, hasError: false };
527
665
  try {
528
666
  const options = normalizeOptions(_options, 'utf8', 'a', 0o644);
529
- const flag = parseFlag(options.flag);
530
- if (!isAppendable(flag)) {
531
- throw new ErrnoError(Errno.EINVAL, 'Flag passed to appendFile must allow for appending');
532
- }
533
- if (typeof data != 'string' && !options.encoding) {
534
- throw new ErrnoError(Errno.EINVAL, 'Encoding not specified');
535
- }
667
+ const flag = flags.parse(options.flag);
668
+ const $ex = { syscall: 'write', path: path instanceof FileHandle ? path.path : path.toString() };
669
+ if (!(flag & constants.O_APPEND))
670
+ throw UV('EBADF', $ex);
536
671
  const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
537
672
  const handle = __addDisposableResource(env_4, typeof path == 'object' && 'fd' in path ? path : await open.call(this, path, options.flag, options.mode), true);
538
673
  await handle.appendFile(encodedData, options);
@@ -548,82 +683,72 @@ export async function appendFile(path, data, _options) {
548
683
  }
549
684
  }
550
685
  appendFile;
551
- // DIRECTORY-ONLY METHODS
552
686
  export async function rmdir(path) {
553
687
  path = await realpath.call(this, path);
554
688
  const { fs, path: resolved } = resolveMount(path, this);
555
- try {
556
- const stats = await fs.stat(resolved);
557
- if (!stats) {
558
- throw ErrnoError.With('ENOENT', path, 'rmdir');
559
- }
560
- if (!stats.isDirectory()) {
561
- throw ErrnoError.With('ENOTDIR', resolved, 'rmdir');
562
- }
563
- if (config.checkAccess && !stats.hasAccess(constants.W_OK, this)) {
564
- throw ErrnoError.With('EACCES', resolved, 'rmdir');
565
- }
566
- await fs.rmdir(resolved);
567
- emitChange(this, 'rename', path.toString());
568
- }
569
- catch (e) {
570
- throw fixError(e, { [resolved]: path });
571
- }
689
+ const $ex = { syscall: 'rmdir', path };
690
+ const stats = await fs.stat(resolved).catch(rethrow($ex));
691
+ if (!stats)
692
+ throw UV('ENOENT', $ex);
693
+ if (!isDirectory(stats))
694
+ throw UV('ENOTDIR', $ex);
695
+ if (checkAccess && !hasAccess(this, stats, constants.W_OK))
696
+ throw UV('EACCES', $ex);
697
+ await fs.rmdir(resolved).catch(rethrow($ex));
698
+ emitChange(this, 'rename', path.toString());
572
699
  }
573
700
  rmdir;
574
701
  export async function mkdir(path, options) {
575
- var _a, _b;
576
- const { euid: uid, egid: gid } = (_a = this === null || this === void 0 ? void 0 : this.credentials) !== null && _a !== void 0 ? _a : credentials;
702
+ var _a;
703
+ const { euid: uid, egid: gid } = (_a = this === null || this === void 0 ? void 0 : this.credentials) !== null && _a !== void 0 ? _a : defaultContext.credentials;
577
704
  options = typeof options === 'object' ? options : { mode: options };
578
705
  const mode = normalizeMode(options === null || options === void 0 ? void 0 : options.mode, 0o777);
579
706
  path = await realpath.call(this, path);
580
- const { fs, path: resolved, root } = resolveMount(path, this);
581
- const errorPaths = { [resolved]: path };
582
- try {
583
- if (!(options === null || options === void 0 ? void 0 : options.recursive)) {
584
- if (config.checkAccess && !(await fs.stat(dirname(resolved))).hasAccess(constants.W_OK, this)) {
585
- throw ErrnoError.With('EACCES', dirname(resolved), 'mkdir');
586
- }
587
- await fs.mkdir(resolved, mode, { uid, gid });
588
- await applySetId(await fs.openFile(resolved, 'r+'), uid, gid);
589
- emitChange(this, 'rename', path.toString());
590
- return;
591
- }
592
- const dirs = [];
593
- for (let dir = resolved, origDir = path; !(await fs.exists(dir)); dir = dirname(dir), origDir = dirname(origDir)) {
594
- dirs.unshift(dir);
595
- errorPaths[dir] = origDir;
596
- }
597
- for (const dir of dirs) {
598
- if (config.checkAccess && !(await fs.stat(dirname(dir))).hasAccess(constants.W_OK, this)) {
599
- throw ErrnoError.With('EACCES', dirname(dir), 'mkdir');
600
- }
601
- await fs.mkdir(dir, mode, { uid, gid });
602
- await applySetId(await fs.openFile(dir, 'r+'), uid, gid);
603
- emitChange(this, 'rename', dir);
604
- }
605
- return root.length == 1 ? dirs[0] : (_b = dirs[0]) === null || _b === void 0 ? void 0 : _b.slice(root.length);
707
+ const { fs, path: resolved } = resolveMount(path, this);
708
+ const __create = async (path, resolved, parent) => {
709
+ if (checkAccess && !hasAccess(this, parent, constants.W_OK))
710
+ throw UV('EACCES', 'mkdir', dirname(path));
711
+ const inode = await fs
712
+ .mkdir(resolved, {
713
+ mode,
714
+ uid: parent.mode & constants.S_ISUID ? parent.uid : uid,
715
+ gid: parent.mode & constants.S_ISGID ? parent.gid : gid,
716
+ })
717
+ .catch(rethrow({ syscall: 'mkdir', path }));
718
+ emitChange(this, 'rename', path);
719
+ return inode;
720
+ };
721
+ if (!(options === null || options === void 0 ? void 0 : options.recursive)) {
722
+ await __create(path, resolved, await fs.stat(dirname(resolved)).catch(rethrow({ path: dirname(path) })));
723
+ return;
606
724
  }
607
- catch (e) {
608
- throw fixError(e, errorPaths);
725
+ const dirs = [];
726
+ let origDir = path;
727
+ for (let dir = resolved; !(await fs.exists(dir).catch(rethrow({ syscall: 'exists', path: origDir }))); dir = dirname(dir), origDir = dirname(origDir)) {
728
+ dirs.unshift([origDir, dir]);
729
+ }
730
+ if (!dirs.length)
731
+ return;
732
+ const stats = [await fs.stat(dirname(dirs[0][1])).catch(rethrow({ syscall: 'stat', path: dirname(dirs[0][0]) }))];
733
+ for (const [i, [path, resolved]] of dirs.entries()) {
734
+ stats.push(await __create(path, resolved, stats[i]));
609
735
  }
736
+ return dirs[0][0];
610
737
  }
611
738
  mkdir;
612
739
  export async function readdir(path, options) {
613
740
  options = typeof options === 'object' ? options : { encoding: options };
614
741
  path = await realpath.call(this, path);
615
742
  const { fs, path: resolved } = resolveMount(path, this);
616
- const stats = await fs.stat(resolved).catch((e) => _throw(fixError(e, { [resolved]: path })));
617
- if (!stats) {
618
- throw ErrnoError.With('ENOENT', path, 'readdir');
619
- }
620
- if (config.checkAccess && !stats.hasAccess(constants.R_OK, this)) {
621
- throw ErrnoError.With('EACCES', path, 'readdir');
622
- }
623
- if (!stats.isDirectory()) {
624
- throw ErrnoError.With('ENOTDIR', path, 'readdir');
625
- }
626
- const entries = await fs.readdir(resolved).catch((e) => _throw(fixError(e, { [resolved]: path })));
743
+ const $ex = { syscall: 'readdir', path };
744
+ const stats = await fs.stat(resolved).catch(rethrow({ syscall: 'stat', path }));
745
+ if (!stats)
746
+ throw UV('ENOENT', $ex);
747
+ if (checkAccess && !hasAccess(this, stats, constants.R_OK))
748
+ throw UV('EACCES', $ex);
749
+ if (!isDirectory(stats))
750
+ throw UV('ENOTDIR', $ex);
751
+ const entries = await fs.readdir(resolved).catch(rethrow($ex));
627
752
  const values = [];
628
753
  const addEntry = async (entry) => {
629
754
  let entryStats;
@@ -631,7 +756,7 @@ export async function readdir(path, options) {
631
756
  entryStats = await fs.stat(join(resolved, entry)).catch((e) => {
632
757
  if (e.code == 'ENOENT')
633
758
  return;
634
- throw fixError(e, { [resolved]: path });
759
+ throw setUVMessage(Object.assign(e, { syscall: 'stat', path: join(path, entry) }));
635
760
  });
636
761
  if (!entryStats)
637
762
  return;
@@ -645,7 +770,7 @@ export async function readdir(path, options) {
645
770
  else {
646
771
  values.push(entry);
647
772
  }
648
- if (!(options === null || options === void 0 ? void 0 : options.recursive) || !(entryStats === null || entryStats === void 0 ? void 0 : entryStats.isDirectory()))
773
+ if (!(options === null || options === void 0 ? void 0 : options.recursive) || !isDirectory(entryStats))
649
774
  return;
650
775
  for (const subEntry of await readdir.call(this, join(path, entry), options)) {
651
776
  if (subEntry instanceof Dirent) {
@@ -665,49 +790,42 @@ export async function readdir(path, options) {
665
790
  return values;
666
791
  }
667
792
  readdir;
668
- export async function link(targetPath, linkPath) {
669
- targetPath = normalizePath(targetPath);
670
- linkPath = normalizePath(linkPath);
671
- const { fs, path } = resolveMount(targetPath, this);
672
- const link = resolveMount(linkPath, this);
673
- if (fs != link.fs) {
674
- throw ErrnoError.With('EXDEV', linkPath, 'link');
675
- }
676
- try {
677
- if (config.checkAccess && !(await fs.stat(dirname(targetPath))).hasAccess(constants.R_OK, this)) {
678
- throw ErrnoError.With('EACCES', dirname(path), 'link');
679
- }
680
- if (config.checkAccess && !(await stat.call(this, dirname(linkPath))).hasAccess(constants.W_OK, this)) {
681
- throw ErrnoError.With('EACCES', dirname(linkPath), 'link');
682
- }
683
- if (config.checkAccess && !(await fs.stat(path)).hasAccess(constants.R_OK, this)) {
684
- throw ErrnoError.With('EACCES', path, 'link');
685
- }
686
- return await fs.link(path, link.path);
687
- }
688
- catch (e) {
689
- throw fixError(e, { [link.path]: linkPath, [path]: targetPath });
690
- }
793
+ export async function link(path, dest) {
794
+ path = normalizePath(path);
795
+ dest = normalizePath(dest);
796
+ const { fs, path: resolved } = resolveMount(path, this);
797
+ const dst = resolveMount(dest, this);
798
+ const $ex = { syscall: 'link', path };
799
+ if (fs != dst.fs)
800
+ throw UV('EXDEV', $ex);
801
+ const stats = await fs.stat(dirname(resolved)).catch(rethrow({ syscall: 'stat', path: dirname(path) }));
802
+ if (checkAccess && !hasAccess(this, stats, constants.R_OK))
803
+ throw UV('EACCES', 'link', dirname(path));
804
+ // We need to use the VFS here since the link path may be a mount point
805
+ if (checkAccess && !(await stat.call(this, dirname(dest))).hasAccess(constants.W_OK, this))
806
+ throw UV('EACCES', 'link', dirname(dest));
807
+ if (checkAccess && !hasAccess(this, await fs.stat(resolved).catch(rethrow($ex)), constants.R_OK))
808
+ throw UV('EACCES', $ex);
809
+ return await fs.link(resolved, dst.path).catch(rethrow($ex));
691
810
  }
692
811
  link;
693
812
  /**
694
813
  * `symlink`.
695
- * @param target target path
814
+ * @param dest target path
696
815
  * @param path link path
697
816
  * @param type can be either `'dir'` or `'file'` (default is `'file'`)
698
817
  */
699
- export async function symlink(target, path, type = 'file') {
818
+ export async function symlink(dest, path, type = 'file') {
700
819
  const env_5 = { stack: [], error: void 0, hasError: false };
701
820
  try {
702
- if (!['file', 'dir', 'junction'].includes(type)) {
703
- throw new ErrnoError(Errno.EINVAL, 'Invalid symlink type: ' + type);
704
- }
821
+ if (!['file', 'dir', 'junction'].includes(type))
822
+ throw new TypeError('Invalid symlink type: ' + type);
705
823
  path = normalizePath(path);
706
824
  if (await exists.call(this, path))
707
- throw ErrnoError.With('EEXIST', path, 'symlink');
825
+ throw UV('EEXIST', 'symlink', path);
708
826
  const handle = __addDisposableResource(env_5, await _open(this, path, { flag: 'w+', mode: 0o644, preserveSymlinks: true }), true);
709
- await handle.writeFile(normalizePath(target, true));
710
- await handle.file.chmod(constants.S_IFLNK);
827
+ await handle.writeFile(normalizePath(dest, true));
828
+ await handle.chmod(constants.S_IFLNK);
711
829
  }
712
830
  catch (e_5) {
713
831
  env_5.error = e_5;
@@ -723,7 +841,11 @@ symlink;
723
841
  export async function readlink(path, options) {
724
842
  const env_6 = { stack: [], error: void 0, hasError: false };
725
843
  try {
726
- const handle = __addDisposableResource(env_6, await _open(this, normalizePath(path), { flag: 'r', mode: 0o644, preserveSymlinks: true }), true);
844
+ path = normalizePath(path);
845
+ __assertType(path);
846
+ const handle = __addDisposableResource(env_6, await _open(this, path, { flag: 'r', mode: 0o644, preserveSymlinks: true }), true);
847
+ if (!isSymbolicLink(handle.inode))
848
+ throw UV('EINVAL', 'readlink', path);
727
849
  const value = await handle.readFile();
728
850
  const encoding = typeof options == 'object' ? options === null || options === void 0 ? void 0 : options.encoding : options;
729
851
  // always defaults to utf-8 to avoid wrangler (cloudflare) worker "unknown encoding" exception
@@ -880,10 +1002,10 @@ async function _resolve($, path, preserveSymlinks) {
880
1002
  const resolved = resolveMount(path, $);
881
1003
  // Stat it to make sure it exists
882
1004
  const stats = await resolved.fs.stat(resolved.path);
883
- if (!stats.isSymbolicLink()) {
1005
+ if (!isSymbolicLink(stats)) {
884
1006
  return { ...resolved, fullPath: path, stats };
885
1007
  }
886
- const target = resolve(dirname(path), (await readlink.call($, path)).toString());
1008
+ const target = resolve.call($, dirname(path), (await readlink.call($, path)).toString());
887
1009
  return await _resolve($, target);
888
1010
  }
889
1011
  catch {
@@ -893,20 +1015,18 @@ async function _resolve($, path, preserveSymlinks) {
893
1015
  const realDir = dir == '/' ? '/' : await realpath.call($, dir);
894
1016
  const maybePath = join(realDir, base);
895
1017
  const resolved = resolveMount(maybePath, $);
896
- try {
897
- const stats = await resolved.fs.stat(resolved.path);
898
- if (!stats.isSymbolicLink()) {
899
- return { ...resolved, fullPath: maybePath, stats };
900
- }
901
- const target = resolve(realDir, (await readlink.call($, maybePath)).toString());
902
- return await _resolve($, target);
903
- }
904
- catch (e) {
905
- if (e.code == 'ENOENT') {
906
- return { ...resolved, fullPath: path };
907
- }
908
- throw fixError(e, { [resolved.path]: maybePath });
1018
+ const stats = await resolved.fs.stat(resolved.path).catch((e) => {
1019
+ if (e.code == 'ENOENT')
1020
+ return;
1021
+ throw setUVMessage(Object.assign(e, { syscall: 'stat', path: maybePath }));
1022
+ });
1023
+ if (!stats)
1024
+ return { ...resolved, fullPath: path };
1025
+ if (!isSymbolicLink(stats)) {
1026
+ return { ...resolved, fullPath: maybePath, stats };
909
1027
  }
1028
+ const target = resolve.call($, realDir, (await readlink.call($, maybePath)).toString());
1029
+ return await _resolve($, target);
910
1030
  }
911
1031
  export async function realpath(path, options) {
912
1032
  var _a;
@@ -959,12 +1079,11 @@ export function watch(filename, options = {}) {
959
1079
  }
960
1080
  watch;
961
1081
  export async function access(path, mode = constants.F_OK) {
962
- if (!config.checkAccess)
1082
+ if (!checkAccess)
963
1083
  return;
964
1084
  const stats = await stat.call(this, path);
965
- if (!stats.hasAccess(mode, this)) {
966
- throw new ErrnoError(Errno.EACCES);
967
- }
1085
+ if (!stats.hasAccess(mode, this))
1086
+ throw UV('EACCES', 'access', path.toString());
968
1087
  }
969
1088
  access;
970
1089
  /**
@@ -998,7 +1117,7 @@ export async function rm(path, options) {
998
1117
  case constants.S_IFIFO:
999
1118
  case constants.S_IFSOCK:
1000
1119
  default:
1001
- throw new ErrnoError(Errno.EPERM, 'File type not supported', path, 'rm');
1120
+ throw UV('ENOSYS', 'rm', path);
1002
1121
  }
1003
1122
  }
1004
1123
  rm;
@@ -1020,9 +1139,8 @@ mkdtemp;
1020
1139
  export async function copyFile(src, dest, mode) {
1021
1140
  src = normalizePath(src);
1022
1141
  dest = normalizePath(dest);
1023
- if (mode && mode & constants.COPYFILE_EXCL && (await exists.call(this, dest))) {
1024
- throw new ErrnoError(Errno.EEXIST, 'Destination file already exists', dest, 'copyFile');
1025
- }
1142
+ if (mode && mode & constants.COPYFILE_EXCL && (await exists.call(this, dest)))
1143
+ throw UV('EEXIST', 'copyFile', dest);
1026
1144
  await writeFile.call(this, dest, await readFile.call(this, src));
1027
1145
  emitChange(this, 'rename', dest.toString());
1028
1146
  }
@@ -1055,14 +1173,12 @@ export async function cp(source, destination, opts) {
1055
1173
  source = normalizePath(source);
1056
1174
  destination = normalizePath(destination);
1057
1175
  const srcStats = await lstat.call(this, source); // Use lstat to follow symlinks if not dereferencing
1058
- if ((opts === null || opts === void 0 ? void 0 : opts.errorOnExist) && (await exists.call(this, destination))) {
1059
- throw new ErrnoError(Errno.EEXIST, 'Destination file or directory already exists', destination, 'cp');
1060
- }
1176
+ if ((opts === null || opts === void 0 ? void 0 : opts.errorOnExist) && (await exists.call(this, destination)))
1177
+ throw UV('EEXIST', 'cp', destination);
1061
1178
  switch (srcStats.mode & constants.S_IFMT) {
1062
1179
  case constants.S_IFDIR: {
1063
- if (!(opts === null || opts === void 0 ? void 0 : opts.recursive)) {
1064
- throw new ErrnoError(Errno.EISDIR, source + ' is a directory (not copied)', source, 'cp');
1065
- }
1180
+ if (!(opts === null || opts === void 0 ? void 0 : opts.recursive))
1181
+ throw UV('EISDIR', 'cp', source);
1066
1182
  const [entries] = await Promise.all([
1067
1183
  readdir.call(this, source, { withFileTypes: true }),
1068
1184
  mkdir.call(this, destination, { recursive: true }),
@@ -1086,7 +1202,7 @@ export async function cp(source, destination, opts) {
1086
1202
  case constants.S_IFIFO:
1087
1203
  case constants.S_IFSOCK:
1088
1204
  default:
1089
- throw new ErrnoError(Errno.EPERM, 'File type not supported', source, 'rm');
1205
+ throw UV('ENOSYS', 'cp', source);
1090
1206
  }
1091
1207
  // Optionally preserve timestamps
1092
1208
  if (opts === null || opts === void 0 ? void 0 : opts.preserveTimestamps) {