You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

TarBuffer.java 14 kB

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