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