12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394 |
- // look up the realpath, but cache stats to minimize overhead
- // If the parent folder is in the realpath cache, then we just
- // lstat the child, since there's no need to do a full realpath
- // This is not a separate module, and is much simpler than Node's
- // built-in fs.realpath, because we only care about symbolic links,
- // so we can handle many fewer edge cases.
- const fs = require('fs')
- /* istanbul ignore next */
- const promisify = require('util').promisify || require('util-promisify')
- const readlink = promisify(fs.readlink)
- const lstat = promisify(fs.lstat)
- const { resolve, basename, dirname } = require('path')
- const realpathCached = (path, rpcache, stcache, depth) => {
- // just a safety against extremely deep eloops
- /* istanbul ignore next */
- if (depth > 2000)
- throw eloop(path)
- path = resolve(path)
- if (rpcache.has(path))
- return Promise.resolve(rpcache.get(path))
- const dir = dirname(path)
- const base = basename(path)
- if (base && rpcache.has(dir))
- return realpathChild(dir, base, rpcache, stcache, depth)
- // if it's the root, then we know it's real
- if (!base) {
- rpcache.set(dir, dir)
- return Promise.resolve(dir)
- }
- // the parent, what is that?
- // find out, and then come back.
- return realpathCached(dir, rpcache, stcache, depth + 1).then(() =>
- realpathCached(path, rpcache, stcache, depth + 1))
- }
- const lstatCached = (path, stcache) => {
- if (stcache.has(path))
- return Promise.resolve(stcache.get(path))
- const p = lstat(path).then(st => {
- stcache.set(path, st)
- return st
- })
- stcache.set(path, p)
- return p
- }
- // This is a slight fib, as it doesn't actually occur during a stat syscall.
- // But file systems are giant piles of lies, so whatever.
- const eloop = path =>
- Object.assign(new Error(
- `ELOOP: too many symbolic links encountered, stat '${path}'`), {
- errno: -62,
- syscall: 'stat',
- code: 'ELOOP',
- path: path,
- })
- const realpathChild = (dir, base, rpcache, stcache, depth) => {
- const realdir = rpcache.get(dir)
- // that unpossible
- /* istanbul ignore next */
- if (typeof realdir === 'undefined')
- throw new Error('in realpathChild without parent being in realpath cache')
- const realish = resolve(realdir, base)
- return lstatCached(realish, stcache).then(st => {
- if (!st.isSymbolicLink()) {
- rpcache.set(resolve(dir, base), realish)
- return realish
- }
- let res
- return readlink(realish).then(target => {
- const resolved = res = resolve(realdir, target)
- if (realish === resolved)
- throw eloop(realish)
- return realpathCached(resolved, rpcache, stcache, depth + 1)
- }).then(real => {
- rpcache.set(resolve(dir, base), real)
- return real
- })
- })
- }
- module.exports = realpathCached
|