unpack.js 24 KB

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