1'use strict' 2// wrapper around mkdirp for tar's needs. 3 4// TODO: This should probably be a class, not functionally 5// passing around state in a gazillion args. 6 7const mkdirp = require('mkdirp') 8const fs = require('fs') 9const path = require('path') 10const chownr = require('chownr') 11 12class SymlinkError extends Error { 13 constructor (symlink, path) { 14 super('Cannot extract through symbolic link') 15 this.path = path 16 this.symlink = symlink 17 } 18 19 get name () { 20 return 'SylinkError' 21 } 22} 23 24class CwdError extends Error { 25 constructor (path, code) { 26 super(code + ': Cannot cd into \'' + path + '\'') 27 this.path = path 28 this.code = code 29 } 30 31 get name () { 32 return 'CwdError' 33 } 34} 35 36const mkdir = module.exports = (dir, opt, cb) => { 37 // if there's any overlap between mask and mode, 38 // then we'll need an explicit chmod 39 const umask = opt.umask 40 const mode = opt.mode | 0o0700 41 const needChmod = (mode & umask) !== 0 42 43 const uid = opt.uid 44 const gid = opt.gid 45 const doChown = typeof uid === 'number' && 46 typeof gid === 'number' && 47 ( uid !== opt.processUid || gid !== opt.processGid ) 48 49 const preserve = opt.preserve 50 const unlink = opt.unlink 51 const cache = opt.cache 52 const cwd = opt.cwd 53 54 const done = (er, created) => { 55 if (er) 56 cb(er) 57 else { 58 cache.set(dir, true) 59 if (created && doChown) 60 chownr(created, uid, gid, er => done(er)) 61 else if (needChmod) 62 fs.chmod(dir, mode, cb) 63 else 64 cb() 65 } 66 } 67 68 if (cache && cache.get(dir) === true) 69 return done() 70 71 if (dir === cwd) 72 return fs.stat(dir, (er, st) => { 73 if (er || !st.isDirectory()) 74 er = new CwdError(dir, er && er.code || 'ENOTDIR') 75 done(er) 76 }) 77 78 if (preserve) 79 return mkdirp(dir, mode, done) 80 81 const sub = path.relative(cwd, dir) 82 const parts = sub.split(/\/|\\/) 83 mkdir_(cwd, parts, mode, cache, unlink, cwd, null, done) 84} 85 86const mkdir_ = (base, parts, mode, cache, unlink, cwd, created, cb) => { 87 if (!parts.length) 88 return cb(null, created) 89 const p = parts.shift() 90 const part = base + '/' + p 91 if (cache.get(part)) 92 return mkdir_(part, parts, mode, cache, unlink, cwd, created, cb) 93 fs.mkdir(part, mode, onmkdir(part, parts, mode, cache, unlink, cwd, created, cb)) 94} 95 96const onmkdir = (part, parts, mode, cache, unlink, cwd, created, cb) => er => { 97 if (er) { 98 if (er.path && path.dirname(er.path) === cwd && 99 (er.code === 'ENOTDIR' || er.code === 'ENOENT')) 100 return cb(new CwdError(cwd, er.code)) 101 102 fs.lstat(part, (statEr, st) => { 103 if (statEr) 104 cb(statEr) 105 else if (st.isDirectory()) 106 mkdir_(part, parts, mode, cache, unlink, cwd, created, cb) 107 else if (unlink) 108 fs.unlink(part, er => { 109 if (er) 110 return cb(er) 111 fs.mkdir(part, mode, onmkdir(part, parts, mode, cache, unlink, cwd, created, cb)) 112 }) 113 else if (st.isSymbolicLink()) 114 return cb(new SymlinkError(part, part + '/' + parts.join('/'))) 115 else 116 cb(er) 117 }) 118 } else { 119 created = created || part 120 mkdir_(part, parts, mode, cache, unlink, cwd, created, cb) 121 } 122} 123 124const mkdirSync = module.exports.sync = (dir, opt) => { 125 // if there's any overlap between mask and mode, 126 // then we'll need an explicit chmod 127 const umask = opt.umask 128 const mode = opt.mode | 0o0700 129 const needChmod = (mode & umask) !== 0 130 131 const uid = opt.uid 132 const gid = opt.gid 133 const doChown = typeof uid === 'number' && 134 typeof gid === 'number' && 135 ( uid !== opt.processUid || gid !== opt.processGid ) 136 137 const preserve = opt.preserve 138 const unlink = opt.unlink 139 const cache = opt.cache 140 const cwd = opt.cwd 141 142 const done = (created) => { 143 cache.set(dir, true) 144 if (created && doChown) 145 chownr.sync(created, uid, gid) 146 if (needChmod) 147 fs.chmodSync(dir, mode) 148 } 149 150 if (cache && cache.get(dir) === true) 151 return done() 152 153 if (dir === cwd) { 154 let ok = false 155 let code = 'ENOTDIR' 156 try { 157 ok = fs.statSync(dir).isDirectory() 158 } catch (er) { 159 code = er.code 160 } finally { 161 if (!ok) 162 throw new CwdError(dir, code) 163 } 164 done() 165 return 166 } 167 168 if (preserve) 169 return done(mkdirp.sync(dir, mode)) 170 171 const sub = path.relative(cwd, dir) 172 const parts = sub.split(/\/|\\/) 173 let created = null 174 for (let p = parts.shift(), part = cwd; 175 p && (part += '/' + p); 176 p = parts.shift()) { 177 178 if (cache.get(part)) 179 continue 180 181 try { 182 fs.mkdirSync(part, mode) 183 created = created || part 184 cache.set(part, true) 185 } catch (er) { 186 if (er.path && path.dirname(er.path) === cwd && 187 (er.code === 'ENOTDIR' || er.code === 'ENOENT')) 188 return new CwdError(cwd, er.code) 189 190 const st = fs.lstatSync(part) 191 if (st.isDirectory()) { 192 cache.set(part, true) 193 continue 194 } else if (unlink) { 195 fs.unlinkSync(part) 196 fs.mkdirSync(part, mode) 197 created = created || part 198 cache.set(part, true) 199 continue 200 } else if (st.isSymbolicLink()) 201 return new SymlinkError(part, part + '/' + parts.join('/')) 202 } 203 } 204 205 return done(created) 206} 207