| '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' |
| |
| const needEISDIRHandled = fs.lchown && |
| !process.version.match(/v1[1-9]+\./) && |
| !process.version.match(/v10\.[6-9]/) |
| |
| /* istanbul ignore next */ |
| const handleEISDIR = |
| needEISDIRHandled ? (path, uid, gid, cb) => er => { |
| // Node prior to v10 had a very questionable implementation of |
| // fs.lchown, which would always try to call fs.open on a directory |
| // Fall back to fs.chown in those cases. |
| if (!er || er.code !== 'EISDIR') |
| cb(er) |
| else |
| fs.chown(path, uid, gid, cb) |
| } |
| : (_, __, ___, cb) => cb |
| |
| /* istanbul ignore next */ |
| const handleEISDirSync = |
| needEISDIRHandled ? (path, uid, gid) => { |
| try { |
| return fs[LCHOWNSYNC](path, uid, gid) |
| } catch (er) { |
| if (er.code !== 'EISDIR') |
| throw er |
| fs.chownSync(path, uid, gid) |
| } |
| } |
| : fs[LCHOWNSYNC] |
| |
| // 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) |
| const cpath = path.resolve(p, child.name) |
| fs[LCHOWN](cpath, uid, gid, handleEISDIR(cpath, uid, gid, cb)) |
| }) |
| } else { |
| const cpath = path.resolve(p, child.name) |
| fs[LCHOWN](cpath, uid, gid, handleEISDIR(cpath, 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, handleEISDIR(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, handleEISDIR(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) |
| |
| handleEISDirSync(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 handleEISDirSync(p, uid, gid) |
| throw er |
| } |
| |
| if (children.length) |
| children.forEach(child => chownrKidSync(p, child, uid, gid)) |
| |
| return handleEISDirSync(p, uid, gid) |
| } |
| |
| module.exports = chownr |
| chownr.sync = chownrSync |