1. /*
  2. * Copyright 2000,2002,2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. /*
  18. * This package is based on the work done by Timothy Gerard Endres
  19. * (time@ice.com) to whom the Ant project is very grateful for his great code.
  20. */
  21. package org.apache.tools.tar;
  22. import java.io.InputStream;
  23. import java.io.OutputStream;
  24. import java.io.IOException;
  25. /**
  26. * The TarBuffer class implements the tar archive concept
  27. * of a buffered input stream. This concept goes back to the
  28. * days of blocked tape drives and special io devices. In the
  29. * Java universe, the only real function that this class
  30. * performs is to ensure that files have the correct "block"
  31. * size, or other tars will complain.
  32. * <p>
  33. * You should never have a need to access this class directly.
  34. * TarBuffers are created by Tar IO Streams.
  35. *
  36. */
  37. public class TarBuffer {
  38. public static final int DEFAULT_RCDSIZE = (512);
  39. public static final int DEFAULT_BLKSIZE = (DEFAULT_RCDSIZE * 20);
  40. private InputStream inStream;
  41. private OutputStream outStream;
  42. private byte[] blockBuffer;
  43. private int currBlkIdx;
  44. private int currRecIdx;
  45. private int blockSize;
  46. private int recordSize;
  47. private int recsPerBlock;
  48. private boolean debug;
  49. public TarBuffer(InputStream inStream) {
  50. this(inStream, TarBuffer.DEFAULT_BLKSIZE);
  51. }
  52. public TarBuffer(InputStream inStream, int blockSize) {
  53. this(inStream, blockSize, TarBuffer.DEFAULT_RCDSIZE);
  54. }
  55. public TarBuffer(InputStream inStream, int blockSize, int recordSize) {
  56. this.inStream = inStream;
  57. this.outStream = null;
  58. this.initialize(blockSize, recordSize);
  59. }
  60. public TarBuffer(OutputStream outStream) {
  61. this(outStream, TarBuffer.DEFAULT_BLKSIZE);
  62. }
  63. public TarBuffer(OutputStream outStream, int blockSize) {
  64. this(outStream, blockSize, TarBuffer.DEFAULT_RCDSIZE);
  65. }
  66. public TarBuffer(OutputStream outStream, int blockSize, int recordSize) {
  67. this.inStream = null;
  68. this.outStream = outStream;
  69. this.initialize(blockSize, recordSize);
  70. }
  71. /**
  72. * Initialization common to all constructors.
  73. */
  74. private void initialize(int blockSize, int recordSize) {
  75. this.debug = false;
  76. this.blockSize = blockSize;
  77. this.recordSize = recordSize;
  78. this.recsPerBlock = (this.blockSize / this.recordSize);
  79. this.blockBuffer = new byte[this.blockSize];
  80. if (this.inStream != null) {
  81. this.currBlkIdx = -1;
  82. this.currRecIdx = this.recsPerBlock;
  83. } else {
  84. this.currBlkIdx = 0;
  85. this.currRecIdx = 0;
  86. }
  87. }
  88. /**
  89. * Get the TAR Buffer's block size. Blocks consist of multiple records.
  90. */
  91. public int getBlockSize() {
  92. return this.blockSize;
  93. }
  94. /**
  95. * Get the TAR Buffer's record size.
  96. */
  97. public int getRecordSize() {
  98. return this.recordSize;
  99. }
  100. /**
  101. * Set the debugging flag for the buffer.
  102. *
  103. * @param debug If true, print debugging output.
  104. */
  105. public void setDebug(boolean debug) {
  106. this.debug = debug;
  107. }
  108. /**
  109. * Determine if an archive record indicate End of Archive. End of
  110. * archive is indicated by a record that consists entirely of null bytes.
  111. *
  112. * @param record The record data to check.
  113. */
  114. public boolean isEOFRecord(byte[] record) {
  115. for (int i = 0, sz = this.getRecordSize(); i < sz; ++i) {
  116. if (record[i] != 0) {
  117. return false;
  118. }
  119. }
  120. return true;
  121. }
  122. /**
  123. * Skip over a record on the input stream.
  124. */
  125. public void skipRecord() throws IOException {
  126. if (this.debug) {
  127. System.err.println("SkipRecord: recIdx = " + this.currRecIdx
  128. + " blkIdx = " + this.currBlkIdx);
  129. }
  130. if (this.inStream == null) {
  131. throw new IOException("reading (via skip) from an output buffer");
  132. }
  133. if (this.currRecIdx >= this.recsPerBlock) {
  134. if (!this.readBlock()) {
  135. return; // UNDONE
  136. }
  137. }
  138. this.currRecIdx++;
  139. }
  140. /**
  141. * Read a record from the input stream and return the data.
  142. *
  143. * @return The record data.
  144. */
  145. public byte[] readRecord() throws IOException {
  146. if (this.debug) {
  147. System.err.println("ReadRecord: recIdx = " + this.currRecIdx
  148. + " blkIdx = " + this.currBlkIdx);
  149. }
  150. if (this.inStream == null) {
  151. throw new IOException("reading from an output buffer");
  152. }
  153. if (this.currRecIdx >= this.recsPerBlock) {
  154. if (!this.readBlock()) {
  155. return null;
  156. }
  157. }
  158. byte[] result = new byte[this.recordSize];
  159. System.arraycopy(this.blockBuffer,
  160. (this.currRecIdx * this.recordSize), result, 0,
  161. this.recordSize);
  162. this.currRecIdx++;
  163. return result;
  164. }
  165. /**
  166. * @return false if End-Of-File, else true
  167. */
  168. private boolean readBlock() throws IOException {
  169. if (this.debug) {
  170. System.err.println("ReadBlock: blkIdx = " + this.currBlkIdx);
  171. }
  172. if (this.inStream == null) {
  173. throw new IOException("reading from an output buffer");
  174. }
  175. this.currRecIdx = 0;
  176. int offset = 0;
  177. int bytesNeeded = this.blockSize;
  178. while (bytesNeeded > 0) {
  179. long numBytes = this.inStream.read(this.blockBuffer, offset,
  180. bytesNeeded);
  181. //
  182. // NOTE
  183. // We have fit EOF, and the block is not full!
  184. //
  185. // This is a broken archive. It does not follow the standard
  186. // blocking algorithm. However, because we are generous, and
  187. // it requires little effort, we will simply ignore the error
  188. // and continue as if the entire block were read. This does
  189. // not appear to break anything upstream. We used to return
  190. // false in this case.
  191. //
  192. // Thanks to 'Yohann.Roussel@alcatel.fr' for this fix.
  193. //
  194. if (numBytes == -1) {
  195. break;
  196. }
  197. offset += numBytes;
  198. bytesNeeded -= numBytes;
  199. if (numBytes != this.blockSize) {
  200. if (this.debug) {
  201. System.err.println("ReadBlock: INCOMPLETE READ "
  202. + numBytes + " of " + this.blockSize
  203. + " bytes read.");
  204. }
  205. }
  206. }
  207. this.currBlkIdx++;
  208. return true;
  209. }
  210. /**
  211. * Get the current block number, zero based.
  212. *
  213. * @return The current zero based block number.
  214. */
  215. public int getCurrentBlockNum() {
  216. return this.currBlkIdx;
  217. }
  218. /**
  219. * Get the current record number, within the current block, zero based.
  220. * Thus, current offset = (currentBlockNum * recsPerBlk) + currentRecNum.
  221. *
  222. * @return The current zero based record number.
  223. */
  224. public int getCurrentRecordNum() {
  225. return this.currRecIdx - 1;
  226. }
  227. /**
  228. * Write an archive record to the archive.
  229. *
  230. * @param record The record data to write to the archive.
  231. */
  232. public void writeRecord(byte[] record) throws IOException {
  233. if (this.debug) {
  234. System.err.println("WriteRecord: recIdx = " + this.currRecIdx
  235. + " blkIdx = " + this.currBlkIdx);
  236. }
  237. if (this.outStream == null) {
  238. throw new IOException("writing to an input buffer");
  239. }
  240. if (record.length != this.recordSize) {
  241. throw new IOException("record to write has length '"
  242. + record.length
  243. + "' which is not the record size of '"
  244. + this.recordSize + "'");
  245. }
  246. if (this.currRecIdx >= this.recsPerBlock) {
  247. this.writeBlock();
  248. }
  249. System.arraycopy(record, 0, this.blockBuffer,
  250. (this.currRecIdx * this.recordSize),
  251. this.recordSize);
  252. this.currRecIdx++;
  253. }
  254. /**
  255. * Write an archive record to the archive, where the record may be
  256. * inside of a larger array buffer. The buffer must be "offset plus
  257. * record size" long.
  258. *
  259. * @param buf The buffer containing the record data to write.
  260. * @param offset The offset of the record data within buf.
  261. */
  262. public void writeRecord(byte[] buf, int offset) throws IOException {
  263. if (this.debug) {
  264. System.err.println("WriteRecord: recIdx = " + this.currRecIdx
  265. + " blkIdx = " + this.currBlkIdx);
  266. }
  267. if (this.outStream == null) {
  268. throw new IOException("writing to an input buffer");
  269. }
  270. if ((offset + this.recordSize) > buf.length) {
  271. throw new IOException("record has length '" + buf.length
  272. + "' with offset '" + offset
  273. + "' which is less than the record size of '"
  274. + this.recordSize + "'");
  275. }
  276. if (this.currRecIdx >= this.recsPerBlock) {
  277. this.writeBlock();
  278. }
  279. System.arraycopy(buf, offset, this.blockBuffer,
  280. (this.currRecIdx * this.recordSize),
  281. this.recordSize);
  282. this.currRecIdx++;
  283. }
  284. /**
  285. * Write a TarBuffer block to the archive.
  286. */
  287. private void writeBlock() throws IOException {
  288. if (this.debug) {
  289. System.err.println("WriteBlock: blkIdx = " + this.currBlkIdx);
  290. }
  291. if (this.outStream == null) {
  292. throw new IOException("writing to an input buffer");
  293. }
  294. this.outStream.write(this.blockBuffer, 0, this.blockSize);
  295. this.outStream.flush();
  296. this.currRecIdx = 0;
  297. this.currBlkIdx++;
  298. }
  299. /**
  300. * Flush the current data block if it has any data in it.
  301. */
  302. private void flushBlock() throws IOException {
  303. if (this.debug) {
  304. System.err.println("TarBuffer.flushBlock() called.");
  305. }
  306. if (this.outStream == null) {
  307. throw new IOException("writing to an input buffer");
  308. }
  309. if (this.currRecIdx > 0) {
  310. this.writeBlock();
  311. }
  312. }
  313. /**
  314. * Close the TarBuffer. If this is an output buffer, also flush the
  315. * current block before closing.
  316. */
  317. public void close() throws IOException {
  318. if (this.debug) {
  319. System.err.println("TarBuffer.closeBuffer().");
  320. }
  321. if (this.outStream != null) {
  322. this.flushBlock();
  323. if (this.outStream != System.out
  324. && this.outStream != System.err) {
  325. this.outStream.close();
  326. this.outStream = null;
  327. }
  328. } else if (this.inStream != null) {
  329. if (this.inStream != System.in) {
  330. this.inStream.close();
  331. this.inStream = null;
  332. }
  333. }
  334. }
  335. }