| 'use strict' | 
|   | 
| // tar -r | 
| const 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.slice(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.slice(1)), | 
|         noResume: true, | 
|         onentry: entry => p.add(entry), | 
|       }).then(_ => addFilesAsync(p, files)) | 
|     } else { | 
|       p.add(file) | 
|     } | 
|   } | 
|   p.end() | 
| } |