@zenfs/core
Advanced tools
Comparing version 1.1.6 to 1.2.0
@@ -154,5 +154,2 @@ /* Note: this file is named file_index.ts because Typescript has special behavior regarding index.ts which can't be disabled. */ | ||
} | ||
if (!stats.isDirectory()) { | ||
throw ErrnoError.With('ENOTDIR', path, 'readdir'); | ||
} | ||
const content = JSON.parse(decodeUTF8(stats.fileData)); | ||
@@ -159,0 +156,0 @@ if (!Array.isArray(content)) { |
@@ -317,6 +317,2 @@ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { | ||
this.checkInitialized(); | ||
const dirStats = await this.stat(path); | ||
if (!dirStats.isDirectory()) { | ||
throw ErrnoError.With('ENOTDIR', path, 'readdir'); | ||
} | ||
// Readdir in both, check delete log on RO file system's listing, merge, return. | ||
@@ -345,6 +341,2 @@ const contents = []; | ||
this.checkInitialized(); | ||
const dirStats = this.statSync(path); | ||
if (!dirStats.isDirectory()) { | ||
throw ErrnoError.With('ENOTDIR', path, 'readdir'); | ||
} | ||
// Readdir in both, check delete log on RO file system's listing, merge, return. | ||
@@ -351,0 +343,0 @@ let contents = []; |
@@ -583,12 +583,8 @@ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { | ||
async getDirListing(tx, inode, path) { | ||
if (!inode.toStats().isDirectory()) { | ||
throw ErrnoError.With('ENOTDIR', path, 'getDirListing'); | ||
} | ||
const data = await tx.get(inode.ino); | ||
/* | ||
Occurs when data is undefined,or corresponds to something other than a directory listing. | ||
The latter should never occur unless the file system is corrupted. | ||
*/ | ||
if (!data) { | ||
/* | ||
Occurs when data is undefined, or corresponds to something other | ||
than a directory listing. The latter should never occur unless | ||
the file system is corrupted. | ||
*/ | ||
throw ErrnoError.With('ENOENT', path, 'getDirListing'); | ||
@@ -602,5 +598,2 @@ } | ||
getDirListingSync(tx, inode, p) { | ||
if (!inode.toStats().isDirectory()) { | ||
throw ErrnoError.With('ENOTDIR', p, 'getDirListing'); | ||
} | ||
const data = tx.getSync(inode.ino); | ||
@@ -763,5 +756,2 @@ if (!data) { | ||
} | ||
if (isDir && !fileNode.toStats().isDirectory()) { | ||
throw ErrnoError.With('ENOTDIR', path, 'remove'); | ||
} | ||
await tx.set(parentNode.ino, encodeDirListing(listing)); | ||
@@ -807,5 +797,2 @@ if (--fileNode.nlink < 1) { | ||
} | ||
if (isDir && !fileNode.toStats().isDirectory()) { | ||
throw ErrnoError.With('ENOTDIR', path, 'remove'); | ||
} | ||
// Update directory listing. | ||
@@ -812,0 +799,0 @@ tx.setSync(parentNode.ino, encodeDirListing(listing)); |
@@ -41,2 +41,16 @@ import type { Backend, BackendConfiguration, FilesystemOf, SharedConfig } from './backends/backend.js'; | ||
addDevices: boolean; | ||
/** | ||
* If true, enables caching stats for certain operations. | ||
* This should reduce the number of stat calls performed. | ||
* @default false | ||
* @experimental | ||
*/ | ||
cacheStats: boolean; | ||
/** | ||
* If true, disables *all* permissions checking. | ||
* This can increase performance | ||
* @default false | ||
* @experimental | ||
*/ | ||
disableAccessChecks: boolean; | ||
} | ||
@@ -43,0 +57,0 @@ /** |
import { checkOptions, isBackend, isBackendConfig } from './backends/backend.js'; | ||
import { credentials } from './credentials.js'; | ||
import { DeviceFS, fullDevice, nullDevice, randomDevice, zeroDevice } from './devices.js'; | ||
import * as cache from './emulation/cache.js'; | ||
import * as fs from './emulation/index.js'; | ||
import { config } from './emulation/shared.js'; | ||
import { Errno, ErrnoError } from './error.js'; | ||
@@ -69,2 +71,4 @@ import { FileSystem } from './filesystem.js'; | ||
Object.assign(credentials, { uid, gid, suid: uid, sgid: gid, euid: uid, egid: gid }); | ||
cache.setEnabled(configuration.cacheStats ?? false); | ||
config.checkAccess = !configuration.disableAccessChecks; | ||
if (configuration.addDevices) { | ||
@@ -71,0 +75,0 @@ const devfs = new DeviceFS(); |
@@ -257,11 +257,5 @@ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { | ||
async rmdir(path) { | ||
if (this.devices.has(path)) { | ||
throw ErrnoError.With('ENOTDIR', path, 'rmdir'); | ||
} | ||
return super.rmdir(path); | ||
} | ||
rmdirSync(path) { | ||
if (this.devices.has(path)) { | ||
throw ErrnoError.With('ENOTDIR', path, 'rmdir'); | ||
} | ||
return super.rmdirSync(path); | ||
@@ -282,5 +276,2 @@ } | ||
async readdir(path) { | ||
if (this.devices.has(path)) { | ||
throw ErrnoError.With('ENOTDIR', path, 'readdir'); | ||
} | ||
const entries = await super.readdir(path); | ||
@@ -295,5 +286,2 @@ for (const dev of this.devices.keys()) { | ||
readdirSync(path) { | ||
if (this.devices.has(path)) { | ||
throw ErrnoError.With('ENOTDIR', path, 'readdirSync'); | ||
} | ||
const entries = super.readdirSync(path); | ||
@@ -300,0 +288,0 @@ for (const dev of this.devices.keys()) { |
@@ -12,2 +12,3 @@ import { Buffer } from 'buffer'; | ||
import { Dir, Dirent } from './dir.js'; | ||
import { type InternalOptions, type ReaddirOptions } from './shared.js'; | ||
import { ReadStream, WriteStream } from './streams.js'; | ||
@@ -252,26 +253,20 @@ export * as constants from './constants.js'; | ||
* Asynchronous readdir(3) - read a directory. | ||
* | ||
* Note: The order of entries is not guaranteed | ||
* @param path A path to a file. If a URL is provided, it must use the `file:` protocol. | ||
* @param options The encoding (or an object specifying the encoding), used as the encoding of the result. If not provided, `'utf8'`. | ||
*/ | ||
export declare function readdir(path: fs.PathLike, options?: (fs.ObjectEncodingOptions & { | ||
export declare function readdir(path: fs.PathLike, options?: (fs.ObjectEncodingOptions & ReaddirOptions & { | ||
withFileTypes?: false; | ||
recursive?: boolean; | ||
}) | BufferEncoding | null): Promise<string[]>; | ||
export declare function readdir(path: fs.PathLike, options: fs.BufferEncodingOption & { | ||
export declare function readdir(path: fs.PathLike, options: fs.BufferEncodingOption & ReaddirOptions & { | ||
withFileTypes?: false; | ||
recursive?: boolean; | ||
}): Promise<Buffer[]>; | ||
export declare function readdir(path: fs.PathLike, options?: (fs.ObjectEncodingOptions & { | ||
export declare function readdir(path: fs.PathLike, options?: (fs.ObjectEncodingOptions & ReaddirOptions & { | ||
withFileTypes?: false; | ||
recursive?: boolean; | ||
}) | BufferEncoding | null): Promise<string[] | Buffer[]>; | ||
export declare function readdir(path: fs.PathLike, options: fs.ObjectEncodingOptions & { | ||
export declare function readdir(path: fs.PathLike, options: fs.ObjectEncodingOptions & ReaddirOptions & { | ||
withFileTypes: true; | ||
recursive?: boolean; | ||
}): Promise<Dirent[]>; | ||
export declare function readdir(path: fs.PathLike, options?: { | ||
withFileTypes?: boolean; | ||
recursive?: boolean; | ||
encoding?: BufferEncoding | 'buffer' | null; | ||
} | BufferEncoding | 'buffer' | null): Promise<string[] | Dirent[] | Buffer[]>; | ||
export declare function readdir(path: fs.PathLike, options?: (ReaddirOptions & (fs.ObjectEncodingOptions | fs.BufferEncodingOption)) | BufferEncoding | null): Promise<string[] | Dirent[] | Buffer[]>; | ||
export declare function link(targetPath: fs.PathLike, linkPath: fs.PathLike): Promise<void>; | ||
@@ -316,3 +311,3 @@ /** | ||
*/ | ||
export declare function rm(path: fs.PathLike, options?: fs.RmOptions): Promise<void>; | ||
export declare function rm(path: fs.PathLike, options?: fs.RmOptions & InternalOptions): Promise<void>; | ||
/** | ||
@@ -319,0 +314,0 @@ * Asynchronous `mkdtemp`. Creates a unique temporary directory. |
@@ -55,6 +55,7 @@ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { | ||
import { decodeUTF8, normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js'; | ||
import * as cache from './cache.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 } from './shared.js'; | ||
import { _statfs, config, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js'; | ||
import { ReadStream, WriteStream } from './streams.js'; | ||
@@ -221,3 +222,3 @@ import { FSWatcher, emitChange } from './watchers.js'; | ||
const stats = await this.file.stat(); | ||
if (!stats.hasAccess(constants.R_OK)) { | ||
if (config.checkAccess && !stats.hasAccess(constants.R_OK)) { | ||
throw ErrnoError.With('EACCES', this.file.path, 'stat'); | ||
@@ -356,3 +357,3 @@ } | ||
const dst = resolveMount(newPath); | ||
if (!(await stat(dirname(oldPath))).hasAccess(constants.W_OK)) { | ||
if (config.checkAccess && !(await stat(dirname(oldPath))).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', oldPath, 'rename'); | ||
@@ -395,3 +396,3 @@ } | ||
const stats = await fs.stat(resolved); | ||
if (!stats.hasAccess(constants.R_OK)) { | ||
if (config.checkAccess && !stats.hasAccess(constants.R_OK)) { | ||
throw ErrnoError.With('EACCES', resolved, 'stat'); | ||
@@ -440,3 +441,3 @@ } | ||
try { | ||
if (!(await fs.stat(resolved)).hasAccess(constants.W_OK)) { | ||
if (config.checkAccess && !(cache.getStats(path) || (await fs.stat(resolved))).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', resolved, 'unlink'); | ||
@@ -468,3 +469,3 @@ } | ||
const parentStats = await fs.stat(dirname(resolved)); | ||
if (!parentStats.hasAccess(constants.W_OK)) { | ||
if (config.checkAccess && !parentStats.hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(path), '_open'); | ||
@@ -477,3 +478,3 @@ } | ||
} | ||
if (!stats.hasAccess(flagToMode(flag))) { | ||
if (config.checkAccess && !stats.hasAccess(flagToMode(flag))) { | ||
throw ErrnoError.With('EACCES', path, '_open'); | ||
@@ -589,7 +590,10 @@ } | ||
export async function rmdir(path) { | ||
path = normalizePath(path); | ||
path = await realpath(path); | ||
const { fs, path: resolved } = resolveMount(path); | ||
try { | ||
if (!(await fs.stat(resolved)).hasAccess(constants.W_OK)) { | ||
const stats = cache.getStats(path) || (await fs.stat(resolved)); | ||
if (!stats.isDirectory()) { | ||
throw ErrnoError.With('ENOTDIR', resolved, 'rmdir'); | ||
} | ||
if (config.checkAccess && !stats.hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', resolved, 'rmdir'); | ||
@@ -608,3 +612,3 @@ } | ||
const mode = normalizeMode(options?.mode, 0o777); | ||
path = await realpath(normalizePath(path)); | ||
path = await realpath(path); | ||
const { fs, path: resolved } = resolveMount(path); | ||
@@ -614,3 +618,3 @@ const errorPaths = { [resolved]: path }; | ||
if (!options?.recursive) { | ||
if (!(await fs.stat(dirname(resolved))).hasAccess(constants.W_OK)) { | ||
if (config.checkAccess && !(await fs.stat(dirname(resolved))).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(resolved), 'mkdir'); | ||
@@ -628,3 +632,3 @@ } | ||
for (const dir of dirs) { | ||
if (!(await fs.stat(dirname(dir))).hasAccess(constants.W_OK)) { | ||
if (config.checkAccess && !(await fs.stat(dirname(dir))).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(dir), 'mkdir'); | ||
@@ -644,10 +648,16 @@ } | ||
options = typeof options === 'object' ? options : { encoding: options }; | ||
path = await realpath(normalizePath(path)); | ||
path = await realpath(path); | ||
const handleError = (e) => { | ||
throw fixError(e, { [resolved]: path }); | ||
}; | ||
const { fs, path: resolved } = resolveMount(path); | ||
if (!(await fs.stat(resolved)).hasAccess(constants.R_OK)) { | ||
const stats = cache.getStats(path) || (await fs.stat(resolved).catch(handleError)); | ||
cache.setStats(path, stats); | ||
if (config.checkAccess && !stats.hasAccess(constants.R_OK)) { | ||
throw ErrnoError.With('EACCES', path, 'readdir'); | ||
} | ||
const entries = await fs.readdir(resolved).catch((e) => { | ||
throw fixError(e, { [resolved]: path }); | ||
}); | ||
if (!stats.isDirectory()) { | ||
throw ErrnoError.With('ENOTDIR', path, 'readdir'); | ||
} | ||
const entries = await fs.readdir(resolved).catch(handleError); | ||
for (const point of mounts.keys()) { | ||
@@ -664,13 +674,12 @@ if (point.startsWith(path)) { | ||
const values = []; | ||
for (const entry of entries) { | ||
let stats; | ||
const addEntry = async (entry) => { | ||
let entryStats; | ||
if (options?.recursive || options?.withFileTypes) { | ||
stats = await fs.stat(join(resolved, entry)).catch((error) => { | ||
throw fixError(error, { [resolved]: path }); | ||
}); | ||
entryStats = cache.getStats(join(path, entry)) || (await fs.stat(join(resolved, entry)).catch(handleError)); | ||
cache.setStats(join(path, entry), entryStats); | ||
} | ||
if (options?.withFileTypes) { | ||
values.push(new Dirent(entry, stats)); | ||
values.push(new Dirent(entry, entryStats)); | ||
} | ||
else if (options?.encoding === 'buffer') { | ||
else if (options?.encoding == 'buffer') { | ||
values.push(Buffer.from(entry)); | ||
@@ -681,6 +690,5 @@ } | ||
} | ||
if (!options?.recursive || !stats?.isDirectory()) { | ||
continue; | ||
} | ||
for (const subEntry of await readdir(join(path, entry), options)) { | ||
if (!options?.recursive || !entryStats?.isDirectory()) | ||
return; | ||
for (const subEntry of await readdir(join(path, entry), { ...options, _isIndirect: true })) { | ||
if (subEntry instanceof Dirent) { | ||
@@ -698,2 +706,6 @@ subEntry.path = join(entry, subEntry.path); | ||
} | ||
}; | ||
await Promise.all(entries.map(addEntry)); | ||
if (!options?._isIndirect) { | ||
cache.clearStats(); | ||
} | ||
@@ -705,9 +717,3 @@ return values; | ||
targetPath = normalizePath(targetPath); | ||
if (!(await stat(dirname(targetPath))).hasAccess(constants.R_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(targetPath), 'link'); | ||
} | ||
linkPath = normalizePath(linkPath); | ||
if (!(await stat(dirname(linkPath))).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(linkPath), 'link'); | ||
} | ||
const { fs, path } = resolveMount(targetPath); | ||
@@ -719,3 +725,9 @@ const link = resolveMount(linkPath); | ||
try { | ||
if (!(await fs.stat(path)).hasAccess(constants.W_OK)) { | ||
if (config.checkAccess && !(await fs.stat(dirname(targetPath))).hasAccess(constants.R_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(path), 'link'); | ||
} | ||
if (config.checkAccess && !(await stat(dirname(linkPath))).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(linkPath), 'link'); | ||
} | ||
if (config.checkAccess && !(await fs.stat(path)).hasAccess(constants.W_OK | constants.R_OK)) { | ||
throw ErrnoError.With('EACCES', path, 'link'); | ||
@@ -927,2 +939,4 @@ } | ||
export async function access(path, mode = constants.F_OK) { | ||
if (!config.checkAccess) | ||
return; | ||
const stats = await stat(path); | ||
@@ -940,22 +954,24 @@ if (!stats.hasAccess(mode)) { | ||
path = normalizePath(path); | ||
const stats = await stat(path).catch((error) => { | ||
if (error.code != 'ENOENT' || !options?.force) | ||
throw error; | ||
}); | ||
const stats = cache.getStats(path) || | ||
(await stat(path).catch((error) => { | ||
if (error.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 await readdir(path)) { | ||
await rm(join(path, entry), options); | ||
for (const entry of await readdir(path, { _isIndirect: true })) { | ||
await rm(join(path, entry), { ...options, _isIndirect: true }); | ||
} | ||
} | ||
await rmdir(path); | ||
return; | ||
break; | ||
case constants.S_IFREG: | ||
case constants.S_IFLNK: | ||
await unlink(path); | ||
return; | ||
break; | ||
case constants.S_IFBLK: | ||
@@ -966,4 +982,8 @@ case constants.S_IFCHR: | ||
default: | ||
cache.clearStats(); | ||
throw new ErrnoError(Errno.EPERM, 'File type not supported', path, 'rm'); | ||
} | ||
if (!options?._isIndirect) { | ||
cache.clearStats(); | ||
} | ||
} | ||
@@ -1028,14 +1048,17 @@ rm; | ||
switch (srcStats.mode & constants.S_IFMT) { | ||
case constants.S_IFDIR: | ||
case constants.S_IFDIR: { | ||
if (!opts?.recursive) { | ||
throw new ErrnoError(Errno.EISDIR, source + ' is a directory (not copied)', source, 'cp'); | ||
} | ||
await mkdir(destination, { recursive: true }); // Ensure the destination directory exists | ||
for (const dirent of await readdir(source, { withFileTypes: true })) { | ||
const [entries] = await Promise.all([readdir(source, { withFileTypes: true }), mkdir(destination, { recursive: true })] // Ensure the destination directory exists | ||
); | ||
const _cp = async (dirent) => { | ||
if (opts.filter && !opts.filter(join(source, dirent.name), join(destination, dirent.name))) { | ||
continue; // Skip if the filter returns false | ||
return; // Skip if the filter returns false | ||
} | ||
await cp(join(source, dirent.name), join(destination, dirent.name), opts); | ||
} | ||
}; | ||
await Promise.all(entries.map(_cp)); | ||
break; | ||
} | ||
case constants.S_IFREG: | ||
@@ -1042,0 +1065,0 @@ case constants.S_IFLNK: |
@@ -46,1 +46,23 @@ import type { BigIntStatsFs, StatsFs } from 'node:fs'; | ||
export declare function _statfs<const T extends boolean>(fs: FileSystem, bigint?: T): T extends true ? BigIntStatsFs : StatsFs; | ||
export declare const config: { | ||
/** | ||
* Whether to perform access checks | ||
*/ | ||
checkAccess: boolean; | ||
}; | ||
/** | ||
* Options used for caching, among other things. | ||
* @internal *UNSTABLE* | ||
*/ | ||
export interface InternalOptions { | ||
/** | ||
* If true, then this readdir was called from another function. | ||
* In this case, don't clear the cache when done. | ||
* @internal *UNSTABLE* | ||
*/ | ||
_isIndirect?: boolean; | ||
} | ||
export interface ReaddirOptions extends InternalOptions { | ||
withFileTypes?: boolean; | ||
recursive?: boolean; | ||
} |
@@ -118,1 +118,7 @@ // Utilities and shared data | ||
} | ||
export const config = { | ||
/** | ||
* Whether to perform access checks | ||
*/ | ||
checkAccess: true, | ||
}; |
@@ -6,2 +6,3 @@ import { Buffer } from 'buffer'; | ||
import { Dir, Dirent } from './dir.js'; | ||
import { type InternalOptions, type ReaddirOptions } from './shared.js'; | ||
export declare function renameSync(oldPath: fs.PathLike, newPath: fs.PathLike): void; | ||
@@ -114,25 +115,15 @@ /** | ||
export declare function mkdirSync(path: fs.PathLike, options?: fs.Mode | fs.MakeDirectoryOptions | null): string | undefined; | ||
export declare function readdirSync(path: fs.PathLike, options?: { | ||
recursive?: boolean; | ||
encoding?: BufferEncoding | null; | ||
export declare function readdirSync(path: fs.PathLike, options?: (fs.ObjectEncodingOptions & ReaddirOptions & { | ||
withFileTypes?: false; | ||
} | BufferEncoding | null): string[]; | ||
export declare function readdirSync(path: fs.PathLike, options: { | ||
recursive?: boolean; | ||
encoding: 'buffer'; | ||
}) | BufferEncoding | null): string[]; | ||
export declare function readdirSync(path: fs.PathLike, options: fs.BufferEncodingOption & ReaddirOptions & { | ||
withFileTypes?: false; | ||
} | 'buffer'): Buffer[]; | ||
export declare function readdirSync(path: fs.PathLike, options: { | ||
recursive?: boolean; | ||
}): Buffer[]; | ||
export declare function readdirSync(path: fs.PathLike, options?: (fs.ObjectEncodingOptions & ReaddirOptions & { | ||
withFileTypes?: false; | ||
}) | BufferEncoding | null): string[] | Buffer[]; | ||
export declare function readdirSync(path: fs.PathLike, options: fs.ObjectEncodingOptions & ReaddirOptions & { | ||
withFileTypes: true; | ||
}): Dirent[]; | ||
export declare function readdirSync(path: fs.PathLike, options?: (fs.ObjectEncodingOptions & { | ||
withFileTypes?: false; | ||
recursive?: boolean; | ||
}) | BufferEncoding | null): string[] | Buffer[]; | ||
export declare function readdirSync(path: fs.PathLike, options?: { | ||
withFileTypes?: boolean; | ||
recursive?: boolean; | ||
encoding?: BufferEncoding | 'buffer' | null; | ||
} | BufferEncoding | 'buffer' | null): string[] | Dirent[] | Buffer[]; | ||
export declare function readdirSync(path: fs.PathLike, options?: (ReaddirOptions & (fs.ObjectEncodingOptions | fs.BufferEncodingOption)) | BufferEncoding | null): string[] | Dirent[] | Buffer[]; | ||
export declare function linkSync(targetPath: fs.PathLike, linkPath: fs.PathLike): void; | ||
@@ -168,3 +159,3 @@ /** | ||
*/ | ||
export declare function rmSync(path: fs.PathLike, options?: fs.RmOptions): void; | ||
export declare function rmSync(path: fs.PathLike, options?: fs.RmOptions & InternalOptions): void; | ||
/** | ||
@@ -171,0 +162,0 @@ * Synchronous `mkdtemp`. Creates a unique temporary directory. |
@@ -56,4 +56,5 @@ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { | ||
import { dirname, join, parse } from './path.js'; | ||
import { _statfs, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js'; | ||
import { _statfs, config, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js'; | ||
import { emitChange } from './watchers.js'; | ||
import * as cache from './cache.js'; | ||
export function renameSync(oldPath, newPath) { | ||
@@ -64,3 +65,3 @@ oldPath = normalizePath(oldPath); | ||
const newMount = resolveMount(newPath); | ||
if (!statSync(dirname(oldPath)).hasAccess(constants.W_OK)) { | ||
if (config.checkAccess && !statSync(dirname(oldPath)).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', oldPath, 'rename'); | ||
@@ -105,3 +106,3 @@ } | ||
const stats = fs.statSync(resolved); | ||
if (!stats.hasAccess(constants.R_OK)) { | ||
if (config.checkAccess && !stats.hasAccess(constants.R_OK)) { | ||
throw ErrnoError.With('EACCES', resolved, 'stat'); | ||
@@ -151,3 +152,3 @@ } | ||
try { | ||
if (!fs.statSync(resolved).hasAccess(constants.W_OK)) { | ||
if (config.checkAccess && !(cache.getStats(path) || fs.statSync(resolved)).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', resolved, 'unlink'); | ||
@@ -181,3 +182,3 @@ } | ||
const parentStats = fs.statSync(dirname(resolved)); | ||
if (!parentStats.hasAccess(constants.W_OK)) { | ||
if (config.checkAccess && !parentStats.hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(path), '_open'); | ||
@@ -190,3 +191,3 @@ } | ||
} | ||
if (!stats.hasAccess(mode) || !stats.hasAccess(flagToMode(flag))) { | ||
if (config.checkAccess && (!stats.hasAccess(mode) || !stats.hasAccess(flagToMode(flag)))) { | ||
throw ErrnoError.With('EACCES', path, '_open'); | ||
@@ -401,3 +402,7 @@ } | ||
try { | ||
if (!fs.statSync(resolved).hasAccess(constants.W_OK)) { | ||
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'); | ||
@@ -416,3 +421,3 @@ } | ||
const mode = normalizeMode(options?.mode, 0o777); | ||
path = realpathSync(normalizePath(path)); | ||
path = realpathSync(path); | ||
const { fs, path: resolved } = resolveMount(path); | ||
@@ -422,3 +427,3 @@ const errorPaths = { [resolved]: path }; | ||
if (!options?.recursive) { | ||
if (!fs.statSync(dirname(resolved)).hasAccess(constants.W_OK)) { | ||
if (config.checkAccess && !fs.statSync(dirname(resolved)).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(resolved), 'mkdir'); | ||
@@ -434,3 +439,3 @@ } | ||
for (const dir of dirs) { | ||
if (!fs.statSync(dirname(dir)).hasAccess(constants.W_OK)) { | ||
if (config.checkAccess && !fs.statSync(dirname(dir)).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(dir), 'mkdir'); | ||
@@ -454,5 +459,10 @@ } | ||
try { | ||
if (!fs.statSync(resolved).hasAccess(constants.R_OK)) { | ||
throw ErrnoError.With('EACCES', path, 'readdir'); | ||
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); | ||
@@ -477,7 +487,8 @@ } | ||
for (const entry of entries) { | ||
const entryStat = fs.statSync(join(resolved, entry)); | ||
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') { | ||
else if (options?.encoding == 'buffer') { | ||
values.push(Buffer.from(entry)); | ||
@@ -490,3 +501,3 @@ } | ||
continue; | ||
for (const subEntry of readdirSync(join(path, entry), options)) { | ||
for (const subEntry of readdirSync(join(path, entry), { ...options, _isIndirect: true })) { | ||
if (subEntry instanceof Dirent) { | ||
@@ -504,2 +515,5 @@ subEntry.path = join(entry, subEntry.path); | ||
} | ||
if (!options?._isIndirect) { | ||
cache.clearStats(); | ||
} | ||
return values; | ||
@@ -511,7 +525,7 @@ } | ||
targetPath = normalizePath(targetPath); | ||
if (!statSync(dirname(targetPath)).hasAccess(constants.R_OK)) { | ||
if (config.checkAccess && !statSync(dirname(targetPath)).hasAccess(constants.R_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(targetPath), 'link'); | ||
} | ||
linkPath = normalizePath(linkPath); | ||
if (!statSync(dirname(linkPath)).hasAccess(constants.W_OK)) { | ||
if (config.checkAccess && !statSync(dirname(linkPath)).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(linkPath), 'link'); | ||
@@ -525,3 +539,3 @@ } | ||
try { | ||
if (!fs.statSync(path).hasAccess(constants.W_OK)) { | ||
if (config.checkAccess && !fs.statSync(path).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', path, 'link'); | ||
@@ -627,2 +641,4 @@ } | ||
export function accessSync(path, mode = 0o600) { | ||
if (!config.checkAccess) | ||
return; | ||
if (!statSync(path).hasAccess(mode)) { | ||
@@ -641,3 +657,3 @@ throw new ErrnoError(Errno.EACCES); | ||
try { | ||
stats = statSync(path); | ||
stats = cache.getStats(path) || statSync(path); | ||
} | ||
@@ -651,15 +667,16 @@ catch (error) { | ||
} | ||
cache.setStats(path, stats); | ||
switch (stats.mode & constants.S_IFMT) { | ||
case constants.S_IFDIR: | ||
if (options?.recursive) { | ||
for (const entry of readdirSync(path)) { | ||
rmSync(join(path, entry), options); | ||
for (const entry of readdirSync(path, { _isIndirect: true })) { | ||
rmSync(join(path, entry), { ...options, _isIndirect: true }); | ||
} | ||
} | ||
rmdirSync(path); | ||
return; | ||
break; | ||
case constants.S_IFREG: | ||
case constants.S_IFLNK: | ||
unlinkSync(path); | ||
return; | ||
break; | ||
case constants.S_IFBLK: | ||
@@ -670,4 +687,8 @@ case constants.S_IFCHR: | ||
default: | ||
cache.clearStats(); | ||
throw new ErrnoError(Errno.EPERM, 'File type not supported', path, 'rm'); | ||
} | ||
if (!options?._isIndirect) { | ||
cache.clearStats(); | ||
} | ||
} | ||
@@ -674,0 +695,0 @@ rmSync; |
{ | ||
"name": "@zenfs/core", | ||
"version": "1.1.6", | ||
"version": "1.2.0", | ||
"description": "A filesystem, anywhere", | ||
@@ -21,2 +21,3 @@ "funding": { | ||
"files": [ | ||
"src", | ||
"dist", | ||
@@ -52,3 +53,4 @@ "tests", | ||
"./eslint": "./eslint.shared.js", | ||
"./tests/*": "./tests/*" | ||
"./tests/*": "./tests/*", | ||
"./src/*": "./src/*" | ||
}, | ||
@@ -55,0 +57,0 @@ "scripts": { |
#!/usr/bin/env node | ||
import { execSync } from 'node:child_process'; | ||
import { existsSync } from 'node:fs'; | ||
import { join } from 'node:path'; | ||
@@ -11,2 +12,4 @@ import { parseArgs } from 'node:util'; | ||
verbose: { type: 'boolean', default: false }, | ||
test: { type: 'string' }, | ||
forceExit: { short: 'f', type: 'boolean', default: false }, | ||
}, | ||
@@ -24,2 +27,4 @@ allowPositionals: true, | ||
--verbose Output verbose messages | ||
--test Which test to run | ||
--forceExit Whether to use --test-force-exit | ||
`); | ||
@@ -29,6 +34,14 @@ process.exit(); | ||
if (options.verbose) console.debug('Forcing tests to exit (--test-force-exit)'); | ||
const testsGlob = join(import.meta.dirname, `../tests/fs/${options.test || '*'}.test.ts`); | ||
for (const setupFile of positionals) { | ||
if (options.verbose) console.debug('Running tests for:', setupFile); | ||
process.env.SETUP = setupFile; | ||
execSync('tsx --test --experimental-test-coverage ' + join(import.meta.dirname, '../tests/fs/*.test.ts'), { stdio: 'inherit' }); | ||
if (!existsSync(setupFile)) { | ||
console.log('ERROR: Skipping non-existent file:', setupFile); | ||
continue; | ||
} | ||
execSync(['tsx --test --experimental-test-coverage', options.forceExit ? '--test-force-exit' : '', testsGlob, process.env.CMD].join(' '), { stdio: 'inherit' }); | ||
} |
@@ -70,5 +70,5 @@ import assert from 'node:assert'; | ||
const entries = await fs.promises.readdir(testDir, { recursive: true, withFileTypes: true }); | ||
assert.equal(entries[0].path, 'file1.txt'); | ||
assert.equal(entries[4].path, 'subdir1/file4.txt'); | ||
assert.equal(entries[entries.length - 1].path, 'subdir2/file5.txt'); | ||
assert(entries.find(entry => entry.path === 'file1.txt')); | ||
assert(entries.find(entry => entry.path === 'subdir1/file4.txt')); | ||
assert(entries.find(entry => entry.path === 'subdir2/file5.txt')); | ||
}); | ||
@@ -75,0 +75,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
862244
168
22878
2