source: node_modules/tar/lib/unpack.js@ 62b2964

finki-main main
Last change on this file since 62b2964 was 81bc7da, checked in by Klimentina Efremova <klimentina08642@…>, 3 months ago

Initial commit

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