replace.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. 'use strict'
  2. // tar -r
  3. const hlo = require('./high-level-opt.js')
  4. const Pack = require('./pack.js')
  5. const fs = require('fs')
  6. const fsm = require('fs-minipass')
  7. const t = require('./list.js')
  8. const path = require('path')
  9. // starting at the head of the file, read a Header
  10. // If the checksum is invalid, that's our position to start writing
  11. // If it is, jump forward by the specified size (round up to 512)
  12. // and try again.
  13. // Write the new Pack stream starting there.
  14. const Header = require('./header.js')
  15. module.exports = (opt_, files, cb) => {
  16. const opt = hlo(opt_)
  17. if (!opt.file)
  18. throw new TypeError('file is required')
  19. if (opt.gzip)
  20. throw new TypeError('cannot append to compressed archives')
  21. if (!files || !Array.isArray(files) || !files.length)
  22. throw new TypeError('no files or directories specified')
  23. files = Array.from(files)
  24. return opt.sync ? replaceSync(opt, files)
  25. : replace(opt, files, cb)
  26. }
  27. const replaceSync = (opt, files) => {
  28. const p = new Pack.Sync(opt)
  29. let threw = true
  30. let fd
  31. let position
  32. try {
  33. try {
  34. fd = fs.openSync(opt.file, 'r+')
  35. } catch (er) {
  36. if (er.code === 'ENOENT')
  37. fd = fs.openSync(opt.file, 'w+')
  38. else
  39. throw er
  40. }
  41. const st = fs.fstatSync(fd)
  42. const headBuf = Buffer.alloc(512)
  43. POSITION: for (position = 0; position < st.size; position += 512) {
  44. for (let bufPos = 0, bytes = 0; bufPos < 512; bufPos += bytes) {
  45. bytes = fs.readSync(
  46. fd, headBuf, bufPos, headBuf.length - bufPos, position + bufPos
  47. )
  48. if (position === 0 && headBuf[0] === 0x1f && headBuf[1] === 0x8b)
  49. throw new Error('cannot append to compressed archives')
  50. if (!bytes)
  51. break POSITION
  52. }
  53. const h = new Header(headBuf)
  54. if (!h.cksumValid)
  55. break
  56. const entryBlockSize = 512 * Math.ceil(h.size / 512)
  57. if (position + entryBlockSize + 512 > st.size)
  58. break
  59. // the 512 for the header we just parsed will be added as well
  60. // also jump ahead all the blocks for the body
  61. position += entryBlockSize
  62. if (opt.mtimeCache)
  63. opt.mtimeCache.set(h.path, h.mtime)
  64. }
  65. threw = false
  66. streamSync(opt, p, position, fd, files)
  67. } finally {
  68. if (threw) {
  69. try {
  70. fs.closeSync(fd)
  71. } catch (er) {}
  72. }
  73. }
  74. }
  75. const streamSync = (opt, p, position, fd, files) => {
  76. const stream = new fsm.WriteStreamSync(opt.file, {
  77. fd: fd,
  78. start: position,
  79. })
  80. p.pipe(stream)
  81. addFilesSync(p, files)
  82. }
  83. const replace = (opt, files, cb) => {
  84. files = Array.from(files)
  85. const p = new Pack(opt)
  86. const getPos = (fd, size, cb_) => {
  87. const cb = (er, pos) => {
  88. if (er)
  89. fs.close(fd, _ => cb_(er))
  90. else
  91. cb_(null, pos)
  92. }
  93. let position = 0
  94. if (size === 0)
  95. return cb(null, 0)
  96. let bufPos = 0
  97. const headBuf = Buffer.alloc(512)
  98. const onread = (er, bytes) => {
  99. if (er)
  100. return cb(er)
  101. bufPos += bytes
  102. if (bufPos < 512 && bytes) {
  103. return fs.read(
  104. fd, headBuf, bufPos, headBuf.length - bufPos,
  105. position + bufPos, onread
  106. )
  107. }
  108. if (position === 0 && headBuf[0] === 0x1f && headBuf[1] === 0x8b)
  109. return cb(new Error('cannot append to compressed archives'))
  110. // truncated header
  111. if (bufPos < 512)
  112. return cb(null, position)
  113. const h = new Header(headBuf)
  114. if (!h.cksumValid)
  115. return cb(null, position)
  116. const entryBlockSize = 512 * Math.ceil(h.size / 512)
  117. if (position + entryBlockSize + 512 > size)
  118. return cb(null, position)
  119. position += entryBlockSize + 512
  120. if (position >= size)
  121. return cb(null, position)
  122. if (opt.mtimeCache)
  123. opt.mtimeCache.set(h.path, h.mtime)
  124. bufPos = 0
  125. fs.read(fd, headBuf, 0, 512, position, onread)
  126. }
  127. fs.read(fd, headBuf, 0, 512, position, onread)
  128. }
  129. const promise = new Promise((resolve, reject) => {
  130. p.on('error', reject)
  131. let flag = 'r+'
  132. const onopen = (er, fd) => {
  133. if (er && er.code === 'ENOENT' && flag === 'r+') {
  134. flag = 'w+'
  135. return fs.open(opt.file, flag, onopen)
  136. }
  137. if (er)
  138. return reject(er)
  139. fs.fstat(fd, (er, st) => {
  140. if (er)
  141. return fs.close(fd, () => reject(er))
  142. getPos(fd, st.size, (er, position) => {
  143. if (er)
  144. return reject(er)
  145. const stream = new fsm.WriteStream(opt.file, {
  146. fd: fd,
  147. start: position,
  148. })
  149. p.pipe(stream)
  150. stream.on('error', reject)
  151. stream.on('close', resolve)
  152. addFilesAsync(p, files)
  153. })
  154. })
  155. }
  156. fs.open(opt.file, flag, onopen)
  157. })
  158. return cb ? promise.then(cb, cb) : promise
  159. }
  160. const addFilesSync = (p, files) => {
  161. files.forEach(file => {
  162. if (file.charAt(0) === '@') {
  163. t({
  164. file: path.resolve(p.cwd, file.substr(1)),
  165. sync: true,
  166. noResume: true,
  167. onentry: entry => p.add(entry),
  168. })
  169. } else
  170. p.add(file)
  171. })
  172. p.end()
  173. }
  174. const addFilesAsync = (p, files) => {
  175. while (files.length) {
  176. const file = files.shift()
  177. if (file.charAt(0) === '@') {
  178. return t({
  179. file: path.resolve(p.cwd, file.substr(1)),
  180. noResume: true,
  181. onentry: entry => p.add(entry),
  182. }).then(_ => addFilesAsync(p, files))
  183. } else
  184. p.add(file)
  185. }
  186. p.end()
  187. }