unpack.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  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 ONENTRY = Symbol('onEntry')
  16. const CHECKFS = Symbol('checkFs')
  17. const CHECKFS2 = Symbol('checkFs2')
  18. const ISREUSABLE = Symbol('isReusable')
  19. const MAKEFS = Symbol('makeFs')
  20. const FILE = Symbol('file')
  21. const DIRECTORY = Symbol('directory')
  22. const LINK = Symbol('link')
  23. const SYMLINK = Symbol('symlink')
  24. const HARDLINK = Symbol('hardlink')
  25. const UNSUPPORTED = Symbol('unsupported')
  26. const CHECKPATH = Symbol('checkPath')
  27. const MKDIR = Symbol('mkdir')
  28. const ONERROR = Symbol('onError')
  29. const PENDING = Symbol('pending')
  30. const PEND = Symbol('pend')
  31. const UNPEND = Symbol('unpend')
  32. const ENDED = Symbol('ended')
  33. const MAYBECLOSE = Symbol('maybeClose')
  34. const SKIP = Symbol('skip')
  35. const DOCHOWN = Symbol('doChown')
  36. const UID = Symbol('uid')
  37. const GID = Symbol('gid')
  38. const crypto = require('crypto')
  39. const getFlag = require('./get-write-flag.js')
  40. /* istanbul ignore next */
  41. const neverCalled = () => {
  42. throw new Error('sync function called cb somehow?!?')
  43. }
  44. // Unlinks on Windows are not atomic.
  45. //
  46. // This means that if you have a file entry, followed by another
  47. // file entry with an identical name, and you cannot re-use the file
  48. // (because it's a hardlink, or because unlink:true is set, or it's
  49. // Windows, which does not have useful nlink values), then the unlink
  50. // will be committed to the disk AFTER the new file has been written
  51. // over the old one, deleting the new file.
  52. //
  53. // To work around this, on Windows systems, we rename the file and then
  54. // delete the renamed file. It's a sloppy kludge, but frankly, I do not
  55. // know of a better way to do this, given windows' non-atomic unlink
  56. // semantics.
  57. //
  58. // See: https://github.com/npm/node-tar/issues/183
  59. /* istanbul ignore next */
  60. const unlinkFile = (path, cb) => {
  61. if (process.platform !== 'win32')
  62. return fs.unlink(path, cb)
  63. const name = path + '.DELETE.' + crypto.randomBytes(16).toString('hex')
  64. fs.rename(path, name, er => {
  65. if (er)
  66. return cb(er)
  67. fs.unlink(name, cb)
  68. })
  69. }
  70. /* istanbul ignore next */
  71. const unlinkFileSync = path => {
  72. if (process.platform !== 'win32')
  73. return fs.unlinkSync(path)
  74. const name = path + '.DELETE.' + crypto.randomBytes(16).toString('hex')
  75. fs.renameSync(path, name)
  76. fs.unlinkSync(name)
  77. }
  78. // this.gid, entry.gid, this.processUid
  79. const uint32 = (a, b, c) =>
  80. a === a >>> 0 ? a
  81. : b === b >>> 0 ? b
  82. : c
  83. class Unpack extends Parser {
  84. constructor (opt) {
  85. if (!opt)
  86. opt = {}
  87. opt.ondone = _ => {
  88. this[ENDED] = true
  89. this[MAYBECLOSE]()
  90. }
  91. super(opt)
  92. this.reservations = pathReservations()
  93. this.transform = typeof opt.transform === 'function' ? opt.transform : null
  94. this.writable = true
  95. this.readable = false
  96. this[PENDING] = 0
  97. this[ENDED] = false
  98. this.dirCache = opt.dirCache || new Map()
  99. if (typeof opt.uid === 'number' || typeof opt.gid === 'number') {
  100. // need both or neither
  101. if (typeof opt.uid !== 'number' || typeof opt.gid !== 'number')
  102. throw new TypeError('cannot set owner without number uid and gid')
  103. if (opt.preserveOwner) {
  104. throw new TypeError(
  105. 'cannot preserve owner in archive and also set owner explicitly')
  106. }
  107. this.uid = opt.uid
  108. this.gid = opt.gid
  109. this.setOwner = true
  110. } else {
  111. this.uid = null
  112. this.gid = null
  113. this.setOwner = false
  114. }
  115. // default true for root
  116. if (opt.preserveOwner === undefined && typeof opt.uid !== 'number')
  117. this.preserveOwner = process.getuid && process.getuid() === 0
  118. else
  119. this.preserveOwner = !!opt.preserveOwner
  120. this.processUid = (this.preserveOwner || this.setOwner) && process.getuid ?
  121. process.getuid() : null
  122. this.processGid = (this.preserveOwner || this.setOwner) && process.getgid ?
  123. process.getgid() : null
  124. // mostly just for testing, but useful in some cases.
  125. // Forcibly trigger a chown on every entry, no matter what
  126. this.forceChown = opt.forceChown === true
  127. // turn ><?| in filenames into 0xf000-higher encoded forms
  128. this.win32 = !!opt.win32 || process.platform === 'win32'
  129. // do not unpack over files that are newer than what's in the archive
  130. this.newer = !!opt.newer
  131. // do not unpack over ANY files
  132. this.keep = !!opt.keep
  133. // do not set mtime/atime of extracted entries
  134. this.noMtime = !!opt.noMtime
  135. // allow .., absolute path entries, and unpacking through symlinks
  136. // without this, warn and skip .., relativize absolutes, and error
  137. // on symlinks in extraction path
  138. this.preservePaths = !!opt.preservePaths
  139. // unlink files and links before writing. This breaks existing hard
  140. // links, and removes symlink directories rather than erroring
  141. this.unlink = !!opt.unlink
  142. this.cwd = path.resolve(opt.cwd || process.cwd())
  143. this.strip = +opt.strip || 0
  144. // if we're not chmodding, then we don't need the process umask
  145. this.processUmask = opt.noChmod ? 0 : process.umask()
  146. this.umask = typeof opt.umask === 'number' ? opt.umask : this.processUmask
  147. // default mode for dirs created as parents
  148. this.dmode = opt.dmode || (0o0777 & (~this.umask))
  149. this.fmode = opt.fmode || (0o0666 & (~this.umask))
  150. this.on('entry', entry => this[ONENTRY](entry))
  151. }
  152. // a bad or damaged archive is a warning for Parser, but an error
  153. // when extracting. Mark those errors as unrecoverable, because
  154. // the Unpack contract cannot be met.
  155. warn (code, msg, data = {}) {
  156. if (code === 'TAR_BAD_ARCHIVE' || code === 'TAR_ABORT')
  157. data.recoverable = false
  158. return super.warn(code, msg, data)
  159. }
  160. [MAYBECLOSE] () {
  161. if (this[ENDED] && this[PENDING] === 0) {
  162. this.emit('prefinish')
  163. this.emit('finish')
  164. this.emit('end')
  165. this.emit('close')
  166. }
  167. }
  168. [CHECKPATH] (entry) {
  169. if (this.strip) {
  170. const parts = entry.path.split(/\/|\\/)
  171. if (parts.length < this.strip)
  172. return false
  173. entry.path = parts.slice(this.strip).join('/')
  174. if (entry.type === 'Link') {
  175. const linkparts = entry.linkpath.split(/\/|\\/)
  176. if (linkparts.length >= this.strip)
  177. entry.linkpath = linkparts.slice(this.strip).join('/')
  178. }
  179. }
  180. if (!this.preservePaths) {
  181. const p = entry.path
  182. if (p.match(/(^|\/|\\)\.\.(\\|\/|$)/)) {
  183. this.warn('TAR_ENTRY_ERROR', `path contains '..'`, {
  184. entry,
  185. path: p,
  186. })
  187. return false
  188. }
  189. // absolutes on posix are also absolutes on win32
  190. // so we only need to test this one to get both
  191. if (path.win32.isAbsolute(p)) {
  192. const parsed = path.win32.parse(p)
  193. entry.path = p.substr(parsed.root.length)
  194. const r = parsed.root
  195. this.warn('TAR_ENTRY_INFO', `stripping ${r} from absolute path`, {
  196. entry,
  197. path: p,
  198. })
  199. }
  200. }
  201. // only encode : chars that aren't drive letter indicators
  202. if (this.win32) {
  203. const parsed = path.win32.parse(entry.path)
  204. entry.path = parsed.root === '' ? wc.encode(entry.path)
  205. : parsed.root + wc.encode(entry.path.substr(parsed.root.length))
  206. }
  207. if (path.isAbsolute(entry.path))
  208. entry.absolute = entry.path
  209. else
  210. entry.absolute = path.resolve(this.cwd, entry.path)
  211. return true
  212. }
  213. [ONENTRY] (entry) {
  214. if (!this[CHECKPATH](entry))
  215. return entry.resume()
  216. assert.equal(typeof entry.absolute, 'string')
  217. switch (entry.type) {
  218. case 'Directory':
  219. case 'GNUDumpDir':
  220. if (entry.mode)
  221. entry.mode = entry.mode | 0o700
  222. case 'File':
  223. case 'OldFile':
  224. case 'ContiguousFile':
  225. case 'Link':
  226. case 'SymbolicLink':
  227. return this[CHECKFS](entry)
  228. case 'CharacterDevice':
  229. case 'BlockDevice':
  230. case 'FIFO':
  231. default:
  232. return this[UNSUPPORTED](entry)
  233. }
  234. }
  235. [ONERROR] (er, entry) {
  236. // Cwd has to exist, or else nothing works. That's serious.
  237. // Other errors are warnings, which raise the error in strict
  238. // mode, but otherwise continue on.
  239. if (er.name === 'CwdError')
  240. this.emit('error', er)
  241. else {
  242. this.warn('TAR_ENTRY_ERROR', er, {entry})
  243. this[UNPEND]()
  244. entry.resume()
  245. }
  246. }
  247. [MKDIR] (dir, mode, cb) {
  248. mkdir(dir, {
  249. uid: this.uid,
  250. gid: this.gid,
  251. processUid: this.processUid,
  252. processGid: this.processGid,
  253. umask: this.processUmask,
  254. preserve: this.preservePaths,
  255. unlink: this.unlink,
  256. cache: this.dirCache,
  257. cwd: this.cwd,
  258. mode: mode,
  259. noChmod: this.noChmod,
  260. }, cb)
  261. }
  262. [DOCHOWN] (entry) {
  263. // in preserve owner mode, chown if the entry doesn't match process
  264. // in set owner mode, chown if setting doesn't match process
  265. return this.forceChown ||
  266. this.preserveOwner &&
  267. (typeof entry.uid === 'number' && entry.uid !== this.processUid ||
  268. typeof entry.gid === 'number' && entry.gid !== this.processGid)
  269. ||
  270. (typeof this.uid === 'number' && this.uid !== this.processUid ||
  271. typeof this.gid === 'number' && this.gid !== this.processGid)
  272. }
  273. [UID] (entry) {
  274. return uint32(this.uid, entry.uid, this.processUid)
  275. }
  276. [GID] (entry) {
  277. return uint32(this.gid, entry.gid, this.processGid)
  278. }
  279. [FILE] (entry, fullyDone) {
  280. const mode = entry.mode & 0o7777 || this.fmode
  281. const stream = new fsm.WriteStream(entry.absolute, {
  282. flags: getFlag(entry.size),
  283. mode: mode,
  284. autoClose: false,
  285. })
  286. stream.on('error', er => this[ONERROR](er, entry))
  287. let actions = 1
  288. const done = er => {
  289. if (er)
  290. return this[ONERROR](er, entry)
  291. if (--actions === 0) {
  292. fs.close(stream.fd, er => {
  293. fullyDone()
  294. er ? this[ONERROR](er, entry) : this[UNPEND]()
  295. })
  296. }
  297. }
  298. stream.on('finish', _ => {
  299. // if futimes fails, try utimes
  300. // if utimes fails, fail with the original error
  301. // same for fchown/chown
  302. const abs = entry.absolute
  303. const fd = stream.fd
  304. if (entry.mtime && !this.noMtime) {
  305. actions++
  306. const atime = entry.atime || new Date()
  307. const mtime = entry.mtime
  308. fs.futimes(fd, atime, mtime, er =>
  309. er ? fs.utimes(abs, atime, mtime, er2 => done(er2 && er))
  310. : done())
  311. }
  312. if (this[DOCHOWN](entry)) {
  313. actions++
  314. const uid = this[UID](entry)
  315. const gid = this[GID](entry)
  316. fs.fchown(fd, uid, gid, er =>
  317. er ? fs.chown(abs, uid, gid, er2 => done(er2 && er))
  318. : done())
  319. }
  320. done()
  321. })
  322. const tx = this.transform ? this.transform(entry) || entry : entry
  323. if (tx !== entry) {
  324. tx.on('error', er => this[ONERROR](er, entry))
  325. entry.pipe(tx)
  326. }
  327. tx.pipe(stream)
  328. }
  329. [DIRECTORY] (entry, fullyDone) {
  330. const mode = entry.mode & 0o7777 || this.dmode
  331. this[MKDIR](entry.absolute, mode, er => {
  332. if (er) {
  333. fullyDone()
  334. return this[ONERROR](er, entry)
  335. }
  336. let actions = 1
  337. const done = _ => {
  338. if (--actions === 0) {
  339. fullyDone()
  340. this[UNPEND]()
  341. entry.resume()
  342. }
  343. }
  344. if (entry.mtime && !this.noMtime) {
  345. actions++
  346. fs.utimes(entry.absolute, entry.atime || new Date(), entry.mtime, done)
  347. }
  348. if (this[DOCHOWN](entry)) {
  349. actions++
  350. fs.chown(entry.absolute, this[UID](entry), this[GID](entry), done)
  351. }
  352. done()
  353. })
  354. }
  355. [UNSUPPORTED] (entry) {
  356. entry.unsupported = true
  357. this.warn('TAR_ENTRY_UNSUPPORTED',
  358. `unsupported entry type: ${entry.type}`, {entry})
  359. entry.resume()
  360. }
  361. [SYMLINK] (entry, done) {
  362. this[LINK](entry, entry.linkpath, 'symlink', done)
  363. }
  364. [HARDLINK] (entry, done) {
  365. this[LINK](entry, path.resolve(this.cwd, entry.linkpath), 'link', done)
  366. }
  367. [PEND] () {
  368. this[PENDING]++
  369. }
  370. [UNPEND] () {
  371. this[PENDING]--
  372. this[MAYBECLOSE]()
  373. }
  374. [SKIP] (entry) {
  375. this[UNPEND]()
  376. entry.resume()
  377. }
  378. // Check if we can reuse an existing filesystem entry safely and
  379. // overwrite it, rather than unlinking and recreating
  380. // Windows doesn't report a useful nlink, so we just never reuse entries
  381. [ISREUSABLE] (entry, st) {
  382. return entry.type === 'File' &&
  383. !this.unlink &&
  384. st.isFile() &&
  385. st.nlink <= 1 &&
  386. process.platform !== 'win32'
  387. }
  388. // check if a thing is there, and if so, try to clobber it
  389. [CHECKFS] (entry) {
  390. this[PEND]()
  391. const paths = [entry.path]
  392. if (entry.linkpath)
  393. paths.push(entry.linkpath)
  394. this.reservations.reserve(paths, done => this[CHECKFS2](entry, done))
  395. }
  396. [CHECKFS2] (entry, done) {
  397. this[MKDIR](path.dirname(entry.absolute), this.dmode, er => {
  398. if (er) {
  399. done()
  400. return this[ONERROR](er, entry)
  401. }
  402. fs.lstat(entry.absolute, (er, st) => {
  403. if (st && (this.keep || this.newer && st.mtime > entry.mtime)) {
  404. this[SKIP](entry)
  405. done()
  406. } else if (er || this[ISREUSABLE](entry, st))
  407. this[MAKEFS](null, entry, done)
  408. else if (st.isDirectory()) {
  409. if (entry.type === 'Directory') {
  410. if (!this.noChmod && (!entry.mode || (st.mode & 0o7777) === entry.mode))
  411. this[MAKEFS](null, entry, done)
  412. else {
  413. fs.chmod(entry.absolute, entry.mode,
  414. er => this[MAKEFS](er, entry, done))
  415. }
  416. } else
  417. fs.rmdir(entry.absolute, er => this[MAKEFS](er, entry, done))
  418. } else
  419. unlinkFile(entry.absolute, er => this[MAKEFS](er, entry, done))
  420. })
  421. })
  422. }
  423. [MAKEFS] (er, entry, done) {
  424. if (er)
  425. return this[ONERROR](er, entry)
  426. switch (entry.type) {
  427. case 'File':
  428. case 'OldFile':
  429. case 'ContiguousFile':
  430. return this[FILE](entry, done)
  431. case 'Link':
  432. return this[HARDLINK](entry, done)
  433. case 'SymbolicLink':
  434. return this[SYMLINK](entry, done)
  435. case 'Directory':
  436. case 'GNUDumpDir':
  437. return this[DIRECTORY](entry, done)
  438. }
  439. }
  440. [LINK] (entry, linkpath, link, done) {
  441. // XXX: get the type ('file' or 'dir') for windows
  442. fs[link](linkpath, entry.absolute, er => {
  443. if (er)
  444. return this[ONERROR](er, entry)
  445. done()
  446. this[UNPEND]()
  447. entry.resume()
  448. })
  449. }
  450. }
  451. class UnpackSync extends Unpack {
  452. [CHECKFS] (entry) {
  453. const er = this[MKDIR](path.dirname(entry.absolute), this.dmode, neverCalled)
  454. if (er)
  455. return this[ONERROR](er, entry)
  456. try {
  457. const st = fs.lstatSync(entry.absolute)
  458. if (this.keep || this.newer && st.mtime > entry.mtime)
  459. return this[SKIP](entry)
  460. else if (this[ISREUSABLE](entry, st))
  461. return this[MAKEFS](null, entry, neverCalled)
  462. else {
  463. try {
  464. if (st.isDirectory()) {
  465. if (entry.type === 'Directory') {
  466. if (!this.noChmod && entry.mode && (st.mode & 0o7777) !== entry.mode)
  467. fs.chmodSync(entry.absolute, entry.mode)
  468. } else
  469. fs.rmdirSync(entry.absolute)
  470. } else
  471. unlinkFileSync(entry.absolute)
  472. return this[MAKEFS](null, entry, neverCalled)
  473. } catch (er) {
  474. return this[ONERROR](er, entry)
  475. }
  476. }
  477. } catch (er) {
  478. return this[MAKEFS](null, entry, neverCalled)
  479. }
  480. }
  481. [FILE] (entry, _) {
  482. const mode = entry.mode & 0o7777 || this.fmode
  483. const oner = er => {
  484. let closeError
  485. try {
  486. fs.closeSync(fd)
  487. } catch (e) {
  488. closeError = e
  489. }
  490. if (er || closeError)
  491. this[ONERROR](er || closeError, entry)
  492. }
  493. let fd
  494. try {
  495. fd = fs.openSync(entry.absolute, getFlag(entry.size), mode)
  496. } catch (er) {
  497. return oner(er)
  498. }
  499. const tx = this.transform ? this.transform(entry) || entry : entry
  500. if (tx !== entry) {
  501. tx.on('error', er => this[ONERROR](er, entry))
  502. entry.pipe(tx)
  503. }
  504. tx.on('data', chunk => {
  505. try {
  506. fs.writeSync(fd, chunk, 0, chunk.length)
  507. } catch (er) {
  508. oner(er)
  509. }
  510. })
  511. tx.on('end', _ => {
  512. let er = null
  513. // try both, falling futimes back to utimes
  514. // if either fails, handle the first error
  515. if (entry.mtime && !this.noMtime) {
  516. const atime = entry.atime || new Date()
  517. const mtime = entry.mtime
  518. try {
  519. fs.futimesSync(fd, atime, mtime)
  520. } catch (futimeser) {
  521. try {
  522. fs.utimesSync(entry.absolute, atime, mtime)
  523. } catch (utimeser) {
  524. er = futimeser
  525. }
  526. }
  527. }
  528. if (this[DOCHOWN](entry)) {
  529. const uid = this[UID](entry)
  530. const gid = this[GID](entry)
  531. try {
  532. fs.fchownSync(fd, uid, gid)
  533. } catch (fchowner) {
  534. try {
  535. fs.chownSync(entry.absolute, uid, gid)
  536. } catch (chowner) {
  537. er = er || fchowner
  538. }
  539. }
  540. }
  541. oner(er)
  542. })
  543. }
  544. [DIRECTORY] (entry, _) {
  545. const mode = entry.mode & 0o7777 || this.dmode
  546. const er = this[MKDIR](entry.absolute, mode)
  547. if (er)
  548. return this[ONERROR](er, entry)
  549. if (entry.mtime && !this.noMtime) {
  550. try {
  551. fs.utimesSync(entry.absolute, entry.atime || new Date(), entry.mtime)
  552. } catch (er) {}
  553. }
  554. if (this[DOCHOWN](entry)) {
  555. try {
  556. fs.chownSync(entry.absolute, this[UID](entry), this[GID](entry))
  557. } catch (er) {}
  558. }
  559. entry.resume()
  560. }
  561. [MKDIR] (dir, mode) {
  562. try {
  563. return mkdir.sync(dir, {
  564. uid: this.uid,
  565. gid: this.gid,
  566. processUid: this.processUid,
  567. processGid: this.processGid,
  568. umask: this.processUmask,
  569. preserve: this.preservePaths,
  570. unlink: this.unlink,
  571. cache: this.dirCache,
  572. cwd: this.cwd,
  573. mode: mode,
  574. })
  575. } catch (er) {
  576. return er
  577. }
  578. }
  579. [LINK] (entry, linkpath, link, _) {
  580. try {
  581. fs[link + 'Sync'](linkpath, entry.absolute)
  582. entry.resume()
  583. } catch (er) {
  584. return this[ONERROR](er, entry)
  585. }
  586. }
  587. }
  588. Unpack.Sync = UnpackSync
  589. module.exports = Unpack