|
|
- '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)
- }
- }
- : (path, uid, gid) => fs[LCHOWNSYNC](path, uid, gid)
-
- // 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
|