@zenfs/core 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/ApiError.d.ts +51 -14
  2. package/dist/ApiError.js +60 -34
  3. package/dist/FileIndex.d.ts +32 -35
  4. package/dist/FileIndex.js +93 -109
  5. package/dist/backends/AsyncMirror.d.ts +42 -43
  6. package/dist/backends/AsyncMirror.js +146 -133
  7. package/dist/backends/AsyncStore.d.ts +29 -28
  8. package/dist/backends/AsyncStore.js +139 -189
  9. package/dist/backends/InMemory.d.ts +16 -13
  10. package/dist/backends/InMemory.js +29 -14
  11. package/dist/backends/Locked.d.ts +8 -28
  12. package/dist/backends/Locked.js +44 -148
  13. package/dist/backends/OverlayFS.d.ts +26 -34
  14. package/dist/backends/OverlayFS.js +208 -371
  15. package/dist/backends/SyncStore.d.ts +54 -72
  16. package/dist/backends/SyncStore.js +159 -161
  17. package/dist/backends/backend.d.ts +45 -29
  18. package/dist/backends/backend.js +83 -13
  19. package/dist/backends/index.d.ts +6 -7
  20. package/dist/backends/index.js +5 -6
  21. package/dist/browser.min.js +5 -7
  22. package/dist/browser.min.js.map +4 -4
  23. package/dist/emulation/callbacks.d.ts +36 -67
  24. package/dist/emulation/callbacks.js +90 -46
  25. package/dist/emulation/constants.js +1 -1
  26. package/dist/emulation/promises.d.ts +228 -129
  27. package/dist/emulation/promises.js +414 -172
  28. package/dist/emulation/shared.d.ts +10 -10
  29. package/dist/emulation/shared.js +18 -20
  30. package/dist/emulation/sync.d.ts +25 -25
  31. package/dist/emulation/sync.js +187 -73
  32. package/dist/file.d.ts +166 -170
  33. package/dist/file.js +199 -218
  34. package/dist/filesystem.d.ts +68 -241
  35. package/dist/filesystem.js +59 -383
  36. package/dist/index.d.ts +7 -44
  37. package/dist/index.js +13 -52
  38. package/dist/inode.d.ts +37 -28
  39. package/dist/inode.js +123 -65
  40. package/dist/stats.d.ts +21 -19
  41. package/dist/stats.js +35 -56
  42. package/dist/utils.d.ts +26 -9
  43. package/dist/utils.js +73 -102
  44. package/package.json +4 -3
@@ -1,12 +1,154 @@
1
1
  import { ApiError, ErrorCode } from '../ApiError.js';
2
- import * as constants from './constants.js';
3
- export { constants };
4
- import { FileFlag } from '../file.js';
2
+ export * as constants from './constants.js';
3
+ import { ActionType, FileFlag } from '../file.js';
5
4
  import { normalizePath, normalizeMode, getFdForFile, normalizeOptions, fd2file, fdMap, normalizeTime, cred, nop, resolveFS, fixError, mounts } from './shared.js';
6
- import { BigIntStats } from '../stats.js';
5
+ import { BigIntStats, FileType } from '../stats.js';
7
6
  import { decode, encode } from '../utils.js';
8
7
  import { Dirent } from './dir.js';
