1'use strict' 2const fs = require('fs') 3const path = require('path') 4 5/* istanbul ignore next */ 6const LCHOWN = fs.lchown ? 'lchown' : 'chown' 7/* istanbul ignore next */ 8const LCHOWNSYNC = fs.lchownSync ? 'lchownSync' : 'chownSync' 9 10/* istanbul ignore next */ 11const needEISDIRHandled = fs.lchown && 12 !process.version.match(/v1[1-9]+\./) && 13 !process.version.match(/v10\.[6-9]/) 14 15const lchownSync = (path, uid, gid) => { 16 try { 17 return fs[LCHOWNSYNC](path, uid, gid) 18 } catch (er) { 19 if (er.code !== 'ENOENT') 20 throw er 21 } 22} 23 24/* istanbul ignore next */ 25const chownSync = (path, uid, gid) => { 26 try { 27 return fs.chownSync(path, uid, gid) 28 } catch (er) { 29 if (er.code !== 'ENOENT') 30 throw er 31 } 32} 33 34/* istanbul ignore next */ 35const handleEISDIR = 36 needEISDIRHandled ? (path, uid, gid, cb) => er => { 37 // Node prior to v10 had a very questionable implementation of 38 // fs.lchown, which would always try to call fs.open on a directory 39 // Fall back to fs.chown in those cases. 40 if (!er || er.code !== 'EISDIR') 41 cb(er) 42 else 43 fs.chown(path, uid, gid, cb) 44 } 45 : (_, __, ___, cb) => cb 46 47/* istanbul ignore next */ 48const handleEISDirSync = 49 needEISDIRHandled ? (path, uid, gid) => { 50 try { 51 return lchownSync(path, uid, gid) 52 } catch (er) { 53 if (er.code !== 'EISDIR') 54 throw er 55 chownSync(path, uid, gid) 56 } 57 } 58 : (path, uid, gid) => lchownSync(path, uid, gid) 59 60// fs.readdir could only accept an options object as of node v6 61const nodeVersion = process.version 62let readdir = (path, options, cb) => fs.readdir(path, options, cb) 63let readdirSync = (path, options) => fs.readdirSync(path, options) 64/* istanbul ignore next */ 65if (/^v4\./.test(nodeVersion)) 66 readdir = (path, options, cb) => fs.readdir(path, cb) 67 68const chown = (cpath, uid, gid, cb) => { 69 fs[LCHOWN](cpath, uid, gid, handleEISDIR(cpath, uid, gid, er => { 70 // Skip ENOENT error 71 cb(er && er.code !== 'ENOENT' ? er : null) 72 })) 73} 74 75const chownrKid = (p, child, uid, gid, cb) => { 76 if (typeof child === 'string') 77 return fs.lstat(path.resolve(p, child), (er, stats) => { 78 // Skip ENOENT error 79 if (er) 80 return cb(er.code !== 'ENOENT' ? er : null) 81 stats.name = child 82 chownrKid(p, stats, uid, gid, cb) 83 }) 84 85 if (child.isDirectory()) { 86 chownr(path.resolve(p, child.name), uid, gid, er => { 87 if (er) 88 return cb(er) 89 const cpath = path.resolve(p, child.name) 90 chown(cpath, uid, gid, cb) 91 }) 92 } else { 93 const cpath = path.resolve(p, child.name) 94 chown(cpath, uid, gid, cb) 95 } 96} 97 98 99const chownr = (p, uid, gid, cb) => { 100 readdir(p, { withFileTypes: true }, (er, children) => { 101 // any error other than ENOTDIR or ENOTSUP means it's not readable, 102 // or doesn't exist. give up. 103 if (er) { 104 if (er.code === 'ENOENT') 105 return cb() 106 else if (er.code !== 'ENOTDIR' && er.code !== 'ENOTSUP') 107 return cb(er) 108 } 109 if (er || !children.length) 110 return chown(p, uid, gid, cb) 111 112 let len = children.length 113 let errState = null 114 const then = er => { 115 if (errState) 116 return 117 if (er) 118 return cb(errState = er) 119 if (-- len === 0) 120 return chown(p, uid, gid, cb) 121 } 122 123 children.forEach(child => chownrKid(p, child, uid, gid, then)) 124 }) 125} 126 127const chownrKidSync = (p, child, uid, gid) => { 128 if (typeof child === 'string') { 129 try { 130 const stats = fs.lstatSync(path.resolve(p, child)) 131 stats.name = child 132 child = stats 133 } catch (er) { 134 if (er.code === 'ENOENT') 135 return 136 else 137 throw er 138 } 139 } 140 141 if (child.isDirectory()) 142 chownrSync(path.resolve(p, child.name), uid, gid) 143 144 handleEISDirSync(path.resolve(p, child.name), uid, gid) 145} 146 147const chownrSync = (p, uid, gid) => { 148 let children 149 try { 150 children = readdirSync(p, { withFileTypes: true }) 151 } catch (er) { 152 if (er.code === 'ENOENT') 153 return 154 else if (er.code === 'ENOTDIR' || er.code === 'ENOTSUP') 155 return handleEISDirSync(p, uid, gid) 156 else 157 throw er 158 } 159 160 if (children && children.length) 161 children.forEach(child => chownrKidSync(p, child, uid, gid)) 162 163 return handleEISDirSync(p, uid, gid) 164} 165 166module.exports = chownr 167chownr.sync = chownrSync 168