| 'use strict' |
| const fs = require('fs') |
| const path = require('path') |
| |
| /* istanbul ignore next */ |
| const LCHOWN = fs.lchown ? 'lchown' : 'chown' |
| /* istanbul ignore next */ |
| const LCHOWNSYNC = fs.lchownSync ? 'lchownSync' : 'chownSync' |
| |
| // fs.readdir could only accept an options object as of node v6 |
| const nodeVersion = process.version |
| let readdir = (path, options, cb) => fs.readdir(path, options, cb) |
| let readdirSync = (path, options) => fs.readdirSync(path, options) |
| /* istanbul ignore next */ |
| if (/^v4\./.test(nodeVersion)) |
| readdir = (path, options, cb) => fs.readdir(path, cb) |
| |
| const chownrKid = (p, child, uid, gid, cb) => { |
| if (typeof child === 'string') |
| return fs.lstat(path.resolve(p, child), (er, stats) => { |
| if (er) |
| return cb(er) |
| stats.name = child |
| chownrKid(p, stats, uid, gid, cb) |
| }) |
| |
| if (child.isDirectory()) { |
| chownr(path.resolve(p, child.name), uid, gid, er => { |
| if (er) |
| return cb(er) |
| fs[LCHOWN](path.resolve(p, child.name), uid, gid, cb) |
| }) |
| } else |
| fs[LCHOWN](path.resolve(p, child.name), uid, gid, cb) |
| } |
| |
| |
| const chownr = (p, uid, gid, cb) => { |
| readdir(p, { withFileTypes: true }, (er, children) => { |
| // any error other than ENOTDIR or ENOTSUP means it's not readable, |
| // or doesn't exist. give up. |
| if (er && er.code !== 'ENOTDIR' && er.code !== 'ENOTSUP') |
| return cb(er) |
| if (er || !children.length) return fs[LCHOWN](p, uid, gid, cb) |
| |
| let len = children.length |
| let errState = null |
| const then = er => { |
| if (errState) return |
| if (er) return cb(errState = er) |
| if (-- len === 0) return fs[LCHOWN](p, uid, gid, cb) |
| } |
| |
| children.forEach(child => chownrKid(p, child, uid, gid, then)) |
| }) |
| } |
| |
| const chownrKidSync = (p, child, uid, gid) => { |
| if (typeof child === 'string') { |
| const stats = fs.lstatSync(path.resolve(p, child)) |
| stats.name = child |
| child = stats |
| } |
| |
| if (child.isDirectory()) |
| chownrSync(path.resolve(p, child.name), uid, gid) |
| |
| fs[LCHOWNSYNC](path.resolve(p, child.name), uid, gid) |
| } |
| |
| const chownrSync = (p, uid, gid) => { |
| let children |
| try { |
| children = readdirSync(p, { withFileTypes: true }) |
| } catch (er) { |
| if (er && er.code === 'ENOTDIR' && er.code !== 'ENOTSUP') |
| return fs[LCHOWNSYNC](p, uid, gid) |
| throw er |
| } |
| |
| if (children.length) |
| children.forEach(child => chownrKidSync(p, child, uid, gid)) |
| |
| return fs[LCHOWNSYNC](p, uid, gid) |
| } |
| |
| module.exports = chownr |
| chownr.sync = chownrSync |