9
- import { join } from './path.js';
8
+ import { dirname, join } from './path.js';
9
+ export class FileHandle {
10
+ constructor(
11
+ /**
12
+ * Gets the file descriptor for this file handle.
13
+ */
14
+ fd) {
15
+ this.fd = fd;
16
+ }
17
+ /**
18
+ * Asynchronous fchown(2) - Change ownership of a file.
19
+ */
20
+ chown(uid, gid) {
21
+ return fd2file(this.fd).chown(uid, gid);
22
+ }
23
+ /**
24
+ * Asynchronous fchmod(2) - Change permissions of a file.
25
+ * @param mode A file mode. If a string is passed, it is parsed as an octal integer.
26
+ */
27
+ chmod(mode) {
28
+ const numMode = normalizeMode(mode, -1);
29
+ if (numMode < 0) {
30
+ throw new ApiError(ErrorCode.EINVAL, 'Invalid mode.');
31
+ }
32
+ return fd2file(this.fd).chmod(numMode);
33
+ }
34
+ /**
35
+ * Asynchronous fdatasync(2) - synchronize a file's in-core state with storage device.
36
+ */
37
+ datasync() {
38
+ return fd2file(this.fd).datasync();
39
+ }
40
+ /**
41
+ * Asynchronous fsync(2) - synchronize a file's in-core state with the underlying storage device.
42
+ */
43
+ sync() {
44
+ return fd2file(this.fd).sync();
45
+ }
46
+ /**
47
+ * Asynchronous ftruncate(2) - Truncate a file to a specified length.
48
+ * @param len If not specified, defaults to `0`.
49
+ */
50
+ truncate(len) {
51
+ if (len < 0) {
52
+ throw new ApiError(ErrorCode.EINVAL);
53
+ }
54
+ return fd2file(this.fd).truncate(len);
55
+ }
56
+ /**
57
+ * Asynchronously change file timestamps of the file.
58
+ * @param atime The last access time. If a string is provided, it will be coerced to number.
59
+ * @param mtime The last modified time. If a string is provided, it will be coerced to number.
60
+ */
61
+ utimes(atime, mtime) {
62
+ return fd2file(this.fd).utimes(normalizeTime(atime), normalizeTime(mtime));
63
+ }
64
+ /**
65
+ * Asynchronously append data to a file, creating the file if it does not exist. The underlying file will _not_ be closed automatically.
66
+ * The `FileHandle` must have been opened for appending.
67
+ * @param data The data to write. If something other than a `Buffer` or `Uint8Array` is provided, the value is coerced to a string.
68
+ * @param options Either the encoding for the file, or an object optionally specifying the encoding, file mode, and flag.
69
+ * If `encoding` is not supplied, the default of `'utf8'` is used.
70
+ * If `mode` is not supplied, the default of `0o666` is used.
71
+ * If `mode` is a string, it is parsed as an octal integer.
72
+ * If `flag` is not supplied, the default of `'a'` is used.
73
+ */
74
+ appendFile(data, options) {
75
+ return appendFile(fd2file(this.fd).path, data, options);
76
+ }
77
+ /**
78
+ * Asynchronously reads data from the file.
79
+ * The `FileHandle` must have been opened for reading.
80
+ * @param buffer The buffer that the data will be written to.
81
+ * @param offset The offset in the buffer at which to start writing.
82
+ * @param length The number of bytes to read.
83
+ * @param position The offset from the beginning of the file from which data should be read. If `null`, data will be read from the current position.
84
+ */
85
+ read(buffer, offset, length, position) {
86
+ if (isNaN(+position)) {
87
+ position = fd2file(this.fd).position;
88
+ }
89
+ return fd2file(this.fd).read(buffer, offset, length, position);
90
+ }
91
+ readFile(options) {
92
+ return readFile(fd2file(this.fd).path, options);
93
+ }
94
+ stat(opts) {
95
+ return stat(fd2file(this.fd).path, opts);
96
+ }
97
+ async write(data, posOrOff, lenOrEnc, position) {
98
+ let buffer, offset = 0, length;
99
+ if (typeof data === 'string') {
100
+ // Signature 1: (fd, string, [position?, [encoding?]])
101
+ position = typeof posOrOff === 'number' ? posOrOff : null;
102
+ const encoding = (typeof lenOrEnc === 'string' ? lenOrEnc : 'utf8');
103
+ offset = 0;
104
+ buffer = encode(data, encoding);
105
+ length = buffer.length;
106
+ }
107
+ else {
108
+ // Signature 2: (fd, buffer, offset, length, position?)
109
+ buffer = data;
110
+ offset = posOrOff;
111
+ length = lenOrEnc;
112
+ position = typeof position === 'number' ? position : null;
113
+ }
114
+ position ?? (position = fd2file(this.fd).position);
115
+ const bytesWritten = await fd2file(this.fd).write(buffer, offset, length, position);
116
+ return { buffer, bytesWritten };
117
+ }
118
+ /**
119
+ * Asynchronously writes data to a file, replacing the file if it already exists. The underlying file will _not_ be closed automatically.
120
+ * The `FileHandle` must have been opened for writing.
121
+ * It is unsafe to call `writeFile()` multiple times on the same file without waiting for the `Promise` to be resolved (or rejected).
122
+ * @param data The data to write. If something other than a `Buffer` or `Uint8Array` is provided, the value is coerced to a string.
123
+ * @param options Either the encoding for the file, or an object optionally specifying the encoding, file mode, and flag.
124
+ * If `encoding` is not supplied, the default of `'utf8'` is used.
125
+ * If `mode` is not supplied, the default of `0o666` is used.
126
+ * If `mode` is a string, it is parsed as an octal integer.
127
+ * If `flag` is not supplied, the default of `'w'` is used.
128
+ */
129
+ writeFile(data, options) {
130
+ return writeFile(fd2file(this.fd).path, data, options);
131
+ }
132
+ /**
133
+ * See `fs.writev` promisified version.
134
+ */
135
+ writev(buffers, position) {
136
+ throw new ApiError(ErrorCode.ENOTSUP);
137
+ }
138
+ /**
139
+ * See `fs.readv` promisified version.
140
+ */
141
+ readv(buffers, position) {
142
+ throw new ApiError(ErrorCode.ENOTSUP);
143
+ }
144
+ /**
145
+ * Asynchronous close(2) - close a `FileHandle`.
146
+ */
147
+ async close() {
148
+ await fd2file(this.fd).close();
149
+ fdMap.delete(this.fd);
150
+ }
151
+ }
10
152
  /**
11
153
  * Utility for FS ops. It handles
12
154
  * - path normalization (for the first parameter to the FS op)
@@ -19,15 +161,15 @@ import { join } from './path.js';
19
161
  * @param args the rest of the parameters are passed to the FS function. Note that the first parameter is required to be a path
20
162
  * @returns
21
163
  */
