1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | const fs = require('fs')
|
9 |
|
10 | const promisify = require('util').promisify || require('util-promisify')
|
11 | const readlink = promisify(fs.readlink)
|
12 | const lstat = promisify(fs.lstat)
|
13 | const { resolve, basename, dirname } = require('path')
|
14 |
|
15 | const realpathCached = (path, rpcache, stcache, depth) => {
|
16 |
|
17 |
|
18 | if (depth > 2000)
|
19 | throw eloop(path)
|
20 |
|
21 | path = resolve(path)
|
22 | if (rpcache.has(path))
|
23 | return Promise.resolve(rpcache.get(path))
|
24 |
|
25 | const dir = dirname(path)
|
26 | const base = basename(path)
|
27 |
|
28 | if (base && rpcache.has(dir))
|
29 | return realpathChild(dir, base, rpcache, stcache, depth)
|
30 |
|
31 |
|
32 | if (!base) {
|
33 | rpcache.set(dir, dir)
|
34 | return Promise.resolve(dir)
|
35 | }
|
36 |
|
37 |
|
38 |
|
39 | return realpathCached(dir, rpcache, stcache, depth + 1).then(() =>
|
40 | realpathCached(path, rpcache, stcache, depth + 1))
|
41 | }
|
42 |
|
43 | const lstatCached = (path, stcache) => {
|
44 | if (stcache.has(path))
|
45 | return Promise.resolve(stcache.get(path))
|
46 |
|
47 | const p = lstat(path).then(st => {
|
48 | stcache.set(path, st)
|
49 | return st
|
50 | })
|
51 | stcache.set(path, p)
|
52 | return p
|
53 | }
|
54 |
|
55 |
|
56 |
|
57 | const eloop = path =>
|
58 | Object.assign(new Error(
|
59 | `ELOOP: too many symbolic links encountered, stat '${path}'`), {
|
60 | errno: -62,
|
61 | syscall: 'stat',
|
62 | code: 'ELOOP',
|
63 | path: path,
|
64 | })
|
65 |
|
66 | const realpathChild = (dir, base, rpcache, stcache, depth) => {
|
67 | const realdir = rpcache.get(dir)
|
68 |
|
69 |
|
70 | if (typeof realdir === 'undefined')
|
71 | throw new Error('in realpathChild without parent being in realpath cache')
|
72 |
|
73 | const realish = resolve(realdir, base)
|
74 | return lstatCached(realish, stcache).then(st => {
|
75 | if (!st.isSymbolicLink()) {
|
76 | rpcache.set(resolve(dir, base), realish)
|
77 | return realish
|
78 | }
|
79 |
|
80 | let res
|
81 | return readlink(realish).then(target => {
|
82 | const resolved = res = resolve(realdir, target)
|
83 | if (realish === resolved)
|
84 | throw eloop(realish)
|
85 |
|
86 | return realpathCached(resolved, rpcache, stcache, depth + 1)
|
87 | }).then(real => {
|
88 | rpcache.set(resolve(dir, base), real)
|
89 | return real
|
90 | })
|
91 | })
|
92 | }
|
93 |
|
94 | module.exports = realpathCached
|