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.*;
21 import javax.activation.*;
26 * Special class designed to parse a Tar archive VERY FAST.
27 * This class is not a general Tar archive solution because
28 * it does not accomodate TarBuffer, or blocking. It does not
29 * allow you to read the entries either. This would not be
30 * difficult to add in a subclass.
32 * The real purpose of this class is that there are folks out
33 * there who wish to parse an ENORMOUS tar archive, and maybe
34 * only want to know the filenames, or they wish to locate the
35 * offset of a particular entry so that can process that entry
38 * @author Timothy Gerard Endres, <time@gjt.org>
45 private boolean debug = false;
46 private boolean hasHitEOF = false;
47 private TarEntry currEntry = null;
48 private InputStream inStream = null;
49 private int recordSize = TarBuffer.DEFAULT_RCDSIZE;
53 FastTarStream( InputStream in )
55 this( in, TarBuffer.DEFAULT_RCDSIZE );
59 FastTarStream( InputStream in, int recordSize )
62 this.hasHitEOF = false;
63 this.currEntry = null;
64 this.recordSize = recordSize;
68 setDebug( boolean debug )
81 * Here I have tried skipping the entry size, I have tried
82 * skipping entrysize - header size,
83 * entrysize + header, and all seem to skip to some bizzarelocation!
85 if ( this.currEntry != null && this.currEntry.getSize() > 0 )
87 // Need to round out the number of records to be read to skip entry...
89 ( (int)this.currEntry.getSize() + (this.recordSize - 1) )
94 this.inStream.skip( numRecords * this.recordSize );
98 byte[] headerBuf = new byte[ this.recordSize ];
100 // NOTE Apparently (with GZIPInputStream maybe?) we are able to
101 // read less then record size bytes in any given read(). So,
102 // we have to be persistent.
105 for ( int bytesNeeded = this.recordSize ; bytesNeeded > 0 ; )
107 int numRead = this.inStream.read( headerBuf, bufIndex, bytesNeeded );
111 this.hasHitEOF = true;
116 bytesNeeded -= numRead;
119 // Check for "EOF block" of all zeros
120 if ( ! this.hasHitEOF )
122 this.hasHitEOF = true;
123 for ( int i = 0 ; i < headerBuf.length ; ++i )
125 if ( headerBuf[i] != 0 )
127 this.hasHitEOF = false;
133 if ( this.hasHitEOF )
135 this.currEntry = null;
140 this.currEntry = new TarEntry( headerBuf );
144 byte[] by = new byte[ headerBuf.length ];
145 for(int i = 0; i < headerBuf.length; i++)
147 by[i] = ( headerBuf[i] == 0? 20: headerBuf[i] );
149 String s = new String( by );
150 System.out.println( "\n" + s );
153 if ( ! ( headerBuf[257] == 'u' &&headerBuf[258] == 's'
154 && headerBuf[259] == 't' &&headerBuf[260] == 'a'
155 && headerBuf[261] == 'r' ) )
157 throw new InvalidHeaderException
158 ( "header magic is not'ustar', but '"
159 + headerBuf[257] +headerBuf[258] + headerBuf[259]
160 + headerBuf[260] +headerBuf[261] + "', or (dec) "
161 +((int)headerBuf[257]) + ", "
162 +((int)headerBuf[258]) + ", "
163 +((int)headerBuf[259]) + ", "
164 +((int)headerBuf[260]) + ", "
165 +((int)headerBuf[261]) );
168 catch ( InvalidHeaderException ex )
170 this.currEntry = null;
175 return this.currEntry;
179 main( String[] args )
181 boolean debug = false;
182 InputStream in = null;
184 String fileName = args[0];
188 if ( args.length > 0 )
190 if ( args[idx].equals( "-d" ) )
196 if ( args[idx].endsWith( ".gz" )
197 || args[idx].endsWith( ".tgz" ) )
199 in = new GZIPInputStream( new FileInputStream( args[idx] ) );
203 in = new FileInputStream( args[idx] );
211 FastTarStream fts = new FastTarStream( in );
212 fts.setDebug( debug );
217 StringBuffer padBuf = new StringBuffer(128);
220 TarEntry entry = fts.getNextEntry();
224 if ( entry.isDirectory() )
227 System.out.print( "D " );
231 padBuf.append( entry.getName() );
232 padBuf.setLength( padBuf.length() - 1 ); // drop '/'
233 if ( padBuf.length() > nameWidth )
234 padBuf.setLength( nameWidth );
235 for ( ; padBuf.length() < nameWidth ; )
236 padBuf.append( '_' );
238 padBuf.append( '_' );
239 System.out.print( padBuf.toString() );
243 for ( ; padBuf.length() < sizeWidth ; )
244 padBuf.insert( 0, '_' );
246 padBuf.append( ' ' );
247 System.out.print( padBuf.toString() );
251 padBuf.append( entry.getUserName() );
252 if ( padBuf.length() > userWidth )
253 padBuf.setLength( userWidth );
254 for ( ; padBuf.length() < userWidth ; )
255 padBuf.append( ' ' );
257 System.out.print( padBuf.toString() );
262 System.out.print( "F " );
266 padBuf.append( entry.getName() );
267 if ( padBuf.length() > nameWidth )
268 padBuf.setLength( nameWidth );
269 for ( ; padBuf.length() < nameWidth ; )
270 padBuf.append( ' ' );
272 padBuf.append( ' ' );
273 System.out.print( padBuf.toString() );
277 padBuf.append( entry.getSize() );
278 if ( padBuf.length() > sizeWidth )
279 padBuf.setLength( sizeWidth );
280 for ( ; padBuf.length() < sizeWidth ; )
281 padBuf.insert( 0, ' ' );
283 padBuf.append( ' ' );
284 System.out.print( padBuf.toString() );
288 padBuf.append( entry.getUserName() );
289 if ( padBuf.length() > userWidth )
290 padBuf.setLength( userWidth );
291 for ( ; padBuf.length() < userWidth ; )
292 padBuf.append( ' ' );
294 System.out.print( padBuf.toString() );
297 System.out.println( "" );
300 catch ( IOException ex )
302 ex.printStackTrace( System.err );
309 ** Authored by Timothy Gerard Endres
310 ** <mailto:time@gjt.org> <http://www.trustice.com>
312 ** This work has been placed into the public domain.
313 ** You may use this work in any way and for any purpose you wish.
315 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
316 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
317 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
318 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
319 ** REDISTRIBUTION OF THIS SOFTWARE.
324 * This exception is used to indicate that there is a problem
325 * with a TAR archive header.
329 InvalidHeaderException extends IOException
333 InvalidHeaderException()
339 InvalidHeaderException( String msg )
347 ** Authored by Timothy Gerard Endres
348 ** <mailto:time@gjt.org> <http://www.trustice.com>
350 ** This work has been placed into the public domain.
351 ** You may use this work in any way and for any purpose you wish.
353 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
354 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
355 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
356 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
357 ** REDISTRIBUTION OF THIS SOFTWARE.
362 * The TarArchive class implements the concept of a
363 * tar archive. A tar archive is a series of entries, each of
364 * which represents a file system object. Each entry in
365 * the archive consists of a header record. Directory entries
366 * consist only of the header record, and are followed by entries
367 * for the directory's contents. File entries consist of a
368 * header record followed by the number of records needed to
369 * contain the file's contents. All entries are written on
370 * record boundaries. Records are 512 bytes long.
372 * TarArchives are instantiated in either read or write mode,
373 * based upon whether they are instantiated with an InputStream
374 * or an OutputStream. Once instantiated TarArchives read/write
375 * mode can not be changed.
377 * There is currently no support for random access to tar archives.
378 * However, it seems that subclassing TarArchive, and using the
379 * TarBuffer.getCurrentRecordNum() and TarBuffer.getCurrentBlockNum()
380 * methods, this would be rather trvial.
382 * @version $Revision: 1.15 $
383 * @author Timothy Gerard Endres, <time@gjt.org>
391 TarArchive extends Object
393 protected boolean verbose;
394 protected boolean debug;
395 protected boolean keepOldFiles;
396 protected boolean asciiTranslate;
398 protected int userId;
399 protected String userName;
400 protected int groupId;
401 protected String groupName;
403 protected String rootPath;
404 protected String tempPath;
405 protected String pathPrefix;
407 protected int recordSize;
408 protected byte[] recordBuf;
410 protected TarInputStream tarIn;
411 protected TarOutputStream tarOut;
413 protected TarTransFileTyper transTyper;
414 protected TarProgressDisplay progressDisplay;
418 * The InputStream based constructors create a TarArchive for the
419 * purposes of e'x'tracting or lis't'ing a tar archive. Thus, use
420 * these constructors when you wish to extract files from or list
421 * the contents of an existing tar archive.
425 TarArchive( InputStream inStream )
427 this( inStream, TarBuffer.DEFAULT_BLKSIZE );
431 TarArchive( InputStream inStream, int blockSize )
433 this( inStream, blockSize, TarBuffer.DEFAULT_RCDSIZE );
437 TarArchive( InputStream inStream, int blockSize, int recordSize )
439 this.tarIn = new TarInputStream( inStream, blockSize, recordSize );
440 this.initialize( recordSize );
444 * The OutputStream based constructors create a TarArchive for the
445 * purposes of 'c'reating a tar archive. Thus, use these constructors
446 * when you wish to create a new tar archive and write files into it.
450 TarArchive( OutputStream outStream )
452 this( outStream, TarBuffer.DEFAULT_BLKSIZE );
456 TarArchive( OutputStream outStream, int blockSize )
458 this( outStream, blockSize, TarBuffer.DEFAULT_RCDSIZE );
462 TarArchive( OutputStream outStream, int blockSize, int recordSize )
464 this.tarOut = new TarOutputStream( outStream, blockSize, recordSize );
465 this.initialize( recordSize );
469 * Common constructor initialization code.
473 initialize( int recordSize )
475 this.rootPath = null;
476 this.pathPrefix = null;
477 this.tempPath = System.getProperty( "user.dir" );
485 this.verbose = false;
486 this.keepOldFiles = false;
487 this.progressDisplay = null;
490 new byte[ this.getRecordSize() ];
494 * Set the debugging flag.
496 * @param debugF The new debug setting.
500 setDebug( boolean debugF )
503 if ( this.tarIn != null )
504 this.tarIn.setDebug( debugF );
505 else if ( this.tarOut != null )
506 this.tarOut.setDebug( debugF );
510 * Returns the verbosity setting.
512 * @return The current verbosity setting.
522 * Set the verbosity flag.
524 * @param verbose The new verbosity setting.
528 setVerbose( boolean verbose )
530 this.verbose = verbose;
534 * Set the current progress display interface. This allows the
535 * programmer to use a custom class to display the progress of
536 * the archive's processing.
538 * @param display The new progress display interface.
539 * @see TarProgressDisplay
543 setTarProgressDisplay( TarProgressDisplay display )
545 this.progressDisplay = display;
549 * Set the flag that determines whether existing files are
550 * kept, or overwritten during extraction.
552 * @param keepOldFiles If true, do not overwrite existing files.
556 setKeepOldFiles( boolean keepOldFiles )
558 this.keepOldFiles = keepOldFiles;
562 * Set the ascii file translation flag. If ascii file translatio
563 * is true, then the MIME file type will be consulted to determine
564 * if the file is of type 'text/*'. If the MIME type is not found,
565 * then the TransFileTyper is consulted if it is not null. If
566 * either of these two checks indicates the file is an ascii text
567 * file, it will be translated. The translation converts the local
568 * operating system's concept of line ends into the UNIX line end,
569 * '\n', which is the defacto standard for a TAR archive. This makes
570 * text files compatible with UNIX, and since most tar implementations
571 * for other platforms, compatible with most other platforms.
573 * @param asciiTranslate If true, translate ascii text files.
577 setAsciiTranslation( boolean asciiTranslate )
579 this.asciiTranslate = asciiTranslate;
583 * Set the object that will determine if a file is of type
584 * ascii text for translation purposes.
586 * @param transTyper The new TransFileTyper object.
590 setTransFileTyper( TarTransFileTyper transTyper )
592 this.transTyper = transTyper;
596 * Set user and group information that will be used to fill in the
597 * tar archive's entry headers. Since Java currently provides no means
598 * of determining a user name, user id, group name, or group id for
599 * a given File, TarArchive allows the programmer to specify values
600 * to be used in their place.
602 * @param userId The user Id to use in the headers.
603 * @param userName The user name to use in the headers.
604 * @param groupId The group id to use in the headers.
605 * @param groupName The group name to use in the headers.
610 int userId, String userName,
611 int groupId, String groupName )
613 this.userId = userId;
614 this.userName = userName;
615 this.groupId = groupId;
616 this.groupName = groupName;
620 * Get the user id being used for archive entry headers.
622 * @return The current user id.
632 * Get the user name being used for archive entry headers.
634 * @return The current user name.
640 return this.userName;
644 * Get the group id being used for archive entry headers.
646 * @return The current group id.
656 * Get the group name being used for archive entry headers.
658 * @return The current group name.
664 return this.groupName;
668 * Get the current temporary directory path. Because Java's
669 * File did not support temporary files until version 1.2,
670 * TarArchive manages its own concept of the temporary
671 * directory. The temporary directory defaults to the
672 * 'user.dir' System property.
674 * @return The current temporary directory path.
680 return this.tempPath;
684 * Set the current temporary directory path.
686 * @param path The new temporary directory path.
690 setTempDirectory( String path )
692 this.tempPath = path;
696 * Get the archive's record size. Because of its history, tar
697 * supports the concept of buffered IO consisting of BLOCKS of
698 * RECORDS. This allowed tar to match the IO characteristics of
699 * the physical device being used. Of course, in the Java world,
700 * this makes no sense, WITH ONE EXCEPTION - archives are expected
701 * to be propertly "blocked". Thus, all of the horrible TarBuffer
702 * support boils down to simply getting the "boundaries" correct.
704 * @return The record size this archive is using.
710 if ( this.tarIn != null )
712 return this.tarIn.getRecordSize();
714 else if ( this.tarOut != null )
716 return this.tarOut.getRecordSize();
719 return TarBuffer.DEFAULT_RCDSIZE;
723 * Get a path for a temporary file for a given File. The
724 * temporary file is NOT created. The algorithm attempts
725 * to handle filename collisions so that the name is
728 * @return The temporary file's path.
732 getTempFilePath( File eFile )
735 this.tempPath + File.separator
736 + eFile.getName() + ".tmp";
738 for ( int i = 1 ; i < 5 ; ++i )
740 File f = new File( pathStr );
746 this.tempPath + File.separator
747 + eFile.getName() + "-" + i + ".tmp";
754 * Close the archive. This simply calls the underlying
755 * tar stream's close() method.
762 if ( this.tarIn != null )
766 else if ( this.tarOut != null )
773 * Perform the "list" command and list the contents of the archive.
774 * NOTE That this method uses the progress display to actually list
775 * the conents. If the progress display is not set, nothing will be
781 throws IOException, InvalidHeaderException
785 TarEntry entry = this.tarIn.getNextEntry();
792 System.err.println( "READ EOF RECORD" );
797 if ( this.progressDisplay != null )
798 this.progressDisplay.showTarProgressMessage
804 * Perform the "extract" command and extract the contents of the archive.
806 * @param destDir The destination directory into which to extract.
810 extractContents( File destDir )
811 throws IOException, InvalidHeaderException
815 TarEntry entry = this.tarIn.getNextEntry();
821 System.err.println( "READ EOF RECORD" );
826 this.extractEntry( destDir, entry );
831 * Extract an entry from the archive. This method assumes that the
832 * tarIn stream has been properly set with a call to getNextEntry().
834 * @param destDir The destination directory into which to extract.
835 * @param entry The TarEntry returned by tarIn.getNextEntry().
839 extractEntry( File destDir, TarEntry entry )
844 if ( this.progressDisplay != null )
845 this.progressDisplay.showTarProgressMessage
849 String name = entry.getName();
850 name = name.replace( '/', File.separatorChar );
852 File destFile = new File( destDir, name );
854 if ( entry.isDirectory() )
856 if ( ! destFile.exists() )
858 if ( ! destFile.mkdirs() )
860 throw new IOException
861 ( "error making directory path '"
862 + destFile.getPath() + "'" );
868 File subDir = new File( destFile.getParent() );
870 if ( ! subDir.exists() )
872 if ( ! subDir.mkdirs() )
874 throw new IOException
875 ( "error making directory path '"
876 + subDir.getPath() + "'" );
880 if ( this.keepOldFiles && destFile.exists() )
884 if ( this.progressDisplay != null )
885 this.progressDisplay.showTarProgressMessage
886 ( "not overwriting " + entry.getName() );
891 boolean asciiTrans = false;
893 FileOutputStream out =
894 new FileOutputStream( destFile );
897 PrintWriter outw = null;
900 outw = new PrintWriter( out );
903 byte[] rdbuf = new byte[32 * 1024];
907 int numRead = this.tarIn.read( rdbuf );
914 for ( int off = 0, b = 0 ; b < numRead ; ++b )
916 if ( rdbuf[ b ] == 10 )
918 String s = new String
919 ( rdbuf, off, (b - off) );
929 out.write( rdbuf, 0, numRead );
942 * Write an entry to the archive. This method will call the putNextEntry()
943 * and then write the contents of the entry, and finally call closeEntry()
944 * for entries that are files. For directories, it will call putNextEntry(),
945 * and then, if the recurse flag is true, process each entry that is a
946 * child of the directory.
948 * @param entry The TarEntry representing the entry to write to the archive.
949 * @param recurse If true, process the children of directory entries.
953 writeEntry( TarEntry oldEntry, boolean recurse )
956 boolean asciiTrans = false;
957 boolean unixArchiveFormat = oldEntry.isUnixTarFormat();
960 File eFile = oldEntry.getFile();
962 // Work on a copy of the entry so we can manipulate it.
963 // Note that we must distinguish how the entry was constructed.
965 TarEntry entry = (TarEntry) oldEntry.clone();
969 if ( this.progressDisplay != null )
970 this.progressDisplay.showTarProgressMessage
975 String newName = null;
977 if ( this.rootPath != null )
979 if ( entry.getName().startsWith( this.rootPath ) )
982 entry.getName().substring
983 ( this.rootPath.length() + 1 );
987 if ( this.pathPrefix != null )
989 newName = (newName == null)
990 ? this.pathPrefix + "/" + entry.getName()
991 : this.pathPrefix + "/" + newName;
994 if ( newName != null )
996 entry.setName( newName );
999 this.tarOut.putNextEntry( entry );
1001 if ( entry.isDirectory() )
1005 TarEntry[] list = entry.getDirectoryEntries();
1007 for ( int i = 0 ; i < list.length ; ++i )
1009 TarEntry dirEntry = list[i];
1011 if ( unixArchiveFormat )
1012 dirEntry.setUnixTarFormat();
1014 this.writeEntry( dirEntry, recurse );
1020 FileInputStream in =
1021 new FileInputStream( eFile );
1023 byte[] eBuf = new byte[ 32 * 1024 ];
1026 int numRead = in.read( eBuf, 0, eBuf.length );
1028 if ( numRead == -1 )
1031 this.tarOut.write( eBuf, 0, numRead );
1036 if ( tFile != null )
1041 this.tarOut.closeEntry();
1049 ** Authored by Timothy Gerard Endres
1050 ** <mailto:time@gjt.org> <http://www.trustice.com>
1052 ** This work has been placed into the public domain.
1053 ** You may use this work in any way and for any purpose you wish.
1055 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
1056 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
1057 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
1058 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
1059 ** REDISTRIBUTION OF THIS SOFTWARE.
1064 * The TarBuffer class implements the tar archive concept
1065 * of a buffered input stream. This concept goes back to the
1066 * days of blocked tape drives and special io devices. In the
1067 * Java universe, the only real function that this class
1068 * performs is to ensure that files have the correct "block"
1069 * size, or other tars will complain.
1071 * You should never have a need to access this class directly.
1072 * TarBuffers are created by Tar IO Streams.
1074 * @version $Revision: 1.10 $
1075 * @author Timothy Gerard Endres,
1076 * <a href="mailto:time@gjt.org">time@trustice.com</a>.
1081 static class TarBuffer
1084 public static final int DEFAULT_RCDSIZE = ( 512 );
1085 public static final int DEFAULT_BLKSIZE = ( DEFAULT_RCDSIZE * 20 );
1087 private InputStream inStream;
1088 private OutputStream outStream;
1090 private byte[] blockBuffer;
1091 private int currBlkIdx;
1092 private int currRecIdx;
1093 private int blockSize;
1094 private int recordSize;
1095 private int recsPerBlock;
1097 private boolean debug;
1101 TarBuffer( InputStream inStream )
1103 this( inStream, TarBuffer.DEFAULT_BLKSIZE );
1107 TarBuffer( InputStream inStream, int blockSize )
1109 this( inStream, blockSize, TarBuffer.DEFAULT_RCDSIZE );
1113 TarBuffer( InputStream inStream, int blockSize, int recordSize )
1115 this.inStream = inStream;
1116 this.outStream = null;
1117 this.initialize( blockSize, recordSize );
1121 TarBuffer( OutputStream outStream )
1123 this( outStream, TarBuffer.DEFAULT_BLKSIZE );
1127 TarBuffer( OutputStream outStream, int blockSize )
1129 this( outStream, blockSize, TarBuffer.DEFAULT_RCDSIZE );
1133 TarBuffer( OutputStream outStream, int blockSize, int recordSize )
1135 this.inStream = null;
1136 this.outStream = outStream;
1137 this.initialize( blockSize, recordSize );
1141 * Initialization common to all constructors.
1144 initialize( int blockSize, int recordSize )
1147 this.blockSize = blockSize;
1148 this.recordSize = recordSize;
1149 this.recsPerBlock = ( this.blockSize / this.recordSize );
1150 this.blockBuffer = new byte[ this.blockSize ];
1152 if ( this.inStream != null )
1154 this.currBlkIdx = -1;
1155 this.currRecIdx = this.recsPerBlock;
1159 this.currBlkIdx = 0;
1160 this.currRecIdx = 0;
1165 * Get the TAR Buffer's block size. Blocks consist of multiple records.
1170 return this.blockSize;
1174 * Get the TAR Buffer's record size.
1179 return this.recordSize;
1183 * Set the debugging flag for the buffer.
1185 * @param debug If true, print debugging output.
1188 setDebug( boolean debug )
1194 * Determine if an archive record indicate End of Archive. End of
1195 * archive is indicated by a record that consists entirely of null bytes.
1197 * @param record The record data to check.
1200 isEOFRecord( byte[] record )
1202 for ( int i = 0, sz = this.getRecordSize() ; i < sz ; ++i )
1203 if ( record[i] != 0 )
1210 * Skip over a record on the input stream.
1220 ( "SkipRecord: recIdx = " + this.currRecIdx
1221 + " blkIdx = " + this.currBlkIdx );
1224 if ( this.inStream == null )
1225 throw new IOException
1226 ( "reading (via skip) from an output buffer" );
1228 if ( this.currRecIdx >= this.recsPerBlock )
1230 if ( ! this.readBlock() )
1238 * Read a record from the input stream and return the data.
1240 * @return The record data.
1250 ( "ReadRecord: recIdx = " + this.currRecIdx
1251 + " blkIdx = " + this.currBlkIdx );
1254 if ( this.inStream == null )
1255 throw new IOException
1256 ( "reading from an output buffer" );
1258 if ( this.currRecIdx >= this.recsPerBlock )
1260 if ( ! this.readBlock() )
1264 byte[] result = new byte[ this.recordSize ];
1267 this.blockBuffer, (this.currRecIdx * this.recordSize),
1268 result, 0, this.recordSize );
1276 * @return false if End-Of-File, else true
1286 ( "ReadBlock: blkIdx = " + this.currBlkIdx );
1289 if ( this.inStream == null )
1290 throw new IOException
1291 ( "reading from an output buffer" );
1293 this.currRecIdx = 0;
1296 int bytesNeeded = this.blockSize;
1297 for ( ; bytesNeeded > 0 ; )
1301 ( this.blockBuffer, offset, bytesNeeded );
1305 // We have fit EOF, and the block is not full!
1307 // This is a broken archive. It does not follow the standard
1308 // blocking algorithm. However, because we are generous, and
1309 // it requires little effort, we will simply ignore the error
1310 // and continue as if the entire block were read. This does
1311 // not appear to break anything upstream. We used to return
1312 // false in this case.
1314 // Thanks to 'Yohann.Roussel@alcatel.fr' for this fix.
1317 if ( numBytes == -1 )
1321 bytesNeeded -= numBytes;
1322 if ( numBytes != this.blockSize )
1327 ( "ReadBlock: INCOMPLETE READ " + numBytes
1328 + " of " + this.blockSize + " bytes read." );
1339 * Get the current block number, zero based.
1341 * @return The current zero based block number.
1344 getCurrentBlockNum()
1346 return this.currBlkIdx;
1350 * Get the current record number, within the current block, zero based.
1351 * Thus, current offset = (currentBlockNum * recsPerBlk) + currentRecNum.
1353 * @return The current zero based record number.
1356 getCurrentRecordNum()
1358 return this.currRecIdx - 1;
1362 * Write an archive record to the archive.
1364 * @param record The record data to write to the archive.
1368 writeRecord( byte[] record )
1374 ( "WriteRecord: recIdx = " + this.currRecIdx
1375 + " blkIdx = " + this.currBlkIdx );
1378 if ( this.outStream == null )
1379 throw new IOException
1380 ( "writing to an input buffer" );
1382 if ( record.length != this.recordSize )
1383 throw new IOException
1384 ( "record to write has length '" + record.length
1385 + "' which is not the record size of '"
1386 + this.recordSize + "'" );
1388 if ( this.currRecIdx >= this.recsPerBlock )
1395 this.blockBuffer, (this.currRecIdx * this.recordSize),
1402 * Write an archive record to the archive, where the record may be
1403 * inside of a larger array buffer. The buffer must be "offset plus
1404 * record size" long.
1406 * @param buf The buffer containing the record data to write.
1407 * @param offset The offset of the record data within buf.
1411 writeRecord( byte[] buf, int offset )
1417 ( "WriteRecord: recIdx = " + this.currRecIdx
1418 + " blkIdx = " + this.currBlkIdx );
1421 if ( this.outStream == null )
1422 throw new IOException
1423 ( "writing to an input buffer" );
1425 if ( (offset + this.recordSize) > buf.length )
1426 throw new IOException
1427 ( "record has length '" + buf.length
1428 + "' with offset '" + offset
1429 + "' which is less than the record size of '"
1430 + this.recordSize + "'" );
1432 if ( this.currRecIdx >= this.recsPerBlock )
1439 this.blockBuffer, (this.currRecIdx * this.recordSize),
1446 * Write a TarBuffer block to the archive.
1455 ( "WriteBlock: blkIdx = " + this.currBlkIdx );
1458 if ( this.outStream == null )
1459 throw new IOException
1460 ( "writing to an input buffer" );
1462 this.outStream.write( this.blockBuffer, 0, this.blockSize );
1463 this.outStream.flush();
1465 this.currRecIdx = 0;
1470 * Flush the current data block if it has any data in it.
1479 System.err.println( "TarBuffer.flushBlock() called." );
1482 if ( this.outStream == null )
1483 throw new IOException
1484 ( "writing to an input buffer" );
1486 // Thanks to 'Todd Kofford <tkofford@bigfoot.com>' for this patch.
1487 // Use a buffer initialized with 0s to initialize everything in the
1488 // blockBuffer after the last current, complete record. This prevents
1489 // any previous data that might have previously existed in the
1490 // blockBuffer from being written to the file.
1492 if ( this.currRecIdx > 0 )
1494 int offset = this.currRecIdx * this.recordSize;
1495 byte[] zeroBuffer = new byte[ this.blockSize - offset ];
1498 ( zeroBuffer, 0, this.blockBuffer, offset, zeroBuffer.length );
1505 * Close the TarBuffer. If this is an output buffer, also flush the
1506 * current block before closing.
1514 System.err.println( "TarBuffer.closeBuffer()." );
1517 if ( this.outStream != null )
1521 if ( this.outStream != System.out
1522 && this.outStream != System.err )
1524 this.outStream.close();
1525 this.outStream = null;
1528 else if ( this.inStream != null )
1530 if ( this.inStream != System.in )
1532 this.inStream.close();
1533 this.inStream = null;
1541 ** Authored by Timothy Gerard Endres
1542 ** <mailto:time@gjt.org> <http://www.trustice.com>
1544 ** This work has been placed into the public domain.
1545 ** You may use this work in any way and for any purpose you wish.
1547 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
1548 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
1549 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
1550 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
1551 ** REDISTRIBUTION OF THIS SOFTWARE.
1558 * This class represents an entry in a Tar archive. It consists
1559 * of the entry's header, as well as the entry's File. Entries
1560 * can be instantiated in one of three ways, depending on how
1561 * they are to be used.
1563 * TarEntries that are created from the header bytes read from
1564 * an archive are instantiated with the TarEntry( byte[] )
1565 * constructor. These entries will be used when extracting from
1566 * or listing the contents of an archive. These entries have their
1567 * header filled in using the header bytes. They also set the File
1568 * to null, since they reference an archive entry not a file.
1570 * TarEntries that are created from Files that are to be written
1571 * into an archive are instantiated with the TarEntry( File )
1572 * constructor. These entries have their header filled in using
1573 * the File's information. They also keep a reference to the File
1574 * for convenience when writing entries.
1576 * Finally, TarEntries can be constructed from nothing but a name.
1577 * This allows the programmer to construct the entry by hand, for
1578 * instance when only an InputStream is available for writing to
1579 * the archive, and the header information is constructed from
1580 * other information. In this case the header fields are set to
1581 * defaults and the File is set to null.
1585 * Original Unix Tar Header:
1588 * Width Name Meaning
1589 * ----- --------- ---------------------------
1590 * 100 name name of file
1592 * 8 uid owner user ID
1593 * 8 gid owner group ID
1594 * 12 size length of file in bytes
1595 * 12 mtime modify time of file
1596 * 8 chksum checksum for header
1597 * 1 link indicator for links
1598 * 100 linkname name of linked file
1604 * POSIX "ustar" Style Tar Header:
1607 * Width Name Meaning
1608 * ----- --------- ---------------------------
1609 * 100 name name of file
1611 * 8 uid owner user ID
1612 * 8 gid owner group ID
1613 * 12 size length of file in bytes
1614 * 12 mtime modify time of file
1615 * 8 chksum checksum for header
1616 * 1 typeflag type of file
1617 * 100 linkname name of linked file
1618 * 6 magic USTAR indicator
1619 * 2 version USTAR version
1620 * 32 uname owner user name
1621 * 32 gname owner group name
1622 * 8 devmajor device major number
1623 * 8 devminor device minor number
1624 * 155 prefix prefix for file name
1626 * struct posix_header
1632 * char size[12]; 124
1633 * char mtime[12]; 136
1634 * char chksum[8]; 148
1635 * char typeflag; 156
1636 * char linkname[100]; 157
1637 * char magic[6]; 257
1638 * char version[2]; 263
1639 * char uname[32]; 265
1640 * char gname[32]; 297
1641 * char devmajor[8]; 329
1642 * char devminor[8]; 337
1643 * char prefix[155]; 345
1648 * Note that while the class does recognize GNU formatted headers,
1649 * it does not perform proper processing of GNU archives. I hope
1650 * to add the GNU support someday.
1652 * Directory "size" fix contributed by:
1653 * Bert Becker <becker@informatik.hu-berlin.de>
1656 * @author Timothy Gerard Endres, <time@gjt.org>
1660 static class TarEntry
1662 implements Cloneable
1665 * If this entry represents a File, this references it.
1667 protected File file;
1670 * This is the entry's header information.
1672 protected TarHeader header;
1675 * Set to true if this is a "old-unix" format entry.
1677 protected boolean unixFormat;
1680 * Set to true if this is a 'ustar' format entry.
1682 protected boolean ustarFormat;
1685 * Set to true if this is a GNU 'ustar' format entry.
1687 protected boolean gnuFormat;
1691 * The default constructor is protected for use only by subclasses.
1699 * Construct an entry with only a name. This allows the programmer
1700 * to construct the entry's header "by hand". File is set to null.
1703 TarEntry( String name )
1706 this.nameTarHeader( this.header, name );
1710 * Construct an entry for a file. File is set to file, and the
1711 * header is constructed from information from the file.
1713 * @param file The file that the entry represents.
1716 TarEntry( File file )
1717 throws InvalidHeaderException
1720 this.getFileTarHeader( this.header, file );
1724 * Construct an entry from an archive's header bytes. File is set
1727 * @param headerBuf The header bytes from a tar archive entry.
1730 TarEntry( byte[] headerBuf )
1731 throws InvalidHeaderException
1734 this.parseTarHeader( this.header, headerBuf );
1738 * Initialization code common to all constructors.
1744 this.header = new TarHeader();
1746 this.gnuFormat = false;
1747 this.ustarFormat = true; // REVIEW What we prefer to use...
1748 this.unixFormat = false;
1757 TarEntry entry = null;
1760 entry = (TarEntry) super.clone();
1762 if ( this.header != null )
1764 entry.header = (TarHeader) this.header.clone();
1767 if ( this.file != null )
1769 entry.file = new File( this.file.getAbsolutePath() );
1772 catch ( CloneNotSupportedException ex )
1774 ex.printStackTrace( System.err );
1781 * Returns true if this entry's header is in "ustar" format.
1783 * @return True if the entry's header is in "ustar" format.
1788 return this.ustarFormat;
1792 * Sets this entry's header format to "ustar".
1797 this.ustarFormat = true;
1798 this.gnuFormat = false;
1799 this.unixFormat = false;
1803 * Returns true if this entry's header is in the GNU 'ustar' format.
1805 * @return True if the entry's header is in the GNU 'ustar' format.
1810 return this.gnuFormat;
1814 * Sets this entry's header format to GNU "ustar".
1819 this.gnuFormat = true;
1820 this.ustarFormat = false;
1821 this.unixFormat = false;
1825 * Returns true if this entry's header is in the old "unix-tar" format.
1827 * @return True if the entry's header is in the old "unix-tar" format.
1832 return this.unixFormat;
1836 * Sets this entry's header format to "unix-style".
1841 this.unixFormat = true;
1842 this.ustarFormat = false;
1843 this.gnuFormat = false;
1847 * Determine if the two entries are equal. Equality is determined
1848 * by the header names being equal.
1850 * @return it Entry to be checked for equality.
1851 * @return True if the entries are equal.
1854 equals( TarEntry it )
1857 this.header.name.toString().equals
1858 ( it.header.name.toString() );
1862 * Determine if the given entry is a descendant of this entry.
1863 * Descendancy is determined by the name of the descendant
1864 * starting with this entry's name.
1866 * @param desc Entry to be checked as a descendent of this.
1867 * @return True if entry is a descendant of this.
1870 isDescendent( TarEntry desc )
1873 desc.header.name.toString().startsWith
1874 ( this.header.name.toString() );
1878 * Get this entry's header.
1880 * @return This entry's TarHeader.
1889 * Get this entry's name.
1891 * @return This entry's name.
1896 return this.header.name.toString();
1900 * Set this entry's name.
1902 * @param name This entry's new name.
1905 setName( String name )
1908 new StringBuffer( name );
1912 * Get this entry's user id.
1914 * @return This entry's user id.
1919 return this.header.userId;
1923 * Set this entry's user id.
1925 * @param userId This entry's new user id.
1928 setUserId( int userId )
1930 this.header.userId = userId;
1934 * Get this entry's group id.
1936 * @return This entry's group id.
1941 return this.header.groupId;
1945 * Set this entry's group id.
1947 * @param groupId This entry's new group id.
1950 setGroupId( int groupId )
1952 this.header.groupId = groupId;
1956 * Get this entry's user name.
1958 * @return This entry's user name.
1963 return this.header.userName.toString();
1967 * Set this entry's user name.
1969 * @param userName This entry's new user name.
1972 setUserName( String userName )
1974 this.header.userName =
1975 new StringBuffer( userName );
1979 * Get this entry's group name.
1981 * @return This entry's group name.
1986 return this.header.groupName.toString();
1990 * Set this entry's group name.
1992 * @param groupName This entry's new group name.
1995 setGroupName( String groupName )
1997 this.header.groupName =
1998 new StringBuffer( groupName );
2002 * Convenience method to set this entry's group and user ids.
2004 * @param userId This entry's new user id.
2005 * @param groupId This entry's new group id.
2008 setIds( int userId, int groupId )
2010 this.setUserId( userId );
2011 this.setGroupId( groupId );
2015 * Convenience method to set this entry's group and user names.
2017 * @param userName This entry's new user name.
2018 * @param groupName This entry's new group name.
2021 setNames( String userName, String groupName )
2023 this.setUserName( userName );
2024 this.setGroupName( groupName );
2028 * Set this entry's modification time. The parameter passed
2029 * to this method is in "Java time".
2031 * @param time This entry's new modification time.
2034 setModTime( long time )
2036 this.header.modTime = time / 1000;
2040 * Set this entry's modification time.
2042 * @param time This entry's new modification time.
2045 setModTime( Date time )
2047 this.header.modTime = time.getTime() / 1000;
2051 * Set this entry's modification time.
2053 * @param time This entry's new modification time.
2058 return new Date( this.header.modTime * 1000 );
2062 * Get this entry's file.
2064 * @return This entry's file.
2073 * Get this entry's file size.
2075 * @return This entry's file size.
2080 return this.header.size;
2084 * Set this entry's file size.
2086 * @param size This entry's new file size.
2089 setSize( long size )
2091 this.header.size = size;
2095 * Return whether or not this entry represents a directory.
2097 * @return True if this entry is a directory.
2102 if ( this.file != null )
2103 return this.file.isDirectory();
2105 if ( this.header != null )
2107 if ( this.header.linkFlag == TarHeader.LF_DIR )
2110 if ( this.header.name.toString().endsWith( "/" ) )
2118 * Fill in a TarHeader with information from a File.
2120 * @param hdr The TarHeader to fill in.
2121 * @param file The file from which to get the header information.
2124 getFileTarHeader( TarHeader hdr, File file )
2125 throws InvalidHeaderException
2129 String name = file.getPath();
2130 String osname = System.getProperty( "os.name" );
2131 if ( osname != null )
2133 // Strip off drive letters!
2134 // REVIEW Would a better check be "(File.separator == '\')"?
2136 // String Win32Prefix = "Windows";
2137 // String prefix = osname.substring( 0, Win32Prefix.length() );
2138 // if ( prefix.equalsIgnoreCase( Win32Prefix ) )
2140 // if ( File.separatorChar == '\\' )
2142 // Windows OS check was contributed by
2143 // Patrick Beard <beard@netscape.com>
2144 String Win32Prefix = "windows";
2145 if ( osname.toLowerCase().startsWith( Win32Prefix ) )
2147 if ( name.length() > 2 )
2149 char ch1 = name.charAt(0);
2150 char ch2 = name.charAt(1);
2152 && ( (ch1 >= 'a' && ch1 <= 'z')
2153 || (ch1 >= 'A' && ch1 <= 'Z') ) )
2155 name = name.substring( 2 );
2161 name = name.replace( File.separatorChar, '/' );
2163 // No absolute pathnames
2164 // Windows (and Posix?) paths can start with "\\NetworkDrive\",
2165 // so we loop on starting /'s.
2167 for ( ; name.startsWith( "/" ) ; )
2168 name = name.substring( 1 );
2170 hdr.linkName = new StringBuffer( "" );
2172 hdr.name = new StringBuffer( name );
2174 if ( file.isDirectory() )
2178 hdr.linkFlag = TarHeader.LF_DIR;
2179 if ( hdr.name.charAt( hdr.name.length() - 1 ) != '/' )
2180 hdr.name.append( "/" );
2184 hdr.size = file.length();
2186 hdr.linkFlag = TarHeader.LF_NORMAL;
2189 // UNDONE When File lets us get the userName, use it!
2191 hdr.modTime = file.lastModified() / 1000;
2198 * If this entry represents a file, and the file is a directory, return
2199 * an array of TarEntries for this entry's children.
2201 * @return An array of TarEntry's for this entry's children.
2204 getDirectoryEntries()
2205 throws InvalidHeaderException
2207 if ( this.file == null
2208 || ! this.file.isDirectory() )
2210 return new TarEntry[0];
2213 String[] list = this.file.list();
2215 TarEntry[] result = new TarEntry[ list.length ];
2217 for ( int i = 0 ; i < list.length ; ++i )
2221 ( new File( this.file, list[i] ) );
2228 * Compute the checksum of a tar entry header.
2230 * @param buf The tar entry's header buffer.
2231 * @return The computed checksum.
2234 computeCheckSum( byte[] buf )
2238 for ( int i = 0 ; i < buf.length ; ++i )
2240 sum += 255 & buf[ i ];
2247 * Write an entry's header information to a header buffer.
2248 * This method can throw an InvalidHeaderException
2250 * @param outbuf The tar entry header buffer to fill in.
2251 * @throws InvalidHeaderException If the name will not fit in the header.
2254 writeEntryHeader( byte[] outbuf )
2255 throws InvalidHeaderException
2259 if ( this.isUnixTarFormat() )
2261 if ( this.header.name.length() > 100 )
2262 throw new InvalidHeaderException
2263 ( "file path is greater than 100 characters, "
2264 + this.header.name );
2267 offset = TarHeader.getFileNameBytes( this.header.name.toString(), outbuf );
2269 offset = TarHeader.getOctalBytes
2270 ( this.header.mode, outbuf, offset, TarHeader.MODELEN );
2272 offset = TarHeader.getOctalBytes
2273 ( this.header.userId, outbuf, offset, TarHeader.UIDLEN );
2275 offset = TarHeader.getOctalBytes
2276 ( this.header.groupId, outbuf, offset, TarHeader.GIDLEN );
2278 long size = this.header.size;
2280 offset = TarHeader.getLongOctalBytes
2281 ( size, outbuf, offset, TarHeader.SIZELEN );
2283 offset = TarHeader.getLongOctalBytes
2284 ( this.header.modTime, outbuf, offset, TarHeader.MODTIMELEN );
2286 int csOffset = offset;
2287 for ( int c = 0 ; c < TarHeader.CHKSUMLEN ; ++c )
2288 outbuf[ offset++ ] = (byte) ' ';
2290 outbuf[ offset++ ] = this.header.linkFlag;
2292 offset = TarHeader.getNameBytes
2293 ( this.header.linkName, outbuf, offset, TarHeader.NAMELEN );
2295 if ( this.unixFormat )
2297 for ( int i = 0 ; i < TarHeader.MAGICLEN ; ++i )
2298 outbuf[ offset++ ] = 0;
2302 offset = TarHeader.getNameBytes
2303 ( this.header.magic, outbuf, offset, TarHeader.MAGICLEN );
2306 offset = TarHeader.getNameBytes
2307 ( this.header.userName, outbuf, offset, TarHeader.UNAMELEN );
2309 offset = TarHeader.getNameBytes
2310 ( this.header.groupName, outbuf, offset, TarHeader.GNAMELEN );
2312 offset = TarHeader.getOctalBytes
2313 ( this.header.devMajor, outbuf, offset, TarHeader.DEVLEN );
2315 offset = TarHeader.getOctalBytes
2316 ( this.header.devMinor, outbuf, offset, TarHeader.DEVLEN );
2318 for ( ; offset < outbuf.length ; )
2319 outbuf[ offset++ ] = 0;
2321 long checkSum = this.computeCheckSum( outbuf );
2323 TarHeader.getCheckSumOctalBytes
2324 ( checkSum, outbuf, csOffset, TarHeader.CHKSUMLEN );
2328 * Parse an entry's TarHeader information from a header buffer.
2330 * Old unix-style code contributed by David Mehringer <dmehring@astro.uiuc.edu>.
2332 * @param hdr The TarHeader to fill in from the buffer information.
2333 * @param header The tar entry header buffer to get information from.
2336 parseTarHeader( TarHeader hdr, byte[] headerBuf )
2337 throws InvalidHeaderException
2342 // NOTE Recognize archive header format.
2344 if ( headerBuf[257] == 0
2345 && headerBuf[258] == 0
2346 && headerBuf[259] == 0
2347 && headerBuf[260] == 0
2348 && headerBuf[261] == 0 )
2350 this.unixFormat = true;
2351 this.ustarFormat = false;
2352 this.gnuFormat = false;
2354 else if ( headerBuf[257] == 'u'
2355 && headerBuf[258] == 's'
2356 && headerBuf[259] == 't'
2357 && headerBuf[260] == 'a'
2358 && headerBuf[261] == 'r'
2359 && headerBuf[262] == 0 )
2361 this.ustarFormat = true;
2362 this.gnuFormat = false;
2363 this.unixFormat = false;
2365 else if ( headerBuf[257] == 'u'
2366 && headerBuf[258] == 's'
2367 && headerBuf[259] == 't'
2368 && headerBuf[260] == 'a'
2369 && headerBuf[261] == 'r'
2370 && headerBuf[262] != 0
2371 && headerBuf[263] != 0 )
2374 this.gnuFormat = true;
2375 this.unixFormat = false;
2376 this.ustarFormat = false;
2380 StringBuffer buf = new StringBuffer( 128 );
2382 buf.append( "header magic is not 'ustar' or unix-style zeros, it is '" );
2383 buf.append( headerBuf[257] );
2384 buf.append( headerBuf[258] );
2385 buf.append( headerBuf[259] );
2386 buf.append( headerBuf[260] );
2387 buf.append( headerBuf[261] );
2388 buf.append( headerBuf[262] );
2389 buf.append( headerBuf[263] );
2390 buf.append( "', or (dec) " );
2391 buf.append( (int)headerBuf[257] );
2393 buf.append( (int)headerBuf[258] );
2395 buf.append( (int)headerBuf[259] );
2397 buf.append( (int)headerBuf[260] );
2399 buf.append( (int)headerBuf[261] );
2401 buf.append( (int)headerBuf[262] );
2403 buf.append( (int)headerBuf[263] );
2405 throw new InvalidHeaderException( buf.toString() );
2408 hdr.name = TarHeader.parseFileName( headerBuf );
2410 offset = TarHeader.NAMELEN;
2413 TarHeader.parseOctal( headerBuf, offset, TarHeader.MODELEN );
2415 offset += TarHeader.MODELEN;
2418 TarHeader.parseOctal( headerBuf, offset, TarHeader.UIDLEN );
2420 offset += TarHeader.UIDLEN;
2423 TarHeader.parseOctal( headerBuf, offset, TarHeader.GIDLEN );
2425 offset += TarHeader.GIDLEN;
2428 TarHeader.parseOctal( headerBuf, offset, TarHeader.SIZELEN );
2430 offset += TarHeader.SIZELEN;
2433 TarHeader.parseOctal( headerBuf, offset, TarHeader.MODTIMELEN );
2435 offset += TarHeader.MODTIMELEN;
2437 hdr.checkSum = (int)
2438 TarHeader.parseOctal( headerBuf, offset, TarHeader.CHKSUMLEN );
2440 offset += TarHeader.CHKSUMLEN;
2442 hdr.linkFlag = headerBuf[ offset++ ];
2445 TarHeader.parseName( headerBuf, offset, TarHeader.NAMELEN );
2447 offset += TarHeader.NAMELEN;
2449 if ( this.ustarFormat )
2452 TarHeader.parseName( headerBuf, offset, TarHeader.MAGICLEN );
2454 offset += TarHeader.MAGICLEN;
2457 TarHeader.parseName( headerBuf, offset, TarHeader.UNAMELEN );
2459 offset += TarHeader.UNAMELEN;
2462 TarHeader.parseName( headerBuf, offset, TarHeader.GNAMELEN );
2464 offset += TarHeader.GNAMELEN;
2466 hdr.devMajor = (int)
2467 TarHeader.parseOctal( headerBuf, offset, TarHeader.DEVLEN );
2469 offset += TarHeader.DEVLEN;
2471 hdr.devMinor = (int)
2472 TarHeader.parseOctal( headerBuf, offset, TarHeader.DEVLEN );
2478 hdr.magic = new StringBuffer( "" );
2479 hdr.userName = new StringBuffer( "" );
2480 hdr.groupName = new StringBuffer( "" );
2485 * Fill in a TarHeader given only the entry's name.
2487 * @param hdr The TarHeader to fill in.
2488 * @param name The tar entry name.
2491 nameTarHeader( TarHeader hdr, String name )
2493 boolean isDir = name.endsWith( "/" );
2495 this.gnuFormat = false;
2496 this.ustarFormat = true;
2497 this.unixFormat = false;
2503 hdr.name = new StringBuffer( name );
2504 hdr.mode = isDir ? 040755 : 0100644;
2511 (new java.util.Date()).getTime() / 1000;
2514 isDir ? TarHeader.LF_DIR : TarHeader.LF_NORMAL;
2516 hdr.linkName = new StringBuffer( "" );
2517 hdr.userName = new StringBuffer( "" );
2518 hdr.groupName = new StringBuffer( "" );
2527 StringBuffer result = new StringBuffer( 128 );
2529 append( "[TarEntry name=" ).
2530 append( this.getName() ).
2531 append( ", isDir=" ).
2532 append( this.isDirectory() ).
2533 append( ", size=" ).
2534 append( this.getSize() ).
2535 append( ", userId=" ).
2536 append( this.getUserId() ).
2537 append( ", user=" ).
2538 append( this.getUserName() ).
2539 append( ", groupId=" ).
2540 append( this.getGroupId() ).
2541 append( ", group=" ).
2542 append( this.getGroupName() ).
2550 ** Tim feel free to integrate this code here.
2552 ** This code has been placed into the Public Domain.
2553 ** This code was written by David M. Gaskin in 1999.
2558 * Enumerate the contents of a "tar" file.
2560 * Last updated 26th Mar 1999.
2562 * @author David. M. Gaskin.
2563 * @version Version 1.0 Mar 1999
2564 * @since Version 1.0
2568 static class TarEntryEnumerator
2569 implements Enumeration
2572 * The instance on which the enumeration works.
2574 private TarInputStream tis = null;
2577 * Has EndOfFile been reached?
2579 private boolean eof = false;
2582 * The read ahead entry (or <B><I>null</I></B> if no read ahead exists)
2584 private TarEntry readAhead = null;
2587 * Construct an instance given a TarInputStream. This method is package
2588 * private because it is not initially forseen that an instance of this class
2589 * should be constructed from outside the package. Should it become necessary
2590 * to construct an instance of this class from outside the package in which it
2591 * exists then the constructor should be made <B>protected</B> and an empty
2592 * subclass should be written in the other package.
2594 * @param <B>tis</B> the <B>TarInputStream</B> on which this enumeration has
2598 TarEntryEnumerator( TarInputStream tis )
2605 * Return the next element in the enumeration. This is a required method
2606 * for implementing <B>java.util.Enumeration</B>.
2608 * @return the next Object in the enumeration
2609 * @exception <B>NoSuchElementException</B> should an attempt be made to
2614 throws NoSuchElementException
2616 if ( eof && ( readAhead == null ) )
2617 throw new NoSuchElementException();
2620 if ( readAhead != null )
2634 * Return <B>true</B> if there are more elements in the enumeration.
2636 * @return <B>true</B> if there are more elements in the enumeration.
2645 readAhead = getNext();
2646 if ( readAhead != null )
2653 * Return the next element of <B>null</B> if there is no next element or
2654 * if an error occured.
2656 * @return the next element of <B>null</B> if there is no next element or
2657 * if an error occured.
2664 rc = tis.getNextEntry();
2666 catch ( IOException ex )
2668 // null will be returned but should not occur
2669 ex.printStackTrace();
2679 ** Contributed by "Bay" <bayard@generationjava.com>
2681 ** This code has been placed into the public domain.
2685 // we extend TarOutputStream to have the same type,
2686 // BUT, we don't use ANY methods. It's all about
2690 * Outputs tar.gz files. Added functionality that it
2691 * doesn't need to know the size of an entry. If an
2692 * entry has zero size when it is put in the Tar, then
2693 * it buffers it until it's closed and it knows the size.
2695 * @author "Bay" <bayard@generationjava.com>
2699 static class TarGzOutputStream
2700 extends TarOutputStream
2702 private TarOutputStream tos = null;
2703 private GZIPOutputStream gzip = null;
2704 private ByteArrayOutputStream bos = null;
2705 private TarEntry currentEntry = null;
2708 TarGzOutputStream( OutputStream out )
2712 this.gzip = new GZIPOutputStream( out );
2713 this.tos = new TarOutputStream( this.gzip );
2714 this.bos = new ByteArrayOutputStream();
2717 // proxy all methods, but buffer if unknown size
2720 setDebug( boolean b )
2722 this.tos.setDebug(b);
2726 setBufferDebug( boolean b )
2728 this.tos.setBufferDebug(b);
2735 if ( this.currentEntry != null )
2754 return this.tos.getRecordSize();
2758 putNextEntry(TarEntry entry)
2761 if ( entry.getSize() != 0 )
2763 this.tos.putNextEntry( entry );
2767 this.currentEntry = entry;
2775 if(this.currentEntry == null)
2777 this.tos.closeEntry();
2781 this.currentEntry.setSize( bos.size() );
2782 this.tos.putNextEntry( this.currentEntry );
2783 this.bos.writeTo( this.tos );
2784 this.tos.closeEntry();
2785 this.currentEntry = null;
2786 this.bos = new ByteArrayOutputStream();
2794 if ( this.currentEntry == null )
2796 this.tos.write( b );
2800 this.bos.write( b );
2808 if ( this.currentEntry == null )
2810 this.tos.write( b );
2814 this.bos.write( b );
2819 write( byte[] b, int start, int length )
2822 if ( this.currentEntry == null )
2824 this.tos.write( b, start, length );
2828 this.bos.write( b, start, length );
2834 ** Authored by Timothy Gerard Endres
2835 ** <mailto:time@gjt.org> <http://www.trustice.com>
2837 ** This work has been placed into the public domain.
2838 ** You may use this work in any way and for any purpose you wish.
2840 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
2841 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
2842 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
2843 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
2844 ** REDISTRIBUTION OF THIS SOFTWARE.
2849 * This class encapsulates the Tar Entry Header used in Tar Archives.
2850 * The class also holds a number of tar constants, used mostly in headers.
2852 * @author Timothy Gerard Endres, <time@gjt.org>
2856 static class TarHeader
2858 implements Cloneable
2861 * The length of the name field in a header buffer.
2863 public static final int NAMELEN = 100;
2865 * The offset of the name field in a header buffer.
2867 public static final int NAMEOFFSET = 0;
2869 * The length of the name prefix field in a header buffer.
2871 public static final int PREFIXLEN = 155;
2873 * The offset of the name prefix field in a header buffer.
2875 public static final int PREFIXOFFSET = 345;
2877 * The length of the mode field in a header buffer.
2879 public static final int MODELEN = 8;
2881 * The length of the user id field in a header buffer.
2883 public static final int UIDLEN = 8;
2885 * The length of the group id field in a header buffer.
2887 public static final int GIDLEN = 8;
2889 * The length of the checksum field in a header buffer.
2891 public static final int CHKSUMLEN = 8;
2893 * The length of the size field in a header buffer.
2895 public static final int SIZELEN = 12;
2897 * The length of the magic field in a header buffer.
2899 public static final int MAGICLEN = 8;
2901 * The length of the modification time field in a header buffer.
2903 public static final int MODTIMELEN = 12;
2905 * The length of the user name field in a header buffer.
2907 public static final int UNAMELEN = 32;
2909 * The length of the group name field in a header buffer.
2911 public static final int GNAMELEN = 32;
2913 * The length of the devices field in a header buffer.
2915 public static final int DEVLEN = 8;
2918 * LF_ constants represent the "link flag" of an entry, or more commonly,
2919 * the "entry type". This is the "old way" of indicating a normal file.
2921 public static final byte LF_OLDNORM = 0;
2925 public static final byte LF_NORMAL = (byte) '0';
2929 public static final byte LF_LINK = (byte) '1';
2931 * Symbolic link file type.
2933 public static final byte LF_SYMLINK = (byte) '2';
2935 * Character device file type.
2937 public static final byte LF_CHR = (byte) '3';
2939 * Block device file type.
2941 public static final byte LF_BLK = (byte) '4';
2943 * Directory file type.
2945 public static final byte LF_DIR = (byte) '5';
2947 * FIFO (pipe) file type.
2949 public static final byte LF_FIFO = (byte) '6';
2951 * Contiguous file type.
2953 public static final byte LF_CONTIG = (byte) '7';
2956 * The magic tag representing a POSIX tar archive.
2958 public static final String TMAGIC = "ustar";
2961 * The magic tag representing a GNU tar archive.
2963 public static final String GNU_TMAGIC = "ustar ";
2968 public StringBuffer name;
2970 * The entry's permission mode.
2974 * The entry's user id.
2978 * The entry's group id.
2986 * The entry's modification time.
2988 public long modTime;
2990 * The entry's checksum.
2992 public int checkSum;
2994 * The entry's link flag.
2996 public byte linkFlag;
2998 * The entry's link name.
3000 public StringBuffer linkName;
3002 * The entry's magic tag.
3004 public StringBuffer magic;
3006 * The entry's user name.
3008 public StringBuffer userName;
3010 * The entry's group name.
3012 public StringBuffer groupName;
3014 * The entry's major device number.
3016 public int devMajor;
3018 * The entry's minor device number.
3020 public int devMinor;
3026 this.magic = new StringBuffer( TarHeader.TMAGIC );
3028 this.name = new StringBuffer();
3029 this.linkName = new StringBuffer();
3032 System.getProperty( "user.name", "" );
3034 if ( user.length() > 31 )
3035 user = user.substring( 0, 31 );
3039 this.userName = new StringBuffer( user );
3040 this.groupName = new StringBuffer( "" );
3044 * TarHeaders can be cloned.
3049 TarHeader hdr = null;
3052 hdr = (TarHeader) super.clone();
3055 (this.name == null ) ? null
3056 : new StringBuffer( this.name.toString() );
3057 hdr.mode = this.mode;
3058 hdr.userId = this.userId;
3059 hdr.groupId = this.groupId;
3060 hdr.size = this.size;
3061 hdr.modTime = this.modTime;
3062 hdr.checkSum = this.checkSum;
3063 hdr.linkFlag = this.linkFlag;
3065 (this.linkName == null ) ? null
3066 : new StringBuffer( this.linkName.toString() );
3068 (this.magic == null ) ? null
3069 : new StringBuffer( this.magic.toString() );
3071 (this.userName == null ) ? null
3072 : new StringBuffer( this.userName.toString() );
3074 (this.groupName == null ) ? null
3075 : new StringBuffer( this.groupName.toString() );
3076 hdr.devMajor = this.devMajor;
3077 hdr.devMinor = this.devMinor;
3079 catch ( CloneNotSupportedException ex )
3081 ex.printStackTrace( System.err );
3088 * Get the name of this entry.
3090 * @return Teh entry's name.
3095 return this.name.toString();
3099 * Parse an octal string from a header buffer. This is used for the
3100 * file permission mode value.
3102 * @param header The header buffer from which to parse.
3103 * @param offset The offset into the buffer from which to parse.
3104 * @param length The number of header bytes to parse.
3105 * @return The long value of the octal string.
3108 parseOctal( byte[] header, int offset, int length )
3109 throws InvalidHeaderException
3112 boolean stillPadding = true;
3114 int end = offset + length;
3115 for ( int i = offset ; i < end ; ++i )
3117 if ( header[i] == 0 )
3120 if ( header[i] == (byte) ' ' || header[i] == '0' )
3125 if ( header[i] == (byte) ' ' )
3129 stillPadding = false;
3133 + (header[i] - '0');
3140 * Parse a file name from a header buffer. This is different from
3141 * parseName() in that is recognizes 'ustar' names and will handle
3142 * adding on the "prefix" field to the name.
3144 * Contributed by Dmitri Tikhonov <dxt2431@yahoo.com>
3146 * @param header The header buffer from which to parse.
3147 * @param offset The offset into the buffer from which to parse.
3148 * @param length The number of header bytes to parse.
3149 * @return The header's entry name.
3151 public static StringBuffer
3152 parseFileName( byte[] header )
3154 StringBuffer result = new StringBuffer( 256 );
3156 // If header[345] is not equal to zero, then it is the "prefix"
3157 // that 'ustar' defines. It must be prepended to the "normal"
3158 // name field. We are responsible for the separating '/'.
3160 if ( header[345] != 0 )
3162 for ( int i = 345 ; i < 500 && header[i] != 0 ; ++i )
3164 result.append( (char)header[i] );
3167 result.append( "/" );
3170 for ( int i = 0 ; i < 100 && header[i] != 0 ; ++i )
3172 result.append( (char)header[i] );
3179 * Parse an entry name from a header buffer.
3181 * @param header The header buffer from which to parse.
3182 * @param offset The offset into the buffer from which to parse.
3183 * @param length The number of header bytes to parse.
3184 * @return The header's entry name.
3186 public static StringBuffer
3187 parseName( byte[] header, int offset, int length )
3188 throws InvalidHeaderException
3190 StringBuffer result = new StringBuffer( length );
3192 int end = offset + length;
3193 for ( int i = offset ; i < end ; ++i )
3195 if ( header[i] == 0 )
3197 result.append( (char)header[i] );
3204 * This method, like getNameBytes(), is intended to place a name
3205 * into a TarHeader's buffer. However, this method is sophisticated
3206 * enough to recognize long names (name.length() > NAMELEN). In these
3207 * cases, the method will break the name into a prefix and suffix and
3208 * place the name in the header in 'ustar' format. It is up to the
3209 * TarEntry to manage the "entry header format". This method assumes
3210 * the name is valid for the type of archive being generated.
3212 * @param outbuf The buffer containing the entry header to modify.
3213 * @param newName The new name to place into the header buffer.
3214 * @return The current offset in the tar header (always TarHeader.NAMELEN).
3215 * @throws InvalidHeaderException If the name will not fit in the header.
3218 getFileNameBytes( String newName, byte[] outbuf )
3219 throws InvalidHeaderException
3221 if ( newName.length() > 100 )
3223 // Locate a pathname "break" prior to the maximum name length...
3224 int index = newName.indexOf( "/", newName.length() - 100 );
3226 throw new InvalidHeaderException
3227 ( "file name is greater than 100 characters, " + newName );
3229 // Get the "suffix subpath" of the name.
3230 String name = newName.substring( index + 1 );
3232 // Get the "prefix subpath", or "prefix", of the name.
3233 String prefix = newName.substring( 0, index );
3234 if ( prefix.length() > TarHeader.PREFIXLEN )
3235 throw new InvalidHeaderException
3236 ( "file prefix is greater than 155 characters" );
3238 TarHeader.getNameBytes
3239 ( new StringBuffer( name ), outbuf,
3240 TarHeader.NAMEOFFSET, TarHeader.NAMELEN );
3242 TarHeader.getNameBytes
3243 ( new StringBuffer( prefix ), outbuf,
3244 TarHeader.PREFIXOFFSET, TarHeader.PREFIXLEN );
3248 TarHeader.getNameBytes
3249 ( new StringBuffer( newName ), outbuf,
3250 TarHeader.NAMEOFFSET, TarHeader.NAMELEN );
3253 // The offset, regardless of the format, is now the end of the
3254 // original name field.
3256 return TarHeader.NAMELEN;
3260 * Move the bytes from the name StringBuffer into the header's buffer.
3262 * @param header The header buffer into which to copy the name.
3263 * @param offset The offset into the buffer at which to store.
3264 * @param length The number of header bytes to store.
3265 * @return The new offset (offset + length).
3268 getNameBytes( StringBuffer name, byte[] buf, int offset, int length )
3272 for ( i = 0 ; i < length && i < name.length() ; ++i )
3274 buf[ offset + i ] = (byte) name.charAt( i );
3277 for ( ; i < length ; ++i )
3279 buf[ offset + i ] = 0;
3282 return offset + length;
3286 * Parse an octal integer from a header buffer.
3288 * @param header The header buffer from which to parse.
3289 * @param offset The offset into the buffer from which to parse.
3290 * @param length The number of header bytes to parse.
3291 * @return The integer value of the octal bytes.
3294 getOctalBytes( long value, byte[] buf, int offset, int length )
3296 byte[] result = new byte[ length ];
3298 int idx = length - 1;
3300 buf[ offset + idx ] = 0;
3302 buf[ offset + idx ] = (byte) ' ';
3307 buf[ offset + idx ] = (byte) '0';
3312 for ( long val = value ; idx >= 0 && val > 0 ; --idx )
3314 buf[ offset + idx ] = (byte)
3315 ( (byte) '0' + (byte) (val & 7) );
3320 for ( ; idx >= 0 ; --idx )
3322 buf[ offset + idx ] = (byte) ' ';
3325 return offset + length;
3329 * Parse an octal long integer from a header buffer.
3331 * @param header The header buffer from which to parse.
3332 * @param offset The offset into the buffer from which to parse.
3333 * @param length The number of header bytes to parse.
3334 * @return The long value of the octal bytes.
3337 getLongOctalBytes( long value, byte[] buf, int offset, int length )
3339 byte[] temp = new byte[ length + 1 ];
3340 TarHeader.getOctalBytes( value, temp, 0, length + 1 );
3341 System.arraycopy( temp, 0, buf, offset, length );
3342 return offset + length;
3346 * Parse the checksum octal integer from a header buffer.
3348 * @param header The header buffer from which to parse.
3349 * @param offset The offset into the buffer from which to parse.
3350 * @param length The number of header bytes to parse.
3351 * @return The integer value of the entry's checksum.
3354 getCheckSumOctalBytes( long value, byte[] buf, int offset, int length )
3356 TarHeader.getOctalBytes( value, buf, offset, length );
3357 buf[ offset + length - 1 ] = (byte) ' ';
3358 buf[ offset + length - 2 ] = 0;
3359 return offset + length;
3365 ** Authored by Timothy Gerard Endres
3366 ** <mailto:time@gjt.org> <http://www.trustice.com>
3368 ** This work has been placed into the public domain.
3369 ** You may use this work in any way and for any purpose you wish.
3371 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
3372 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
3373 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
3374 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
3375 ** REDISTRIBUTION OF THIS SOFTWARE.
3381 * The TarInputStream reads a UNIX tar archive as an InputStream.
3382 * methods are provided to position at each successive entry in
3383 * the archive, and the read each entry as a normal input stream
3386 * Kerry Menzel <kmenzel@cfl.rr.com> Contributed the code to support
3387 * file sizes greater than 2GB (longs versus ints).
3390 * @version $Revision: 1.9 $
3391 * @author Timothy Gerard Endres, <time@gjt.org>
3399 static class TarInputStream
3400 extends FilterInputStream
3402 protected boolean debug;
3403 protected boolean hasHitEOF;
3405 protected long entrySize;
3406 protected long entryOffset;
3408 protected byte[] oneBuf;
3409 protected byte[] readBuf;
3411 protected TarBuffer buffer;
3413 protected TarEntry currEntry;
3415 protected EntryFactory eFactory;
3419 TarInputStream( InputStream is )
3421 this( is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE );
3425 TarInputStream( InputStream is, int blockSize )
3427 this( is, blockSize, TarBuffer.DEFAULT_RCDSIZE );
3431 TarInputStream( InputStream is, int blockSize, int recordSize )
3435 this.buffer = new TarBuffer( is, blockSize, recordSize );
3437 this.readBuf = null;
3438 this.oneBuf = new byte[1];
3440 this.hasHitEOF = false;
3441 this.eFactory = null;
3445 * Sets the debugging flag.
3447 * @param debugF True to turn on debugging.
3450 setDebug( boolean debugF )
3452 this.debug = debugF;
3456 * Sets the debugging flag.
3458 * @param debugF True to turn on debugging.
3461 setEntryFactory( EntryFactory factory )
3463 this.eFactory = factory;
3467 * Sets the debugging flag in this stream's TarBuffer.
3469 * @param debugF True to turn on debugging.
3472 setBufferDebug( boolean debug )
3474 this.buffer.setDebug( debug );
3478 * Closes this stream. Calls the TarBuffer's close() method.
3484 this.buffer.close();
3488 * Get the record size being used by this stream's TarBuffer.
3490 * @return The TarBuffer record size.
3495 return this.buffer.getRecordSize();
3499 * Get the available data that can be read from the current
3500 * entry in the archive. This does not indicate how much data
3501 * is left in the entire archive, only in the current entry.
3502 * This value is determined from the entry's size header field
3503 * and the amount of data already read from the current entry.
3506 * @return The number of available bytes for the current entry.
3512 return (int)(this.entrySize - this.entryOffset);
3516 * Skip bytes in the input buffer. This skips bytes in the
3517 * current entry's data, not the entire archive, and will
3518 * stop at the end of the current entry's data if the number
3519 * to skip extends beyond that point.
3521 * @param numToSkip The number of bytes to skip.
3522 * @return The actual number of bytes skipped.
3525 skip( long numToSkip )
3529 // This is horribly inefficient, but it ensures that we
3530 // properly skip over bytes via the TarBuffer...
3533 byte[] skipBuf = new byte[ 8 * 1024 ];
3534 long num = numToSkip;
3538 this.read( skipBuf, 0,
3539 ( num > skipBuf.length ? skipBuf.length : (int) num ) );
3541 if ( numRead == -1 )
3547 return ( numToSkip - num );
3551 * Since we do not support marking just yet, we return false.
3562 * Since we do not support marking just yet, we do nothing.
3564 * @param markLimit The limit to mark.
3567 mark( int markLimit )
3572 * Since we do not support marking just yet, we do nothing.
3580 * Get the number of bytes into the current TarEntry.
3581 * This method returns the number of bytes that have been read
3582 * from the current TarEntry's data.
3584 * @returns The current entry offset.
3590 return this.entryOffset;
3594 * Get the number of bytes into the stream we are currently at.
3595 * This method accounts for the blocking stream that tar uses,
3596 * so it represents the actual position in input stream, as
3597 * opposed to the place where the tar archive parsing is.
3599 * @returns The current file pointer.
3605 return ( buffer.getBlockSize() * buffer.getCurrentBlockNum() )
3606 + buffer.getCurrentRecordNum();
3610 * Get the next entry in this tar archive. This will skip
3611 * over any remaining data in the current entry, if there
3612 * is one, and place the input stream at the header of the
3613 * next entry, and read the header and instantiate a new
3614 * TarEntry from the header bytes and return that entry.
3615 * If there are no more entries in the archive, null will
3616 * be returned to indicate that the end of the archive has
3619 * @return The next TarEntry in the archive, or null.
3625 if ( this.hasHitEOF )
3628 if ( this.currEntry != null )
3630 long numToSkip = (this.entrySize - this.entryOffset);
3634 ( "TarInputStream: SKIP currENTRY '"
3635 + this.currEntry.getName() + "' SZ "
3636 + this.entrySize + " OFF " + this.entryOffset
3637 + " skipping " + numToSkip + " bytes" );
3639 if ( numToSkip > 0 )
3641 this.skip( numToSkip );
3644 this.readBuf = null;
3647 byte[] headerBuf = this.buffer.readRecord();
3649 if ( headerBuf == null )
3653 System.err.println( "READ NULL RECORD" );
3656 this.hasHitEOF = true;
3658 else if ( this.buffer.isEOFRecord( headerBuf ) )
3662 System.err.println( "READ EOF RECORD" );
3665 this.hasHitEOF = true;
3668 if ( this.hasHitEOF )
3670 this.currEntry = null;
3675 if ( this.eFactory == null )
3677 this.currEntry = new TarEntry( headerBuf );
3682 this.eFactory.createEntry( headerBuf );
3687 ( "TarInputStream: SET CURRENTRY '"
3688 + this.currEntry.getName()
3689 + "' size = " + this.currEntry.getSize() );
3691 this.entryOffset = 0;
3692 this.entrySize = this.currEntry.getSize();
3694 catch ( InvalidHeaderException ex )
3697 this.entryOffset = 0;
3698 this.currEntry = null;
3699 throw new InvalidHeaderException
3700 ( "bad header in block "
3701 + this.buffer.getCurrentBlockNum()
3703 + this.buffer.getCurrentRecordNum()
3704 + ", " + ex.getMessage() );
3708 return this.currEntry;
3712 * Reads a byte from the current tar archive entry.
3714 * This method simply calls read( byte[], int, int ).
3716 * @return The byte read, or -1 at EOF.
3722 int num = this.read( this.oneBuf, 0, 1 );
3726 return (int) this.oneBuf[0];
3730 * Reads bytes from the current tar archive entry.
3732 * This method simply calls read( byte[], int, int ).
3734 * @param buf The buffer into which to place bytes read.
3735 * @return The number of bytes read, or -1 at EOF.
3741 return this.read( buf, 0, buf.length );
3745 * Reads bytes from the current tar archive entry.
3747 * This method is aware of the boundaries of the current
3748 * entry in the archive and will deal with them as if they
3749 * were this stream's start and EOF.
3751 * @param buf The buffer into which to place bytes read.
3752 * @param offset The offset at which to place bytes read.
3753 * @param numToRead The number of bytes to read.
3754 * @return The number of bytes read, or -1 at EOF.
3757 read( byte[] buf, int offset, int numToRead )
3762 if ( this.entryOffset >= this.entrySize )
3765 if ( (numToRead + this.entryOffset) > this.entrySize )
3767 numToRead = (int) (this.entrySize - this.entryOffset);
3770 if ( this.readBuf != null )
3772 int sz = ( numToRead > this.readBuf.length )
3773 ? this.readBuf.length : numToRead;
3775 System.arraycopy( this.readBuf, 0, buf, offset, sz );
3777 if ( sz >= this.readBuf.length )
3779 this.readBuf = null;
3783 int newLen = this.readBuf.length - sz;
3784 byte[] newBuf = new byte[ newLen ];
3785 System.arraycopy( this.readBuf, sz, newBuf, 0, newLen );
3786 this.readBuf = newBuf;
3794 for ( ; numToRead > 0 ; )
3796 byte[] rec = this.buffer.readRecord();
3800 throw new IOException
3801 ( "unexpected EOF with " + numToRead + " bytes unread" );
3805 int recLen = rec.length;
3809 System.arraycopy( rec, 0, buf, offset, sz );
3810 this.readBuf = new byte[ recLen - sz ];
3811 System.arraycopy( rec, sz, this.readBuf, 0, recLen - sz );
3816 System.arraycopy( rec, 0, buf, offset, recLen );
3824 this.entryOffset += totalRead;
3830 * Copies the contents of the current tar archive entry directly into
3833 * @param out The OutputStream into which to write the entry's data.
3836 copyEntryContents( OutputStream out )
3839 byte[] buf = new byte[ 32 * 1024 ];
3843 int numRead = this.read( buf, 0, buf.length );
3844 if ( numRead == -1 )
3846 out.write( buf, 0, numRead );
3851 * This interface is provided, with the method setEntryFactory(), to allow
3852 * the programmer to have their own TarEntry subclass instantiated for the
3853 * entries return from getNextEntry().
3857 interface EntryFactory
3860 createEntry( String name );
3863 createEntry( File path )
3864 throws InvalidHeaderException;
3867 createEntry( byte[] headerBuf )
3868 throws InvalidHeaderException;
3873 implements EntryFactory
3876 createEntry( String name )
3878 return new TarEntry( name );
3882 createEntry( File path )
3883 throws InvalidHeaderException
3885 return new TarEntry( path );
3889 createEntry( byte[] headerBuf )
3890 throws InvalidHeaderException
3892 return new TarEntry( headerBuf );
3900 ** Authored by Timothy Gerard Endres
3901 ** <mailto:time@gjt.org> <http://www.trustice.com>
3903 ** This work has been placed into the public domain.
3904 ** You may use this work in any way and for any purpose you wish.
3906 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
3907 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
3908 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
3909 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
3910 ** REDISTRIBUTION OF THIS SOFTWARE.
3916 * The TarOutputStream writes a UNIX tar archive as an OutputStream.
3917 * Methods are provided to put entries, and then write their contents
3918 * by writing to this stream using write().
3920 * Kerry Menzel <kmenzel@cfl.rr.com> Contributed the code to support
3921 * file sizes greater than 2GB (longs versus ints).
3923 * @version $Revision: 1.8 $
3924 * @author Timothy Gerard Endres, <time@gjt.org>
3932 static class TarOutputStream
3933 extends FilterOutputStream
3935 protected boolean debug;
3936 protected long currSize;
3937 protected long currBytes;
3938 protected byte[] oneBuf;
3939 protected byte[] recordBuf;
3940 protected int assemLen;
3941 protected byte[] assemBuf;
3942 protected TarBuffer buffer;
3946 TarOutputStream( OutputStream os )
3948 this( os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE );
3952 TarOutputStream( OutputStream os, int blockSize )
3954 this( os, blockSize, TarBuffer.DEFAULT_RCDSIZE );
3958 TarOutputStream( OutputStream os, int blockSize, int recordSize )
3962 this.buffer = new TarBuffer( os, blockSize, recordSize );
3966 this.assemBuf = new byte[ recordSize ];
3967 this.recordBuf = new byte[ recordSize ];
3968 this.oneBuf = new byte[1];
3972 * Sets the debugging flag.
3974 * @param debugF True to turn on debugging.
3977 setDebug( boolean debugF )
3979 this.debug = debugF;
3983 * Sets the debugging flag in this stream's TarBuffer.
3985 * @param debugF True to turn on debugging.
3988 setBufferDebug( boolean debug )
3990 this.buffer.setDebug( debug );
3994 * Ends the TAR archive without closing the underlying OutputStream.
3995 * The result is that the EOF record of nulls is written.
4002 this.writeEOFRecord();
4006 * Ends the TAR archive and closes the underlying OutputStream.
4007 * This means that finish() is called followed by calling the
4008 * TarBuffer's close().
4016 this.buffer.close();
4020 * Get the record size being used by this stream's TarBuffer.
4022 * @return The TarBuffer record size.
4027 return this.buffer.getRecordSize();
4031 * Put an entry on the output stream. This writes the entry's
4032 * header record and positions the output stream for writing
4033 * the contents of the entry. Once this method is called, the
4034 * stream is ready for calls to write() to write the entry's
4035 * contents. Once the contents are written, closeEntry()
4036 * <B>MUST</B> be called to ensure that all buffered data
4037 * is completely written to the output stream.
4039 * @param entry The TarEntry to be written to the archive.
4042 putNextEntry( TarEntry entry )
4045 StringBuffer name = entry.getHeader().name;
4048 // This check is not adequate, because the maximum file length that
4049 // can be placed into a POSIX (ustar) header depends on the precise
4050 // locations of the path elements (slashes) within the file's full
4051 // pathname. For this reason, writeEntryHeader() can still throw an
4052 // InvalidHeaderException if the file's full pathname will not fit
4055 if ( ( entry.isUnixTarFormat()
4056 && name.length() > TarHeader.NAMELEN )
4058 ( ! entry.isUnixTarFormat()
4059 && name.length() > (TarHeader.NAMELEN + TarHeader.PREFIXLEN) )
4062 throw new InvalidHeaderException
4065 + "' is too long ( "
4068 + ( entry.isUnixTarFormat()
4070 : (TarHeader.NAMELEN + TarHeader.PREFIXLEN) )
4074 entry.writeEntryHeader( this.recordBuf );
4076 this.buffer.writeRecord( this.recordBuf );
4080 if ( entry.isDirectory() )
4083 this.currSize = entry.getSize();
4087 * Close an entry. This method MUST be called for all file
4088 * entries that contain data. The reason is that we must
4089 * buffer data written to the stream in order to satisfy
4090 * the buffer's record based writes. Thus, there may be
4091 * data fragments still being assembled that must be written
4092 * to the output stream before this entry is closed and the
4093 * next entry written.
4099 if ( this.assemLen > 0 )
4101 for ( int i = this.assemLen ; i < this.assemBuf.length ; ++i )
4102 this.assemBuf[i] = 0;
4104 this.buffer.writeRecord( this.assemBuf );
4106 this.currBytes += this.assemLen;
4110 if ( this.currBytes < this.currSize )
4111 throw new IOException
4112 ( "entry closed at '" + this.currBytes
4113 + "' before the '" + this.currSize
4114 + "' bytes specified in the header were written" );
4118 * Writes a byte to the current tar archive entry.
4120 * This method simply calls read( byte[], int, int ).
4122 * @param b The byte written.
4128 this.oneBuf[0] = (byte) b;
4129 this.write( this.oneBuf, 0, 1 );
4133 * Writes bytes to the current tar archive entry.
4135 * This method simply calls read( byte[], int, int ).
4137 * @param wBuf The buffer to write to the archive.
4138 * @return The number of bytes read, or -1 at EOF.
4141 write( byte[] wBuf )
4144 this.write( wBuf, 0, wBuf.length );
4148 * Writes bytes to the current tar archive entry. This method
4149 * is aware of the current entry and will throw an exception if
4150 * you attempt to write bytes past the length specified for the
4151 * current entry. The method is also (painfully) aware of the
4152 * record buffering required by TarBuffer, and manages buffers
4153 * that are not a multiple of recordsize in length, including
4154 * assembling records from small buffers.
4156 * This method simply calls read( byte[], int, int ).
4158 * @param wBuf The buffer to write to the archive.
4159 * @param wOffset The offset in the buffer from which to get bytes.
4160 * @param numToWrite The number of bytes to write.
4163 write( byte[] wBuf, int wOffset, int numToWrite )
4166 if ( (this.currBytes + numToWrite) > this.currSize )
4167 throw new IOException
4168 ( "request to write '" + numToWrite
4169 + "' bytes exceeds size in header of '"
4170 + this.currSize + "' bytes" );
4173 // We have to deal with assembly!!!
4174 // The programmer can be writing little 32 byte chunks for all
4175 // we know, and we must assemble complete records for writing.
4176 // REVIEW Maybe this should be in TarBuffer? Could that help to
4177 // eliminate some of the buffer copying.
4179 if ( this.assemLen > 0 )
4181 if ( (this.assemLen + numToWrite ) >= this.recordBuf.length )
4183 int aLen = this.recordBuf.length - this.assemLen;
4186 ( this.assemBuf, 0, this.recordBuf, 0, this.assemLen );
4189 ( wBuf, wOffset, this.recordBuf, this.assemLen, aLen );
4191 this.buffer.writeRecord( this.recordBuf );
4193 this.currBytes += this.recordBuf.length;
4199 else // ( (this.assemLen + numToWrite ) < this.recordBuf.length )
4202 ( wBuf, wOffset, this.assemBuf,
4203 this.assemLen, numToWrite );
4204 wOffset += numToWrite;
4205 this.assemLen += numToWrite;
4206 numToWrite -= numToWrite;
4211 // When we get here we have EITHER:
4212 // o An empty "assemble" buffer.
4213 // o No bytes to write (numToWrite == 0)
4216 for ( ; numToWrite > 0 ; )
4218 if ( numToWrite < this.recordBuf.length )
4221 ( wBuf, wOffset, this.assemBuf, this.assemLen, numToWrite );
4222 this.assemLen += numToWrite;
4226 this.buffer.writeRecord( wBuf, wOffset );
4228 long num = this.recordBuf.length;
4229 this.currBytes += num;
4236 * Write an EOF (end of archive) record to the tar archive.
4237 * An EOF record consists of a record of all zeros.
4243 for ( int i = 0 ; i < this.recordBuf.length ; ++i )
4244 this.recordBuf[i] = 0;
4245 this.buffer.writeRecord( this.recordBuf );
4251 ** Authored by Timothy Gerard Endres
4252 ** <mailto:time@gjt.org> <http://www.trustice.com>
4254 ** This work has been placed into the public domain.
4255 ** You may use this work in any way and for any purpose you wish.
4257 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
4258 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
4259 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
4260 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
4261 ** REDISTRIBUTION OF THIS SOFTWARE.
4266 * This interface is provided to TarArchive to display progress
4267 * information during operation. This is required to display the
4268 * results of the 'list' operation.
4275 * Display a progress message.
4277 * @param msg The message to display.
4281 showTarProgressMessage( String msg );
4285 ** Authored by Timothy Gerard Endres
4286 ** <mailto:time@gjt.org> <http://www.trustice.com>
4288 ** This work has been placed into the public domain.
4289 ** You may use this work in any way and for any purpose you wish.
4291 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
4292 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
4293 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
4294 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
4295 ** REDISTRIBUTION OF THIS SOFTWARE.
4300 * This interface indicates if a file qualifies for ASCII translation.
4301 * To support customization of TAR translation, this interface allows
4302 * the programmer to provide an object that will check files that do
4303 * not match the MIME types file's check for 'text/*' types. To provide
4304 * your own typer, subclass this class and set the TarArchive's TransFileTyper
4305 * via the method setTransFileTyper().
4312 * Return true if the file should be translated as ASCII.
4314 * @param f The file to be checked to see if it need ASCII translation.
4318 isAsciiFile( File f )
4324 * Return true if the file should be translated as ASCII based on its name.
4325 * The file DOES NOT EXIST. This is called during extract, so all we know
4328 * @param name The name of the file to be checked to see if it need ASCII
4333 isAsciiFile( String name )
4340 ** Authored by Timothy Gerard Endres
4341 ** <mailto:time@gjt.org> <http://www.trustice.com>
4343 ** This work has been placed into the public domain.
4344 ** You may use this work in any way and for any purpose you wish.
4346 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
4347 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
4348 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
4349 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
4350 ** REDISTRIBUTION OF THIS SOFTWARE.
4355 * The tar class implements a weak reproduction of the
4356 * traditional UNIX tar command. It currently supports
4357 * creating, listing, and extracting from archives. It
4358 * also supports GZIP-ed archives with the '-z' flag.
4359 * See the usage (-? or --usage) for option details.
4362 * usage: com.ice.tar.tar has three basic modes:
4364 * com.ice.tar -c [options] archive files...
4365 * Create new archive containing files.
4367 * com.ice.tar -t [options] archive
4368 * List contents of tar archive
4370 * com.ice.tar -x [options] archive
4371 * Extract contents of tar archive.
4374 * -f file, use 'file' as the tar archive
4376 * -z, use GZIP compression
4377 * -D, debug archive and buffer operation
4378 * -b blks, set blocking size to (blks * 512) bytes
4379 * -o, write a V7 format archive rather than ANSI
4380 * -u name, set user name to 'name'
4381 * -U id, set user id to 'id'
4382 * -g name, set group name to 'name'
4383 * -G id, set group id to 'id'
4384 * -?, print usage information
4385 * --trans, translate 'text/*' files
4386 * --mime file, use this mime types file and translate
4387 * --usage, print usage information
4388 * --version, print version information
4390 * The translation options will translate from local line
4391 * endings to UNIX line endings of '\\n' when writing tar
4392 * archives, and from UNIX line endings into local line endings
4393 * when extracting archives.
4395 * Written by Tim Endres
4396 * This software has been placed into the public domain.
4399 * @version $Revision: 1.10 $
4400 * @author Timothy Gerard Endres, <time@gjt.org>
4406 implements TarProgressDisplay
4409 * Flag that determines if debugging information is displayed.
4411 private boolean debug;
4413 * Flag that determines if verbose feedback is provided.
4415 private boolean verbose;
4417 * Flag that determines if IO is GZIP-ed ('-z' option).
4419 private boolean compressed;
4421 * True if we are listing the archive. False if writing or extracting.
4423 private boolean listingArchive;
4425 * True if we are writing the archive. False if we are extracting it.
4427 private boolean writingArchive;
4429 * True if we are writing an old UNIX archive format (sets entry format).
4431 private boolean unixArchiveFormat;
4433 * True if we are not to overwrite existing files.
4435 private boolean keepOldFiles;
4437 * True if we are to convert ASCII text files from local line endings
4438 * to the UNIX standard '\n'.
4440 private boolean asciiTranslate;
4442 * True if a MIME file has been loaded with the '--mime' option.
4444 private boolean mimeFileLoaded;
4447 * The archive name provided on the command line, null if stdio.
4449 private String archiveName;
4452 * The blocksize to use for the tar archive IO. Set by the '-b' option.
4454 private int blockSize;
4457 * The userId to use for files written to archives. Set by '-U' option.
4461 * The userName to use for files written to archives. Set by '-u' option.
4463 private String userName;
4465 * The groupId to use for files written to archives. Set by '-G' option.
4467 private int groupId;
4469 * The groupName to use for files written to archives. Set by '-g' option.
4471 private String groupName;
4475 * The main entry point of the tar class.
4478 main( String argv[] )
4480 tar app = new tar();
4482 app.instanceMain( argv );
4486 * Establishes the default userName with the 'user.name' property.
4492 this.verbose = false;
4493 this.compressed = false;
4494 this.archiveName = null;
4495 this.listingArchive = false;
4496 this.writingArchive = true;
4497 this.unixArchiveFormat = false;
4498 this.keepOldFiles = false;
4499 this.asciiTranslate = false;
4501 this.blockSize = TarBuffer.DEFAULT_BLKSIZE;
4503 String sysUserName =
4504 System.getProperty( "user.name" );
4508 ( (sysUserName == null) ? "" : sysUserName );
4511 this.groupName = "";
4515 * This is the "real" main. The class main() instantiates a tar object
4516 * for the application and then calls this method. Process the arguments
4517 * and perform the requested operation.
4520 instanceMain( String argv[] )
4522 TarArchive archive = null;
4524 int argIdx = this.processArguments( argv );
4526 if ( writingArchive ) // WRITING
4528 OutputStream outStream = System.out;
4530 if ( this.archiveName != null
4531 && ! this.archiveName.equals( "-" ) )
4534 outStream = new FileOutputStream( this.archiveName );
4536 catch ( IOException ex )
4539 ex.printStackTrace( System.err );
4543 if ( outStream != null )
4545 if ( this.compressed )
4548 outStream = new GZIPOutputStream( outStream );
4550 catch ( IOException ex )
4553 ex.printStackTrace( System.err );
4557 archive = new TarArchive( outStream, this.blockSize );
4560 else // EXTRACING OR LISTING
4562 InputStream inStream = System.in;
4564 if ( this.archiveName != null
4565 && ! this.archiveName.equals( "-" ) )
4568 inStream = new FileInputStream( this.archiveName );
4570 catch ( IOException ex )
4573 ex.printStackTrace( System.err );
4577 if ( inStream != null )
4579 if ( this.compressed )
4582 inStream = new GZIPInputStream( inStream );
4584 catch ( IOException ex )
4587 ex.printStackTrace( System.err );
4591 archive = new TarArchive( inStream, this.blockSize );
4595 if ( archive != null ) // SET ARCHIVE OPTIONS
4597 archive.setDebug( this.debug );
4598 archive.setVerbose( this.verbose );
4599 archive.setTarProgressDisplay( this );
4600 archive.setKeepOldFiles( this.keepOldFiles );
4601 archive.setAsciiTranslation( this.asciiTranslate );
4603 archive.setUserInfo(
4604 this.userId, this.userName,
4605 this.groupId, this.groupName );
4608 if ( archive == null )
4610 System.err.println( "no processing due to errors" );
4612 else if ( this.writingArchive ) // WRITING
4614 for ( ; argIdx < argv.length ; ++argIdx )
4617 File f = new File( argv[ argIdx ] );
4619 TarEntry entry = new TarEntry( f );
4621 if ( this.unixArchiveFormat )
4622 entry.setUnixTarFormat();
4624 entry.setUSTarFormat();
4626 archive.writeEntry( entry, true );
4628 catch ( IOException ex )
4630 ex.printStackTrace( System.err );
4634 else if ( this.listingArchive ) // LISTING
4637 archive.listContents();
4639 catch ( InvalidHeaderException ex )
4641 ex.printStackTrace( System.err );
4643 catch ( IOException ex )
4645 ex.printStackTrace( System.err );
4651 System.getProperty( "user.dir", null );
4653 File destDir = new File( userDir );
4654 if ( ! destDir.exists() )
4656 if ( ! destDir.mkdirs() )
4659 Throwable ex = new Throwable
4660 ( "ERROR, mkdirs() on '" + destDir.getPath()
4661 + "' returned false." );
4662 ex.printStackTrace( System.err );
4666 if ( destDir != null )
4669 archive.extractContents( destDir );
4671 catch ( InvalidHeaderException ex )
4673 ex.printStackTrace( System.err );
4675 catch ( IOException ex )
4677 ex.printStackTrace( System.err );
4682 if ( archive != null ) // CLOSE ARCHIVE
4685 archive.closeArchive();
4687 catch ( IOException ex )
4689 ex.printStackTrace( System.err );
4695 * Process arguments, handling options, and return the index of the
4696 * first non-option argument.
4698 * @return The index of the first non-option argument.
4702 processArguments( String args[] )
4705 boolean gotOP = false;
4707 for ( ; idx < args.length ; ++idx )
4709 String arg = args[ idx ];
4711 if ( ! arg.startsWith( "-" ) )
4714 if ( arg.startsWith( "--" ) )
4716 if ( arg.equals( "--usage" ) )
4721 else if ( arg.equals( "--version" ) )
4729 ( "unknown option: " + arg );
4734 else for ( int cIdx = 1 ; cIdx < arg.length() ; ++cIdx )
4736 char ch = arg.charAt( cIdx );
4743 else if ( ch == 'f' )
4745 this.archiveName = args[ ++idx ];
4747 else if ( ch == 'z' )
4749 this.compressed = true;
4751 else if ( ch == 'c' )
4754 this.writingArchive = true;
4755 this.listingArchive = false;
4757 else if ( ch == 'x' )
4760 this.writingArchive = false;
4761 this.listingArchive = false;
4763 else if ( ch == 't' )
4766 this.writingArchive = false;
4767 this.listingArchive = true;
4769 else if ( ch == 'k' )
4771 this.keepOldFiles = true;
4773 else if ( ch == 'o' )
4775 this.unixArchiveFormat = true;
4777 else if ( ch == 'b' )
4780 int blks = Integer.parseInt( args[ ++idx ] );
4782 ( blks * TarBuffer.DEFAULT_RCDSIZE );
4784 catch ( NumberFormatException ex )
4786 ex.printStackTrace( System.err );
4789 else if ( ch == 'u' )
4791 this.userName = args[ ++idx ];
4793 else if ( ch == 'U' )
4795 String idStr = args[ ++idx ];
4797 this.userId = Integer.parseInt( idStr );
4799 catch ( NumberFormatException ex )
4802 ex.printStackTrace( System.err );
4805 else if ( ch == 'g' )
4807 this.groupName = args[ ++idx ];
4809 else if ( ch == 'G' )
4811 String idStr = args[ ++idx ];
4813 this.groupId = Integer.parseInt( idStr );
4815 catch ( NumberFormatException ex )
4818 ex.printStackTrace( System.err );
4821 else if ( ch == 'v' )
4823 this.verbose = true;
4825 else if ( ch == 'D' )
4832 ( "unknown option: " + ch );
4842 ( "you must specify an operation option (c, x, or t)" );
4850 // I N T E R F A C E TarProgressDisplay
4853 * Display progress information by printing it to System.out.
4857 showTarProgressMessage( String msg )
4859 System.out.println( msg );
4863 * Print version information.
4870 ( "Release 2.4 - $Revision: 1.10 $ $Name: $" );
4874 * Print usage information.
4880 System.err.println( "usage: com.ice.tar.tar has three basic modes:" );
4881 System.err.println( " com.ice.tar -c [options] archive files..." );
4882 System.err.println( " Create new archive containing files." );
4883 System.err.println( " com.ice.tar -t [options] archive" );
4884 System.err.println( " List contents of tar archive" );
4885 System.err.println( " com.ice.tar -x [options] archive" );
4886 System.err.println( " Extract contents of tar archive." );
4887 System.err.println( "" );
4888 System.err.println( "options:" );
4889 System.err.println( " -f file, use 'file' as the tar archive" );
4890 System.err.println( " -v, verbose mode" );
4891 System.err.println( " -z, use GZIP compression" );
4892 System.err.println( " -D, debug archive and buffer operation" );
4893 System.err.println( " -b blks, set blocking size to (blks * 512) bytes" );
4894 System.err.println( " -o, write a V7 format archive rather than ANSI" );
4895 System.err.println( " -u name, set user name to 'name'" );
4896 System.err.println( " -U id, set user id to 'id'" );
4897 System.err.println( " -g name, set group name to 'name'" );
4898 System.err.println( " -G id, set group id to 'id'" );
4899 System.err.println( " -?, print usage information" );
4900 System.err.println( " --trans, translate 'text/*' files" );
4901 System.err.println( " --mime file, use this mime types file and translate" );
4902 System.err.println( " --usage, print usage information" );
4903 System.err.println( " --version, print version information" );
4904 System.err.println( "" );
4905 System.err.println( "The translation options will translate from local line" );
4906 System.err.println( "endings to UNIX line endings of '\\n' when writing tar" );
4907 System.err.println( "archives, and from UNIX line endings into local line endings" );
4908 System.err.println( "when extracting archives." );
4909 System.err.println( "" );
4910 System.err.println( "Written by Tim Endres" );
4911 System.err.println( "" );
4912 System.err.println( "This software has been placed into the public domain." );
4913 System.err.println( "" );