22
- async function doOp(...[name, resolveSymlinks, path, ...args]) {
23
- path = normalizePath(path);
24
- const { fs, path: resolvedPath } = resolveFS(resolveSymlinks && (await exists(path)) ? await realpath(path) : path);
164
+ async function doOp(...[name, resolveSymlinks, rawPath, ...args]) {
165
+ rawPath = normalizePath(rawPath);
166
+ const { fs, path } = resolveFS(resolveSymlinks && (await exists(rawPath)) ? await realpath(rawPath) : rawPath);
25
167
  try {
26
168
  // @ts-expect-error 2556 (since ...args is not correctly picked up as being a tuple)
27
- return fs[name](resolvedPath, ...args);
169
+ return fs[name](path, ...args);
28
170
  }
29
171
  catch (e) {
30
- throw fixError(e, { [resolvedPath]: path });
172
+ throw fixError(e, { [path]: rawPath });
31
173
  }
32
174
  }
33
175
  // fs.promises
@@ -39,30 +181,26 @@ async function doOp(...[name, resolveSymlinks, path, ...args]) {
39
181
  export async function rename(oldPath, newPath) {
40
182
  oldPath = normalizePath(oldPath);
41
183
  newPath = normalizePath(newPath);
42
- const _old = resolveFS(oldPath);
43
- const _new = resolveFS(newPath);
44
- const paths = { [_old.path]: oldPath, [_new.path]: newPath };
184
+ const { path: old } = resolveFS(oldPath);
185
+ const { fs, path } = resolveFS(newPath);
45
186
  try {
46
- if (_old === _new) {
47
- return _old.fs.rename(_old.path, _new.path, cred);
48
- }
49
187
  const data = await readFile(oldPath);
50
188
  await writeFile(newPath, data);
51
189
  await unlink(oldPath);
52
190
  }
53
191
  catch (e) {
54
- throw fixError(e, paths);
192
+ throw fixError(e, { [old]: oldPath, [path]: newPath });
55
193
  }
56
194
  }
195
+ rename;
57
196
  /**
58
197
  * Test whether or not the given path exists by checking with the file system.
59
- * @param path
198
+ * @param _path
60
199
  */
61
- export async function exists(path) {
62
- path = normalizePath(path);
200
+ export async function exists(_path) {
63
201
  try {
64
- const { fs, path: resolvedPath } = resolveFS(path);
65
- return fs.exists(resolvedPath, cred);
202
+ const { fs, path } = resolveFS(_path);
203
+ return fs.exists(path, cred);
66
204
  }
67
205
  catch (e) {
68
206
  if (e.errno == ErrorCode.ENOENT) {
@@ -75,10 +213,12 @@ export async function stat(path, options) {
75
213
  const stats = await doOp('stat', true, path, cred);
76
214
  return options?.bigint ? BigIntStats.clone(stats) : stats;
77
215
  }
216
+ stat;
78
217
  export async function lstat(path, options) {
79
218
  const stats = await doOp('stat', false, path, cred);
80
219
  return options?.bigint ? BigIntStats.clone(stats) : stats;
81
220
  }
221
+ lstat;
82
222
  // FILE-ONLY METHODS
83
223
  /**
84
224
  * `truncate`.
@@ -86,11 +226,15 @@ export async function lstat(path, options) {
86
226
  * @param len
87
227
  */
88
228
  export async function truncate(path, len = 0) {
89
- if (len < 0) {
90
- throw new ApiError(ErrorCode.EINVAL);
229
+ const handle = await open(path, 'r+');
230
+ try {
231
+ await handle.truncate(len);
232
+ }
233
+ finally {
234
+ await handle.close();
91
235
  }
92
- return doOp('truncate', true, path, len, cred);
93
236
  }
237
+ truncate;
94
238
  /**
95
239
  * `unlink`.
96
240
  * @param path
@@ -98,121 +242,181 @@ export async function truncate(path, len = 0) {
98
242
  export async function unlink(path) {
99
243
  return doOp('unlink', false, path, cred);
100
244
  }
245
+ unlink;
246
+ /**
247
+ * Opens a file. This helper handles the complexity of file flags.
248
+ * @internal
249
+ */
250
+ async function _open(_path, _flag, _mode = 0o644, resolveSymlinks) {
251
+ const path = normalizePath(_path), mode = normalizeMode(_mode, 0o644), flag = FileFlag.FromString(_flag);
252
+ try {
253
+ switch (flag.pathExistsAction()) {
254
+ case ActionType.THROW:
255
+ throw ApiError.EEXIST(path);
256
+ case ActionType.TRUNCATE:
257
+ /*
258
+ In a previous implementation, we deleted the file and
259
+ re-created it. However, this created a race condition if another
260
+ asynchronous request was trying to read the file, as the file
261
+ would not exist for a small period of time.
262
+ */
263
+ const file = await doOp('openFile', resolveSymlinks, path, flag, cred);
264
+ if (!file) {
265
+ throw new ApiError(ErrorCode.EIO, 'Impossible code path reached');
266
+ }
267
+ await file.truncate(0);
268
+ await file.sync();
269
+ return file;
270
+ case ActionType.NOP:
271
+ // Must await so thrown errors are caught by the catch below
272
+ return await doOp('openFile', resolveSymlinks, path, flag, cred);
273
+ default:
274
+ throw new ApiError(ErrorCode.EINVAL, 'Invalid file flag');
275
+ }
276
+ }
277
+ catch (e) {
278
+ switch (flag.pathNotExistsAction()) {
279
+ case ActionType.CREATE:
280
+ // Ensure parent exists.
281
+ const parentStats = await doOp('stat', resolveSymlinks, dirname(path), cred);
282
+ if (parentStats && !parentStats.isDirectory()) {
283
+ throw ApiError.ENOTDIR(dirname(path));
284
+ }
285
+ return await doOp('createFile', resolveSymlinks, path, flag, mode, cred);
286
+ case ActionType.THROW:
287
+ throw ApiError.ENOENT(path);
288
+ default:
289
+ throw new ApiError(ErrorCode.EINVAL, 'Invalid file flag');
290
+ }
291
+ }
292
+ }
101
293
  /**
102
- * file open.
294
+ * Asynchronous file open.
103
295
  * @see http://www.manpagez.com/man/2/open/
104
- * @param path
105
- * @param flags
106
- * @param mode defaults to `0644`
296
+ * @param flags Handles the complexity of the various file modes. See its API for more details.
297
+ * @param mode Mode to use to open the file. Can be ignored if the filesystem doesn't support permissions.
107
298
  */
108
299
  export async function open(path, flag, mode = 0o644) {
109
- const file = await doOp('open', true, path, FileFlag.getFileFlag(flag), normalizeMode(mode, 0o644), cred);
110
- return getFdForFile(file);
300
+ const file = await _open(path, flag, mode, true);
301
+ return new FileHandle(getFdForFile(file));
111
302
  }
112
- export async function readFile(filename, arg2 = {}) {
113
- const options = normalizeOptions(arg2, null, 'r', null);
114
- const flag = FileFlag.getFileFlag(options.flag);
115
- if (!flag.isReadable()) {
116
- throw new ApiError(ErrorCode.EINVAL, 'Flag passed to readFile must allow for reading.');
117
- }
118
- const data = await doOp('readFile', true, filename, flag, cred);
119
- switch (options.encoding) {
120
- case 'utf8':
121
- case 'utf-8':
122
- return decode(data);
123
- default:
124
- return data;
125
- }
303
+ open;
304
+ /**
305
+ * Opens a file without resolving symlinks
306
+ * @internal
307
+ */
308
+ export async function lopen(path, flag, mode = 0o644) {
309
+ const file = await _open(path, flag, mode, false);
310
+ return new FileHandle(getFdForFile(file));
126
311
  }
127
- export async function writeFile(filename, data, arg3) {
128
- const options = normalizeOptions(arg3, 'utf8', 'w', 0o644);
129
- const flag = FileFlag.getFileFlag(options.flag);
130
- if (!flag.isWriteable()) {
131
- throw new ApiError(ErrorCode.EINVAL, 'Flag passed to writeFile must allow for writing.');
312
+ /**
313
+ * Asynchronously reads the entire contents of a file.
314
+ */
315
+ async function _readFile(fname, flag, resolveSymlinks) {
316
+ const file = await _open(normalizePath(fname), flag, 0o644, resolveSymlinks);
317
+ try {
318
+ const stat = await file.stat();
319
+ const data = new Uint8Array(stat.size);
320
+ await file.read(data, 0, stat.size, 0);
321
+ await file.close();
322
+ return data;
132
323
  }
133
- if (typeof data != 'string' && !options.encoding) {
134
- throw new ApiError(ErrorCode.EINVAL, 'Encoding not specified');
324
+ finally {
325
+ await file.close();
135
326
  }
136
- const encodedData = typeof data == 'string' ? encode(data) : data;
137
- return doOp('writeFile', true, filename, encodedData, flag, options.mode, cred);
138
327
  }
139
- export async function appendFile(filename, data, arg3) {
140
- const options = normalizeOptions(arg3, 'utf8', 'a', 0o644);
141
- const flag = FileFlag.getFileFlag(options.flag);
142
- if (!flag.isAppendable()) {
143
- throw new ApiError(ErrorCode.EINVAL, 'Flag passed to appendFile must allow for appending.');
144
- }
145
- if (typeof data != 'string' && !options.encoding) {
146
- throw new ApiError(ErrorCode.EINVAL, 'Encoding not specified');
328
+ export async function readFile(filename, _options) {
329
+ const options = normalizeOptions(_options, null, 'r', null);
330
+ const flag = FileFlag.FromString(options.flag);
331
+ if (!flag.isReadable()) {
332
+ throw new ApiError(ErrorCode.EINVAL, 'Flag passed must allow for reading.');
147
333
  }
148
- const encodedData = typeof data == 'string' ? encode(data) : data;
149
- return doOp('appendFile', true, filename, encodedData, flag, options.mode, cred);
150
- }
151
- export async function fstat(fd, options) {
152
- const stats = await fd2file(fd).stat();
153
- return options?.bigint ? BigIntStats.clone(stats) : stats;
334
+ const data = await _readFile(filename, options.flag, true);
335
+ return options.encoding ? decode(data, options.encoding) : data;
154
336
  }
337
+ readFile;
155
338
  /**
156
- * close.
157
- * @param fd
339
+ * Asynchronously writes data to a file, replacing the file
340
+ * if it already exists.
341
+ *
342
+ * The encoding option is ignored if data is a buffer.
158
343
  */
159
- export async function close(fd) {
160
- await fd2file(fd).close();
161
- fdMap.delete(fd);
162
- return;
344
+ async function _writeFile(fname, data, flag, mode, resolveSymlinks) {
345
+ const file = await _open(fname, flag, mode, resolveSymlinks);
346
+ try {
347
+ await file.write(data, 0, data.length, 0);
348
+ }
349
+ finally {
350
+ await file.close();
351
+ }
163
352
  }
164
353
  /**
165
- * ftruncate.
166
- * @param fd
167
- * @param len
354
+ * Synchronously writes data to a file, replacing the file if it already exists.
355
+ *
356
+ * The encoding option is ignored if data is a buffer.
357
+ * @param filename
358
+ * @param data
359
+ * @param _options
360
+ * @option options encoding Defaults to `'utf8'`.
361
+ * @option options mode Defaults to `0644`.
362
+ * @option options flag Defaults to `'w'`.
168
363
  */
169
- export async function ftruncate(fd, len = 0) {
170
- const file = fd2file(fd);
171
- if (len < 0) {
172
- throw new ApiError(ErrorCode.EINVAL);
364
+ export async function writeFile(filename, data, _options) {
365
+ const options = normalizeOptions(_options, 'utf8', 'w', 0o644);
366
+ const flag = FileFlag.FromString(options.flag);
367
+ if (!flag.isWriteable()) {
368
+ throw new ApiError(ErrorCode.EINVAL, 'Flag passed must allow for writing.');
369
+ }
370
+ if (typeof data != 'string' && !options.encoding) {
371
+ throw new ApiError(ErrorCode.EINVAL, 'Encoding not specified');
173
372
  }
174
- return file.truncate(len);
373
+ const encodedData = typeof data == 'string' ? encode(data, options.encoding) : data;
374
+ await _writeFile(filename, encodedData, options.flag, options.mode, true);
175
375
  }
376
+ writeFile;
176
377
  /**
177
- * fsync.
178
- * @param fd
378
+ * Asynchronously append data to a file, creating the file if
379
+ * it not yet exists.
179
380
  */
180
- export async function fsync(fd) {
181
- return fd2file(fd).sync();
381
+ async function _appendFile(fname, data, flag, mode, resolveSymlinks) {
382
+ const file = await _open(fname, flag, mode, resolveSymlinks);
383
+ try {
384
+ await file.write(data, 0, data.length, null);
385
+ }
386
+ finally {
387
+ await file.close();
388
+ }
182
389
  }
183
390
  /**
184
- * fdatasync.
185
- * @param fd
391
+ * Asynchronously append data to a file, creating the file if it not yet
392
+ * exists.
393
+ * @param filename
394
+ * @param data
395
+ * @param options
396
+ * @option options encoding Defaults to `'utf8'`.
397
+ * @option options mode Defaults to `0644`.
398
+ * @option options flag Defaults to `'a'`.
186
399
  */
187
- export async function fdatasync(fd) {
188
- return fd2file(fd).datasync();
189
- }
190
- export async function write(fd, arg2, arg3, arg4, arg5) {
191
- let buffer, offset = 0, length, position;
192
- if (typeof arg2 === 'string') {
193
- // Signature 1: (fd, string, [position?, [encoding?]])
194
- position = typeof arg3 === 'number' ? arg3 : null;
195
- const encoding = (typeof arg4 === 'string' ? arg4 : 'utf8');
196
- offset = 0;
197
- buffer = encode(arg2, encoding);
198
- length = buffer.length;
199
- }
200
- else {
201
- // Signature 2: (fd, buffer, offset, length, position?)
202
- buffer = arg2;
203
- offset = arg3;
204
- length = arg4;
205
- position = typeof arg5 === 'number' ? arg5 : null;
206
- }
207
- const file = fd2file(fd);
208
- if (position === undefined || position === null) {
209
- position = file.getPos();
210
- }
211
- return file.write(buffer, offset, length, position);
400
+ export async function appendFile(filename, data, _options) {
401
+ const options = normalizeOptions(_options, 'utf8', 'a', 0o644);
402
+ const flag = FileFlag.FromString(options.flag);
403
+ if (!flag.isAppendable()) {
404
+ throw new ApiError(ErrorCode.EINVAL, 'Flag passed to appendFile must allow for appending.');
405
+ }
406
+ if (typeof data != 'string' && !options.encoding) {
407
+ throw new ApiError(ErrorCode.EINVAL, 'Encoding not specified');
408
+ }
409
+ const encodedData = typeof data == 'string' ? encode(data) : data;
410
+ await _appendFile(filename, encodedData, options.flag, options.mode, true);
411
+ }
412
+ appendFile;
413
+ export function write(handle, data, posOrOff, lenOrEnc, position) {
414
+ return handle.write(data, posOrOff, lenOrEnc, position);
212
415
  }
416
+ write;
213
417
  /**
214
418
  * Read data from the file specified by `fd`.
215
- * @param fd
419
+ * @param handle
216
420
  * @param buffer The buffer that the data will be
217
421
  * written to.
218
422
  * @param offset The offset within the buffer where writing will
@@ -222,41 +426,40 @@ export async function write(fd, arg2, arg3, arg4, arg5) {
222
426
  * in the file. If position is null, data will be read from the current file
223
427
  * position.
224
428
  */
225
- export async function read(fd, buffer, offset, length, position) {
226
- const file = fd2file(fd);
227
- if (isNaN(+position)) {
228
- position = file.getPos();
229
- }
230
- return file.read(buffer, offset, length, position);
429
+ export function read(handle, buffer, offset, length, position) {
430
+ return handle.read(buffer, offset, length, position);
231
431
  }
432
+ read;
232
433
  /**
233
434
  * `fchown`.
234
- * @param fd
435
+ * @param handle
235
436
  * @param uid
236
437
  * @param gid
237
438
  */
238
- export async function fchown(fd, uid, gid) {
239
- return fd2file(fd).chown(uid, gid);
439
+ export function fchown(handle, uid, gid) {
440
+ return handle.chown(uid, gid);
240
441
  }
442
+ fchown;
241
443
  /**
242
444
  * `fchmod`.
243
- * @param fd
445
+ * @param handle
244
446
  * @param mode
245
447
  */
246
- export async function fchmod(fd, mode) {
247
- const numMode = typeof mode === 'string' ? parseInt(mode, 8) : mode;
248
- return fd2file(fd).chmod(numMode);
448
+ export function fchmod(handle, mode) {
449
+ return handle.chmod(mode);
249
450
  }
451
+ fchmod;
250
452
  /**
251
453
  * Change the file timestamps of a file referenced by the supplied file
252
454
  * descriptor.
253
- * @param fd
455
+ * @param handle
254
456
  * @param atime
255
457
  * @param mtime
256
458
  */
257
- export async function futimes(fd, atime, mtime) {
258
- return fd2file(fd).utimes(normalizeTime(atime), normalizeTime(mtime));
459
+ export function futimes(handle, atime, mtime) {
460
+ return handle.utimes(atime, mtime);
259
461
  }
462
+ futimes;
260
463
  // DIRECTORY-ONLY METHODS
261
464
  /**
262
465
  * `rmdir`.
@@ -265,14 +468,11 @@ export async function futimes(fd, atime, mtime) {
265
468
  export async function rmdir(path) {
266
469
  return doOp('rmdir', true, path, cred);
267
470
  }
268
- /**
269
- * `mkdir`.
270
- * @param path
271
- * @param mode defaults to `0777`
272
- */
471
+ rmdir;
273
472
  export async function mkdir(path, mode) {
274
- return doOp('mkdir', true, path, normalizeMode(mode, 0o777), cred);
473
+ await doOp('mkdir', true, path, normalizeMode(typeof mode == 'object' ? mode?.mode : mode, 0o777), cred);
275
474
  }
475
+ mkdir;
276
476
  export async function readdir(path, options) {
277
477
  path = normalizePath(path);
278
478
  const entries = await doOp('readdir', true, path, cred);
@@ -289,37 +489,49 @@ export async function readdir(path, options) {
289
489
  }
290
490
  const values = [];
291
491
  for (const entry of entries) {
292
- values.push(options?.withFileTypes ? new Dirent(entry, await stat(join(path, entry))) : entry);
492
+ values.push(typeof options == 'object' && options?.withFileTypes ? new Dirent(entry, await stat(join(path, entry))) : entry);
293
493
  }
294
494
  return values;
295
495
  }
496
+ readdir;
296
497
  // SYMLINK METHODS
297
498
  /**
298
499
  * `link`.
299
- * @param srcpath
300
- * @param dstpath
500
+ * @param existing
501
+ * @param newpath
301
502
  */
302
- export async function link(srcpath, dstpath) {
303
- dstpath = normalizePath(dstpath);
304
- return doOp('link', false, srcpath, dstpath, cred);
503
+ export async function link(existing, newpath) {
504
+ newpath = normalizePath(newpath);
505
+ return doOp('link', false, existing, newpath, cred);
305
506
  }
507
+ link;
306
508
  /**
307
509
  * `symlink`.
308
- * @param srcpath
309
- * @param dstpath
510
+ * @param target target path
511
+ * @param path link path
310
512
  * @param type can be either `'dir'` or `'file'` (default is `'file'`)
311
513
  */
312
- export async function symlink(srcpath, dstpath, type = 'file') {
514
+ export async function symlink(target, path, type = 'file') {
313
515
  if (!['file', 'dir', 'junction'].includes(type)) {
314
- throw new ApiError(ErrorCode.EINVAL, 'Invalid type: ' + type);
516
+ throw new ApiError(ErrorCode.EINVAL, 'Invalid symlink type: ' + type);
517
+ }
518
+ if (await exists(path)) {
519
+ throw ApiError.EEXIST(path);
315
520
  }
316
- dstpath = normalizePath(dstpath);
317
- return doOp('symlink', false, srcpath, dstpath, type, cred);
521
+ await writeFile(path, target);
522
+ const file = await _open(path, 'r+', 0o644, false);
523
+ await file._setType(FileType.SYMLINK);
318
524
  }
525
+ symlink;
319
526
  export async function readlink(path, options) {
320
- const value = await doOp('readlink', false, path, cred);
321
- return encode(value, typeof options == 'object' ? options.encoding : options);
527
+ const value = await _readFile(path, 'r', false);
528
+ const encoding = typeof options == 'object' ? options.encoding : options;
529
+ if (encoding == 'buffer') {
530
+ return value;
531
+ }
532
+ return decode(value, encoding);
322
533
  }
534
+ readlink;
323
535
  // PROPERTY OPERATIONS
324
536
  /**
325
537
  * `chown`.
@@ -328,8 +540,15 @@ export async function readlink(path, options) {
328
540
  * @param gid
329
541
  */
330
542
  export async function chown(path, uid, gid) {
331
- return doOp('chown', true, path, uid, gid, cred);
543
+ const handle = await open(path, 'r+');
544
+ try {
545
+ await handle.chown(uid, gid);
546
+ }
547
+ finally {
548
+ await handle.close();
549
+ }
332
550
  }
551
+ chown;
333
552
  /**
334
553
  * `lchown`.
335
554
  * @param path
@@ -337,32 +556,45 @@ export async function chown(path, uid, gid) {
337
556
  * @param gid
338
557
  */
339
558
  export async function lchown(path, uid, gid) {
340
- return doOp('chown', false, path, uid, gid, cred);
559
+ const handle = await lopen(path, 'r+');
560
+ try {
561
+ await handle.chown(uid, gid);
562
+ }
563
+ finally {
564
+ await handle.close();
565
+ }
341
566
  }
567
+ lchown;
342
568
  /**
343
569
  * `chmod`.
344
570
  * @param path
345
571
  * @param mode
346
572
  */
347
573
  export async function chmod(path, mode) {
348
- const numMode = normalizeMode(mode, -1);
349
- if (numMode < 0) {
350
- throw new ApiError(ErrorCode.EINVAL, `Invalid mode.`);
574
+ const handle = await open(path, 'r+');
575
+ try {
576
+ await handle.chmod(mode);
577
+ }
578
+ finally {
579
+ await handle.close();
351
580
  }
352
- return doOp('chmod', true, path, numMode, cred);
353
581
  }
582
+ chmod;
354
583
  /**
355
584
  * `lchmod`.
356
585
  * @param path
357
586
  * @param mode
358
587
  */
359
588
  export async function lchmod(path, mode) {
360
- const numMode = normalizeMode(mode, -1);
361
- if (numMode < 1) {
362
- throw new ApiError(ErrorCode.EINVAL, `Invalid mode.`);
589
+ const handle = await lopen(path, 'r+');
590
+ try {
591
+ await handle.chmod(mode);
592
+ }
593
+ finally {
594
+ await handle.close();
363
595
  }
364
- return doOp('chmod', false, normalizePath(path), numMode, cred);
365
596
  }
597
+ lchmod;
366
598
  /**
367
599
  * Change file timestamps of the file referenced by the supplied path.
368
600
  * @param path
@@ -370,8 +602,15 @@ export async function lchmod(path, mode) {
370
602
  * @param mtime
371
603
  */
372
604
  export async function utimes(path, atime, mtime) {
373
- return doOp('utimes', true, path, normalizeTime(atime), normalizeTime(mtime), cred);
605
+ const handle = await open(path, 'r+');
606
+ try {
607
+ await handle.utimes(atime, mtime);
608
+ }
609
+ finally {
610
+ await handle.close();
611
+ }
374
612
  }
613
+ utimes;
375
614
  /**
376
615
  * Change file timestamps of the file referenced by the supplied path.
377
616
  * @param path
@@ -379,16 +618,15 @@ export async function utimes(path, atime, mtime) {
379
618
  * @param mtime
380
619
  */
381
620
  export async function lutimes(path, atime, mtime) {
382
- return doOp('utimes', false, path, normalizeTime(atime), normalizeTime(mtime), cred);
621
+ const handle = await lopen(path, 'r+');
622
+ try {
623
+ await handle.utimes(atime, mtime);
624
+ }
625
+ finally {
626
+ await handle.close();
627
+ }
383
628
  }
384
- /**
385
- * `realpath`.
386
- * @param path
387
- * @param options
388
- * @return resolved path
389
- *
390
- * Note: This *Can not* use doOp since doOp depends on it
391
- */
629
+ lutimes;
392
630
  export async function realpath(path, options) {
393
631
  path = normalizePath(path);
394
632
  const { fs, path: resolvedPath, mountPoint } = resolveFS(path);
@@ -397,13 +635,14 @@ export async function realpath(path, options) {
397
635
  if (!stats.isSymbolicLink()) {
398
636
  return path;
399
637
  }
400
- const dst = mountPoint + normalizePath(await fs.readlink(resolvedPath, cred));
638
+ const dst = mountPoint + normalizePath(decode(await _readFile(resolvedPath, 'r+', false)));
401
639
  return realpath(dst);
402
640
  }
403
641
  catch (e) {
404
642
  throw fixError(e, { [resolvedPath]: path });
405
643
  }
406
644
  }
645
+ realpath;
407
646
  export async function watchFile(filename, arg2, listener = nop) {
408
647
  throw new ApiError(ErrorCode.ENOTSUP);
409
648
  }
@@ -419,7 +658,10 @@ export async function watch(filename, arg2, listener = nop) {
419
658
  * @param mode
420
659
  */
421
660
  export async function access(path, mode = 0o600) {
422
- return doOp('access', true, path, mode, cred);
661
+ const stats = await stat(path);
662
+ if (!stats.hasAccess(mode, cred)) {
663
+ throw new ApiError(ErrorCode.EACCES);
664
+ }
423
665
  }
424
666
  export async function createReadStream(path, options) {
425
667
  throw new ApiError(ErrorCode.ENOTSUP);