Built motion from commit 6a09e18b.|2.6.11
[motion2.git] / legacy-libs / node-pre-gyp / node_modules / tar / lib / unpack.js
1 'use strict'
2
3 const assert = require('assert')
4 const EE = require('events').EventEmitter
5 const Parser = require('./parse.js')
6 const fs = require('fs')
7 const fsm = require('fs-minipass')
8 const path = require('path')
9 const mkdir = require('./mkdir.js')
10 const mkdirSync = mkdir.sync
11 const wc = require('./winchars.js')
12
13 const ONENTRY = Symbol('onEntry')
14 const CHECKFS = Symbol('checkFs')
15 const ISREUSABLE = Symbol('isReusable')
16 const MAKEFS = Symbol('makeFs')
17 const FILE = Symbol('file')
18 const DIRECTORY = Symbol('directory')
19 const LINK = Symbol('link')
20 const SYMLINK = Symbol('symlink')
21 const HARDLINK = Symbol('hardlink')
22 const UNSUPPORTED = Symbol('unsupported')
23 const UNKNOWN = Symbol('unknown')
24 const CHECKPATH = Symbol('checkPath')
25 const MKDIR = Symbol('mkdir')
26 const ONERROR = Symbol('onError')
27 const PENDING = Symbol('pending')
28 const PEND = Symbol('pend')
29 const UNPEND = Symbol('unpend')
30 const ENDED = Symbol('ended')
31 const MAYBECLOSE = Symbol('maybeClose')
32 const SKIP = Symbol('skip')
33 const DOCHOWN = Symbol('doChown')
34 const UID = Symbol('uid')
35 const GID = Symbol('gid')
36 const crypto = require('crypto')
37
38 // Unlinks on Windows are not atomic.
39 //
40 // This means that if you have a file entry, followed by another
41 // file entry with an identical name, and you cannot re-use the file
42 // (because it's a hardlink, or because unlink:true is set, or it's
43 // Windows, which does not have useful nlink values), then the unlink
44 // will be committed to the disk AFTER the new file has been written
45 // over the old one, deleting the new file.
46 //
47 // To work around this, on Windows systems, we rename the file and then
48 // delete the renamed file.  It's a sloppy kludge, but frankly, I do not
49 // know of a better way to do this, given windows' non-atomic unlink
50 // semantics.
51 //
52 // See: https://github.com/npm/node-tar/issues/183
53 /* istanbul ignore next */
54 const unlinkFile = (path, cb) => {
55   if (process.platform !== 'win32')
56     return fs.unlink(path, cb)
57
58   const name = path + '.DELETE.' + crypto.randomBytes(16).toString('hex')
59   fs.rename(path, name, er => {
60     if (er)
61       return cb(er)
62     fs.unlink(name, cb)
63   })
64 }
65
66 /* istanbul ignore next */
67 const unlinkFileSync = path => {
68   if (process.platform !== 'win32')
69     return fs.unlinkSync(path)
70
71   const name = path + '.DELETE.' + crypto.randomBytes(16).toString('hex')
72   fs.renameSync(path, name)
73   fs.unlinkSync(name)
74 }
75
76 // this.gid, entry.gid, this.processUid
77 const uint32 = (a, b, c) =>
78   a === a >>> 0 ? a
79   : b === b >>> 0 ? b
80   : c
81
82 class Unpack extends Parser {
83   constructor (opt) {
84     if (!opt)
85       opt = {}
86
87     opt.ondone = _ => {
88       this[ENDED] = true
89       this[MAYBECLOSE]()
90     }
91
92     super(opt)
93
94     this.transform = typeof opt.transform === 'function' ? opt.transform : null
95
96     this.writable = true
97     this.readable = false
98
99     this[PENDING] = 0
100     this[ENDED] = false
101
102     this.dirCache = opt.dirCache || new Map()
103
104     if (typeof opt.uid === 'number' || typeof opt.gid === 'number') {
105       // need both or neither
106       if (typeof opt.uid !== 'number' || typeof opt.gid !== 'number')
107         throw new TypeError('cannot set owner without number uid and gid')
108       if (opt.preserveOwner)
109         throw new TypeError(
110           'cannot preserve owner in archive and also set owner explicitly')
111       this.uid = opt.uid
112       this.gid = opt.gid
113       this.setOwner = true
114     } else {
115       this.uid = null
116       this.gid = null
117       this.setOwner = false
118     }
119
120     // default true for root
121     if (opt.preserveOwner === undefined && typeof opt.uid !== 'number')
122       this.preserveOwner = process.getuid && process.getuid() === 0
123     else
124       this.preserveOwner = !!opt.preserveOwner
125
126     this.processUid = (this.preserveOwner || this.setOwner) && process.getuid ?
127       process.getuid() : null
128     this.processGid = (this.preserveOwner || this.setOwner) && process.getgid ?
129       process.getgid() : null
130
131     // mostly just for testing, but useful in some cases.
132     // Forcibly trigger a chown on every entry, no matter what
133     this.forceChown = opt.forceChown === true
134
135     // turn ><?| in filenames into 0xf000-higher encoded forms
136     this.win32 = !!opt.win32 || process.platform === 'win32'
137
138     // do not unpack over files that are newer than what's in the archive
139     this.newer = !!opt.newer
140
141     // do not unpack over ANY files
142     this.keep = !!opt.keep
143
144     // do not set mtime/atime of extracted entries
145     this.noMtime = !!opt.noMtime
146
147     // allow .., absolute path entries, and unpacking through symlinks
148     // without this, warn and skip .., relativize absolutes, and error
149     // on symlinks in extraction path
150     this.preservePaths = !!opt.preservePaths
151
152     // unlink files and links before writing. This breaks existing hard
153     // links, and removes symlink directories rather than erroring
154     this.unlink = !!opt.unlink
155
156     this.cwd = path.resolve(opt.cwd || process.cwd())
157     this.strip = +opt.strip || 0
158     this.processUmask = process.umask()
159     this.umask = typeof opt.umask === 'number' ? opt.umask : this.processUmask
160     // default mode for dirs created as parents
161     this.dmode = opt.dmode || (0o0777 & (~this.umask))
162     this.fmode = opt.fmode || (0o0666 & (~this.umask))
163     this.on('entry', entry => this[ONENTRY](entry))
164   }
165
166   [MAYBECLOSE] () {
167     if (this[ENDED] && this[PENDING] === 0) {
168       this.emit('prefinish')
169       this.emit('finish')
170       this.emit('end')
171       this.emit('close')
172     }
173   }
174
175   [CHECKPATH] (entry) {
176     if (this.strip) {
177       const parts = entry.path.split(/\/|\\/)
178       if (parts.length < this.strip)
179         return false
180       entry.path = parts.slice(this.strip).join('/')
181
182       if (entry.type === 'Link') {
183         const linkparts = entry.linkpath.split(/\/|\\/)
184         if (linkparts.length >= this.strip)
185           entry.linkpath = linkparts.slice(this.strip).join('/')
186       }
187     }
188
189     if (!this.preservePaths) {
190       const p = entry.path
191       if (p.match(/(^|\/|\\)\.\.(\\|\/|$)/)) {
192         this.warn('path contains \'..\'', p)
193         return false
194       }
195
196       // absolutes on posix are also absolutes on win32
197       // so we only need to test this one to get both
198       if (path.win32.isAbsolute(p)) {
199         const parsed = path.win32.parse(p)
200         this.warn('stripping ' + parsed.root + ' from absolute path', p)
201         entry.path = p.substr(parsed.root.length)
202       }
203     }
204
205     // only encode : chars that aren't drive letter indicators
206     if (this.win32) {
207       const parsed = path.win32.parse(entry.path)
208       entry.path = parsed.root === '' ? wc.encode(entry.path)
209         : parsed.root + wc.encode(entry.path.substr(parsed.root.length))
210     }
211
212     if (path.isAbsolute(entry.path))
213       entry.absolute = entry.path
214     else
215       entry.absolute = path.resolve(this.cwd, entry.path)
216
217     return true
218   }
219
220   [ONENTRY] (entry) {
221     if (!this[CHECKPATH](entry))
222       return entry.resume()
223
224     assert.equal(typeof entry.absolute, 'string')
225
226     switch (entry.type) {
227       case 'Directory':
228       case 'GNUDumpDir':
229         if (entry.mode)
230           entry.mode = entry.mode | 0o700
231
232       case 'File':
233       case 'OldFile':
234       case 'ContiguousFile':
235       case 'Link':
236       case 'SymbolicLink':
237         return this[CHECKFS](entry)
238
239       case 'CharacterDevice':
240       case 'BlockDevice':
241       case 'FIFO':
242         return this[UNSUPPORTED](entry)
243     }
244   }
245
246   [ONERROR] (er, entry) {
247     // Cwd has to exist, or else nothing works. That's serious.
248     // Other errors are warnings, which raise the error in strict
249     // mode, but otherwise continue on.
250     if (er.name === 'CwdError')
251       this.emit('error', er)
252     else {
253       this.warn(er.message, er)
254       this[UNPEND]()
255       entry.resume()
256     }
257   }
258
259   [MKDIR] (dir, mode, cb) {
260     mkdir(dir, {
261       uid: this.uid,
262       gid: this.gid,
263       processUid: this.processUid,
264       processGid: this.processGid,
265       umask: this.processUmask,
266       preserve: this.preservePaths,
267       unlink: this.unlink,
268       cache: this.dirCache,
269       cwd: this.cwd,
270       mode: mode
271     }, cb)
272   }
273
274   [DOCHOWN] (entry) {
275     // in preserve owner mode, chown if the entry doesn't match process
276     // in set owner mode, chown if setting doesn't match process
277     return this.forceChown ||
278       this.preserveOwner &&
279       ( typeof entry.uid === 'number' && entry.uid !== this.processUid ||
280         typeof entry.gid === 'number' && entry.gid !== this.processGid )
281       ||
282       ( typeof this.uid === 'number' && this.uid !== this.processUid ||
283         typeof this.gid === 'number' && this.gid !== this.processGid )
284   }
285
286   [UID] (entry) {
287     return uint32(this.uid, entry.uid, this.processUid)
288   }
289
290   [GID] (entry) {
291     return uint32(this.gid, entry.gid, this.processGid)
292   }
293
294   [FILE] (entry) {
295     const mode = entry.mode & 0o7777 || this.fmode
296     const stream = new fsm.WriteStream(entry.absolute, {
297       mode: mode,
298       autoClose: false
299     })
300     stream.on('error', er => this[ONERROR](er, entry))
301
302     let actions = 1
303     const done = er => {
304       if (er)
305         return this[ONERROR](er, entry)
306
307       if (--actions === 0)
308         fs.close(stream.fd, _ => this[UNPEND]())
309     }
310
311     stream.on('finish', _ => {
312       // if futimes fails, try utimes
313       // if utimes fails, fail with the original error
314       // same for fchown/chown
315       const abs = entry.absolute
316       const fd = stream.fd
317
318       if (entry.mtime && !this.noMtime) {
319         actions++
320         const atime = entry.atime || new Date()
321         const mtime = entry.mtime
322         fs.futimes(fd, atime, mtime, er =>
323           er ? fs.utimes(abs, atime, mtime, er2 => done(er2 && er))
324           : done())
325       }
326
327       if (this[DOCHOWN](entry)) {
328         actions++
329         const uid = this[UID](entry)
330         const gid = this[GID](entry)
331         fs.fchown(fd, uid, gid, er =>
332           er ? fs.chown(abs, uid, gid, er2 => done(er2 && er))
333           : done())
334       }
335
336       done()
337     })
338
339     const tx = this.transform ? this.transform(entry) || entry : entry
340     if (tx !== entry) {
341       tx.on('error', er => this[ONERROR](er, entry))
342       entry.pipe(tx)
343     }
344     tx.pipe(stream)
345   }
346
347   [DIRECTORY] (entry) {
348     const mode = entry.mode & 0o7777 || this.dmode
349     this[MKDIR](entry.absolute, mode, er => {
350       if (er)
351         return this[ONERROR](er, entry)
352
353       let actions = 1
354       const done = _ => {
355         if (--actions === 0) {
356           this[UNPEND]()
357           entry.resume()
358         }
359       }
360
361       if (entry.mtime && !this.noMtime) {
362         actions++
363         fs.utimes(entry.absolute, entry.atime || new Date(), entry.mtime, done)
364       }
365
366       if (this[DOCHOWN](entry)) {
367         actions++
368         fs.chown(entry.absolute, this[UID](entry), this[GID](entry), done)
369       }
370
371       done()
372     })
373   }
374
375   [UNSUPPORTED] (entry) {
376     this.warn('unsupported entry type: ' + entry.type, entry)
377     entry.resume()
378   }
379
380   [SYMLINK] (entry) {
381     this[LINK](entry, entry.linkpath, 'symlink')
382   }
383
384   [HARDLINK] (entry) {
385     this[LINK](entry, path.resolve(this.cwd, entry.linkpath), 'link')
386   }
387
388   [PEND] () {
389     this[PENDING]++
390   }
391
392   [UNPEND] () {
393     this[PENDING]--
394     this[MAYBECLOSE]()
395   }
396
397   [SKIP] (entry) {
398     this[UNPEND]()
399     entry.resume()
400   }
401
402   // Check if we can reuse an existing filesystem entry safely and
403   // overwrite it, rather than unlinking and recreating
404   // Windows doesn't report a useful nlink, so we just never reuse entries
405   [ISREUSABLE] (entry, st) {
406     return entry.type === 'File' &&
407       !this.unlink &&
408       st.isFile() &&
409       st.nlink <= 1 &&
410       process.platform !== 'win32'
411   }
412
413   // check if a thing is there, and if so, try to clobber it
414   [CHECKFS] (entry) {
415     this[PEND]()
416     this[MKDIR](path.dirname(entry.absolute), this.dmode, er => {
417       if (er)
418         return this[ONERROR](er, entry)
419       fs.lstat(entry.absolute, (er, st) => {
420         if (st && (this.keep || this.newer && st.mtime > entry.mtime))
421           this[SKIP](entry)
422         else if (er || this[ISREUSABLE](entry, st))
423           this[MAKEFS](null, entry)
424         else if (st.isDirectory()) {
425           if (entry.type === 'Directory') {
426             if (!entry.mode || (st.mode & 0o7777) === entry.mode)
427               this[MAKEFS](null, entry)
428             else
429               fs.chmod(entry.absolute, entry.mode, er => this[MAKEFS](er, entry))
430           } else
431             fs.rmdir(entry.absolute, er => this[MAKEFS](er, entry))
432         } else
433           unlinkFile(entry.absolute, er => this[MAKEFS](er, entry))
434       })
435     })
436   }
437
438   [MAKEFS] (er, entry) {
439     if (er)
440       return this[ONERROR](er, entry)
441
442     switch (entry.type) {
443       case 'File':
444       case 'OldFile':
445       case 'ContiguousFile':
446         return this[FILE](entry)
447
448       case 'Link':
449         return this[HARDLINK](entry)
450
451       case 'SymbolicLink':
452         return this[SYMLINK](entry)
453
454       case 'Directory':
455       case 'GNUDumpDir':
456         return this[DIRECTORY](entry)
457     }
458   }
459
460   [LINK] (entry, linkpath, link) {
461     // XXX: get the type ('file' or 'dir') for windows
462     fs[link](linkpath, entry.absolute, er => {
463       if (er)
464         return this[ONERROR](er, entry)
465       this[UNPEND]()
466       entry.resume()
467     })
468   }
469 }
470
471 class UnpackSync extends Unpack {
472   constructor (opt) {
473     super(opt)
474   }
475
476   [CHECKFS] (entry) {
477     const er = this[MKDIR](path.dirname(entry.absolute), this.dmode)
478     if (er)
479       return this[ONERROR](er, entry)
480     try {
481       const st = fs.lstatSync(entry.absolute)
482       if (this.keep || this.newer && st.mtime > entry.mtime)
483         return this[SKIP](entry)
484       else if (this[ISREUSABLE](entry, st))
485         return this[MAKEFS](null, entry)
486       else {
487         try {
488           if (st.isDirectory()) {
489             if (entry.type === 'Directory') {
490               if (entry.mode && (st.mode & 0o7777) !== entry.mode)
491                 fs.chmodSync(entry.absolute, entry.mode)
492             } else
493               fs.rmdirSync(entry.absolute)
494           } else
495             unlinkFileSync(entry.absolute)
496           return this[MAKEFS](null, entry)
497         } catch (er) {
498           return this[ONERROR](er, entry)
499         }
500       }
501     } catch (er) {
502       return this[MAKEFS](null, entry)
503     }
504   }
505
506   [FILE] (entry) {
507     const mode = entry.mode & 0o7777 || this.fmode
508
509     const oner = er => {
510       try { fs.closeSync(fd) } catch (_) {}
511       if (er)
512         this[ONERROR](er, entry)
513     }
514
515     let stream
516     let fd
517     try {
518       fd = fs.openSync(entry.absolute, 'w', mode)
519     } catch (er) {
520       return oner(er)
521     }
522     const tx = this.transform ? this.transform(entry) || entry : entry
523     if (tx !== entry) {
524       tx.on('error', er => this[ONERROR](er, entry))
525       entry.pipe(tx)
526     }
527
528     tx.on('data', chunk => {
529       try {
530         fs.writeSync(fd, chunk, 0, chunk.length)
531       } catch (er) {
532         oner(er)
533       }
534     })
535
536     tx.on('end', _ => {
537       let er = null
538       // try both, falling futimes back to utimes
539       // if either fails, handle the first error
540       if (entry.mtime && !this.noMtime) {
541         const atime = entry.atime || new Date()
542         const mtime = entry.mtime
543         try {
544           fs.futimesSync(fd, atime, mtime)
545         } catch (futimeser) {
546           try {
547             fs.utimesSync(entry.absolute, atime, mtime)
548           } catch (utimeser) {
549             er = futimeser
550           }
551         }
552       }
553
554       if (this[DOCHOWN](entry)) {
555         const uid = this[UID](entry)
556         const gid = this[GID](entry)
557
558         try {
559           fs.fchownSync(fd, uid, gid)
560         } catch (fchowner) {
561           try {
562             fs.chownSync(entry.absolute, uid, gid)
563           } catch (chowner) {
564             er = er || fchowner
565           }
566         }
567       }
568
569       oner(er)
570     })
571   }
572
573   [DIRECTORY] (entry) {
574     const mode = entry.mode & 0o7777 || this.dmode
575     const er = this[MKDIR](entry.absolute, mode)
576     if (er)
577       return this[ONERROR](er, entry)
578     if (entry.mtime && !this.noMtime) {
579       try {
580         fs.utimesSync(entry.absolute, entry.atime || new Date(), entry.mtime)
581       } catch (er) {}
582     }
583     if (this[DOCHOWN](entry)) {
584       try {
585         fs.chownSync(entry.absolute, this[UID](entry), this[GID](entry))
586       } catch (er) {}
587     }
588     entry.resume()
589   }
590
591   [MKDIR] (dir, mode) {
592     try {
593       return mkdir.sync(dir, {
594         uid: this.uid,
595         gid: this.gid,
596         processUid: this.processUid,
597         processGid: this.processGid,
598         umask: this.processUmask,
599         preserve: this.preservePaths,
600         unlink: this.unlink,
601         cache: this.dirCache,
602         cwd: this.cwd,
603         mode: mode
604       })
605     } catch (er) {
606       return er
607     }
608   }
609
610   [LINK] (entry, linkpath, link) {
611     try {
612       fs[link + 'Sync'](linkpath, entry.absolute)
613       entry.resume()
614     } catch (er) {
615       return this[ONERROR](er, entry)
616     }
617   }
618 }
619
620 Unpack.Sync = UnpackSync
621 module.exports = Unpack