6828beb905ecc6bb3addf6fc72410d8980ba0dba
[org.ibex.util.git] / src / org / ibex / util / Tar.java
1 /*
2 ** Authored by Timothy Gerard Endres
3 ** <mailto:time@gjt.org>  <http://www.trustice.com>
4 ** 
5 ** This work has been placed into the public domain.
6 ** You may use this work in any way and for any purpose you wish.
7 **
8 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
9 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
10 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
11 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
12 ** REDISTRIBUTION OF THIS SOFTWARE. 
13 ** 
14 */
15
16 package org.ibex.util;
17 import java.io.*;
18 import java.util.*;
19 import java.net.*;
20 import java.util.zip.*;
21 import javax.activation.*;
22
23
24 public class Tar {
25 /**
26  * Special class designed to parse a Tar archive VERY FAST.
27  * This class is not a general Tar archive solution because
28  * it does not accomodate TarBuffer, or blocking. It does not
29  * allow you to read the entries either. This would not be
30  * difficult to add in a subclass.
31  *
32  * The real purpose of this class is that there are folks out
33  * there who wish to parse an ENORMOUS tar archive, and maybe
34  * only want to know the filenames, or they wish to locate the
35  * offset of a particular entry so that can process that entry
36  * with special code.
37  *
38  * @author Timothy Gerard Endres, <time@gjt.org>
39  *
40  */
41
42 public static
43 class           FastTarStream
44         {
45         private boolean                 debug = false;
46         private boolean                 hasHitEOF = false;
47         private TarEntry                currEntry = null;
48         private InputStream             inStream = null;
49         private int                             recordSize = TarBuffer.DEFAULT_RCDSIZE;
50
51
52         public
53         FastTarStream( InputStream in )
54                 {
55                 this( in, TarBuffer.DEFAULT_RCDSIZE );
56                 }
57
58         public
59         FastTarStream( InputStream in, int recordSize )
60                 {
61                 this.inStream = in;
62                 this.hasHitEOF = false;
63                 this.currEntry = null;
64                 this.recordSize = recordSize;
65                 }
66
67         public void
68         setDebug( boolean debug )
69                 {
70                 this.debug = debug;
71                 }
72
73         public TarEntry
74         getNextEntry()
75                 throws IOException
76                 {
77                 if ( this.hasHitEOF )
78                         return null;
79                 
80                 /**
81                  * Here I have tried skipping the entry size, I have tried
82                  * skipping entrysize - header size,
83          * entrysize + header, and all seem to skip to some bizzarelocation!
84          */
85                 if ( this.currEntry != null && this.currEntry.getSize() > 0 )
86                         {
87                         // Need to round out the number of records to be read to skip entry...
88                         int numRecords =
89                                 ( (int)this.currEntry.getSize() + (this.recordSize - 1) )
90                                         / this.recordSize;
91
92                         if ( numRecords > 0 )
93                                 {
94                 this.inStream.skip( numRecords * this.recordSize );
95                 }
96                 }
97
98                 byte[] headerBuf = new byte[ this.recordSize ];
99
100                 // NOTE Apparently (with GZIPInputStream maybe?) we are able to
101                 //      read less then record size bytes in any given read(). So,
102                 //      we have to be persistent.
103
104                 int bufIndex = 0;
105                 for ( int bytesNeeded = this.recordSize ; bytesNeeded > 0 ; )
106                         {
107                         int numRead = this.inStream.read( headerBuf, bufIndex, bytesNeeded );
108
109                         if ( numRead == -1 )
110                                 {
111                                 this.hasHitEOF = true;
112                                 break;
113                                 }
114
115                         bufIndex += numRead;
116                         bytesNeeded -= numRead;
117                         }
118
119                 // Check for "EOF block" of all zeros
120                 if ( ! this.hasHitEOF )
121                         {
122                         this.hasHitEOF = true;
123                         for ( int i = 0 ; i < headerBuf.length ; ++i )
124                                 {
125                                 if ( headerBuf[i] != 0 )
126                                         {
127                                         this.hasHitEOF = false;
128                                         break;
129                                         }
130                                 }
131                         }
132
133                 if ( this.hasHitEOF )
134                         {
135                         this.currEntry = null;
136                         }
137                 else
138                         {
139                         try {
140                                 this.currEntry = new TarEntry( headerBuf );
141
142                                 if ( this.debug )
143                                         {
144                                         byte[] by = new byte[ headerBuf.length ];
145                                         for(int i = 0; i < headerBuf.length; i++)
146                                                 {
147                                                 by[i] = ( headerBuf[i] == 0? 20: headerBuf[i] );
148                                                 }
149                                         String s = new String( by );
150                                         System.out.println( "\n" + s );
151                                         }
152
153                                 if ( ! ( headerBuf[257] == 'u' &&headerBuf[258] == 's'
154                                                 && headerBuf[259] == 't' &&headerBuf[260] == 'a'
155                                                 && headerBuf[261] == 'r' ) )
156                                         {
157                                             throw new InvalidHeaderException
158                                                 ( "header magic is not'ustar', but '"
159                                                         + headerBuf[257] +headerBuf[258] + headerBuf[259]
160                                                         + headerBuf[260] +headerBuf[261] + "', or (dec) "
161                                                         +((int)headerBuf[257]) + ", "
162                                                         +((int)headerBuf[258]) + ", "
163                                                         +((int)headerBuf[259]) + ", "
164                                                         +((int)headerBuf[260]) + ", "
165                                                         +((int)headerBuf[261]) );
166                                         }
167                                 }
168                         catch ( InvalidHeaderException ex )
169                                 {
170                                 this.currEntry = null;
171                                 throw ex;
172                                 }
173                         }
174
175                 return this.currEntry;
176                 }
177
178         public static void
179         main( String[] args )
180                 {
181                 boolean debug = false;
182                 InputStream in = null;
183
184                 String fileName = args[0];
185
186                 try {
187                         int idx = 0;
188                         if ( args.length > 0 )
189                                 {
190                                 if ( args[idx].equals( "-d" ) )
191                                         {
192                                         debug = true;
193                                         idx++;
194                                         }
195
196                                 if ( args[idx].endsWith( ".gz" )
197                                                 || args[idx].endsWith( ".tgz" ) )
198                                         {
199                                         in = new GZIPInputStream( new FileInputStream( args[idx] ) );
200                                         }
201                                 else
202                                         {
203                                         in = new FileInputStream( args[idx] );
204                                         }
205                                 }
206                         else
207                                 {
208                                 in = System.in;
209                                 }
210
211                         FastTarStream fts = new FastTarStream( in );
212                         fts.setDebug( debug );
213
214                         int nameWidth = 56;
215                         int sizeWidth = 9;
216                         int userWidth = 8;
217                         StringBuffer padBuf = new StringBuffer(128);
218                         for ( ; ; )
219                                 {
220                                 TarEntry entry = fts.getNextEntry();
221                                 if ( entry == null )
222                                         break;
223
224                                 if ( entry.isDirectory() )
225                                         {
226                                         // TYPE
227                                         System.out.print( "D " );
228
229                                         // NAME
230                                         padBuf.setLength(0);
231                                         padBuf.append( entry.getName() );
232                                         padBuf.setLength( padBuf.length() - 1 ); // drop '/'
233                                         if ( padBuf.length() > nameWidth )
234                                                 padBuf.setLength( nameWidth );
235                                         for ( ; padBuf.length() < nameWidth ; )
236                                                 padBuf.append( '_' );
237
238                                         padBuf.append( '_' );
239                                         System.out.print( padBuf.toString() );
240
241                                         // SIZE
242                                         padBuf.setLength(0);
243                                         for ( ; padBuf.length() < sizeWidth ; )
244                                                 padBuf.insert( 0, '_' );
245
246                                         padBuf.append( ' ' );
247                                         System.out.print( padBuf.toString() );
248
249                                         // USER
250                                         padBuf.setLength(0);
251                                         padBuf.append( entry.getUserName() );
252                                         if ( padBuf.length() > userWidth )
253                                                 padBuf.setLength( userWidth );
254                                         for ( ; padBuf.length() < userWidth ; )
255                                                 padBuf.append( ' ' );
256
257                                         System.out.print( padBuf.toString() );
258                                         }
259                                 else
260                                         {
261                                         // TYPE
262                                         System.out.print( "F " );
263
264                                         // NAME
265                                         padBuf.setLength(0);
266                                         padBuf.append( entry.getName() );
267                                         if ( padBuf.length() > nameWidth )
268                                                 padBuf.setLength( nameWidth );
269                                         for ( ; padBuf.length() < nameWidth ; )
270                                                 padBuf.append( ' ' );
271
272                                         padBuf.append( ' ' );
273                                         System.out.print( padBuf.toString() );
274
275                                         // SIZE
276                                         padBuf.setLength(0);
277                                         padBuf.append( entry.getSize() );
278                                         if ( padBuf.length() > sizeWidth )
279                                                 padBuf.setLength( sizeWidth );
280                                         for ( ; padBuf.length() < sizeWidth ; )
281                                                 padBuf.insert( 0, ' ' );
282
283                                         padBuf.append( ' ' );
284                                         System.out.print( padBuf.toString() );
285
286                                         // USER
287                                         padBuf.setLength(0);
288                                         padBuf.append( entry.getUserName() );
289                                         if ( padBuf.length() > userWidth )
290                                                 padBuf.setLength( userWidth );
291                                         for ( ; padBuf.length() < userWidth ; )
292                                                 padBuf.append( ' ' );
293
294                                         System.out.print( padBuf.toString() );
295                                         }
296
297                                 System.out.println( "" );
298                                 }
299                         }
300                 catch ( IOException ex )
301                         {
302                         ex.printStackTrace( System.err );
303                         }
304                 }
305
306         }
307
308 /*
309 ** Authored by Timothy Gerard Endres
310 ** <mailto:time@gjt.org>  <http://www.trustice.com>
311 ** 
312 ** This work has been placed into the public domain.
313 ** You may use this work in any way and for any purpose you wish.
314 **
315 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
316 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
317 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
318 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
319 ** REDISTRIBUTION OF THIS SOFTWARE. 
320 ** 
321 */
322
323 /**
324  * This exception is used to indicate that there is a problem
325  * with a TAR archive header.
326  */
327
328 public static class
329 InvalidHeaderException extends IOException
330         {
331
332         public
333         InvalidHeaderException()
334                 {
335                 super();
336                 }
337
338         public
339         InvalidHeaderException( String msg )
340                 {
341                 super( msg );
342                 }
343
344         }
345
346 /*
347 ** Authored by Timothy Gerard Endres
348 ** <mailto:time@gjt.org>  <http://www.trustice.com>
349 ** 
350 ** This work has been placed into the public domain.
351 ** You may use this work in any way and for any purpose you wish.
352 **
353 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
354 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
355 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
356 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
357 ** REDISTRIBUTION OF THIS SOFTWARE. 
358 ** 
359 */
360
361 /**
362  * The TarArchive class implements the concept of a
363  * tar archive. A tar archive is a series of entries, each of
364  * which represents a file system object. Each entry in
365  * the archive consists of a header record. Directory entries
366  * consist only of the header record, and are followed by entries
367  * for the directory's contents. File entries consist of a
368  * header record followed by the number of records needed to
369  * contain the file's contents. All entries are written on
370  * record boundaries. Records are 512 bytes long.
371  *
372  * TarArchives are instantiated in either read or write mode,
373  * based upon whether they are instantiated with an InputStream
374  * or an OutputStream. Once instantiated TarArchives read/write
375  * mode can not be changed.
376  *
377  * There is currently no support for random access to tar archives.
378  * However, it seems that subclassing TarArchive, and using the
379  * TarBuffer.getCurrentRecordNum() and TarBuffer.getCurrentBlockNum()
380  * methods, this would be rather trvial.
381  *
382  * @version $Revision: 1.15 $
383  * @author Timothy Gerard Endres, <time@gjt.org>
384  * @see TarBuffer
385  * @see TarHeader
386  * @see TarEntry
387  */
388
389
390 public static class
391 TarArchive extends Object
392         {
393         protected boolean                       verbose;
394         protected boolean                       debug;
395         protected boolean                       keepOldFiles;
396         protected boolean                       asciiTranslate;
397
398         protected int                           userId;
399         protected String                        userName;
400         protected int                           groupId;
401         protected String                        groupName;
402
403         protected String                        rootPath;
404         protected String                        tempPath;
405         protected String                        pathPrefix;
406
407         protected int                           recordSize;
408         protected byte[]                        recordBuf;
409
410         protected TarInputStream        tarIn;
411         protected TarOutputStream       tarOut;
412
413         protected TarTransFileTyper             transTyper;
414         protected TarProgressDisplay    progressDisplay;
415
416
417         /**
418          * The InputStream based constructors create a TarArchive for the
419          * purposes of e'x'tracting or lis't'ing a tar archive. Thus, use
420          * these constructors when you wish to extract files from or list
421          * the contents of an existing tar archive.
422          */
423
424         public
425         TarArchive( InputStream inStream )
426                 {
427                 this( inStream, TarBuffer.DEFAULT_BLKSIZE );
428                 }
429
430         public
431         TarArchive( InputStream inStream, int blockSize )
432                 {
433                 this( inStream, blockSize, TarBuffer.DEFAULT_RCDSIZE );
434                 }
435
436         public
437         TarArchive( InputStream inStream, int blockSize, int recordSize )
438                 {
439                 this.tarIn = new TarInputStream( inStream, blockSize, recordSize );
440                 this.initialize( recordSize );
441                 }
442
443         /**
444          * The OutputStream based constructors create a TarArchive for the
445          * purposes of 'c'reating a tar archive. Thus, use these constructors
446          * when you wish to create a new tar archive and write files into it.
447          */
448
449         public
450         TarArchive( OutputStream outStream )
451                 {
452                 this( outStream, TarBuffer.DEFAULT_BLKSIZE );
453                 }
454
455         public
456         TarArchive( OutputStream outStream, int blockSize )
457                 {
458                 this( outStream, blockSize, TarBuffer.DEFAULT_RCDSIZE );
459                 }
460
461         public
462         TarArchive( OutputStream outStream, int blockSize, int recordSize )
463                 {
464                 this.tarOut = new TarOutputStream( outStream, blockSize, recordSize );
465                 this.initialize( recordSize );
466                 }
467
468         /**
469          * Common constructor initialization code.
470          */
471
472         private void
473         initialize( int recordSize )
474                 {
475                 this.rootPath = null;
476                 this.pathPrefix = null;
477                 this.tempPath = System.getProperty( "user.dir" );
478
479                 this.userId = 0;
480                 this.userName = "";
481                 this.groupId = 0;
482                 this.groupName = "";
483
484                 this.debug = false;
485                 this.verbose = false;
486                 this.keepOldFiles = false;
487                 this.progressDisplay = null;
488
489                 this.recordBuf =
490                         new byte[ this.getRecordSize() ];
491                 }
492
493         /**
494          * Set the debugging flag.
495          *
496          * @param debugF The new debug setting.
497          */
498
499         public void
500         setDebug( boolean debugF )
501                 {
502                 this.debug = debugF;
503                 if ( this.tarIn != null )
504                         this.tarIn.setDebug( debugF );
505                 else if ( this.tarOut != null )
506                         this.tarOut.setDebug( debugF );
507                 }
508
509         /**
510          * Returns the verbosity setting.
511          *
512          * @return The current verbosity setting.
513          */
514
515         public boolean
516         isVerbose()
517                 {
518                 return this.verbose;
519                 }
520
521         /**
522          * Set the verbosity flag.
523          *
524          * @param verbose The new verbosity setting.
525          */
526
527         public void
528         setVerbose( boolean verbose )
529                 {
530                 this.verbose = verbose;
531                 }
532
533         /**
534          * Set the current progress display interface. This allows the
535          * programmer to use a custom class to display the progress of
536          * the archive's processing.
537          *
538          * @param display The new progress display interface.
539          * @see TarProgressDisplay
540          */
541
542         public void
543         setTarProgressDisplay( TarProgressDisplay display )
544                 {
545                 this.progressDisplay = display;
546                 }
547
548         /**
549          * Set the flag that determines whether existing files are
550          * kept, or overwritten during extraction.
551          *
552          * @param keepOldFiles If true, do not overwrite existing files.
553          */
554
555         public void
556         setKeepOldFiles( boolean keepOldFiles )
557                 {
558                 this.keepOldFiles = keepOldFiles;
559                 }
560         
561         /**
562          * Set the ascii file translation flag. If ascii file translatio
563          * is true, then the MIME file type will be consulted to determine
564          * if the file is of type 'text/*'. If the MIME type is not found,
565          * then the TransFileTyper is consulted if it is not null. If
566          * either of these two checks indicates the file is an ascii text
567          * file, it will be translated. The translation converts the local
568          * operating system's concept of line ends into the UNIX line end,
569          * '\n', which is the defacto standard for a TAR archive. This makes
570          * text files compatible with UNIX, and since most tar implementations
571          * for other platforms, compatible with most other platforms.
572          *
573          * @param asciiTranslate If true, translate ascii text files.
574          */
575
576         public void
577         setAsciiTranslation( boolean asciiTranslate )
578                 {
579                 this.asciiTranslate = asciiTranslate;
580                 }
581
582         /**
583          * Set the object that will determine if a file is of type
584          * ascii text for translation purposes.
585          *
586          * @param transTyper The new TransFileTyper object.
587          */
588
589         public void
590         setTransFileTyper( TarTransFileTyper transTyper )
591                 {
592                 this.transTyper = transTyper;
593                 }
594
595         /**
596          * Set user and group information that will be used to fill in the
597          * tar archive's entry headers. Since Java currently provides no means
598          * of determining a user name, user id, group name, or group id for
599          * a given File, TarArchive allows the programmer to specify values
600          * to be used in their place.
601          *
602          * @param userId The user Id to use in the headers.
603          * @param userName The user name to use in the headers.
604          * @param groupId The group id to use in the headers.
605          * @param groupName The group name to use in the headers.
606          */
607
608         public void
609         setUserInfo(
610                         int userId, String userName,
611                         int groupId, String groupName )
612                 {
613                 this.userId = userId;
614                 this.userName = userName;
615                 this.groupId = groupId;
616                 this.groupName = groupName;
617                 }
618
619         /**
620          * Get the user id being used for archive entry headers.
621          *
622          * @return The current user id.
623          */
624
625         public int
626         getUserId()
627                 {
628                 return this.userId;
629                 }
630
631         /**
632          * Get the user name being used for archive entry headers.
633          *
634          * @return The current user name.
635          */
636
637         public String
638         getUserName()
639                 {
640                 return this.userName;
641                 }
642
643         /**
644          * Get the group id being used for archive entry headers.
645          *
646          * @return The current group id.
647          */
648
649         public int
650         getGroupId()
651                 {
652                 return this.groupId;
653                 }
654
655         /**
656          * Get the group name being used for archive entry headers.
657          *
658          * @return The current group name.
659          */
660
661         public String
662         getGroupName()
663                 {
664                 return this.groupName;
665                 }
666
667         /**
668          * Get the current temporary directory path. Because Java's
669          * File did not support temporary files until version 1.2,
670          * TarArchive manages its own concept of the temporary
671          * directory. The temporary directory defaults to the
672          * 'user.dir' System property.
673          *
674          * @return The current temporary directory path.
675          */
676
677         public String
678         getTempDirectory()
679                 {
680                 return this.tempPath;
681                 }
682
683         /**
684          * Set the current temporary directory path.
685          *
686          * @param path The new temporary directory path.
687          */
688
689         public void
690         setTempDirectory( String path )
691                 {
692                 this.tempPath = path;
693                 }
694
695         /**
696          * Get the archive's record size. Because of its history, tar
697          * supports the concept of buffered IO consisting of BLOCKS of
698          * RECORDS. This allowed tar to match the IO characteristics of
699          * the physical device being used. Of course, in the Java world,
700          * this makes no sense, WITH ONE EXCEPTION - archives are expected
701          * to be propertly "blocked". Thus, all of the horrible TarBuffer
702          * support boils down to simply getting the "boundaries" correct.
703          *
704          * @return The record size this archive is using.
705          */
706
707         public int
708         getRecordSize()
709                 {
710                 if ( this.tarIn != null )
711                         {
712                         return this.tarIn.getRecordSize();
713                         }
714                 else if ( this.tarOut != null )
715                         {
716                         return this.tarOut.getRecordSize();
717                         }
718                 
719                 return TarBuffer.DEFAULT_RCDSIZE;
720                 }
721
722         /**
723          * Get a path for a temporary file for a given File. The
724          * temporary file is NOT created. The algorithm attempts
725          * to handle filename collisions so that the name is
726          * unique.
727          *
728          * @return The temporary file's path.
729          */
730
731         private String
732         getTempFilePath( File eFile )
733                 {
734                 String pathStr =
735                         this.tempPath + File.separator
736                                 + eFile.getName() + ".tmp";
737
738                 for ( int i = 1 ; i < 5 ; ++i )
739                         {
740                         File f = new File( pathStr );
741
742                         if ( ! f.exists() )
743                                 break;
744
745                         pathStr =
746                                 this.tempPath + File.separator
747                                         + eFile.getName() + "-" + i + ".tmp";
748                         }
749
750                 return pathStr;
751                 }
752
753         /**
754          * Close the archive. This simply calls the underlying
755          * tar stream's close() method.
756          */
757
758         public void
759         closeArchive()
760                 throws IOException
761                 {
762                 if ( this.tarIn != null )
763                         {
764                         this.tarIn.close();
765                         }
766                 else if ( this.tarOut != null )
767                         {
768                         this.tarOut.close();
769                         }
770                 }
771
772         /**
773          * Perform the "list" command and list the contents of the archive.
774          * NOTE That this method uses the progress display to actually list
775          * the conents. If the progress display is not set, nothing will be
776          * listed!
777          */
778
779         public void
780         listContents()
781                 throws IOException, InvalidHeaderException
782                 {
783                 for ( ; ; )
784                         {
785                         TarEntry entry = this.tarIn.getNextEntry();
786
787
788                         if ( entry == null )
789                                 {
790                                 if ( this.debug )
791                                         {
792                                         System.err.println( "READ EOF RECORD" );
793                                         }
794                                 break;
795                                 }
796
797                         if ( this.progressDisplay != null )
798                                 this.progressDisplay.showTarProgressMessage
799                                         ( entry.getName() );
800                         }
801                 }
802
803         /**
804          * Perform the "extract" command and extract the contents of the archive.
805          *
806          * @param destDir The destination directory into which to extract.
807          */
808
809         public void
810         extractContents( File destDir )
811                 throws IOException, InvalidHeaderException
812                 {
813                 for ( ; ; )
814                         {
815                         TarEntry entry = this.tarIn.getNextEntry();
816
817                         if ( entry == null )
818                                 {
819                                 if ( this.debug )
820                                         {
821                                         System.err.println( "READ EOF RECORD" );
822                                         }
823                                 break;
824                                 }
825
826                         this.extractEntry( destDir, entry );
827                         }
828                 }
829
830         /**
831          * Extract an entry from the archive. This method assumes that the
832          * tarIn stream has been properly set with a call to getNextEntry().
833          *
834          * @param destDir The destination directory into which to extract.
835          * @param entry The TarEntry returned by tarIn.getNextEntry().
836          */
837
838         private void
839         extractEntry( File destDir, TarEntry entry )
840                 throws IOException
841                 {
842                 if ( this.verbose )
843                         {
844                         if ( this.progressDisplay != null )
845                                 this.progressDisplay.showTarProgressMessage
846                                         ( entry.getName() );
847                         }
848
849                 String name = entry.getName();
850                 name = name.replace( '/', File.separatorChar );
851
852                 File destFile = new File( destDir, name );
853
854                 if ( entry.isDirectory() )
855                         {
856                         if ( ! destFile.exists() )
857                                 {
858                                 if ( ! destFile.mkdirs() )
859                                         {
860                                         throw new IOException
861                                                 ( "error making directory path '"
862                                                         + destFile.getPath() + "'" );
863                                         }
864                                 }
865                         }
866                 else
867                         {
868                         File subDir = new File( destFile.getParent() );
869
870                         if ( ! subDir.exists() )
871                                 {
872                                 if ( ! subDir.mkdirs() )
873                                         {
874                                         throw new IOException
875                                                 ( "error making directory path '"
876                                                         + subDir.getPath() + "'" );
877                                         }
878                                 }
879
880                         if ( this.keepOldFiles && destFile.exists() )
881                                 {
882                                 if ( this.verbose )
883                                         {
884                                         if ( this.progressDisplay != null )
885                                                 this.progressDisplay.showTarProgressMessage
886                                                         ( "not overwriting " + entry.getName() );
887                                         }
888                                 }
889                         else
890                                 {
891                                 boolean asciiTrans = false;
892
893                                 FileOutputStream out =
894                                         new FileOutputStream( destFile );
895
896
897                                 PrintWriter outw = null;
898                                 if ( asciiTrans )
899                                         {
900                                         outw = new PrintWriter( out );
901                                         }
902
903                                 byte[] rdbuf = new byte[32 * 1024];
904
905                                 for ( ; ; )
906                                         {
907                                         int numRead = this.tarIn.read( rdbuf );
908
909                                         if ( numRead == -1 )
910                                                 break;
911                                         
912                                         if ( asciiTrans )
913                                                 {
914                                                 for ( int off = 0, b = 0 ; b < numRead ; ++b )
915                                                         {
916                                                         if ( rdbuf[ b ] == 10 )
917                                                                 {
918                                                                 String s = new String
919                                                                         ( rdbuf, off, (b - off) );
920
921                                                                 outw.println( s );
922
923                                                                 off = b + 1;
924                                                                 }
925                                                         }
926                                                 }
927                                         else
928                                                 {
929                                                 out.write( rdbuf, 0, numRead );
930                                                 }
931                                         }
932
933                                 if ( asciiTrans )
934                                         outw.close();
935                                 else
936                                         out.close();
937                                 }
938                         }
939                 }
940
941         /**
942          * Write an entry to the archive. This method will call the putNextEntry()
943          * and then write the contents of the entry, and finally call closeEntry()
944          * for entries that are files. For directories, it will call putNextEntry(),
945          * and then, if the recurse flag is true, process each entry that is a
946          * child of the directory.
947          *
948          * @param entry The TarEntry representing the entry to write to the archive.
949          * @param recurse If true, process the children of directory entries.
950          */
951
952         public void
953         writeEntry( TarEntry oldEntry, boolean recurse )
954                 throws IOException
955                 {
956                 boolean asciiTrans = false;
957                 boolean unixArchiveFormat = oldEntry.isUnixTarFormat();
958
959                 File tFile = null;
960                 File eFile = oldEntry.getFile();
961
962                 // Work on a copy of the entry so we can manipulate it.
963                 // Note that we must distinguish how the entry was constructed.
964                 //
965                 TarEntry entry = (TarEntry) oldEntry.clone();
966
967                 if ( this.verbose )
968                         {
969                         if ( this.progressDisplay != null )
970                                 this.progressDisplay.showTarProgressMessage
971                                         ( entry.getName() );
972                         }
973
974
975                 String newName = null;
976
977                 if ( this.rootPath != null )
978                         {
979                         if ( entry.getName().startsWith( this.rootPath ) )
980                                 {
981                                 newName =
982                                         entry.getName().substring
983                                                 ( this.rootPath.length() + 1 );
984                                 }
985                         }
986
987                 if ( this.pathPrefix != null )
988                         {
989                         newName = (newName == null)
990                                 ? this.pathPrefix + "/" + entry.getName()
991                                 : this.pathPrefix + "/" + newName;
992                         }
993
994                 if ( newName != null )
995                         {
996                         entry.setName( newName );
997                         }
998
999                 this.tarOut.putNextEntry( entry );
1000
1001                 if ( entry.isDirectory() )
1002                         {
1003                         if ( recurse )
1004                                 {
1005                                 TarEntry[] list = entry.getDirectoryEntries();
1006
1007                                 for ( int i = 0 ; i < list.length ; ++i )
1008                                         {
1009                                         TarEntry dirEntry = list[i];
1010
1011                                         if ( unixArchiveFormat )
1012                                                 dirEntry.setUnixTarFormat();
1013
1014                                         this.writeEntry( dirEntry, recurse );
1015                                         }
1016                                 }
1017                         }
1018                 else
1019                         {
1020                         FileInputStream in =
1021                                 new FileInputStream( eFile );
1022
1023                         byte[] eBuf = new byte[ 32 * 1024 ];
1024                         for ( ; ; )
1025                                 {
1026                                 int numRead = in.read( eBuf, 0, eBuf.length );
1027
1028                                 if ( numRead == -1 )
1029                                         break;
1030
1031                                 this.tarOut.write( eBuf, 0, numRead );
1032                                 }
1033
1034                         in.close();
1035
1036                         if ( tFile != null )
1037                                 {
1038                                 tFile.delete();
1039                                 }
1040                         
1041                         this.tarOut.closeEntry();
1042                         }
1043                 }
1044
1045         }
1046
1047
1048 /*
1049 ** Authored by Timothy Gerard Endres
1050 ** <mailto:time@gjt.org>  <http://www.trustice.com>
1051 ** 
1052 ** This work has been placed into the public domain.
1053 ** You may use this work in any way and for any purpose you wish.
1054 **
1055 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
1056 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
1057 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
1058 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
1059 ** REDISTRIBUTION OF THIS SOFTWARE. 
1060 ** 
1061 */
1062
1063 /**
1064  * The TarBuffer class implements the tar archive concept
1065  * of a buffered input stream. This concept goes back to the
1066  * days of blocked tape drives and special io devices. In the
1067  * Java universe, the only real function that this class
1068  * performs is to ensure that files have the correct "block"
1069  * size, or other tars will complain.
1070  * <p>
1071  * You should never have a need to access this class directly.
1072  * TarBuffers are created by Tar IO Streams.
1073  *
1074  * @version $Revision: 1.10 $
1075  * @author Timothy Gerard Endres,
1076  *  <a href="mailto:time@gjt.org">time@trustice.com</a>.
1077  * @see TarArchive
1078  */
1079
1080 public
1081 static class            TarBuffer
1082 extends         Object
1083         {
1084         public static final int         DEFAULT_RCDSIZE = ( 512 );
1085         public static final int         DEFAULT_BLKSIZE = ( DEFAULT_RCDSIZE * 20 );
1086
1087         private InputStream             inStream;
1088         private OutputStream    outStream;
1089
1090         private byte[]  blockBuffer;
1091         private int             currBlkIdx;
1092         private int             currRecIdx;
1093         private int             blockSize;
1094         private int             recordSize;
1095         private int             recsPerBlock;
1096
1097         private boolean debug;
1098
1099
1100         public
1101         TarBuffer( InputStream inStream )
1102                 {
1103                 this( inStream, TarBuffer.DEFAULT_BLKSIZE );
1104                 }
1105
1106         public
1107         TarBuffer( InputStream inStream, int blockSize )
1108                 {
1109                 this( inStream, blockSize, TarBuffer.DEFAULT_RCDSIZE );
1110                 }
1111
1112         public
1113         TarBuffer( InputStream inStream, int blockSize, int recordSize )
1114                 {
1115                 this.inStream = inStream;
1116                 this.outStream = null;
1117                 this.initialize( blockSize, recordSize );
1118                 }
1119
1120         public
1121         TarBuffer( OutputStream outStream )
1122                 {
1123                 this( outStream, TarBuffer.DEFAULT_BLKSIZE );
1124                 }
1125
1126         public
1127         TarBuffer( OutputStream outStream, int blockSize )
1128                 {
1129                 this( outStream, blockSize, TarBuffer.DEFAULT_RCDSIZE );
1130                 }
1131
1132         public
1133         TarBuffer( OutputStream outStream, int blockSize, int recordSize )
1134                 {
1135                 this.inStream = null;
1136                 this.outStream = outStream;
1137                 this.initialize( blockSize, recordSize );
1138                 }
1139
1140         /**
1141          * Initialization common to all constructors.
1142          */
1143         private void
1144         initialize( int blockSize, int recordSize )
1145                 {
1146                 this.debug = false;
1147                 this.blockSize = blockSize;
1148                 this.recordSize = recordSize;
1149                 this.recsPerBlock = ( this.blockSize / this.recordSize );
1150                 this.blockBuffer = new byte[ this.blockSize ];
1151
1152                 if ( this.inStream != null )
1153                         {
1154                         this.currBlkIdx = -1;
1155                         this.currRecIdx = this.recsPerBlock;
1156                         }
1157                 else
1158                         {
1159                         this.currBlkIdx = 0;
1160                         this.currRecIdx = 0;
1161                         }
1162                 }
1163
1164         /**
1165          * Get the TAR Buffer's block size. Blocks consist of multiple records.
1166          */
1167         public int
1168         getBlockSize()
1169                 {
1170                 return this.blockSize;
1171                 }
1172
1173         /**
1174          * Get the TAR Buffer's record size.
1175          */
1176         public int
1177         getRecordSize()
1178                 {
1179                 return this.recordSize;
1180                 }
1181
1182         /**
1183          * Set the debugging flag for the buffer.
1184          *
1185          * @param debug If true, print debugging output.
1186          */
1187         public void
1188         setDebug( boolean debug )
1189                 {
1190                 this.debug = debug;
1191                 }
1192
1193         /**
1194          * Determine if an archive record indicate End of Archive. End of
1195          * archive is indicated by a record that consists entirely of null bytes.
1196          *
1197          * @param record The record data to check.
1198          */
1199         public boolean
1200         isEOFRecord( byte[] record )
1201                 {
1202                 for ( int i = 0, sz = this.getRecordSize() ; i < sz ; ++i )
1203                         if ( record[i] != 0 )
1204                                 return false;
1205
1206                 return true;
1207                 }
1208
1209         /**
1210          * Skip over a record on the input stream.
1211          */
1212
1213         public void
1214         skipRecord()
1215                 throws IOException
1216                 {
1217                 if ( this.debug )
1218                         {
1219                         System.err.println
1220                                 ( "SkipRecord: recIdx = " + this.currRecIdx
1221                                         + " blkIdx = " + this.currBlkIdx );
1222                         }
1223
1224                 if ( this.inStream == null )
1225                         throw new IOException
1226                                 ( "reading (via skip) from an output buffer" );
1227
1228                 if ( this.currRecIdx >= this.recsPerBlock )
1229                         {
1230                         if ( ! this.readBlock() )
1231                                 return; // UNDONE
1232                         }
1233
1234                 this.currRecIdx++;
1235                 }
1236
1237         /**
1238          * Read a record from the input stream and return the data.
1239          *
1240          * @return The record data.
1241          */
1242
1243         public byte[]
1244         readRecord()
1245                 throws IOException
1246                 {
1247                 if ( this.debug )
1248                         {
1249                         System.err.println
1250                                 ( "ReadRecord: recIdx = " + this.currRecIdx
1251                                         + " blkIdx = " + this.currBlkIdx );
1252                         }
1253
1254                 if ( this.inStream == null )
1255                         throw new IOException
1256                                 ( "reading from an output buffer" );
1257
1258                 if ( this.currRecIdx >= this.recsPerBlock )
1259                         {
1260                         if ( ! this.readBlock() )
1261                                 return null;
1262                         }
1263
1264                 byte[] result = new byte[ this.recordSize ];
1265
1266                 System.arraycopy(
1267                         this.blockBuffer, (this.currRecIdx * this.recordSize),
1268                         result, 0, this.recordSize );
1269
1270                 this.currRecIdx++;
1271
1272                 return result;
1273                 }
1274
1275         /**
1276          * @return false if End-Of-File, else true
1277          */
1278
1279         private boolean
1280         readBlock()
1281                 throws IOException
1282                 {
1283                 if ( this.debug )
1284                         {
1285                         System.err.println
1286                                 ( "ReadBlock: blkIdx = " + this.currBlkIdx );
1287                         }
1288
1289                 if ( this.inStream == null )
1290                         throw new IOException
1291                                 ( "reading from an output buffer" );
1292
1293                 this.currRecIdx = 0;
1294
1295                 int offset = 0;
1296                 int bytesNeeded = this.blockSize;
1297                 for ( ; bytesNeeded > 0 ; )
1298                         {
1299                         long numBytes =
1300                                 this.inStream.read
1301                                         ( this.blockBuffer, offset, bytesNeeded );
1302
1303                         //
1304                         // NOTE
1305                         // We have fit EOF, and the block is not full!
1306                         //
1307                         // This is a broken archive. It does not follow the standard
1308                         // blocking algorithm. However, because we are generous, and
1309                         // it requires little effort, we will simply ignore the error
1310                         // and continue as if the entire block were read. This does
1311                         // not appear to break anything upstream. We used to return
1312                         // false in this case.
1313                         //
1314                         // Thanks to 'Yohann.Roussel@alcatel.fr' for this fix.
1315                         //
1316
1317                         if ( numBytes == -1 )
1318                                 break;
1319
1320                         offset += numBytes;
1321                         bytesNeeded -= numBytes;
1322                         if ( numBytes != this.blockSize )
1323                                 {
1324                                 if ( this.debug )
1325                                         {
1326                                         System.err.println
1327                                                 ( "ReadBlock: INCOMPLETE READ " + numBytes
1328                                                         + " of " + this.blockSize + " bytes read." );
1329                                         }
1330                                 }
1331                         }
1332
1333                 this.currBlkIdx++;
1334
1335                 return true;
1336                 }
1337
1338         /**
1339          * Get the current block number, zero based.
1340          *
1341          * @return The current zero based block number.
1342          */
1343         public int
1344         getCurrentBlockNum()
1345                 {
1346                 return this.currBlkIdx;
1347                 }
1348
1349         /**
1350          * Get the current record number, within the current block, zero based.
1351          * Thus, current offset = (currentBlockNum * recsPerBlk) + currentRecNum.
1352          *
1353          * @return The current zero based record number.
1354          */
1355         public int
1356         getCurrentRecordNum()
1357                 {
1358                 return this.currRecIdx - 1;
1359                 }
1360
1361         /**
1362          * Write an archive record to the archive.
1363          *
1364          * @param record The record data to write to the archive.
1365          */
1366
1367         public void
1368         writeRecord( byte[] record )
1369                 throws IOException
1370                 {
1371                 if ( this.debug )
1372                         {
1373                         System.err.println
1374                                 ( "WriteRecord: recIdx = " + this.currRecIdx
1375                                         + " blkIdx = " + this.currBlkIdx );
1376                         }
1377
1378                 if ( this.outStream == null )
1379                         throw new IOException
1380                                 ( "writing to an input buffer" );
1381
1382                 if ( record.length != this.recordSize )
1383                         throw new IOException
1384                                 ( "record to write has length '" + record.length
1385                                         + "' which is not the record size of '"
1386                                         + this.recordSize + "'" );
1387
1388                 if ( this.currRecIdx >= this.recsPerBlock )
1389                         {
1390                         this.writeBlock();
1391                         }
1392
1393                 System.arraycopy(
1394                         record, 0,
1395                         this.blockBuffer, (this.currRecIdx * this.recordSize),
1396                         this.recordSize );
1397
1398                 this.currRecIdx++;
1399                 }
1400
1401         /**
1402          * Write an archive record to the archive, where the record may be
1403          * inside of a larger array buffer. The buffer must be "offset plus
1404          * record size" long.
1405          *
1406          * @param buf The buffer containing the record data to write.
1407          * @param offset The offset of the record data within buf.
1408          */
1409
1410         public void
1411         writeRecord( byte[] buf, int offset )
1412                 throws IOException
1413                 {
1414                 if ( this.debug )
1415                         {
1416                         System.err.println
1417                                 ( "WriteRecord: recIdx = " + this.currRecIdx
1418                                         + " blkIdx = " + this.currBlkIdx );
1419                         }
1420
1421                 if ( this.outStream == null )
1422                         throw new IOException
1423                                 ( "writing to an input buffer" );
1424
1425                 if ( (offset + this.recordSize) > buf.length )
1426                         throw new IOException
1427                                 ( "record has length '" + buf.length
1428                                         + "' with offset '" + offset
1429                                         + "' which is less than the record size of '"
1430                                         + this.recordSize + "'" );
1431
1432                 if ( this.currRecIdx >= this.recsPerBlock )
1433                         {
1434                         this.writeBlock();
1435                         }
1436
1437                 System.arraycopy(
1438                         buf, offset,
1439                         this.blockBuffer, (this.currRecIdx * this.recordSize),
1440                         this.recordSize );
1441
1442                 this.currRecIdx++;
1443                 }
1444
1445         /**
1446          * Write a TarBuffer block to the archive.
1447          */
1448         private void
1449         writeBlock()
1450                 throws IOException
1451                 {
1452                 if ( this.debug )
1453                         {
1454                         System.err.println
1455                                 ( "WriteBlock: blkIdx = " + this.currBlkIdx );
1456                         }
1457
1458                 if ( this.outStream == null )
1459                         throw new IOException
1460                                 ( "writing to an input buffer" );
1461
1462                 this.outStream.write( this.blockBuffer, 0, this.blockSize );
1463                 this.outStream.flush();
1464
1465                 this.currRecIdx = 0;
1466                 this.currBlkIdx++;
1467                 }
1468
1469         /**
1470          * Flush the current data block if it has any data in it.
1471          */
1472
1473         private void
1474         flushBlock()
1475                 throws IOException
1476                 {
1477                 if ( this.debug )
1478                         {
1479                         System.err.println( "TarBuffer.flushBlock() called." );
1480                         }
1481
1482                 if ( this.outStream == null )
1483                         throw new IOException
1484                                 ( "writing to an input buffer" );
1485
1486                 // Thanks to 'Todd Kofford <tkofford@bigfoot.com>' for this patch.
1487                 // Use a buffer initialized with 0s to initialize everything in the
1488                 // blockBuffer after the last current, complete record. This prevents
1489                 // any previous data that might have previously existed in the
1490                 // blockBuffer from being written to the file.
1491
1492                 if ( this.currRecIdx > 0 )
1493                         {
1494                         int offset = this.currRecIdx * this.recordSize;
1495                         byte[]  zeroBuffer = new byte[ this.blockSize - offset ];
1496
1497                         System.arraycopy
1498                                 ( zeroBuffer, 0, this.blockBuffer, offset, zeroBuffer.length );
1499
1500                         this.writeBlock();
1501                         }
1502                 }
1503
1504         /**
1505          * Close the TarBuffer. If this is an output buffer, also flush the
1506          * current block before closing.
1507          */
1508         public void
1509         close()
1510                 throws IOException
1511                 {
1512                 if ( this.debug )
1513                         {
1514                         System.err.println( "TarBuffer.closeBuffer()." );
1515                         }
1516
1517                 if ( this.outStream != null )
1518                         {
1519                         this.flushBlock();
1520
1521                         if ( this.outStream != System.out
1522                                         && this.outStream != System.err )
1523                                 {
1524                                 this.outStream.close();
1525                                 this.outStream = null;
1526                                 }
1527                         }
1528                 else if ( this.inStream != null )
1529                         {
1530                         if ( this.inStream != System.in )
1531                                 {
1532                                 this.inStream.close();
1533                                 this.inStream = null;
1534                                 }
1535                         }
1536                 }
1537
1538         }
1539
1540 /*
1541 ** Authored by Timothy Gerard Endres
1542 ** <mailto:time@gjt.org>  <http://www.trustice.com>
1543 ** 
1544 ** This work has been placed into the public domain.
1545 ** You may use this work in any way and for any purpose you wish.
1546 **
1547 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
1548 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
1549 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
1550 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
1551 ** REDISTRIBUTION OF THIS SOFTWARE. 
1552 ** 
1553 */
1554
1555
1556 /**
1557  *
1558  * This class represents an entry in a Tar archive. It consists
1559  * of the entry's header, as well as the entry's File. Entries
1560  * can be instantiated in one of three ways, depending on how
1561  * they are to be used.
1562  * <p>
1563  * TarEntries that are created from the header bytes read from
1564  * an archive are instantiated with the TarEntry( byte[] )
1565  * constructor. These entries will be used when extracting from
1566  * or listing the contents of an archive. These entries have their
1567  * header filled in using the header bytes. They also set the File
1568  * to null, since they reference an archive entry not a file.
1569  * <p>
1570  * TarEntries that are created from Files that are to be written
1571  * into an archive are instantiated with the TarEntry( File )
1572  * constructor. These entries have their header filled in using
1573  * the File's information. They also keep a reference to the File
1574  * for convenience when writing entries.
1575  * <p>
1576  * Finally, TarEntries can be constructed from nothing but a name.
1577  * This allows the programmer to construct the entry by hand, for
1578  * instance when only an InputStream is available for writing to
1579  * the archive, and the header information is constructed from
1580  * other information. In this case the header fields are set to
1581  * defaults and the File is set to null.
1582  *
1583  * <pre>
1584  *
1585  * Original Unix Tar Header:
1586  *
1587  * Field  Field     Field
1588  * Width  Name      Meaning
1589  * -----  --------- ---------------------------
1590  *   100  name      name of file
1591  *     8  mode      file mode
1592  *     8  uid       owner user ID
1593  *     8  gid       owner group ID
1594  *    12  size      length of file in bytes
1595  *    12  mtime     modify time of file
1596  *     8  chksum    checksum for header
1597  *     1  link      indicator for links
1598  *   100  linkname  name of linked file
1599  *
1600  * </pre>
1601  *
1602  * <pre>
1603  *
1604  * POSIX "ustar" Style Tar Header:
1605  *
1606  * Field  Field     Field
1607  * Width  Name      Meaning
1608  * -----  --------- ---------------------------
1609  *   100  name      name of file
1610  *     8  mode      file mode
1611  *     8  uid       owner user ID
1612  *     8  gid       owner group ID
1613  *    12  size      length of file in bytes
1614  *    12  mtime     modify time of file
1615  *     8  chksum    checksum for header
1616  *     1  typeflag  type of file
1617  *   100  linkname  name of linked file
1618  *     6  magic     USTAR indicator
1619  *     2  version   USTAR version
1620  *    32  uname     owner user name
1621  *    32  gname     owner group name
1622  *     8  devmajor  device major number
1623  *     8  devminor  device minor number
1624  *   155  prefix    prefix for file name
1625  *
1626  * struct posix_header
1627  *   {                     byte offset
1628  *   char name[100];            0
1629  *   char mode[8];            100
1630  *   char uid[8];             108
1631  *   char gid[8];             116
1632  *   char size[12];           124
1633  *   char mtime[12];          136
1634  *   char chksum[8];          148
1635  *   char typeflag;           156
1636  *   char linkname[100];      157
1637  *   char magic[6];           257
1638  *   char version[2];         263
1639  *   char uname[32];          265
1640  *   char gname[32];          297
1641  *   char devmajor[8];        329
1642  *   char devminor[8];        337
1643  *   char prefix[155];        345
1644  *   };                       500
1645  *
1646  * </pre>
1647  *
1648  * Note that while the class does recognize GNU formatted headers,
1649  * it does not perform proper processing of GNU archives. I hope
1650  * to add the GNU support someday.
1651  *
1652  * Directory "size" fix contributed by:
1653  * Bert Becker <becker@informatik.hu-berlin.de>
1654  *
1655  * @see TarHeader
1656  * @author Timothy Gerard Endres, <time@gjt.org>
1657  */
1658
1659 public
1660 static class            TarEntry
1661 extends         Object
1662 implements      Cloneable
1663         {
1664         /**
1665          * If this entry represents a File, this references it.
1666          */
1667         protected File                          file;
1668
1669         /**
1670          * This is the entry's header information.
1671          */
1672         protected TarHeader                     header;
1673
1674         /**
1675          * Set to true if this is a "old-unix" format entry.
1676          */
1677         protected boolean                       unixFormat;
1678
1679         /**
1680          * Set to true if this is a 'ustar' format entry.
1681          */
1682         protected boolean                       ustarFormat;
1683
1684         /**
1685          * Set to true if this is a GNU 'ustar' format entry.
1686          */
1687         protected boolean                       gnuFormat;
1688
1689
1690         /**
1691          * The default constructor is protected for use only by subclasses.
1692          */
1693         protected
1694         TarEntry()
1695                 {
1696                 }
1697
1698         /**
1699          * Construct an entry with only a name. This allows the programmer
1700          * to construct the entry's header "by hand". File is set to null.
1701          */
1702         public
1703         TarEntry( String name )
1704                 {
1705                 this.initialize();
1706                 this.nameTarHeader( this.header, name );
1707                 }
1708
1709         /**
1710          * Construct an entry for a file. File is set to file, and the
1711          * header is constructed from information from the file.
1712          *
1713          * @param file The file that the entry represents.
1714          */
1715         public
1716         TarEntry( File file )
1717                 throws InvalidHeaderException
1718                 {
1719                 this.initialize();
1720                 this.getFileTarHeader( this.header, file );
1721                 }
1722
1723         /**
1724          * Construct an entry from an archive's header bytes. File is set
1725          * to null.
1726          *
1727          * @param headerBuf The header bytes from a tar archive entry.
1728          */
1729         public
1730         TarEntry( byte[] headerBuf )
1731                 throws InvalidHeaderException
1732                 {
1733                 this.initialize();
1734                 this.parseTarHeader( this.header, headerBuf );
1735                 }
1736
1737         /**
1738          * Initialization code common to all constructors.
1739          */
1740         private void
1741         initialize()
1742                 {
1743                 this.file = null;
1744                 this.header = new TarHeader();
1745
1746                 this.gnuFormat = false;
1747                 this.ustarFormat = true; // REVIEW What we prefer to use...
1748                 this.unixFormat = false;
1749                 }
1750
1751         /**
1752          * Clone the entry.
1753          */
1754         public Object
1755         clone()
1756                 {
1757                 TarEntry entry = null;
1758
1759                 try {
1760                         entry = (TarEntry) super.clone();
1761
1762                         if ( this.header != null )
1763                                 {
1764                                 entry.header = (TarHeader) this.header.clone();
1765                                 }
1766
1767                         if ( this.file != null )
1768                                 {
1769                                 entry.file = new File( this.file.getAbsolutePath() );
1770                                 }
1771                         }
1772                 catch ( CloneNotSupportedException ex )
1773                         {
1774                         ex.printStackTrace( System.err );
1775                         }
1776
1777                 return entry;
1778                 }
1779
1780         /**
1781          * Returns true if this entry's header is in "ustar" format.
1782          *
1783          * @return True if the entry's header is in "ustar" format.
1784          */
1785         public boolean
1786         isUSTarFormat()
1787                 {
1788                 return this.ustarFormat;
1789                 }
1790
1791         /**
1792          * Sets this entry's header format to "ustar".
1793          */
1794         public void
1795         setUSTarFormat()
1796                 {
1797                 this.ustarFormat = true;
1798                 this.gnuFormat = false;
1799                 this.unixFormat = false;
1800                 }
1801
1802         /**
1803          * Returns true if this entry's header is in the GNU 'ustar' format.
1804          *
1805          * @return True if the entry's header is in the GNU 'ustar' format.
1806          */
1807         public boolean
1808         isGNUTarFormat()
1809                 {
1810                 return this.gnuFormat;
1811                 }
1812
1813         /**
1814          * Sets this entry's header format to GNU "ustar".
1815          */
1816         public void
1817         setGNUTarFormat()
1818                 {
1819                 this.gnuFormat = true;
1820                 this.ustarFormat = false;
1821                 this.unixFormat = false;
1822                 }
1823
1824         /**
1825          * Returns true if this entry's header is in the old "unix-tar" format.
1826          *
1827          * @return True if the entry's header is in the old "unix-tar" format.
1828          */
1829         public boolean
1830         isUnixTarFormat()
1831                 {
1832                 return this.unixFormat;
1833                 }
1834
1835         /**
1836          * Sets this entry's header format to "unix-style".
1837          */
1838         public void
1839         setUnixTarFormat()
1840                 {
1841                 this.unixFormat = true;
1842                 this.ustarFormat = false;
1843                 this.gnuFormat = false;
1844                 }
1845
1846         /**
1847          * Determine if the two entries are equal. Equality is determined
1848          * by the header names being equal.
1849          *
1850          * @return it Entry to be checked for equality.
1851          * @return True if the entries are equal.
1852          */
1853         public boolean
1854         equals( TarEntry it )
1855                 {
1856                 return
1857                         this.header.name.toString().equals
1858                                 ( it.header.name.toString() );
1859                 }
1860
1861         /**
1862          * Determine if the given entry is a descendant of this entry.
1863          * Descendancy is determined by the name of the descendant
1864          * starting with this entry's name.
1865          *
1866          * @param desc Entry to be checked as a descendent of this.
1867          * @return True if entry is a descendant of this.
1868          */
1869         public boolean
1870         isDescendent( TarEntry desc )
1871                 {
1872                 return
1873                         desc.header.name.toString().startsWith
1874                                 ( this.header.name.toString() );
1875                 }
1876
1877         /**
1878          * Get this entry's header.
1879          *
1880          * @return This entry's TarHeader.
1881          */
1882         public TarHeader
1883         getHeader()
1884                 {
1885                 return this.header;
1886                 }
1887
1888         /**
1889          * Get this entry's name.
1890          *
1891          * @return This entry's name.
1892          */
1893         public String
1894         getName()
1895                 {
1896                 return this.header.name.toString();
1897                 }
1898
1899         /**
1900          * Set this entry's name.
1901          *
1902          * @param name This entry's new name.
1903          */
1904         public void
1905         setName( String name )
1906                 {
1907                 this.header.name =
1908                         new StringBuffer( name );
1909                 }
1910
1911         /**
1912          * Get this entry's user id.
1913          *
1914          * @return This entry's user id.
1915          */
1916         public int
1917         getUserId()
1918                 {
1919                 return this.header.userId;
1920                 }
1921
1922         /**
1923          * Set this entry's user id.
1924          *
1925          * @param userId This entry's new user id.
1926          */
1927         public void
1928         setUserId( int userId )
1929                 {
1930                 this.header.userId = userId;
1931                 }
1932
1933         /**
1934          * Get this entry's group id.
1935          *
1936          * @return This entry's group id.
1937          */
1938         public int
1939         getGroupId()
1940                 {
1941                 return this.header.groupId;
1942                 }
1943
1944         /**
1945          * Set this entry's group id.
1946          *
1947          * @param groupId This entry's new group id.
1948          */
1949         public void
1950         setGroupId( int groupId )
1951                 {
1952                 this.header.groupId = groupId;
1953                 }
1954
1955         /**
1956          * Get this entry's user name.
1957          *
1958          * @return This entry's user name.
1959          */
1960         public String
1961         getUserName()
1962                 {
1963                 return this.header.userName.toString();
1964                 }
1965
1966         /**
1967          * Set this entry's user name.
1968          *
1969          * @param userName This entry's new user name.
1970          */
1971         public void
1972         setUserName( String userName )
1973                 {
1974                 this.header.userName =
1975                         new StringBuffer( userName );
1976                 }
1977
1978         /**
1979          * Get this entry's group name.
1980          *
1981          * @return This entry's group name.
1982          */
1983         public String
1984         getGroupName()
1985                 {
1986                 return this.header.groupName.toString();
1987                 }
1988
1989         /**
1990          * Set this entry's group name.
1991          *
1992          * @param groupName This entry's new group name.
1993          */
1994         public void
1995         setGroupName( String groupName )
1996                 {
1997                 this.header.groupName =
1998                         new StringBuffer( groupName );
1999                 }
2000
2001         /**
2002          * Convenience method to set this entry's group and user ids.
2003          *
2004          * @param userId This entry's new user id.
2005          * @param groupId This entry's new group id.
2006          */
2007         public void
2008         setIds( int userId, int groupId )
2009                 {
2010                 this.setUserId( userId );
2011                 this.setGroupId( groupId );
2012                 }
2013
2014         /**
2015          * Convenience method to set this entry's group and user names.
2016          *
2017          * @param userName This entry's new user name.
2018          * @param groupName This entry's new group name.
2019          */
2020         public void
2021         setNames( String userName, String groupName )
2022                 {
2023                 this.setUserName( userName );
2024                 this.setGroupName( groupName );
2025                 }
2026
2027         /**
2028          * Set this entry's modification time. The parameter passed
2029          * to this method is in "Java time".
2030          *
2031          * @param time This entry's new modification time.
2032          */
2033         public void
2034         setModTime( long time )
2035                 {
2036                 this.header.modTime = time / 1000;
2037                 }
2038
2039         /**
2040          * Set this entry's modification time.
2041          *
2042          * @param time This entry's new modification time.
2043          */
2044         public void
2045         setModTime( Date time )
2046                 {
2047                 this.header.modTime = time.getTime() / 1000;
2048                 }
2049
2050         /**
2051          * Set this entry's modification time.
2052          *
2053          * @param time This entry's new modification time.
2054          */
2055         public Date
2056         getModTime()
2057                 {
2058                 return new Date( this.header.modTime * 1000 );
2059                 }
2060
2061         /**
2062          * Get this entry's file.
2063          *
2064          * @return This entry's file.
2065          */
2066         public File
2067         getFile()
2068                 {
2069                 return this.file;
2070                 }
2071
2072         /**
2073          * Get this entry's file size.
2074          *
2075          * @return This entry's file size.
2076          */
2077         public long
2078         getSize()
2079                 {
2080                 return this.header.size;
2081                 }
2082
2083         /**
2084          * Set this entry's file size.
2085          *
2086          * @param size This entry's new file size.
2087          */
2088         public void
2089         setSize( long size )
2090                 {
2091                 this.header.size = size;
2092                 }
2093
2094         /**
2095          * Return whether or not this entry represents a directory.
2096          *
2097          * @return True if this entry is a directory.
2098          */
2099         public boolean
2100         isDirectory()
2101                 {
2102                 if ( this.file != null )
2103                         return this.file.isDirectory();
2104
2105                 if ( this.header != null )
2106                         {
2107                         if ( this.header.linkFlag == TarHeader.LF_DIR )
2108                                 return true;
2109
2110                         if ( this.header.name.toString().endsWith( "/" ) )
2111                                 return true;
2112                         }
2113
2114                 return false;
2115                 }
2116
2117         /**
2118          * Fill in a TarHeader with information from a File.
2119          *
2120          * @param hdr The TarHeader to fill in.
2121          * @param file The file from which to get the header information.
2122          */
2123         public void
2124         getFileTarHeader( TarHeader hdr, File file )
2125                 throws InvalidHeaderException
2126                 {
2127                 this.file = file;
2128
2129                 String name = file.getPath();
2130                 String osname = System.getProperty( "os.name" );
2131                 if ( osname != null )
2132                         {
2133                         // Strip off drive letters!
2134                         // REVIEW Would a better check be "(File.separator == '\')"?
2135
2136                         // String Win32Prefix = "Windows";
2137                         // String prefix = osname.substring( 0, Win32Prefix.length() );
2138                         // if ( prefix.equalsIgnoreCase( Win32Prefix ) )
2139
2140                         // if ( File.separatorChar == '\\' )
2141
2142                         // Windows OS check was contributed by
2143                         // Patrick Beard <beard@netscape.com>
2144                         String Win32Prefix = "windows";
2145                         if ( osname.toLowerCase().startsWith( Win32Prefix ) )
2146                                 {
2147                                 if ( name.length() > 2 )
2148                                         {
2149                                         char ch1 = name.charAt(0);
2150                                         char ch2 = name.charAt(1);
2151                                         if ( ch2 == ':'
2152                                                 && ( (ch1 >= 'a' && ch1 <= 'z')
2153                                                         || (ch1 >= 'A' && ch1 <= 'Z') ) )
2154                                                 {
2155                                                 name = name.substring( 2 );
2156                                                 }
2157                                         }
2158                                 }
2159                         }
2160
2161                 name = name.replace( File.separatorChar, '/' );
2162
2163                 // No absolute pathnames
2164                 // Windows (and Posix?) paths can start with "\\NetworkDrive\",
2165                 // so we loop on starting /'s.
2166                 
2167                 for ( ; name.startsWith( "/" ) ; )
2168                         name = name.substring( 1 );
2169
2170                 hdr.linkName = new StringBuffer( "" );
2171
2172                 hdr.name = new StringBuffer( name );
2173
2174                 if ( file.isDirectory() )
2175                         {
2176                         hdr.size = 0;
2177                         hdr.mode = 040755;
2178                         hdr.linkFlag = TarHeader.LF_DIR;
2179                         if ( hdr.name.charAt( hdr.name.length() - 1 ) != '/' )
2180                                 hdr.name.append( "/" );
2181                         }
2182                 else
2183                         {
2184                         hdr.size = file.length();
2185                         hdr.mode = 0100644;
2186                         hdr.linkFlag = TarHeader.LF_NORMAL;
2187                         }
2188
2189                 // UNDONE When File lets us get the userName, use it!
2190
2191                 hdr.modTime = file.lastModified() / 1000;
2192                 hdr.checkSum = 0;
2193                 hdr.devMajor = 0;
2194                 hdr.devMinor = 0;
2195                 }
2196
2197         /**
2198          * If this entry represents a file, and the file is a directory, return
2199          * an array of TarEntries for this entry's children.
2200          *
2201          * @return An array of TarEntry's for this entry's children.
2202          */
2203         public TarEntry[]
2204         getDirectoryEntries()
2205                 throws InvalidHeaderException
2206                 {
2207                 if ( this.file == null
2208                                 || ! this.file.isDirectory() )
2209                         {
2210                         return new TarEntry[0];
2211                         }
2212
2213                 String[] list = this.file.list();
2214
2215                 TarEntry[] result = new TarEntry[ list.length ];
2216
2217                 for ( int i = 0 ; i < list.length ; ++i )
2218                         {
2219                         result[i] =
2220                                 new TarEntry
2221                                         ( new File( this.file, list[i] ) );
2222                         }
2223
2224                 return result;
2225                 }
2226
2227         /**
2228          * Compute the checksum of a tar entry header.
2229          *
2230          * @param buf The tar entry's header buffer.
2231          * @return The computed checksum.
2232          */
2233         public long
2234         computeCheckSum( byte[] buf )
2235                 {
2236                 long sum = 0;
2237
2238                 for ( int i = 0 ; i < buf.length ; ++i )
2239                         {
2240                         sum += 255 & buf[ i ];
2241                         }
2242
2243                 return sum;
2244                 }
2245
2246         /**
2247          * Write an entry's header information to a header buffer.
2248          * This method can throw an InvalidHeaderException
2249          *
2250          * @param outbuf The tar entry header buffer to fill in.
2251          * @throws InvalidHeaderException If the name will not fit in the header.
2252          */
2253         public void
2254         writeEntryHeader( byte[] outbuf )
2255                 throws InvalidHeaderException
2256                 {
2257                 int offset = 0;
2258
2259                 if ( this.isUnixTarFormat() )
2260                         {
2261                         if ( this.header.name.length() > 100 )
2262                                 throw new InvalidHeaderException
2263                                         ( "file path is greater than 100 characters, "
2264                                                 + this.header.name );
2265                         }
2266
2267                 offset = TarHeader.getFileNameBytes( this.header.name.toString(), outbuf );
2268
2269                 offset = TarHeader.getOctalBytes
2270                         ( this.header.mode, outbuf, offset, TarHeader.MODELEN );
2271
2272                 offset = TarHeader.getOctalBytes
2273                         ( this.header.userId, outbuf, offset, TarHeader.UIDLEN );
2274
2275                 offset = TarHeader.getOctalBytes
2276                         ( this.header.groupId, outbuf, offset, TarHeader.GIDLEN );
2277
2278                 long size = this.header.size;
2279
2280                 offset = TarHeader.getLongOctalBytes
2281                         ( size, outbuf, offset, TarHeader.SIZELEN );
2282
2283                 offset = TarHeader.getLongOctalBytes
2284                         ( this.header.modTime, outbuf, offset, TarHeader.MODTIMELEN );
2285
2286                 int csOffset = offset;
2287                 for ( int c = 0 ; c < TarHeader.CHKSUMLEN ; ++c )
2288                         outbuf[ offset++ ] = (byte) ' ';
2289
2290                 outbuf[ offset++ ] = this.header.linkFlag;
2291
2292                 offset = TarHeader.getNameBytes
2293                         ( this.header.linkName, outbuf, offset, TarHeader.NAMELEN );
2294
2295                 if ( this.unixFormat )
2296                         {
2297                         for ( int i = 0 ; i < TarHeader.MAGICLEN ; ++i )
2298                                 outbuf[ offset++ ] = 0;
2299                         }
2300                 else
2301                         {
2302                         offset = TarHeader.getNameBytes
2303                                 ( this.header.magic, outbuf, offset, TarHeader.MAGICLEN );
2304                         }
2305
2306                 offset = TarHeader.getNameBytes
2307                         ( this.header.userName, outbuf, offset, TarHeader.UNAMELEN );
2308
2309                 offset = TarHeader.getNameBytes
2310                         ( this.header.groupName, outbuf, offset, TarHeader.GNAMELEN );
2311
2312                 offset = TarHeader.getOctalBytes
2313                         ( this.header.devMajor, outbuf, offset, TarHeader.DEVLEN );
2314
2315                 offset = TarHeader.getOctalBytes
2316                         ( this.header.devMinor, outbuf, offset, TarHeader.DEVLEN );
2317
2318                 for ( ; offset < outbuf.length ; )
2319                         outbuf[ offset++ ] = 0;
2320
2321                 long checkSum = this.computeCheckSum( outbuf );
2322
2323                 TarHeader.getCheckSumOctalBytes
2324                         ( checkSum, outbuf, csOffset, TarHeader.CHKSUMLEN );
2325                 }
2326
2327         /**
2328          * Parse an entry's TarHeader information from a header buffer.
2329          *
2330          * Old unix-style code contributed by David Mehringer <dmehring@astro.uiuc.edu>.
2331          *
2332          * @param hdr The TarHeader to fill in from the buffer information.
2333          * @param header The tar entry header buffer to get information from.
2334          */
2335         public void
2336         parseTarHeader( TarHeader hdr, byte[] headerBuf )
2337                 throws InvalidHeaderException
2338                 {
2339                 int offset = 0;
2340
2341                 //
2342                 // NOTE Recognize archive header format.
2343                 //
2344                 if (       headerBuf[257] == 0
2345                                 && headerBuf[258] == 0
2346                                 && headerBuf[259] == 0
2347                                 && headerBuf[260] == 0
2348                                 && headerBuf[261] == 0 )
2349                         {
2350                         this.unixFormat = true;
2351                         this.ustarFormat = false;
2352                         this.gnuFormat = false;
2353                         }
2354                 else if (  headerBuf[257] == 'u'
2355                                 && headerBuf[258] == 's'
2356                                 && headerBuf[259] == 't'
2357                                 && headerBuf[260] == 'a'
2358                                 && headerBuf[261] == 'r'
2359                                 && headerBuf[262] == 0 )
2360                         {
2361                         this.ustarFormat = true;
2362                         this.gnuFormat = false;
2363                         this.unixFormat = false;
2364                         }
2365                 else if (  headerBuf[257] == 'u'
2366                                 && headerBuf[258] == 's'
2367                                 && headerBuf[259] == 't'
2368                                 && headerBuf[260] == 'a'
2369                                 && headerBuf[261] == 'r'
2370                                 && headerBuf[262] != 0
2371                                 && headerBuf[263] != 0 )
2372                         {
2373                         // REVIEW
2374                         this.gnuFormat = true;
2375                         this.unixFormat = false;
2376                         this.ustarFormat = false;
2377                         }
2378                 else
2379                         {
2380                         StringBuffer buf = new StringBuffer( 128 );
2381
2382                         buf.append( "header magic is not 'ustar' or unix-style zeros, it is '" );
2383                         buf.append( headerBuf[257] );
2384                         buf.append( headerBuf[258] );
2385                         buf.append( headerBuf[259] );
2386                         buf.append( headerBuf[260] );
2387                         buf.append( headerBuf[261] );
2388                         buf.append( headerBuf[262] );
2389                         buf.append( headerBuf[263] );
2390                         buf.append( "', or (dec) " );
2391                         buf.append( (int)headerBuf[257] );
2392                         buf.append( ", " );
2393                         buf.append( (int)headerBuf[258] );
2394                         buf.append( ", " );
2395                         buf.append( (int)headerBuf[259] );
2396                         buf.append( ", " );
2397                         buf.append( (int)headerBuf[260] );
2398                         buf.append( ", " );
2399                         buf.append( (int)headerBuf[261] );
2400                         buf.append( ", " );
2401                         buf.append( (int)headerBuf[262] );
2402                         buf.append( ", " );
2403                         buf.append( (int)headerBuf[263] );
2404
2405                         throw new InvalidHeaderException( buf.toString() );
2406                         }
2407
2408                 hdr.name = TarHeader.parseFileName( headerBuf );
2409
2410                 offset = TarHeader.NAMELEN;
2411
2412                 hdr.mode = (int)
2413                         TarHeader.parseOctal( headerBuf, offset, TarHeader.MODELEN );
2414
2415                 offset += TarHeader.MODELEN;
2416
2417                 hdr.userId = (int)
2418                         TarHeader.parseOctal( headerBuf, offset, TarHeader.UIDLEN );
2419
2420                 offset += TarHeader.UIDLEN;
2421
2422                 hdr.groupId = (int)
2423                         TarHeader.parseOctal( headerBuf, offset, TarHeader.GIDLEN );
2424
2425                 offset += TarHeader.GIDLEN;
2426
2427                 hdr.size =
2428                         TarHeader.parseOctal( headerBuf, offset, TarHeader.SIZELEN );
2429
2430                 offset += TarHeader.SIZELEN;
2431
2432                 hdr.modTime =
2433                         TarHeader.parseOctal( headerBuf, offset, TarHeader.MODTIMELEN );
2434
2435                 offset += TarHeader.MODTIMELEN;
2436
2437                 hdr.checkSum = (int)
2438                         TarHeader.parseOctal( headerBuf, offset, TarHeader.CHKSUMLEN );
2439
2440                 offset += TarHeader.CHKSUMLEN;
2441
2442                 hdr.linkFlag = headerBuf[ offset++ ];
2443
2444                 hdr.linkName =
2445                         TarHeader.parseName( headerBuf, offset, TarHeader.NAMELEN );
2446
2447                 offset += TarHeader.NAMELEN;
2448
2449                 if ( this.ustarFormat )
2450                         {
2451                         hdr.magic =
2452                                 TarHeader.parseName( headerBuf, offset, TarHeader.MAGICLEN );
2453
2454                         offset += TarHeader.MAGICLEN;
2455
2456                         hdr.userName =
2457                                 TarHeader.parseName( headerBuf, offset, TarHeader.UNAMELEN );
2458
2459                         offset += TarHeader.UNAMELEN;
2460
2461                         hdr.groupName =
2462                                 TarHeader.parseName( headerBuf, offset, TarHeader.GNAMELEN );
2463
2464                         offset += TarHeader.GNAMELEN;
2465
2466                         hdr.devMajor = (int)
2467                                 TarHeader.parseOctal( headerBuf, offset, TarHeader.DEVLEN );
2468
2469                         offset += TarHeader.DEVLEN;
2470
2471                         hdr.devMinor = (int)
2472                                 TarHeader.parseOctal( headerBuf, offset, TarHeader.DEVLEN );
2473                         }
2474                 else
2475                         {
2476                         hdr.devMajor = 0;
2477                         hdr.devMinor = 0;
2478                         hdr.magic = new StringBuffer( "" );
2479                         hdr.userName = new StringBuffer( "" );
2480                         hdr.groupName = new StringBuffer( "" );
2481                         }
2482                 }
2483
2484         /**
2485          * Fill in a TarHeader given only the entry's name.
2486          *
2487          * @param hdr The TarHeader to fill in.
2488          * @param name The tar entry name.
2489          */
2490         public void
2491         nameTarHeader( TarHeader hdr, String name )
2492                 {
2493                 boolean isDir = name.endsWith( "/" );
2494
2495                 this.gnuFormat = false;
2496                 this.ustarFormat = true;
2497                 this.unixFormat = false;
2498
2499                 hdr.checkSum = 0;
2500                 hdr.devMajor = 0;
2501                 hdr.devMinor = 0;
2502
2503                 hdr.name = new StringBuffer( name );
2504                 hdr.mode = isDir ? 040755 : 0100644;
2505                 hdr.userId = 0;
2506                 hdr.groupId = 0;
2507                 hdr.size = 0;
2508                 hdr.checkSum = 0;
2509
2510                 hdr.modTime =
2511                         (new java.util.Date()).getTime() / 1000;
2512
2513                 hdr.linkFlag =
2514                         isDir ? TarHeader.LF_DIR : TarHeader.LF_NORMAL;
2515
2516                 hdr.linkName = new StringBuffer( "" );
2517                 hdr.userName = new StringBuffer( "" );
2518                 hdr.groupName = new StringBuffer( "" );
2519
2520                 hdr.devMajor = 0;
2521                 hdr.devMinor = 0;
2522                 }
2523
2524         public String
2525         toString()
2526                 {
2527                 StringBuffer result = new StringBuffer( 128 );
2528                 return result.
2529                         append( "[TarEntry name=" ).
2530                         append( this.getName() ).
2531                         append( ", isDir=" ).
2532                         append( this.isDirectory() ).
2533                         append( ", size=" ).
2534                         append( this.getSize() ).
2535                         append( ", userId=" ).
2536                         append( this.getUserId() ).
2537                         append( ", user=" ).
2538                         append( this.getUserName() ).
2539                         append( ", groupId=" ).
2540                         append( this.getGroupId() ).
2541                         append( ", group=" ).
2542                         append( this.getGroupName() ).
2543                         append( "]" ).
2544                         toString();
2545                 }
2546
2547         }
2548
2549 /*
2550 ** Tim feel free to integrate this code here.
2551 **
2552 ** This code has been placed into the Public Domain.
2553 ** This code was written by David M. Gaskin in 1999.
2554 **
2555 */
2556
2557 /**
2558  * Enumerate the contents of a "tar" file.
2559  *
2560  * Last updated 26th Mar 1999.
2561  *
2562  * @author  David. M. Gaskin.
2563  * @version Version 1.0 Mar 1999
2564  * @since    Version 1.0
2565  */
2566
2567 public
2568 static class            TarEntryEnumerator
2569 implements      Enumeration
2570         {
2571         /**
2572          * The instance on which the enumeration works.
2573          */
2574         private TarInputStream  tis = null;
2575
2576         /**
2577          * Has EndOfFile been reached?
2578          */
2579         private boolean                 eof = false;
2580
2581         /**
2582          * The read ahead entry (or <B><I>null</I></B> if no read ahead exists)
2583          */
2584         private TarEntry                readAhead = null;
2585
2586         /**
2587          * Construct an instance given a TarInputStream. This method is package
2588          * private because it is not initially forseen that an instance of this class
2589          * should be constructed from outside the package. Should it become necessary
2590          * to construct an instance of this class from outside the package in which it
2591          * exists then the constructor should be made <B>protected</B> and an empty
2592          * subclass should be written in the other package.
2593          *
2594          * @param <B>tis</B> the <B>TarInputStream</B> on which this enumeration has
2595          *  to be based.
2596          */
2597         public
2598         TarEntryEnumerator( TarInputStream tis )
2599                 {
2600                 this.tis      = tis;
2601                 eof           = false;
2602                 }
2603
2604         /**
2605          * Return the next element in the enumeration. This is a required method
2606          * for implementing <B>java.util.Enumeration</B>.
2607          *
2608          * @return the next Object in the enumeration
2609          * @exception <B>NoSuchElementException</B> should an attempt be made to
2610          *  read beyond EOF
2611          */
2612         public Object
2613         nextElement()
2614                 throws NoSuchElementException
2615                 {
2616                 if ( eof && ( readAhead == null ) )
2617                         throw new NoSuchElementException();
2618
2619                 TarEntry rc = null;
2620                 if ( readAhead != null )
2621                         {
2622                         rc        = readAhead;
2623                         readAhead = null;
2624                         }
2625                 else
2626                         {
2627                         rc = getNext();
2628                         }
2629
2630                 return rc;
2631                 }
2632
2633         /**
2634          * Return <B>true</B> if there are more elements in the enumeration.
2635          *
2636          * @return <B>true</B> if there are more elements in the enumeration.
2637          */
2638         public boolean
2639         hasMoreElements()
2640                 {
2641                 if (eof)
2642                         return false;
2643
2644                 boolean rc = false;
2645                 readAhead = getNext();
2646                 if ( readAhead != null )
2647                         rc = true;
2648
2649                 return rc;
2650                 }
2651
2652         /**
2653          * Return the next element of <B>null</B> if there is no next element or
2654          * if an error occured.
2655          *
2656          * @return the next element of <B>null</B> if there is no next element or
2657          * if an error occured.
2658          */
2659         private TarEntry
2660         getNext()
2661                 {
2662                 TarEntry rc = null;
2663                 try {
2664                         rc = tis.getNextEntry();
2665                         }
2666                 catch ( IOException ex )
2667                         {
2668                         // null will be returned but should not occur
2669                         ex.printStackTrace();
2670                         }
2671
2672                 if ( rc == null )
2673                         eof = true;
2674
2675                 return rc;
2676                 }
2677         }
2678 /*
2679 ** Contributed by "Bay" <bayard@generationjava.com>
2680 **
2681 ** This code has been placed into the public domain.
2682 */
2683
2684
2685 // we extend TarOutputStream to have the same type, 
2686 // BUT, we don't use ANY methods. It's all about 
2687 // typing.
2688
2689 /**
2690  * Outputs tar.gz files. Added functionality that it 
2691  * doesn't need to know the size of an entry. If an 
2692  * entry has zero size when it is put in the Tar, then 
2693  * it buffers it until it's closed and it knows the size.
2694  *
2695  * @author "Bay" <bayard@generationjava.com>
2696  */
2697
2698 public
2699 static class            TarGzOutputStream
2700 extends         TarOutputStream
2701         {
2702     private TarOutputStream                     tos = null;
2703     private GZIPOutputStream            gzip = null;
2704     private ByteArrayOutputStream       bos = null;
2705     private TarEntry                            currentEntry = null;
2706
2707         public
2708         TarGzOutputStream( OutputStream out )
2709                 throws IOException
2710                 {
2711                 super( null );
2712                 this.gzip = new GZIPOutputStream( out );
2713                 this.tos = new TarOutputStream( this.gzip );
2714                 this.bos = new ByteArrayOutputStream();
2715                 }
2716
2717         // proxy all methods, but buffer if unknown size
2718
2719         public void
2720         setDebug( boolean b )
2721                 {
2722                 this.tos.setDebug(b);
2723                 }
2724
2725         public void
2726         setBufferDebug( boolean b )
2727                 {
2728                 this.tos.setBufferDebug(b);
2729                 }
2730
2731         public void
2732         finish()
2733                 throws IOException
2734                 {
2735                 if ( this.currentEntry != null )
2736                         {
2737                         closeEntry();
2738                         }
2739
2740                 this.tos.finish();
2741                 }
2742
2743         public void
2744         close()
2745                 throws IOException
2746                 {
2747                 this.tos.close();
2748                 this.gzip.finish();
2749                 }
2750
2751         public int
2752         getRecordSize()
2753                 {
2754                 return this.tos.getRecordSize();
2755                 }
2756
2757         public void
2758         putNextEntry(TarEntry entry)
2759                 throws IOException
2760                 {
2761                 if ( entry.getSize() != 0 )
2762                         {
2763                         this.tos.putNextEntry( entry );
2764                         }
2765                 else
2766                         {
2767                         this.currentEntry = entry;
2768                         }
2769                 }
2770
2771         public void
2772         closeEntry()
2773                 throws IOException
2774                 {
2775                 if(this.currentEntry == null)
2776                         {
2777                         this.tos.closeEntry();
2778                         }
2779                 else
2780                         {
2781                         this.currentEntry.setSize( bos.size() );
2782                         this.tos.putNextEntry( this.currentEntry );
2783                         this.bos.writeTo( this.tos );
2784                         this.tos.closeEntry();
2785                         this.currentEntry = null;
2786                         this.bos = new ByteArrayOutputStream();
2787                         }
2788                 }
2789
2790         public void
2791         write( int b )
2792                 throws IOException
2793                 {
2794                 if ( this.currentEntry == null )
2795                         {
2796                         this.tos.write( b );
2797                         }
2798                 else
2799                         {
2800                         this.bos.write( b );
2801                         }
2802                 }
2803
2804         public void
2805         write( byte[] b )
2806                 throws IOException
2807                 {
2808                 if ( this.currentEntry == null )
2809                         {
2810                         this.tos.write( b );
2811                         }
2812                 else
2813                         {
2814                         this.bos.write( b );
2815                         }
2816                 }
2817
2818         public void
2819         write( byte[] b, int start, int length )
2820                 throws IOException
2821                 {
2822                 if ( this.currentEntry == null )
2823                         {
2824                         this.tos.write( b, start, length );
2825                         }
2826                 else
2827                         {
2828                         this.bos.write( b, start, length );
2829                         }
2830                 }
2831
2832         }
2833 /*
2834 ** Authored by Timothy Gerard Endres
2835 ** <mailto:time@gjt.org>  <http://www.trustice.com>
2836 ** 
2837 ** This work has been placed into the public domain.
2838 ** You may use this work in any way and for any purpose you wish.
2839 **
2840 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
2841 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
2842 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
2843 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
2844 ** REDISTRIBUTION OF THIS SOFTWARE. 
2845 ** 
2846 */
2847
2848 /**
2849  * This class encapsulates the Tar Entry Header used in Tar Archives.
2850  * The class also holds a number of tar constants, used mostly in headers.
2851  *
2852  * @author Timothy Gerard Endres, <time@gjt.org>
2853  */
2854
2855 public
2856 static class            TarHeader
2857 extends         Object
2858 implements      Cloneable
2859         {
2860         /**
2861          * The length of the name field in a header buffer.
2862          */
2863         public static final int         NAMELEN = 100;
2864         /**
2865          * The offset of the name field in a header buffer.
2866          */
2867         public static final int         NAMEOFFSET = 0;
2868         /**
2869          * The length of the name prefix field in a header buffer.
2870          */
2871         public static final int         PREFIXLEN = 155;
2872         /**
2873          * The offset of the name prefix field in a header buffer.
2874          */
2875         public static final int         PREFIXOFFSET = 345;
2876         /**
2877          * The length of the mode field in a header buffer.
2878          */
2879         public static final int         MODELEN = 8;
2880         /**
2881          * The length of the user id field in a header buffer.
2882          */
2883         public static final int         UIDLEN = 8;
2884         /**
2885          * The length of the group id field in a header buffer.
2886          */
2887         public static final int         GIDLEN = 8;
2888         /**
2889          * The length of the checksum field in a header buffer.
2890          */
2891         public static final int         CHKSUMLEN = 8;
2892         /**
2893          * The length of the size field in a header buffer.
2894          */
2895         public static final int         SIZELEN = 12;
2896         /**
2897          * The length of the magic field in a header buffer.
2898          */
2899         public static final int         MAGICLEN = 8;
2900         /**
2901          * The length of the modification time field in a header buffer.
2902          */
2903         public static final int         MODTIMELEN = 12;
2904         /**
2905          * The length of the user name field in a header buffer.
2906          */
2907         public static final int         UNAMELEN = 32;
2908         /**
2909          * The length of the group name field in a header buffer.
2910          */
2911         public static final int         GNAMELEN = 32;
2912         /**
2913          * The length of the devices field in a header buffer.
2914          */
2915         public static final int         DEVLEN = 8;
2916
2917         /**
2918          * LF_ constants represent the "link flag" of an entry, or more commonly,
2919          * the "entry type". This is the "old way" of indicating a normal file.
2920          */
2921         public static final byte        LF_OLDNORM      = 0;
2922         /**
2923          * Normal file type.
2924          */
2925         public static final byte        LF_NORMAL       = (byte) '0';
2926         /**
2927          * Link file type.
2928          */
2929         public static final byte        LF_LINK         = (byte) '1';
2930         /**
2931          * Symbolic link file type.
2932          */
2933         public static final byte        LF_SYMLINK      = (byte) '2';
2934         /**
2935          * Character device file type.
2936          */
2937         public static final byte        LF_CHR          = (byte) '3';
2938         /**
2939          * Block device file type.
2940          */
2941         public static final byte        LF_BLK          = (byte) '4';
2942         /**
2943          * Directory file type.
2944          */
2945         public static final byte        LF_DIR          = (byte) '5';
2946         /**
2947          * FIFO (pipe) file type.
2948          */
2949         public static final byte        LF_FIFO         = (byte) '6';
2950         /**
2951          * Contiguous file type.
2952          */
2953         public static final byte        LF_CONTIG       = (byte) '7';
2954
2955         /**
2956          * The magic tag representing a POSIX tar archive.
2957          */
2958         public static final String      TMAGIC          = "ustar";
2959
2960         /**
2961          * The magic tag representing a GNU tar archive.
2962          */
2963         public static final String      GNU_TMAGIC      = "ustar  ";
2964
2965         /**
2966          * The entry's name.
2967          */
2968         public StringBuffer             name;
2969         /**
2970          * The entry's permission mode.
2971          */
2972         public int                              mode;
2973         /**
2974          * The entry's user id.
2975          */
2976         public int                              userId;
2977         /**
2978          * The entry's group id.
2979          */
2980         public int                              groupId;
2981         /**
2982          * The entry's size.
2983          */
2984         public long                             size;
2985         /**
2986          * The entry's modification time.
2987          */
2988         public long                             modTime;
2989         /**
2990          * The entry's checksum.
2991          */
2992         public int                              checkSum;
2993         /**
2994          * The entry's link flag.
2995          */
2996         public byte                             linkFlag;
2997         /**
2998          * The entry's link name.
2999          */
3000         public StringBuffer             linkName;
3001         /**
3002          * The entry's magic tag.
3003          */
3004         public StringBuffer             magic;
3005         /**
3006          * The entry's user name.
3007          */
3008         public StringBuffer             userName;
3009         /**
3010          * The entry's group name.
3011          */
3012         public StringBuffer             groupName;
3013         /**
3014          * The entry's major device number.
3015          */
3016         public int                              devMajor;
3017         /**
3018          * The entry's minor device number.
3019          */
3020         public int                              devMinor;
3021
3022
3023         public
3024         TarHeader()
3025                 {
3026                 this.magic = new StringBuffer( TarHeader.TMAGIC );
3027
3028                 this.name = new StringBuffer();
3029                 this.linkName = new StringBuffer();
3030
3031                 String user =
3032                         System.getProperty( "user.name", "" );
3033
3034                 if ( user.length() > 31 )
3035                         user = user.substring( 0, 31 );
3036
3037                 this.userId = 0;
3038                 this.groupId = 0;
3039                 this.userName = new StringBuffer( user );
3040                 this.groupName = new StringBuffer( "" );
3041                 }
3042
3043         /**
3044          * TarHeaders can be cloned.
3045          */
3046         public Object
3047         clone()
3048                 {
3049                 TarHeader hdr = null;
3050
3051                 try {
3052                         hdr = (TarHeader) super.clone();
3053
3054                         hdr.name =
3055                                 (this.name == null ) ? null
3056                                         : new StringBuffer( this.name.toString() );
3057                         hdr.mode = this.mode;
3058                         hdr.userId = this.userId;
3059                         hdr.groupId = this.groupId;
3060                         hdr.size = this.size;
3061                         hdr.modTime = this.modTime;
3062                         hdr.checkSum = this.checkSum;
3063                         hdr.linkFlag = this.linkFlag;
3064                         hdr.linkName =
3065                                 (this.linkName == null ) ? null
3066                                         : new StringBuffer( this.linkName.toString() );
3067                         hdr.magic =
3068                                 (this.magic == null ) ? null
3069                                         : new StringBuffer( this.magic.toString() );
3070                         hdr.userName =
3071                                 (this.userName == null ) ? null
3072                                         : new StringBuffer( this.userName.toString() );
3073                         hdr.groupName =
3074                                 (this.groupName == null ) ? null
3075                                         : new StringBuffer( this.groupName.toString() );
3076                         hdr.devMajor = this.devMajor;
3077                         hdr.devMinor = this.devMinor;
3078                         }
3079                 catch ( CloneNotSupportedException ex )
3080                         {
3081                         ex.printStackTrace( System.err );
3082                         }
3083
3084                 return hdr;
3085                 }
3086
3087         /**
3088          * Get the name of this entry.
3089          *
3090          * @return Teh entry's name.
3091          */
3092         public String
3093         getName()
3094                 {
3095                 return this.name.toString();
3096                 }
3097
3098         /**
3099          * Parse an octal string from a header buffer. This is used for the
3100          * file permission mode value.
3101          *
3102          * @param header The header buffer from which to parse.
3103          * @param offset The offset into the buffer from which to parse.
3104          * @param length The number of header bytes to parse.
3105          * @return The long value of the octal string.
3106          */
3107         public static long
3108         parseOctal( byte[] header, int offset, int length )
3109                 throws InvalidHeaderException
3110                 {
3111                 long result = 0;
3112                 boolean stillPadding = true;
3113
3114                 int end = offset + length;
3115                 for ( int i = offset ; i < end ; ++i )
3116                         {
3117                         if ( header[i] == 0 )
3118                                 break;
3119
3120                         if ( header[i] == (byte) ' ' || header[i] == '0' )
3121                                 {
3122                                 if ( stillPadding )
3123                                         continue;
3124
3125                                 if ( header[i] == (byte) ' ' )
3126                                         break;
3127                                 }
3128                         
3129                         stillPadding = false;
3130
3131                         result =
3132                                 (result << 3)
3133                                         + (header[i] - '0');
3134                         }
3135
3136                 return result;
3137                 }
3138
3139         /**
3140          * Parse a file name from a header buffer. This is different from
3141          * parseName() in that is recognizes 'ustar' names and will handle
3142          * adding on the "prefix" field to the name.
3143          *
3144          * Contributed by Dmitri Tikhonov <dxt2431@yahoo.com>
3145          *
3146          * @param header The header buffer from which to parse.
3147          * @param offset The offset into the buffer from which to parse.
3148          * @param length The number of header bytes to parse.
3149          * @return The header's entry name.
3150          */
3151         public static StringBuffer
3152         parseFileName( byte[] header )
3153                 {
3154                 StringBuffer result = new StringBuffer( 256 );
3155
3156                 // If header[345] is not equal to zero, then it is the "prefix"
3157                 // that 'ustar' defines. It must be prepended to the "normal"
3158                 // name field. We are responsible for the separating '/'.
3159                 //
3160                 if ( header[345] != 0 )
3161                         {
3162                         for ( int i = 345 ; i < 500 && header[i] != 0 ; ++i )
3163                                 {
3164                                 result.append( (char)header[i] );
3165                                 }
3166
3167                         result.append( "/" );
3168                         }
3169
3170                 for ( int i = 0 ; i < 100 && header[i] != 0 ; ++i )
3171                         {
3172                         result.append( (char)header[i] );
3173                         }
3174
3175                 return result;
3176                 }
3177
3178         /**
3179          * Parse an entry name from a header buffer.
3180          *
3181          * @param header The header buffer from which to parse.
3182          * @param offset The offset into the buffer from which to parse.
3183          * @param length The number of header bytes to parse.
3184          * @return The header's entry name.
3185          */
3186         public static StringBuffer
3187         parseName( byte[] header, int offset, int length )
3188                 throws InvalidHeaderException
3189                 {
3190                 StringBuffer result = new StringBuffer( length );
3191
3192                 int end = offset + length;
3193                 for ( int i = offset ; i < end ; ++i )
3194                         {
3195                         if ( header[i] == 0 )
3196                                 break;
3197                         result.append( (char)header[i] );
3198                         }
3199
3200                 return result;
3201                 }
3202
3203         /**
3204          * This method, like getNameBytes(), is intended to place a name
3205          * into a TarHeader's buffer. However, this method is sophisticated
3206          * enough to recognize long names (name.length() > NAMELEN). In these
3207          * cases, the method will break the name into a prefix and suffix and
3208          * place the name in the header in 'ustar' format. It is up to the
3209          * TarEntry to manage the "entry header format". This method assumes
3210          * the name is valid for the type of archive being generated.
3211          *
3212          * @param outbuf The buffer containing the entry header to modify.
3213          * @param newName The new name to place into the header buffer.
3214          * @return The current offset in the tar header (always TarHeader.NAMELEN).
3215          * @throws InvalidHeaderException If the name will not fit in the header.
3216          */
3217         public static int
3218         getFileNameBytes( String newName, byte[] outbuf )
3219                 throws InvalidHeaderException
3220                 {
3221                 if ( newName.length() > 100 )
3222                         {
3223                         // Locate a pathname "break" prior to the maximum name length...
3224                         int index = newName.indexOf( "/", newName.length() - 100 );
3225                         if ( index == -1 )
3226                                 throw new InvalidHeaderException
3227                                         ( "file name is greater than 100 characters, " + newName );
3228
3229                         // Get the "suffix subpath" of the name.
3230                         String name = newName.substring( index + 1 );
3231
3232                         // Get the "prefix subpath", or "prefix", of the name.
3233                         String prefix = newName.substring( 0, index );
3234                         if ( prefix.length() > TarHeader.PREFIXLEN )
3235                                 throw new InvalidHeaderException
3236                                         ( "file prefix is greater than 155 characters" );
3237
3238                         TarHeader.getNameBytes
3239                                 ( new StringBuffer( name ), outbuf,
3240                                         TarHeader.NAMEOFFSET, TarHeader.NAMELEN );
3241
3242                         TarHeader.getNameBytes
3243                                 ( new StringBuffer( prefix ), outbuf,
3244                                         TarHeader.PREFIXOFFSET, TarHeader.PREFIXLEN );
3245                         }
3246                 else
3247                         {
3248                         TarHeader.getNameBytes
3249                                 ( new StringBuffer( newName ), outbuf,
3250                                         TarHeader.NAMEOFFSET, TarHeader.NAMELEN );
3251                         }
3252
3253                 // The offset, regardless of the format, is now the end of the
3254                 // original name field.
3255                 //
3256                 return TarHeader.NAMELEN;
3257                 }
3258
3259         /**
3260          * Move the bytes from the name StringBuffer into the header's buffer.
3261          *
3262          * @param header The header buffer into which to copy the name.
3263          * @param offset The offset into the buffer at which to store.
3264          * @param length The number of header bytes to store.
3265          * @return The new offset (offset + length).
3266          */
3267         public static int
3268         getNameBytes( StringBuffer name, byte[] buf, int offset, int length )
3269                 {
3270                 int i;
3271
3272                 for ( i = 0 ; i < length && i < name.length() ; ++i )
3273                         {
3274                         buf[ offset + i ] = (byte) name.charAt( i );
3275                         }
3276
3277                 for ( ; i < length ; ++i )
3278                         {
3279                         buf[ offset + i ] = 0;
3280                         }
3281
3282                 return offset + length;
3283                 }
3284
3285         /**
3286          * Parse an octal integer from a header buffer.
3287          *
3288          * @param header The header buffer from which to parse.
3289          * @param offset The offset into the buffer from which to parse.
3290          * @param length The number of header bytes to parse.
3291          * @return The integer value of the octal bytes.
3292          */
3293         public static int
3294         getOctalBytes( long value, byte[] buf, int offset, int length )
3295                 {
3296                 byte[] result = new byte[ length ];
3297
3298                 int idx = length - 1;
3299
3300                 buf[ offset + idx ] = 0;
3301                 --idx;
3302                 buf[ offset + idx ] = (byte) ' ';
3303                 --idx;
3304
3305                 if ( value == 0 )
3306                         {
3307                         buf[ offset + idx ] = (byte) '0';
3308                         --idx;
3309                         }
3310                 else
3311                         {
3312                         for ( long val = value ; idx >= 0 && val > 0 ; --idx )
3313                                 {
3314                                 buf[ offset + idx ] = (byte)
3315                                         ( (byte) '0' + (byte) (val & 7) );
3316                                 val = val >> 3;
3317                                 }
3318                         }
3319
3320                 for ( ; idx >= 0 ; --idx )
3321                         {
3322                         buf[ offset + idx ] = (byte) ' ';
3323                         }
3324
3325                 return offset + length;
3326                 }
3327
3328         /**
3329          * Parse an octal long integer from a header buffer.
3330          *
3331          * @param header The header buffer from which to parse.
3332          * @param offset The offset into the buffer from which to parse.
3333          * @param length The number of header bytes to parse.
3334          * @return The long value of the octal bytes.
3335          */
3336         public static int
3337         getLongOctalBytes( long value, byte[] buf, int offset, int length )
3338                 {
3339                 byte[] temp = new byte[ length + 1 ];
3340                 TarHeader.getOctalBytes( value, temp, 0, length + 1 );
3341                 System.arraycopy( temp, 0, buf, offset, length );
3342                 return offset + length;
3343                 }
3344
3345         /**
3346          * Parse the checksum octal integer from a header buffer.
3347          *
3348          * @param header The header buffer from which to parse.
3349          * @param offset The offset into the buffer from which to parse.
3350          * @param length The number of header bytes to parse.
3351          * @return The integer value of the entry's checksum.
3352          */
3353         public static int
3354         getCheckSumOctalBytes( long value, byte[] buf, int offset, int length )
3355                 {
3356                 TarHeader.getOctalBytes( value, buf, offset, length );
3357                 buf[ offset + length - 1 ] = (byte) ' ';
3358                 buf[ offset + length - 2 ] = 0;
3359                 return offset + length;
3360                 }
3361
3362         }
3363  
3364 /*
3365 ** Authored by Timothy Gerard Endres
3366 ** <mailto:time@gjt.org>  <http://www.trustice.com>
3367 ** 
3368 ** This work has been placed into the public domain.
3369 ** You may use this work in any way and for any purpose you wish.
3370 **
3371 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
3372 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
3373 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
3374 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
3375 ** REDISTRIBUTION OF THIS SOFTWARE. 
3376 ** 
3377 */
3378
3379
3380 /**
3381  * The TarInputStream reads a UNIX tar archive as an InputStream.
3382  * methods are provided to position at each successive entry in
3383  * the archive, and the read each entry as a normal input stream
3384  * using read().
3385  *
3386  * Kerry Menzel <kmenzel@cfl.rr.com> Contributed the code to support
3387  * file sizes greater than 2GB (longs versus ints).
3388  *
3389  *
3390  * @version $Revision: 1.9 $
3391  * @author Timothy Gerard Endres, <time@gjt.org>
3392  * @see TarBuffer
3393  * @see TarHeader
3394  * @see TarEntry
3395  */
3396
3397
3398 public
3399 static class            TarInputStream
3400 extends         FilterInputStream
3401         {
3402         protected boolean                       debug;
3403         protected boolean                       hasHitEOF;
3404
3405         protected long                          entrySize;
3406         protected long                          entryOffset;
3407
3408         protected byte[]                        oneBuf;
3409         protected byte[]                        readBuf;
3410
3411         protected TarBuffer                     buffer;
3412
3413         protected TarEntry                      currEntry;
3414
3415         protected EntryFactory          eFactory;
3416
3417
3418         public
3419         TarInputStream( InputStream is )
3420                 {
3421                 this( is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE );
3422                 }
3423
3424         public
3425         TarInputStream( InputStream is, int blockSize )
3426                 {
3427                 this( is, blockSize, TarBuffer.DEFAULT_RCDSIZE );
3428                 }
3429
3430         public
3431         TarInputStream( InputStream is, int blockSize, int recordSize )
3432                 {
3433                 super( is );
3434
3435                 this.buffer = new TarBuffer( is, blockSize, recordSize );
3436
3437                 this.readBuf = null;
3438                 this.oneBuf = new byte[1];
3439                 this.debug = false;
3440                 this.hasHitEOF = false;
3441                 this.eFactory = null;
3442                 }
3443
3444         /**
3445          * Sets the debugging flag.
3446          *
3447          * @param debugF True to turn on debugging.
3448          */
3449         public void
3450         setDebug( boolean debugF )
3451                 {
3452                 this.debug = debugF;
3453                 }
3454
3455         /**
3456          * Sets the debugging flag.
3457          *
3458          * @param debugF True to turn on debugging.
3459          */
3460         public void
3461         setEntryFactory( EntryFactory factory )
3462                 {
3463                 this.eFactory = factory;
3464                 }
3465
3466         /**
3467          * Sets the debugging flag in this stream's TarBuffer.
3468          *
3469          * @param debugF True to turn on debugging.
3470          */
3471         public void
3472         setBufferDebug( boolean debug )
3473                 {
3474                 this.buffer.setDebug( debug );
3475                 }
3476
3477         /**
3478          * Closes this stream. Calls the TarBuffer's close() method.
3479          */
3480         public void
3481         close()
3482                 throws IOException
3483                 {
3484                 this.buffer.close();
3485                 }
3486
3487         /**
3488          * Get the record size being used by this stream's TarBuffer.
3489          *
3490          * @return The TarBuffer record size.
3491          */
3492         public int
3493         getRecordSize()
3494                 {
3495                 return this.buffer.getRecordSize();
3496                 }
3497
3498         /**
3499          * Get the available data that can be read from the current
3500          * entry in the archive. This does not indicate how much data
3501          * is left in the entire archive, only in the current entry.
3502          * This value is determined from the entry's size header field
3503          * and the amount of data already read from the current entry.
3504          * 
3505          *
3506          * @return The number of available bytes for the current entry.
3507          */
3508         public int
3509         available()
3510                 throws IOException
3511                 {
3512                 return (int)(this.entrySize - this.entryOffset);
3513                 }
3514
3515         /**
3516          * Skip bytes in the input buffer. This skips bytes in the
3517          * current entry's data, not the entire archive, and will
3518          * stop at the end of the current entry's data if the number
3519          * to skip extends beyond that point.
3520          *
3521          * @param numToSkip The number of bytes to skip.
3522          * @return The actual number of bytes skipped.
3523          */
3524         public long
3525         skip( long numToSkip )
3526                 throws IOException
3527                 {
3528                 // REVIEW
3529                 // This is horribly inefficient, but it ensures that we
3530                 // properly skip over bytes via the TarBuffer...
3531                 //
3532
3533                 byte[] skipBuf = new byte[ 8 * 1024 ];
3534         long num = numToSkip;
3535                 for ( ; num > 0 ; )
3536                         {
3537                         int numRead =
3538                                 this.read( skipBuf, 0,
3539                                         ( num > skipBuf.length ? skipBuf.length : (int) num ) );
3540
3541                         if ( numRead == -1 )
3542                                 break;
3543
3544                         num -= numRead;
3545                         }
3546
3547                 return ( numToSkip - num );
3548                 }
3549
3550         /**
3551          * Since we do not support marking just yet, we return false.
3552          *
3553          * @return False.
3554          */
3555         public boolean
3556         markSupported()
3557                 {
3558                 return false;
3559                 }
3560
3561         /**
3562          * Since we do not support marking just yet, we do nothing.
3563          *
3564          * @param markLimit The limit to mark.
3565          */
3566         public void
3567         mark( int markLimit )
3568                 {
3569                 }
3570
3571         /**
3572          * Since we do not support marking just yet, we do nothing.
3573          */
3574         public void
3575         reset()
3576                 {
3577                 }
3578
3579         /**
3580          * Get the number of bytes into the current TarEntry.
3581          * This method returns the number of bytes that have been read
3582          * from the current TarEntry's data.
3583          *
3584          * @returns The current entry offset.
3585          */
3586
3587         public long
3588         getEntryPosition()
3589                 {
3590                 return this.entryOffset;
3591                 }
3592
3593         /**
3594          * Get the number of bytes into the stream we are currently at.
3595          * This method accounts for the blocking stream that tar uses,
3596          * so it represents the actual position in input stream, as
3597          * opposed to the place where the tar archive parsing is.
3598          *
3599          * @returns The current file pointer.
3600          */
3601
3602         public long
3603         getStreamPosition()
3604                 {
3605                 return ( buffer.getBlockSize() * buffer.getCurrentBlockNum() )
3606                                         + buffer.getCurrentRecordNum();
3607                 }
3608
3609         /**
3610          * Get the next entry in this tar archive. This will skip
3611          * over any remaining data in the current entry, if there
3612          * is one, and place the input stream at the header of the
3613          * next entry, and read the header and instantiate a new
3614          * TarEntry from the header bytes and return that entry.
3615          * If there are no more entries in the archive, null will
3616          * be returned to indicate that the end of the archive has
3617          * been reached.
3618          *
3619          * @return The next TarEntry in the archive, or null.
3620          */
3621         public TarEntry
3622         getNextEntry()
3623                 throws IOException
3624                 {
3625                 if ( this.hasHitEOF )
3626                         return null;
3627
3628                 if ( this.currEntry != null )
3629                         {
3630                         long numToSkip = (this.entrySize - this.entryOffset);
3631
3632                         if ( this.debug )
3633                         System.err.println
3634                                 ( "TarInputStream: SKIP currENTRY '"
3635                                 + this.currEntry.getName() + "' SZ "
3636                                 + this.entrySize + " OFF " + this.entryOffset
3637                                 + "  skipping " + numToSkip + " bytes" );
3638
3639                         if ( numToSkip > 0 )
3640                                 {
3641                                 this.skip( numToSkip );
3642                                 }
3643
3644                         this.readBuf = null;
3645                         }
3646
3647                 byte[] headerBuf = this.buffer.readRecord();
3648
3649                 if ( headerBuf == null )
3650                         {
3651                         if ( this.debug )
3652                                 {
3653                                 System.err.println( "READ NULL RECORD" );
3654                                 }
3655
3656                         this.hasHitEOF = true;
3657                         }
3658                 else if ( this.buffer.isEOFRecord( headerBuf ) )
3659                         {
3660                         if ( this.debug )
3661                                 {
3662                                 System.err.println( "READ EOF RECORD" );
3663                                 }
3664
3665                         this.hasHitEOF = true;
3666                         }
3667
3668                 if ( this.hasHitEOF )
3669                         {
3670                         this.currEntry = null;
3671                         }
3672                 else
3673                         {
3674                         try {
3675                                 if ( this.eFactory == null )
3676                                         {
3677                                         this.currEntry = new TarEntry( headerBuf );
3678                                         }
3679                                 else
3680                                         {
3681                                         this.currEntry =
3682                                                 this.eFactory.createEntry( headerBuf );
3683                                         }
3684
3685                                 if ( this.debug )
3686                                 System.err.println
3687                                         ( "TarInputStream: SET CURRENTRY '"
3688                                                 + this.currEntry.getName()
3689                                                 + "' size = " + this.currEntry.getSize() );
3690
3691                                 this.entryOffset = 0;
3692                                 this.entrySize = this.currEntry.getSize();
3693                                 }
3694                         catch ( InvalidHeaderException ex )
3695                                 {
3696                                 this.entrySize = 0;
3697                                 this.entryOffset = 0;
3698                                 this.currEntry = null;
3699                                 throw new InvalidHeaderException
3700                                         ( "bad header in block "
3701                                                 + this.buffer.getCurrentBlockNum()
3702                                                 + " record "
3703                                                 + this.buffer.getCurrentRecordNum()
3704                                                 + ", " + ex.getMessage() );
3705                                 }
3706                         }
3707
3708                 return this.currEntry;
3709                 }
3710
3711         /**
3712          * Reads a byte from the current tar archive entry.
3713          *
3714          * This method simply calls read( byte[], int, int ).
3715          *
3716          * @return The byte read, or -1 at EOF.
3717          */
3718         public int
3719         read()
3720                 throws IOException
3721                 {
3722                 int num = this.read( this.oneBuf, 0, 1 );
3723                 if ( num == -1 )
3724                         return num;
3725                 else
3726                         return (int) this.oneBuf[0];
3727                 }
3728
3729         /**
3730          * Reads bytes from the current tar archive entry.
3731          *
3732          * This method simply calls read( byte[], int, int ).
3733          *
3734          * @param buf The buffer into which to place bytes read.
3735          * @return The number of bytes read, or -1 at EOF.
3736          */
3737         public int
3738         read( byte[] buf )
3739                 throws IOException
3740                 {
3741                 return this.read( buf, 0, buf.length );
3742                 }
3743
3744         /**
3745          * Reads bytes from the current tar archive entry.
3746          *
3747          * This method is aware of the boundaries of the current
3748          * entry in the archive and will deal with them as if they
3749          * were this stream's start and EOF.
3750          *
3751          * @param buf The buffer into which to place bytes read.
3752          * @param offset The offset at which to place bytes read.
3753          * @param numToRead The number of bytes to read.
3754          * @return The number of bytes read, or -1 at EOF.
3755          */
3756         public int
3757         read( byte[] buf, int offset, int numToRead )
3758                 throws IOException
3759                 {
3760                 int totalRead = 0;
3761
3762                 if ( this.entryOffset >= this.entrySize )
3763                         return -1;
3764
3765                 if ( (numToRead + this.entryOffset) > this.entrySize )
3766                         {
3767                         numToRead = (int) (this.entrySize - this.entryOffset);
3768                         }
3769
3770                 if ( this.readBuf != null )
3771                         {
3772                         int sz = ( numToRead > this.readBuf.length )
3773                                                 ? this.readBuf.length : numToRead;
3774
3775                         System.arraycopy( this.readBuf, 0, buf, offset, sz );
3776
3777                         if ( sz >= this.readBuf.length )
3778                                 {
3779                                 this.readBuf = null;
3780                                 }
3781                         else
3782                                 {
3783                                 int newLen = this.readBuf.length - sz;
3784                                 byte[] newBuf = new byte[ newLen ];
3785                                 System.arraycopy( this.readBuf, sz, newBuf, 0, newLen );
3786                                 this.readBuf = newBuf;
3787                                 }
3788
3789                         totalRead += sz;
3790                         numToRead -= sz;
3791                         offset += sz;
3792                         }
3793
3794                 for ( ; numToRead > 0 ; )
3795                         {
3796                         byte[] rec = this.buffer.readRecord();
3797                         if ( rec == null )
3798                                 {
3799                                 // Unexpected EOF!
3800                                 throw new IOException
3801                                         ( "unexpected EOF with " + numToRead + " bytes unread" );
3802                                 }
3803
3804                         int sz = numToRead;
3805                         int recLen = rec.length;
3806
3807                         if ( recLen > sz )
3808                                 {
3809                                 System.arraycopy( rec, 0, buf, offset, sz );
3810                                 this.readBuf = new byte[ recLen - sz ];
3811                                 System.arraycopy( rec, sz, this.readBuf, 0, recLen - sz );
3812                                 }
3813                         else
3814                                 {
3815                                 sz = recLen;
3816                                 System.arraycopy( rec, 0, buf, offset, recLen );
3817                                 }
3818
3819                         totalRead += sz;
3820                         numToRead -= sz;
3821                         offset += sz;
3822                         }
3823
3824                 this.entryOffset += totalRead;
3825
3826                 return totalRead;
3827                 }
3828
3829         /**
3830          * Copies the contents of the current tar archive entry directly into
3831          * an output stream.
3832          *
3833          * @param out The OutputStream into which to write the entry's data.
3834          */
3835         public void
3836         copyEntryContents( OutputStream out )
3837                 throws IOException
3838                 {
3839                 byte[] buf = new byte[ 32 * 1024 ];
3840
3841                 for ( ; ; )
3842                         {
3843                         int numRead = this.read( buf, 0, buf.length );
3844                         if ( numRead == -1 )
3845                                 break;
3846                         out.write( buf, 0, numRead );
3847                         }
3848                 }
3849
3850         /**
3851          * This interface is provided, with the method setEntryFactory(), to allow
3852          * the programmer to have their own TarEntry subclass instantiated for the
3853          * entries return from getNextEntry().
3854          */
3855
3856         public
3857         interface       EntryFactory
3858                 {
3859                 public TarEntry
3860                         createEntry( String name );
3861
3862                 public TarEntry
3863                         createEntry( File path )
3864                                 throws InvalidHeaderException;
3865
3866                 public TarEntry
3867                         createEntry( byte[] headerBuf )
3868                                 throws InvalidHeaderException;
3869                 }
3870
3871         public
3872         class           EntryAdapter
3873         implements      EntryFactory
3874                 {
3875                 public TarEntry
3876                 createEntry( String name )
3877                         {
3878                         return new TarEntry( name );
3879                         }
3880
3881                 public TarEntry
3882                 createEntry( File path )
3883                         throws InvalidHeaderException
3884                         {
3885                         return new TarEntry( path );
3886                         }
3887
3888                 public TarEntry
3889                 createEntry( byte[] headerBuf )
3890                         throws InvalidHeaderException
3891                         {
3892                         return new TarEntry( headerBuf );
3893                         }
3894                 }
3895
3896         }
3897
3898
3899 /*
3900 ** Authored by Timothy Gerard Endres
3901 ** <mailto:time@gjt.org>  <http://www.trustice.com>
3902 ** 
3903 ** This work has been placed into the public domain.
3904 ** You may use this work in any way and for any purpose you wish.
3905 **
3906 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
3907 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
3908 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
3909 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
3910 ** REDISTRIBUTION OF THIS SOFTWARE. 
3911 ** 
3912 */
3913
3914
3915 /**
3916  * The TarOutputStream writes a UNIX tar archive as an OutputStream.
3917  * Methods are provided to put entries, and then write their contents
3918  * by writing to this stream using write().
3919  *
3920  * Kerry Menzel <kmenzel@cfl.rr.com> Contributed the code to support
3921  * file sizes greater than 2GB (longs versus ints).
3922  *
3923  * @version $Revision: 1.8 $
3924  * @author Timothy Gerard Endres, <time@gjt.org>
3925  * @see TarBuffer
3926  * @see TarHeader
3927  * @see TarEntry
3928  */
3929
3930
3931 public
3932 static class            TarOutputStream
3933 extends         FilterOutputStream
3934         {
3935         protected boolean                       debug;
3936         protected long                          currSize;
3937         protected long                          currBytes;
3938         protected byte[]                        oneBuf;
3939         protected byte[]                        recordBuf;
3940         protected int                           assemLen;
3941         protected byte[]                        assemBuf;
3942         protected TarBuffer                     buffer;
3943
3944
3945         public
3946         TarOutputStream( OutputStream os )
3947                 {
3948                 this( os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE );
3949                 }
3950
3951         public
3952         TarOutputStream( OutputStream os, int blockSize )
3953                 {
3954                 this( os, blockSize, TarBuffer.DEFAULT_RCDSIZE );
3955                 }
3956
3957         public
3958         TarOutputStream( OutputStream os, int blockSize, int recordSize )
3959                 {
3960                 super( os );
3961
3962                 this.buffer = new TarBuffer( os, blockSize, recordSize );
3963                 
3964                 this.debug = false;
3965                 this.assemLen = 0;
3966                 this.assemBuf = new byte[ recordSize ];
3967                 this.recordBuf = new byte[ recordSize ];
3968                 this.oneBuf = new byte[1];
3969                 }
3970
3971         /**
3972          * Sets the debugging flag.
3973          *
3974          * @param debugF True to turn on debugging.
3975          */
3976         public void
3977         setDebug( boolean debugF )
3978                 {
3979                 this.debug = debugF;
3980                 }
3981
3982         /**
3983          * Sets the debugging flag in this stream's TarBuffer.
3984          *
3985          * @param debugF True to turn on debugging.
3986          */
3987         public void
3988         setBufferDebug( boolean debug )
3989                 {
3990                 this.buffer.setDebug( debug );
3991                 }
3992
3993         /**
3994          * Ends the TAR archive without closing the underlying OutputStream.
3995          * The result is that the EOF record of nulls is written.
3996          */
3997
3998         public void
3999         finish()
4000                 throws IOException
4001                 {
4002                 this.writeEOFRecord();
4003                 }
4004
4005         /**
4006          * Ends the TAR archive and closes the underlying OutputStream.
4007          * This means that finish() is called followed by calling the
4008          * TarBuffer's close().
4009          */
4010
4011         public void
4012         close()
4013                 throws IOException
4014                 {
4015                 this.finish();
4016                 this.buffer.close();
4017                 }
4018
4019         /**
4020          * Get the record size being used by this stream's TarBuffer.
4021          *
4022          * @return The TarBuffer record size.
4023          */
4024         public int
4025         getRecordSize()
4026                 {
4027                 return this.buffer.getRecordSize();
4028                 }
4029
4030         /**
4031          * Put an entry on the output stream. This writes the entry's
4032          * header record and positions the output stream for writing
4033          * the contents of the entry. Once this method is called, the
4034          * stream is ready for calls to write() to write the entry's
4035          * contents. Once the contents are written, closeEntry()
4036          * <B>MUST</B> be called to ensure that all buffered data
4037          * is completely written to the output stream.
4038          *
4039          * @param entry The TarEntry to be written to the archive.
4040          */
4041         public void
4042         putNextEntry( TarEntry entry )
4043                 throws IOException
4044                 {
4045                 StringBuffer name = entry.getHeader().name;
4046
4047                 // NOTE
4048                 // This check is not adequate, because the maximum file length that
4049                 // can be placed into a POSIX (ustar) header depends on the precise
4050                 // locations of the path elements (slashes) within the file's full
4051                 // pathname. For this reason, writeEntryHeader() can still throw an
4052                 // InvalidHeaderException if the file's full pathname will not fit
4053                 // in the header.
4054
4055                 if (    ( entry.isUnixTarFormat()
4056                                         && name.length() > TarHeader.NAMELEN )
4057                         ||
4058                                 ( ! entry.isUnixTarFormat()
4059                                         && name.length() > (TarHeader.NAMELEN + TarHeader.PREFIXLEN) )
4060                         )
4061                         {
4062                         throw new InvalidHeaderException
4063                                 ( "file name '"
4064                                         + name
4065                                         + "' is too long ( "
4066                                         + name.length()
4067                                         + " > "
4068                                         + ( entry.isUnixTarFormat()
4069                                                 ? TarHeader.NAMELEN
4070                                                 : (TarHeader.NAMELEN + TarHeader.PREFIXLEN) )
4071                                         + " bytes )" );
4072                         }
4073
4074                 entry.writeEntryHeader( this.recordBuf );
4075
4076                 this.buffer.writeRecord( this.recordBuf );
4077
4078                 this.currBytes = 0;
4079
4080                 if ( entry.isDirectory() )
4081                         this.currSize = 0;
4082                 else
4083                         this.currSize = entry.getSize();
4084                 }
4085
4086         /**
4087          * Close an entry. This method MUST be called for all file
4088          * entries that contain data. The reason is that we must
4089          * buffer data written to the stream in order to satisfy
4090          * the buffer's record based writes. Thus, there may be
4091          * data fragments still being assembled that must be written
4092          * to the output stream before this entry is closed and the
4093          * next entry written.
4094          */
4095         public void
4096         closeEntry()
4097                 throws IOException
4098                 {
4099                 if ( this.assemLen > 0 )
4100                         {
4101                         for ( int i = this.assemLen ; i < this.assemBuf.length ; ++i )
4102                                 this.assemBuf[i] = 0;
4103
4104                         this.buffer.writeRecord( this.assemBuf );
4105
4106                         this.currBytes += this.assemLen;
4107                         this.assemLen = 0;
4108                         }
4109
4110                 if ( this.currBytes < this.currSize )
4111                         throw new IOException
4112                                 ( "entry closed at '" + this.currBytes
4113                                         + "' before the '" + this.currSize
4114                                         + "' bytes specified in the header were written" );
4115                 }
4116
4117         /**
4118          * Writes a byte to the current tar archive entry.
4119          *
4120          * This method simply calls read( byte[], int, int ).
4121          *
4122          * @param b The byte written.
4123          */
4124         public void
4125         write( int b )
4126                 throws IOException
4127                 {
4128                 this.oneBuf[0] = (byte) b;
4129                 this.write( this.oneBuf, 0, 1 );
4130                 }
4131
4132         /**
4133          * Writes bytes to the current tar archive entry.
4134          *
4135          * This method simply calls read( byte[], int, int ).
4136          *
4137          * @param wBuf The buffer to write to the archive.
4138          * @return The number of bytes read, or -1 at EOF.
4139          */
4140         public void
4141         write( byte[] wBuf )
4142                 throws IOException
4143                 {
4144                 this.write( wBuf, 0, wBuf.length );
4145                 }
4146
4147         /**
4148          * Writes bytes to the current tar archive entry. This method
4149          * is aware of the current entry and will throw an exception if
4150          * you attempt to write bytes past the length specified for the
4151          * current entry. The method is also (painfully) aware of the
4152          * record buffering required by TarBuffer, and manages buffers
4153          * that are not a multiple of recordsize in length, including
4154          * assembling records from small buffers.
4155          *
4156          * This method simply calls read( byte[], int, int ).
4157          *
4158          * @param wBuf The buffer to write to the archive.
4159          * @param wOffset The offset in the buffer from which to get bytes.
4160          * @param numToWrite The number of bytes to write.
4161          */
4162         public void
4163         write( byte[] wBuf, int wOffset, int numToWrite )
4164                 throws IOException
4165                 {
4166                 if ( (this.currBytes + numToWrite) > this.currSize )
4167                         throw new IOException
4168                                 ( "request to write '" + numToWrite
4169                                         + "' bytes exceeds size in header of '"
4170                                         + this.currSize + "' bytes" );
4171
4172                 //
4173                 // We have to deal with assembly!!!
4174                 // The programmer can be writing little 32 byte chunks for all
4175                 // we know, and we must assemble complete records for writing.
4176                 // REVIEW Maybe this should be in TarBuffer? Could that help to
4177                 //        eliminate some of the buffer copying.
4178                 //
4179                 if ( this.assemLen > 0 )
4180                         {
4181                         if ( (this.assemLen + numToWrite ) >= this.recordBuf.length )
4182                                 {
4183                                 int aLen = this.recordBuf.length - this.assemLen;
4184
4185                                 System.arraycopy
4186                                         ( this.assemBuf, 0, this.recordBuf, 0, this.assemLen );
4187
4188                                 System.arraycopy
4189                                         ( wBuf, wOffset, this.recordBuf, this.assemLen, aLen );
4190
4191                                 this.buffer.writeRecord( this.recordBuf );
4192
4193                                 this.currBytes += this.recordBuf.length;
4194
4195                                 wOffset += aLen;
4196                                 numToWrite -= aLen;
4197                                 this.assemLen = 0;
4198                                 }
4199                         else // ( (this.assemLen + numToWrite ) < this.recordBuf.length )
4200                                 {
4201                                 System.arraycopy
4202                                         ( wBuf, wOffset, this.assemBuf,
4203                                                 this.assemLen, numToWrite );
4204                                 wOffset += numToWrite;
4205                                 this.assemLen += numToWrite; 
4206                                 numToWrite -= numToWrite;
4207                                 }
4208                         }
4209
4210                 //
4211                 // When we get here we have EITHER:
4212                 //   o An empty "assemble" buffer.
4213                 //   o No bytes to write (numToWrite == 0)
4214                 //
4215
4216                 for ( ; numToWrite > 0 ; )
4217                         {
4218                         if ( numToWrite < this.recordBuf.length )
4219                                 {
4220                                 System.arraycopy
4221                                         ( wBuf, wOffset, this.assemBuf, this.assemLen, numToWrite );
4222                                 this.assemLen += numToWrite;
4223                                 break;
4224                                 }
4225
4226                         this.buffer.writeRecord( wBuf, wOffset );
4227
4228                         long num = this.recordBuf.length;
4229                         this.currBytes += num;
4230                         numToWrite -= num;
4231                         wOffset += num;
4232                         }
4233                 }
4234
4235         /**
4236          * Write an EOF (end of archive) record to the tar archive.
4237          * An EOF record consists of a record of all zeros.
4238          */
4239         private void
4240         writeEOFRecord()
4241                 throws IOException
4242                 {
4243                 for ( int i = 0 ; i < this.recordBuf.length ; ++i )
4244                         this.recordBuf[i] = 0;
4245                 this.buffer.writeRecord( this.recordBuf );
4246                 }
4247
4248         }
4249
4250 /*
4251 ** Authored by Timothy Gerard Endres
4252 ** <mailto:time@gjt.org>  <http://www.trustice.com>
4253 ** 
4254 ** This work has been placed into the public domain.
4255 ** You may use this work in any way and for any purpose you wish.
4256 **
4257 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
4258 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
4259 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
4260 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
4261 ** REDISTRIBUTION OF THIS SOFTWARE. 
4262 ** 
4263 */
4264
4265 /**
4266  * This interface is provided to TarArchive to display progress
4267  * information during operation. This is required to display the
4268  * results of the 'list' operation.
4269  */
4270
4271 public interface
4272 TarProgressDisplay
4273         {
4274         /**
4275          * Display a progress message.
4276          *
4277          * @param msg The message to display.
4278          */
4279
4280         public void
4281                 showTarProgressMessage( String msg );
4282         }
4283
4284 /*
4285 ** Authored by Timothy Gerard Endres
4286 ** <mailto:time@gjt.org>  <http://www.trustice.com>
4287 ** 
4288 ** This work has been placed into the public domain.
4289 ** You may use this work in any way and for any purpose you wish.
4290 **
4291 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
4292 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
4293 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
4294 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
4295 ** REDISTRIBUTION OF THIS SOFTWARE. 
4296 ** 
4297 */
4298
4299 /**
4300  * This interface indicates if a file qualifies for ASCII translation.
4301  * To support customization of TAR translation, this interface allows
4302  * the programmer to provide an object that will check files that do
4303  * not match the MIME types file's check for 'text/*' types. To provide
4304  * your own typer, subclass this class and set the TarArchive's TransFileTyper
4305  * via the method setTransFileTyper().
4306  */
4307
4308 public static class
4309 TarTransFileTyper
4310         {
4311         /**
4312          * Return true if the file should be translated as ASCII.
4313          *
4314          * @param f The file to be checked to see if it need ASCII translation.
4315          */
4316
4317         public boolean
4318         isAsciiFile( File f )
4319                 {
4320                 return false;
4321                 }
4322
4323         /**
4324          * Return true if the file should be translated as ASCII based on its name.
4325          * The file DOES NOT EXIST. This is called during extract, so all we know
4326          * is the file name.
4327          *
4328          * @param name The name of the file to be checked to see if it need ASCII
4329          *        translation.
4330          */
4331
4332         public boolean
4333         isAsciiFile( String name )
4334                 {
4335                 return false;
4336                 }
4337
4338         }
4339 /*
4340 ** Authored by Timothy Gerard Endres
4341 ** <mailto:time@gjt.org>  <http://www.trustice.com>
4342 ** 
4343 ** This work has been placed into the public domain.
4344 ** You may use this work in any way and for any purpose you wish.
4345 **
4346 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
4347 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
4348 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
4349 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
4350 ** REDISTRIBUTION OF THIS SOFTWARE. 
4351 ** 
4352 */
4353
4354 /**
4355  * The tar class implements a weak reproduction of the
4356  * traditional UNIX tar command. It currently supports
4357  * creating, listing, and extracting from archives. It
4358  * also supports GZIP-ed archives with the '-z' flag.
4359  * See the usage (-? or --usage) for option details.
4360  *
4361  * <pre>
4362  * usage: com.ice.tar.tar has three basic modes:
4363  * 
4364  *   com.ice.tar -c [options] archive files...
4365  *      Create new archive containing files.
4366  * 
4367  *   com.ice.tar -t [options] archive
4368  *      List contents of tar archive
4369  * 
4370  *   com.ice.tar -x [options] archive
4371  *      Extract contents of tar archive.
4372  * 
4373  * options:
4374  *    -f file, use 'file' as the tar archive
4375  *    -v, verbose mode
4376  *    -z, use GZIP compression
4377  *    -D, debug archive and buffer operation
4378  *    -b blks, set blocking size to (blks * 512) bytes
4379  *    -o, write a V7 format archive rather than ANSI
4380  *    -u name, set user name to 'name'
4381  *    -U id, set user id to 'id'
4382  *    -g name, set group name to 'name'
4383  *    -G id, set group id to 'id'
4384  *    -?, print usage information
4385  *    --trans, translate 'text/*' files
4386  *    --mime file, use this mime types file and translate
4387  *    --usage, print usage information
4388  *    --version, print version information
4389  * 
4390  * The translation options will translate from local line
4391  * endings to UNIX line endings of '\\n' when writing tar
4392  * archives, and from UNIX line endings into local line endings
4393  * when extracting archives.
4394  * 
4395  * Written by Tim Endres
4396  * This software has been placed into the public domain.
4397  * </pre>
4398  *
4399  * @version $Revision: 1.10 $
4400  * @author Timothy Gerard Endres, <time@gjt.org>
4401  * @see TarArchive
4402  */
4403
4404 public static class
4405 tar extends Object
4406         implements TarProgressDisplay
4407         {
4408         /**
4409          * Flag that determines if debugging information is displayed.
4410          */
4411         private boolean         debug;
4412         /**
4413          * Flag that determines if verbose feedback is provided.
4414          */
4415         private boolean         verbose;
4416         /**
4417          * Flag that determines if IO is GZIP-ed ('-z' option).
4418          */
4419         private boolean         compressed;
4420         /**
4421          * True if we are listing the archive. False if writing or extracting.
4422          */
4423         private boolean         listingArchive;
4424         /**
4425          * True if we are writing the archive. False if we are extracting it.
4426          */
4427         private boolean         writingArchive;
4428         /**
4429          * True if we are writing an old UNIX archive format (sets entry format).
4430          */
4431         private boolean         unixArchiveFormat;
4432         /**
4433          * True if we are not to overwrite existing files.
4434          */
4435         private boolean         keepOldFiles;
4436         /**
4437          * True if we are to convert ASCII text files from local line endings
4438          * to the UNIX standard '\n'.
4439          */
4440         private boolean         asciiTranslate;
4441         /**
4442          * True if a MIME file has been loaded with the '--mime' option.
4443          */
4444         private boolean         mimeFileLoaded;
4445
4446         /**
4447          * The archive name provided on the command line, null if stdio.
4448          */
4449         private String          archiveName;
4450
4451         /**
4452          * The blocksize to use for the tar archive IO. Set by the '-b' option.
4453          */
4454         private int                     blockSize;
4455
4456         /**
4457          * The userId to use for files written to archives. Set by '-U' option.
4458          */
4459         private int                     userId;
4460         /**
4461          * The userName to use for files written to archives. Set by '-u' option.
4462          */
4463         private String          userName;
4464         /**
4465          * The groupId to use for files written to archives. Set by '-G' option.
4466          */
4467         private int                     groupId;
4468         /**
4469          * The groupName to use for files written to archives. Set by '-g' option.
4470          */
4471         private String          groupName;
4472
4473
4474         /**
4475          * The main entry point of the tar class.
4476          */
4477         public static void
4478         main( String argv[] )
4479                 {
4480                 tar app = new tar();
4481
4482                 app.instanceMain( argv );
4483                 }
4484
4485         /**
4486          * Establishes the default userName with the 'user.name' property.
4487          */
4488         public
4489         tar()
4490                 {
4491                 this.debug = false;
4492                 this.verbose = false;
4493                 this.compressed = false;
4494                 this.archiveName = null;
4495                 this.listingArchive = false;
4496                 this.writingArchive = true;
4497                 this.unixArchiveFormat = false;
4498                 this.keepOldFiles = false;
4499                 this.asciiTranslate = false;
4500
4501                 this.blockSize = TarBuffer.DEFAULT_BLKSIZE;
4502
4503                 String sysUserName =
4504                         System.getProperty( "user.name" );
4505
4506                 this.userId = 0;
4507                 this.userName =
4508                         ( (sysUserName == null) ? "" : sysUserName );
4509
4510                 this.groupId = 0;
4511                 this.groupName = "";
4512                 }
4513
4514         /**
4515          * This is the "real" main. The class main() instantiates a tar object
4516          * for the application and then calls this method. Process the arguments
4517          * and perform the requested operation.
4518          */
4519         public void
4520         instanceMain( String argv[] )
4521                 {
4522                 TarArchive archive = null;
4523
4524                 int argIdx = this.processArguments( argv );
4525
4526                 if ( writingArchive )                           // WRITING
4527                         {
4528                         OutputStream outStream = System.out;
4529
4530                         if ( this.archiveName != null
4531                                         && ! this.archiveName.equals( "-" ) )
4532                                 {
4533                                 try {
4534                                         outStream = new FileOutputStream( this.archiveName );
4535                                         }
4536                                 catch ( IOException ex )
4537                                         {
4538                                         outStream = null;
4539                                         ex.printStackTrace( System.err );
4540                                         }
4541                                 }
4542
4543                         if ( outStream != null )
4544                                 {
4545                                 if ( this.compressed )
4546                                         {
4547                                         try {
4548                                                 outStream = new GZIPOutputStream( outStream );
4549                                                 }
4550                                         catch ( IOException ex )
4551                                                 {
4552                                                 outStream = null;
4553                                                 ex.printStackTrace( System.err );
4554                                                 }
4555                                         }
4556                                 
4557                                 archive = new TarArchive( outStream, this.blockSize );
4558                                 }
4559                         }
4560                 else                                                            // EXTRACING OR LISTING
4561                         {
4562                         InputStream inStream = System.in;
4563
4564                         if ( this.archiveName != null
4565                                         && ! this.archiveName.equals( "-" ) )
4566                                 {
4567                                 try {
4568                                         inStream = new FileInputStream( this.archiveName );
4569                                         }
4570                                 catch ( IOException ex )
4571                                         {
4572                                         inStream = null;
4573                                         ex.printStackTrace( System.err );
4574                                         }
4575                                 }
4576
4577                         if ( inStream != null )
4578                                 {
4579                                 if ( this.compressed )
4580                                         {
4581                                         try {
4582                                                 inStream = new GZIPInputStream( inStream );
4583                                                 }
4584                                         catch ( IOException ex )
4585                                                 {
4586                                                 inStream = null;
4587                                                 ex.printStackTrace( System.err );
4588                                                 }
4589                                         }
4590
4591                                 archive = new TarArchive( inStream, this.blockSize );
4592                                 }
4593                         }
4594
4595                 if ( archive != null )                                          // SET ARCHIVE OPTIONS
4596                         {
4597                         archive.setDebug( this.debug );
4598                         archive.setVerbose( this.verbose );
4599                         archive.setTarProgressDisplay( this );
4600                         archive.setKeepOldFiles( this.keepOldFiles );
4601                         archive.setAsciiTranslation( this.asciiTranslate );
4602
4603                         archive.setUserInfo(
4604                                         this.userId, this.userName,
4605                                         this.groupId, this.groupName );
4606                         }
4607
4608                 if ( archive == null )
4609                         {
4610                         System.err.println( "no processing due to errors" );
4611                         }
4612                 else if ( this.writingArchive )                         // WRITING
4613                         {
4614                         for ( ; argIdx < argv.length ; ++argIdx )
4615                                 {
4616                                 try {
4617                                         File f = new File( argv[ argIdx ] );
4618                                 
4619                                         TarEntry entry = new TarEntry( f );
4620
4621                                         if ( this.unixArchiveFormat )
4622                                                 entry.setUnixTarFormat();
4623                                         else
4624                                                 entry.setUSTarFormat();
4625
4626                                         archive.writeEntry( entry, true );
4627                                         }
4628                                 catch ( IOException ex )
4629                                         {
4630                                         ex.printStackTrace( System.err );
4631                                         }
4632                                 }
4633                         }
4634                 else if ( this.listingArchive )                         // LISTING
4635                         {
4636                         try {
4637                                 archive.listContents();
4638                                 }
4639                         catch ( InvalidHeaderException ex )
4640                                 {
4641                                 ex.printStackTrace( System.err );
4642                                 }
4643                         catch ( IOException ex )
4644                                 {
4645                                 ex.printStackTrace( System.err );
4646                                 }
4647                         }
4648                 else                                                                            // EXTRACTING
4649                         {
4650                         String userDir =
4651                                 System.getProperty( "user.dir", null );
4652
4653                         File destDir = new File( userDir );
4654                         if ( ! destDir.exists() )
4655                                 {
4656                                 if ( ! destDir.mkdirs() )
4657                                         {
4658                                         destDir = null;
4659                                         Throwable ex = new Throwable
4660                                                 ( "ERROR, mkdirs() on '" + destDir.getPath()
4661                                                         + "' returned false." );
4662                                         ex.printStackTrace( System.err );
4663                                         }
4664                                 }
4665
4666                         if ( destDir != null )
4667                                 {
4668                                 try {
4669                                         archive.extractContents( destDir );
4670                                         }
4671                                 catch ( InvalidHeaderException ex )
4672                                         {
4673                                         ex.printStackTrace( System.err );
4674                                         }
4675                                 catch ( IOException ex )
4676                                         {
4677                                         ex.printStackTrace( System.err );
4678                                         }
4679                                 }
4680                         }
4681
4682                 if ( archive != null )                                          // CLOSE ARCHIVE
4683                         {
4684                         try {
4685                                 archive.closeArchive();
4686                                 }
4687                         catch ( IOException ex )
4688                                 {
4689                                 ex.printStackTrace( System.err );
4690                                 }
4691                         }
4692                 }
4693
4694         /**
4695          * Process arguments, handling options, and return the index of the
4696          * first non-option argument.
4697          *
4698          * @return The index of the first non-option argument.
4699          */
4700
4701         private int
4702         processArguments( String args[] )
4703                 {
4704                 int idx = 0;
4705                 boolean gotOP = false;
4706
4707                 for ( ; idx < args.length ; ++idx )
4708                         {
4709                         String arg = args[ idx ];
4710
4711                         if ( ! arg.startsWith( "-" ) )
4712                                 break;
4713
4714                         if ( arg.startsWith( "--" ) )
4715                                 {
4716                                 if ( arg.equals( "--usage" ) )
4717                                         {
4718                                         this.usage();
4719                                         System.exit(1);
4720                                         }
4721                                 else if ( arg.equals( "--version" ) )
4722                                         {
4723                                         this.version();
4724                                         System.exit(1);
4725                                         }
4726                                 else
4727                                         {
4728                                         System.err.println
4729                                                 ( "unknown option: " + arg );
4730                                         this.usage();
4731                                         System.exit(1);
4732                                         }
4733                                 }
4734                         else for ( int cIdx = 1 ; cIdx < arg.length() ; ++cIdx )
4735                                 {
4736                                 char ch = arg.charAt( cIdx );
4737
4738                                 if ( ch == '?' )
4739                                         {
4740                                         this.usage();
4741                                         System.exit(1);
4742                                         }
4743                                 else if ( ch == 'f' )
4744                                         {
4745                                         this.archiveName = args[ ++idx ];
4746                                         }
4747                                 else if ( ch == 'z' )
4748                                         {
4749                                         this.compressed = true;
4750                                         }
4751                                 else if ( ch == 'c' )
4752                                         {
4753                                         gotOP = true;
4754                                         this.writingArchive = true;
4755                                         this.listingArchive = false;
4756                                         }
4757                                 else if ( ch == 'x' )
4758                                         {
4759                                         gotOP = true;
4760                                         this.writingArchive = false;
4761                                         this.listingArchive = false;
4762                                         }
4763                                 else if ( ch == 't' )
4764                                         {
4765                                         gotOP = true;
4766                                         this.writingArchive = false;
4767                                         this.listingArchive = true;
4768                                         }
4769                                 else if ( ch == 'k' )
4770                                         {
4771                                         this.keepOldFiles = true;
4772                                         }
4773                                 else if ( ch == 'o' )
4774                                         {
4775                                         this.unixArchiveFormat = true;
4776                                         }
4777                                 else if ( ch == 'b' )
4778                                         {
4779                                         try {
4780                                                 int blks = Integer.parseInt( args[ ++idx ] );
4781                                                 this.blockSize =
4782                                                         ( blks * TarBuffer.DEFAULT_RCDSIZE );
4783                                                 }
4784                                         catch ( NumberFormatException ex )
4785                                                 {
4786                                                 ex.printStackTrace( System.err );
4787                                                 }
4788                                         }
4789                                 else if ( ch == 'u' )
4790                                         {
4791                                         this.userName = args[ ++idx ];
4792                                         }
4793                                 else if ( ch == 'U' )
4794                                         {
4795                                         String idStr = args[ ++idx ];
4796                                         try {
4797                                                 this.userId = Integer.parseInt( idStr );
4798                                                 }
4799                                         catch ( NumberFormatException ex )
4800                                                 {
4801                                                 this.userId = 0;
4802                                                 ex.printStackTrace( System.err );
4803                                                 }
4804                                         }
4805                                 else if ( ch == 'g' )
4806                                         {
4807                                         this.groupName = args[ ++idx ];
4808                                         }
4809                                 else if ( ch == 'G' )
4810                                         {
4811                                         String idStr = args[ ++idx ];
4812                                         try {
4813                                                 this.groupId = Integer.parseInt( idStr );
4814                                                 }
4815                                         catch ( NumberFormatException ex )
4816                                                 {
4817                                                 this.groupId = 0;
4818                                                 ex.printStackTrace( System.err );
4819                                                 }
4820                                         }
4821                                 else if ( ch == 'v' )
4822                                         {
4823                                         this.verbose = true;
4824                                         }
4825                                 else if ( ch == 'D' )
4826                                         {
4827                                         this.debug = true;
4828                                         }
4829                                 else
4830                                         {
4831                                         System.err.println
4832                                                 ( "unknown option: " + ch );
4833                                         this.usage();
4834                                         System.exit(1);
4835                                         }
4836                                 }
4837                         }
4838
4839                 if ( ! gotOP )
4840                         {
4841                         System.err.println
4842                                 ( "you must specify an operation option (c, x, or t)" );
4843                         this.usage();
4844                         System.exit(1);
4845                         }
4846
4847                 return idx;
4848                 }
4849
4850          // I N T E R F A C E   TarProgressDisplay
4851
4852          /**
4853          * Display progress information by printing it to System.out.
4854          */
4855
4856         public void
4857         showTarProgressMessage( String msg )
4858                 {
4859                 System.out.println( msg );
4860                 }
4861
4862         /**
4863          * Print version information.
4864          */
4865
4866         private void
4867         version()
4868                 {
4869                 System.err.println
4870                         ( "Release 2.4 - $Revision: 1.10 $ $Name:  $" );
4871                 }
4872
4873         /**
4874          * Print usage information.
4875          */
4876
4877         private void
4878         usage()
4879                 {
4880                 System.err.println( "usage: com.ice.tar.tar has three basic modes:" );
4881                 System.err.println( "  com.ice.tar -c [options] archive files..." );
4882                 System.err.println( "    Create new archive containing files." );
4883                 System.err.println( "  com.ice.tar -t [options] archive" );
4884                 System.err.println( "    List contents of tar archive" );
4885                 System.err.println( "  com.ice.tar -x [options] archive" );
4886                 System.err.println( "    Extract contents of tar archive." );
4887                 System.err.println( "" );
4888                 System.err.println( "options:" );
4889                 System.err.println( "   -f file, use 'file' as the tar archive" );
4890                 System.err.println( "   -v, verbose mode" );
4891                 System.err.println( "   -z, use GZIP compression" );
4892                 System.err.println( "   -D, debug archive and buffer operation" );
4893                 System.err.println( "   -b blks, set blocking size to (blks * 512) bytes" );
4894                 System.err.println( "   -o, write a V7 format archive rather than ANSI" );
4895                 System.err.println( "   -u name, set user name to 'name'" );
4896                 System.err.println( "   -U id, set user id to 'id'" );
4897                 System.err.println( "   -g name, set group name to 'name'" );
4898                 System.err.println( "   -G id, set group id to 'id'" );
4899                 System.err.println( "   -?, print usage information" );
4900                 System.err.println( "   --trans, translate 'text/*' files" );
4901                 System.err.println( "   --mime file, use this mime types file and translate" );
4902                 System.err.println( "   --usage, print usage information" );
4903                 System.err.println( "   --version, print version information" );
4904                 System.err.println( "" );
4905                 System.err.println( "The translation options will translate from local line" );
4906                 System.err.println( "endings to UNIX line endings of '\\n' when writing tar" );
4907                 System.err.println( "archives, and from UNIX line endings into local line endings" );
4908                 System.err.println( "when extracting archives." );
4909                 System.err.println( "" );
4910                 System.err.println( "Written by Tim Endres" );
4911                 System.err.println( "" );
4912                 System.err.println( "This software has been placed into the public domain." );
4913                 System.err.println( "" );
4914
4915                 this.version();
4916
4917                 System.exit( 1 );
4918                 }
4919
4920         }
4921
4922
4923 }