11cb0ef41Sopenharmony_ci'use strict'
21cb0ef41Sopenharmony_ci
31cb0ef41Sopenharmony_ci// tar -r
41cb0ef41Sopenharmony_ciconst hlo = require('./high-level-opt.js')
51cb0ef41Sopenharmony_ciconst Pack = require('./pack.js')
61cb0ef41Sopenharmony_ciconst fs = require('fs')
71cb0ef41Sopenharmony_ciconst fsm = require('fs-minipass')
81cb0ef41Sopenharmony_ciconst t = require('./list.js')
91cb0ef41Sopenharmony_ciconst path = require('path')
101cb0ef41Sopenharmony_ci
111cb0ef41Sopenharmony_ci// starting at the head of the file, read a Header
121cb0ef41Sopenharmony_ci// If the checksum is invalid, that's our position to start writing
131cb0ef41Sopenharmony_ci// If it is, jump forward by the specified size (round up to 512)
141cb0ef41Sopenharmony_ci// and try again.
151cb0ef41Sopenharmony_ci// Write the new Pack stream starting there.
161cb0ef41Sopenharmony_ci
171cb0ef41Sopenharmony_ciconst Header = require('./header.js')
181cb0ef41Sopenharmony_ci
191cb0ef41Sopenharmony_cimodule.exports = (opt_, files, cb) => {
201cb0ef41Sopenharmony_ci  const opt = hlo(opt_)
211cb0ef41Sopenharmony_ci
221cb0ef41Sopenharmony_ci  if (!opt.file) {
231cb0ef41Sopenharmony_ci    throw new TypeError('file is required')
241cb0ef41Sopenharmony_ci  }
251cb0ef41Sopenharmony_ci
261cb0ef41Sopenharmony_ci  if (opt.gzip || opt.brotli || opt.file.endsWith('.br') || opt.file.endsWith('.tbr')) {
271cb0ef41Sopenharmony_ci    throw new TypeError('cannot append to compressed archives')
281cb0ef41Sopenharmony_ci  }
291cb0ef41Sopenharmony_ci
301cb0ef41Sopenharmony_ci  if (!files || !Array.isArray(files) || !files.length) {
311cb0ef41Sopenharmony_ci    throw new TypeError('no files or directories specified')
321cb0ef41Sopenharmony_ci  }
331cb0ef41Sopenharmony_ci
341cb0ef41Sopenharmony_ci  files = Array.from(files)
351cb0ef41Sopenharmony_ci
361cb0ef41Sopenharmony_ci  return opt.sync ? replaceSync(opt, files)
371cb0ef41Sopenharmony_ci    : replace(opt, files, cb)
381cb0ef41Sopenharmony_ci}
391cb0ef41Sopenharmony_ci
401cb0ef41Sopenharmony_ciconst replaceSync = (opt, files) => {
411cb0ef41Sopenharmony_ci  const p = new Pack.Sync(opt)
421cb0ef41Sopenharmony_ci
431cb0ef41Sopenharmony_ci  let threw = true
441cb0ef41Sopenharmony_ci  let fd
451cb0ef41Sopenharmony_ci  let position
461cb0ef41Sopenharmony_ci
471cb0ef41Sopenharmony_ci  try {
481cb0ef41Sopenharmony_ci    try {
491cb0ef41Sopenharmony_ci      fd = fs.openSync(opt.file, 'r+')
501cb0ef41Sopenharmony_ci    } catch (er) {
511cb0ef41Sopenharmony_ci      if (er.code === 'ENOENT') {
521cb0ef41Sopenharmony_ci        fd = fs.openSync(opt.file, 'w+')
531cb0ef41Sopenharmony_ci      } else {
541cb0ef41Sopenharmony_ci        throw er
551cb0ef41Sopenharmony_ci      }
561cb0ef41Sopenharmony_ci    }
571cb0ef41Sopenharmony_ci
581cb0ef41Sopenharmony_ci    const st = fs.fstatSync(fd)
591cb0ef41Sopenharmony_ci    const headBuf = Buffer.alloc(512)
601cb0ef41Sopenharmony_ci
611cb0ef41Sopenharmony_ci    POSITION: for (position = 0; position < st.size; position += 512) {
621cb0ef41Sopenharmony_ci      for (let bufPos = 0, bytes = 0; bufPos < 512; bufPos += bytes) {
631cb0ef41Sopenharmony_ci        bytes = fs.readSync(
641cb0ef41Sopenharmony_ci          fd, headBuf, bufPos, headBuf.length - bufPos, position + bufPos
651cb0ef41Sopenharmony_ci        )
661cb0ef41Sopenharmony_ci
671cb0ef41Sopenharmony_ci        if (position === 0 && headBuf[0] === 0x1f && headBuf[1] === 0x8b) {
681cb0ef41Sopenharmony_ci          throw new Error('cannot append to compressed archives')
691cb0ef41Sopenharmony_ci        }
701cb0ef41Sopenharmony_ci
711cb0ef41Sopenharmony_ci        if (!bytes) {
721cb0ef41Sopenharmony_ci          break POSITION
731cb0ef41Sopenharmony_ci        }
741cb0ef41Sopenharmony_ci      }
751cb0ef41Sopenharmony_ci
761cb0ef41Sopenharmony_ci      const h = new Header(headBuf)
771cb0ef41Sopenharmony_ci      if (!h.cksumValid) {
781cb0ef41Sopenharmony_ci        break
791cb0ef41Sopenharmony_ci      }
801cb0ef41Sopenharmony_ci      const entryBlockSize = 512 * Math.ceil(h.size / 512)
811cb0ef41Sopenharmony_ci      if (position + entryBlockSize + 512 > st.size) {
821cb0ef41Sopenharmony_ci        break
831cb0ef41Sopenharmony_ci      }
841cb0ef41Sopenharmony_ci      // the 512 for the header we just parsed will be added as well
851cb0ef41Sopenharmony_ci      // also jump ahead all the blocks for the body
861cb0ef41Sopenharmony_ci      position += entryBlockSize
871cb0ef41Sopenharmony_ci      if (opt.mtimeCache) {
881cb0ef41Sopenharmony_ci        opt.mtimeCache.set(h.path, h.mtime)
891cb0ef41Sopenharmony_ci      }
901cb0ef41Sopenharmony_ci    }
911cb0ef41Sopenharmony_ci    threw = false
921cb0ef41Sopenharmony_ci
931cb0ef41Sopenharmony_ci    streamSync(opt, p, position, fd, files)
941cb0ef41Sopenharmony_ci  } finally {
951cb0ef41Sopenharmony_ci    if (threw) {
961cb0ef41Sopenharmony_ci      try {
971cb0ef41Sopenharmony_ci        fs.closeSync(fd)
981cb0ef41Sopenharmony_ci      } catch (er) {}
991cb0ef41Sopenharmony_ci    }
1001cb0ef41Sopenharmony_ci  }
1011cb0ef41Sopenharmony_ci}
1021cb0ef41Sopenharmony_ci
1031cb0ef41Sopenharmony_ciconst streamSync = (opt, p, position, fd, files) => {
1041cb0ef41Sopenharmony_ci  const stream = new fsm.WriteStreamSync(opt.file, {
1051cb0ef41Sopenharmony_ci    fd: fd,
1061cb0ef41Sopenharmony_ci    start: position,
1071cb0ef41Sopenharmony_ci  })
1081cb0ef41Sopenharmony_ci  p.pipe(stream)
1091cb0ef41Sopenharmony_ci  addFilesSync(p, files)
1101cb0ef41Sopenharmony_ci}
1111cb0ef41Sopenharmony_ci
1121cb0ef41Sopenharmony_ciconst replace = (opt, files, cb) => {
1131cb0ef41Sopenharmony_ci  files = Array.from(files)
1141cb0ef41Sopenharmony_ci  const p = new Pack(opt)
1151cb0ef41Sopenharmony_ci
1161cb0ef41Sopenharmony_ci  const getPos = (fd, size, cb_) => {
1171cb0ef41Sopenharmony_ci    const cb = (er, pos) => {
1181cb0ef41Sopenharmony_ci      if (er) {
1191cb0ef41Sopenharmony_ci        fs.close(fd, _ => cb_(er))
1201cb0ef41Sopenharmony_ci      } else {
1211cb0ef41Sopenharmony_ci        cb_(null, pos)
1221cb0ef41Sopenharmony_ci      }
1231cb0ef41Sopenharmony_ci    }
1241cb0ef41Sopenharmony_ci
1251cb0ef41Sopenharmony_ci    let position = 0
1261cb0ef41Sopenharmony_ci    if (size === 0) {
1271cb0ef41Sopenharmony_ci      return cb(null, 0)
1281cb0ef41Sopenharmony_ci    }
1291cb0ef41Sopenharmony_ci
1301cb0ef41Sopenharmony_ci    let bufPos = 0
1311cb0ef41Sopenharmony_ci    const headBuf = Buffer.alloc(512)
1321cb0ef41Sopenharmony_ci    const onread = (er, bytes) => {
1331cb0ef41Sopenharmony_ci      if (er) {
1341cb0ef41Sopenharmony_ci        return cb(er)
1351cb0ef41Sopenharmony_ci      }
1361cb0ef41Sopenharmony_ci      bufPos += bytes
1371cb0ef41Sopenharmony_ci      if (bufPos < 512 && bytes) {
1381cb0ef41Sopenharmony_ci        return fs.read(
1391cb0ef41Sopenharmony_ci          fd, headBuf, bufPos, headBuf.length - bufPos,
1401cb0ef41Sopenharmony_ci          position + bufPos, onread
1411cb0ef41Sopenharmony_ci        )
1421cb0ef41Sopenharmony_ci      }
1431cb0ef41Sopenharmony_ci
1441cb0ef41Sopenharmony_ci      if (position === 0 && headBuf[0] === 0x1f && headBuf[1] === 0x8b) {
1451cb0ef41Sopenharmony_ci        return cb(new Error('cannot append to compressed archives'))
1461cb0ef41Sopenharmony_ci      }
1471cb0ef41Sopenharmony_ci
1481cb0ef41Sopenharmony_ci      // truncated header
1491cb0ef41Sopenharmony_ci      if (bufPos < 512) {
1501cb0ef41Sopenharmony_ci        return cb(null, position)
1511cb0ef41Sopenharmony_ci      }
1521cb0ef41Sopenharmony_ci
1531cb0ef41Sopenharmony_ci      const h = new Header(headBuf)
1541cb0ef41Sopenharmony_ci      if (!h.cksumValid) {
1551cb0ef41Sopenharmony_ci        return cb(null, position)
1561cb0ef41Sopenharmony_ci      }
1571cb0ef41Sopenharmony_ci
1581cb0ef41Sopenharmony_ci      const entryBlockSize = 512 * Math.ceil(h.size / 512)
1591cb0ef41Sopenharmony_ci      if (position + entryBlockSize + 512 > size) {
1601cb0ef41Sopenharmony_ci        return cb(null, position)
1611cb0ef41Sopenharmony_ci      }
1621cb0ef41Sopenharmony_ci
1631cb0ef41Sopenharmony_ci      position += entryBlockSize + 512
1641cb0ef41Sopenharmony_ci      if (position >= size) {
1651cb0ef41Sopenharmony_ci        return cb(null, position)
1661cb0ef41Sopenharmony_ci      }
1671cb0ef41Sopenharmony_ci
1681cb0ef41Sopenharmony_ci      if (opt.mtimeCache) {
1691cb0ef41Sopenharmony_ci        opt.mtimeCache.set(h.path, h.mtime)
1701cb0ef41Sopenharmony_ci      }
1711cb0ef41Sopenharmony_ci      bufPos = 0
1721cb0ef41Sopenharmony_ci      fs.read(fd, headBuf, 0, 512, position, onread)
1731cb0ef41Sopenharmony_ci    }
1741cb0ef41Sopenharmony_ci    fs.read(fd, headBuf, 0, 512, position, onread)
1751cb0ef41Sopenharmony_ci  }
1761cb0ef41Sopenharmony_ci
1771cb0ef41Sopenharmony_ci  const promise = new Promise((resolve, reject) => {
1781cb0ef41Sopenharmony_ci    p.on('error', reject)
1791cb0ef41Sopenharmony_ci    let flag = 'r+'
1801cb0ef41Sopenharmony_ci    const onopen = (er, fd) => {
1811cb0ef41Sopenharmony_ci      if (er && er.code === 'ENOENT' && flag === 'r+') {
1821cb0ef41Sopenharmony_ci        flag = 'w+'
1831cb0ef41Sopenharmony_ci        return fs.open(opt.file, flag, onopen)
1841cb0ef41Sopenharmony_ci      }
1851cb0ef41Sopenharmony_ci
1861cb0ef41Sopenharmony_ci      if (er) {
1871cb0ef41Sopenharmony_ci        return reject(er)
1881cb0ef41Sopenharmony_ci      }
1891cb0ef41Sopenharmony_ci
1901cb0ef41Sopenharmony_ci      fs.fstat(fd, (er, st) => {
1911cb0ef41Sopenharmony_ci        if (er) {
1921cb0ef41Sopenharmony_ci          return fs.close(fd, () => reject(er))
1931cb0ef41Sopenharmony_ci        }
1941cb0ef41Sopenharmony_ci
1951cb0ef41Sopenharmony_ci        getPos(fd, st.size, (er, position) => {
1961cb0ef41Sopenharmony_ci          if (er) {
1971cb0ef41Sopenharmony_ci            return reject(er)
1981cb0ef41Sopenharmony_ci          }
1991cb0ef41Sopenharmony_ci          const stream = new fsm.WriteStream(opt.file, {
2001cb0ef41Sopenharmony_ci            fd: fd,
2011cb0ef41Sopenharmony_ci            start: position,
2021cb0ef41Sopenharmony_ci          })
2031cb0ef41Sopenharmony_ci          p.pipe(stream)
2041cb0ef41Sopenharmony_ci          stream.on('error', reject)
2051cb0ef41Sopenharmony_ci          stream.on('close', resolve)
2061cb0ef41Sopenharmony_ci          addFilesAsync(p, files)
2071cb0ef41Sopenharmony_ci        })
2081cb0ef41Sopenharmony_ci      })
2091cb0ef41Sopenharmony_ci    }
2101cb0ef41Sopenharmony_ci    fs.open(opt.file, flag, onopen)
2111cb0ef41Sopenharmony_ci  })
2121cb0ef41Sopenharmony_ci
2131cb0ef41Sopenharmony_ci  return cb ? promise.then(cb, cb) : promise
2141cb0ef41Sopenharmony_ci}
2151cb0ef41Sopenharmony_ci
2161cb0ef41Sopenharmony_ciconst addFilesSync = (p, files) => {
2171cb0ef41Sopenharmony_ci  files.forEach(file => {
2181cb0ef41Sopenharmony_ci    if (file.charAt(0) === '@') {
2191cb0ef41Sopenharmony_ci      t({
2201cb0ef41Sopenharmony_ci        file: path.resolve(p.cwd, file.slice(1)),
2211cb0ef41Sopenharmony_ci        sync: true,
2221cb0ef41Sopenharmony_ci        noResume: true,
2231cb0ef41Sopenharmony_ci        onentry: entry => p.add(entry),
2241cb0ef41Sopenharmony_ci      })
2251cb0ef41Sopenharmony_ci    } else {
2261cb0ef41Sopenharmony_ci      p.add(file)
2271cb0ef41Sopenharmony_ci    }
2281cb0ef41Sopenharmony_ci  })
2291cb0ef41Sopenharmony_ci  p.end()
2301cb0ef41Sopenharmony_ci}
2311cb0ef41Sopenharmony_ci
2321cb0ef41Sopenharmony_ciconst addFilesAsync = (p, files) => {
2331cb0ef41Sopenharmony_ci  while (files.length) {
2341cb0ef41Sopenharmony_ci    const file = files.shift()
2351cb0ef41Sopenharmony_ci    if (file.charAt(0) === '@') {
2361cb0ef41Sopenharmony_ci      return t({
2371cb0ef41Sopenharmony_ci        file: path.resolve(p.cwd, file.slice(1)),
2381cb0ef41Sopenharmony_ci        noResume: true,
2391cb0ef41Sopenharmony_ci        onentry: entry => p.add(entry),
2401cb0ef41Sopenharmony_ci      }).then(_ => addFilesAsync(p, files))
2411cb0ef41Sopenharmony_ci    } else {
2421cb0ef41Sopenharmony_ci      p.add(file)
2431cb0ef41Sopenharmony_ci    }
2441cb0ef41Sopenharmony_ci  }
2451cb0ef41Sopenharmony_ci  p.end()
2461cb0ef41Sopenharmony_ci}
247