| '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') |
| const normPath = require('./normalize-windows-path.js') |
| |
| 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 cGet = (cache, key) => cache.get(normPath(key)) |
| const cSet = (cache, key, val) => cache.set(normPath(key), val) |
| |
| const checkCwd = (dir, cb) => { |
| fs.stat(dir, (er, st) => { |
| if (er || !st.isDirectory()) |
| er = new CwdError(dir, er && er.code || 'ENOTDIR') |
| cb(er) |
| }) |
| } |
| |
| module.exports = (dir, opt, cb) => { |
| dir = normPath(dir) |
| |
| // 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 = normPath(opt.cwd) |
| |
| const done = (er, created) => { |
| if (er) |
| cb(er) |
| else { |
| cSet(cache, dir, true) |
| if (created && doChown) |
| chownr(created, uid, gid, er => done(er)) |
| else if (needChmod) |
| fs.chmod(dir, mode, cb) |
| else |
| cb() |
| } |
| } |
| |
| if (cache && cGet(cache, dir) === true) |
| return done() |
| |
| if (dir === cwd) |
| return checkCwd(dir, done) |
| |
| if (preserve) |
| return mkdirp(dir, {mode}).then(made => done(null, made), done) |
| |
| const sub = normPath(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 = normPath(path.resolve(base + '/' + p)) |
| if (cGet(cache, 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) { |
| fs.lstat(part, (statEr, st) => { |
| if (statEr) { |
| statEr.path = statEr.path && normPath(statEr.path) |
| 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 checkCwdSync = dir => { |
| let ok = false |
| let code = 'ENOTDIR' |
| try { |
| ok = fs.statSync(dir).isDirectory() |
| } catch (er) { |
| code = er.code |
| } finally { |
| if (!ok) |
| throw new CwdError(dir, code) |
| } |
| } |
| |
| module.exports.sync = (dir, opt) => { |
| dir = normPath(dir) |
| // 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 = normPath(opt.cwd) |
| |
| const done = (created) => { |
| cSet(cache, dir, true) |
| if (created && doChown) |
| chownr.sync(created, uid, gid) |
| if (needChmod) |
| fs.chmodSync(dir, mode) |
| } |
| |
| if (cache && cGet(cache, dir) === true) |
| return done() |
| |
| if (dir === cwd) { |
| checkCwdSync(cwd) |
| return done() |
| } |
| |
| if (preserve) |
| return done(mkdirp.sync(dir, mode)) |
| |
| const sub = normPath(path.relative(cwd, dir)) |
| const parts = sub.split('/') |
| let created = null |
| for (let p = parts.shift(), part = cwd; |
| p && (part += '/' + p); |
| p = parts.shift()) { |
| part = normPath(path.resolve(part)) |
| if (cGet(cache, part)) |
| continue |
| |
| try { |
| fs.mkdirSync(part, mode) |
| created = created || part |
| cSet(cache, part, true) |
| } catch (er) { |
| const st = fs.lstatSync(part) |
| if (st.isDirectory()) { |
| cSet(cache, part, true) |
| continue |
| } else if (unlink) { |
| fs.unlinkSync(part) |
| fs.mkdirSync(part, mode) |
| created = created || part |
| cSet(cache, part, true) |
| continue |
| } else if (st.isSymbolicLink()) |
| return new SymlinkError(part, part + '/' + parts.join('/')) |
| } |
| } |
| |
| return done(created) |
| } |