source: trip-planner-front/node_modules/tar/lib/unpack.js@ 59329aa

Last change on this file since 59329aa was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

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