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.*; 019 020 021/** 022 * The TarBuffer class implements the tar archive concept 023 * of a buffered input stream. This concept goes back to the 024 * days of blocked tape drives and special io devices. In the 025 * Java universe, the only real function that this class 026 * performs is to ensure that files have the correct "block" 027 * size, or other tars will complain. 028 * <p> 029 * You should never have a need to access this class directly. 030 * TarBuffers are created by Tar IO Streams. 031 * 032 * @version $Revision: 1.10 $ 033 * @author Timothy Gerard Endres, 034 * <a href="mailto:time@gjt.org">time@trustice.com</a>. 035 * @see TarArchive 036 */ 037 038public 039class TarBuffer 040extends Object 041 { 042 public static final int DEFAULT_RCDSIZE = ( 512 ); 043 public static final int DEFAULT_BLKSIZE = ( DEFAULT_RCDSIZE * 20 ); 044 045 private InputStream inStream; 046 private OutputStream outStream; 047 048 private byte[] blockBuffer; 049 private int currBlkIdx; 050 private int currRecIdx; 051 private int blockSize; 052 private int recordSize; 053 private int recsPerBlock; 054 055 private boolean debug; 056 057 058 public 059 TarBuffer( InputStream inStream ) 060 { 061 this( inStream, TarBuffer.DEFAULT_BLKSIZE ); 062 } 063 064 public 065 TarBuffer( InputStream inStream, int blockSize ) 066 { 067 this( inStream, blockSize, TarBuffer.DEFAULT_RCDSIZE ); 068 } 069 070 public 071 TarBuffer( InputStream inStream, int blockSize, int recordSize ) 072 { 073 this.inStream = inStream; 074 this.outStream = null; 075 this.initialize( blockSize, recordSize ); 076 } 077 078 public 079 TarBuffer( OutputStream outStream ) 080 { 081 this( outStream, TarBuffer.DEFAULT_BLKSIZE ); 082 } 083 084 public 085 TarBuffer( OutputStream outStream, int blockSize ) 086 { 087 this( outStream, blockSize, TarBuffer.DEFAULT_RCDSIZE ); 088 } 089 090 public 091 TarBuffer( OutputStream outStream, int blockSize, int recordSize ) 092 { 093 this.inStream = null; 094 this.outStream = outStream; 095 this.initialize( blockSize, recordSize ); 096 } 097 098 /** 099 * Initialization common to all constructors. 100 */ 101 private void 102 initialize( int blockSize, int recordSize ) 103 { 104 this.debug = false; 105 this.blockSize = blockSize; 106 this.recordSize = recordSize; 107 this.recsPerBlock = ( this.blockSize / this.recordSize ); 108 this.blockBuffer = new byte[ this.blockSize ]; 109 110 if ( this.inStream != null ) 111 { 112 this.currBlkIdx = -1; 113 this.currRecIdx = this.recsPerBlock; 114 } 115 else 116 { 117 this.currBlkIdx = 0; 118 this.currRecIdx = 0; 119 } 120 } 121 122 /** 123 * Get the TAR Buffer's block size. Blocks consist of multiple records. 124 */ 125 public int 126 getBlockSize() 127 { 128 return this.blockSize; 129 } 130 131 /** 132 * Get the TAR Buffer's record size. 133 */ 134 public int 135 getRecordSize() 136 { 137 return this.recordSize; 138 } 139 140 /** 141 * Set the debugging flag for the buffer. 142 * 143 * @param debug If true, print debugging output. 144 */ 145 public void 146 setDebug( boolean debug ) 147 { 148 this.debug = debug; 149 } 150 151 /** 152 * Determine if an archive record indicate End of Archive. End of 153 * archive is indicated by a record that consists entirely of null bytes. 154 * 155 * @param record The record data to check. 156 */ 157 public boolean 158 isEOFRecord( byte[] record ) 159 { 160 for ( int i = 0, sz = this.getRecordSize() ; i < sz ; ++i ) 161 if ( record[i] != 0 ) 162 return false; 163 164 return true; 165 } 166 167 /** 168 * Skip over a record on the input stream. 169 */ 170 171 public void 172 skipRecord() 173 throws IOException 174 { 175 if ( this.debug ) 176 { 177 System.err.println 178 ( "SkipRecord: recIdx = " + this.currRecIdx 179 + " blkIdx = " + this.currBlkIdx ); 180 } 181 182 if ( this.inStream == null ) 183 throw new IOException 184 ( "reading (via skip) from an output buffer" ); 185 186 if ( this.currRecIdx >= this.recsPerBlock ) 187 { 188 if ( ! this.readBlock() ) 189 return; // UNDONE 190 } 191 192 this.currRecIdx++; 193 } 194 195 /** 196 * Read a record from the input stream and return the data. 197 * 198 * @return The record data. 199 */ 200 201 public byte[] 202 readRecord() 203 throws IOException 204 { 205 if ( this.debug ) 206 { 207 System.err.println 208 ( "ReadRecord: recIdx = " + this.currRecIdx 209 + " blkIdx = " + this.currBlkIdx ); 210 } 211 212 if ( this.inStream == null ) 213 throw new IOException 214 ( "reading from an output buffer" ); 215 216 if ( this.currRecIdx >= this.recsPerBlock ) 217 { 218 if ( ! this.readBlock() ) 219 return null; 220 } 221 222 byte[] result = new byte[ this.recordSize ]; 223 224 System.arraycopy( 225 this.blockBuffer, (this.currRecIdx * this.recordSize), 226 result, 0, this.recordSize ); 227 228 this.currRecIdx++; 229 230 return result; 231 } 232 233 /** 234 * @return false if End-Of-File, else true 235 */ 236 237 private boolean 238 readBlock() 239 throws IOException 240 { 241 if ( this.debug ) 242 { 243 System.err.println 244 ( "ReadBlock: blkIdx = " + this.currBlkIdx ); 245 } 246 247 if ( this.inStream == null ) 248 throw new IOException 249 ( "reading from an output buffer" ); 250 251 this.currRecIdx = 0; 252 253 int offset = 0; 254 int bytesNeeded = this.blockSize; 255 for ( ; bytesNeeded > 0 ; ) 256 { 257 long numBytes = 258 this.inStream.read 259 ( this.blockBuffer, offset, bytesNeeded ); 260 261 // 262 // NOTE 263 // We have fit EOF, and the block is not full! 264 // 265 // This is a broken archive. It does not follow the standard 266 // blocking algorithm. However, because we are generous, and 267 // it requires little effort, we will simply ignore the error 268 // and continue as if the entire block were read. This does 269 // not appear to break anything upstream. We used to return 270 // false in this case. 271 // 272 // Thanks to 'Yohann.Roussel@alcatel.fr' for this fix. 273 // 274 275 if ( numBytes == -1 ) 276 break; 277 278 offset += numBytes; 279 bytesNeeded -= numBytes; 280 if ( numBytes != this.blockSize ) 281 { 282 if ( this.debug ) 283 { 284 System.err.println 285 ( "ReadBlock: INCOMPLETE READ " + numBytes 286 + " of " + this.blockSize + " bytes read." ); 287 } 288 } 289 } 290 291 this.currBlkIdx++; 292 293 return true; 294 } 295 296 /** 297 * Get the current block number, zero based. 298 * 299 * @return The current zero based block number. 300 */ 301 public int 302 getCurrentBlockNum() 303 { 304 return this.currBlkIdx; 305 } 306 307 /** 308 * Get the current record number, within the current block, zero based. 309 * Thus, current offset = (currentBlockNum * recsPerBlk) + currentRecNum. 310 * 311 * @return The current zero based record number. 312 */ 313 public int 314 getCurrentRecordNum() 315 { 316 return this.currRecIdx - 1; 317 } 318 319 /** 320 * Write an archive record to the archive. 321 * 322 * @param record The record data to write to the archive. 323 */ 324 325 public void 326 writeRecord( byte[] record ) 327 throws IOException 328 { 329 if ( this.debug ) 330 { 331 System.err.println 332 ( "WriteRecord: recIdx = " + this.currRecIdx 333 + " blkIdx = " + this.currBlkIdx ); 334 } 335 336 if ( this.outStream == null ) 337 throw new IOException 338 ( "writing to an input buffer" ); 339 340 if ( record.length != this.recordSize ) 341 throw new IOException 342 ( "record to write has length '" + record.length 343 + "' which is not the record size of '" 344 + this.recordSize + "'" ); 345 346 if ( this.currRecIdx >= this.recsPerBlock ) 347 { 348 this.writeBlock(); 349 } 350 351 System.arraycopy( 352 record, 0, 353 this.blockBuffer, (this.currRecIdx * this.recordSize), 354 this.recordSize ); 355 356 this.currRecIdx++; 357 } 358 359 /** 360 * Write an archive record to the archive, where the record may be 361 * inside of a larger array buffer. The buffer must be "offset plus 362 * record size" long. 363 * 364 * @param buf The buffer containing the record data to write. 365 * @param offset The offset of the record data within buf. 366 */ 367 368 public void 369 writeRecord( byte[] buf, int offset ) 370 throws IOException 371 { 372 if ( this.debug ) 373 { 374 System.err.println 375 ( "WriteRecord: recIdx = " + this.currRecIdx 376 + " blkIdx = " + this.currBlkIdx ); 377 } 378 379 if ( this.outStream == null ) 380 throw new IOException 381 ( "writing to an input buffer" ); 382 383 if ( (offset + this.recordSize) > buf.length ) 384 throw new IOException 385 ( "record has length '" + buf.length 386 + "' with offset '" + offset 387 + "' which is less than the record size of '" 388 + this.recordSize + "'" ); 389 390 if ( this.currRecIdx >= this.recsPerBlock ) 391 { 392 this.writeBlock(); 393 } 394 395 System.arraycopy( 396 buf, offset, 397 this.blockBuffer, (this.currRecIdx * this.recordSize), 398 this.recordSize ); 399 400 this.currRecIdx++; 401 } 402 403 /** 404 * Write a TarBuffer block to the archive. 405 */ 406 private void 407 writeBlock() 408 throws IOException 409 { 410 if ( this.debug ) 411 { 412 System.err.println 413 ( "WriteBlock: blkIdx = " + this.currBlkIdx ); 414 } 415 416 if ( this.outStream == null ) 417 throw new IOException 418 ( "writing to an input buffer" ); 419 420 this.outStream.write( this.blockBuffer, 0, this.blockSize ); 421 this.outStream.flush(); 422 423 this.currRecIdx = 0; 424 this.currBlkIdx++; 425 } 426 427 /** 428 * Flush the current data block if it has any data in it. 429 */ 430 431 private void 432 flushBlock() 433 throws IOException 434 { 435 if ( this.debug ) 436 { 437 System.err.println( "TarBuffer.flushBlock() called." ); 438 } 439 440 if ( this.outStream == null ) 441 throw new IOException 442 ( "writing to an input buffer" ); 443 444 // Thanks to 'Todd Kofford <tkofford@bigfoot.com>' for this patch. 445 // Use a buffer initialized with 0s to initialize everything in the 446 // blockBuffer after the last current, complete record. This prevents 447 // any previous data that might have previously existed in the 448 // blockBuffer from being written to the file. 449 450 if ( this.currRecIdx > 0 ) 451 { 452 int offset = this.currRecIdx * this.recordSize; 453 byte[] zeroBuffer = new byte[ this.blockSize - offset ]; 454 455 System.arraycopy 456 ( zeroBuffer, 0, this.blockBuffer, offset, zeroBuffer.length ); 457 458 this.writeBlock(); 459 } 460 } 461 462 /** 463 * Close the TarBuffer. If this is an output buffer, also flush the 464 * current block before closing. 465 */ 466 public void 467 close() 468 throws IOException 469 { 470 if ( this.debug ) 471 { 472 System.err.println( "TarBuffer.closeBuffer()." ); 473 } 474 475 if ( this.outStream != null ) 476 { 477 this.flushBlock(); 478 479 if ( this.outStream != System.out 480 && this.outStream != System.err ) 481 { 482 this.outStream.close(); 483 this.outStream = null; 484 } 485 } 486 else if ( this.inStream != null ) 487 { 488 if ( this.inStream != System.in ) 489 { 490 this.inStream.close(); 491 this.inStream = null; 492 } 493 } 494 } 495 496 } 497