You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					247 lines
				
				5.6 KiB
			
		
		
			
		
	
	
					247 lines
				
				5.6 KiB
			| 
											3 years ago
										 | '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() | ||
|  | } |