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