You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
167 lines
4.2 KiB
167 lines
4.2 KiB
'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' |
|
|
|
/* istanbul ignore next */ |
|
const needEISDIRHandled = fs.lchown && |
|
!process.version.match(/v1[1-9]+\./) && |
|
!process.version.match(/v10\.[6-9]/) |
|
|
|
const lchownSync = (path, uid, gid) => { |
|
try { |
|
return fs[LCHOWNSYNC](path, uid, gid) |
|
} catch (er) { |
|
if (er.code !== 'ENOENT') |
|
throw er |
|
} |
|
} |
|
|
|
/* istanbul ignore next */ |
|
const chownSync = (path, uid, gid) => { |
|
try { |
|
return fs.chownSync(path, uid, gid) |
|
} catch (er) { |
|
if (er.code !== 'ENOENT') |
|
throw er |
|
} |
|
} |
|
|
|
/* 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 lchownSync(path, uid, gid) |
|
} catch (er) { |
|
if (er.code !== 'EISDIR') |
|
throw er |
|
chownSync(path, uid, gid) |
|
} |
|
} |
|
: (path, uid, gid) => 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 chown = (cpath, uid, gid, cb) => { |
|
fs[LCHOWN](cpath, uid, gid, handleEISDIR(cpath, uid, gid, er => { |
|
// Skip ENOENT error |
|
cb(er && er.code !== 'ENOENT' ? er : null) |
|
})) |
|
} |
|
|
|
const chownrKid = (p, child, uid, gid, cb) => { |
|
if (typeof child === 'string') |
|
return fs.lstat(path.resolve(p, child), (er, stats) => { |
|
// Skip ENOENT error |
|
if (er) |
|
return cb(er.code !== 'ENOENT' ? er : null) |
|
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) |
|
chown(cpath, uid, gid, cb) |
|
}) |
|
} else { |
|
const cpath = path.resolve(p, child.name) |
|
chown(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) { |
|
if (er.code === 'ENOENT') |
|
return cb() |
|
else if (er.code !== 'ENOTDIR' && er.code !== 'ENOTSUP') |
|
return cb(er) |
|
} |
|
if (er || !children.length) |
|
return chown(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 chown(p, uid, gid, cb) |
|
} |
|
|
|
children.forEach(child => chownrKid(p, child, uid, gid, then)) |
|
}) |
|
} |
|
|
|
const chownrKidSync = (p, child, uid, gid) => { |
|
if (typeof child === 'string') { |
|
try { |
|
const stats = fs.lstatSync(path.resolve(p, child)) |
|
stats.name = child |
|
child = stats |
|
} catch (er) { |
|
if (er.code === 'ENOENT') |
|
return |
|
else |
|
throw er |
|
} |
|
} |
|
|
|
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.code === 'ENOENT') |
|
return |
|
else if (er.code === 'ENOTDIR' || er.code === 'ENOTSUP') |
|
return handleEISDirSync(p, uid, gid) |
|
else |
|
throw er |
|
} |
|
|
|
if (children && children.length) |
|
children.forEach(child => chownrKidSync(p, child, uid, gid)) |
|
|
|
return handleEISDirSync(p, uid, gid) |
|
} |
|
|
|
module.exports = chownr |
|
chownr.sync = chownrSync
|
|
|