• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict'
2
3// tar -x
4const hlo = require('./high-level-opt.js')
5const Unpack = require('./unpack.js')
6const fs = require('fs')
7const fsm = require('fs-minipass')
8const path = require('path')
9
10const x = module.exports = (opt_, files, cb) => {
11  if (typeof opt_ === 'function')
12    cb = opt_, files = null, opt_ = {}
13  else if (Array.isArray(opt_))
14    files = opt_, opt_ = {}
15
16  if (typeof files === 'function')
17    cb = files, files = null
18
19  if (!files)
20    files = []
21  else
22    files = Array.from(files)
23
24  const opt = hlo(opt_)
25
26  if (opt.sync && typeof cb === 'function')
27    throw new TypeError('callback not supported for sync tar functions')
28
29  if (!opt.file && typeof cb === 'function')
30    throw new TypeError('callback only supported with file option')
31
32  if (files.length)
33    filesFilter(opt, files)
34
35  return opt.file && opt.sync ? extractFileSync(opt)
36    : opt.file ? extractFile(opt, cb)
37    : opt.sync ? extractSync(opt)
38    : extract(opt)
39}
40
41// construct a filter that limits the file entries listed
42// include child entries if a dir is included
43const filesFilter = (opt, files) => {
44  const map = new Map(files.map(f => [f.replace(/\/+$/, ''), true]))
45  const filter = opt.filter
46
47  const mapHas = (file, r) => {
48    const root = r || path.parse(file).root || '.'
49    const ret = file === root ? false
50      : map.has(file) ? map.get(file)
51      : mapHas(path.dirname(file), root)
52
53    map.set(file, ret)
54    return ret
55  }
56
57  opt.filter = filter
58    ? (file, entry) => filter(file, entry) && mapHas(file.replace(/\/+$/, ''))
59    : file => mapHas(file.replace(/\/+$/, ''))
60}
61
62const extractFileSync = opt => {
63  const u = new Unpack.Sync(opt)
64
65  const file = opt.file
66  let threw = true
67  let fd
68  const stat = fs.statSync(file)
69  // This trades a zero-byte read() syscall for a stat
70  // However, it will usually result in less memory allocation
71  const readSize = opt.maxReadSize || 16*1024*1024
72  const stream = new fsm.ReadStreamSync(file, {
73    readSize: readSize,
74    size: stat.size
75  })
76  stream.pipe(u)
77}
78
79const extractFile = (opt, cb) => {
80  const u = new Unpack(opt)
81  const readSize = opt.maxReadSize || 16*1024*1024
82
83  const file = opt.file
84  const p = new Promise((resolve, reject) => {
85    u.on('error', reject)
86    u.on('close', resolve)
87
88    // This trades a zero-byte read() syscall for a stat
89    // However, it will usually result in less memory allocation
90    fs.stat(file, (er, stat) => {
91      if (er)
92        reject(er)
93      else {
94        const stream = new fsm.ReadStream(file, {
95          readSize: readSize,
96          size: stat.size
97        })
98        stream.on('error', reject)
99        stream.pipe(u)
100      }
101    })
102  })
103  return cb ? p.then(cb, cb) : p
104}
105
106const extractSync = opt => {
107  return new Unpack.Sync(opt)
108}
109
110const extract = opt => {
111  return new Unpack(opt)
112}
113