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