• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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