mirror of https://github.com/lukechilds/node.git
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.
191 lines
6.1 KiB
191 lines
6.1 KiB
|
|
var log = require("./log.js")
|
|
, fs = require("graceful-fs")
|
|
, path = require("path")
|
|
, npm = require("../npm.js")
|
|
, exec = require("./exec.js")
|
|
, uidNumber = require("./uid-number.js")
|
|
, umask = process.umask()
|
|
, umaskOrig = umask
|
|
, addedUmaskExit = false
|
|
, mkdirCache = {}
|
|
|
|
module.exports = mkdir
|
|
function mkdir (ensure, mode, uid, gid, noChmod, cb_) {
|
|
if (typeof cb_ !== "function") cb_ = noChmod, noChmod = null
|
|
if (typeof cb_ !== "function") cb_ = gid, gid = null
|
|
if (typeof cb_ !== "function") cb_ = uid, uid = null
|
|
if (typeof cb_ !== "function") cb_ = mode, mode = npm.modes.exec
|
|
|
|
if (mode & umask) {
|
|
log.verbose(mode.toString(8), "umasking from "+umask.toString(8))
|
|
process.umask(umask = 0)
|
|
if (!addedUmaskExit) {
|
|
addedUmaskExit = true
|
|
process.on("exit", function () { process.umask(umask = umaskOrig) })
|
|
}
|
|
}
|
|
|
|
ensure = path.resolve(ensure).replace(/\/+$/, '')
|
|
|
|
// mkdir("/") should not do anything, since that always exists.
|
|
if (!ensure
|
|
|| ( process.platform === "win32"
|
|
&& ensure.match(/^[a-zA-Z]:(\\|\/)?$/))) {
|
|
return cb_()
|
|
}
|
|
|
|
if (mkdirCache.hasOwnProperty(ensure)) {
|
|
return mkdirCache[ensure].push(cb_)
|
|
}
|
|
mkdirCache[ensure] = [cb_]
|
|
|
|
function cb (er) {
|
|
var cbs = mkdirCache[ensure]
|
|
delete mkdirCache[ensure]
|
|
cbs.forEach(function (c) { c(er) })
|
|
}
|
|
|
|
if (uid === null && gid === null) {
|
|
return mkdir_(ensure, mode, uid, gid, noChmod, cb)
|
|
}
|
|
|
|
uidNumber(uid, gid, function (er, uid, gid) {
|
|
if (er) return cb(er)
|
|
mkdir_(ensure, mode, uid, gid, noChmod, cb)
|
|
})
|
|
}
|
|
|
|
function mkdir_ (ensure, mode, uid, gid, noChmod, cb) {
|
|
// if it's already a dir, then just check the bits and owner.
|
|
fs.stat(ensure, function (er, s) {
|
|
if (s && s.isDirectory()) {
|
|
// check mode, uid, and gid.
|
|
if ((noChmod || (s.mode & mode) === mode)
|
|
&& (typeof uid !== "number" || s.uid === uid)
|
|
&& (typeof gid !== "number" || s.gid === gid)) return cb()
|
|
return done(ensure, mode, uid, gid, noChmod, cb)
|
|
}
|
|
return walkDirs(ensure, mode, uid, gid, noChmod, cb)
|
|
})
|
|
}
|
|
|
|
function done (ensure, mode, uid, gid, noChmod, cb) {
|
|
// now the directory has been created.
|
|
// chown it to the desired uid/gid
|
|
// Don't chown the npm.root dir, though, in case we're
|
|
// in unsafe-perm mode.
|
|
log.verbose("done: "+ensure+" "+mode.toString(8), "mkdir")
|
|
|
|
// only chmod if noChmod isn't set.
|
|
var d = done_(ensure, mode, uid, gid, cb)
|
|
if (noChmod) return d()
|
|
fs.chmod(ensure, mode, d)
|
|
}
|
|
|
|
function done_ (ensure, mode, uid, gid, cb) {
|
|
return function (er) {
|
|
if (er
|
|
|| ensure === npm.dir
|
|
|| typeof uid !== "number"
|
|
|| typeof gid !== "number"
|
|
|| npm.config.get("unsafe-perm")) return cb(er)
|
|
uid = Math.floor(uid)
|
|
gid = Math.floor(gid)
|
|
fs.chown(ensure, uid, gid, cb)
|
|
}
|
|
}
|
|
|
|
var pathSplit = process.platform === "win32" ? /\/|\\/ : "/"
|
|
function walkDirs (ensure, mode, uid, gid, noChmod, cb) {
|
|
var dirs = ensure.split(pathSplit)
|
|
, walker = []
|
|
, foundUID = null
|
|
, foundGID = null
|
|
|
|
// gobble the "/" or C: first
|
|
walker.push(dirs.shift())
|
|
|
|
// The loop that goes through and stats each dir.
|
|
;(function S (d) {
|
|
// no more directory steps left.
|
|
if (d === undefined) {
|
|
// do the chown stuff
|
|
return done(ensure, mode, uid, gid, noChmod, cb)
|
|
}
|
|
|
|
// get the absolute dir for the next piece being stat'd
|
|
walker.push(d)
|
|
var dir = walker.join(path.SPLIT_CHAR)
|
|
|
|
// stat callback lambda
|
|
fs.stat(dir, function STATCB (er, s) {
|
|
if (er) {
|
|
// the stat failed - directory does not exist.
|
|
|
|
log.verbose(er.message, "mkdir (expected) error")
|
|
|
|
// use the same uid/gid as the nearest parent, if not set.
|
|
if (foundUID !== null) uid = foundUID
|
|
if (foundGID !== null) gid = foundGID
|
|
|
|
// make the directory
|
|
fs.mkdir(dir, mode, function MKDIRCB (er) {
|
|
// since stat and mkdir are done as two separate syscalls,
|
|
// operating on a path rather than a file descriptor, it's
|
|
// possible that the directory didn't exist when we did
|
|
// the stat, but then *did* exist when we go to to the mkdir.
|
|
// If we didn't care about uid/gid, we could just mkdir
|
|
// repeatedly, failing on any error other than "EEXIST".
|
|
if (er && er.message.indexOf("EEXIST") === 0) {
|
|
return fs.stat(dir, STATCB)
|
|
}
|
|
|
|
// any other kind of error is not saveable.
|
|
if (er) return cb(er)
|
|
|
|
// at this point, we've just created a new directory successfully.
|
|
|
|
// if we care about permissions
|
|
if (!npm.config.get("unsafe-perm") // care about permissions
|
|
// specified a uid and gid
|
|
&& uid !== null
|
|
&& gid !== null ) {
|
|
// set the proper ownership
|
|
return fs.chown(dir, uid, gid, function (er) {
|
|
if (er) return cb(er)
|
|
// attack the next portion of the path.
|
|
S(dirs.shift())
|
|
})
|
|
} else {
|
|
// either we don't care about ownership, or it's already right.
|
|
S(dirs.shift())
|
|
}
|
|
}) // mkdir
|
|
|
|
} else {
|
|
// the stat succeeded.
|
|
if (s.isDirectory()) {
|
|
// if it's a directory, that's good.
|
|
// if the uid and gid aren't already set, then try to preserve
|
|
// the ownership on up the tree. Things in ~ remain owned by
|
|
// the user, things in / remain owned by root, etc.
|
|
if (uid === null && typeof s.uid === "number") foundUID = s.uid
|
|
if (gid === null && typeof s.gid === "number") foundGID = s.gid
|
|
|
|
// move onto next portion of path
|
|
S(dirs.shift())
|
|
|
|
} else {
|
|
// the stat succeeded, but it's not a directory
|
|
log.verbose(dir, "mkdir exists")
|
|
log.silly(s, "stat("+dir+")")
|
|
log.verbose(s.isDirectory(), "isDirectory()")
|
|
cb(new Error("Failed to mkdir "+dir+": File exists"))
|
|
}// if (isDirectory) else
|
|
} // if (stat failed) else
|
|
}) // stat
|
|
|
|
// start the S function with the first item in the list of directories.
|
|
})(dirs.shift())
|
|
}
|
|
|