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