UNPKG

@zenfs/core

Version:

A filesystem, anywhere

869 lines (778 loc) 32.7 kB
import { Buffer } from 'buffer'; import type * as fs from 'node:fs'; import { Errno, ErrnoError } from '../error.js'; import type { File } from '../file.js'; import { flagToMode, isAppendable, isExclusive, isReadable, isTruncating, isWriteable, parseFlag } from '../file.js'; import type { FileContents } from '../filesystem.js'; import { BigIntStats, type Stats } from '../stats.js'; import { decodeUTF8, normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js'; import * as constants from './constants.js'; import { Dir, Dirent } from './dir.js'; import { dirname, join, parse } from './path.js'; import { _statfs, fd2file, fdMap, file2fd, fixError, mounts, resolveMount, type InternalOptions, type ReaddirOptions } from './shared.js'; import { config } from './config.js'; import { emitChange } from './watchers.js'; import * as cache from './cache.js'; export function renameSync(oldPath: fs.PathLike, newPath: fs.PathLike): void { oldPath = normalizePath(oldPath); newPath = normalizePath(newPath); const oldMount = resolveMount(oldPath); const newMount = resolveMount(newPath); if (config.checkAccess && !statSync(dirname(oldPath)).hasAccess(constants.W_OK)) { throw ErrnoError.With('EACCES', oldPath, 'rename'); } try { if (oldMount === newMount) { oldMount.fs.renameSync(oldMount.path, newMount.path); emitChange('rename', oldPath.toString()); return; } writeFileSync(newPath, readFileSync(oldPath)); unlinkSync(oldPath); emitChange('rename', oldPath.toString()); } catch (e) { throw fixError(e as ErrnoError, { [oldMount.path]: oldPath, [newMount.path]: newPath }); } } renameSync satisfies typeof fs.renameSync; /** * Test whether or not `path` exists by checking with the file system. */ export function existsSync(path: fs.PathLike): boolean { path = normalizePath(path); try { const { fs, path: resolvedPath } = resolveMount(realpathSync(path)); return fs.existsSync(resolvedPath); } catch (e) { if ((e as ErrnoError).errno == Errno.ENOENT) { return false; } throw e; } } existsSync satisfies typeof fs.existsSync; export function statSync(path: fs.PathLike, options?: { bigint?: boolean }): Stats; export function statSync(path: fs.PathLike, options: { bigint: true }): BigIntStats; export function statSync(path: fs.PathLike, options?: fs.StatOptions): Stats | BigIntStats { path = normalizePath(path); const { fs, path: resolved } = resolveMount(realpathSync(path)); try { const stats = fs.statSync(resolved); if (config.checkAccess && !stats.hasAccess(constants.R_OK)) { throw ErrnoError.With('EACCES', resolved, 'stat'); } return options?.bigint ? new BigIntStats(stats) : stats; } catch (e) { throw fixError(e as ErrnoError, { [resolved]: path }); } } statSync satisfies typeof fs.statSync; /** * Synchronous `lstat`. * `lstat()` is identical to `stat()`, except that if path is a symbolic link, * then the link itself is stat-ed, not the file that it refers to. */ export function lstatSync(path: fs.PathLike, options?: { bigint?: boolean }): Stats; export function lstatSync(path: fs.PathLike, options: { bigint: true }): BigIntStats; export function lstatSync(path: fs.PathLike, options?: fs.StatOptions): Stats | BigIntStats { path = normalizePath(path); const { fs, path: resolved } = resolveMount(path); try { const stats = fs.statSync(resolved); return options?.bigint ? new BigIntStats(stats) : stats; } catch (e) { throw fixError(e as ErrnoError, { [resolved]: path }); } } lstatSync satisfies typeof fs.lstatSync; export function truncateSync(path: fs.PathLike, len: number | null = 0): void { using file = _openSync(path, 'r+'); len ||= 0; if (len < 0) { throw new ErrnoError(Errno.EINVAL); } file.truncateSync(len); } truncateSync satisfies typeof fs.truncateSync; export function unlinkSync(path: fs.PathLike): void { path = normalizePath(path); const { fs, path: resolved } = resolveMount(path); try { if (config.checkAccess && !(cache.getStats(path) || fs.statSync(resolved)).hasAccess(constants.W_OK)) { throw ErrnoError.With('EACCES', resolved, 'unlink'); } fs.unlinkSync(resolved); emitChange('rename', path.toString()); } catch (e) { throw fixError(e as ErrnoError, { [resolved]: path }); } } unlinkSync satisfies typeof fs.unlinkSync; function _openSync(path: fs.PathLike, _flag: fs.OpenMode, _mode?: fs.Mode | null, resolveSymlinks: boolean = true): File { path = normalizePath(path); const mode = normalizeMode(_mode, 0o644), flag = parseFlag(_flag); path = resolveSymlinks ? realpathSync(path) : path; const { fs, path: resolved } = resolveMount(path); let stats: Stats | undefined; try { stats = fs.statSync(resolved); } catch { // nothing } if (!stats) { if ((!isWriteable(flag) && !isAppendable(flag)) || flag == 'r+') { throw ErrnoError.With('ENOENT', path, '_open'); } // Create the file const parentStats: Stats = fs.statSync(dirname(resolved)); if (config.checkAccess && !parentStats.hasAccess(constants.W_OK)) { throw ErrnoError.With('EACCES', dirname(path), '_open'); } if (!parentStats.isDirectory()) { throw ErrnoError.With('ENOTDIR', dirname(path), '_open'); } return fs.createFileSync(resolved, flag, mode); } if (config.checkAccess && (!stats.hasAccess(mode) || !stats.hasAccess(flagToMode(flag)))) { throw ErrnoError.With('EACCES', path, '_open'); } if (isExclusive(flag)) { throw ErrnoError.With('EEXIST', path, '_open'); } const file = fs.openFileSync(resolved, flag); if (isTruncating(flag)) { file.truncateSync(0); file.syncSync(); } return file; } /** * Synchronous file open. * @see http://www.manpagez.com/man/2/open/ */ export function openSync(path: fs.PathLike, flag: fs.OpenMode, mode: fs.Mode | null = constants.F_OK): number { return file2fd(_openSync(path, flag, mode, true)); } openSync satisfies typeof fs.openSync; /** * Opens a file or symlink * @internal */ export function lopenSync(path: fs.PathLike, flag: string, mode?: fs.Mode | null): number { return file2fd(_openSync(path, flag, mode, false)); } function _readFileSync(fname: string, flag: string, resolveSymlinks: boolean): Uint8Array { // Get file. using file = _openSync(fname, flag, 0o644, resolveSymlinks); const stat = file.statSync(); // Allocate buffer. const data = new Uint8Array(stat.size); file.readSync(data, 0, stat.size, 0); return data; } /** * Synchronously reads the entire contents of a file. * @option encoding The string encoding for the file contents. Defaults to `null`. * @option flag Defaults to `'r'`. * @returns file contents */ export function readFileSync(path: fs.PathOrFileDescriptor, options?: { flag?: string } | null): Buffer; export function readFileSync(path: fs.PathOrFileDescriptor, options?: (fs.EncodingOption & { flag?: string }) | BufferEncoding | null): string; export function readFileSync(path: fs.PathOrFileDescriptor, _options: fs.WriteFileOptions | null = {}): FileContents { const options = normalizeOptions(_options, null, 'r', 0o644); const flag = parseFlag(options.flag); if (!isReadable(flag)) { throw new ErrnoError(Errno.EINVAL, 'Flag passed to readFile must allow for reading.'); } const data: Buffer = Buffer.from(_readFileSync(typeof path == 'number' ? fd2file(path).path : path.toString(), options.flag, true)); return options.encoding ? data.toString(options.encoding) : data; } readFileSync satisfies typeof fs.readFileSync; /** * Synchronously writes data to a file, replacing the file if it already exists. * * The encoding option is ignored if data is a buffer. * @option encoding Defaults to `'utf8'`. * @option mode Defaults to `0644`. * @option flag Defaults to `'w'`. */ export function writeFileSync(path: fs.PathOrFileDescriptor, data: FileContents, options?: fs.WriteFileOptions): void; export function writeFileSync(path: fs.PathOrFileDescriptor, data: FileContents, encoding?: BufferEncoding): void; export function writeFileSync(path: fs.PathOrFileDescriptor, data: FileContents, _options: fs.WriteFileOptions | BufferEncoding = {}): void { const options = normalizeOptions(_options, 'utf8', 'w+', 0o644); const flag = parseFlag(options.flag); if (!isWriteable(flag)) { throw new ErrnoError(Errno.EINVAL, 'Flag passed to writeFile must allow for writing.'); } if (typeof data != 'string' && !options.encoding) { throw new ErrnoError(Errno.EINVAL, 'Encoding not specified'); } const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding!) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength); if (!encodedData) { throw new ErrnoError(Errno.EINVAL, 'Data not specified'); } using file = _openSync(typeof path == 'number' ? fd2file(path).path : path.toString(), flag, options.mode, true); file.writeSync(encodedData, 0, encodedData.byteLength, 0); emitChange('change', path.toString()); } writeFileSync satisfies typeof fs.writeFileSync; /** * Asynchronously append data to a file, creating the file if it not yet exists. * @option encoding Defaults to `'utf8'`. * @option mode Defaults to `0644`. * @option flag Defaults to `'a'`. */ export function appendFileSync(filename: fs.PathOrFileDescriptor, data: FileContents, _options: fs.WriteFileOptions = {}): void { const options = normalizeOptions(_options, 'utf8', 'a', 0o644); const flag = parseFlag(options.flag); if (!isAppendable(flag)) { throw new ErrnoError(Errno.EINVAL, 'Flag passed to appendFile must allow for appending.'); } if (typeof data != 'string' && !options.encoding) { throw new ErrnoError(Errno.EINVAL, 'Encoding not specified'); } const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding!) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength); using file = _openSync(typeof filename == 'number' ? fd2file(filename).path : filename.toString(), flag, options.mode, true); file.writeSync(encodedData, 0, encodedData.byteLength); } appendFileSync satisfies typeof fs.appendFileSync; /** * Synchronous `fstat`. * `fstat()` is identical to `stat()`, except that the file to be stat-ed is * specified by the file descriptor `fd`. */ export function fstatSync(fd: number, options?: { bigint?: boolean }): Stats; export function fstatSync(fd: number, options: { bigint: true }): BigIntStats; export function fstatSync(fd: number, options?: fs.StatOptions): Stats | BigIntStats { const stats: Stats = fd2file(fd).statSync(); return options?.bigint ? new BigIntStats(stats) : stats; } fstatSync satisfies typeof fs.fstatSync; export function closeSync(fd: number): void { fd2file(fd).closeSync(); fdMap.delete(fd); } closeSync satisfies typeof fs.closeSync; export function ftruncateSync(fd: number, len: number | null = 0): void { len ||= 0; if (len < 0) { throw new ErrnoError(Errno.EINVAL); } fd2file(fd).truncateSync(len); } ftruncateSync satisfies typeof fs.ftruncateSync; export function fsyncSync(fd: number): void { fd2file(fd).syncSync(); } fsyncSync satisfies typeof fs.fsyncSync; export function fdatasyncSync(fd: number): void { fd2file(fd).datasyncSync(); } fdatasyncSync satisfies typeof fs.fdatasyncSync; /** * Write buffer to the file specified by `fd`. * @param data Uint8Array containing the data to write to the file. * @param offset Offset in the buffer to start reading data from. * @param length The amount of bytes to write to the file. * @param position Offset from the beginning of the file where this data should be written. * If position is null, the data will be written at the current position. */ export function writeSync(fd: number, data: ArrayBufferView, offset?: number | null, length?: number | null, position?: number | null): number; export function writeSync(fd: number, data: string, position?: number | null, encoding?: BufferEncoding | null): number; export function writeSync(fd: number, data: FileContents, posOrOff?: number | null, lenOrEnc?: BufferEncoding | number | null, pos?: number | null): number { let buffer: Uint8Array, offset: number | undefined, length: number, position: number | null; if (typeof data === 'string') { // Signature 1: (fd, string, [position?, [encoding?]]) position = typeof posOrOff === 'number' ? posOrOff : null; const encoding = typeof lenOrEnc === 'string' ? lenOrEnc : ('utf8' as BufferEncoding); offset = 0; buffer = Buffer.from(data, encoding); length = buffer.byteLength; } else { // Signature 2: (fd, buffer, offset, length, position?) buffer = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); offset = posOrOff!; length = lenOrEnc as number; position = typeof pos === 'number' ? pos : null; } const file = fd2file(fd); position ??= file.position; const bytesWritten = file.writeSync(buffer, offset, length, position); emitChange('change', file.path); return bytesWritten; } writeSync satisfies typeof fs.writeSync; export function readSync(fd: number, buffer: ArrayBufferView, options?: fs.ReadSyncOptions): number; export function readSync(fd: number, buffer: ArrayBufferView, offset: number, length: number, position?: fs.ReadPosition | null): number; /** * Read data from the file specified by `fd`. * @param buffer The buffer that the data will be written to. * @param offset The offset within the buffer where writing will start. * @param length An integer specifying the number of bytes to read. * @param position An integer specifying where to begin reading from in the file. * If position is null, data will be read from the current file position. */ export function readSync(fd: number, buffer: ArrayBufferView, options?: fs.ReadSyncOptions | number, length?: number, position?: fs.ReadPosition | null): number { const file = fd2file(fd); const offset = typeof options == 'object' ? options.offset : options; if (typeof options == 'object') { length = options.length; position = options.position; } position = Number(position); if (isNaN(position)) { position = file.position!; } return file.readSync(buffer, offset, length, position); } readSync satisfies typeof fs.readSync; export function fchownSync(fd: number, uid: number, gid: number): void { fd2file(fd).chownSync(uid, gid); } fchownSync satisfies typeof fs.fchownSync; export function fchmodSync(fd: number, mode: number | string): void { const numMode = normalizeMode(mode, -1); if (numMode < 0) { throw new ErrnoError(Errno.EINVAL, `Invalid mode.`); } fd2file(fd).chmodSync(numMode); } fchmodSync satisfies typeof fs.fchmodSync; /** * Change the file timestamps of a file referenced by the supplied file descriptor. */ export function futimesSync(fd: number, atime: string | number | Date, mtime: string | number | Date): void { fd2file(fd).utimesSync(normalizeTime(atime), normalizeTime(mtime)); } futimesSync satisfies typeof fs.futimesSync; export function rmdirSync(path: fs.PathLike): void { path = normalizePath(path); const { fs, path: resolved } = resolveMount(realpathSync(path)); try { const stats = cache.getStats(path) || fs.statSync(resolved); if (!stats.isDirectory()) { throw ErrnoError.With('ENOTDIR', resolved, 'rmdir'); } if (config.checkAccess && !stats.hasAccess(constants.W_OK)) { throw ErrnoError.With('EACCES', resolved, 'rmdir'); } fs.rmdirSync(resolved); emitChange('rename', path.toString()); } catch (e) { throw fixError(e as ErrnoError, { [resolved]: path }); } } rmdirSync satisfies typeof fs.rmdirSync; /** * Synchronous `mkdir`. Mode defaults to `o777`. */ export function mkdirSync(path: fs.PathLike, options: fs.MakeDirectoryOptions & { recursive: true }): string | undefined; export function mkdirSync(path: fs.PathLike, options?: fs.Mode | (fs.MakeDirectoryOptions & { recursive?: false }) | null): void; export function mkdirSync(path: fs.PathLike, options?: fs.Mode | fs.MakeDirectoryOptions | null): string | undefined; export function mkdirSync(path: fs.PathLike, options?: fs.Mode | fs.MakeDirectoryOptions | null): string | undefined | void { options = typeof options === 'object' ? options : { mode: options }; const mode = normalizeMode(options?.mode, 0o777); path = realpathSync(path); const { fs, path: resolved } = resolveMount(path); const errorPaths: Record<string, string> = { [resolved]: path }; try { if (!options?.recursive) { if (config.checkAccess && !fs.statSync(dirname(resolved)).hasAccess(constants.W_OK)) { throw ErrnoError.With('EACCES', dirname(resolved), 'mkdir'); } return fs.mkdirSync(resolved, mode); } const dirs: string[] = []; for (let dir = resolved, original = path; !fs.existsSync(dir); dir = dirname(dir), original = dirname(original)) { dirs.unshift(dir); errorPaths[dir] = original; } for (const dir of dirs) { if (config.checkAccess && !fs.statSync(dirname(dir)).hasAccess(constants.W_OK)) { throw ErrnoError.With('EACCES', dirname(dir), 'mkdir'); } fs.mkdirSync(dir, mode); emitChange('rename', dir); } return dirs[0]; } catch (e) { throw fixError(e as ErrnoError, errorPaths); } } mkdirSync satisfies typeof fs.mkdirSync; export function readdirSync(path: fs.PathLike, options?: (fs.ObjectEncodingOptions & ReaddirOptions & { withFileTypes?: false }) | BufferEncoding | null): string[]; export function readdirSync(path: fs.PathLike, options: fs.BufferEncodingOption & ReaddirOptions & { withFileTypes?: false }): Buffer[]; export function readdirSync(path: fs.PathLike, options?: (fs.ObjectEncodingOptions & ReaddirOptions & { withFileTypes?: false }) | BufferEncoding | null): string[] | Buffer[]; export function readdirSync(path: fs.PathLike, options: fs.ObjectEncodingOptions & ReaddirOptions & { withFileTypes: true }): Dirent[]; export function readdirSync( path: fs.PathLike, options?: (ReaddirOptions & (fs.ObjectEncodingOptions | fs.BufferEncodingOption)) | BufferEncoding | null ): string[] | Dirent[] | Buffer[]; export function readdirSync( path: fs.PathLike, options?: (ReaddirOptions & (fs.ObjectEncodingOptions | fs.BufferEncodingOption)) | BufferEncoding | null ): string[] | Dirent[] | Buffer[] { options = typeof options === 'object' ? options : { encoding: options }; path = normalizePath(path); const { fs, path: resolved } = resolveMount(realpathSync(path)); let entries: string[]; try { const stats = cache.getStats(path) || fs.statSync(resolved); cache.setStats(path, stats); if (config.checkAccess && !stats.hasAccess(constants.R_OK)) { throw ErrnoError.With('EACCES', resolved, 'readdir'); } if (!stats.isDirectory()) { throw ErrnoError.With('ENOTDIR', resolved, 'readdir'); } entries = fs.readdirSync(resolved); } catch (e) { throw fixError(e as ErrnoError, { [resolved]: path }); } for (const mount of mounts.keys()) { if (!mount.startsWith(path)) { continue; } const entry = mount.slice(path.length); if (entry.includes('/') || entry.length == 0) { // ignore FSs mounted in subdirectories and any FS mounted to `path`. continue; } entries.push(entry); } // Iterate over entries and handle recursive case if needed const values: (string | Dirent | Buffer)[] = []; for (const entry of entries) { const entryStat = cache.getStats(join(path, entry)) || fs.statSync(join(resolved, entry)); cache.setStats(join(path, entry), entryStat); if (options?.withFileTypes) { values.push(new Dirent(entry, entryStat)); } else if (options?.encoding == 'buffer') { values.push(Buffer.from(entry)); } else { values.push(entry); } if (!entryStat.isDirectory() || !options?.recursive) continue; for (const subEntry of readdirSync(join(path, entry), { ...options, _isIndirect: true })) { if (subEntry instanceof Dirent) { subEntry.path = join(entry, subEntry.path); values.push(subEntry); } else if (Buffer.isBuffer(subEntry)) { values.push(Buffer.from(join(entry, decodeUTF8(subEntry)))); } else { values.push(join(entry, subEntry)); } } } if (!options?._isIndirect) { cache.clearStats(); } return values as string[] | Dirent[] | Buffer[]; } readdirSync satisfies typeof fs.readdirSync; // SYMLINK METHODS export function linkSync(targetPath: fs.PathLike, linkPath: fs.PathLike): void { targetPath = normalizePath(targetPath); if (config.checkAccess && !statSync(dirname(targetPath)).hasAccess(constants.R_OK)) { throw ErrnoError.With('EACCES', dirname(targetPath), 'link'); } linkPath = normalizePath(linkPath); if (config.checkAccess && !statSync(dirname(linkPath)).hasAccess(constants.W_OK)) { throw ErrnoError.With('EACCES', dirname(linkPath), 'link'); } const { fs, path } = resolveMount(targetPath); const link = resolveMount(linkPath); if (fs != link.fs) { throw ErrnoError.With('EXDEV', linkPath, 'link'); } try { if (config.checkAccess && !fs.statSync(path).hasAccess(constants.W_OK)) { throw ErrnoError.With('EACCES', path, 'link'); } return fs.linkSync(path, linkPath); } catch (e) { throw fixError(e as ErrnoError, { [path]: targetPath, [link.path]: linkPath }); } } linkSync satisfies typeof fs.linkSync; /** * Synchronous `symlink`. * @param target target path * @param path link path * @param type can be either `'dir'` or `'file'` (default is `'file'`) */ export function symlinkSync(target: fs.PathLike, path: fs.PathLike, type: fs.symlink.Type | null = 'file'): void { if (!['file', 'dir', 'junction'].includes(type!)) { throw new ErrnoError(Errno.EINVAL, 'Invalid type: ' + type); } if (existsSync(path)) { throw ErrnoError.With('EEXIST', path.toString(), 'symlink'); } writeFileSync(path, target.toString()); const file = _openSync(path, 'r+', 0o644, false); file._setTypeSync(constants.S_IFLNK); } symlinkSync satisfies typeof fs.symlinkSync; export function readlinkSync(path: fs.PathLike, options?: fs.BufferEncodingOption): Buffer; export function readlinkSync(path: fs.PathLike, options: fs.EncodingOption | BufferEncoding): string; export function readlinkSync(path: fs.PathLike, options?: fs.EncodingOption | BufferEncoding | fs.BufferEncodingOption): Buffer | string; export function readlinkSync(path: fs.PathLike, options?: fs.EncodingOption | BufferEncoding | fs.BufferEncodingOption): Buffer | string { const value: Buffer = Buffer.from(_readFileSync(path.toString(), 'r', false)); const encoding = typeof options == 'object' ? options?.encoding : options; if (encoding == 'buffer') { return value; } return value.toString(encoding!); } readlinkSync satisfies typeof fs.readlinkSync; // PROPERTY OPERATIONS export function chownSync(path: fs.PathLike, uid: number, gid: number): void { const fd = openSync(path, 'r+'); fchownSync(fd, uid, gid); closeSync(fd); } chownSync satisfies typeof fs.chownSync; export function lchownSync(path: fs.PathLike, uid: number, gid: number): void { const fd = lopenSync(path, 'r+'); fchownSync(fd, uid, gid); closeSync(fd); } lchownSync satisfies typeof fs.lchownSync; export function chmodSync(path: fs.PathLike, mode: fs.Mode): void { const fd = openSync(path, 'r+'); fchmodSync(fd, mode); closeSync(fd); } chmodSync satisfies typeof fs.chmodSync; export function lchmodSync(path: fs.PathLike, mode: number | string): void { const fd = lopenSync(path, 'r+'); fchmodSync(fd, mode); closeSync(fd); } lchmodSync satisfies typeof fs.lchmodSync; /** * Change file timestamps of the file referenced by the supplied path. */ export function utimesSync(path: fs.PathLike, atime: string | number | Date, mtime: string | number | Date): void { const fd = openSync(path, 'r+'); futimesSync(fd, atime, mtime); closeSync(fd); } utimesSync satisfies typeof fs.utimesSync; /** * Change file timestamps of the file referenced by the supplied path. */ export function lutimesSync(path: fs.PathLike, atime: string | number | Date, mtime: string | number | Date): void { const fd = lopenSync(path, 'r+'); futimesSync(fd, atime, mtime); closeSync(fd); } lutimesSync satisfies typeof fs.lutimesSync; export function realpathSync(path: fs.PathLike, options: fs.BufferEncodingOption): Buffer; export function realpathSync(path: fs.PathLike, options?: fs.EncodingOption): string; export function realpathSync(path: fs.PathLike, options?: fs.EncodingOption | fs.BufferEncodingOption): string | Buffer { path = normalizePath(path); const { base, dir } = parse(path); const lpath = join(dir == '/' ? '/' : realpathSync(dir), base); const { fs, path: resolvedPath, mountPoint } = resolveMount(lpath); try { const stats = fs.statSync(resolvedPath); if (!stats.isSymbolicLink()) { return lpath; } return realpathSync(mountPoint + readlinkSync(lpath, options).toString()); } catch (e) { if ((e as ErrnoError).code == 'ENOENT') { return path; } throw fixError(e as ErrnoError, { [resolvedPath]: lpath }); } } realpathSync satisfies Omit<typeof fs.realpathSync, 'native'>; export function accessSync(path: fs.PathLike, mode: number = 0o600): void { if (!config.checkAccess) return; if (!statSync(path).hasAccess(mode)) { throw new ErrnoError(Errno.EACCES); } } accessSync satisfies typeof fs.accessSync; /** * Synchronous `rm`. Removes files or directories (recursively). * @param path The path to the file or directory to remove. */ export function rmSync(path: fs.PathLike, options?: fs.RmOptions & InternalOptions): void { path = normalizePath(path); let stats: Stats | undefined; try { stats = cache.getStats(path) || statSync(path); } catch (error) { if ((error as ErrnoError).code != 'ENOENT' || !options?.force) throw error; } if (!stats) { return; } cache.setStats(path, stats); switch (stats.mode & constants.S_IFMT) { case constants.S_IFDIR: if (options?.recursive) { for (const entry of readdirSync(path, { _isIndirect: true })) { rmSync(join(path, entry), { ...options, _isIndirect: true }); } } rmdirSync(path); break; case constants.S_IFREG: case constants.S_IFLNK: unlinkSync(path); break; case constants.S_IFBLK: case constants.S_IFCHR: case constants.S_IFIFO: case constants.S_IFSOCK: default: cache.clearStats(); throw new ErrnoError(Errno.EPERM, 'File type not supported', path, 'rm'); } if (!options?._isIndirect) { cache.clearStats(); } } rmSync satisfies typeof fs.rmSync; /** * Synchronous `mkdtemp`. Creates a unique temporary directory. * @param prefix The directory prefix. * @param options The encoding (or an object including `encoding`). * @returns The path to the created temporary directory, encoded as a string or buffer. */ export function mkdtempSync(prefix: string, options: fs.BufferEncodingOption): Buffer; export function mkdtempSync(prefix: string, options?: fs.EncodingOption): string; export function mkdtempSync(prefix: string, options?: fs.EncodingOption | fs.BufferEncodingOption): string | Buffer { const encoding = typeof options === 'object' ? options?.encoding : options || 'utf8'; const fsName = `${prefix}${Date.now()}-${Math.random().toString(36).slice(2)}`; const resolvedPath = '/tmp/' + fsName; mkdirSync(resolvedPath); return encoding == 'buffer' ? Buffer.from(resolvedPath) : resolvedPath; } mkdtempSync satisfies typeof fs.mkdtempSync; /** * Synchronous `copyFile`. Copies a file. * @param flags Optional flags for the copy operation. Currently supports these flags: * - `fs.constants.COPYFILE_EXCL`: If the destination file already exists, the operation fails. */ export function copyFileSync(source: fs.PathLike, destination: fs.PathLike, flags?: number): void { source = normalizePath(source); destination = normalizePath(destination); if (flags && flags & constants.COPYFILE_EXCL && existsSync(destination)) { throw new ErrnoError(Errno.EEXIST, 'Destination file already exists.', destination, 'copyFile'); } writeFileSync(destination, readFileSync(source)); emitChange('rename', destination.toString()); } copyFileSync satisfies typeof fs.copyFileSync; /** * Synchronous `readv`. Reads from a file descriptor into multiple buffers. * @param fd The file descriptor. * @param buffers An array of Uint8Array buffers. * @param position The position in the file where to begin reading. * @returns The number of bytes read. */ export function readvSync(fd: number, buffers: readonly NodeJS.ArrayBufferView[], position?: number): number { const file = fd2file(fd); let bytesRead = 0; for (const buffer of buffers) { bytesRead += file.readSync(buffer, 0, buffer.byteLength, position! + bytesRead); } return bytesRead; } readvSync satisfies typeof fs.readvSync; /** * Synchronous `writev`. Writes from multiple buffers into a file descriptor. * @param fd The file descriptor. * @param buffers An array of Uint8Array buffers. * @param position The position in the file where to begin writing. * @returns The number of bytes written. */ export function writevSync(fd: number, buffers: readonly ArrayBufferView[], position?: number): number { const file = fd2file(fd); let bytesWritten = 0; for (const buffer of buffers) { bytesWritten += file.writeSync(new Uint8Array(buffer.buffer), 0, buffer.byteLength, position! + bytesWritten); } return bytesWritten; } writevSync satisfies typeof fs.writevSync; /** * Synchronous `opendir`. Opens a directory. * @param path The path to the directory. * @param options Options for opening the directory. * @returns A `Dir` object representing the opened directory. * @todo Handle options */ export function opendirSync(path: fs.PathLike, options?: fs.OpenDirOptions): Dir { path = normalizePath(path); return new Dir(path); } opendirSync satisfies typeof fs.opendirSync; /** * Synchronous `cp`. Recursively copies a file or directory. * @param source The source file or directory. * @param destination The destination file or directory. * @param opts Options for the copy operation. Currently supports these options from Node.js 'fs.cpSync': * - `dereference`: Dereference symbolic links. *(unconfirmed)* * - `errorOnExist`: Throw an error if the destination file or directory already exists. * - `filter`: A function that takes a source and destination path and returns a boolean, indicating whether to copy `source` element. * - `force`: Overwrite the destination if it exists, and overwrite existing readonly destination files. *(unconfirmed)* * - `preserveTimestamps`: Preserve file timestamps. * - `recursive`: If `true`, copies directories recursively. */ export function cpSync(source: fs.PathLike, destination: fs.PathLike, opts?: fs.CopySyncOptions): void { source = normalizePath(source); destination = normalizePath(destination); const srcStats = lstatSync(source); // Use lstat to follow symlinks if not dereferencing if (opts?.errorOnExist && existsSync(destination)) { throw new ErrnoError(Errno.EEXIST, 'Destination file or directory already exists.', destination, 'cp'); } switch (srcStats.mode & constants.S_IFMT) { case constants.S_IFDIR: if (!opts?.recursive) { throw new ErrnoError(Errno.EISDIR, source + ' is a directory (not copied)', source, 'cp'); } mkdirSync(destination, { recursive: true }); // Ensure the destination directory exists for (const dirent of readdirSync(source, { withFileTypes: true })) { if (opts.filter && !opts.filter(join(source, dirent.name), join(destination, dirent.name))) { continue; // Skip if the filter returns false } cpSync(join(source, dirent.name), join(destination, dirent.name), opts); } break; case constants.S_IFREG: case constants.S_IFLNK: copyFileSync(source, destination); break; case constants.S_IFBLK: case constants.S_IFCHR: case constants.S_IFIFO: case constants.S_IFSOCK: default: throw new ErrnoError(Errno.EPERM, 'File type not supported', source, 'rm'); } // Optionally preserve timestamps if (opts?.preserveTimestamps) { utimesSync(destination, srcStats.atime, srcStats.mtime); } } cpSync satisfies typeof fs.cpSync; /** * Synchronous statfs(2). Returns information about the mounted file system which contains path. * In case of an error, the err.code will be one of Common System Errors. * @param path A path to an existing file or directory on the file system to be queried. */ export function statfsSync(path: fs.PathLike, options?: fs.StatFsOptions & { bigint?: false }): fs.StatsFs; export function statfsSync(path: fs.PathLike, options: fs.StatFsOptions & { bigint: true }): fs.BigIntStatsFs; export function statfsSync(path: fs.PathLike, options?: fs.StatFsOptions): fs.StatsFs | fs.BigIntStatsFs; export function statfsSync(path: fs.PathLike, options?: fs.StatFsOptions): fs.StatsFs | fs.BigIntStatsFs { path = normalizePath(path); const { fs } = resolveMount(path); return _statfs(fs, options?.bigint); }