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