@zenfs/core 0.9.2 → 0.9.4

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.
@@ -0,0 +1,898 @@
1
+ import { Buffer } from 'buffer';
2
+ import type * as Node from 'fs';
3
+ import type { BufferEncodingOption, EncodingOption, ReadSyncOptions, StatOptions, symlink } from 'fs';
4
+ import { ApiError, ErrorCode } from '../ApiError.js';
5
+ import { ActionType, File, isAppendable, isReadable, isWriteable, parseFlag, pathExistsAction, pathNotExistsAction } from '../file.js';
6
+ import { FileContents, FileSystem } from '../filesystem.js';
7
+ import { BigIntStats, FileType, type BigIntStatsFs, type Stats, type StatsFs } from '../stats.js';
8
+ import { normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
9
+ import { COPYFILE_EXCL, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK } from './constants.js';
10
+ import { Dir, Dirent } from './dir.js';
11
+ import { dirname, join, parse } from './path.js';
12
+ import { PathLike, cred, fd2file, fdMap, fixError, getFdForFile, mounts, resolveMount } from './shared.js';
13
+
14
+ type FileSystemMethod = {
15
+ [K in keyof FileSystem]: FileSystem[K] extends (...args) => unknown
16
+ ? (name: K, resolveSymlinks: boolean, ...args: Parameters<FileSystem[K]>) => ReturnType<FileSystem[K]>
17
+ : never;
18
+ }[keyof FileSystem]; // https://stackoverflow.com/a/76335220/17637456
19
+
20
+ function doOp<M extends FileSystemMethod, RT extends ReturnType<M>>(...[name, resolveSymlinks, path, ...args]: Parameters<M>): RT {
21
+ path = normalizePath(path);
22
+ const { fs, path: resolvedPath } = resolveMount(resolveSymlinks && existsSync(path) ? realpathSync(path) : path);
23
+ try {
24
+ // @ts-expect-error 2556 (since ...args is not correctly picked up as being a tuple)
25
+ return fs[name](resolvedPath, ...args) as RT;
26
+ } catch (e) {
27
+ throw fixError(e, { [resolvedPath]: path });
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Synchronous rename.
33
+ * @param oldPath
34
+ * @param newPath
35
+ */
36
+ export function renameSync(oldPath: PathLike, newPath: PathLike): void {
37
+ oldPath = normalizePath(oldPath);
38
+ newPath = normalizePath(newPath);
39
+ const _old = resolveMount(oldPath);
40
+ const _new = resolveMount(newPath);
41
+ const paths = { [_old.path]: oldPath, [_new.path]: newPath };
42
+ try {
43
+ if (_old === _new) {
44
+ return _old.fs.renameSync(_old.path, _new.path, cred);
45
+ }
46
+
47
+ writeFileSync(newPath, readFileSync(oldPath));
48
+ unlinkSync(oldPath);
49
+ } catch (e) {
50
+ throw fixError(e, paths);
51
+ }
52
+ }
53
+ renameSync satisfies typeof Node.renameSync;
54
+
55
+ /**
56
+ * Test whether or not the given path exists by checking with the file system.
57
+ * @param path
58
+ */
59
+ export function existsSync(path: PathLike): boolean {
60
+ path = normalizePath(path);
61
+ try {
62
+ const { fs, path: resolvedPath } = resolveMount(realpathSync(path));
63
+ return fs.existsSync(resolvedPath, cred);
64
+ } catch (e) {
65
+ if ((e as ApiError).errno == ErrorCode.ENOENT) {
66
+ return false;
67
+ }
68
+
69
+ throw e;
70
+ }
71
+ }
72
+ existsSync satisfies typeof Node.existsSync;
73
+
74
+ /**
75
+ * Synchronous `stat`.
76
+ * @param path
77
+ * @returns Stats
78
+ */
79
+ export function statSync(path: PathLike, options?: { bigint?: false }): Stats;
80
+ export function statSync(path: PathLike, options: { bigint: true }): BigIntStats;
81
+ export function statSync(path: PathLike, options?: StatOptions): Stats | BigIntStats {
82
+ const stats: Stats = doOp('statSync', true, path, cred);
83
+ return options?.bigint ? new BigIntStats(stats) : stats;
84
+ }
85
+ statSync satisfies typeof Node.statSync;
86
+
87
+ /**
88
+ * Synchronous `lstat`.
89
+ * `lstat()` is identical to `stat()`, except that if path is a symbolic link,
90
+ * then the link itself is stat-ed, not the file that it refers to.
91
+ * @param path
92
+ */
93
+ export function lstatSync(path: PathLike, options?: { bigint?: false }): Stats;
94
+ export function lstatSync(path: PathLike, options: { bigint: true }): BigIntStats;
95
+ export function lstatSync(path: PathLike, options?: StatOptions): Stats | BigIntStats {
96
+ const stats: Stats = doOp('statSync', false, path, cred);
97
+ return options?.bigint ? new BigIntStats(stats) : stats;
98
+ }
99
+ lstatSync satisfies typeof Node.lstatSync;
100
+
101
+ /**
102
+ * Synchronous `truncate`.
103
+ * @param path
104
+ * @param len
105
+ */
106
+ export function truncateSync(path: PathLike, len: number = 0): void {
107
+ const fd = openSync(path, 'r+');
108
+ try {
109
+ ftruncateSync(fd, len);
110
+ } finally {
111
+ closeSync(fd);
112
+ }
113
+ }
114
+ truncateSync satisfies typeof Node.truncateSync;
115
+
116
+ /**
117
+ * Synchronous `unlink`.
118
+ * @param path
119
+ */
120
+ export function unlinkSync(path: PathLike): void {
121
+ return doOp('unlinkSync', false, path, cred);
122
+ }
123
+ unlinkSync satisfies typeof Node.unlinkSync;
124
+
125
+ function _openSync(_path: PathLike, _flag: string, _mode: Node.Mode, resolveSymlinks: boolean): File {
126
+ const path = normalizePath(_path),
127
+ mode = normalizeMode(_mode, 0o644),
128
+ flag = parseFlag(_flag);
129
+ // Check if the path exists, and is a file.
130
+ let stats: Stats;
131
+ try {
132
+ stats = doOp('statSync', resolveSymlinks, path, cred);
133
+ } catch (e) {
134
+ // File does not exist.
135
+ switch (pathNotExistsAction(flag)) {
136
+ case ActionType.CREATE:
137
+ // Ensure parent exists.
138
+ const parentStats: Stats = doOp('statSync', resolveSymlinks, dirname(path), cred);
139
+ if (!parentStats.isDirectory()) {
140
+ throw ApiError.With('ENOTDIR', dirname(path), '_open');
141
+ }
142
+ return doOp('createFileSync', resolveSymlinks, path, flag, mode, cred);
143
+ case ActionType.THROW:
144
+ throw ApiError.With('ENOENT', path, '_open');
145
+ default:
146
+ throw new ApiError(ErrorCode.EINVAL, 'Invalid FileFlag object.');
147
+ }
148
+ }
149
+ if (!stats.hasAccess(mode, cred)) {
150
+ throw ApiError.With('EACCES', path, '_open');
151
+ }
152
+
153
+ // File exists.
154
+ switch (pathExistsAction(flag)) {
155
+ case ActionType.THROW:
156
+ throw ApiError.With('EEXIST', path, '_open');
157
+ case ActionType.TRUNCATE:
158
+ // Delete file.
159
+ doOp('unlinkSync', resolveSymlinks, path, cred);
160
+ /*
161
+ Create file. Use the same mode as the old file.
162
+ Node itself modifies the ctime when this occurs, so this action
163
+ will preserve that behavior if the underlying file system
164
+ supports those properties.
165
+ */
166
+ return doOp('createFileSync', resolveSymlinks, path, flag, stats.mode, cred);
167
+ case ActionType.NOP:
168
+ return doOp('openFileSync', resolveSymlinks, path, flag, cred);
169
+ default:
170
+ throw new ApiError(ErrorCode.EINVAL, 'Invalid FileFlag object.');
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Synchronous file open.
176
+ * @see http://www.manpagez.com/man/2/open/
177
+ * @param flags Handles the complexity of the various file
178
+ * modes. See its API for more details.
179
+ * @param mode Mode to use to open the file. Can be ignored if the
180
+ * filesystem doesn't support permissions.
181
+ */
182
+ export function openSync(path: PathLike, flag: string, mode?: Node.Mode): number {
183
+ return getFdForFile(_openSync(path, flag, mode, true));
184
+ }
185
+ openSync satisfies typeof Node.openSync;
186
+
187
+ /**
188
+ * Opens a file or symlink
189
+ * @internal
190
+ */
191
+ export function lopenSync(path: PathLike, flag: string, mode?: Node.Mode): number {
192
+ return getFdForFile(_openSync(path, flag, mode, false));
193
+ }
194
+
195
+ /**
196
+ * Synchronously reads the entire contents of a file.
197
+ */
198
+ function _readFileSync(fname: string, flag: string, resolveSymlinks: boolean): Uint8Array {
199
+ // Get file.
200
+ const file = _openSync(fname, flag, 0o644, resolveSymlinks);
201
+ try {
202
+ const stat = file.statSync();
203
+ // Allocate buffer.
204
+ const data = new Uint8Array(stat.size);
205
+ file.readSync(data, 0, stat.size, 0);
206
+ file.closeSync();
207
+ return data;
208
+ } finally {
209
+ file.closeSync();
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Synchronously reads the entire contents of a file.
215
+ * @param filename
216
+ * @param options
217
+ * @option options encoding The string encoding for the file contents. Defaults to `null`.
218
+ * @option options flag Defaults to `'r'`.
219
+ * @returns file contents
220
+ */
221
+ export function readFileSync(filename: string, options?: { flag?: string }): Buffer;
222
+ export function readFileSync(filename: string, options: (Node.EncodingOption & { flag?: string }) | BufferEncoding): string;
223
+ export function readFileSync(filename: string, arg2: Node.WriteFileOptions = {}): FileContents {
224
+ const options = normalizeOptions(arg2, null, 'r', 0o644);
225
+ const flag = parseFlag(options.flag);
226
+ if (!isReadable(flag)) {
227
+ throw new ApiError(ErrorCode.EINVAL, 'Flag passed to readFile must allow for reading.');
228
+ }
229
+ const data: Buffer = Buffer.from(_readFileSync(filename, options.flag, true));
230
+ return options.encoding ? data.toString(options.encoding) : data;
231
+ }
232
+ readFileSync satisfies typeof Node.readFileSync;
233
+
234
+ /**
235
+ * Synchronously writes data to a file, replacing the file
236
+ * if it already exists.
237
+ *
238
+ * The encoding option is ignored if data is a buffer.
239
+ */
240
+ function _writeFileSync(fname: string, data: Uint8Array, flag: string, mode: number, resolveSymlinks: boolean): void {
241
+ const file = _openSync(fname, flag, mode, resolveSymlinks);
242
+ try {
243
+ file.writeSync(data, 0, data.length, 0);
244
+ } finally {
245
+ file.closeSync();
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Synchronously writes data to a file, replacing the file if it already
251
+ * exists.
252
+ *
253
+ * The encoding option is ignored if data is a buffer.
254
+ * @param filename
255
+ * @param data
256
+ * @param options
257
+ * @option options encoding Defaults to `'utf8'`.
258
+ * @option options mode Defaults to `0644`.
259
+ * @option options flag Defaults to `'w'`.
260
+ */
261
+ export function writeFileSync(filename: string, data: FileContents, options?: Node.WriteFileOptions): void;
262
+ export function writeFileSync(filename: string, data: FileContents, encoding?: BufferEncoding): void;
263
+ export function writeFileSync(filename: string, data: FileContents, _options?: Node.WriteFileOptions | BufferEncoding): void {
264
+ const options = normalizeOptions(_options, 'utf8', 'w+', 0o644);
265
+ const flag = parseFlag(options.flag);
266
+ if (!isWriteable(flag)) {
267
+ throw new ApiError(ErrorCode.EINVAL, 'Flag passed to writeFile must allow for writing.');
268
+ }
269
+ if (typeof data != 'string' && !options.encoding) {
270
+ throw new ApiError(ErrorCode.EINVAL, 'Encoding not specified');
271
+ }
272
+ const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : data;
273
+ if (encodedData === undefined) {
274
+ throw new ApiError(ErrorCode.EINVAL, 'Data not specified');
275
+ }
276
+ _writeFileSync(filename, encodedData, options.flag, options.mode, true);
277
+ }
278
+ writeFileSync satisfies typeof Node.writeFileSync;
279
+
280
+ /**
281
+ * Synchronously append data to a file, creating the file if
282
+ * it not yet exists.
283
+ */
284
+ function _appendFileSync(fname: string, data: Uint8Array, flag: string, mode: number, resolveSymlinks: boolean): void {
285
+ const file = _openSync(fname, flag, mode, resolveSymlinks);
286
+ try {
287
+ file.writeSync(data, 0, data.length, null);
288
+ } finally {
289
+ file.closeSync();
290
+ }
291
+ }
292
+
293
+ /**
294
+ * Asynchronously append data to a file, creating the file if it not yet
295
+ * exists.
296
+ *
297
+ * @param filename
298
+ * @param data
299
+ * @param options
300
+ * @option options encoding Defaults to `'utf8'`.
301
+ * @option options mode Defaults to `0644`.
302
+ * @option options flag Defaults to `'a'`.
303
+ */
304
+ export function appendFileSync(filename: string, data: FileContents, _options?: Node.WriteFileOptions): void {
305
+ const options = normalizeOptions(_options, 'utf8', 'a', 0o644);
306
+ const flag = parseFlag(options.flag);
307
+ if (!isAppendable(flag)) {
308
+ throw new ApiError(ErrorCode.EINVAL, 'Flag passed to appendFile must allow for appending.');
309
+ }
310
+ if (typeof data != 'string' && !options.encoding) {
311
+ throw new ApiError(ErrorCode.EINVAL, 'Encoding not specified');
312
+ }
313
+ const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : data;
314
+ _appendFileSync(filename, encodedData, options.flag, options.mode, true);
315
+ }
316
+ appendFileSync satisfies typeof Node.appendFileSync;
317
+
318
+ /**
319
+ * Synchronous `fstat`.
320
+ * `fstat()` is identical to `stat()`, except that the file to be stat-ed is
321
+ * specified by the file descriptor `fd`.
322
+ * @param fd
323
+ */
324
+ export function fstatSync(fd: number, options?: { bigint?: false }): Stats;
325
+ export function fstatSync(fd: number, options: { bigint: true }): BigIntStats;
326
+ export function fstatSync(fd: number, options?: StatOptions): Stats | BigIntStats {
327
+ const stats: Stats = fd2file(fd).statSync();
328
+ return options?.bigint ? new BigIntStats(stats) : stats;
329
+ }
330
+ fstatSync satisfies typeof Node.fstatSync;
331
+
332
+ /**
333
+ * Synchronous close.
334
+ * @param fd
335
+ */
336
+ export function closeSync(fd: number): void {
337
+ fd2file(fd).closeSync();
338
+ fdMap.delete(fd);
339
+ }
340
+ closeSync satisfies typeof Node.closeSync;
341
+
342
+ /**
343
+ * Synchronous ftruncate.
344
+ * @param fd
345
+ * @param len
346
+ */
347
+ export function ftruncateSync(fd: number, len: number = 0): void {
348
+ if (len < 0) {
349
+ throw new ApiError(ErrorCode.EINVAL);
350
+ }
351
+ fd2file(fd).truncateSync(len);
352
+ }
353
+ ftruncateSync satisfies typeof Node.ftruncateSync;
354
+
355
+ /**
356
+ * Synchronous fsync.
357
+ * @param fd
358
+ */
359
+ export function fsyncSync(fd: number): void {
360
+ fd2file(fd).syncSync();
361
+ }
362
+ fsyncSync satisfies typeof Node.fsyncSync;
363
+
364
+ /**
365
+ * Synchronous fdatasync.
366
+ * @param fd
367
+ */
368
+ export function fdatasyncSync(fd: number): void {
369
+ fd2file(fd).datasyncSync();
370
+ }
371
+ fdatasyncSync satisfies typeof Node.fdatasyncSync;
372
+
373
+ /**
374
+ * Write buffer to the file specified by `fd`.
375
+ * Note that it is unsafe to use fs.write multiple times on the same file
376
+ * without waiting for it to return.
377
+ * @param fd
378
+ * @param data Uint8Array containing the data to write to
379
+ * the file.
380
+ * @param offset Offset in the buffer to start reading data from.
381
+ * @param length The amount of bytes to write to the file.
382
+ * @param position Offset from the beginning of the file where this
383
+ * data should be written. If position is null, the data will be written at
384
+ * the current position.
385
+ */
386
+ export function writeSync(fd: number, data: Uint8Array, offset: number, length: number, position?: number): number;
387
+ export function writeSync(fd: number, data: string, position?: number, encoding?: BufferEncoding): number;
388
+ export function writeSync(fd: number, data: FileContents, posOrOff?: number, lenOrEnc?: BufferEncoding | number, pos?: number): number {
389
+ let buffer: Uint8Array,
390
+ offset: number = 0,
391
+ length: number,
392
+ position: number;
393
+ if (typeof data === 'string') {
394
+ // Signature 1: (fd, string, [position?, [encoding?]])
395
+ position = typeof posOrOff === 'number' ? posOrOff : null;
396
+ const encoding = <BufferEncoding>(typeof lenOrEnc === 'string' ? lenOrEnc : 'utf8');
397
+ offset = 0;
398
+ buffer = Buffer.from(data, encoding);
399
+ length = buffer.length;
400
+ } else {
401
+ // Signature 2: (fd, buffer, offset, length, position?)
402
+ buffer = data;
403
+ offset = posOrOff;
404
+ length = lenOrEnc as number;
405
+ position = typeof pos === 'number' ? pos : null;
406
+ }
407
+
408
+ const file = fd2file(fd);
409
+ if (position === undefined || position === null) {
410
+ position = file.position!;
411
+ }
412
+ return file.writeSync(buffer, offset, length, position);
413
+ }
414
+ writeSync satisfies typeof Node.writeSync;
415
+
416
+ /**
417
+ * Read data from the file specified by `fd`.
418
+ * @param fd
419
+ * @param buffer The buffer that the data will be
420
+ * written to.
421
+ * @param offset The offset within the buffer where writing will
422
+ * start.
423
+ * @param length An integer specifying the number of bytes to read.
424
+ * @param position An integer specifying where to begin reading from
425
+ * in the file. If position is null, data will be read from the current file
426
+ * position.
427
+ */
428
+ export function readSync(fd: number, buffer: Uint8Array, opts?: ReadSyncOptions): number;
429
+ export function readSync(fd: number, buffer: Uint8Array, offset: number, length: number, position?: number): number;
430
+ export function readSync(fd: number, buffer: Uint8Array, opts?: ReadSyncOptions | number, length?: number, position?: number | bigint): number {
431
+ const file = fd2file(fd);
432
+ const offset = typeof opts == 'object' ? opts.offset : opts;
433
+ if (typeof opts == 'object') {
434
+ length = opts.length;
435
+ position = opts.position;
436
+ }
437
+
438
+ position = Number(position);
439
+ if (isNaN(position)) {
440
+ position = file.position!;
441
+ }
442
+
443
+ return file.readSync(buffer, offset, length, position);
444
+ }
445
+ readSync satisfies typeof Node.readSync;
446
+
447
+ /**
448
+ * Synchronous `fchown`.
449
+ * @param fd
450
+ * @param uid
451
+ * @param gid
452
+ */
453
+ export function fchownSync(fd: number, uid: number, gid: number): void {
454
+ fd2file(fd).chownSync(uid, gid);
455
+ }
456
+ fchownSync satisfies typeof Node.fchownSync;
457
+
458
+ /**
459
+ * Synchronous `fchmod`.
460
+ * @param fd
461
+ * @param mode
462
+ */
463
+ export function fchmodSync(fd: number, mode: number | string): void {
464
+ const numMode = normalizeMode(mode, -1);
465
+ if (numMode < 0) {
466
+ throw new ApiError(ErrorCode.EINVAL, `Invalid mode.`);
467
+ }
468
+ fd2file(fd).chmodSync(numMode);
469
+ }
470
+ fchmodSync satisfies typeof Node.fchmodSync;
471
+
472
+ /**
473
+ * Change the file timestamps of a file referenced by the supplied file
474
+ * descriptor.
475
+ * @param fd
476
+ * @param atime
477
+ * @param mtime
478
+ */
479
+ export function futimesSync(fd: number, atime: string | number | Date, mtime: string | number | Date): void {
480
+ fd2file(fd).utimesSync(normalizeTime(atime), normalizeTime(mtime));
481
+ }
482
+ futimesSync satisfies typeof Node.futimesSync;
483
+
484
+ /**
485
+ * Synchronous `rmdir`.
486
+ * @param path
487
+ */
488
+ export function rmdirSync(path: PathLike): void {
489
+ return doOp('rmdirSync', true, path, cred);
490
+ }
491
+ rmdirSync satisfies typeof Node.rmdirSync;
492
+
493
+ /**
494
+ * Synchronous `mkdir`.
495
+ * @param path
496
+ * @param mode defaults to o777
497
+ * @todo Implement recursion
498
+ */
499
+ export function mkdirSync(path: PathLike, options: Node.MakeDirectoryOptions & { recursive: true }): string;
500
+ export function mkdirSync(path: PathLike, options?: Node.Mode | (Node.MakeDirectoryOptions & { recursive?: false })): void;
501
+ export function mkdirSync(path: PathLike, options?: Node.Mode | Node.MakeDirectoryOptions): string | void {
502
+ const mode: Node.Mode = typeof options == 'number' || typeof options == 'string' ? options : options?.mode;
503
+ const recursive = typeof options == 'object' && options?.recursive;
504
+ doOp('mkdirSync', true, path, normalizeMode(mode, 0o777), cred);
505
+ }
506
+ mkdirSync satisfies typeof Node.mkdirSync;
507
+
508
+ /**
509
+ * Synchronous `readdir`. Reads the contents of a directory.
510
+ * @param path
511
+ */
512
+ export function readdirSync(path: PathLike, options?: { encoding?: BufferEncoding; withFileTypes?: false } | BufferEncoding): string[];
513
+ export function readdirSync(path: PathLike, options: { encoding: 'buffer'; withFileTypes?: false } | 'buffer'): Buffer[];
514
+ export function readdirSync(path: PathLike, options: { withFileTypes: true }): Dirent[];
515
+ export function readdirSync(path: PathLike, options?: { encoding?: BufferEncoding | 'buffer'; withFileTypes?: boolean } | string): string[] | Dirent[] | Buffer[] {
516
+ path = normalizePath(path);
517
+ const entries: string[] = doOp('readdirSync', true, path, cred);
518
+ for (const mount of mounts.keys()) {
519
+ if (!mount.startsWith(path)) {
520
+ continue;
521
+ }
522
+ const entry = mount.slice(path.length);
523
+ if (entry.includes('/') || entry.length == 0) {
524
+ // ignore FSs mounted in subdirectories and any FS mounted to `path`.
525
+ continue;
526
+ }
527
+ entries.push(entry);
528
+ }
529
+ return <string[] | Dirent[] | Buffer[]>entries.map((entry: string): string | Dirent | Buffer => {
530
+ if (typeof options == 'object' && options?.withFileTypes) {
531
+ return new Dirent(entry, statSync(join(path, entry)));
532
+ }
533
+
534
+ if (options == 'buffer' || (typeof options == 'object' && options.encoding == 'buffer')) {
535
+ return Buffer.from(entry);
536
+ }
537
+
538
+ return entry;
539
+ });
540
+ }
541
+ readdirSync satisfies typeof Node.readdirSync;
542
+
543
+ // SYMLINK METHODS
544
+
545
+ /**
546
+ * Synchronous `link`.
547
+ * @param existing
548
+ * @param newpath
549
+ */
550
+ export function linkSync(existing: PathLike, newpath: PathLike): void {
551
+ newpath = normalizePath(newpath);
552
+ return doOp('linkSync', false, existing, newpath, cred);
553
+ }
554
+ linkSync satisfies typeof Node.linkSync;
555
+
556
+ /**
557
+ * Synchronous `symlink`.
558
+ * @param target target path
559
+ * @param path link path
560
+ * @param type can be either `'dir'` or `'file'` (default is `'file'`)
561
+ */
562
+ export function symlinkSync(target: PathLike, path: PathLike, type: symlink.Type = 'file'): void {
563
+ if (!['file', 'dir', 'junction'].includes(type)) {
564
+ throw new ApiError(ErrorCode.EINVAL, 'Invalid type: ' + type);
565
+ }
566
+ if (existsSync(path)) {
567
+ throw ApiError.With('EEXIST', path, 'symlink');
568
+ }
569
+
570
+ writeFileSync(path, target);
571
+ const file = _openSync(path, 'r+', 0o644, false);
572
+ file._setTypeSync(FileType.SYMLINK);
573
+ }
574
+ symlinkSync satisfies typeof Node.symlinkSync;
575
+
576
+ /**
577
+ * Synchronous readlink.
578
+ * @param path
579
+ */
580
+ export function readlinkSync(path: PathLike, options?: BufferEncodingOption): Buffer;
581
+ export function readlinkSync(path: PathLike, options: EncodingOption | BufferEncoding): string;
582
+ export function readlinkSync(path: PathLike, options?: EncodingOption | BufferEncoding | BufferEncodingOption): Buffer | string {
583
+ const value: Buffer = Buffer.from(_readFileSync(path, 'r', false));
584
+ const encoding: BufferEncoding | 'buffer' = typeof options == 'object' ? options.encoding : options;
585
+ if (encoding == 'buffer') {
586
+ return value;
587
+ }
588
+ return value.toString(encoding);
589
+ }
590
+ readlinkSync satisfies typeof Node.readlinkSync;
591
+
592
+ // PROPERTY OPERATIONS
593
+
594
+ /**
595
+ * Synchronous `chown`.
596
+ * @param path
597
+ * @param uid
598
+ * @param gid
599
+ */
600
+ export function chownSync(path: PathLike, uid: number, gid: number): void {
601
+ const fd = openSync(path, 'r+');
602
+ fchownSync(fd, uid, gid);
603
+ closeSync(fd);
604
+ }
605
+ chownSync satisfies typeof Node.chownSync;
606
+
607
+ /**
608
+ * Synchronous `lchown`.
609
+ * @param path
610
+ * @param uid
611
+ * @param gid
612
+ */
613
+ export function lchownSync(path: PathLike, uid: number, gid: number): void {
614
+ const fd = lopenSync(path, 'r+');
615
+ fchownSync(fd, uid, gid);
616
+ closeSync(fd);
617
+ }
618
+ lchownSync satisfies typeof Node.lchownSync;
619
+
620
+ /**
621
+ * Synchronous `chmod`.
622
+ * @param path
623
+ * @param mode
624
+ */
625
+ export function chmodSync(path: PathLike, mode: Node.Mode): void {
626
+ const fd = openSync(path, 'r+');
627
+ fchmodSync(fd, mode);
628
+ closeSync(fd);
629
+ }
630
+ chmodSync satisfies typeof Node.chmodSync;
631
+
632
+ /**
633
+ * Synchronous `lchmod`.
634
+ * @param path
635
+ * @param mode
636
+ */
637
+ export function lchmodSync(path: PathLike, mode: number | string): void {
638
+ const fd = lopenSync(path, 'r+');
639
+ fchmodSync(fd, mode);
640
+ closeSync(fd);
641
+ }
642
+ lchmodSync satisfies typeof Node.lchmodSync;
643
+
644
+ /**
645
+ * Change file timestamps of the file referenced by the supplied path.
646
+ * @param path
647
+ * @param atime
648
+ * @param mtime
649
+ */
650
+ export function utimesSync(path: PathLike, atime: string | number | Date, mtime: string | number | Date): void {
651
+ const fd = openSync(path, 'r+');
652
+ futimesSync(fd, atime, mtime);
653
+ closeSync(fd);
654
+ }
655
+ utimesSync satisfies typeof Node.utimesSync;
656
+
657
+ /**
658
+ * Change file timestamps of the file referenced by the supplied path.
659
+ * @param path
660
+ * @param atime
661
+ * @param mtime
662
+ */
663
+ export function lutimesSync(path: PathLike, atime: string | number | Date, mtime: string | number | Date): void {
664
+ const fd = lopenSync(path, 'r+');
665
+ futimesSync(fd, atime, mtime);
666
+ closeSync(fd);
667
+ }
668
+ lutimesSync satisfies typeof Node.lutimesSync;
669
+
670
+ /**
671
+ * Synchronous `realpath`.
672
+ * @param path
673
+ * @param cache An object literal of mapped paths that can be used to
674
+ * force a specific path resolution or avoid additional `fs.stat` calls for
675
+ * known real paths.
676
+ * @returns the real path
677
+ */
678
+ export function realpathSync(path: PathLike, options: BufferEncodingOption): Buffer;
679
+ export function realpathSync(path: PathLike, options?: EncodingOption): string;
680
+ export function realpathSync(path: PathLike, options?: EncodingOption | BufferEncodingOption): string | Buffer {
681
+ path = normalizePath(path);
682
+ const { base, dir } = parse(path);
683
+ const lpath = join(dir == '/' ? '/' : realpathSync(dir), base);
684
+ const { fs, path: resolvedPath, mountPoint } = resolveMount(lpath);
685
+
686
+ try {
687
+ const stats = fs.statSync(resolvedPath, cred);
688
+ if (!stats.isSymbolicLink()) {
689
+ return lpath;
690
+ }
691
+
692
+ return realpathSync(mountPoint + readlinkSync(lpath));
693
+ } catch (e) {
694
+ throw fixError(e, { [resolvedPath]: lpath });
695
+ }
696
+ }
697
+ realpathSync satisfies Omit<typeof Node.realpathSync, 'native'>;
698
+
699
+ /**
700
+ * Synchronous `access`.
701
+ * @param path
702
+ * @param mode
703
+ */
704
+ export function accessSync(path: PathLike, mode: number = 0o600): void {
705
+ const stats = statSync(path);
706
+ if (!stats.hasAccess(mode, cred)) {
707
+ throw new ApiError(ErrorCode.EACCES);
708
+ }
709
+ }
710
+ accessSync satisfies typeof Node.accessSync;
711
+
712
+ /**
713
+ * Synchronous `rm`. Removes files or directories (recursively).
714
+ * @param path The path to the file or directory to remove.
715
+ */
716
+ export function rmSync(path: PathLike, options?: Node.RmOptions): void {
717
+ path = normalizePath(path);
718
+
719
+ const stats = statSync(path);
720
+
721
+ switch (stats.mode & S_IFMT) {
722
+ case S_IFDIR:
723
+ if (options?.recursive) {
724
+ for (const entry of readdirSync(path)) {
725
+ rmSync(join(path, entry));
726
+ }
727
+ }
728
+
729
+ rmdirSync(path);
730
+ return;
731
+ case S_IFREG:
732
+ case S_IFLNK:
733
+ unlinkSync(path);
734
+ return;
735
+ case S_IFBLK:
736
+ case S_IFCHR:
737
+ case S_IFIFO:
738
+ case S_IFSOCK:
739
+ default:
740
+ throw new ApiError(ErrorCode.EPERM, 'File type not supported', path, 'rm');
741
+ }
742
+ }
743
+ rmSync satisfies typeof Node.rmSync;
744
+
745
+ /**
746
+ * Synchronous `mkdtemp`. Creates a unique temporary directory.
747
+ * @param prefix The directory prefix.
748
+ * @param options The encoding (or an object including `encoding`).
749
+ * @returns The path to the created temporary directory, encoded as a string or buffer.
750
+ */
751
+ export function mkdtempSync(prefix: string, options: BufferEncodingOption): Buffer;
752
+ export function mkdtempSync(prefix: string, options?: EncodingOption): string;
753
+ export function mkdtempSync(prefix: string, options?: EncodingOption | BufferEncodingOption): string | Buffer {
754
+ const encoding = typeof options === 'object' ? options.encoding : options || 'utf8';
755
+ const fsName = `${prefix}${Date.now()}-${Math.random().toString(36).slice(2)}`;
756
+ const resolvedPath = '/tmp/' + fsName;
757
+
758
+ mkdirSync(resolvedPath);
759
+
760
+ return encoding == 'buffer' ? Buffer.from(resolvedPath) : resolvedPath;
761
+ }
762
+ mkdtempSync satisfies typeof Node.mkdtempSync;
763
+
764
+ /**
765
+ * Synchronous `copyFile`. Copies a file.
766
+ * @param src The source file.
767
+ * @param dest The destination file.
768
+ * @param flags Optional flags for the copy operation. Currently supports these flags:
769
+ * * `fs.constants.COPYFILE_EXCL`: If the destination file already exists, the operation fails.
770
+ */
771
+ export function copyFileSync(src: PathLike, dest: PathLike, flags?: number): void {
772
+ src = normalizePath(src);
773
+ dest = normalizePath(dest);
774
+
775
+ if (flags && flags & COPYFILE_EXCL && existsSync(dest)) {
776
+ throw new ApiError(ErrorCode.EEXIST, 'Destination file already exists.', dest, 'copyFile');
777
+ }
778
+
779
+ writeFileSync(dest, readFileSync(src));
780
+ }
781
+ copyFileSync satisfies typeof Node.copyFileSync;
782
+
783
+ /**
784
+ * Synchronous `readv`. Reads from a file descriptor into multiple buffers.
785
+ * @param fd The file descriptor.
786
+ * @param buffers An array of Uint8Array buffers.
787
+ * @param position The position in the file where to begin reading.
788
+ * @returns The number of bytes read.
789
+ */
790
+ export function readvSync(fd: number, buffers: readonly Uint8Array[], position?: number): number {
791
+ const file = fd2file(fd);
792
+ let bytesRead = 0;
793
+
794
+ for (const buffer of buffers) {
795
+ bytesRead += file.readSync(buffer, 0, buffer.length, position + bytesRead);
796
+ }
797
+
798
+ return bytesRead;
799
+ }
800
+ readvSync satisfies typeof Node.readvSync;
801
+
802
+ /**
803
+ * Synchronous `writev`. Writes from multiple buffers into a file descriptor.
804
+ * @param fd The file descriptor.
805
+ * @param buffers An array of Uint8Array buffers.
806
+ * @param position The position in the file where to begin writing.
807
+ * @returns The number of bytes written.
808
+ */
809
+ export function writevSync(fd: number, buffers: readonly Uint8Array[], position?: number): number {
810
+ const file = fd2file(fd);
811
+ let bytesWritten = 0;
812
+
813
+ for (const buffer of buffers) {
814
+ bytesWritten += file.writeSync(buffer, 0, buffer.length, position + bytesWritten);
815
+ }
816
+
817
+ return bytesWritten;
818
+ }
819
+ writevSync satisfies typeof Node.writevSync;
820
+
821
+ /**
822
+ * Synchronous `opendir`. Opens a directory.
823
+ * @param path The path to the directory.
824
+ * @param options Options for opening the directory.
825
+ * @returns A `Dir` object representing the opened directory.
826
+ */
827
+ export function opendirSync(path: PathLike, options?: Node.OpenDirOptions): Dir {
828
+ path = normalizePath(path);
829
+ return new Dir(path); // Re-use existing `Dir` class
830
+ }
831
+ opendirSync satisfies typeof Node.opendirSync;
832
+
833
+ /**
834
+ * Synchronous `cp`. Recursively copies a file or directory.
835
+ * @param source The source file or directory.
836
+ * @param destination The destination file or directory.
837
+ * @param opts Options for the copy operation. Currently supports these options from Node.js 'fs.cpSync':
838
+ * * `dereference`: Dereference symbolic links.
839
+ * * `errorOnExist`: Throw an error if the destination file or directory already exists.
840
+ * * `filter`: A function that takes a source and destination path and returns a boolean, indicating whether to copy the given source element.
841
+ * * `force`: Overwrite the destination if it exists, and overwrite existing readonly destination files.
842
+ * * `preserveTimestamps`: Preserve file timestamps.
843
+ * * `recursive`: If `true`, copies directories recursively.
844
+ */
845
+ export function cpSync(source: PathLike, destination: PathLike, opts?: Node.CopySyncOptions): void {
846
+ source = normalizePath(source);
847
+ destination = normalizePath(destination);
848
+
849
+ const srcStats = lstatSync(source); // Use lstat to follow symlinks if not dereferencing
850
+
851
+ if (opts?.errorOnExist && existsSync(destination)) {
852
+ throw new ApiError(ErrorCode.EEXIST, 'Destination file or directory already exists.', destination, 'cp');
853
+ }
854
+
855
+ switch (srcStats.mode & S_IFMT) {
856
+ case S_IFDIR:
857
+ if (!opts?.recursive) {
858
+ throw new ApiError(ErrorCode.EISDIR, source + ' is a directory (not copied)', source, 'cp');
859
+ }
860
+ mkdirSync(destination, { recursive: true }); // Ensure the destination directory exists
861
+ for (const dirent of readdirSync(source, { withFileTypes: true })) {
862
+ if (opts.filter && !opts.filter(join(source, dirent.name), join(destination, dirent.name))) {
863
+ continue; // Skip if the filter returns false
864
+ }
865
+ cpSync(join(source, dirent.name), join(destination, dirent.name), opts);
866
+ }
867
+ break;
868
+ case S_IFREG:
869
+ case S_IFLNK:
870
+ copyFileSync(source, destination);
871
+ break;
872
+ case S_IFBLK:
873
+ case S_IFCHR:
874
+ case S_IFIFO:
875
+ case S_IFSOCK:
876
+ default:
877
+ throw new ApiError(ErrorCode.EPERM, 'File type not supported', source, 'rm');
878
+ }
879
+
880
+ // Optionally preserve timestamps
881
+ if (opts?.preserveTimestamps) {
882
+ utimesSync(destination, srcStats.atime, srcStats.mtime);
883
+ }
884
+ }
885
+ cpSync satisfies typeof Node.cpSync;
886
+
887
+ /**
888
+ * Synchronous statfs(2). Returns information about the mounted file system which contains path.
889
+ * In case of an error, the err.code will be one of Common System Errors.
890
+ * @param path A path to an existing file or directory on the file system to be queried.
891
+ * @param callback
892
+ */
893
+ export function statfsSync(path: PathLike, options?: Node.StatFsOptions & { bigint?: false }): StatsFs;
894
+ export function statfsSync(path: PathLike, options: Node.StatFsOptions & { bigint: true }): BigIntStatsFs;
895
+ export function statfsSync(path: PathLike, options?: Node.StatFsOptions): StatsFs | BigIntStatsFs;
896
+ export function statfsSync(path: PathLike, options?: Node.StatFsOptions): StatsFs | BigIntStatsFs {
897
+ throw ApiError.With('ENOSYS', path, 'statfs');
898
+ }