blob: f4c9a5ec53411c463e27511ac5982f4fb82e2611 [file] [log] [blame]
/**
* @license
* Copyright 2019 The Bazel Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { PathLike, Stats, StatSyncOptions, BigIntStats } from 'fs'
import type * as FsType from 'fs'
import type * as UrlType from 'url'
import * as path from 'path'
import * as util from 'util'
// windows cant find the right types
type Dir = FsType.Dir
type Dirent = FsType.Dirent
// using require here on purpose so we can override methods with any
// also even though imports are mutable in typescript the cognitive dissonance is too high because
// es modules
const fs = require('node:fs') as any
const url = require('node:url') as typeof UrlType
const esmModule = require('node:module')
const HOP_NON_LINK = Symbol.for('HOP NON LINK')
const HOP_NOT_FOUND = Symbol.for('HOP NOT FOUND')
type HopResults = string | typeof HOP_NON_LINK | typeof HOP_NOT_FOUND
type ErrPathCallback = (err: Error | null, path: string | undefined) => void
const PATCHED_FS_METHODS: ReadonlyArray<keyof typeof FsType> = [
'lstat',
'lstatSync',
'realpath',
'realpathSync',
'readlink',
'readlinkSync',
'readdir',
'readdirSync',
'opendir',
]
/**
* Function that patches the `fs` module to not escape the given roots.
* @returns a function to undo the patches.
*/
export function patcher(roots: string[]): () => void {
if (fs._unpatched) {
throw new Error('FS is already patched.')
}
// Make the original version of the library available for when access to the
// unguarded file system is necessary, such as the esbuild plugin that
// protects against sandbox escaping that occurs through module resolution
// in the Go binary. See
// https://github.com/aspect-build/rules_esbuild/issues/58.
fs._unpatched = PATCHED_FS_METHODS.reduce(
(obj, method) => {
obj[method] = fs[method]
return obj
},
{} as { [key in (typeof PATCHED_FS_METHODS)[number]]: any }
)
roots = roots || []
roots = roots.filter((root) => fs.existsSync(root))
if (!roots.length) {
if (process.env.VERBOSE_LOGS) {
console.error(
'fs patcher called without any valid root paths ' + __filename
)
}
return function () {}
}
const origLstat = fs.lstat.bind(fs) as typeof FsType.lstat
const origLstatSync = fs.lstatSync.bind(fs) as typeof FsType.lstatSync
const origReaddir = fs.readdir.bind(fs) as typeof FsType.readdir
const origReaddirSync = fs.readdirSync.bind(fs) as typeof FsType.readdirSync
const origReadlink = fs.readlink.bind(fs) as typeof FsType.readlink
const origReadlinkSync = fs.readlinkSync.bind(
fs
) as typeof FsType.readlinkSync
const origRealpath = fs.realpath.bind(fs) as typeof FsType.realpath
const origRealpathNative = fs.realpath
.native as typeof FsType.realpath.native
const origRealpathSync = fs.realpathSync.bind(
fs
) as typeof FsType.realpathSync
const origRealpathSyncNative = fs.realpathSync
.native as typeof FsType.realpathSync.native
const { canEscape, isEscape } = escapeFunction(roots)
// =========================================================================
// fs.lstat
// =========================================================================
fs.lstat = function lstat(...args: Parameters<typeof FsType.lstat>) {
// preserve error when calling function without required callback
if (typeof args[args.length - 1] !== 'function') {
return origLstat(...args)
}
const cb = once(args[args.length - 1] as Function)
// override the callback
args[args.length - 1] = function lstatCb(
err: Error | null,
stats: Stats | BigIntStats
) {
if (err) return cb(err)
if (!stats.isSymbolicLink()) {
// the file is not a symbolic link so there is nothing more to do
return cb(null, stats)
}
args[0] = resolvePathLike(args[0])
if (!canEscape(args[0])) {
// the file can not escaped the sandbox so there is nothing more to do
return cb(null, stats)
}
return guardedReadLink(args[0], guardedReadLinkCb)
function guardedReadLinkCb(str: string) {
if (str != args[0]) {
// there are one or more hops within the guards so there is nothing more to do
return cb(null, stats)
}
// there are no hops so lets report the stats of the real file;
// we can't use origRealPath here since that function calls lstat internally
// which can result in an infinite loop
return unguardedRealPath(args[0], unguardedRealPathCb)
function unguardedRealPathCb(err: Error | null, str?: string) {
if (err) {
if ((err as any).code === 'ENOENT') {
// broken link so there is nothing more to do
return cb(null, stats)
}
return cb(err)
}
return origLstat(str!, cb)
}
}
}
origLstat(...args)
}
fs.lstatSync = function lstatSync(
...args: Parameters<typeof FsType.lstatSync>
) {
const stats = origLstatSync(...args)
if (!stats?.isSymbolicLink()) {
// the file is not a symbolic link so there is nothing more to do
return stats
}
args[0] = resolvePathLike(args[0])
if (!canEscape(args[0])) {
// the file can not escaped the sandbox so there is nothing more to do
return stats
}
const guardedReadLink: string = guardedReadLinkSync(args[0])
if (guardedReadLink != args[0]) {
// there are one or more hops within the guards so there is nothing more to do
return stats
}
try {
args[0] = unguardedRealPathSync(args[0])
// there are no hops so lets report the stats of the real file;
// we can't use origRealPathSync here since that function calls lstat internally
// which can result in an infinite loop
return origLstatSync(...args)
} catch (err: any) {
if (err.code === 'ENOENT') {
// broken link so there is nothing more to do
return stats
}
throw err
}
}
// =========================================================================
// fs.realpath
// =========================================================================
fs.realpath = function realpath(...args: Parameters<typeof origRealpath>) {
// preserve error when calling function without required callback
if (typeof args[args.length - 1] !== 'function') {
return origRealpath(...args)
}
const cb = once(args[args.length - 1] as Function)
args[args.length - 1] = function realpathCb(
err: Error | null,
str?: string
) {
if (err) return cb(err)
const escapedRoot: string | false = isEscape(args[0], str!)
if (escapedRoot) {
return guardedRealPath(args[0], cb, escapedRoot)
} else {
return cb(null, str)
}
}
origRealpath(...args)
}
fs.realpath.native = function realpath_native(
...args: Parameters<typeof origRealpathNative>
) {
// preserve error when calling function without required callback
if (typeof args[args.length - 1] !== 'function') {
return origRealpathNative(...args)
}
const cb = once(args[args.length - 1] as Function)
args[args.length - 1] = function nativeCb(
err: Error | null,
str?: string
) {
if (err) return cb(err)
const escapedRoot: string | false = isEscape(args[0], str!)
if (escapedRoot) {
return guardedRealPath(args[0], cb, escapedRoot)
} else {
return cb(null, str)
}
}
origRealpathNative(...args)
}
fs.realpathSync = function realpathSync(
...args: Parameters<typeof origRealpathSync>
) {
const str = origRealpathSync(...args)
const escapedRoot: string | false = isEscape(args[0], str)
if (escapedRoot) {
return guardedRealPathSync(args[0], escapedRoot)
}
return str
}
fs.realpathSync.native = function native_realpathSync(
...args: Parameters<typeof origRealpathSyncNative>
) {
const str = origRealpathSyncNative(...args)
const escapedRoot: string | false = isEscape(args[0], str)
if (escapedRoot) {
return guardedRealPathSync(args[0], escapedRoot)
}
return str
}
// =========================================================================
// fs.readlink
// =========================================================================
fs.readlink = function readlink(...args: Parameters<typeof origReadlink>) {
// preserve error when calling function without required callback
if (typeof args[args.length - 1] !== 'function') {
return origReadlink(...args)
}
const cb = once(args[args.length - 1] as Function)
args[args.length - 1] = function readlinkCb(
err: Error | null,
p?: string
) {
if (err) return cb(err)
const resolved = resolvePathLike(args[0])
const str = path.resolve(path.dirname(resolved), p!)
const escapedRoot: string | false = isEscape(resolved, str)
if (escapedRoot) {
return nextHop(str, readlinkNextHopCb)
function readlinkNextHopCb(next: string | undefined | false) {
if (!next) {
if (next == undefined) {
// The escape from the root is not mappable back into the root; throw EINVAL
return cb(enoent('readlink', args[0]))
} else {
// The escape from the root is not mappable back into the root; throw EINVAL
return cb(einval('readlink', args[0]))
}
}
const r = path.resolve(
path.dirname(resolved),
path.relative(path.dirname(str), next)
)
if (
r != resolved &&
!isEscape(resolved, r, [escapedRoot as string])
) {
return cb(null, r)
}
// The escape from the root is not mappable back into the root; throw EINVAL
return cb(einval('readlink', args[0]))
}
} else {
return cb(null, str)
}
}
origReadlink(...args)
}
fs.readlinkSync = function readlinkSync(
...args: Parameters<typeof origReadlinkSync>
) {
const resolved = resolvePathLike(args[0])
const str = path.resolve(
path.dirname(resolved),
origReadlinkSync(...args)
)
const escapedRoot: string | false = isEscape(resolved, str)
if (escapedRoot) {
const next = nextHopSync(str)
if (!next) {
if (next == undefined) {
// The escape from the root is not mappable back into the root; throw EINVAL
throw enoent('readlink', args[0])
} else {
// The escape from the root is not mappable back into the root; throw EINVAL
throw einval('readlink', args[0])
}
}
const r = path.resolve(
path.dirname(resolved),
path.relative(path.dirname(str), next)
)
if (r != resolved && !isEscape(resolved, r, [escapedRoot])) {
return r
}
// The escape from the root is not mappable back into the root; throw EINVAL
throw einval('readlink', args[0])
}
return str
}
// =========================================================================
// fs.readdir
// =========================================================================
fs.readdir = function readdir(...args: Parameters<typeof origReaddir>) {
// preserve error when calling function without required callback
if (typeof args[args.length - 1] !== 'function') {
return origReaddir(...args)
}
const cb = once(args[args.length - 1] as Function)
const p = resolvePathLike(args[0])
args[args.length - 1] = function readdirCb(
err: Error | null,
result: Dirent[]
) {
if (err) return cb(err)
// user requested withFileTypes
if (result[0] && (result[0] as any).isSymbolicLink) {
Promise.all(result.map((v: Dirent) => handleDirent(p, v)))
.then(() => {
cb(null, result)
})
.catch((err) => {
cb(err)
})
} else {
// string array return for readdir.
cb(null, result)
}
}
origReaddir(...args)
}
fs.readdirSync = function readdirSync(
...args: Parameters<typeof origReaddirSync>
) {
const res = origReaddirSync(...args)
const p = resolvePathLike(args[0])
res.forEach((v: Dirent | any) => {
handleDirentSync(p, v)
})
return res
}
// =========================================================================
// fs.opendir
// =========================================================================
if (fs.opendir) {
const origOpendir = fs.opendir.bind(fs)
fs.opendir = function opendir(...args: Parameters<typeof origOpendir>) {
// if this is not a function opendir should throw an error.
// we call it so we don't have to throw a mock
if (typeof args[args.length - 1] === 'function') {
const cb = once(args[args.length - 1] as Function)
args[args.length - 1] = async function opendirCb(
err: Error | null,
dir: Dir
) {
try {
cb(err, err ? undefined : handleDir(dir))
} catch (err) {
cb(err)
}
}
origOpendir(...args)
} else {
return origOpendir(...args).then(handleDir)
}
}
}
if (fs.opendirSync) {
const origOpendirSync = fs.opendirSync.bind(fs)
fs.opendirSync = function opendirSync(
...args: Parameters<typeof origOpendirSync>
) {
const dir = origOpendirSync(...args)
return handleDir(dir)
}
}
// =========================================================================
// fs.promises
// =========================================================================
/**
* patch fs.promises here.
*
* this requires a light touch because if we trigger the getter on older nodejs versions
* it will log an experimental warning to stderr
*
* `(node:62945) ExperimentalWarning: The fs.promises API is experimental`
*
* this api is available as experimental without a flag so users can access it at any time.
*/
const promisePropertyDescriptor = Object.getOwnPropertyDescriptor(
fs,
'promises'
)
let unpatchPromises: Function
if (promisePropertyDescriptor) {
const promises: typeof fs.promises = {}
promises.lstat = util.promisify(fs.lstat)
// NOTE: node core uses the newer realpath function fs.promises.native instead of fs.realPath
promises.realpath = util.promisify(fs.realpath.native)
promises.readlink = util.promisify(fs.readlink)
promises.readdir = util.promisify(fs.readdir)
if (fs.opendir) promises.opendir = util.promisify(fs.opendir)
// handle experimental api warnings.
// only applies to version of node where promises is a getter property.
if (promisePropertyDescriptor.get) {
const oldGetter = promisePropertyDescriptor.get.bind(fs)
const cachedPromises = {}
function promisePropertyGetter() {
const _promises = oldGetter()
Object.assign(cachedPromises, _promises, promises)
return cachedPromises
}
Object.defineProperty(
fs,
'promises',
Object.assign(Object.create(promisePropertyDescriptor), {
get: promisePropertyGetter,
})
)
unpatchPromises = function unpatchFsPromises() {
Object.defineProperty(fs, 'promises', promisePropertyDescriptor)
}
} else {
const unpatchedPromises = Object.keys(promises).reduce(
(obj, method) => {
obj[method] = fs.promises[method]
return obj
},
Object.create(fs.promises)
)
// api can be patched directly
Object.assign(fs.promises, promises)
unpatchPromises = function unpatchFsPromises() {
Object.assign(fs.promises, unpatchedPromises)
}
}
}
// =========================================================================
// helper functions for dirs
// =========================================================================
function handleDir(dir: Dir) {
const p = path.resolve(dir.path)
const origIterator = dir[Symbol.asyncIterator].bind(dir)
dir[Symbol.asyncIterator] = async function* () {
for await (const entry of origIterator()) {
await handleDirent(p, entry)
yield entry
}
}
const origRead = dir.read.bind(dir)
dir.read = function readWrapper() {
if (typeof arguments[0] === 'function') {
return handleDirReadCallback(arguments[0])
} else {
return handleDirReadPromise()
}
} as any
// read(cb: (err: NodeJS.ErrnoException | null, dirEnt: Dirent | null) => void): void;
function handleDirReadCallback(
cb: (err: Error | null, entry: Dirent | null) => void
): void {
origRead(function handleDirReadCb(
err: Error | null,
entry: Dirent | null
) {
if (err) return cb(err, null)
handleDirent(p, entry!).then(
() => {
cb(null, entry!)
},
(err) => cb(err, null)
)
})
}
// read(): Promise<Dirent | null>;
async function handleDirReadPromise(): Promise<Dirent | null> {
const entry = await origRead()
if (entry) {
await handleDirent(p, entry)
}
return entry
}
const origReadSync = dir.readSync.bind(dir)
dir.readSync = function handleDirReadSync() {
return handleDirentSync(p, origReadSync())
}
return dir
}
async function handleDirent(p: string, v: Dirent) {
if (!v.isSymbolicLink()) {
return v
}
const f = path.resolve(p, v.name)
return new Promise(function handleDirentExecutor(resolve, reject) {
return guardedReadLink(f, handleDirentReadLinkCb)
function handleDirentReadLinkCb(str: string) {
if (f != str) {
return resolve(v)
}
// There are no hops so we should hide the fact that the file is a symlink
v.isSymbolicLink = () => false
origRealpath(f, function handleDirentRealpathCb(err, str) {
if (err) {
return reject(err)
}
fs.stat(
str,
function handleDirentStatCb(
err: Error | null,
stat: Stats | undefined
) {
if (err) {
return reject(err)
}
patchDirent(v, stat)
resolve(v)
}
)
})
}
})
}
function handleDirentSync(p: string, v: Dirent | null): Dirent | null {
if (v && v.isSymbolicLink) {
if (v.isSymbolicLink()) {
const f = path.resolve(p, v.name)
if (f == guardedReadLinkSync(f)) {
// There are no hops so we should hide the fact that the file is a symlink
v.isSymbolicLink = () => false
const stat = fs.statSync(origRealpathSync(f))
patchDirent(v, stat)
}
}
}
return v
}
function nextHop(
loc: string,
cb: (next: string | undefined | false) => void
): void {
let nested = ''
let maybe = loc
let escapedHop: string | false = false
readHopLink(maybe, function readNextHop(link) {
if (link === HOP_NOT_FOUND) {
return cb(undefined)
}
if (link !== HOP_NON_LINK) {
if (nested) {
link = link + path.sep + nested
}
if (!isEscape(loc, link)) {
return cb(link)
}
if (!escapedHop) {
escapedHop = link
}
}
const dirname = path.dirname(maybe)
if (
!dirname ||
dirname == maybe ||
dirname == '.' ||
dirname == '/'
) {
// not a link
return cb(escapedHop)
}
nested = path.basename(maybe) + (nested ? path.sep + nested : '')
maybe = dirname
readHopLink(maybe, readNextHop)
})
}
const symlinkNoThrow: StatSyncOptions = Object.freeze({
throwIfNoEntry: false,
})
const hopLinkCache = Object.create(null) as { [f: string]: HopResults }
function readHopLinkSync(p: string): HopResults {
if (hopLinkCache[p]) {
return hopLinkCache[p]
}
let link: HopResults
const pStats = origLstatSync(p, symlinkNoThrow)
if (!pStats) {
link = HOP_NOT_FOUND
} else if (pStats.isSymbolicLink()) {
link = origReadlinkSync(p) as string
if (link) {
if (!path.isAbsolute(link)) {
link = path.resolve(path.dirname(p), link)
}
} else {
link = HOP_NON_LINK
}
} else {
link = HOP_NON_LINK
}
hopLinkCache[p] = link
return link
}
function readHopLink(p: string, cb: (l: HopResults) => void) {
if (hopLinkCache[p]) {
return cb(hopLinkCache[p])
}
origReadlink(
p,
function readHopLinkCb(err: Error | null, link: string) {
if (err) {
let result: HopResults
if ((err as any).code === 'ENOENT') {
// file does not exist
result = HOP_NOT_FOUND
} else {
result = HOP_NON_LINK
}
hopLinkCache[p] = result
return cb(result)
}
if (link === undefined) {
hopLinkCache[p] = HOP_NON_LINK
return cb(HOP_NON_LINK)
}
if (!path.isAbsolute(link)) {
link = path.resolve(path.dirname(p), link)
}
hopLinkCache[p] = link
cb(link)
}
)
}
function nextHopSync(loc: string): string | false {
let nested = ''
let maybe = loc
let escapedHop: string | false = false
for (;;) {
let link = readHopLinkSync(maybe)
if (link === HOP_NOT_FOUND) {
return false
}
if (link !== HOP_NON_LINK) {
if (nested) {
link = link + path.sep + nested
}
if (!isEscape(loc, link)) {
return link
}
if (!escapedHop) {
escapedHop = link
}
}
const dirname = path.dirname(maybe)
if (
!dirname ||
dirname == maybe ||
dirname == '.' ||
dirname == '/'
) {
// not a link
return escapedHop
}
nested = path.basename(maybe) + (nested ? path.sep + nested : '')
maybe = dirname
}
}
function guardedReadLink(loc: string, cb: (str: string) => void): void {
nextHop(loc, guardedReadLinkHopCb)
function guardedReadLinkHopCb(next: string | undefined | false) {
if (!next) {
// we're no longer hopping but we haven't escaped;
// something funky happened in the filesystem
return cb(loc)
}
if (isEscape(loc, next)) {
// this hop takes us out of the guard
return cb(loc)
}
return cb(next)
}
}
function guardedReadLinkSync(loc: string): string {
const next = nextHopSync(loc)
if (!next) {
// we're no longer hopping but we haven't escaped;
// something funky happened in the filesystem
return loc
}
if (isEscape(loc, next)) {
// this hop takes us out of the guard
return loc
}
return next
}
function unguardedRealPath(start: PathLike, cb: ErrPathCallback): void {
// stringifyPathLike() to handle the "undefined" case (matches behavior as fs.realpath)
oneHop(stringifyPathLike(start), cb)
function oneHop(loc: string, cb: ErrPathCallback) {
nextHop(loc, function oneHopeNextCb(next) {
if (next == undefined) {
// file does not exist (broken link)
return cb(enoent('realpath', start), undefined)
} else if (!next) {
// we've hit a real file
return cb(null, loc)
}
oneHop(next, cb)
})
}
}
function guardedRealPath(
start: PathLike,
cb: ErrPathCallback,
escapedRoot?: string
): void {
// stringifyPathLike() to handle the "undefined" case (matches behavior as fs.realpath)
oneHop(stringifyPathLike(start), cb)
function oneHop(loc: string, cb: ErrPathCallback) {
nextHop(loc, function guardedRealPathHopCb(next) {
if (!next) {
return cb(enoent('realpath', start), undefined)
}
if (
escapedRoot
? isEscape(loc, next, [escapedRoot])
: isEscape(loc, next)
) {
// this hop takes us out of the guard
return cb(null, loc)
}
oneHop(next, cb)
})
}
}
function unguardedRealPathSync(start: PathLike): string {
// stringifyPathLike() to handle the "undefined" case (matches behavior as fs.realpathSync)
for (let loc = stringifyPathLike(start), next; ; loc = next) {
next = nextHopSync(loc)
if (next == undefined) {
// file does not exist (broken link)
throw enoent('realpath', start)
} else if (!next) {
// we've hit a real file
return loc
}
}
}
function guardedRealPathSync(
start: PathLike,
escapedRoot?: string
): string {
// stringifyPathLike() to handle the "undefined" case (matches behavior as fs.realpathSync)
for (
let loc = stringifyPathLike(start), next: string | false;
;
loc = next as string
) {
next = nextHopSync(loc)
if (!next) {
// we're no longer hopping but we haven't escaped
if (fs.existsSync(loc)) {
// we hit a real file within the guard and can go no further
return loc
} else {
// something funky happened in the filesystem; throw ENOENT
throw enoent('realpath', start)
}
}
if (
escapedRoot
? isEscape(loc, next, [escapedRoot])
: isEscape(loc, next)
) {
// this hop takes us out of the guard
return loc
}
}
}
// Sync the esm modules to use the now patched fs cjs module.
// See: https://nodejs.org/api/esm.html#builtin-modules
esmModule.syncBuiltinESMExports()
return function revertPatch() {
Object.assign(fs, fs._unpatched)
delete fs._unpatched
if (unpatchPromises) {
unpatchPromises()
}
// Re-sync the esm modules to revert to the unpatched module.
esmModule.syncBuiltinESMExports()
}
}
// =========================================================================
// generic helper functions
// =========================================================================
export function isSubPath(parent: string, child: string): boolean {
return (
parent === child ||
(child[parent.length] === path.sep && child.startsWith(parent))
)
}
function stringifyPathLike(p: PathLike): string {
if (p instanceof URL) {
return url.fileURLToPath(p)
} else {
return String(p)
}
}
function resolvePathLike(p: PathLike): string {
return path.resolve(stringifyPathLike(p))
}
function normalizePathLike(p: PathLike): string {
const s = stringifyPathLike(p)
// TODO: are URLs always absolute?
if (!path.isAbsolute(s)) {
return path.resolve(s)
} else {
return path.normalize(s)
}
}
export function escapeFunction(_roots: string[]) {
// Ensure roots are always absolute.
// Sort to ensure escaping multiple roots chooses the longest one.
const defaultRoots = _roots
.map((root) => path.resolve(root))
.sort((a, b) => b.length - a.length)
function fs_isEscape(
linkPath: PathLike,
linkTarget: string,
roots = defaultRoots
): false | string {
// linkPath is the path of the symlink file itself
// linkTarget is a path that the symlink points to one or more hops away
// linkTarget must already be normalized
linkPath = normalizePathLike(linkPath)
for (const root of roots) {
// If the link is in the root check if the realPath has escaped
if (isSubPath(root, linkPath) && !isSubPath(root, linkTarget)) {
return root
}
}
return false
}
function fs_canEscape(
maybeLinkPath: string,
roots = defaultRoots
): boolean {
// maybeLinkPath is the path which may be a symlink
// maybeLinkPath must already be normalized
for (const root of roots) {
// If the link is in the root check if the realPath has escaped
if (isSubPath(root, maybeLinkPath)) {
return true
}
}
return false
}
return {
isEscape: fs_isEscape,
canEscape: fs_canEscape,
}
}
function once(fn: Function) {
let called = false
return function callOnce(...args: any[]) {
if (called) return
called = true
let err: Error | false = false
try {
fn(...args)
} catch (_e: any) {
err = _e
}
// blow the stack to make sure this doesn't fall into any unresolved promise contexts
if (err) {
setImmediate(() => {
throw err
})
}
}
}
function patchDirent(dirent: Dirent | any, stat: Stats | any): void {
// add all stat is methods to Dirent instances with their result.
for (const i in stat) {
if (i.startsWith('is') && typeof stat[i] === 'function') {
//
const result = stat[i]()
if (result) dirent[i] = () => true
else dirent[i] = () => false
}
}
}
function enoent(s: string, p: PathLike): Error {
let err = new Error(`ENOENT: no such file or directory, ${s} '${p}'`)
;(err as any).errno = -2
;(err as any).syscall = s
;(err as any).code = 'ENOENT'
;(err as any).path = p
return err
}
function einval(s: string, p: PathLike): Error {
let err = new Error(`EINVAL: invalid argument, ${s} '${p}'`)
;(err as any).errno = -22
;(err as any).syscall = s
;(err as any).code = 'EINVAL'
;(err as any).path = p
return err
}