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.

TarOutputStream.java 12 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  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.FilterOutputStream;
  24. import java.io.OutputStream;
  25. import java.io.IOException;
  26. /**
  27. * The TarOutputStream writes a UNIX tar archive as an OutputStream.
  28. * Methods are provided to put entries, and then write their contents
  29. * by writing to this stream using write().
  30. *
  31. */
  32. public class TarOutputStream extends FilterOutputStream {
  33. /** Fail if a long file name is required in the archive. */
  34. public static final int LONGFILE_ERROR = 0;
  35. /** Long paths will be truncated in the archive. */
  36. public static final int LONGFILE_TRUNCATE = 1;
  37. /** GNU tar extensions are used to store long file names in the archive. */
  38. public static final int LONGFILE_GNU = 2;
  39. // CheckStyle:VisibilityModifier OFF - bc
  40. protected boolean debug;
  41. protected long currSize;
  42. protected String currName;
  43. protected long currBytes;
  44. protected byte[] oneBuf;
  45. protected byte[] recordBuf;
  46. protected int assemLen;
  47. protected byte[] assemBuf;
  48. protected TarBuffer buffer;
  49. protected int longFileMode = LONGFILE_ERROR;
  50. // CheckStyle:VisibilityModifier ON
  51. private boolean closed = false;
  52. /**
  53. * Constructor for TarInputStream.
  54. * @param os the output stream to use
  55. */
  56. public TarOutputStream(OutputStream os) {
  57. this(os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE);
  58. }
  59. /**
  60. * Constructor for TarInputStream.
  61. * @param os the output stream to use
  62. * @param blockSize the block size to use
  63. */
  64. public TarOutputStream(OutputStream os, int blockSize) {
  65. this(os, blockSize, TarBuffer.DEFAULT_RCDSIZE);
  66. }
  67. /**
  68. * Constructor for TarInputStream.
  69. * @param os the output stream to use
  70. * @param blockSize the block size to use
  71. * @param recordSize the record size to use
  72. */
  73. public TarOutputStream(OutputStream os, int blockSize, int recordSize) {
  74. super(os);
  75. this.buffer = new TarBuffer(os, blockSize, recordSize);
  76. this.debug = false;
  77. this.assemLen = 0;
  78. this.assemBuf = new byte[recordSize];
  79. this.recordBuf = new byte[recordSize];
  80. this.oneBuf = new byte[1];
  81. }
  82. /**
  83. * Set the long file mode.
  84. * This can be LONGFILE_ERROR(0), LONGFILE_TRUNCATE(1) or LONGFILE_GNU(2).
  85. * This specifies the treatment of long file names (names >= TarConstants.NAMELEN).
  86. * Default is LONGFILE_ERROR.
  87. * @param longFileMode the mode to use
  88. */
  89. public void setLongFileMode(int longFileMode) {
  90. this.longFileMode = longFileMode;
  91. }
  92. /**
  93. * Sets the debugging flag.
  94. *
  95. * @param debugF True to turn on debugging.
  96. */
  97. public void setDebug(boolean debugF) {
  98. this.debug = debugF;
  99. }
  100. /**
  101. * Sets the debugging flag in this stream's TarBuffer.
  102. *
  103. * @param debug True to turn on debugging.
  104. */
  105. public void setBufferDebug(boolean debug) {
  106. buffer.setDebug(debug);
  107. }
  108. /**
  109. * Ends the TAR archive without closing the underlying OutputStream.
  110. * The result is that the two EOF records of nulls are written.
  111. * @throws IOException on error
  112. */
  113. public void finish() throws IOException {
  114. // See Bugzilla 28776 for a discussion on this
  115. // http://issues.apache.org/bugzilla/show_bug.cgi?id=28776
  116. writeEOFRecord();
  117. writeEOFRecord();
  118. }
  119. /**
  120. * Ends the TAR archive and closes the underlying OutputStream.
  121. * This means that finish() is called followed by calling the
  122. * TarBuffer's close().
  123. * @throws IOException on error
  124. */
  125. public void close() throws IOException {
  126. if (!closed) {
  127. finish();
  128. buffer.close();
  129. out.close();
  130. closed = true;
  131. }
  132. }
  133. /**
  134. * Get the record size being used by this stream's TarBuffer.
  135. *
  136. * @return The TarBuffer record size.
  137. */
  138. public int getRecordSize() {
  139. return buffer.getRecordSize();
  140. }
  141. /**
  142. * Put an entry on the output stream. This writes the entry's
  143. * header record and positions the output stream for writing
  144. * the contents of the entry. Once this method is called, the
  145. * stream is ready for calls to write() to write the entry's
  146. * contents. Once the contents are written, closeEntry()
  147. * <B>MUST</B> be called to ensure that all buffered data
  148. * is completely written to the output stream.
  149. *
  150. * @param entry The TarEntry to be written to the archive.
  151. * @throws IOException on error
  152. */
  153. public void putNextEntry(TarEntry entry) throws IOException {
  154. if (entry.getName().length() >= TarConstants.NAMELEN) {
  155. if (longFileMode == LONGFILE_GNU) {
  156. // create a TarEntry for the LongLink, the contents
  157. // of which are the entry's name
  158. TarEntry longLinkEntry = new TarEntry(TarConstants.GNU_LONGLINK,
  159. TarConstants.LF_GNUTYPE_LONGNAME);
  160. longLinkEntry.setSize(entry.getName().length() + 1);
  161. putNextEntry(longLinkEntry);
  162. write(entry.getName().getBytes());
  163. write(0);
  164. closeEntry();
  165. } else if (longFileMode != LONGFILE_TRUNCATE) {
  166. throw new RuntimeException("file name '" + entry.getName()
  167. + "' is too long ( > "
  168. + TarConstants.NAMELEN + " bytes)");
  169. }
  170. }
  171. entry.writeEntryHeader(recordBuf);
  172. buffer.writeRecord(recordBuf);
  173. currBytes = 0;
  174. if (entry.isDirectory()) {
  175. currSize = 0;
  176. } else {
  177. currSize = entry.getSize();
  178. }
  179. currName = entry.getName();
  180. }
  181. /**
  182. * Close an entry. This method MUST be called for all file
  183. * entries that contain data. The reason is that we must
  184. * buffer data written to the stream in order to satisfy
  185. * the buffer's record based writes. Thus, there may be
  186. * data fragments still being assembled that must be written
  187. * to the output stream before this entry is closed and the
  188. * next entry written.
  189. * @throws IOException on error
  190. */
  191. public void closeEntry() throws IOException {
  192. if (assemLen > 0) {
  193. for (int i = assemLen; i < assemBuf.length; ++i) {
  194. assemBuf[i] = 0;
  195. }
  196. buffer.writeRecord(assemBuf);
  197. currBytes += assemLen;
  198. assemLen = 0;
  199. }
  200. if (currBytes < currSize) {
  201. throw new IOException("entry '" + currName + "' closed at '"
  202. + currBytes
  203. + "' before the '" + currSize
  204. + "' bytes specified in the header were written");
  205. }
  206. }
  207. /**
  208. * Writes a byte to the current tar archive entry.
  209. *
  210. * This method simply calls read( byte[], int, int ).
  211. *
  212. * @param b The byte written.
  213. * @throws IOException on error
  214. */
  215. public void write(int b) throws IOException {
  216. oneBuf[0] = (byte) b;
  217. write(oneBuf, 0, 1);
  218. }
  219. /**
  220. * Writes bytes to the current tar archive entry.
  221. *
  222. * This method simply calls write( byte[], int, int ).
  223. *
  224. * @param wBuf The buffer to write to the archive.
  225. * @throws IOException on error
  226. */
  227. public void write(byte[] wBuf) throws IOException {
  228. write(wBuf, 0, wBuf.length);
  229. }
  230. /**
  231. * Writes bytes to the current tar archive entry. This method
  232. * is aware of the current entry and will throw an exception if
  233. * you attempt to write bytes past the length specified for the
  234. * current entry. The method is also (painfully) aware of the
  235. * record buffering required by TarBuffer, and manages buffers
  236. * that are not a multiple of recordsize in length, including
  237. * assembling records from small buffers.
  238. *
  239. * @param wBuf The buffer to write to the archive.
  240. * @param wOffset The offset in the buffer from which to get bytes.
  241. * @param numToWrite The number of bytes to write.
  242. * @throws IOException on error
  243. */
  244. public void write(byte[] wBuf, int wOffset, int numToWrite) throws IOException {
  245. if ((currBytes + numToWrite) > currSize) {
  246. throw new IOException("request to write '" + numToWrite
  247. + "' bytes exceeds size in header of '"
  248. + currSize + "' bytes for entry '"
  249. + currName + "'");
  250. //
  251. // We have to deal with assembly!!!
  252. // The programmer can be writing little 32 byte chunks for all
  253. // we know, and we must assemble complete records for writing.
  254. // REVIEW Maybe this should be in TarBuffer? Could that help to
  255. // eliminate some of the buffer copying.
  256. //
  257. }
  258. if (assemLen > 0) {
  259. if ((assemLen + numToWrite) >= recordBuf.length) {
  260. int aLen = recordBuf.length - assemLen;
  261. System.arraycopy(assemBuf, 0, recordBuf, 0,
  262. assemLen);
  263. System.arraycopy(wBuf, wOffset, recordBuf,
  264. assemLen, aLen);
  265. buffer.writeRecord(recordBuf);
  266. currBytes += recordBuf.length;
  267. wOffset += aLen;
  268. numToWrite -= aLen;
  269. assemLen = 0;
  270. } else {
  271. System.arraycopy(wBuf, wOffset, assemBuf, assemLen,
  272. numToWrite);
  273. wOffset += numToWrite;
  274. assemLen += numToWrite;
  275. numToWrite -= numToWrite;
  276. }
  277. }
  278. //
  279. // When we get here we have EITHER:
  280. // o An empty "assemble" buffer.
  281. // o No bytes to write (numToWrite == 0)
  282. //
  283. while (numToWrite > 0) {
  284. if (numToWrite < recordBuf.length) {
  285. System.arraycopy(wBuf, wOffset, assemBuf, assemLen,
  286. numToWrite);
  287. assemLen += numToWrite;
  288. break;
  289. }
  290. buffer.writeRecord(wBuf, wOffset);
  291. int num = recordBuf.length;
  292. currBytes += num;
  293. numToWrite -= num;
  294. wOffset += num;
  295. }
  296. }
  297. /**
  298. * Write an EOF (end of archive) record to the tar archive.
  299. * An EOF record consists of a record of all zeros.
  300. */
  301. private void writeEOFRecord() throws IOException {
  302. for (int i = 0; i < recordBuf.length; ++i) {
  303. recordBuf[i] = 0;
  304. }
  305. buffer.writeRecord(recordBuf);
  306. }
  307. }