230 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			230 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
| '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)
 | |
| }
 |