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