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