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 java.util.zip.GZIPInputStream; 020 021/** 022 * Special class designed to parse a Tar archive VERY FAST. 023 * This class is not a general Tar archive solution because 024 * it does not accomodate TarBuffer, or blocking. It does not 025 * allow you to read the entries either. This would not be 026 * difficult to add in a subclass. 027 * 028 * The real purpose of this class is that there are folks out 029 * there who wish to parse an ENORMOUS tar archive, and maybe 030 * only want to know the filenames, or they wish to locate the 031 * offset of a particular entry so that can process that entry 032 * with special code. 033 * 034 * @author Timothy Gerard Endres, <time@gjt.org> 035 * 036 */ 037 038public 039class FastTarStream 040 { 041 private boolean debug = false; 042 private boolean hasHitEOF = false; 043 private TarEntry currEntry = null; 044 private InputStream inStream = null; 045 private int recordSize = TarBuffer.DEFAULT_RCDSIZE; 046 047 048 public 049 FastTarStream( InputStream in ) 050 { 051 this( in, TarBuffer.DEFAULT_RCDSIZE ); 052 } 053 054 public 055 FastTarStream( InputStream in, int recordSize ) 056 { 057 this.inStream = in; 058 this.hasHitEOF = false; 059 this.currEntry = null; 060 this.recordSize = recordSize; 061 } 062 063 public void 064 setDebug( boolean debug ) 065 { 066 this.debug = debug; 067 } 068 069 public TarEntry 070 getNextEntry() 071 throws IOException 072 { 073 if ( this.hasHitEOF ) 074 return null; 075 076 /** 077 * Here I have tried skipping the entry size, I have tried 078 * skipping entrysize - header size, 079 * entrysize + header, and all seem to skip to some bizzarelocation! 080 */ 081 if ( this.currEntry != null && this.currEntry.getSize() > 0 ) 082 { 083 // Need to round out the number of records to be read to skip entry... 084 int numRecords = 085 ( (int)this.currEntry.getSize() + (this.recordSize - 1) ) 086 / this.recordSize; 087 088 if ( numRecords > 0 ) 089 { 090 this.inStream.skip( numRecords * this.recordSize ); 091 } 092 } 093 094 byte[] headerBuf = new byte[ this.recordSize ]; 095 096 // NOTE Apparently (with GZIPInputStream maybe?) we are able to 097 // read less then record size bytes in any given read(). So, 098 // we have to be persistent. 099 100 int bufIndex = 0; 101 for ( int bytesNeeded = this.recordSize ; bytesNeeded > 0 ; ) 102 { 103 int numRead = this.inStream.read( headerBuf, bufIndex, bytesNeeded ); 104 105 if ( numRead == -1 ) 106 { 107 this.hasHitEOF = true; 108 break; 109 } 110 111 bufIndex += numRead; 112 bytesNeeded -= numRead; 113 } 114 115 // Check for "EOF block" of all zeros 116 if ( ! this.hasHitEOF ) 117 { 118 this.hasHitEOF = true; 119 for ( int i = 0 ; i < headerBuf.length ; ++i ) 120 { 121 if ( headerBuf[i] != 0 ) 122 { 123 this.hasHitEOF = false; 124 break; 125 } 126 } 127 } 128 129 if ( this.hasHitEOF ) 130 { 131 this.currEntry = null; 132 } 133 else 134 { 135 try { 136 this.currEntry = new TarEntry( headerBuf ); 137 138 if ( this.debug ) 139 { 140 byte[] by = new byte[ headerBuf.length ]; 141 for(int i = 0; i < headerBuf.length; i++) 142 { 143 by[i] = ( headerBuf[i] == 0? 20: headerBuf[i] ); 144 } 145 String s = new String( by ); 146 System.out.println( "\n" + s ); 147 } 148 149 if ( ! ( headerBuf[257] == 'u' &&headerBuf[258] == 's' 150 && headerBuf[259] == 't' &&headerBuf[260] == 'a' 151 && headerBuf[261] == 'r' ) ) 152 { 153 throw new InvalidHeaderException 154 ( "header magic is not'ustar', but '" 155 + headerBuf[257] +headerBuf[258] + headerBuf[259] 156 + headerBuf[260] +headerBuf[261] + "', or (dec) " 157 +((int)headerBuf[257]) + ", " 158 +((int)headerBuf[258]) + ", " 159 +((int)headerBuf[259]) + ", " 160 +((int)headerBuf[260]) + ", " 161 +((int)headerBuf[261]) ); 162 } 163 } 164 catch ( InvalidHeaderException ex ) 165 { 166 this.currEntry = null; 167 throw ex; 168 } 169 } 170 171 return this.currEntry; 172 } 173 174 public static void 175 main( String[] args ) 176 { 177 boolean debug = false; 178 InputStream in = null; 179 180 String fileName = args[0]; 181 182 try { 183 int idx = 0; 184 if ( args.length > 0 ) 185 { 186 if ( args[idx].equals( "-d" ) ) 187 { 188 debug = true; 189 idx++; 190 } 191 192 if ( args[idx].endsWith( ".gz" ) 193 || args[idx].endsWith( ".tgz" ) ) 194 { 195 in = new GZIPInputStream( new FileInputStream( args[idx] ) ); 196 } 197 else 198 { 199 in = new FileInputStream( args[idx] ); 200 } 201 } 202 else 203 { 204 in = System.in; 205 } 206 207 FastTarStream fts = new FastTarStream( in ); 208 fts.setDebug( debug ); 209 210 int nameWidth = 56; 211 int sizeWidth = 9; 212 int userWidth = 8; 213 StringBuffer padBuf = new StringBuffer(128); 214 for ( ; ; ) 215 { 216 TarEntry entry = fts.getNextEntry(); 217 if ( entry == null ) 218 break; 219 220 if ( entry.isDirectory() ) 221 { 222 // TYPE 223 System.out.print( "D " ); 224 225 // NAME 226 padBuf.setLength(0); 227 padBuf.append( entry.getName() ); 228 padBuf.setLength( padBuf.length() - 1 ); // drop '/' 229 if ( padBuf.length() > nameWidth ) 230 padBuf.setLength( nameWidth ); 231 for ( ; padBuf.length() < nameWidth ; ) 232 padBuf.append( '_' ); 233 234 padBuf.append( '_' ); 235 System.out.print( padBuf.toString() ); 236 237 // SIZE 238 padBuf.setLength(0); 239 for ( ; padBuf.length() < sizeWidth ; ) 240 padBuf.insert( 0, '_' ); 241 242 padBuf.append( ' ' ); 243 System.out.print( padBuf.toString() ); 244 245 // USER 246 padBuf.setLength(0); 247 padBuf.append( entry.getUserName() ); 248 if ( padBuf.length() > userWidth ) 249 padBuf.setLength( userWidth ); 250 for ( ; padBuf.length() < userWidth ; ) 251 padBuf.append( ' ' ); 252 253 System.out.print( padBuf.toString() ); 254 } 255 else 256 { 257 // TYPE 258 System.out.print( "F " ); 259 260 // NAME 261 padBuf.setLength(0); 262 padBuf.append( entry.getName() ); 263 if ( padBuf.length() > nameWidth ) 264 padBuf.setLength( nameWidth ); 265 for ( ; padBuf.length() < nameWidth ; ) 266 padBuf.append( ' ' ); 267 268 padBuf.append( ' ' ); 269 System.out.print( padBuf.toString() ); 270 271 // SIZE 272 padBuf.setLength(0); 273 padBuf.append( entry.getSize() ); 274 if ( padBuf.length() > sizeWidth ) 275 padBuf.setLength( sizeWidth ); 276 for ( ; padBuf.length() < sizeWidth ; ) 277 padBuf.insert( 0, ' ' ); 278 279 padBuf.append( ' ' ); 280 System.out.print( padBuf.toString() ); 281 282 // USER 283 padBuf.setLength(0); 284 padBuf.append( entry.getUserName() ); 285 if ( padBuf.length() > userWidth ) 286 padBuf.setLength( userWidth ); 287 for ( ; padBuf.length() < userWidth ; ) 288 padBuf.append( ' ' ); 289 290 System.out.print( padBuf.toString() ); 291 } 292 293 System.out.println( "" ); 294 } 295 } 296 catch ( IOException ex ) 297 { 298 ex.printStackTrace( System.err ); 299 } 300 } 301 302 } 303