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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. /*
  2. * The Apache Software License, Version 1.1
  3. *
  4. * Copyright (c) 2000 The Apache Software Foundation. All rights
  5. * reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions
  9. * are met:
  10. *
  11. * 1. Redistributions of source code must retain the above copyright
  12. * notice, this list of conditions and the following disclaimer.
  13. *
  14. * 2. Redistributions in binary form must reproduce the above copyright
  15. * notice, this list of conditions and the following disclaimer in
  16. * the documentation and/or other materials provided with the
  17. * distribution.
  18. *
  19. * 3. The end-user documentation included with the redistribution, if
  20. * any, must include the following acknowlegement:
  21. * "This product includes software developed by the
  22. * Apache Software Foundation (http://www.apache.org/)."
  23. * Alternately, this acknowlegement may appear in the software itself,
  24. * if and wherever such third-party acknowlegements normally appear.
  25. *
  26. * 4. The names "The Jakarta Project", "Ant", and "Apache Software
  27. * Foundation" must not be used to endorse or promote products derived
  28. * from this software without prior written permission. For written
  29. * permission, please contact apache@apache.org.
  30. *
  31. * 5. Products derived from this software may not be called "Apache"
  32. * nor may "Apache" appear in their names without prior written
  33. * permission of the Apache Group.
  34. *
  35. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  36. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  37. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  38. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  39. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  40. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  41. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  42. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  43. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  44. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  45. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  46. * SUCH DAMAGE.
  47. * ====================================================================
  48. *
  49. * This software consists of voluntary contributions made by many
  50. * individuals on behalf of the Apache Software Foundation. For more
  51. * information on the Apache Software Foundation, please see
  52. * <http://www.apache.org/>.
  53. */
  54. /*
  55. * This package is based on the work done by Timothy Gerard Endres
  56. * (time@ice.com) to whom the Ant project is very grateful for his great code.
  57. */
  58. package org.apache.tools.tar;
  59. import java.io.InputStream;
  60. import java.io.OutputStream;
  61. import java.io.IOException;
  62. /**
  63. * The TarBuffer class implements the tar archive concept
  64. * of a buffered input stream. This concept goes back to the
  65. * days of blocked tape drives and special io devices. In the
  66. * Java universe, the only real function that this class
  67. * performs is to ensure that files have the correct "block"
  68. * size, or other tars will complain.
  69. * <p>
  70. * You should never have a need to access this class directly.
  71. * TarBuffers are created by Tar IO Streams.
  72. *
  73. * @author Timothy Gerard Endres <a href="mailto:time@ice.com">time@ice.com</a>
  74. */
  75. public class TarBuffer {
  76. public static final int DEFAULT_RCDSIZE = (512);
  77. public static final int DEFAULT_BLKSIZE = (DEFAULT_RCDSIZE * 20);
  78. private InputStream inStream;
  79. private OutputStream outStream;
  80. private byte[] blockBuffer;
  81. private int currBlkIdx;
  82. private int currRecIdx;
  83. private int blockSize;
  84. private int recordSize;
  85. private int recsPerBlock;
  86. private boolean debug;
  87. public TarBuffer(InputStream inStream) {
  88. this(inStream, TarBuffer.DEFAULT_BLKSIZE);
  89. }
  90. public TarBuffer(InputStream inStream, int blockSize) {
  91. this(inStream, blockSize, TarBuffer.DEFAULT_RCDSIZE);
  92. }
  93. public TarBuffer(InputStream inStream, int blockSize, int recordSize) {
  94. this.inStream = inStream;
  95. this.outStream = null;
  96. this.initialize(blockSize, recordSize);
  97. }
  98. public TarBuffer(OutputStream outStream) {
  99. this(outStream, TarBuffer.DEFAULT_BLKSIZE);
  100. }
  101. public TarBuffer(OutputStream outStream, int blockSize) {
  102. this(outStream, blockSize, TarBuffer.DEFAULT_RCDSIZE);
  103. }
  104. public TarBuffer(OutputStream outStream, int blockSize, int recordSize) {
  105. this.inStream = null;
  106. this.outStream = outStream;
  107. this.initialize(blockSize, recordSize);
  108. }
  109. /**
  110. * Initialization common to all constructors.
  111. */
  112. private void initialize(int blockSize, int recordSize) {
  113. this.debug = false;
  114. this.blockSize = blockSize;
  115. this.recordSize = recordSize;
  116. this.recsPerBlock = (this.blockSize / this.recordSize);
  117. this.blockBuffer = new byte[this.blockSize];
  118. if (this.inStream != null) {
  119. this.currBlkIdx = -1;
  120. this.currRecIdx = this.recsPerBlock;
  121. } else {
  122. this.currBlkIdx = 0;
  123. this.currRecIdx = 0;
  124. }
  125. }
  126. /**
  127. * Get the TAR Buffer's block size. Blocks consist of multiple records.
  128. */
  129. public int getBlockSize() {
  130. return this.blockSize;
  131. }
  132. /**
  133. * Get the TAR Buffer's record size.
  134. */
  135. public int getRecordSize() {
  136. return this.recordSize;
  137. }
  138. /**
  139. * Set the debugging flag for the buffer.
  140. *
  141. * @param debug If true, print debugging output.
  142. */
  143. public void setDebug(boolean debug) {
  144. this.debug = debug;
  145. }
  146. /**
  147. * Determine if an archive record indicate End of Archive. End of
  148. * archive is indicated by a record that consists entirely of null bytes.
  149. *
  150. * @param record The record data to check.
  151. */
  152. public boolean isEOFRecord(byte[] record) {
  153. for (int i = 0, sz = this.getRecordSize(); i < sz; ++i) {
  154. if (record[i] != 0) {
  155. return false;
  156. }
  157. }
  158. return true;
  159. }
  160. /**
  161. * Skip over a record on the input stream.
  162. */
  163. public void skipRecord() throws IOException {
  164. if (this.debug) {
  165. System.err.println("SkipRecord: recIdx = " + this.currRecIdx
  166. + " blkIdx = " + this.currBlkIdx);
  167. }
  168. if (this.inStream == null) {
  169. throw new IOException("reading (via skip) from an output buffer");
  170. }
  171. if (this.currRecIdx >= this.recsPerBlock) {
  172. if (!this.readBlock()) {
  173. return; // UNDONE
  174. }
  175. }
  176. this.currRecIdx++;
  177. }
  178. /**
  179. * Read a record from the input stream and return the data.
  180. *
  181. * @return The record data.
  182. */
  183. public byte[] readRecord() throws IOException {
  184. if (this.debug) {
  185. System.err.println("ReadRecord: recIdx = " + this.currRecIdx
  186. + " blkIdx = " + this.currBlkIdx);
  187. }
  188. if (this.inStream == null) {
  189. throw new IOException("reading from an output buffer");
  190. }
  191. if (this.currRecIdx >= this.recsPerBlock) {
  192. if (!this.readBlock()) {
  193. return null;
  194. }
  195. }
  196. byte[] result = new byte[this.recordSize];
  197. System.arraycopy(this.blockBuffer,
  198. (this.currRecIdx * this.recordSize), result, 0,
  199. this.recordSize);
  200. this.currRecIdx++;
  201. return result;
  202. }
  203. /**
  204. * @return false if End-Of-File, else true
  205. */
  206. private boolean readBlock() throws IOException {
  207. if (this.debug) {
  208. System.err.println("ReadBlock: blkIdx = " + this.currBlkIdx);
  209. }
  210. if (this.inStream == null) {
  211. throw new IOException("reading from an output buffer");
  212. }
  213. this.currRecIdx = 0;
  214. int offset = 0;
  215. int bytesNeeded = this.blockSize;
  216. while (bytesNeeded > 0) {
  217. long numBytes = this.inStream.read(this.blockBuffer, offset,
  218. bytesNeeded);
  219. //
  220. // NOTE
  221. // We have fit EOF, and the block is not full!
  222. //
  223. // This is a broken archive. It does not follow the standard
  224. // blocking algorithm. However, because we are generous, and
  225. // it requires little effort, we will simply ignore the error
  226. // and continue as if the entire block were read. This does
  227. // not appear to break anything upstream. We used to return
  228. // false in this case.
  229. //
  230. // Thanks to 'Yohann.Roussel@alcatel.fr' for this fix.
  231. //
  232. if (numBytes == -1) {
  233. break;
  234. }
  235. offset += numBytes;
  236. bytesNeeded -= numBytes;
  237. if (numBytes != this.blockSize) {
  238. if (this.debug) {
  239. System.err.println("ReadBlock: INCOMPLETE READ "
  240. + numBytes + " of " + this.blockSize
  241. + " bytes read.");
  242. }
  243. }
  244. }
  245. this.currBlkIdx++;
  246. return true;
  247. }
  248. /**
  249. * Get the current block number, zero based.
  250. *
  251. * @return The current zero based block number.
  252. */
  253. public int getCurrentBlockNum() {
  254. return this.currBlkIdx;
  255. }
  256. /**
  257. * Get the current record number, within the current block, zero based.
  258. * Thus, current offset = (currentBlockNum * recsPerBlk) + currentRecNum.
  259. *
  260. * @return The current zero based record number.
  261. */
  262. public int getCurrentRecordNum() {
  263. return this.currRecIdx - 1;
  264. }
  265. /**
  266. * Write an archive record to the archive.
  267. *
  268. * @param record The record data to write to the archive.
  269. */
  270. public void writeRecord(byte[] record) throws IOException {
  271. if (this.debug) {
  272. System.err.println("WriteRecord: recIdx = " + this.currRecIdx
  273. + " blkIdx = " + this.currBlkIdx);
  274. }
  275. if (this.outStream == null) {
  276. throw new IOException("writing to an input buffer");
  277. }
  278. if (record.length != this.recordSize) {
  279. throw new IOException("record to write has length '"
  280. + record.length
  281. + "' which is not the record size of '"
  282. + this.recordSize + "'");
  283. }
  284. if (this.currRecIdx >= this.recsPerBlock) {
  285. this.writeBlock();
  286. }
  287. System.arraycopy(record, 0, this.blockBuffer,
  288. (this.currRecIdx * this.recordSize),
  289. this.recordSize);
  290. this.currRecIdx++;
  291. }
  292. /**
  293. * Write an archive record to the archive, where the record may be
  294. * inside of a larger array buffer. The buffer must be "offset plus
  295. * record size" long.
  296. *
  297. * @param buf The buffer containing the record data to write.
  298. * @param offset The offset of the record data within buf.
  299. */
  300. public void writeRecord(byte[] buf, int offset) throws IOException {
  301. if (this.debug) {
  302. System.err.println("WriteRecord: recIdx = " + this.currRecIdx
  303. + " blkIdx = " + this.currBlkIdx);
  304. }
  305. if (this.outStream == null) {
  306. throw new IOException("writing to an input buffer");
  307. }
  308. if ((offset + this.recordSize) > buf.length) {
  309. throw new IOException("record has length '" + buf.length
  310. + "' with offset '" + offset
  311. + "' which is less than the record size of '"
  312. + this.recordSize + "'");
  313. }
  314. if (this.currRecIdx >= this.recsPerBlock) {
  315. this.writeBlock();
  316. }
  317. System.arraycopy(buf, offset, this.blockBuffer,
  318. (this.currRecIdx * this.recordSize),
  319. this.recordSize);
  320. this.currRecIdx++;
  321. }
  322. /**
  323. * Write a TarBuffer block to the archive.
  324. */
  325. private void writeBlock() throws IOException {
  326. if (this.debug) {
  327. System.err.println("WriteBlock: blkIdx = " + this.currBlkIdx);
  328. }
  329. if (this.outStream == null) {
  330. throw new IOException("writing to an input buffer");
  331. }
  332. this.outStream.write(this.blockBuffer, 0, this.blockSize);
  333. this.outStream.flush();
  334. this.currRecIdx = 0;
  335. this.currBlkIdx++;
  336. }
  337. /**
  338. * Flush the current data block if it has any data in it.
  339. */
  340. private void flushBlock() throws IOException {
  341. if (this.debug) {
  342. System.err.println("TarBuffer.flushBlock() called.");
  343. }
  344. if (this.outStream == null) {
  345. throw new IOException("writing to an input buffer");
  346. }
  347. if (this.currRecIdx > 0) {
  348. this.writeBlock();
  349. }
  350. }
  351. /**
  352. * Close the TarBuffer. If this is an output buffer, also flush the
  353. * current block before closing.
  354. */
  355. public void close() throws IOException {
  356. if (this.debug) {
  357. System.err.println("TarBuffer.closeBuffer().");
  358. }
  359. if (this.outStream != null) {
  360. this.flushBlock();
  361. if (this.outStream != System.out
  362. && this.outStream != System.err) {
  363. this.outStream.close();
  364. this.outStream = null;
  365. }
  366. } else if (this.inStream != null) {
  367. if (this.inStream != System.in) {
  368. this.inStream.close();
  369. this.inStream = null;
  370. }
  371. }
  372. }
  373. }