From 233e1975bcb6ddca24fd67818e29222c0de176a8 Mon Sep 17 00:00:00 2001 From: isaacs Date: Wed, 3 Jul 2019 11:32:32 -0700 Subject: [PATCH] Handle EISDIR from lchown in node v8 Fix #20 --- chownr.js | 55 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/chownr.js b/chownr.js index 7e63928..788ba29 100644 --- a/chownr.js +++ b/chownr.js @@ -7,6 +7,34 @@ const LCHOWN = fs.lchown ? 'lchown' : 'chown' /* istanbul ignore next */ const LCHOWNSYNC = fs.lchownSync ? 'lchownSync' : 'chownSync' +const needEISDIRHandled = fs.lchown && !process.version.match(/v[1-9][0-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) @@ -28,10 +56,13 @@ const chownrKid = (p, child, uid, gid, cb) => { chownr(path.resolve(p, child.name), uid, gid, er => { if (er) return cb(er) - fs[LCHOWN](path.resolve(p, child.name), uid, gid, cb) + const cpath = path.resolve(p, child.name) + fs[LCHOWN](cpath, uid, gid, handleEISDIR(cpath, uid, gid, cb)) }) - } else - fs[LCHOWN](path.resolve(p, child.name), uid, gid, cb) + } else { + const cpath = path.resolve(p, child.name) + fs[LCHOWN](cpath, uid, gid, handleEISDIR(cpath, uid, gid, cb)) + } } @@ -41,14 +72,18 @@ const chownr = (p, uid, gid, cb) => { // 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) + 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, cb) + 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)) @@ -65,7 +100,7 @@ const chownrKidSync = (p, child, uid, gid) => { if (child.isDirectory()) chownrSync(path.resolve(p, child.name), uid, gid) - fs[LCHOWNSYNC](path.resolve(p, child.name), uid, gid) + handleEISDirSync(path.resolve(p, child.name), uid, gid) } const chownrSync = (p, uid, gid) => { @@ -74,14 +109,14 @@ const chownrSync = (p, uid, gid) => { children = readdirSync(p, { withFileTypes: true }) } catch (er) { if (er && er.code === 'ENOTDIR' && er.code !== 'ENOTSUP') - return fs[LCHOWNSYNC](p, uid, gid) + return handleEISDirSync(p, uid, gid) throw er } if (children.length) children.forEach(child => chownrKidSync(p, child, uid, gid)) - return fs[LCHOWNSYNC](p, uid, gid) + return handleEISDirSync(p, uid, gid) } module.exports = chownr