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