2 ** Authored by Timothy Gerard Endres
3 ** <mailto:time@gjt.org> <http://www.trustice.com>
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.
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.
16 package org.ibex.util;
20 import java.util.zip.*;
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.
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
37 * @author Timothy Gerard Endres, <time@gjt.org>
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;
52 FastTarStream( InputStream in )
54 this( in, TarBuffer.DEFAULT_RCDSIZE );
58 FastTarStream( InputStream in, int recordSize )
61 this.hasHitEOF = false;
62 this.currEntry = null;
63 this.recordSize = recordSize;
67 setDebug( boolean debug )
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!
84 if ( this.currEntry != null && this.currEntry.getSize() > 0 )
86 // Need to round out the number of records to be read to skip entry...
88 ( (int)this.currEntry.getSize() + (this.recordSize - 1) )
93 this.inStream.skip( numRecords * this.recordSize );
97 byte[] headerBuf = new byte[ this.recordSize ];
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.
104 for ( int bytesNeeded = this.recordSize ; bytesNeeded > 0 ; )
106 int numRead = this.inStream.read( headerBuf, bufIndex, bytesNeeded );
110 this.hasHitEOF = true;
115 bytesNeeded -= numRead;
118 // Check for "EOF block" of all zeros
119 if ( ! this.hasHitEOF )
121 this.hasHitEOF = true;
122 for ( int i = 0 ; i < headerBuf.length ; ++i )
124 if ( headerBuf[i] != 0 )
126 this.hasHitEOF = false;
132 if ( this.hasHitEOF )
134 this.currEntry = null;
139 this.currEntry = new TarEntry( headerBuf );
143 byte[] by = new byte[ headerBuf.length ];
144 for(int i = 0; i < headerBuf.length; i++)
146 by[i] = ( headerBuf[i] == 0? 20: headerBuf[i] );
148 String s = new String( by );
149 System.out.println( "\n" + s );
152 if ( ! ( headerBuf[257] == 'u' &&headerBuf[258] == 's'
153 && headerBuf[259] == 't' &&headerBuf[260] == 'a'
154 && headerBuf[261] == 'r' ) )
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]) );
167 catch ( InvalidHeaderException ex )
169 this.currEntry = null;
174 return this.currEntry;
178 main( String[] args )
180 boolean debug = false;
181 InputStream in = null;
183 String fileName = args[0];
187 if ( args.length > 0 )
189 if ( args[idx].equals( "-d" ) )
195 if ( args[idx].endsWith( ".gz" )
196 || args[idx].endsWith( ".tgz" ) )
198 in = new GZIPInputStream( new FileInputStream( args[idx] ) );
202 in = new FileInputStream( args[idx] );
210 FastTarStream fts = new FastTarStream( in );
211 fts.setDebug( debug );
216 StringBuffer padBuf = new StringBuffer(128);
219 TarEntry entry = fts.getNextEntry();
223 if ( entry.isDirectory() )
226 System.out.print( "D " );
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( '_' );
237 padBuf.append( '_' );
238 System.out.print( padBuf.toString() );
242 for ( ; padBuf.length() < sizeWidth ; )
243 padBuf.insert( 0, '_' );
245 padBuf.append( ' ' );
246 System.out.print( padBuf.toString() );
250 padBuf.append( entry.getUserName() );
251 if ( padBuf.length() > userWidth )
252 padBuf.setLength( userWidth );
253 for ( ; padBuf.length() < userWidth ; )
254 padBuf.append( ' ' );
256 System.out.print( padBuf.toString() );
261 System.out.print( "F " );
265 padBuf.append( entry.getName() );
266 if ( padBuf.length() > nameWidth )
267 padBuf.setLength( nameWidth );
268 for ( ; padBuf.length() < nameWidth ; )
269 padBuf.append( ' ' );
271 padBuf.append( ' ' );
272 System.out.print( padBuf.toString() );
276 padBuf.append( entry.getSize() );
277 if ( padBuf.length() > sizeWidth )
278 padBuf.setLength( sizeWidth );
279 for ( ; padBuf.length() < sizeWidth ; )
280 padBuf.insert( 0, ' ' );
282 padBuf.append( ' ' );
283 System.out.print( padBuf.toString() );
287 padBuf.append( entry.getUserName() );
288 if ( padBuf.length() > userWidth )
289 padBuf.setLength( userWidth );
290 for ( ; padBuf.length() < userWidth ; )
291 padBuf.append( ' ' );
293 System.out.print( padBuf.toString() );
296 System.out.println( "" );
299 catch ( IOException ex )
301 ex.printStackTrace( System.err );
308 ** Authored by Timothy Gerard Endres
309 ** <mailto:time@gjt.org> <http://www.trustice.com>
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.
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.
323 * This exception is used to indicate that there is a problem
324 * with a TAR archive header.
328 InvalidHeaderException extends IOException
332 InvalidHeaderException()
338 InvalidHeaderException( String msg )
346 ** Authored by Timothy Gerard Endres
347 ** <mailto:time@gjt.org> <http://www.trustice.com>
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.
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.
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.
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.
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.
381 * @version $Revision: 1.15 $
382 * @author Timothy Gerard Endres, <time@gjt.org>
390 TarArchive extends Object
392 protected boolean verbose;
393 protected boolean debug;
394 protected boolean keepOldFiles;
395 protected boolean asciiTranslate;
397 protected int userId;
398 protected String userName;
399 protected int groupId;
400 protected String groupName;
402 protected String rootPath;
403 protected String tempPath;
404 protected String pathPrefix;
406 protected int recordSize;
407 protected byte[] recordBuf;
409 protected TarInputStream tarIn;
410 protected TarOutputStream tarOut;
412 protected TarTransFileTyper transTyper;
413 protected TarProgressDisplay progressDisplay;
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.
424 TarArchive( InputStream inStream )
426 this( inStream, TarBuffer.DEFAULT_BLKSIZE );
430 TarArchive( InputStream inStream, int blockSize )
432 this( inStream, blockSize, TarBuffer.DEFAULT_RCDSIZE );
436 TarArchive( InputStream inStream, int blockSize, int recordSize )
438 this.tarIn = new TarInputStream( inStream, blockSize, recordSize );
439 this.initialize( recordSize );
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.
449 TarArchive( OutputStream outStream )
451 this( outStream, TarBuffer.DEFAULT_BLKSIZE );
455 TarArchive( OutputStream outStream, int blockSize )
457 this( outStream, blockSize, TarBuffer.DEFAULT_RCDSIZE );
461 TarArchive( OutputStream outStream, int blockSize, int recordSize )
463 this.tarOut = new TarOutputStream( outStream, blockSize, recordSize );
464 this.initialize( recordSize );
468 * Common constructor initialization code.
472 initialize( int recordSize )
474 this.rootPath = null;
475 this.pathPrefix = null;
476 this.tempPath = System.getProperty( "user.dir" );
484 this.verbose = false;
485 this.keepOldFiles = false;
486 this.progressDisplay = null;
489 new byte[ this.getRecordSize() ];
493 * Set the debugging flag.
495 * @param debugF The new debug setting.
499 setDebug( boolean debugF )
502 if ( this.tarIn != null )
503 this.tarIn.setDebug( debugF );
504 else if ( this.tarOut != null )
505 this.tarOut.setDebug( debugF );
509 * Returns the verbosity setting.
511 * @return The current verbosity setting.
521 * Set the verbosity flag.
523 * @param verbose The new verbosity setting.
527 setVerbose( boolean verbose )
529 this.verbose = verbose;
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.
537 * @param display The new progress display interface.
538 * @see TarProgressDisplay
542 setTarProgressDisplay( TarProgressDisplay display )
544 this.progressDisplay = display;
548 * Set the flag that determines whether existing files are
549 * kept, or overwritten during extraction.
551 * @param keepOldFiles If true, do not overwrite existing files.
555 setKeepOldFiles( boolean keepOldFiles )
557 this.keepOldFiles = keepOldFiles;
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.
572 * @param asciiTranslate If true, translate ascii text files.
576 setAsciiTranslation( boolean asciiTranslate )
578 this.asciiTranslate = asciiTranslate;
582 * Set the object that will determine if a file is of type
583 * ascii text for translation purposes.
585 * @param transTyper The new TransFileTyper object.
589 setTransFileTyper( TarTransFileTyper transTyper )
591 this.transTyper = transTyper;
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.
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.
609 int userId, String userName,
610 int groupId, String groupName )
612 this.userId = userId;
613 this.userName = userName;
614 this.groupId = groupId;
615 this.groupName = groupName;
619 * Get the user id being used for archive entry headers.
621 * @return The current user id.
631 * Get the user name being used for archive entry headers.
633 * @return The current user name.
639 return this.userName;
643 * Get the group id being used for archive entry headers.
645 * @return The current group id.
655 * Get the group name being used for archive entry headers.
657 * @return The current group name.
663 return this.groupName;
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.
673 * @return The current temporary directory path.
679 return this.tempPath;
683 * Set the current temporary directory path.
685 * @param path The new temporary directory path.
689 setTempDirectory( String path )
691 this.tempPath = path;
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.
703 * @return The record size this archive is using.
709 if ( this.tarIn != null )
711 return this.tarIn.getRecordSize();
713 else if ( this.tarOut != null )
715 return this.tarOut.getRecordSize();
718 return TarBuffer.DEFAULT_RCDSIZE;
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
727 * @return The temporary file's path.
731 getTempFilePath( File eFile )
734 this.tempPath + File.separator
735 + eFile.getName() + ".tmp";
737 for ( int i = 1 ; i < 5 ; ++i )
739 File f = new File( pathStr );
745 this.tempPath + File.separator
746 + eFile.getName() + "-" + i + ".tmp";
753 * Close the archive. This simply calls the underlying
754 * tar stream's close() method.
761 if ( this.tarIn != null )
765 else if ( this.tarOut != null )
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
780 throws IOException, InvalidHeaderException
784 TarEntry entry = this.tarIn.getNextEntry();
791 System.err.println( "READ EOF RECORD" );
796 if ( this.progressDisplay != null )
797 this.progressDisplay.showTarProgressMessage
803 * Perform the "extract" command and extract the contents of the archive.
805 * @param destDir The destination directory into which to extract.
809 extractContents( File destDir )
810 throws IOException, InvalidHeaderException
814 TarEntry entry = this.tarIn.getNextEntry();
820 System.err.println( "READ EOF RECORD" );
825 this.extractEntry( destDir, entry );
830 * Extract an entry from the archive. This method assumes that the
831 * tarIn stream has been properly set with a call to getNextEntry().
833 * @param destDir The destination directory into which to extract.
834 * @param entry The TarEntry returned by tarIn.getNextEntry().
838 extractEntry( File destDir, TarEntry entry )
843 if ( this.progressDisplay != null )
844 this.progressDisplay.showTarProgressMessage
848 String name = entry.getName();
849 name = name.replace( '/', File.separatorChar );
851 File destFile = new File( destDir, name );
853 if ( entry.isDirectory() )
855 if ( ! destFile.exists() )
857 if ( ! destFile.mkdirs() )
859 throw new IOException
860 ( "error making directory path '"
861 + destFile.getPath() + "'" );
867 File subDir = new File( destFile.getParent() );
869 if ( ! subDir.exists() )
871 if ( ! subDir.mkdirs() )
873 throw new IOException
874 ( "error making directory path '"
875 + subDir.getPath() + "'" );
879 if ( this.keepOldFiles && destFile.exists() )
883 if ( this.progressDisplay != null )
884 this.progressDisplay.showTarProgressMessage
885 ( "not overwriting " + entry.getName() );
890 boolean asciiTrans = false;
892 FileOutputStream out =
893 new FileOutputStream( destFile );
896 PrintWriter outw = null;
899 outw = new PrintWriter( out );
902 byte[] rdbuf = new byte[32 * 1024];
906 int numRead = this.tarIn.read( rdbuf );
913 for ( int off = 0, b = 0 ; b < numRead ; ++b )
915 if ( rdbuf[ b ] == 10 )
917 String s = new String
918 ( rdbuf, off, (b - off) );
928 out.write( rdbuf, 0, numRead );
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.
947 * @param entry The TarEntry representing the entry to write to the archive.
948 * @param recurse If true, process the children of directory entries.
952 writeEntry( TarEntry oldEntry, boolean recurse )
955 boolean asciiTrans = false;
956 boolean unixArchiveFormat = oldEntry.isUnixTarFormat();
959 File eFile = oldEntry.getFile();
961 // Work on a copy of the entry so we can manipulate it.
962 // Note that we must distinguish how the entry was constructed.
964 TarEntry entry = (TarEntry) oldEntry.clone();
968 if ( this.progressDisplay != null )
969 this.progressDisplay.showTarProgressMessage
974 String newName = null;
976 if ( this.rootPath != null )
978 if ( entry.getName().startsWith( this.rootPath ) )
981 entry.getName().substring
982 ( this.rootPath.length() + 1 );
986 if ( this.pathPrefix != null )
988 newName = (newName == null)
989 ? this.pathPrefix + "/" + entry.getName()
990 : this.pathPrefix + "/" + newName;
993 if ( newName != null )
995 entry.setName( newName );
998 this.tarOut.putNextEntry( entry );
1000 if ( entry.isDirectory() )
1004 TarEntry[] list = entry.getDirectoryEntries();
1006 for ( int i = 0 ; i < list.length ; ++i )
1008 TarEntry dirEntry = list[i];
1010 if ( unixArchiveFormat )
1011 dirEntry.setUnixTarFormat();
1013 this.writeEntry( dirEntry, recurse );
1019 FileInputStream in =
1020 new FileInputStream( eFile );
1022 byte[] eBuf = new byte[ 32 * 1024 ];
1025 int numRead = in.read( eBuf, 0, eBuf.length );
1027 if ( numRead == -1 )
1030 this.tarOut.write( eBuf, 0, numRead );
1035 if ( tFile != null )
1040 this.tarOut.closeEntry();
1048 ** Authored by Timothy Gerard Endres
1049 ** <mailto:time@gjt.org> <http://www.trustice.com>
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.
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.
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.
1070 * You should never have a need to access this class directly.
1071 * TarBuffers are created by Tar IO Streams.
1073 * @version $Revision: 1.10 $
1074 * @author Timothy Gerard Endres,
1075 * <a href="mailto:time@gjt.org">time@trustice.com</a>.
1080 static class TarBuffer
1083 public static final int DEFAULT_RCDSIZE = ( 512 );
1084 public static final int DEFAULT_BLKSIZE = ( DEFAULT_RCDSIZE * 20 );
1086 private InputStream inStream;
1087 private OutputStream outStream;
1089 private byte[] blockBuffer;
1090 private int currBlkIdx;
1091 private int currRecIdx;
1092 private int blockSize;
1093 private int recordSize;
1094 private int recsPerBlock;
1096 private boolean debug;
1100 TarBuffer( InputStream inStream )
1102 this( inStream, TarBuffer.DEFAULT_BLKSIZE );
1106 TarBuffer( InputStream inStream, int blockSize )
1108 this( inStream, blockSize, TarBuffer.DEFAULT_RCDSIZE );
1112 TarBuffer( InputStream inStream, int blockSize, int recordSize )
1114 this.inStream = inStream;
1115 this.outStream = null;
1116 this.initialize( blockSize, recordSize );
1120 TarBuffer( OutputStream outStream )
1122 this( outStream, TarBuffer.DEFAULT_BLKSIZE );
1126 TarBuffer( OutputStream outStream, int blockSize )
1128 this( outStream, blockSize, TarBuffer.DEFAULT_RCDSIZE );
1132 TarBuffer( OutputStream outStream, int blockSize, int recordSize )
1134 this.inStream = null;
1135 this.outStream = outStream;
1136 this.initialize( blockSize, recordSize );
1140 * Initialization common to all constructors.
1143 initialize( int blockSize, int recordSize )
1146 this.blockSize = blockSize;
1147 this.recordSize = recordSize;
1148 this.recsPerBlock = ( this.blockSize / this.recordSize );
1149 this.blockBuffer = new byte[ this.blockSize ];
1151 if ( this.inStream != null )
1153 this.currBlkIdx = -1;
1154 this.currRecIdx = this.recsPerBlock;
1158 this.currBlkIdx = 0;
1159 this.currRecIdx = 0;
1164 * Get the TAR Buffer's block size. Blocks consist of multiple records.
1169 return this.blockSize;
1173 * Get the TAR Buffer's record size.
1178 return this.recordSize;
1182 * Set the debugging flag for the buffer.
1184 * @param debug If true, print debugging output.
1187 setDebug( boolean debug )
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.
1196 * @param record The record data to check.
1199 isEOFRecord( byte[] record )
1201 for ( int i = 0, sz = this.getRecordSize() ; i < sz ; ++i )
1202 if ( record[i] != 0 )
1209 * Skip over a record on the input stream.
1219 ( "SkipRecord: recIdx = " + this.currRecIdx
1220 + " blkIdx = " + this.currBlkIdx );
1223 if ( this.inStream == null )
1224 throw new IOException
1225 ( "reading (via skip) from an output buffer" );
1227 if ( this.currRecIdx >= this.recsPerBlock )
1229 if ( ! this.readBlock() )
1237 * Read a record from the input stream and return the data.
1239 * @return The record data.
1249 ( "ReadRecord: recIdx = " + this.currRecIdx
1250 + " blkIdx = " + this.currBlkIdx );
1253 if ( this.inStream == null )
1254 throw new IOException
1255 ( "reading from an output buffer" );
1257 if ( this.currRecIdx >= this.recsPerBlock )
1259 if ( ! this.readBlock() )
1263 byte[] result = new byte[ this.recordSize ];
1266 this.blockBuffer, (this.currRecIdx * this.recordSize),
1267 result, 0, this.recordSize );
1275 * @return false if End-Of-File, else true
1285 ( "ReadBlock: blkIdx = " + this.currBlkIdx );
1288 if ( this.inStream == null )
1289 throw new IOException
1290 ( "reading from an output buffer" );
1292 this.currRecIdx = 0;
1295 int bytesNeeded = this.blockSize;
1296 for ( ; bytesNeeded > 0 ; )
1300 ( this.blockBuffer, offset, bytesNeeded );
1304 // We have fit EOF, and the block is not full!
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.
1313 // Thanks to 'Yohann.Roussel@alcatel.fr' for this fix.
1316 if ( numBytes == -1 )
1320 bytesNeeded -= numBytes;
1321 if ( numBytes != this.blockSize )
1326 ( "ReadBlock: INCOMPLETE READ " + numBytes
1327 + " of " + this.blockSize + " bytes read." );
1338 * Get the current block number, zero based.
1340 * @return The current zero based block number.
1343 getCurrentBlockNum()
1345 return this.currBlkIdx;
1349 * Get the current record number, within the current block, zero based.
1350 * Thus, current offset = (currentBlockNum * recsPerBlk) + currentRecNum.
1352 * @return The current zero based record number.
1355 getCurrentRecordNum()
1357 return this.currRecIdx - 1;
1361 * Write an archive record to the archive.
1363 * @param record The record data to write to the archive.
1367 writeRecord( byte[] record )
1373 ( "WriteRecord: recIdx = " + this.currRecIdx
1374 + " blkIdx = " + this.currBlkIdx );
1377 if ( this.outStream == null )
1378 throw new IOException
1379 ( "writing to an input buffer" );
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 + "'" );
1387 if ( this.currRecIdx >= this.recsPerBlock )
1394 this.blockBuffer, (this.currRecIdx * this.recordSize),
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.
1405 * @param buf The buffer containing the record data to write.
1406 * @param offset The offset of the record data within buf.
1410 writeRecord( byte[] buf, int offset )
1416 ( "WriteRecord: recIdx = " + this.currRecIdx
1417 + " blkIdx = " + this.currBlkIdx );
1420 if ( this.outStream == null )
1421 throw new IOException
1422 ( "writing to an input buffer" );
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 + "'" );
1431 if ( this.currRecIdx >= this.recsPerBlock )
1438 this.blockBuffer, (this.currRecIdx * this.recordSize),
1445 * Write a TarBuffer block to the archive.
1454 ( "WriteBlock: blkIdx = " + this.currBlkIdx );
1457 if ( this.outStream == null )
1458 throw new IOException
1459 ( "writing to an input buffer" );
1461 this.outStream.write( this.blockBuffer, 0, this.blockSize );
1462 this.outStream.flush();
1464 this.currRecIdx = 0;
1469 * Flush the current data block if it has any data in it.
1478 System.err.println( "TarBuffer.flushBlock() called." );
1481 if ( this.outStream == null )
1482 throw new IOException
1483 ( "writing to an input buffer" );
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.
1491 if ( this.currRecIdx > 0 )
1493 int offset = this.currRecIdx * this.recordSize;
1494 byte[] zeroBuffer = new byte[ this.blockSize - offset ];
1497 ( zeroBuffer, 0, this.blockBuffer, offset, zeroBuffer.length );
1504 * Close the TarBuffer. If this is an output buffer, also flush the
1505 * current block before closing.
1513 System.err.println( "TarBuffer.closeBuffer()." );
1516 if ( this.outStream != null )
1520 if ( this.outStream != System.out
1521 && this.outStream != System.err )
1523 this.outStream.close();
1524 this.outStream = null;
1527 else if ( this.inStream != null )
1529 if ( this.inStream != System.in )
1531 this.inStream.close();
1532 this.inStream = null;
1540 ** Authored by Timothy Gerard Endres
1541 ** <mailto:time@gjt.org> <http://www.trustice.com>
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.
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.
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.
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.
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.
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.
1584 * Original Unix Tar Header:
1587 * Width Name Meaning
1588 * ----- --------- ---------------------------
1589 * 100 name name of file
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
1603 * POSIX "ustar" Style Tar Header:
1606 * Width Name Meaning
1607 * ----- --------- ---------------------------
1608 * 100 name name of file
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
1625 * struct posix_header
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
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.
1651 * Directory "size" fix contributed by:
1652 * Bert Becker <becker@informatik.hu-berlin.de>
1655 * @author Timothy Gerard Endres, <time@gjt.org>
1659 static class TarEntry
1661 implements Cloneable
1664 * If this entry represents a File, this references it.
1666 protected File file;
1669 * This is the entry's header information.
1671 protected TarHeader header;
1674 * Set to true if this is a "old-unix" format entry.
1676 protected boolean unixFormat;
1679 * Set to true if this is a 'ustar' format entry.
1681 protected boolean ustarFormat;
1684 * Set to true if this is a GNU 'ustar' format entry.
1686 protected boolean gnuFormat;
1690 * The default constructor is protected for use only by subclasses.
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.
1702 TarEntry( String name )
1705 this.nameTarHeader( this.header, name );
1709 * Construct an entry for a file. File is set to file, and the
1710 * header is constructed from information from the file.
1712 * @param file The file that the entry represents.
1715 TarEntry( File file )
1716 throws InvalidHeaderException
1719 this.getFileTarHeader( this.header, file );
1723 * Construct an entry from an archive's header bytes. File is set
1726 * @param headerBuf The header bytes from a tar archive entry.
1729 TarEntry( byte[] headerBuf )
1730 throws InvalidHeaderException
1733 this.parseTarHeader( this.header, headerBuf );
1737 * Initialization code common to all constructors.
1743 this.header = new TarHeader();
1745 this.gnuFormat = false;
1746 this.ustarFormat = true; // REVIEW What we prefer to use...
1747 this.unixFormat = false;
1756 TarEntry entry = null;
1759 entry = (TarEntry) super.clone();
1761 if ( this.header != null )
1763 entry.header = (TarHeader) this.header.clone();
1766 if ( this.file != null )
1768 entry.file = new File( this.file.getAbsolutePath() );
1771 catch ( CloneNotSupportedException ex )
1773 ex.printStackTrace( System.err );
1780 * Returns true if this entry's header is in "ustar" format.
1782 * @return True if the entry's header is in "ustar" format.
1787 return this.ustarFormat;
1791 * Sets this entry's header format to "ustar".
1796 this.ustarFormat = true;
1797 this.gnuFormat = false;
1798 this.unixFormat = false;
1802 * Returns true if this entry's header is in the GNU 'ustar' format.
1804 * @return True if the entry's header is in the GNU 'ustar' format.
1809 return this.gnuFormat;
1813 * Sets this entry's header format to GNU "ustar".
1818 this.gnuFormat = true;
1819 this.ustarFormat = false;
1820 this.unixFormat = false;
1824 * Returns true if this entry's header is in the old "unix-tar" format.
1826 * @return True if the entry's header is in the old "unix-tar" format.
1831 return this.unixFormat;
1835 * Sets this entry's header format to "unix-style".
1840 this.unixFormat = true;
1841 this.ustarFormat = false;
1842 this.gnuFormat = false;
1846 * Determine if the two entries are equal. Equality is determined
1847 * by the header names being equal.
1849 * @return it Entry to be checked for equality.
1850 * @return True if the entries are equal.
1853 equals( TarEntry it )
1856 this.header.name.toString().equals
1857 ( it.header.name.toString() );
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.
1865 * @param desc Entry to be checked as a descendent of this.
1866 * @return True if entry is a descendant of this.
1869 isDescendent( TarEntry desc )
1872 desc.header.name.toString().startsWith
1873 ( this.header.name.toString() );
1877 * Get this entry's header.
1879 * @return This entry's TarHeader.
1888 * Get this entry's name.
1890 * @return This entry's name.
1895 return this.header.name.toString();
1899 * Set this entry's name.
1901 * @param name This entry's new name.
1904 setName( String name )
1907 new StringBuffer( name );
1911 * Get this entry's user id.
1913 * @return This entry's user id.
1918 return this.header.userId;
1922 * Set this entry's user id.
1924 * @param userId This entry's new user id.
1927 setUserId( int userId )
1929 this.header.userId = userId;
1933 * Get this entry's group id.
1935 * @return This entry's group id.
1940 return this.header.groupId;
1944 * Set this entry's group id.
1946 * @param groupId This entry's new group id.
1949 setGroupId( int groupId )
1951 this.header.groupId = groupId;
1955 * Get this entry's user name.
1957 * @return This entry's user name.
1962 return this.header.userName.toString();
1966 * Set this entry's user name.
1968 * @param userName This entry's new user name.
1971 setUserName( String userName )
1973 this.header.userName =
1974 new StringBuffer( userName );
1978 * Get this entry's group name.
1980 * @return This entry's group name.
1985 return this.header.groupName.toString();
1989 * Set this entry's group name.
1991 * @param groupName This entry's new group name.
1994 setGroupName( String groupName )
1996 this.header.groupName =
1997 new StringBuffer( groupName );
2001 * Convenience method to set this entry's group and user ids.
2003 * @param userId This entry's new user id.
2004 * @param groupId This entry's new group id.
2007 setIds( int userId, int groupId )
2009 this.setUserId( userId );
2010 this.setGroupId( groupId );
2014 * Convenience method to set this entry's group and user names.
2016 * @param userName This entry's new user name.
2017 * @param groupName This entry's new group name.
2020 setNames( String userName, String groupName )
2022 this.setUserName( userName );
2023 this.setGroupName( groupName );
2027 * Set this entry's modification time. The parameter passed
2028 * to this method is in "Java time".
2030 * @param time This entry's new modification time.
2033 setModTime( long time )
2035 this.header.modTime = time / 1000;
2039 * Set this entry's modification time.
2041 * @param time This entry's new modification time.
2044 setModTime( Date time )
2046 this.header.modTime = time.getTime() / 1000;
2050 * Set this entry's modification time.
2052 * @param time This entry's new modification time.
2057 return new Date( this.header.modTime * 1000 );
2061 * Get this entry's file.
2063 * @return This entry's file.
2072 * Get this entry's file size.
2074 * @return This entry's file size.
2079 return this.header.size;
2083 * Set this entry's file size.
2085 * @param size This entry's new file size.
2088 setSize( long size )
2090 this.header.size = size;
2094 * Return whether or not this entry represents a directory.
2096 * @return True if this entry is a directory.
2101 if ( this.file != null )
2102 return this.file.isDirectory();
2104 if ( this.header != null )
2106 if ( this.header.linkFlag == TarHeader.LF_DIR )
2109 if ( this.header.name.toString().endsWith( "/" ) )
2117 * Fill in a TarHeader with information from a File.
2119 * @param hdr The TarHeader to fill in.
2120 * @param file The file from which to get the header information.
2123 getFileTarHeader( TarHeader hdr, File file )
2124 throws InvalidHeaderException
2128 String name = file.getPath();
2129 String osname = System.getProperty( "os.name" );
2130 if ( osname != null )
2132 // Strip off drive letters!
2133 // REVIEW Would a better check be "(File.separator == '\')"?
2135 // String Win32Prefix = "Windows";
2136 // String prefix = osname.substring( 0, Win32Prefix.length() );
2137 // if ( prefix.equalsIgnoreCase( Win32Prefix ) )
2139 // if ( File.separatorChar == '\\' )
2141 // Windows OS check was contributed by
2142 // Patrick Beard <beard@netscape.com>
2143 String Win32Prefix = "windows";
2144 if ( osname.toLowerCase().startsWith( Win32Prefix ) )
2146 if ( name.length() > 2 )
2148 char ch1 = name.charAt(0);
2149 char ch2 = name.charAt(1);
2151 && ( (ch1 >= 'a' && ch1 <= 'z')
2152 || (ch1 >= 'A' && ch1 <= 'Z') ) )
2154 name = name.substring( 2 );
2160 name = name.replace( File.separatorChar, '/' );
2162 // No absolute pathnames
2163 // Windows (and Posix?) paths can start with "\\NetworkDrive\",
2164 // so we loop on starting /'s.
2166 for ( ; name.startsWith( "/" ) ; )
2167 name = name.substring( 1 );
2169 hdr.linkName = new StringBuffer( "" );
2171 hdr.name = new StringBuffer( name );
2173 if ( file.isDirectory() )
2177 hdr.linkFlag = TarHeader.LF_DIR;
2178 if ( hdr.name.charAt( hdr.name.length() - 1 ) != '/' )
2179 hdr.name.append( "/" );
2183 hdr.size = file.length();
2185 hdr.linkFlag = TarHeader.LF_NORMAL;
2188 // UNDONE When File lets us get the userName, use it!
2190 hdr.modTime = file.lastModified() / 1000;
2197 * If this entry represents a file, and the file is a directory, return
2198 * an array of TarEntries for this entry's children.
2200 * @return An array of TarEntry's for this entry's children.
2203 getDirectoryEntries()
2204 throws InvalidHeaderException
2206 if ( this.file == null
2207 || ! this.file.isDirectory() )
2209 return new TarEntry[0];
2212 String[] list = this.file.list();
2214 TarEntry[] result = new TarEntry[ list.length ];
2216 for ( int i = 0 ; i < list.length ; ++i )
2220 ( new File( this.file, list[i] ) );
2227 * Compute the checksum of a tar entry header.
2229 * @param buf The tar entry's header buffer.
2230 * @return The computed checksum.
2233 computeCheckSum( byte[] buf )
2237 for ( int i = 0 ; i < buf.length ; ++i )
2239 sum += 255 & buf[ i ];
2246 * Write an entry's header information to a header buffer.
2247 * This method can throw an InvalidHeaderException
2249 * @param outbuf The tar entry header buffer to fill in.
2250 * @throws InvalidHeaderException If the name will not fit in the header.
2253 writeEntryHeader( byte[] outbuf )
2254 throws InvalidHeaderException
2258 if ( this.isUnixTarFormat() )
2260 if ( this.header.name.length() > 100 )
2261 throw new InvalidHeaderException
2262 ( "file path is greater than 100 characters, "
2263 + this.header.name );
2266 offset = TarHeader.getFileNameBytes( this.header.name.toString(), outbuf );
2268 offset = TarHeader.getOctalBytes
2269 ( this.header.mode, outbuf, offset, TarHeader.MODELEN );
2271 offset = TarHeader.getOctalBytes
2272 ( this.header.userId, outbuf, offset, TarHeader.UIDLEN );
2274 offset = TarHeader.getOctalBytes
2275 ( this.header.groupId, outbuf, offset, TarHeader.GIDLEN );
2277 long size = this.header.size;
2279 offset = TarHeader.getLongOctalBytes
2280 ( size, outbuf, offset, TarHeader.SIZELEN );
2282 offset = TarHeader.getLongOctalBytes
2283 ( this.header.modTime, outbuf, offset, TarHeader.MODTIMELEN );
2285 int csOffset = offset;
2286 for ( int c = 0 ; c < TarHeader.CHKSUMLEN ; ++c )
2287 outbuf[ offset++ ] = (byte) ' ';
2289 outbuf[ offset++ ] = this.header.linkFlag;
2291 offset = TarHeader.getNameBytes
2292 ( this.header.linkName, outbuf, offset, TarHeader.NAMELEN );
2294 if ( this.unixFormat )
2296 for ( int i = 0 ; i < TarHeader.MAGICLEN ; ++i )
2297 outbuf[ offset++ ] = 0;
2301 offset = TarHeader.getNameBytes
2302 ( this.header.magic, outbuf, offset, TarHeader.MAGICLEN );
2305 offset = TarHeader.getNameBytes
2306 ( this.header.userName, outbuf, offset, TarHeader.UNAMELEN );
2308 offset = TarHeader.getNameBytes
2309 ( this.header.groupName, outbuf, offset, TarHeader.GNAMELEN );
2311 offset = TarHeader.getOctalBytes
2312 ( this.header.devMajor, outbuf, offset, TarHeader.DEVLEN );
2314 offset = TarHeader.getOctalBytes
2315 ( this.header.devMinor, outbuf, offset, TarHeader.DEVLEN );
2317 for ( ; offset < outbuf.length ; )
2318 outbuf[ offset++ ] = 0;
2320 long checkSum = this.computeCheckSum( outbuf );
2322 TarHeader.getCheckSumOctalBytes
2323 ( checkSum, outbuf, csOffset, TarHeader.CHKSUMLEN );
2327 * Parse an entry's TarHeader information from a header buffer.
2329 * Old unix-style code contributed by David Mehringer <dmehring@astro.uiuc.edu>.
2331 * @param hdr The TarHeader to fill in from the buffer information.
2332 * @param header The tar entry header buffer to get information from.
2335 parseTarHeader( TarHeader hdr, byte[] headerBuf )
2336 throws InvalidHeaderException
2341 // NOTE Recognize archive header format.
2343 if ( headerBuf[257] == 0
2344 && headerBuf[258] == 0
2345 && headerBuf[259] == 0
2346 && headerBuf[260] == 0
2347 && headerBuf[261] == 0 )
2349 this.unixFormat = true;
2350 this.ustarFormat = false;
2351 this.gnuFormat = false;
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 )
2360 this.ustarFormat = true;
2361 this.gnuFormat = false;
2362 this.unixFormat = false;
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 )
2373 this.gnuFormat = true;
2374 this.unixFormat = false;
2375 this.ustarFormat = false;
2379 StringBuffer buf = new StringBuffer( 128 );
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] );
2392 buf.append( (int)headerBuf[258] );
2394 buf.append( (int)headerBuf[259] );
2396 buf.append( (int)headerBuf[260] );
2398 buf.append( (int)headerBuf[261] );
2400 buf.append( (int)headerBuf[262] );
2402 buf.append( (int)headerBuf[263] );
2404 throw new InvalidHeaderException( buf.toString() );
2407 hdr.name = TarHeader.parseFileName( headerBuf );
2409 offset = TarHeader.NAMELEN;
2412 TarHeader.parseOctal( headerBuf, offset, TarHeader.MODELEN );
2414 offset += TarHeader.MODELEN;
2417 TarHeader.parseOctal( headerBuf, offset, TarHeader.UIDLEN );
2419 offset += TarHeader.UIDLEN;
2422 TarHeader.parseOctal( headerBuf, offset, TarHeader.GIDLEN );
2424 offset += TarHeader.GIDLEN;
2427 TarHeader.parseOctal( headerBuf, offset, TarHeader.SIZELEN );
2429 offset += TarHeader.SIZELEN;
2432 TarHeader.parseOctal( headerBuf, offset, TarHeader.MODTIMELEN );
2434 offset += TarHeader.MODTIMELEN;
2436 hdr.checkSum = (int)
2437 TarHeader.parseOctal( headerBuf, offset, TarHeader.CHKSUMLEN );
2439 offset += TarHeader.CHKSUMLEN;
2441 hdr.linkFlag = headerBuf[ offset++ ];
2444 TarHeader.parseName( headerBuf, offset, TarHeader.NAMELEN );
2446 offset += TarHeader.NAMELEN;
2448 if ( this.ustarFormat )
2451 TarHeader.parseName( headerBuf, offset, TarHeader.MAGICLEN );
2453 offset += TarHeader.MAGICLEN;
2456 TarHeader.parseName( headerBuf, offset, TarHeader.UNAMELEN );
2458 offset += TarHeader.UNAMELEN;
2461 TarHeader.parseName( headerBuf, offset, TarHeader.GNAMELEN );
2463 offset += TarHeader.GNAMELEN;
2465 hdr.devMajor = (int)
2466 TarHeader.parseOctal( headerBuf, offset, TarHeader.DEVLEN );
2468 offset += TarHeader.DEVLEN;
2470 hdr.devMinor = (int)
2471 TarHeader.parseOctal( headerBuf, offset, TarHeader.DEVLEN );
2477 hdr.magic = new StringBuffer( "" );
2478 hdr.userName = new StringBuffer( "" );
2479 hdr.groupName = new StringBuffer( "" );
2484 * Fill in a TarHeader given only the entry's name.
2486 * @param hdr The TarHeader to fill in.
2487 * @param name The tar entry name.
2490 nameTarHeader( TarHeader hdr, String name )
2492 boolean isDir = name.endsWith( "/" );
2494 this.gnuFormat = false;
2495 this.ustarFormat = true;
2496 this.unixFormat = false;
2502 hdr.name = new StringBuffer( name );
2503 hdr.mode = isDir ? 040755 : 0100644;
2510 (new java.util.Date()).getTime() / 1000;
2513 isDir ? TarHeader.LF_DIR : TarHeader.LF_NORMAL;
2515 hdr.linkName = new StringBuffer( "" );
2516 hdr.userName = new StringBuffer( "" );
2517 hdr.groupName = new StringBuffer( "" );
2526 StringBuffer result = new StringBuffer( 128 );
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() ).
2549 ** Tim feel free to integrate this code here.
2551 ** This code has been placed into the Public Domain.
2552 ** This code was written by David M. Gaskin in 1999.
2557 * Enumerate the contents of a "tar" file.
2559 * Last updated 26th Mar 1999.
2561 * @author David. M. Gaskin.
2562 * @version Version 1.0 Mar 1999
2563 * @since Version 1.0
2567 static class TarEntryEnumerator
2568 implements Enumeration
2571 * The instance on which the enumeration works.
2573 private TarInputStream tis = null;
2576 * Has EndOfFile been reached?
2578 private boolean eof = false;
2581 * The read ahead entry (or <B><I>null</I></B> if no read ahead exists)
2583 private TarEntry readAhead = null;
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.
2593 * @param <B>tis</B> the <B>TarInputStream</B> on which this enumeration has
2597 TarEntryEnumerator( TarInputStream tis )
2604 * Return the next element in the enumeration. This is a required method
2605 * for implementing <B>java.util.Enumeration</B>.
2607 * @return the next Object in the enumeration
2608 * @exception <B>NoSuchElementException</B> should an attempt be made to
2613 throws NoSuchElementException
2615 if ( eof && ( readAhead == null ) )
2616 throw new NoSuchElementException();
2619 if ( readAhead != null )
2633 * Return <B>true</B> if there are more elements in the enumeration.
2635 * @return <B>true</B> if there are more elements in the enumeration.
2644 readAhead = getNext();
2645 if ( readAhead != null )
2652 * Return the next element of <B>null</B> if there is no next element or
2653 * if an error occured.
2655 * @return the next element of <B>null</B> if there is no next element or
2656 * if an error occured.
2663 rc = tis.getNextEntry();
2665 catch ( IOException ex )
2667 // null will be returned but should not occur
2668 ex.printStackTrace();
2678 ** Contributed by "Bay" <bayard@generationjava.com>
2680 ** This code has been placed into the public domain.
2684 // we extend TarOutputStream to have the same type,
2685 // BUT, we don't use ANY methods. It's all about
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.
2694 * @author "Bay" <bayard@generationjava.com>
2698 static class TarGzOutputStream
2699 extends TarOutputStream
2701 private TarOutputStream tos = null;
2702 private GZIPOutputStream gzip = null;
2703 private ByteArrayOutputStream bos = null;
2704 private TarEntry currentEntry = null;
2707 TarGzOutputStream( OutputStream out )
2711 this.gzip = new GZIPOutputStream( out );
2712 this.tos = new TarOutputStream( this.gzip );
2713 this.bos = new ByteArrayOutputStream();
2716 // proxy all methods, but buffer if unknown size
2719 setDebug( boolean b )
2721 this.tos.setDebug(b);
2725 setBufferDebug( boolean b )
2727 this.tos.setBufferDebug(b);
2734 if ( this.currentEntry != null )
2753 return this.tos.getRecordSize();
2757 putNextEntry(TarEntry entry)
2760 if ( entry.getSize() != 0 )
2762 this.tos.putNextEntry( entry );
2766 this.currentEntry = entry;
2774 if(this.currentEntry == null)
2776 this.tos.closeEntry();
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();
2793 if ( this.currentEntry == null )
2795 this.tos.write( b );
2799 this.bos.write( b );
2807 if ( this.currentEntry == null )
2809 this.tos.write( b );
2813 this.bos.write( b );
2818 write( byte[] b, int start, int length )
2821 if ( this.currentEntry == null )
2823 this.tos.write( b, start, length );
2827 this.bos.write( b, start, length );
2833 ** Authored by Timothy Gerard Endres
2834 ** <mailto:time@gjt.org> <http://www.trustice.com>
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.
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.
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.
2851 * @author Timothy Gerard Endres, <time@gjt.org>
2855 static class TarHeader
2857 implements Cloneable
2860 * The length of the name field in a header buffer.
2862 public static final int NAMELEN = 100;
2864 * The offset of the name field in a header buffer.
2866 public static final int NAMEOFFSET = 0;
2868 * The length of the name prefix field in a header buffer.
2870 public static final int PREFIXLEN = 155;
2872 * The offset of the name prefix field in a header buffer.
2874 public static final int PREFIXOFFSET = 345;
2876 * The length of the mode field in a header buffer.
2878 public static final int MODELEN = 8;
2880 * The length of the user id field in a header buffer.
2882 public static final int UIDLEN = 8;
2884 * The length of the group id field in a header buffer.
2886 public static final int GIDLEN = 8;
2888 * The length of the checksum field in a header buffer.
2890 public static final int CHKSUMLEN = 8;
2892 * The length of the size field in a header buffer.
2894 public static final int SIZELEN = 12;
2896 * The length of the magic field in a header buffer.
2898 public static final int MAGICLEN = 8;
2900 * The length of the modification time field in a header buffer.
2902 public static final int MODTIMELEN = 12;
2904 * The length of the user name field in a header buffer.
2906 public static final int UNAMELEN = 32;
2908 * The length of the group name field in a header buffer.
2910 public static final int GNAMELEN = 32;
2912 * The length of the devices field in a header buffer.
2914 public static final int DEVLEN = 8;
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.
2920 public static final byte LF_OLDNORM = 0;
2924 public static final byte LF_NORMAL = (byte) '0';
2928 public static final byte LF_LINK = (byte) '1';
2930 * Symbolic link file type.
2932 public static final byte LF_SYMLINK = (byte) '2';
2934 * Character device file type.
2936 public static final byte LF_CHR = (byte) '3';
2938 * Block device file type.
2940 public static final byte LF_BLK = (byte) '4';
2942 * Directory file type.
2944 public static final byte LF_DIR = (byte) '5';
2946 * FIFO (pipe) file type.
2948 public static final byte LF_FIFO = (byte) '6';
2950 * Contiguous file type.
2952 public static final byte LF_CONTIG = (byte) '7';
2955 * The magic tag representing a POSIX tar archive.
2957 public static final String TMAGIC = "ustar";
2960 * The magic tag representing a GNU tar archive.
2962 public static final String GNU_TMAGIC = "ustar ";
2967 public StringBuffer name;
2969 * The entry's permission mode.
2973 * The entry's user id.
2977 * The entry's group id.
2985 * The entry's modification time.
2987 public long modTime;
2989 * The entry's checksum.
2991 public int checkSum;
2993 * The entry's link flag.
2995 public byte linkFlag;
2997 * The entry's link name.
2999 public StringBuffer linkName;
3001 * The entry's magic tag.
3003 public StringBuffer magic;
3005 * The entry's user name.
3007 public StringBuffer userName;
3009 * The entry's group name.
3011 public StringBuffer groupName;
3013 * The entry's major device number.
3015 public int devMajor;
3017 * The entry's minor device number.
3019 public int devMinor;
3025 this.magic = new StringBuffer( TarHeader.TMAGIC );
3027 this.name = new StringBuffer();
3028 this.linkName = new StringBuffer();
3031 System.getProperty( "user.name", "" );
3033 if ( user.length() > 31 )
3034 user = user.substring( 0, 31 );
3038 this.userName = new StringBuffer( user );
3039 this.groupName = new StringBuffer( "" );
3043 * TarHeaders can be cloned.
3048 TarHeader hdr = null;
3051 hdr = (TarHeader) super.clone();
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;
3064 (this.linkName == null ) ? null
3065 : new StringBuffer( this.linkName.toString() );
3067 (this.magic == null ) ? null
3068 : new StringBuffer( this.magic.toString() );
3070 (this.userName == null ) ? null
3071 : new StringBuffer( this.userName.toString() );
3073 (this.groupName == null ) ? null
3074 : new StringBuffer( this.groupName.toString() );
3075 hdr.devMajor = this.devMajor;
3076 hdr.devMinor = this.devMinor;
3078 catch ( CloneNotSupportedException ex )
3080 ex.printStackTrace( System.err );
3087 * Get the name of this entry.
3089 * @return Teh entry's name.
3094 return this.name.toString();
3098 * Parse an octal string from a header buffer. This is used for the
3099 * file permission mode value.
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.
3107 parseOctal( byte[] header, int offset, int length )
3108 throws InvalidHeaderException
3111 boolean stillPadding = true;
3113 int end = offset + length;
3114 for ( int i = offset ; i < end ; ++i )
3116 if ( header[i] == 0 )
3119 if ( header[i] == (byte) ' ' || header[i] == '0' )
3124 if ( header[i] == (byte) ' ' )
3128 stillPadding = false;
3132 + (header[i] - '0');
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.
3143 * Contributed by Dmitri Tikhonov <dxt2431@yahoo.com>
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.
3150 public static StringBuffer
3151 parseFileName( byte[] header )
3153 StringBuffer result = new StringBuffer( 256 );
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 '/'.
3159 if ( header[345] != 0 )
3161 for ( int i = 345 ; i < 500 && header[i] != 0 ; ++i )
3163 result.append( (char)header[i] );
3166 result.append( "/" );
3169 for ( int i = 0 ; i < 100 && header[i] != 0 ; ++i )
3171 result.append( (char)header[i] );
3178 * Parse an entry name from a header buffer.
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.
3185 public static StringBuffer
3186 parseName( byte[] header, int offset, int length )
3187 throws InvalidHeaderException
3189 StringBuffer result = new StringBuffer( length );
3191 int end = offset + length;
3192 for ( int i = offset ; i < end ; ++i )
3194 if ( header[i] == 0 )
3196 result.append( (char)header[i] );
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.
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.
3217 getFileNameBytes( String newName, byte[] outbuf )
3218 throws InvalidHeaderException
3220 if ( newName.length() > 100 )
3222 // Locate a pathname "break" prior to the maximum name length...
3223 int index = newName.indexOf( "/", newName.length() - 100 );
3225 throw new InvalidHeaderException
3226 ( "file name is greater than 100 characters, " + newName );
3228 // Get the "suffix subpath" of the name.
3229 String name = newName.substring( index + 1 );
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" );
3237 TarHeader.getNameBytes
3238 ( new StringBuffer( name ), outbuf,
3239 TarHeader.NAMEOFFSET, TarHeader.NAMELEN );
3241 TarHeader.getNameBytes
3242 ( new StringBuffer( prefix ), outbuf,
3243 TarHeader.PREFIXOFFSET, TarHeader.PREFIXLEN );
3247 TarHeader.getNameBytes
3248 ( new StringBuffer( newName ), outbuf,
3249 TarHeader.NAMEOFFSET, TarHeader.NAMELEN );
3252 // The offset, regardless of the format, is now the end of the
3253 // original name field.
3255 return TarHeader.NAMELEN;
3259 * Move the bytes from the name StringBuffer into the header's buffer.
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).
3267 getNameBytes( StringBuffer name, byte[] buf, int offset, int length )
3271 for ( i = 0 ; i < length && i < name.length() ; ++i )
3273 buf[ offset + i ] = (byte) name.charAt( i );
3276 for ( ; i < length ; ++i )
3278 buf[ offset + i ] = 0;
3281 return offset + length;
3285 * Parse an octal integer from a header buffer.
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.
3293 getOctalBytes( long value, byte[] buf, int offset, int length )
3295 byte[] result = new byte[ length ];
3297 int idx = length - 1;
3299 buf[ offset + idx ] = 0;
3301 buf[ offset + idx ] = (byte) ' ';
3306 buf[ offset + idx ] = (byte) '0';
3311 for ( long val = value ; idx >= 0 && val > 0 ; --idx )
3313 buf[ offset + idx ] = (byte)
3314 ( (byte) '0' + (byte) (val & 7) );
3319 for ( ; idx >= 0 ; --idx )
3321 buf[ offset + idx ] = (byte) ' ';
3324 return offset + length;
3328 * Parse an octal long integer from a header buffer.
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.
3336 getLongOctalBytes( long value, byte[] buf, int offset, int length )
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;
3345 * Parse the checksum octal integer from a header buffer.
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.
3353 getCheckSumOctalBytes( long value, byte[] buf, int offset, int length )
3355 TarHeader.getOctalBytes( value, buf, offset, length );
3356 buf[ offset + length - 1 ] = (byte) ' ';
3357 buf[ offset + length - 2 ] = 0;
3358 return offset + length;
3364 ** Authored by Timothy Gerard Endres
3365 ** <mailto:time@gjt.org> <http://www.trustice.com>
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.
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.
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
3385 * Kerry Menzel <kmenzel@cfl.rr.com> Contributed the code to support
3386 * file sizes greater than 2GB (longs versus ints).
3389 * @version $Revision: 1.9 $
3390 * @author Timothy Gerard Endres, <time@gjt.org>
3398 static class TarInputStream
3399 extends FilterInputStream
3401 protected boolean debug;
3402 protected boolean hasHitEOF;
3404 protected long entrySize;
3405 protected long entryOffset;
3407 protected byte[] oneBuf;
3408 protected byte[] readBuf;
3410 protected TarBuffer buffer;
3412 protected TarEntry currEntry;
3414 protected EntryFactory eFactory;
3418 TarInputStream( InputStream is )
3420 this( is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE );
3424 TarInputStream( InputStream is, int blockSize )
3426 this( is, blockSize, TarBuffer.DEFAULT_RCDSIZE );
3430 TarInputStream( InputStream is, int blockSize, int recordSize )
3434 this.buffer = new TarBuffer( is, blockSize, recordSize );
3436 this.readBuf = null;
3437 this.oneBuf = new byte[1];
3439 this.hasHitEOF = false;
3440 this.eFactory = null;
3444 * Sets the debugging flag.
3446 * @param debugF True to turn on debugging.
3449 setDebug( boolean debugF )
3451 this.debug = debugF;
3455 * Sets the debugging flag.
3457 * @param debugF True to turn on debugging.
3460 setEntryFactory( EntryFactory factory )
3462 this.eFactory = factory;
3466 * Sets the debugging flag in this stream's TarBuffer.
3468 * @param debugF True to turn on debugging.
3471 setBufferDebug( boolean debug )
3473 this.buffer.setDebug( debug );
3477 * Closes this stream. Calls the TarBuffer's close() method.
3483 this.buffer.close();
3487 * Get the record size being used by this stream's TarBuffer.
3489 * @return The TarBuffer record size.
3494 return this.buffer.getRecordSize();
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.
3505 * @return The number of available bytes for the current entry.
3511 return (int)(this.entrySize - this.entryOffset);
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.
3520 * @param numToSkip The number of bytes to skip.
3521 * @return The actual number of bytes skipped.
3524 skip( long numToSkip )
3528 // This is horribly inefficient, but it ensures that we
3529 // properly skip over bytes via the TarBuffer...
3532 byte[] skipBuf = new byte[ 8 * 1024 ];
3533 long num = numToSkip;
3537 this.read( skipBuf, 0,
3538 ( num > skipBuf.length ? skipBuf.length : (int) num ) );
3540 if ( numRead == -1 )
3546 return ( numToSkip - num );
3550 * Since we do not support marking just yet, we return false.
3561 * Since we do not support marking just yet, we do nothing.
3563 * @param markLimit The limit to mark.
3566 mark( int markLimit )
3571 * Since we do not support marking just yet, we do nothing.
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.
3583 * @returns The current entry offset.
3589 return this.entryOffset;
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.
3598 * @returns The current file pointer.
3604 return ( buffer.getBlockSize() * buffer.getCurrentBlockNum() )
3605 + buffer.getCurrentRecordNum();
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
3618 * @return The next TarEntry in the archive, or null.
3624 if ( this.hasHitEOF )
3627 if ( this.currEntry != null )
3629 long numToSkip = (this.entrySize - this.entryOffset);
3633 ( "TarInputStream: SKIP currENTRY '"
3634 + this.currEntry.getName() + "' SZ "
3635 + this.entrySize + " OFF " + this.entryOffset
3636 + " skipping " + numToSkip + " bytes" );
3638 if ( numToSkip > 0 )
3640 this.skip( numToSkip );
3643 this.readBuf = null;
3646 byte[] headerBuf = this.buffer.readRecord();
3648 if ( headerBuf == null )
3652 System.err.println( "READ NULL RECORD" );
3655 this.hasHitEOF = true;
3657 else if ( this.buffer.isEOFRecord( headerBuf ) )
3661 System.err.println( "READ EOF RECORD" );
3664 this.hasHitEOF = true;
3667 if ( this.hasHitEOF )
3669 this.currEntry = null;
3674 if ( this.eFactory == null )
3676 this.currEntry = new TarEntry( headerBuf );
3681 this.eFactory.createEntry( headerBuf );
3686 ( "TarInputStream: SET CURRENTRY '"
3687 + this.currEntry.getName()
3688 + "' size = " + this.currEntry.getSize() );
3690 this.entryOffset = 0;
3691 this.entrySize = this.currEntry.getSize();
3693 catch ( InvalidHeaderException ex )
3696 this.entryOffset = 0;
3697 this.currEntry = null;
3698 throw new InvalidHeaderException
3699 ( "bad header in block "
3700 + this.buffer.getCurrentBlockNum()
3702 + this.buffer.getCurrentRecordNum()
3703 + ", " + ex.getMessage() );
3707 return this.currEntry;
3711 * Reads a byte from the current tar archive entry.
3713 * This method simply calls read( byte[], int, int ).
3715 * @return The byte read, or -1 at EOF.
3721 int num = this.read( this.oneBuf, 0, 1 );
3725 return (int) this.oneBuf[0];
3729 * Reads bytes from the current tar archive entry.
3731 * This method simply calls read( byte[], int, int ).
3733 * @param buf The buffer into which to place bytes read.
3734 * @return The number of bytes read, or -1 at EOF.
3740 return this.read( buf, 0, buf.length );
3744 * Reads bytes from the current tar archive entry.
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.
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.
3756 read( byte[] buf, int offset, int numToRead )
3761 if ( this.entryOffset >= this.entrySize )
3764 if ( (numToRead + this.entryOffset) > this.entrySize )
3766 numToRead = (int) (this.entrySize - this.entryOffset);
3769 if ( this.readBuf != null )
3771 int sz = ( numToRead > this.readBuf.length )
3772 ? this.readBuf.length : numToRead;
3774 System.arraycopy( this.readBuf, 0, buf, offset, sz );
3776 if ( sz >= this.readBuf.length )
3778 this.readBuf = null;
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;
3793 for ( ; numToRead > 0 ; )
3795 byte[] rec = this.buffer.readRecord();
3799 throw new IOException
3800 ( "unexpected EOF with " + numToRead + " bytes unread" );
3804 int recLen = rec.length;
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 );
3815 System.arraycopy( rec, 0, buf, offset, recLen );
3823 this.entryOffset += totalRead;
3829 * Copies the contents of the current tar archive entry directly into
3832 * @param out The OutputStream into which to write the entry's data.
3835 copyEntryContents( OutputStream out )
3838 byte[] buf = new byte[ 32 * 1024 ];
3842 int numRead = this.read( buf, 0, buf.length );
3843 if ( numRead == -1 )
3845 out.write( buf, 0, numRead );
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().
3856 interface EntryFactory
3859 createEntry( String name );
3862 createEntry( File path )
3863 throws InvalidHeaderException;
3866 createEntry( byte[] headerBuf )
3867 throws InvalidHeaderException;
3872 implements EntryFactory
3875 createEntry( String name )
3877 return new TarEntry( name );
3881 createEntry( File path )
3882 throws InvalidHeaderException
3884 return new TarEntry( path );
3888 createEntry( byte[] headerBuf )
3889 throws InvalidHeaderException
3891 return new TarEntry( headerBuf );
3899 ** Authored by Timothy Gerard Endres
3900 ** <mailto:time@gjt.org> <http://www.trustice.com>
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.
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.
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().
3919 * Kerry Menzel <kmenzel@cfl.rr.com> Contributed the code to support
3920 * file sizes greater than 2GB (longs versus ints).
3922 * @version $Revision: 1.8 $
3923 * @author Timothy Gerard Endres, <time@gjt.org>
3931 static class TarOutputStream
3932 extends FilterOutputStream
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;
3945 TarOutputStream( OutputStream os )
3947 this( os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE );
3951 TarOutputStream( OutputStream os, int blockSize )
3953 this( os, blockSize, TarBuffer.DEFAULT_RCDSIZE );
3957 TarOutputStream( OutputStream os, int blockSize, int recordSize )
3961 this.buffer = new TarBuffer( os, blockSize, recordSize );
3965 this.assemBuf = new byte[ recordSize ];
3966 this.recordBuf = new byte[ recordSize ];
3967 this.oneBuf = new byte[1];
3971 * Sets the debugging flag.
3973 * @param debugF True to turn on debugging.
3976 setDebug( boolean debugF )
3978 this.debug = debugF;
3982 * Sets the debugging flag in this stream's TarBuffer.
3984 * @param debugF True to turn on debugging.
3987 setBufferDebug( boolean debug )
3989 this.buffer.setDebug( debug );
3993 * Ends the TAR archive without closing the underlying OutputStream.
3994 * The result is that the EOF record of nulls is written.
4001 this.writeEOFRecord();
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().
4015 this.buffer.close();
4019 * Get the record size being used by this stream's TarBuffer.
4021 * @return The TarBuffer record size.
4026 return this.buffer.getRecordSize();
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.
4038 * @param entry The TarEntry to be written to the archive.
4041 putNextEntry( TarEntry entry )
4044 StringBuffer name = entry.getHeader().name;
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
4054 if ( ( entry.isUnixTarFormat()
4055 && name.length() > TarHeader.NAMELEN )
4057 ( ! entry.isUnixTarFormat()
4058 && name.length() > (TarHeader.NAMELEN + TarHeader.PREFIXLEN) )
4061 throw new InvalidHeaderException
4064 + "' is too long ( "
4067 + ( entry.isUnixTarFormat()
4069 : (TarHeader.NAMELEN + TarHeader.PREFIXLEN) )
4073 entry.writeEntryHeader( this.recordBuf );
4075 this.buffer.writeRecord( this.recordBuf );
4079 if ( entry.isDirectory() )
4082 this.currSize = entry.getSize();
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.
4098 if ( this.assemLen > 0 )
4100 for ( int i = this.assemLen ; i < this.assemBuf.length ; ++i )
4101 this.assemBuf[i] = 0;
4103 this.buffer.writeRecord( this.assemBuf );
4105 this.currBytes += this.assemLen;
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" );
4117 * Writes a byte to the current tar archive entry.
4119 * This method simply calls read( byte[], int, int ).
4121 * @param b The byte written.
4127 this.oneBuf[0] = (byte) b;
4128 this.write( this.oneBuf, 0, 1 );
4132 * Writes bytes to the current tar archive entry.
4134 * This method simply calls read( byte[], int, int ).
4136 * @param wBuf The buffer to write to the archive.
4137 * @return The number of bytes read, or -1 at EOF.
4140 write( byte[] wBuf )
4143 this.write( wBuf, 0, wBuf.length );
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.
4155 * This method simply calls read( byte[], int, int ).
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.
4162 write( byte[] wBuf, int wOffset, int numToWrite )
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" );
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.
4178 if ( this.assemLen > 0 )
4180 if ( (this.assemLen + numToWrite ) >= this.recordBuf.length )
4182 int aLen = this.recordBuf.length - this.assemLen;
4185 ( this.assemBuf, 0, this.recordBuf, 0, this.assemLen );
4188 ( wBuf, wOffset, this.recordBuf, this.assemLen, aLen );
4190 this.buffer.writeRecord( this.recordBuf );
4192 this.currBytes += this.recordBuf.length;
4198 else // ( (this.assemLen + numToWrite ) < this.recordBuf.length )
4201 ( wBuf, wOffset, this.assemBuf,
4202 this.assemLen, numToWrite );
4203 wOffset += numToWrite;
4204 this.assemLen += numToWrite;
4205 numToWrite -= numToWrite;
4210 // When we get here we have EITHER:
4211 // o An empty "assemble" buffer.
4212 // o No bytes to write (numToWrite == 0)
4215 for ( ; numToWrite > 0 ; )
4217 if ( numToWrite < this.recordBuf.length )
4220 ( wBuf, wOffset, this.assemBuf, this.assemLen, numToWrite );
4221 this.assemLen += numToWrite;
4225 this.buffer.writeRecord( wBuf, wOffset );
4227 long num = this.recordBuf.length;
4228 this.currBytes += num;
4235 * Write an EOF (end of archive) record to the tar archive.
4236 * An EOF record consists of a record of all zeros.
4242 for ( int i = 0 ; i < this.recordBuf.length ; ++i )
4243 this.recordBuf[i] = 0;
4244 this.buffer.writeRecord( this.recordBuf );
4250 ** Authored by Timothy Gerard Endres
4251 ** <mailto:time@gjt.org> <http://www.trustice.com>
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.
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.
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.
4274 * Display a progress message.
4276 * @param msg The message to display.
4280 showTarProgressMessage( String msg );
4284 ** Authored by Timothy Gerard Endres
4285 ** <mailto:time@gjt.org> <http://www.trustice.com>
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.
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.
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().
4311 * Return true if the file should be translated as ASCII.
4313 * @param f The file to be checked to see if it need ASCII translation.
4317 isAsciiFile( File f )
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
4327 * @param name The name of the file to be checked to see if it need ASCII
4332 isAsciiFile( String name )
4339 ** Authored by Timothy Gerard Endres
4340 ** <mailto:time@gjt.org> <http://www.trustice.com>
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.
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.
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.
4361 * usage: com.ice.tar.tar has three basic modes:
4363 * com.ice.tar -c [options] archive files...
4364 * Create new archive containing files.
4366 * com.ice.tar -t [options] archive
4367 * List contents of tar archive
4369 * com.ice.tar -x [options] archive
4370 * Extract contents of tar archive.
4373 * -f file, use 'file' as the tar archive
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
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.
4394 * Written by Tim Endres
4395 * This software has been placed into the public domain.
4398 * @version $Revision: 1.10 $
4399 * @author Timothy Gerard Endres, <time@gjt.org>
4405 implements TarProgressDisplay
4408 * Flag that determines if debugging information is displayed.
4410 private boolean debug;
4412 * Flag that determines if verbose feedback is provided.
4414 private boolean verbose;
4416 * Flag that determines if IO is GZIP-ed ('-z' option).
4418 private boolean compressed;
4420 * True if we are listing the archive. False if writing or extracting.
4422 private boolean listingArchive;
4424 * True if we are writing the archive. False if we are extracting it.
4426 private boolean writingArchive;
4428 * True if we are writing an old UNIX archive format (sets entry format).
4430 private boolean unixArchiveFormat;
4432 * True if we are not to overwrite existing files.
4434 private boolean keepOldFiles;
4436 * True if we are to convert ASCII text files from local line endings
4437 * to the UNIX standard '\n'.
4439 private boolean asciiTranslate;
4441 * True if a MIME file has been loaded with the '--mime' option.
4443 private boolean mimeFileLoaded;
4446 * The archive name provided on the command line, null if stdio.
4448 private String archiveName;
4451 * The blocksize to use for the tar archive IO. Set by the '-b' option.
4453 private int blockSize;
4456 * The userId to use for files written to archives. Set by '-U' option.
4460 * The userName to use for files written to archives. Set by '-u' option.
4462 private String userName;
4464 * The groupId to use for files written to archives. Set by '-G' option.
4466 private int groupId;
4468 * The groupName to use for files written to archives. Set by '-g' option.
4470 private String groupName;
4474 * The main entry point of the tar class.
4477 main( String argv[] )
4479 tar app = new tar();
4481 app.instanceMain( argv );
4485 * Establishes the default userName with the 'user.name' property.
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;
4500 this.blockSize = TarBuffer.DEFAULT_BLKSIZE;
4502 String sysUserName =
4503 System.getProperty( "user.name" );
4507 ( (sysUserName == null) ? "" : sysUserName );
4510 this.groupName = "";
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.
4519 instanceMain( String argv[] )
4521 TarArchive archive = null;
4523 int argIdx = this.processArguments( argv );
4525 if ( writingArchive ) // WRITING
4527 OutputStream outStream = System.out;
4529 if ( this.archiveName != null
4530 && ! this.archiveName.equals( "-" ) )
4533 outStream = new FileOutputStream( this.archiveName );
4535 catch ( IOException ex )
4538 ex.printStackTrace( System.err );
4542 if ( outStream != null )
4544 if ( this.compressed )
4547 outStream = new GZIPOutputStream( outStream );
4549 catch ( IOException ex )
4552 ex.printStackTrace( System.err );
4556 archive = new TarArchive( outStream, this.blockSize );
4559 else // EXTRACING OR LISTING
4561 InputStream inStream = System.in;
4563 if ( this.archiveName != null
4564 && ! this.archiveName.equals( "-" ) )
4567 inStream = new FileInputStream( this.archiveName );
4569 catch ( IOException ex )
4572 ex.printStackTrace( System.err );
4576 if ( inStream != null )
4578 if ( this.compressed )
4581 inStream = new GZIPInputStream( inStream );
4583 catch ( IOException ex )
4586 ex.printStackTrace( System.err );
4590 archive = new TarArchive( inStream, this.blockSize );
4594 if ( archive != null ) // SET ARCHIVE OPTIONS
4596 archive.setDebug( this.debug );
4597 archive.setVerbose( this.verbose );
4598 archive.setTarProgressDisplay( this );
4599 archive.setKeepOldFiles( this.keepOldFiles );
4600 archive.setAsciiTranslation( this.asciiTranslate );
4602 archive.setUserInfo(
4603 this.userId, this.userName,
4604 this.groupId, this.groupName );
4607 if ( archive == null )
4609 System.err.println( "no processing due to errors" );
4611 else if ( this.writingArchive ) // WRITING
4613 for ( ; argIdx < argv.length ; ++argIdx )
4616 File f = new File( argv[ argIdx ] );
4618 TarEntry entry = new TarEntry( f );
4620 if ( this.unixArchiveFormat )
4621 entry.setUnixTarFormat();
4623 entry.setUSTarFormat();
4625 archive.writeEntry( entry, true );
4627 catch ( IOException ex )
4629 ex.printStackTrace( System.err );
4633 else if ( this.listingArchive ) // LISTING
4636 archive.listContents();
4638 catch ( InvalidHeaderException ex )
4640 ex.printStackTrace( System.err );
4642 catch ( IOException ex )
4644 ex.printStackTrace( System.err );
4650 System.getProperty( "user.dir", null );
4652 File destDir = new File( userDir );
4653 if ( ! destDir.exists() )
4655 if ( ! destDir.mkdirs() )
4658 Throwable ex = new Throwable
4659 ( "ERROR, mkdirs() on '" + destDir.getPath()
4660 + "' returned false." );
4661 ex.printStackTrace( System.err );
4665 if ( destDir != null )
4668 archive.extractContents( destDir );
4670 catch ( InvalidHeaderException ex )
4672 ex.printStackTrace( System.err );
4674 catch ( IOException ex )
4676 ex.printStackTrace( System.err );
4681 if ( archive != null ) // CLOSE ARCHIVE
4684 archive.closeArchive();
4686 catch ( IOException ex )
4688 ex.printStackTrace( System.err );
4694 * Process arguments, handling options, and return the index of the
4695 * first non-option argument.
4697 * @return The index of the first non-option argument.
4701 processArguments( String args[] )
4704 boolean gotOP = false;
4706 for ( ; idx < args.length ; ++idx )
4708 String arg = args[ idx ];
4710 if ( ! arg.startsWith( "-" ) )
4713 if ( arg.startsWith( "--" ) )
4715 if ( arg.equals( "--usage" ) )
4720 else if ( arg.equals( "--version" ) )
4728 ( "unknown option: " + arg );
4733 else for ( int cIdx = 1 ; cIdx < arg.length() ; ++cIdx )
4735 char ch = arg.charAt( cIdx );
4742 else if ( ch == 'f' )
4744 this.archiveName = args[ ++idx ];
4746 else if ( ch == 'z' )
4748 this.compressed = true;
4750 else if ( ch == 'c' )
4753 this.writingArchive = true;
4754 this.listingArchive = false;
4756 else if ( ch == 'x' )
4759 this.writingArchive = false;
4760 this.listingArchive = false;
4762 else if ( ch == 't' )
4765 this.writingArchive = false;
4766 this.listingArchive = true;
4768 else if ( ch == 'k' )
4770 this.keepOldFiles = true;
4772 else if ( ch == 'o' )
4774 this.unixArchiveFormat = true;
4776 else if ( ch == 'b' )
4779 int blks = Integer.parseInt( args[ ++idx ] );
4781 ( blks * TarBuffer.DEFAULT_RCDSIZE );
4783 catch ( NumberFormatException ex )
4785 ex.printStackTrace( System.err );
4788 else if ( ch == 'u' )
4790 this.userName = args[ ++idx ];
4792 else if ( ch == 'U' )
4794 String idStr = args[ ++idx ];
4796 this.userId = Integer.parseInt( idStr );
4798 catch ( NumberFormatException ex )
4801 ex.printStackTrace( System.err );
4804 else if ( ch == 'g' )
4806 this.groupName = args[ ++idx ];
4808 else if ( ch == 'G' )
4810 String idStr = args[ ++idx ];
4812 this.groupId = Integer.parseInt( idStr );
4814 catch ( NumberFormatException ex )
4817 ex.printStackTrace( System.err );
4820 else if ( ch == 'v' )
4822 this.verbose = true;
4824 else if ( ch == 'D' )
4831 ( "unknown option: " + ch );
4841 ( "you must specify an operation option (c, x, or t)" );
4849 // I N T E R F A C E TarProgressDisplay
4852 * Display progress information by printing it to System.out.
4856 showTarProgressMessage( String msg )
4858 System.out.println( msg );
4862 * Print version information.
4869 ( "Release 2.4 - $Revision: 1.10 $ $Name: $" );
4873 * Print usage information.
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( "" );