001/* 002** Authored by Timothy Gerard Endres 003** <mailto:time@gjt.org> <http://www.trustice.com> 004** 005** This work has been placed into the public domain. 006** You may use this work in any way and for any purpose you wish. 007** 008** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND, 009** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR 010** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY 011** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR 012** REDISTRIBUTION OF THIS SOFTWARE. 013** 014*/ 015 016package com.ice.tar; 017 018import java.io.*; 019import javax.activation.*; 020 021 022/** 023 * The TarOutputStream writes a UNIX tar archive as an OutputStream. 024 * Methods are provided to put entries, and then write their contents 025 * by writing to this stream using write(). 026 * 027 * Kerry Menzel <kmenzel@cfl.rr.com> Contributed the code to support 028 * file sizes greater than 2GB (longs versus ints). 029 * 030 * @version $Revision: 1.8 $ 031 * @author Timothy Gerard Endres, <time@gjt.org> 032 * @see TarBuffer 033 * @see TarHeader 034 * @see TarEntry 035 */ 036 037 038public 039class TarOutputStream 040extends FilterOutputStream 041 { 042 protected boolean debug; 043 protected long currSize; 044 protected long currBytes; 045 protected byte[] oneBuf; 046 protected byte[] recordBuf; 047 protected int assemLen; 048 protected byte[] assemBuf; 049 protected TarBuffer buffer; 050 051 052 public 053 TarOutputStream( OutputStream os ) 054 { 055 this( os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE ); 056 } 057 058 public 059 TarOutputStream( OutputStream os, int blockSize ) 060 { 061 this( os, blockSize, TarBuffer.DEFAULT_RCDSIZE ); 062 } 063 064 public 065 TarOutputStream( OutputStream os, int blockSize, int recordSize ) 066 { 067 super( os ); 068 069 this.buffer = new TarBuffer( os, blockSize, recordSize ); 070 071 this.debug = false; 072 this.assemLen = 0; 073 this.assemBuf = new byte[ recordSize ]; 074 this.recordBuf = new byte[ recordSize ]; 075 this.oneBuf = new byte[1]; 076 } 077 078 /** 079 * Sets the debugging flag. 080 * 081 * @param debugF True to turn on debugging. 082 */ 083 public void 084 setDebug( boolean debugF ) 085 { 086 this.debug = debugF; 087 } 088 089 /** 090 * Sets the debugging flag in this stream's TarBuffer. 091 * 092 * @param debugF True to turn on debugging. 093 */ 094 public void 095 setBufferDebug( boolean debug ) 096 { 097 this.buffer.setDebug( debug ); 098 } 099 100 /** 101 * Ends the TAR archive without closing the underlying OutputStream. 102 * The result is that the EOF record of nulls is written. 103 */ 104 105 public void 106 finish() 107 throws IOException 108 { 109 this.writeEOFRecord(); 110 } 111 112 /** 113 * Ends the TAR archive and closes the underlying OutputStream. 114 * This means that finish() is called followed by calling the 115 * TarBuffer's close(). 116 */ 117 118 public void 119 close() 120 throws IOException 121 { 122 this.finish(); 123 this.buffer.close(); 124 } 125 126 /** 127 * Get the record size being used by this stream's TarBuffer. 128 * 129 * @return The TarBuffer record size. 130 */ 131 public int 132 getRecordSize() 133 { 134 return this.buffer.getRecordSize(); 135 } 136 137 /** 138 * Put an entry on the output stream. This writes the entry's 139 * header record and positions the output stream for writing 140 * the contents of the entry. Once this method is called, the 141 * stream is ready for calls to write() to write the entry's 142 * contents. Once the contents are written, closeEntry() 143 * <B>MUST</B> be called to ensure that all buffered data 144 * is completely written to the output stream. 145 * 146 * @param entry The TarEntry to be written to the archive. 147 */ 148 public void 149 putNextEntry( TarEntry entry ) 150 throws IOException 151 { 152 StringBuffer name = entry.getHeader().name; 153 154 // NOTE 155 // This check is not adequate, because the maximum file length that 156 // can be placed into a POSIX (ustar) header depends on the precise 157 // locations of the path elements (slashes) within the file's full 158 // pathname. For this reason, writeEntryHeader() can still throw an 159 // InvalidHeaderException if the file's full pathname will not fit 160 // in the header. 161 162 if ( ( entry.isUnixTarFormat() 163 && name.length() > TarHeader.NAMELEN ) 164 || 165 ( ! entry.isUnixTarFormat() 166 && name.length() > (TarHeader.NAMELEN + TarHeader.PREFIXLEN) ) 167 ) 168 { 169 throw new InvalidHeaderException 170 ( "file name '" 171 + name 172 + "' is too long ( " 173 + name.length() 174 + " > " 175 + ( entry.isUnixTarFormat() 176 ? TarHeader.NAMELEN 177 : (TarHeader.NAMELEN + TarHeader.PREFIXLEN) ) 178 + " bytes )" ); 179 } 180 181 entry.writeEntryHeader( this.recordBuf ); 182 183 this.buffer.writeRecord( this.recordBuf ); 184 185 this.currBytes = 0; 186 187 if ( entry.isDirectory() ) 188 this.currSize = 0; 189 else 190 this.currSize = entry.getSize(); 191 } 192 193 /** 194 * Close an entry. This method MUST be called for all file 195 * entries that contain data. The reason is that we must 196 * buffer data written to the stream in order to satisfy 197 * the buffer's record based writes. Thus, there may be 198 * data fragments still being assembled that must be written 199 * to the output stream before this entry is closed and the 200 * next entry written. 201 */ 202 public void 203 closeEntry() 204 throws IOException 205 { 206 if ( this.assemLen > 0 ) 207 { 208 for ( int i = this.assemLen ; i < this.assemBuf.length ; ++i ) 209 this.assemBuf[i] = 0; 210 211 this.buffer.writeRecord( this.assemBuf ); 212 213 this.currBytes += this.assemLen; 214 this.assemLen = 0; 215 } 216 217 if ( this.currBytes < this.currSize ) 218 throw new IOException 219 ( "entry closed at '" + this.currBytes 220 + "' before the '" + this.currSize 221 + "' bytes specified in the header were written" ); 222 } 223 224 /** 225 * Writes a byte to the current tar archive entry. 226 * 227 * This method simply calls read( byte[], int, int ). 228 * 229 * @param b The byte written. 230 */ 231 public void 232 write( int b ) 233 throws IOException 234 { 235 this.oneBuf[0] = (byte) b; 236 this.write( this.oneBuf, 0, 1 ); 237 } 238 239 /** 240 * Writes bytes to the current tar archive entry. 241 * 242 * This method simply calls read( byte[], int, int ). 243 * 244 * @param wBuf The buffer to write to the archive. 245 * @return The number of bytes read, or -1 at EOF. 246 */ 247 public void 248 write( byte[] wBuf ) 249 throws IOException 250 { 251 this.write( wBuf, 0, wBuf.length ); 252 } 253 254 /** 255 * Writes bytes to the current tar archive entry. This method 256 * is aware of the current entry and will throw an exception if 257 * you attempt to write bytes past the length specified for the 258 * current entry. The method is also (painfully) aware of the 259 * record buffering required by TarBuffer, and manages buffers 260 * that are not a multiple of recordsize in length, including 261 * assembling records from small buffers. 262 * 263 * This method simply calls read( byte[], int, int ). 264 * 265 * @param wBuf The buffer to write to the archive. 266 * @param wOffset The offset in the buffer from which to get bytes. 267 * @param numToWrite The number of bytes to write. 268 */ 269 public void 270 write( byte[] wBuf, int wOffset, int numToWrite ) 271 throws IOException 272 { 273 if ( (this.currBytes + numToWrite) > this.currSize ) 274 throw new IOException 275 ( "request to write '" + numToWrite 276 + "' bytes exceeds size in header of '" 277 + this.currSize + "' bytes" ); 278 279 // 280 // We have to deal with assembly!!! 281 // The programmer can be writing little 32 byte chunks for all 282 // we know, and we must assemble complete records for writing. 283 // REVIEW Maybe this should be in TarBuffer? Could that help to 284 // eliminate some of the buffer copying. 285 // 286 if ( this.assemLen > 0 ) 287 { 288 if ( (this.assemLen + numToWrite ) >= this.recordBuf.length ) 289 { 290 int aLen = this.recordBuf.length - this.assemLen; 291 292 System.arraycopy 293 ( this.assemBuf, 0, this.recordBuf, 0, this.assemLen ); 294 295 System.arraycopy 296 ( wBuf, wOffset, this.recordBuf, this.assemLen, aLen ); 297 298 this.buffer.writeRecord( this.recordBuf ); 299 300 this.currBytes += this.recordBuf.length; 301 302 wOffset += aLen; 303 numToWrite -= aLen; 304 this.assemLen = 0; 305 } 306 else // ( (this.assemLen + numToWrite ) < this.recordBuf.length ) 307 { 308 System.arraycopy 309 ( wBuf, wOffset, this.assemBuf, 310 this.assemLen, numToWrite ); 311 wOffset += numToWrite; 312 this.assemLen += numToWrite; 313 numToWrite -= numToWrite; 314 } 315 } 316 317 // 318 // When we get here we have EITHER: 319 // o An empty "assemble" buffer. 320 // o No bytes to write (numToWrite == 0) 321 // 322 323 for ( ; numToWrite > 0 ; ) 324 { 325 if ( numToWrite < this.recordBuf.length ) 326 { 327 System.arraycopy 328 ( wBuf, wOffset, this.assemBuf, this.assemLen, numToWrite ); 329 this.assemLen += numToWrite; 330 break; 331 } 332 333 this.buffer.writeRecord( wBuf, wOffset ); 334 335 long num = this.recordBuf.length; 336 this.currBytes += num; 337 numToWrite -= num; 338 wOffset += num; 339 } 340 } 341 342 /** 343 * Write an EOF (end of archive) record to the tar archive. 344 * An EOF record consists of a record of all zeros. 345 */ 346 private void 347 writeEOFRecord() 348 throws IOException 349 { 350 for ( int i = 0 ; i < this.recordBuf.length ; ++i ) 351 this.recordBuf[i] = 0; 352 this.buffer.writeRecord( this.recordBuf ); 353 } 354 355 } 356