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.

pack.js 5.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. var constants = require('fs-constants')
  2. var eos = require('end-of-stream')
  3. var util = require('util')
  4. var alloc = require('buffer-alloc')
  5. var toBuffer = require('to-buffer')
  6. var Readable = require('readable-stream').Readable
  7. var Writable = require('readable-stream').Writable
  8. var StringDecoder = require('string_decoder').StringDecoder
  9. var headers = require('./headers')
  10. var DMODE = parseInt('755', 8)
  11. var FMODE = parseInt('644', 8)
  12. var END_OF_TAR = alloc(1024)
  13. var noop = function () {}
  14. var overflow = function (self, size) {
  15. size &= 511
  16. if (size) self.push(END_OF_TAR.slice(0, 512 - size))
  17. }
  18. function modeToType (mode) {
  19. switch (mode & constants.S_IFMT) {
  20. case constants.S_IFBLK: return 'block-device'
  21. case constants.S_IFCHR: return 'character-device'
  22. case constants.S_IFDIR: return 'directory'
  23. case constants.S_IFIFO: return 'fifo'
  24. case constants.S_IFLNK: return 'symlink'
  25. }
  26. return 'file'
  27. }
  28. var Sink = function (to) {
  29. Writable.call(this)
  30. this.written = 0
  31. this._to = to
  32. this._destroyed = false
  33. }
  34. util.inherits(Sink, Writable)
  35. Sink.prototype._write = function (data, enc, cb) {
  36. this.written += data.length
  37. if (this._to.push(data)) return cb()
  38. this._to._drain = cb
  39. }
  40. Sink.prototype.destroy = function () {
  41. if (this._destroyed) return
  42. this._destroyed = true
  43. this.emit('close')
  44. }
  45. var LinkSink = function () {
  46. Writable.call(this)
  47. this.linkname = ''
  48. this._decoder = new StringDecoder('utf-8')
  49. this._destroyed = false
  50. }
  51. util.inherits(LinkSink, Writable)
  52. LinkSink.prototype._write = function (data, enc, cb) {
  53. this.linkname += this._decoder.write(data)
  54. cb()
  55. }
  56. LinkSink.prototype.destroy = function () {
  57. if (this._destroyed) return
  58. this._destroyed = true
  59. this.emit('close')
  60. }
  61. var Void = function () {
  62. Writable.call(this)
  63. this._destroyed = false
  64. }
  65. util.inherits(Void, Writable)
  66. Void.prototype._write = function (data, enc, cb) {
  67. cb(new Error('No body allowed for this entry'))
  68. }
  69. Void.prototype.destroy = function () {
  70. if (this._destroyed) return
  71. this._destroyed = true
  72. this.emit('close')
  73. }
  74. var Pack = function (opts) {
  75. if (!(this instanceof Pack)) return new Pack(opts)
  76. Readable.call(this, opts)
  77. this._drain = noop
  78. this._finalized = false
  79. this._finalizing = false
  80. this._destroyed = false
  81. this._stream = null
  82. }
  83. util.inherits(Pack, Readable)
  84. Pack.prototype.entry = function (header, buffer, callback) {
  85. if (this._stream) throw new Error('already piping an entry')
  86. if (this._finalized || this._destroyed) return
  87. if (typeof buffer === 'function') {
  88. callback = buffer
  89. buffer = null
  90. }
  91. if (!callback) callback = noop
  92. var self = this
  93. if (!header.size || header.type === 'symlink') header.size = 0
  94. if (!header.type) header.type = modeToType(header.mode)
  95. if (!header.mode) header.mode = header.type === 'directory' ? DMODE : FMODE
  96. if (!header.uid) header.uid = 0
  97. if (!header.gid) header.gid = 0
  98. if (!header.mtime) header.mtime = new Date()
  99. if (typeof buffer === 'string') buffer = toBuffer(buffer)
  100. if (Buffer.isBuffer(buffer)) {
  101. header.size = buffer.length
  102. this._encode(header)
  103. this.push(buffer)
  104. overflow(self, header.size)
  105. process.nextTick(callback)
  106. return new Void()
  107. }
  108. if (header.type === 'symlink' && !header.linkname) {
  109. var linkSink = new LinkSink()
  110. eos(linkSink, function (err) {
  111. if (err) { // stream was closed
  112. self.destroy()
  113. return callback(err)
  114. }
  115. header.linkname = linkSink.linkname
  116. self._encode(header)
  117. callback()
  118. })
  119. return linkSink
  120. }
  121. this._encode(header)
  122. if (header.type !== 'file' && header.type !== 'contiguous-file') {
  123. process.nextTick(callback)
  124. return new Void()
  125. }
  126. var sink = new Sink(this)
  127. this._stream = sink
  128. eos(sink, function (err) {
  129. self._stream = null
  130. if (err) { // stream was closed
  131. self.destroy()
  132. return callback(err)
  133. }
  134. if (sink.written !== header.size) { // corrupting tar
  135. self.destroy()
  136. return callback(new Error('size mismatch'))
  137. }
  138. overflow(self, header.size)
  139. if (self._finalizing) self.finalize()
  140. callback()
  141. })
  142. return sink
  143. }
  144. Pack.prototype.finalize = function () {
  145. if (this._stream) {
  146. this._finalizing = true
  147. return
  148. }
  149. if (this._finalized) return
  150. this._finalized = true
  151. this.push(END_OF_TAR)
  152. this.push(null)
  153. }
  154. Pack.prototype.destroy = function (err) {
  155. if (this._destroyed) return
  156. this._destroyed = true
  157. if (err) this.emit('error', err)
  158. this.emit('close')
  159. if (this._stream && this._stream.destroy) this._stream.destroy()
  160. }
  161. Pack.prototype._encode = function (header) {
  162. if (!header.pax) {
  163. var buf = headers.encode(header)
  164. if (buf) {
  165. this.push(buf)
  166. return
  167. }
  168. }
  169. this._encodePax(header)
  170. }
  171. Pack.prototype._encodePax = function (header) {
  172. var paxHeader = headers.encodePax({
  173. name: header.name,
  174. linkname: header.linkname,
  175. pax: header.pax
  176. })
  177. var newHeader = {
  178. name: 'PaxHeader',
  179. mode: header.mode,
  180. uid: header.uid,
  181. gid: header.gid,
  182. size: paxHeader.length,
  183. mtime: header.mtime,
  184. type: 'pax-header',
  185. linkname: header.linkname && 'PaxHeader',
  186. uname: header.uname,
  187. gname: header.gname,
  188. devmajor: header.devmajor,
  189. devminor: header.devminor
  190. }
  191. this.push(headers.encode(newHeader))
  192. this.push(paxHeader)
  193. overflow(this, paxHeader.length)
  194. newHeader.size = header.size
  195. newHeader.type = header.type
  196. this.push(headers.encode(newHeader))
  197. }
  198. Pack.prototype._read = function (n) {
  199. var drain = this._drain
  200. this._drain = noop
  201. drain()
  202. }
  203. module.exports = Pack