| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 | 'use strict'// tar -rconst hlo = require('./high-level-opt.js')const Pack = require('./pack.js')const fs = require('fs')const fsm = require('fs-minipass')const t = require('./list.js')const path = require('path')// starting at the head of the file, read a Header// If the checksum is invalid, that's our position to start writing// If it is, jump forward by the specified size (round up to 512)// and try again.// Write the new Pack stream starting there.const Header = require('./header.js')module.exports = (opt_, files, cb) => {  const opt = hlo(opt_)  if (!opt.file)    throw new TypeError('file is required')  if (opt.gzip)    throw new TypeError('cannot append to compressed archives')  if (!files || !Array.isArray(files) || !files.length)    throw new TypeError('no files or directories specified')  files = Array.from(files)  return opt.sync ? replaceSync(opt, files)    : replace(opt, files, cb)}const replaceSync = (opt, files) => {  const p = new Pack.Sync(opt)  let threw = true  let fd  let position  try {    try {      fd = fs.openSync(opt.file, 'r+')    } catch (er) {      if (er.code === 'ENOENT')        fd = fs.openSync(opt.file, 'w+')      else        throw er    }    const st = fs.fstatSync(fd)    const headBuf = Buffer.alloc(512)    POSITION: for (position = 0; position < st.size; position += 512) {      for (let bufPos = 0, bytes = 0; bufPos < 512; bufPos += bytes) {        bytes = fs.readSync(          fd, headBuf, bufPos, headBuf.length - bufPos, position + bufPos        )        if (position === 0 && headBuf[0] === 0x1f && headBuf[1] === 0x8b)          throw new Error('cannot append to compressed archives')        if (!bytes)          break POSITION      }      const h = new Header(headBuf)      if (!h.cksumValid)        break      const entryBlockSize = 512 * Math.ceil(h.size / 512)      if (position + entryBlockSize + 512 > st.size)        break      // the 512 for the header we just parsed will be added as well      // also jump ahead all the blocks for the body      position += entryBlockSize      if (opt.mtimeCache)        opt.mtimeCache.set(h.path, h.mtime)    }    threw = false    streamSync(opt, p, position, fd, files)  } finally {    if (threw) {      try {        fs.closeSync(fd)      } catch (er) {}    }  }}const streamSync = (opt, p, position, fd, files) => {  const stream = new fsm.WriteStreamSync(opt.file, {    fd: fd,    start: position,  })  p.pipe(stream)  addFilesSync(p, files)}const replace = (opt, files, cb) => {  files = Array.from(files)  const p = new Pack(opt)  const getPos = (fd, size, cb_) => {    const cb = (er, pos) => {      if (er)        fs.close(fd, _ => cb_(er))      else        cb_(null, pos)    }    let position = 0    if (size === 0)      return cb(null, 0)    let bufPos = 0    const headBuf = Buffer.alloc(512)    const onread = (er, bytes) => {      if (er)        return cb(er)      bufPos += bytes      if (bufPos < 512 && bytes) {        return fs.read(          fd, headBuf, bufPos, headBuf.length - bufPos,          position + bufPos, onread        )      }      if (position === 0 && headBuf[0] === 0x1f && headBuf[1] === 0x8b)        return cb(new Error('cannot append to compressed archives'))      // truncated header      if (bufPos < 512)        return cb(null, position)      const h = new Header(headBuf)      if (!h.cksumValid)        return cb(null, position)      const entryBlockSize = 512 * Math.ceil(h.size / 512)      if (position + entryBlockSize + 512 > size)        return cb(null, position)      position += entryBlockSize + 512      if (position >= size)        return cb(null, position)      if (opt.mtimeCache)        opt.mtimeCache.set(h.path, h.mtime)      bufPos = 0      fs.read(fd, headBuf, 0, 512, position, onread)    }    fs.read(fd, headBuf, 0, 512, position, onread)  }  const promise = new Promise((resolve, reject) => {    p.on('error', reject)    let flag = 'r+'    const onopen = (er, fd) => {      if (er && er.code === 'ENOENT' && flag === 'r+') {        flag = 'w+'        return fs.open(opt.file, flag, onopen)      }      if (er)        return reject(er)      fs.fstat(fd, (er, st) => {        if (er)          return fs.close(fd, () => reject(er))        getPos(fd, st.size, (er, position) => {          if (er)            return reject(er)          const stream = new fsm.WriteStream(opt.file, {            fd: fd,            start: position,          })          p.pipe(stream)          stream.on('error', reject)          stream.on('close', resolve)          addFilesAsync(p, files)        })      })    }    fs.open(opt.file, flag, onopen)  })  return cb ? promise.then(cb, cb) : promise}const addFilesSync = (p, files) => {  files.forEach(file => {    if (file.charAt(0) === '@') {      t({        file: path.resolve(p.cwd, file.substr(1)),        sync: true,        noResume: true,        onentry: entry => p.add(entry),      })    } else      p.add(file)  })  p.end()}const addFilesAsync = (p, files) => {  while (files.length) {    const file = files.shift()    if (file.charAt(0) === '@') {      return t({        file: path.resolve(p.cwd, file.substr(1)),        noResume: true,        onentry: entry => p.add(entry),      }).then(_ => addFilesAsync(p, files))    } else      p.add(file)  }  p.end()}
 |