unpack.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923
  1. 'use strict'
  2. // the PEND/UNPEND stuff tracks whether we're ready to emit end/close yet.
  3. // but the path reservations are required to avoid race conditions where
  4. // parallelized unpack ops may mess with one another, due to dependencies
  5. // (like a Link depending on its target) or destructive operations (like
  6. // clobbering an fs object to create one of a different type.)
  7. const assert = require('assert')
  8. const Parser = require('./parse.js')
  9. const fs = require('fs')
  10. const fsm = require('fs-minipass')
  11. const path = require('path')
  12. const mkdir = require('./mkdir.js')
  13. const wc = require('./winchars.js')
  14. const pathReservations = require('./path-reservations.js')
  15. const stripAbsolutePath = require('./strip-absolute-path.js')
  16. const normPath = require('./normalize-windows-path.js')
  17. const stripSlash = require('./strip-trailing-slashes.js')
  18. const normalize = require('./normalize-unicode.js')
  19. const ONENTRY = Symbol('onEntry')
  20. const CHECKFS = Symbol('checkFs')
  21. const CHECKFS2 = Symbol('checkFs2')
  22. const PRUNECACHE = Symbol('pruneCache')
  23. const ISREUSABLE = Symbol('isReusable')
  24. const MAKEFS = Symbol('makeFs')
  25. const FILE = Symbol('file')
  26. const DIRECTORY = Symbol('directory')
  27. const LINK = Symbol('link')
  28. const SYMLINK = Symbol('symlink')
  29. const HARDLINK = Symbol('hardlink')
  30. const UNSUPPORTED = Symbol('unsupported')
  31. const CHECKPATH = Symbol('checkPath')
  32. const MKDIR = Symbol('mkdir')
  33. const ONERROR = Symbol('onError')
  34. const PENDING = Symbol('pending')
  35. const PEND = Symbol('pend')
  36. const UNPEND = Symbol('unpend')
  37. const ENDED = Symbol('ended')
  38. const MAYBECLOSE = Symbol('maybeClose')
  39. const SKIP = Symbol('skip')
  40. const DOCHOWN = Symbol('doChown')
  41. const UID = Symbol('uid')
  42. const GID = Symbol('gid')
  43. const CHECKED_CWD = Symbol('checkedCwd')
  44. const crypto = require('crypto')
  45. const getFlag = require('./get-write-flag.js')
  46. const platform = process.env.TESTING_TAR_FAKE_PLATFORM || process.platform
  47. const isWindows = platform === 'win32'
  48. const DEFAULT_MAX_DEPTH = 1024
  49. // Unlinks on Windows are not atomic.
  50. //
  51. // This means that if you have a file entry, followed by another
  52. // file entry with an identical name, and you cannot re-use the file
  53. // (because it's a hardlink, or because unlink:true is set, or it's
  54. // Windows, which does not have useful nlink values), then the unlink
  55. // will be committed to the disk AFTER the new file has been written
  56. // over the old one, deleting the new file.
  57. //
  58. // To work around this, on Windows systems, we rename the file and then
  59. // delete the renamed file. It's a sloppy kludge, but frankly, I do not
  60. // know of a better way to do this, given windows' non-atomic unlink
  61. // semantics.
  62. //
  63. // See: https://github.com/npm/node-tar/issues/183
  64. /* istanbul ignore next */
  65. const unlinkFile = (path, cb) => {
  66. if (!isWindows) {
  67. return fs.unlink(path, cb)
  68. }
  69. const name = path + '.DELETE.' + crypto.randomBytes(16).toString('hex')
  70. fs.rename(path, name, er => {
  71. if (er) {
  72. return cb(er)
  73. }
  74. fs.unlink(name, cb)
  75. })
  76. }
  77. /* istanbul ignore next */
  78. const unlinkFileSync = path => {
  79. if (!isWindows) {
  80. return fs.unlinkSync(path)
  81. }
  82. const name = path + '.DELETE.' + crypto.randomBytes(16).toString('hex')
  83. fs.renameSync(path, name)
  84. fs.unlinkSync(name)
  85. }
  86. // this.gid, entry.gid, this.processUid
  87. const uint32 = (a, b, c) =>
  88. a === a >>> 0 ? a
  89. : b === b >>> 0 ? b
  90. : c
  91. // clear the cache if it's a case-insensitive unicode-squashing match.
  92. // we can't know if the current file system is case-sensitive or supports
  93. // unicode fully, so we check for similarity on the maximally compatible
  94. // representation. Err on the side of pruning, since all it's doing is
  95. // preventing lstats, and it's not the end of the world if we get a false
  96. // positive.
  97. // Note that on windows, we always drop the entire cache whenever a
  98. // symbolic link is encountered, because 8.3 filenames are impossible
  99. // to reason about, and collisions are hazards rather than just failures.
  100. const cacheKeyNormalize = path => stripSlash(normPath(normalize(path)))
  101. .toLowerCase()
  102. const pruneCache = (cache, abs) => {
  103. abs = cacheKeyNormalize(abs)
  104. for (const path of cache.keys()) {
  105. const pnorm = cacheKeyNormalize(path)
  106. if (pnorm === abs || pnorm.indexOf(abs + '/') === 0) {
  107. cache.delete(path)
  108. }
  109. }
  110. }
  111. const dropCache = cache => {
  112. for (const key of cache.keys()) {
  113. cache.delete(key)
  114. }
  115. }
  116. class Unpack extends Parser {
  117. constructor (opt) {
  118. if (!opt) {
  119. opt = {}
  120. }
  121. opt.ondone = _ => {
  122. this[ENDED] = true
  123. this[MAYBECLOSE]()
  124. }
  125. super(opt)
  126. this[CHECKED_CWD] = false
  127. this.reservations = pathReservations()
  128. this.transform = typeof opt.transform === 'function' ? opt.transform : null
  129. this.writable = true
  130. this.readable = false
  131. this[PENDING] = 0
  132. this[ENDED] = false
  133. this.dirCache = opt.dirCache || new Map()
  134. if (typeof opt.uid === 'number' || typeof opt.gid === 'number') {
  135. // need both or neither
  136. if (typeof opt.uid !== 'number' || typeof opt.gid !== 'number') {
  137. throw new TypeError('cannot set owner without number uid and gid')
  138. }
  139. if (opt.preserveOwner) {
  140. throw new TypeError(
  141. 'cannot preserve owner in archive and also set owner explicitly')
  142. }
  143. this.uid = opt.uid
  144. this.gid = opt.gid
  145. this.setOwner = true
  146. } else {
  147. this.uid = null
  148. this.gid = null
  149. this.setOwner = false
  150. }
  151. // default true for root
  152. if (opt.preserveOwner === undefined && typeof opt.uid !== 'number') {
  153. this.preserveOwner = process.getuid && process.getuid() === 0
  154. } else {
  155. this.preserveOwner = !!opt.preserveOwner
  156. }
  157. this.processUid = (this.preserveOwner || this.setOwner) && process.getuid ?
  158. process.getuid() : null
  159. this.processGid = (this.preserveOwner || this.setOwner) && process.getgid ?
  160. process.getgid() : null
  161. // prevent excessively deep nesting of subfolders
  162. // set to `Infinity` to remove this restriction
  163. this.maxDepth = typeof opt.maxDepth === 'number'
  164. ? opt.maxDepth
  165. : DEFAULT_MAX_DEPTH
  166. // mostly just for testing, but useful in some cases.
  167. // Forcibly trigger a chown on every entry, no matter what
  168. this.forceChown = opt.forceChown === true
  169. // turn ><?| in filenames into 0xf000-higher encoded forms
  170. this.win32 = !!opt.win32 || isWindows
  171. // do not unpack over files that are newer than what's in the archive
  172. this.newer = !!opt.newer
  173. // do not unpack over ANY files
  174. this.keep = !!opt.keep
  175. // do not set mtime/atime of extracted entries
  176. this.noMtime = !!opt.noMtime
  177. // allow .., absolute path entries, and unpacking through symlinks
  178. // without this, warn and skip .., relativize absolutes, and error
  179. // on symlinks in extraction path
  180. this.preservePaths = !!opt.preservePaths
  181. // unlink files and links before writing. This breaks existing hard
  182. // links, and removes symlink directories rather than erroring
  183. this.unlink = !!opt.unlink
  184. this.cwd = normPath(path.resolve(opt.cwd || process.cwd()))
  185. this.strip = +opt.strip || 0
  186. // if we're not chmodding, then we don't need the process umask
  187. this.processUmask = opt.noChmod ? 0 : process.umask()
  188. this.umask = typeof opt.umask === 'number' ? opt.umask : this.processUmask
  189. // default mode for dirs created as parents
  190. this.dmode = opt.dmode || (0o0777 & (~this.umask))
  191. this.fmode = opt.fmode || (0o0666 & (~this.umask))
  192. this.on('entry', entry => this[ONENTRY](entry))
  193. }
  194. // a bad or damaged archive is a warning for Parser, but an error
  195. // when extracting. Mark those errors as unrecoverable, because
  196. // the Unpack contract cannot be met.
  197. warn (code, msg, data = {}) {
  198. if (code === 'TAR_BAD_ARCHIVE' || code === 'TAR_ABORT') {
  199. data.recoverable = false
  200. }
  201. return super.warn(code, msg, data)
  202. }
  203. [MAYBECLOSE] () {
  204. if (this[ENDED] && this[PENDING] === 0) {
  205. this.emit('prefinish')
  206. this.emit('finish')
  207. this.emit('end')
  208. }
  209. }
  210. [CHECKPATH] (entry) {
  211. const p = normPath(entry.path)
  212. const parts = p.split('/')
  213. if (this.strip) {
  214. if (parts.length < this.strip) {
  215. return false
  216. }
  217. if (entry.type === 'Link') {
  218. const linkparts = normPath(entry.linkpath).split('/')
  219. if (linkparts.length >= this.strip) {
  220. entry.linkpath = linkparts.slice(this.strip).join('/')
  221. } else {
  222. return false
  223. }
  224. }
  225. parts.splice(0, this.strip)
  226. entry.path = parts.join('/')
  227. }
  228. if (isFinite(this.maxDepth) && parts.length > this.maxDepth) {
  229. this.warn('TAR_ENTRY_ERROR', 'path excessively deep', {
  230. entry,
  231. path: p,
  232. depth: parts.length,
  233. maxDepth: this.maxDepth,
  234. })
  235. return false
  236. }
  237. if (!this.preservePaths) {
  238. if (parts.includes('..') || isWindows && /^[a-z]:\.\.$/i.test(parts[0])) {
  239. this.warn('TAR_ENTRY_ERROR', `path contains '..'`, {
  240. entry,
  241. path: p,
  242. })
  243. return false
  244. }
  245. // strip off the root
  246. const [root, stripped] = stripAbsolutePath(p)
  247. if (root) {
  248. entry.path = stripped
  249. this.warn('TAR_ENTRY_INFO', `stripping ${root} from absolute path`, {
  250. entry,
  251. path: p,
  252. })
  253. }
  254. }
  255. if (path.isAbsolute(entry.path)) {
  256. entry.absolute = normPath(path.resolve(entry.path))
  257. } else {
  258. entry.absolute = normPath(path.resolve(this.cwd, entry.path))
  259. }
  260. // if we somehow ended up with a path that escapes the cwd, and we are
  261. // not in preservePaths mode, then something is fishy! This should have
  262. // been prevented above, so ignore this for coverage.
  263. /* istanbul ignore if - defense in depth */
  264. if (!this.preservePaths &&
  265. entry.absolute.indexOf(this.cwd + '/') !== 0 &&
  266. entry.absolute !== this.cwd) {
  267. this.warn('TAR_ENTRY_ERROR', 'path escaped extraction target', {
  268. entry,
  269. path: normPath(entry.path),
  270. resolvedPath: entry.absolute,
  271. cwd: this.cwd,
  272. })
  273. return false
  274. }
  275. // an archive can set properties on the extraction directory, but it
  276. // may not replace the cwd with a different kind of thing entirely.
  277. if (entry.absolute === this.cwd &&
  278. entry.type !== 'Directory' &&
  279. entry.type !== 'GNUDumpDir') {
  280. return false
  281. }
  282. // only encode : chars that aren't drive letter indicators
  283. if (this.win32) {
  284. const { root: aRoot } = path.win32.parse(entry.absolute)
  285. entry.absolute = aRoot + wc.encode(entry.absolute.slice(aRoot.length))
  286. const { root: pRoot } = path.win32.parse(entry.path)
  287. entry.path = pRoot + wc.encode(entry.path.slice(pRoot.length))
  288. }
  289. return true
  290. }
  291. [ONENTRY] (entry) {
  292. if (!this[CHECKPATH](entry)) {
  293. return entry.resume()
  294. }
  295. assert.equal(typeof entry.absolute, 'string')
  296. switch (entry.type) {
  297. case 'Directory':
  298. case 'GNUDumpDir':
  299. if (entry.mode) {
  300. entry.mode = entry.mode | 0o700
  301. }
  302. // eslint-disable-next-line no-fallthrough
  303. case 'File':
  304. case 'OldFile':
  305. case 'ContiguousFile':
  306. case 'Link':
  307. case 'SymbolicLink':
  308. return this[CHECKFS](entry)
  309. case 'CharacterDevice':
  310. case 'BlockDevice':
  311. case 'FIFO':
  312. default:
  313. return this[UNSUPPORTED](entry)
  314. }
  315. }
  316. [ONERROR] (er, entry) {
  317. // Cwd has to exist, or else nothing works. That's serious.
  318. // Other errors are warnings, which raise the error in strict
  319. // mode, but otherwise continue on.
  320. if (er.name === 'CwdError') {
  321. this.emit('error', er)
  322. } else {
  323. this.warn('TAR_ENTRY_ERROR', er, { entry })
  324. this[UNPEND]()
  325. entry.resume()
  326. }
  327. }
  328. [MKDIR] (dir, mode, cb) {
  329. mkdir(normPath(dir), {
  330. uid: this.uid,
  331. gid: this.gid,
  332. processUid: this.processUid,
  333. processGid: this.processGid,
  334. umask: this.processUmask,
  335. preserve: this.preservePaths,
  336. unlink: this.unlink,
  337. cache: this.dirCache,
  338. cwd: this.cwd,
  339. mode: mode,
  340. noChmod: this.noChmod,
  341. }, cb)
  342. }
  343. [DOCHOWN] (entry) {
  344. // in preserve owner mode, chown if the entry doesn't match process
  345. // in set owner mode, chown if setting doesn't match process
  346. return this.forceChown ||
  347. this.preserveOwner &&
  348. (typeof entry.uid === 'number' && entry.uid !== this.processUid ||
  349. typeof entry.gid === 'number' && entry.gid !== this.processGid)
  350. ||
  351. (typeof this.uid === 'number' && this.uid !== this.processUid ||
  352. typeof this.gid === 'number' && this.gid !== this.processGid)
  353. }
  354. [UID] (entry) {
  355. return uint32(this.uid, entry.uid, this.processUid)
  356. }
  357. [GID] (entry) {
  358. return uint32(this.gid, entry.gid, this.processGid)
  359. }
  360. [FILE] (entry, fullyDone) {
  361. const mode = entry.mode & 0o7777 || this.fmode
  362. const stream = new fsm.WriteStream(entry.absolute, {
  363. flags: getFlag(entry.size),
  364. mode: mode,
  365. autoClose: false,
  366. })
  367. stream.on('error', er => {
  368. if (stream.fd) {
  369. fs.close(stream.fd, () => {})
  370. }
  371. // flush all the data out so that we aren't left hanging
  372. // if the error wasn't actually fatal. otherwise the parse
  373. // is blocked, and we never proceed.
  374. stream.write = () => true
  375. this[ONERROR](er, entry)
  376. fullyDone()
  377. })
  378. let actions = 1
  379. const done = er => {
  380. if (er) {
  381. /* istanbul ignore else - we should always have a fd by now */
  382. if (stream.fd) {
  383. fs.close(stream.fd, () => {})
  384. }
  385. this[ONERROR](er, entry)
  386. fullyDone()
  387. return
  388. }
  389. if (--actions === 0) {
  390. fs.close(stream.fd, er => {
  391. if (er) {
  392. this[ONERROR](er, entry)
  393. } else {
  394. this[UNPEND]()
  395. }
  396. fullyDone()
  397. })
  398. }
  399. }
  400. stream.on('finish', _ => {
  401. // if futimes fails, try utimes
  402. // if utimes fails, fail with the original error
  403. // same for fchown/chown
  404. const abs = entry.absolute
  405. const fd = stream.fd
  406. if (entry.mtime && !this.noMtime) {
  407. actions++
  408. const atime = entry.atime || new Date()
  409. const mtime = entry.mtime
  410. fs.futimes(fd, atime, mtime, er =>
  411. er ? fs.utimes(abs, atime, mtime, er2 => done(er2 && er))
  412. : done())
  413. }
  414. if (this[DOCHOWN](entry)) {
  415. actions++
  416. const uid = this[UID](entry)
  417. const gid = this[GID](entry)
  418. fs.fchown(fd, uid, gid, er =>
  419. er ? fs.chown(abs, uid, gid, er2 => done(er2 && er))
  420. : done())
  421. }
  422. done()
  423. })
  424. const tx = this.transform ? this.transform(entry) || entry : entry
  425. if (tx !== entry) {
  426. tx.on('error', er => {
  427. this[ONERROR](er, entry)
  428. fullyDone()
  429. })
  430. entry.pipe(tx)
  431. }
  432. tx.pipe(stream)
  433. }
  434. [DIRECTORY] (entry, fullyDone) {
  435. const mode = entry.mode & 0o7777 || this.dmode
  436. this[MKDIR](entry.absolute, mode, er => {
  437. if (er) {
  438. this[ONERROR](er, entry)
  439. fullyDone()
  440. return
  441. }
  442. let actions = 1
  443. const done = _ => {
  444. if (--actions === 0) {
  445. fullyDone()
  446. this[UNPEND]()
  447. entry.resume()
  448. }
  449. }
  450. if (entry.mtime && !this.noMtime) {
  451. actions++
  452. fs.utimes(entry.absolute, entry.atime || new Date(), entry.mtime, done)
  453. }
  454. if (this[DOCHOWN](entry)) {
  455. actions++
  456. fs.chown(entry.absolute, this[UID](entry), this[GID](entry), done)
  457. }
  458. done()
  459. })
  460. }
  461. [UNSUPPORTED] (entry) {
  462. entry.unsupported = true
  463. this.warn('TAR_ENTRY_UNSUPPORTED',
  464. `unsupported entry type: ${entry.type}`, { entry })
  465. entry.resume()
  466. }
  467. [SYMLINK] (entry, done) {
  468. this[LINK](entry, entry.linkpath, 'symlink', done)
  469. }
  470. [HARDLINK] (entry, done) {
  471. const linkpath = normPath(path.resolve(this.cwd, entry.linkpath))
  472. this[LINK](entry, linkpath, 'link', done)
  473. }
  474. [PEND] () {
  475. this[PENDING]++
  476. }
  477. [UNPEND] () {
  478. this[PENDING]--
  479. this[MAYBECLOSE]()
  480. }
  481. [SKIP] (entry) {
  482. this[UNPEND]()
  483. entry.resume()
  484. }
  485. // Check if we can reuse an existing filesystem entry safely and
  486. // overwrite it, rather than unlinking and recreating
  487. // Windows doesn't report a useful nlink, so we just never reuse entries
  488. [ISREUSABLE] (entry, st) {
  489. return entry.type === 'File' &&
  490. !this.unlink &&
  491. st.isFile() &&
  492. st.nlink <= 1 &&
  493. !isWindows
  494. }
  495. // check if a thing is there, and if so, try to clobber it
  496. [CHECKFS] (entry) {
  497. this[PEND]()
  498. const paths = [entry.path]
  499. if (entry.linkpath) {
  500. paths.push(entry.linkpath)
  501. }
  502. this.reservations.reserve(paths, done => this[CHECKFS2](entry, done))
  503. }
  504. [PRUNECACHE] (entry) {
  505. // if we are not creating a directory, and the path is in the dirCache,
  506. // then that means we are about to delete the directory we created
  507. // previously, and it is no longer going to be a directory, and neither
  508. // is any of its children.
  509. // If a symbolic link is encountered, all bets are off. There is no
  510. // reasonable way to sanitize the cache in such a way we will be able to
  511. // avoid having filesystem collisions. If this happens with a non-symlink
  512. // entry, it'll just fail to unpack, but a symlink to a directory, using an
  513. // 8.3 shortname or certain unicode attacks, can evade detection and lead
  514. // to arbitrary writes to anywhere on the system.
  515. if (entry.type === 'SymbolicLink') {
  516. dropCache(this.dirCache)
  517. } else if (entry.type !== 'Directory') {
  518. pruneCache(this.dirCache, entry.absolute)
  519. }
  520. }
  521. [CHECKFS2] (entry, fullyDone) {
  522. this[PRUNECACHE](entry)
  523. const done = er => {
  524. this[PRUNECACHE](entry)
  525. fullyDone(er)
  526. }
  527. const checkCwd = () => {
  528. this[MKDIR](this.cwd, this.dmode, er => {
  529. if (er) {
  530. this[ONERROR](er, entry)
  531. done()
  532. return
  533. }
  534. this[CHECKED_CWD] = true
  535. start()
  536. })
  537. }
  538. const start = () => {
  539. if (entry.absolute !== this.cwd) {
  540. const parent = normPath(path.dirname(entry.absolute))
  541. if (parent !== this.cwd) {
  542. return this[MKDIR](parent, this.dmode, er => {
  543. if (er) {
  544. this[ONERROR](er, entry)
  545. done()
  546. return
  547. }
  548. afterMakeParent()
  549. })
  550. }
  551. }
  552. afterMakeParent()
  553. }
  554. const afterMakeParent = () => {
  555. fs.lstat(entry.absolute, (lstatEr, st) => {
  556. if (st && (this.keep || this.newer && st.mtime > entry.mtime)) {
  557. this[SKIP](entry)
  558. done()
  559. return
  560. }
  561. if (lstatEr || this[ISREUSABLE](entry, st)) {
  562. return this[MAKEFS](null, entry, done)
  563. }
  564. if (st.isDirectory()) {
  565. if (entry.type === 'Directory') {
  566. const needChmod = !this.noChmod &&
  567. entry.mode &&
  568. (st.mode & 0o7777) !== entry.mode
  569. const afterChmod = er => this[MAKEFS](er, entry, done)
  570. if (!needChmod) {
  571. return afterChmod()
  572. }
  573. return fs.chmod(entry.absolute, entry.mode, afterChmod)
  574. }
  575. // Not a dir entry, have to remove it.
  576. // NB: the only way to end up with an entry that is the cwd
  577. // itself, in such a way that == does not detect, is a
  578. // tricky windows absolute path with UNC or 8.3 parts (and
  579. // preservePaths:true, or else it will have been stripped).
  580. // In that case, the user has opted out of path protections
  581. // explicitly, so if they blow away the cwd, c'est la vie.
  582. if (entry.absolute !== this.cwd) {
  583. return fs.rmdir(entry.absolute, er =>
  584. this[MAKEFS](er, entry, done))
  585. }
  586. }
  587. // not a dir, and not reusable
  588. // don't remove if the cwd, we want that error
  589. if (entry.absolute === this.cwd) {
  590. return this[MAKEFS](null, entry, done)
  591. }
  592. unlinkFile(entry.absolute, er =>
  593. this[MAKEFS](er, entry, done))
  594. })
  595. }
  596. if (this[CHECKED_CWD]) {
  597. start()
  598. } else {
  599. checkCwd()
  600. }
  601. }
  602. [MAKEFS] (er, entry, done) {
  603. if (er) {
  604. this[ONERROR](er, entry)
  605. done()
  606. return
  607. }
  608. switch (entry.type) {
  609. case 'File':
  610. case 'OldFile':
  611. case 'ContiguousFile':
  612. return this[FILE](entry, done)
  613. case 'Link':
  614. return this[HARDLINK](entry, done)
  615. case 'SymbolicLink':
  616. return this[SYMLINK](entry, done)
  617. case 'Directory':
  618. case 'GNUDumpDir':
  619. return this[DIRECTORY](entry, done)
  620. }
  621. }
  622. [LINK] (entry, linkpath, link, done) {
  623. // XXX: get the type ('symlink' or 'junction') for windows
  624. fs[link](linkpath, entry.absolute, er => {
  625. if (er) {
  626. this[ONERROR](er, entry)
  627. } else {
  628. this[UNPEND]()
  629. entry.resume()
  630. }
  631. done()
  632. })
  633. }
  634. }
  635. const callSync = fn => {
  636. try {
  637. return [null, fn()]
  638. } catch (er) {
  639. return [er, null]
  640. }
  641. }
  642. class UnpackSync extends Unpack {
  643. [MAKEFS] (er, entry) {
  644. return super[MAKEFS](er, entry, () => {})
  645. }
  646. [CHECKFS] (entry) {
  647. this[PRUNECACHE](entry)
  648. if (!this[CHECKED_CWD]) {
  649. const er = this[MKDIR](this.cwd, this.dmode)
  650. if (er) {
  651. return this[ONERROR](er, entry)
  652. }
  653. this[CHECKED_CWD] = true
  654. }
  655. // don't bother to make the parent if the current entry is the cwd,
  656. // we've already checked it.
  657. if (entry.absolute !== this.cwd) {
  658. const parent = normPath(path.dirname(entry.absolute))
  659. if (parent !== this.cwd) {
  660. const mkParent = this[MKDIR](parent, this.dmode)
  661. if (mkParent) {
  662. return this[ONERROR](mkParent, entry)
  663. }
  664. }
  665. }
  666. const [lstatEr, st] = callSync(() => fs.lstatSync(entry.absolute))
  667. if (st && (this.keep || this.newer && st.mtime > entry.mtime)) {
  668. return this[SKIP](entry)
  669. }
  670. if (lstatEr || this[ISREUSABLE](entry, st)) {
  671. return this[MAKEFS](null, entry)
  672. }
  673. if (st.isDirectory()) {
  674. if (entry.type === 'Directory') {
  675. const needChmod = !this.noChmod &&
  676. entry.mode &&
  677. (st.mode & 0o7777) !== entry.mode
  678. const [er] = needChmod ? callSync(() => {
  679. fs.chmodSync(entry.absolute, entry.mode)
  680. }) : []
  681. return this[MAKEFS](er, entry)
  682. }
  683. // not a dir entry, have to remove it
  684. const [er] = callSync(() => fs.rmdirSync(entry.absolute))
  685. this[MAKEFS](er, entry)
  686. }
  687. // not a dir, and not reusable.
  688. // don't remove if it's the cwd, since we want that error.
  689. const [er] = entry.absolute === this.cwd ? []
  690. : callSync(() => unlinkFileSync(entry.absolute))
  691. this[MAKEFS](er, entry)
  692. }
  693. [FILE] (entry, done) {
  694. const mode = entry.mode & 0o7777 || this.fmode
  695. const oner = er => {
  696. let closeError
  697. try {
  698. fs.closeSync(fd)
  699. } catch (e) {
  700. closeError = e
  701. }
  702. if (er || closeError) {
  703. this[ONERROR](er || closeError, entry)
  704. }
  705. done()
  706. }
  707. let fd
  708. try {
  709. fd = fs.openSync(entry.absolute, getFlag(entry.size), mode)
  710. } catch (er) {
  711. return oner(er)
  712. }
  713. const tx = this.transform ? this.transform(entry) || entry : entry
  714. if (tx !== entry) {
  715. tx.on('error', er => this[ONERROR](er, entry))
  716. entry.pipe(tx)
  717. }
  718. tx.on('data', chunk => {
  719. try {
  720. fs.writeSync(fd, chunk, 0, chunk.length)
  721. } catch (er) {
  722. oner(er)
  723. }
  724. })
  725. tx.on('end', _ => {
  726. let er = null
  727. // try both, falling futimes back to utimes
  728. // if either fails, handle the first error
  729. if (entry.mtime && !this.noMtime) {
  730. const atime = entry.atime || new Date()
  731. const mtime = entry.mtime
  732. try {
  733. fs.futimesSync(fd, atime, mtime)
  734. } catch (futimeser) {
  735. try {
  736. fs.utimesSync(entry.absolute, atime, mtime)
  737. } catch (utimeser) {
  738. er = futimeser
  739. }
  740. }
  741. }
  742. if (this[DOCHOWN](entry)) {
  743. const uid = this[UID](entry)
  744. const gid = this[GID](entry)
  745. try {
  746. fs.fchownSync(fd, uid, gid)
  747. } catch (fchowner) {
  748. try {
  749. fs.chownSync(entry.absolute, uid, gid)
  750. } catch (chowner) {
  751. er = er || fchowner
  752. }
  753. }
  754. }
  755. oner(er)
  756. })
  757. }
  758. [DIRECTORY] (entry, done) {
  759. const mode = entry.mode & 0o7777 || this.dmode
  760. const er = this[MKDIR](entry.absolute, mode)
  761. if (er) {
  762. this[ONERROR](er, entry)
  763. done()
  764. return
  765. }
  766. if (entry.mtime && !this.noMtime) {
  767. try {
  768. fs.utimesSync(entry.absolute, entry.atime || new Date(), entry.mtime)
  769. } catch (er) {}
  770. }
  771. if (this[DOCHOWN](entry)) {
  772. try {
  773. fs.chownSync(entry.absolute, this[UID](entry), this[GID](entry))
  774. } catch (er) {}
  775. }
  776. done()
  777. entry.resume()
  778. }
  779. [MKDIR] (dir, mode) {
  780. try {
  781. return mkdir.sync(normPath(dir), {
  782. uid: this.uid,
  783. gid: this.gid,
  784. processUid: this.processUid,
  785. processGid: this.processGid,
  786. umask: this.processUmask,
  787. preserve: this.preservePaths,
  788. unlink: this.unlink,
  789. cache: this.dirCache,
  790. cwd: this.cwd,
  791. mode: mode,
  792. })
  793. } catch (er) {
  794. return er
  795. }
  796. }
  797. [LINK] (entry, linkpath, link, done) {
  798. try {
  799. fs[link + 'Sync'](linkpath, entry.absolute)
  800. done()
  801. entry.resume()
  802. } catch (er) {
  803. return this[ONERROR](er, entry)
  804. }
  805. }
  806. }
  807. Unpack.Sync = UnpackSync
  808. module.exports = Unpack