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 TarInputStream reads a UNIX tar archive as an InputStream. 024 * methods are provided to position at each successive entry in 025 * the archive, and the read each entry as a normal input stream 026 * using read(). 027 * 028 * Kerry Menzel <kmenzel@cfl.rr.com> Contributed the code to support 029 * file sizes greater than 2GB (longs versus ints). 030 * 031 * 032 * @version $Revision: 1.9 $ 033 * @author Timothy Gerard Endres, <time@gjt.org> 034 * @see TarBuffer 035 * @see TarHeader 036 * @see TarEntry 037 */ 038 039 040public 041class TarInputStream 042extends FilterInputStream 043 { 044 protected boolean debug; 045 protected boolean hasHitEOF; 046 047 protected long entrySize; 048 protected long entryOffset; 049 050 protected byte[] oneBuf; 051 protected byte[] readBuf; 052 053 protected TarBuffer buffer; 054 055 protected TarEntry currEntry; 056 057 protected EntryFactory eFactory; 058 059 060 public 061 TarInputStream( InputStream is ) 062 { 063 this( is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE ); 064 } 065 066 public 067 TarInputStream( InputStream is, int blockSize ) 068 { 069 this( is, blockSize, TarBuffer.DEFAULT_RCDSIZE ); 070 } 071 072 public 073 TarInputStream( InputStream is, int blockSize, int recordSize ) 074 { 075 super( is ); 076 077 this.buffer = new TarBuffer( is, blockSize, recordSize ); 078 079 this.readBuf = null; 080 this.oneBuf = new byte[1]; 081 this.debug = false; 082 this.hasHitEOF = false; 083 this.eFactory = null; 084 } 085 086 /** 087 * Sets the debugging flag. 088 * 089 * @param debugF True to turn on debugging. 090 */ 091 public void 092 setDebug( boolean debugF ) 093 { 094 this.debug = debugF; 095 } 096 097 /** 098 * Sets the debugging flag. 099 * 100 * @param debugF True to turn on debugging. 101 */ 102 public void 103 setEntryFactory( EntryFactory factory ) 104 { 105 this.eFactory = factory; 106 } 107 108 /** 109 * Sets the debugging flag in this stream's TarBuffer. 110 * 111 * @param debugF True to turn on debugging. 112 */ 113 public void 114 setBufferDebug( boolean debug ) 115 { 116 this.buffer.setDebug( debug ); 117 } 118 119 /** 120 * Closes this stream. Calls the TarBuffer's close() method. 121 */ 122 public void 123 close() 124 throws IOException 125 { 126 this.buffer.close(); 127 } 128 129 /** 130 * Get the record size being used by this stream's TarBuffer. 131 * 132 * @return The TarBuffer record size. 133 */ 134 public int 135 getRecordSize() 136 { 137 return this.buffer.getRecordSize(); 138 } 139 140 /** 141 * Get the available data that can be read from the current 142 * entry in the archive. This does not indicate how much data 143 * is left in the entire archive, only in the current entry. 144 * This value is determined from the entry's size header field 145 * and the amount of data already read from the current entry. 146 * 147 * 148 * @return The number of available bytes for the current entry. 149 */ 150 public int 151 available() 152 throws IOException 153 { 154 return (int)(this.entrySize - this.entryOffset); 155 } 156 157 /** 158 * Skip bytes in the input buffer. This skips bytes in the 159 * current entry's data, not the entire archive, and will 160 * stop at the end of the current entry's data if the number 161 * to skip extends beyond that point. 162 * 163 * @param numToSkip The number of bytes to skip. 164 * @return The actual number of bytes skipped. 165 */ 166 public long 167 skip( long numToSkip ) 168 throws IOException 169 { 170 // REVIEW 171 // This is horribly inefficient, but it ensures that we 172 // properly skip over bytes via the TarBuffer... 173 // 174 175 byte[] skipBuf = new byte[ 8 * 1024 ]; 176 long num = numToSkip; 177 for ( ; num > 0 ; ) 178 { 179 int numRead = 180 this.read( skipBuf, 0, 181 ( num > skipBuf.length ? skipBuf.length : (int) num ) ); 182 183 if ( numRead == -1 ) 184 break; 185 186 num -= numRead; 187 } 188 189 return ( numToSkip - num ); 190 } 191 192 /** 193 * Since we do not support marking just yet, we return false. 194 * 195 * @return False. 196 */ 197 public boolean 198 markSupported() 199 { 200 return false; 201 } 202 203 /** 204 * Since we do not support marking just yet, we do nothing. 205 * 206 * @param markLimit The limit to mark. 207 */ 208 public void 209 mark( int markLimit ) 210 { 211 } 212 213 /** 214 * Since we do not support marking just yet, we do nothing. 215 */ 216 public void 217 reset() 218 { 219 } 220 221 /** 222 * Get the number of bytes into the current TarEntry. 223 * This method returns the number of bytes that have been read 224 * from the current TarEntry's data. 225 * 226 * @returns The current entry offset. 227 */ 228 229 public long 230 getEntryPosition() 231 { 232 return this.entryOffset; 233 } 234 235 /** 236 * Get the number of bytes into the stream we are currently at. 237 * This method accounts for the blocking stream that tar uses, 238 * so it represents the actual position in input stream, as 239 * opposed to the place where the tar archive parsing is. 240 * 241 * @returns The current file pointer. 242 */ 243 244 public long 245 getStreamPosition() 246 { 247 return ( buffer.getBlockSize() * buffer.getCurrentBlockNum() ) 248 + buffer.getCurrentRecordNum(); 249 } 250 251 /** 252 * Get the next entry in this tar archive. This will skip 253 * over any remaining data in the current entry, if there 254 * is one, and place the input stream at the header of the 255 * next entry, and read the header and instantiate a new 256 * TarEntry from the header bytes and return that entry. 257 * If there are no more entries in the archive, null will 258 * be returned to indicate that the end of the archive has 259 * been reached. 260 * 261 * @return The next TarEntry in the archive, or null. 262 */ 263 public TarEntry 264 getNextEntry() 265 throws IOException 266 { 267 if ( this.hasHitEOF ) 268 return null; 269 270 if ( this.currEntry != null ) 271 { 272 long numToSkip = (this.entrySize - this.entryOffset); 273 274 if ( this.debug ) 275 System.err.println 276 ( "TarInputStream: SKIP currENTRY '" 277 + this.currEntry.getName() + "' SZ " 278 + this.entrySize + " OFF " + this.entryOffset 279 + " skipping " + numToSkip + " bytes" ); 280 281 if ( numToSkip > 0 ) 282 { 283 this.skip( numToSkip ); 284 } 285 286 this.readBuf = null; 287 } 288 289 byte[] headerBuf = this.buffer.readRecord(); 290 291 if ( headerBuf == null ) 292 { 293 if ( this.debug ) 294 { 295 System.err.println( "READ NULL RECORD" ); 296 } 297 298 this.hasHitEOF = true; 299 } 300 else if ( this.buffer.isEOFRecord( headerBuf ) ) 301 { 302 if ( this.debug ) 303 { 304 System.err.println( "READ EOF RECORD" ); 305 } 306 307 this.hasHitEOF = true; 308 } 309 310 if ( this.hasHitEOF ) 311 { 312 this.currEntry = null; 313 } 314 else 315 { 316 try { 317 if ( this.eFactory == null ) 318 { 319 this.currEntry = new TarEntry( headerBuf ); 320 } 321 else 322 { 323 this.currEntry = 324 this.eFactory.createEntry( headerBuf ); 325 } 326 327 if ( this.debug ) 328 System.err.println 329 ( "TarInputStream: SET CURRENTRY '" 330 + this.currEntry.getName() 331 + "' size = " + this.currEntry.getSize() ); 332 333 this.entryOffset = 0; 334 this.entrySize = this.currEntry.getSize(); 335 } 336 catch ( InvalidHeaderException ex ) 337 { 338 this.entrySize = 0; 339 this.entryOffset = 0; 340 this.currEntry = null; 341 throw new InvalidHeaderException 342 ( "bad header in block " 343 + this.buffer.getCurrentBlockNum() 344 + " record " 345 + this.buffer.getCurrentRecordNum() 346 + ", " + ex.getMessage() ); 347 } 348 } 349 350 return this.currEntry; 351 } 352 353 /** 354 * Reads a byte from the current tar archive entry. 355 * 356 * This method simply calls read( byte[], int, int ). 357 * 358 * @return The byte read, or -1 at EOF. 359 */ 360 public int 361 read() 362 throws IOException 363 { 364 int num = this.read( this.oneBuf, 0, 1 ); 365 if ( num == -1 ) 366 return num; 367 else 368 return (int) this.oneBuf[0]; 369 } 370 371 /** 372 * Reads bytes from the current tar archive entry. 373 * 374 * This method simply calls read( byte[], int, int ). 375 * 376 * @param buf The buffer into which to place bytes read. 377 * @return The number of bytes read, or -1 at EOF. 378 */ 379 public int 380 read( byte[] buf ) 381 throws IOException 382 { 383 return this.read( buf, 0, buf.length ); 384 } 385 386 /** 387 * Reads bytes from the current tar archive entry. 388 * 389 * This method is aware of the boundaries of the current 390 * entry in the archive and will deal with them as if they 391 * were this stream's start and EOF. 392 * 393 * @param buf The buffer into which to place bytes read. 394 * @param offset The offset at which to place bytes read. 395 * @param numToRead The number of bytes to read. 396 * @return The number of bytes read, or -1 at EOF. 397 */ 398 public int 399 read( byte[] buf, int offset, int numToRead ) 400 throws IOException 401 { 402 int totalRead = 0; 403 404 if ( this.entryOffset >= this.entrySize ) 405 return -1; 406 407 if ( (numToRead + this.entryOffset) > this.entrySize ) 408 { 409 numToRead = (int) (this.entrySize - this.entryOffset); 410 } 411 412 if ( this.readBuf != null ) 413 { 414 int sz = ( numToRead > this.readBuf.length ) 415 ? this.readBuf.length : numToRead; 416 417 System.arraycopy( this.readBuf, 0, buf, offset, sz ); 418 419 if ( sz >= this.readBuf.length ) 420 { 421 this.readBuf = null; 422 } 423 else 424 { 425 int newLen = this.readBuf.length - sz; 426 byte[] newBuf = new byte[ newLen ]; 427 System.arraycopy( this.readBuf, sz, newBuf, 0, newLen ); 428 this.readBuf = newBuf; 429 } 430 431 totalRead += sz; 432 numToRead -= sz; 433 offset += sz; 434 } 435 436 for ( ; numToRead > 0 ; ) 437 { 438 byte[] rec = this.buffer.readRecord(); 439 if ( rec == null ) 440 { 441 // Unexpected EOF! 442 throw new IOException 443 ( "unexpected EOF with " + numToRead + " bytes unread" ); 444 } 445 446 int sz = numToRead; 447 int recLen = rec.length; 448 449 if ( recLen > sz ) 450 { 451 System.arraycopy( rec, 0, buf, offset, sz ); 452 this.readBuf = new byte[ recLen - sz ]; 453 System.arraycopy( rec, sz, this.readBuf, 0, recLen - sz ); 454 } 455 else 456 { 457 sz = recLen; 458 System.arraycopy( rec, 0, buf, offset, recLen ); 459 } 460 461 totalRead += sz; 462 numToRead -= sz; 463 offset += sz; 464 } 465 466 this.entryOffset += totalRead; 467 468 return totalRead; 469 } 470 471 /** 472 * Copies the contents of the current tar archive entry directly into 473 * an output stream. 474 * 475 * @param out The OutputStream into which to write the entry's data. 476 */ 477 public void 478 copyEntryContents( OutputStream out ) 479 throws IOException 480 { 481 byte[] buf = new byte[ 32 * 1024 ]; 482 483 for ( ; ; ) 484 { 485 int numRead = this.read( buf, 0, buf.length ); 486 if ( numRead == -1 ) 487 break; 488 out.write( buf, 0, numRead ); 489 } 490 } 491 492 /** 493 * This interface is provided, with the method setEntryFactory(), to allow 494 * the programmer to have their own TarEntry subclass instantiated for the 495 * entries return from getNextEntry(). 496 */ 497 498 public 499 interface EntryFactory 500 { 501 public TarEntry 502 createEntry( String name ); 503 504 public TarEntry 505 createEntry( File path ) 506 throws InvalidHeaderException; 507 508 public TarEntry 509 createEntry( byte[] headerBuf ) 510 throws InvalidHeaderException; 511 } 512 513 public 514 class EntryAdapter 515 implements EntryFactory 516 { 517 public TarEntry 518 createEntry( String name ) 519 { 520 return new TarEntry( name ); 521 } 522 523 public TarEntry 524 createEntry( File path ) 525 throws InvalidHeaderException 526 { 527 return new TarEntry( path ); 528 } 529 530 public TarEntry 531 createEntry( byte[] headerBuf ) 532 throws InvalidHeaderException 533 { 534 return new TarEntry( headerBuf ); 535 } 536 } 537 538 } 539 540