unpack.js 25 KB

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