unpack.js 24 KB

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