| const cache = new Map() |
| const fs = require('fs') |
| const { dirname, resolve } = require('path') |
| |
| |
| const lstat = path => new Promise((res, rej) => |
| fs.lstat(path, (er, st) => er ? rej(er) : res(st))) |
| |
| const inferOwner = path => { |
| path = resolve(path) |
| if (cache.has(path)) |
| return Promise.resolve(cache.get(path)) |
| |
| const statThen = st => { |
| const { uid, gid } = st |
| cache.set(path, { uid, gid }) |
| return { uid, gid } |
| } |
| const parent = dirname(path) |
| const parentTrap = parent === path ? null : er => { |
| return inferOwner(parent).then((owner) => { |
| cache.set(path, owner) |
| return owner |
| }) |
| } |
| return lstat(path).then(statThen, parentTrap) |
| } |
| |
| const inferOwnerSync = path => { |
| path = resolve(path) |
| if (cache.has(path)) |
| return cache.get(path) |
| |
| const parent = dirname(path) |
| |
| // avoid obscuring call site by re-throwing |
| // "catch" the error by returning from a finally, |
| // only if we're not at the root, and the parent call works. |
| let threw = true |
| try { |
| const st = fs.lstatSync(path) |
| threw = false |
| const { uid, gid } = st |
| cache.set(path, { uid, gid }) |
| return { uid, gid } |
| } finally { |
| if (threw && parent !== path) { |
| const owner = inferOwnerSync(parent) |
| cache.set(path, owner) |
| return owner // eslint-disable-line no-unsafe-finally |
| } |
| } |
| } |
| |
| const inflight = new Map() |
| module.exports = path => { |
| path = resolve(path) |
| if (inflight.has(path)) |
| return Promise.resolve(inflight.get(path)) |
| const p = inferOwner(path).then(owner => { |
| inflight.delete(path) |
| return owner |
| }) |
| inflight.set(path, p) |
| return p |
| } |
| module.exports.sync = inferOwnerSync |
| module.exports.clearCache = () => { |
| cache.clear() |
| inflight.clear() |
| } |