/* ** Authored by Timothy Gerard Endres ** <mailto:time@gjt.org> <http://www.trustice.com> ** ** This work has been placed into the public domain. ** You may use this work in any way and for any purpose you wish. ** ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND, ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR ** REDISTRIBUTION OF THIS SOFTWARE. ** */ package installer; import java.io.*; /** * The TarOutputStream writes a UNIX tar archive as an OutputStream. * Methods are provided to put entries, and then write their contents * by writing to this stream using write(). * * * @version $Revision: 12504 $ * @author Timothy Gerard Endres, * <a href="mailto:time@gjt.org">time@trustice.com</a>. * @see TarBuffer * @see TarHeader * @see TarEntry */ public class TarOutputStream extends FilterOutputStream { protected boolean debug; protected int currSize; protected int currBytes; protected byte[] oneBuf; protected byte[] recordBuf; protected int assemLen; protected byte[] assemBuf; protected TarBuffer buffer; public TarOutputStream( OutputStream os ) { this( os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE ); } public TarOutputStream( OutputStream os, int blockSize ) { this( os, blockSize, TarBuffer.DEFAULT_RCDSIZE ); } public TarOutputStream( OutputStream os, int blockSize, int recordSize ) { super( os ); this.buffer = new TarBuffer( os, blockSize, recordSize ); this.debug = false; this.assemLen = 0; this.assemBuf = new byte[ recordSize ]; this.recordBuf = new byte[ recordSize ]; this.oneBuf = new byte[1]; } /** * Sets the debugging flag. * * @param debugF True to turn on debugging. */ public void setDebug( boolean debugF ) { this.debug = debugF; } /** * Sets the debugging flag in this stream's TarBuffer. * * @param debugF True to turn on debugging. */ public void setBufferDebug( boolean debug ) { this.buffer.setDebug( debug ); } /** * Ends the TAR archive without closing the underlying OutputStream. * The result is that the EOF record of nulls is written. */ public void finish() throws IOException { this.writeEOFRecord(); } /** * Ends the TAR archive and closes the underlying OutputStream. * This means that finish() is called followed by calling the * TarBuffer's close(). */ public void close() throws IOException { this.finish(); this.buffer.close(); } /** * Get the record size being used by this stream's TarBuffer. * * @return The TarBuffer record size. */ public int getRecordSize() { return this.buffer.getRecordSize(); } /** * Put an entry on the output stream. This writes the entry's * header record and positions the output stream for writing * the contents of the entry. Once this method is called, the * stream is ready for calls to write() to write the entry's * contents. Once the contents are written, closeEntry() * <B>MUST</B> be called to ensure that all buffered data * is completely written to the output stream. * * @param entry The TarEntry to be written to the archive. */ public void putNextEntry( TarEntry entry ) throws IOException { if ( entry.getHeader().name.length() > TarHeader.NAMELEN ) throw new InvalidHeaderException ( "file name '" + entry.getHeader().name + "' is too long ( > " + TarHeader.NAMELEN + " bytes )" ); entry.writeEntryHeader( this.recordBuf ); this.buffer.writeRecord( this.recordBuf ); this.currBytes = 0; if ( entry.isDirectory() ) this.currSize = 0; else this.currSize = (int)entry.getSize(); } /** * Close an entry. This method MUST be called for all file * entries that contain data. The reason is that we must * buffer data written to the stream in order to satisfy * the buffer's record based writes. Thus, there may be * data fragments still being assembled that must be written * to the output stream before this entry is closed and the * next entry written. */ public void closeEntry() throws IOException { if ( this.assemLen > 0 ) { for ( int i = this.assemLen ; i < this.assemBuf.length ; ++i ) this.assemBuf[i] = 0; this.buffer.writeRecord( this.assemBuf ); this.currBytes += this.assemLen; this.assemLen = 0; } if ( this.currBytes < this.currSize ) throw new IOException ( "entry closed at '" + this.currBytes + "' before the '" + this.currSize + "' bytes specified in the header were written" ); } /** * Writes a byte to the current tar archive entry. * * This method simply calls read( byte[], int, int ). * * @param b The byte written. */ public void write( int b ) throws IOException { this.oneBuf[0] = (byte) b; this.write( this.oneBuf, 0, 1 ); } /** * Writes bytes to the current tar archive entry. * * This method simply calls read( byte[], int, int ). * * @param wBuf The buffer to write to the archive. * @return The number of bytes read, or -1 at EOF. */ public void write( byte[] wBuf ) throws IOException { this.write( wBuf, 0, wBuf.length ); } /** * Writes bytes to the current tar archive entry. This method * is aware of the current entry and will throw an exception if * you attempt to write bytes past the length specified for the * current entry. The method is also (painfully) aware of the * record buffering required by TarBuffer, and manages buffers * that are not a multiple of recordsize in length, including * assembling records from small buffers. * * This method simply calls read( byte[], int, int ). * * @param wBuf The buffer to write to the archive. * @param wOffset The offset in the buffer from which to get bytes. * @param numToWrite The number of bytes to write. */ public void write( byte[] wBuf, int wOffset, int numToWrite ) throws IOException { if ( (this.currBytes + numToWrite) > this.currSize ) throw new IOException ( "request to write '" + numToWrite + "' bytes exceeds size in header of '" + this.currSize + "' bytes" ); // // We have to deal with assembly!!! // The programmer can be writing little 32 byte chunks for all // we know, and we must assemble complete records for writing. // REVIEW Maybe this should be in TarBuffer? Could that help to // eliminate some of the buffer copying. // if ( this.assemLen > 0 ) { if ( (this.assemLen + numToWrite ) >= this.recordBuf.length ) { int aLen = this.recordBuf.length - this.assemLen; System.arraycopy ( this.assemBuf, 0, this.recordBuf, 0, this.assemLen ); System.arraycopy ( wBuf, wOffset, this.recordBuf, this.assemLen, aLen ); this.buffer.writeRecord( this.recordBuf ); this.currBytes += this.recordBuf.length; wOffset += aLen; numToWrite -= aLen; this.assemLen = 0; } else // ( (this.assemLen + numToWrite ) < this.recordBuf.length ) { System.arraycopy ( wBuf, wOffset, this.assemBuf, this.assemLen, numToWrite ); wOffset += numToWrite; this.assemLen += numToWrite; numToWrite -= numToWrite; } } // // When we get here we have EITHER: // o An empty "assemble" buffer. // o No bytes to write (numToWrite == 0) // for ( ; numToWrite > 0 ; ) { if ( numToWrite < this.recordBuf.length ) { System.arraycopy ( wBuf, wOffset, this.assemBuf, this.assemLen, numToWrite ); this.assemLen += numToWrite; break; } this.buffer.writeRecord( wBuf, wOffset ); int num = this.recordBuf.length; this.currBytes += num; numToWrite -= num; wOffset += num; } } /** * Write an EOF (end of archive) record to the tar archive. * An EOF record consists of a record of all zeros. */ private void writeEOFRecord() throws IOException { for ( int i = 0 ; i < this.recordBuf.length ; ++i ) this.recordBuf[i] = 0; this.buffer.writeRecord( this.recordBuf ); } }