207 lines
5.1 KiB
JavaScript
207 lines
5.1 KiB
JavaScript
'use strict'
|
|
// wrapper around mkdirp for tar's needs.
|
|
|
|
// TODO: This should probably be a class, not functionally
|
|
// passing around state in a gazillion args.
|
|
|
|
const mkdirp = require('mkdirp')
|
|
const fs = require('fs')
|
|
const path = require('path')
|
|
const chownr = require('chownr')
|
|
|
|
class SymlinkError extends Error {
|
|
constructor (symlink, path) {
|
|
super('Cannot extract through symbolic link')
|
|
this.path = path
|
|
this.symlink = symlink
|
|
}
|
|
|
|
get name () {
|
|
return 'SylinkError'
|
|
}
|
|
}
|
|
|
|
class CwdError extends Error {
|
|
constructor (path, code) {
|
|
super(code + ': Cannot cd into \'' + path + '\'')
|
|
this.path = path
|
|
this.code = code
|
|
}
|
|
|
|
get name () {
|
|
return 'CwdError'
|
|
}
|
|
}
|
|
|
|
const mkdir = module.exports = (dir, opt, cb) => {
|
|
// if there's any overlap between mask and mode,
|
|
// then we'll need an explicit chmod
|
|
const umask = opt.umask
|
|
const mode = opt.mode | 0o0700
|
|
const needChmod = (mode & umask) !== 0
|
|
|
|
const uid = opt.uid
|
|
const gid = opt.gid
|
|
const doChown = typeof uid === 'number' &&
|
|
typeof gid === 'number' &&
|
|
( uid !== opt.processUid || gid !== opt.processGid )
|
|
|
|
const preserve = opt.preserve
|
|
const unlink = opt.unlink
|
|
const cache = opt.cache
|
|
const cwd = opt.cwd
|
|
|
|
const done = (er, created) => {
|
|
if (er)
|
|
cb(er)
|
|
else {
|
|
cache.set(dir, true)
|
|
if (created && doChown)
|
|
chownr(created, uid, gid, er => done(er))
|
|
else if (needChmod)
|
|
fs.chmod(dir, mode, cb)
|
|
else
|
|
cb()
|
|
}
|
|
}
|
|
|
|
if (cache && cache.get(dir) === true)
|
|
return done()
|
|
|
|
if (dir === cwd)
|
|
return fs.lstat(dir, (er, st) => {
|
|
if (er || !st.isDirectory())
|
|
er = new CwdError(dir, er && er.code || 'ENOTDIR')
|
|
done(er)
|
|
})
|
|
|
|
if (preserve)
|
|
return mkdirp(dir, mode, done)
|
|
|
|
const sub = path.relative(cwd, dir)
|
|
const parts = sub.split(/\/|\\/)
|
|
mkdir_(cwd, parts, mode, cache, unlink, cwd, null, done)
|
|
}
|
|
|
|
const mkdir_ = (base, parts, mode, cache, unlink, cwd, created, cb) => {
|
|
if (!parts.length)
|
|
return cb(null, created)
|
|
const p = parts.shift()
|
|
const part = base + '/' + p
|
|
if (cache.get(part))
|
|
return mkdir_(part, parts, mode, cache, unlink, cwd, created, cb)
|
|
fs.mkdir(part, mode, onmkdir(part, parts, mode, cache, unlink, cwd, created, cb))
|
|
}
|
|
|
|
const onmkdir = (part, parts, mode, cache, unlink, cwd, created, cb) => er => {
|
|
if (er) {
|
|
if (er.path && path.dirname(er.path) === cwd &&
|
|
(er.code === 'ENOTDIR' || er.code === 'ENOENT'))
|
|
return cb(new CwdError(cwd, er.code))
|
|
|
|
fs.lstat(part, (statEr, st) => {
|
|
if (statEr)
|
|
cb(statEr)
|
|
else if (st.isDirectory())
|
|
mkdir_(part, parts, mode, cache, unlink, cwd, created, cb)
|
|
else if (unlink)
|
|
fs.unlink(part, er => {
|
|
if (er)
|
|
return cb(er)
|
|
fs.mkdir(part, mode, onmkdir(part, parts, mode, cache, unlink, cwd, created, cb))
|
|
})
|
|
else if (st.isSymbolicLink())
|
|
return cb(new SymlinkError(part, part + '/' + parts.join('/')))
|
|
else
|
|
cb(er)
|
|
})
|
|
} else {
|
|
created = created || part
|
|
mkdir_(part, parts, mode, cache, unlink, cwd, created, cb)
|
|
}
|
|
}
|
|
|
|
const mkdirSync = module.exports.sync = (dir, opt) => {
|
|
// if there's any overlap between mask and mode,
|
|
// then we'll need an explicit chmod
|
|
const umask = opt.umask
|
|
const mode = opt.mode | 0o0700
|
|
const needChmod = (mode & umask) !== 0
|
|
|
|
const uid = opt.uid
|
|
const gid = opt.gid
|
|
const doChown = typeof uid === 'number' &&
|
|
typeof gid === 'number' &&
|
|
( uid !== opt.processUid || gid !== opt.processGid )
|
|
|
|
const preserve = opt.preserve
|
|
const unlink = opt.unlink
|
|
const cache = opt.cache
|
|
const cwd = opt.cwd
|
|
|
|
const done = (created) => {
|
|
cache.set(dir, true)
|
|
if (created && doChown)
|
|
chownr.sync(created, uid, gid)
|
|
if (needChmod)
|
|
fs.chmodSync(dir, mode)
|
|
}
|
|
|
|
if (cache && cache.get(dir) === true)
|
|
return done()
|
|
|
|
if (dir === cwd) {
|
|
let ok = false
|
|
let code = 'ENOTDIR'
|
|
try {
|
|
ok = fs.lstatSync(dir).isDirectory()
|
|
} catch (er) {
|
|
code = er.code
|
|
} finally {
|
|
if (!ok)
|
|
throw new CwdError(dir, code)
|
|
}
|
|
done()
|
|
return
|
|
}
|
|
|
|
if (preserve)
|
|
return done(mkdirp.sync(dir, mode))
|
|
|
|
const sub = path.relative(cwd, dir)
|
|
const parts = sub.split(/\/|\\/)
|
|
let created = null
|
|
for (let p = parts.shift(), part = cwd;
|
|
p && (part += '/' + p);
|
|
p = parts.shift()) {
|
|
|
|
if (cache.get(part))
|
|
continue
|
|
|
|
try {
|
|
fs.mkdirSync(part, mode)
|
|
created = created || part
|
|
cache.set(part, true)
|
|
} catch (er) {
|
|
if (er.path && path.dirname(er.path) === cwd &&
|
|
(er.code === 'ENOTDIR' || er.code === 'ENOENT'))
|
|
return new CwdError(cwd, er.code)
|
|
|
|
const st = fs.lstatSync(part)
|
|
if (st.isDirectory()) {
|
|
cache.set(part, true)
|
|
continue
|
|
} else if (unlink) {
|
|
fs.unlinkSync(part)
|
|
fs.mkdirSync(part, mode)
|
|
created = created || part
|
|
cache.set(part, true)
|
|
continue
|
|
} else if (st.isSymbolicLink())
|
|
return new SymlinkError(part, part + '/' + parts.join('/'))
|
|
}
|
|
}
|
|
|
|
return done(created)
|
|
}
|