3cea46414a9bb5e626035a3622ee5b734beeff05eba5d46913f33214e0a4c842f36d8d32b63d5e86e1576ed37324ec019c45c0a2c13ce1dc306301a72c9335 32 KB

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