• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1const cache = new Map()
2const fs = require('fs')
3const { dirname, resolve } = require('path')
4
5
6const lstat = path => new Promise((res, rej) =>
7  fs.lstat(path, (er, st) => er ? rej(er) : res(st)))
8
9const inferOwner = path => {
10  path = resolve(path)
11  if (cache.has(path))
12    return Promise.resolve(cache.get(path))
13
14  const statThen = st => {
15    const { uid, gid } = st
16    cache.set(path, { uid, gid })
17    return { uid, gid }
18  }
19  const parent = dirname(path)
20  const parentTrap = parent === path ? null : er => {
21    return inferOwner(parent).then((owner) => {
22      cache.set(path, owner)
23      return owner
24    })
25  }
26  return lstat(path).then(statThen, parentTrap)
27}
28
29const inferOwnerSync = path => {
30  path = resolve(path)
31  if (cache.has(path))
32    return cache.get(path)
33
34  const parent = dirname(path)
35
36  // avoid obscuring call site by re-throwing
37  // "catch" the error by returning from a finally,
38  // only if we're not at the root, and the parent call works.
39  let threw = true
40  try {
41    const st = fs.lstatSync(path)
42    threw = false
43    const { uid, gid } = st
44    cache.set(path, { uid, gid })
45    return { uid, gid }
46  } finally {
47    if (threw && parent !== path) {
48      const owner = inferOwnerSync(parent)
49      cache.set(path, owner)
50      return owner // eslint-disable-line no-unsafe-finally
51    }
52  }
53}
54
55const inflight = new Map()
56module.exports = path => {
57  path = resolve(path)
58  if (inflight.has(path))
59    return Promise.resolve(inflight.get(path))
60  const p = inferOwner(path).then(owner => {
61    inflight.delete(path)
62    return owner
63  })
64  inflight.set(path, p)
65  return p
66}
67module.exports.sync = inferOwnerSync
68module.exports.clearCache = () => {
69  cache.clear()
70  inflight.clear()
71}
72