--- /dev/null
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time@gjt.org> <http://www.trustice.com>
+**
+** This work has been placed into the public domain.
+** You may use this work in any way and for any purpose you wish.
+**
+** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
+** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
+** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
+** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
+** REDISTRIBUTION OF THIS SOFTWARE.
+**
+*/
+
+package org.ibex.util;
+import java.io.*;
+import java.util.*;
+import java.net.*;
+import java.util.zip.*;
+import javax.activation.*;
+
+
+public class Tar {
+/**
+ * Special class designed to parse a Tar archive VERY FAST.
+ * This class is not a general Tar archive solution because
+ * it does not accomodate TarBuffer, or blocking. It does not
+ * allow you to read the entries either. This would not be
+ * difficult to add in a subclass.
+ *
+ * The real purpose of this class is that there are folks out
+ * there who wish to parse an ENORMOUS tar archive, and maybe
+ * only want to know the filenames, or they wish to locate the
+ * offset of a particular entry so that can process that entry
+ * with special code.
+ *
+ * @author Timothy Gerard Endres, <time@gjt.org>
+ *
+ */
+
+public static
+class FastTarStream
+ {
+ private boolean debug = false;
+ private boolean hasHitEOF = false;
+ private TarEntry currEntry = null;
+ private InputStream inStream = null;
+ private int recordSize = TarBuffer.DEFAULT_RCDSIZE;
+
+
+ public
+ FastTarStream( InputStream in )
+ {
+ this( in, TarBuffer.DEFAULT_RCDSIZE );
+ }
+
+ public
+ FastTarStream( InputStream in, int recordSize )
+ {
+ this.inStream = in;
+ this.hasHitEOF = false;
+ this.currEntry = null;
+ this.recordSize = recordSize;
+ }
+
+ public void
+ setDebug( boolean debug )
+ {
+ this.debug = debug;
+ }
+
+ public TarEntry
+ getNextEntry()
+ throws IOException
+ {
+ if ( this.hasHitEOF )
+ return null;
+
+ /**
+ * Here I have tried skipping the entry size, I have tried
+ * skipping entrysize - header size,
+ * entrysize + header, and all seem to skip to some bizzarelocation!
+ */
+ if ( this.currEntry != null && this.currEntry.getSize() > 0 )
+ {
+ // Need to round out the number of records to be read to skip entry...
+ int numRecords =
+ ( (int)this.currEntry.getSize() + (this.recordSize - 1) )
+ / this.recordSize;
+
+ if ( numRecords > 0 )
+ {
+ this.inStream.skip( numRecords * this.recordSize );
+ }
+ }
+
+ byte[] headerBuf = new byte[ this.recordSize ];
+
+ // NOTE Apparently (with GZIPInputStream maybe?) we are able to
+ // read less then record size bytes in any given read(). So,
+ // we have to be persistent.
+
+ int bufIndex = 0;
+ for ( int bytesNeeded = this.recordSize ; bytesNeeded > 0 ; )
+ {
+ int numRead = this.inStream.read( headerBuf, bufIndex, bytesNeeded );
+
+ if ( numRead == -1 )
+ {
+ this.hasHitEOF = true;
+ break;
+ }
+
+ bufIndex += numRead;
+ bytesNeeded -= numRead;
+ }
+
+ // Check for "EOF block" of all zeros
+ if ( ! this.hasHitEOF )
+ {
+ this.hasHitEOF = true;
+ for ( int i = 0 ; i < headerBuf.length ; ++i )
+ {
+ if ( headerBuf[i] != 0 )
+ {
+ this.hasHitEOF = false;
+ break;
+ }
+ }
+ }
+
+ if ( this.hasHitEOF )
+ {
+ this.currEntry = null;
+ }
+ else
+ {
+ try {
+ this.currEntry = new TarEntry( headerBuf );
+
+ if ( this.debug )
+ {
+ byte[] by = new byte[ headerBuf.length ];
+ for(int i = 0; i < headerBuf.length; i++)
+ {
+ by[i] = ( headerBuf[i] == 0? 20: headerBuf[i] );
+ }
+ String s = new String( by );
+ System.out.println( "\n" + s );
+ }
+
+ if ( ! ( headerBuf[257] == 'u' &&headerBuf[258] == 's'
+ && headerBuf[259] == 't' &&headerBuf[260] == 'a'
+ && headerBuf[261] == 'r' ) )
+ {
+ throw new InvalidHeaderException
+ ( "header magic is not'ustar', but '"
+ + headerBuf[257] +headerBuf[258] + headerBuf[259]
+ + headerBuf[260] +headerBuf[261] + "', or (dec) "
+ +((int)headerBuf[257]) + ", "
+ +((int)headerBuf[258]) + ", "
+ +((int)headerBuf[259]) + ", "
+ +((int)headerBuf[260]) + ", "
+ +((int)headerBuf[261]) );
+ }
+ }
+ catch ( InvalidHeaderException ex )
+ {
+ this.currEntry = null;
+ throw ex;
+ }
+ }
+
+ return this.currEntry;
+ }
+
+ public static void
+ main( String[] args )
+ {
+ boolean debug = false;
+ InputStream in = null;
+
+ String fileName = args[0];
+
+ try {
+ int idx = 0;
+ if ( args.length > 0 )
+ {
+ if ( args[idx].equals( "-d" ) )
+ {
+ debug = true;
+ idx++;
+ }
+
+ if ( args[idx].endsWith( ".gz" )
+ || args[idx].endsWith( ".tgz" ) )
+ {
+ in = new GZIPInputStream( new FileInputStream( args[idx] ) );
+ }
+ else
+ {
+ in = new FileInputStream( args[idx] );
+ }
+ }
+ else
+ {
+ in = System.in;
+ }
+
+ FastTarStream fts = new FastTarStream( in );
+ fts.setDebug( debug );
+
+ int nameWidth = 56;
+ int sizeWidth = 9;
+ int userWidth = 8;
+ StringBuffer padBuf = new StringBuffer(128);
+ for ( ; ; )
+ {
+ TarEntry entry = fts.getNextEntry();
+ if ( entry == null )
+ break;
+
+ if ( entry.isDirectory() )
+ {
+ // TYPE
+ System.out.print( "D " );
+
+ // NAME
+ padBuf.setLength(0);
+ padBuf.append( entry.getName() );
+ padBuf.setLength( padBuf.length() - 1 ); // drop '/'
+ if ( padBuf.length() > nameWidth )
+ padBuf.setLength( nameWidth );
+ for ( ; padBuf.length() < nameWidth ; )
+ padBuf.append( '_' );
+
+ padBuf.append( '_' );
+ System.out.print( padBuf.toString() );
+
+ // SIZE
+ padBuf.setLength(0);
+ for ( ; padBuf.length() < sizeWidth ; )
+ padBuf.insert( 0, '_' );
+
+ padBuf.append( ' ' );
+ System.out.print( padBuf.toString() );
+
+ // USER
+ padBuf.setLength(0);
+ padBuf.append( entry.getUserName() );
+ if ( padBuf.length() > userWidth )
+ padBuf.setLength( userWidth );
+ for ( ; padBuf.length() < userWidth ; )
+ padBuf.append( ' ' );
+
+ System.out.print( padBuf.toString() );
+ }
+ else
+ {
+ // TYPE
+ System.out.print( "F " );
+
+ // NAME
+ padBuf.setLength(0);
+ padBuf.append( entry.getName() );
+ if ( padBuf.length() > nameWidth )
+ padBuf.setLength( nameWidth );
+ for ( ; padBuf.length() < nameWidth ; )
+ padBuf.append( ' ' );
+
+ padBuf.append( ' ' );
+ System.out.print( padBuf.toString() );
+
+ // SIZE
+ padBuf.setLength(0);
+ padBuf.append( entry.getSize() );
+ if ( padBuf.length() > sizeWidth )
+ padBuf.setLength( sizeWidth );
+ for ( ; padBuf.length() < sizeWidth ; )
+ padBuf.insert( 0, ' ' );
+
+ padBuf.append( ' ' );
+ System.out.print( padBuf.toString() );
+
+ // USER
+ padBuf.setLength(0);
+ padBuf.append( entry.getUserName() );
+ if ( padBuf.length() > userWidth )
+ padBuf.setLength( userWidth );
+ for ( ; padBuf.length() < userWidth ; )
+ padBuf.append( ' ' );
+
+ System.out.print( padBuf.toString() );
+ }
+
+ System.out.println( "" );
+ }
+ }
+ catch ( IOException ex )
+ {
+ ex.printStackTrace( System.err );
+ }
+ }
+
+ }
+
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time@gjt.org> <http://www.trustice.com>
+**
+** This work has been placed into the public domain.
+** You may use this work in any way and for any purpose you wish.
+**
+** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
+** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
+** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
+** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
+** REDISTRIBUTION OF THIS SOFTWARE.
+**
+*/
+
+/**
+ * This exception is used to indicate that there is a problem
+ * with a TAR archive header.
+ */
+
+public static class
+InvalidHeaderException extends IOException
+ {
+
+ public
+ InvalidHeaderException()
+ {
+ super();
+ }
+
+ public
+ InvalidHeaderException( String msg )
+ {
+ super( msg );
+ }
+
+ }
+
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time@gjt.org> <http://www.trustice.com>
+**
+** This work has been placed into the public domain.
+** You may use this work in any way and for any purpose you wish.
+**
+** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
+** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
+** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
+** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
+** REDISTRIBUTION OF THIS SOFTWARE.
+**
+*/
+
+/**
+ * The TarArchive class implements the concept of a
+ * tar archive. A tar archive is a series of entries, each of
+ * which represents a file system object. Each entry in
+ * the archive consists of a header record. Directory entries
+ * consist only of the header record, and are followed by entries
+ * for the directory's contents. File entries consist of a
+ * header record followed by the number of records needed to
+ * contain the file's contents. All entries are written on
+ * record boundaries. Records are 512 bytes long.
+ *
+ * TarArchives are instantiated in either read or write mode,
+ * based upon whether they are instantiated with an InputStream
+ * or an OutputStream. Once instantiated TarArchives read/write
+ * mode can not be changed.
+ *
+ * There is currently no support for random access to tar archives.
+ * However, it seems that subclassing TarArchive, and using the
+ * TarBuffer.getCurrentRecordNum() and TarBuffer.getCurrentBlockNum()
+ * methods, this would be rather trvial.
+ *
+ * @version $Revision: 1.15 $
+ * @author Timothy Gerard Endres, <time@gjt.org>
+ * @see TarBuffer
+ * @see TarHeader
+ * @see TarEntry
+ */
+
+
+public static class
+TarArchive extends Object
+ {
+ protected boolean verbose;
+ protected boolean debug;
+ protected boolean keepOldFiles;
+ protected boolean asciiTranslate;
+
+ protected int userId;
+ protected String userName;
+ protected int groupId;
+ protected String groupName;
+
+ protected String rootPath;
+ protected String tempPath;
+ protected String pathPrefix;
+
+ protected int recordSize;
+ protected byte[] recordBuf;
+
+ protected TarInputStream tarIn;
+ protected TarOutputStream tarOut;
+
+ protected TarTransFileTyper transTyper;
+ protected TarProgressDisplay progressDisplay;
+
+
+ /**
+ * The InputStream based constructors create a TarArchive for the
+ * purposes of e'x'tracting or lis't'ing a tar archive. Thus, use
+ * these constructors when you wish to extract files from or list
+ * the contents of an existing tar archive.
+ */
+
+ public
+ TarArchive( InputStream inStream )
+ {
+ this( inStream, TarBuffer.DEFAULT_BLKSIZE );
+ }
+
+ public
+ TarArchive( InputStream inStream, int blockSize )
+ {
+ this( inStream, blockSize, TarBuffer.DEFAULT_RCDSIZE );
+ }
+
+ public
+ TarArchive( InputStream inStream, int blockSize, int recordSize )
+ {
+ this.tarIn = new TarInputStream( inStream, blockSize, recordSize );
+ this.initialize( recordSize );
+ }
+
+ /**
+ * The OutputStream based constructors create a TarArchive for the
+ * purposes of 'c'reating a tar archive. Thus, use these constructors
+ * when you wish to create a new tar archive and write files into it.
+ */
+
+ public
+ TarArchive( OutputStream outStream )
+ {
+ this( outStream, TarBuffer.DEFAULT_BLKSIZE );
+ }
+
+ public
+ TarArchive( OutputStream outStream, int blockSize )
+ {
+ this( outStream, blockSize, TarBuffer.DEFAULT_RCDSIZE );
+ }
+
+ public
+ TarArchive( OutputStream outStream, int blockSize, int recordSize )
+ {
+ this.tarOut = new TarOutputStream( outStream, blockSize, recordSize );
+ this.initialize( recordSize );
+ }
+
+ /**
+ * Common constructor initialization code.
+ */
+
+ private void
+ initialize( int recordSize )
+ {
+ this.rootPath = null;
+ this.pathPrefix = null;
+ this.tempPath = System.getProperty( "user.dir" );
+
+ this.userId = 0;
+ this.userName = "";
+ this.groupId = 0;
+ this.groupName = "";
+
+ this.debug = false;
+ this.verbose = false;
+ this.keepOldFiles = false;
+ this.progressDisplay = null;
+
+ this.recordBuf =
+ new byte[ this.getRecordSize() ];
+ }
+
+ /**
+ * Set the debugging flag.
+ *
+ * @param debugF The new debug setting.
+ */
+
+ public void
+ setDebug( boolean debugF )
+ {
+ this.debug = debugF;
+ if ( this.tarIn != null )
+ this.tarIn.setDebug( debugF );
+ else if ( this.tarOut != null )
+ this.tarOut.setDebug( debugF );
+ }
+
+ /**
+ * Returns the verbosity setting.
+ *
+ * @return The current verbosity setting.
+ */
+
+ public boolean
+ isVerbose()
+ {
+ return this.verbose;
+ }
+
+ /**
+ * Set the verbosity flag.
+ *
+ * @param verbose The new verbosity setting.
+ */
+
+ public void
+ setVerbose( boolean verbose )
+ {
+ this.verbose = verbose;
+ }
+
+ /**
+ * Set the current progress display interface. This allows the
+ * programmer to use a custom class to display the progress of
+ * the archive's processing.
+ *
+ * @param display The new progress display interface.
+ * @see TarProgressDisplay
+ */
+
+ public void
+ setTarProgressDisplay( TarProgressDisplay display )
+ {
+ this.progressDisplay = display;
+ }
+
+ /**
+ * Set the flag that determines whether existing files are
+ * kept, or overwritten during extraction.
+ *
+ * @param keepOldFiles If true, do not overwrite existing files.
+ */
+
+ public void
+ setKeepOldFiles( boolean keepOldFiles )
+ {
+ this.keepOldFiles = keepOldFiles;
+ }
+
+ /**
+ * Set the ascii file translation flag. If ascii file translatio
+ * is true, then the MIME file type will be consulted to determine
+ * if the file is of type 'text/*'. If the MIME type is not found,
+ * then the TransFileTyper is consulted if it is not null. If
+ * either of these two checks indicates the file is an ascii text
+ * file, it will be translated. The translation converts the local
+ * operating system's concept of line ends into the UNIX line end,
+ * '\n', which is the defacto standard for a TAR archive. This makes
+ * text files compatible with UNIX, and since most tar implementations
+ * for other platforms, compatible with most other platforms.
+ *
+ * @param asciiTranslate If true, translate ascii text files.
+ */
+
+ public void
+ setAsciiTranslation( boolean asciiTranslate )
+ {
+ this.asciiTranslate = asciiTranslate;
+ }
+
+ /**
+ * Set the object that will determine if a file is of type
+ * ascii text for translation purposes.
+ *
+ * @param transTyper The new TransFileTyper object.
+ */
+
+ public void
+ setTransFileTyper( TarTransFileTyper transTyper )
+ {
+ this.transTyper = transTyper;
+ }
+
+ /**
+ * Set user and group information that will be used to fill in the
+ * tar archive's entry headers. Since Java currently provides no means
+ * of determining a user name, user id, group name, or group id for
+ * a given File, TarArchive allows the programmer to specify values
+ * to be used in their place.
+ *
+ * @param userId The user Id to use in the headers.
+ * @param userName The user name to use in the headers.
+ * @param groupId The group id to use in the headers.
+ * @param groupName The group name to use in the headers.
+ */
+
+ public void
+ setUserInfo(
+ int userId, String userName,
+ int groupId, String groupName )
+ {
+ this.userId = userId;
+ this.userName = userName;
+ this.groupId = groupId;
+ this.groupName = groupName;
+ }
+
+ /**
+ * Get the user id being used for archive entry headers.
+ *
+ * @return The current user id.
+ */
+
+ public int
+ getUserId()
+ {
+ return this.userId;
+ }
+
+ /**
+ * Get the user name being used for archive entry headers.
+ *
+ * @return The current user name.
+ */
+
+ public String
+ getUserName()
+ {
+ return this.userName;
+ }
+
+ /**
+ * Get the group id being used for archive entry headers.
+ *
+ * @return The current group id.
+ */
+
+ public int
+ getGroupId()
+ {
+ return this.groupId;
+ }
+
+ /**
+ * Get the group name being used for archive entry headers.
+ *
+ * @return The current group name.
+ */
+
+ public String
+ getGroupName()
+ {
+ return this.groupName;
+ }
+
+ /**
+ * Get the current temporary directory path. Because Java's
+ * File did not support temporary files until version 1.2,
+ * TarArchive manages its own concept of the temporary
+ * directory. The temporary directory defaults to the
+ * 'user.dir' System property.
+ *
+ * @return The current temporary directory path.
+ */
+
+ public String
+ getTempDirectory()
+ {
+ return this.tempPath;
+ }
+
+ /**
+ * Set the current temporary directory path.
+ *
+ * @param path The new temporary directory path.
+ */
+
+ public void
+ setTempDirectory( String path )
+ {
+ this.tempPath = path;
+ }
+
+ /**
+ * Get the archive's record size. Because of its history, tar
+ * supports the concept of buffered IO consisting of BLOCKS of
+ * RECORDS. This allowed tar to match the IO characteristics of
+ * the physical device being used. Of course, in the Java world,
+ * this makes no sense, WITH ONE EXCEPTION - archives are expected
+ * to be propertly "blocked". Thus, all of the horrible TarBuffer
+ * support boils down to simply getting the "boundaries" correct.
+ *
+ * @return The record size this archive is using.
+ */
+
+ public int
+ getRecordSize()
+ {
+ if ( this.tarIn != null )
+ {
+ return this.tarIn.getRecordSize();
+ }
+ else if ( this.tarOut != null )
+ {
+ return this.tarOut.getRecordSize();
+ }
+
+ return TarBuffer.DEFAULT_RCDSIZE;
+ }
+
+ /**
+ * Get a path for a temporary file for a given File. The
+ * temporary file is NOT created. The algorithm attempts
+ * to handle filename collisions so that the name is
+ * unique.
+ *
+ * @return The temporary file's path.
+ */
+
+ private String
+ getTempFilePath( File eFile )
+ {
+ String pathStr =
+ this.tempPath + File.separator
+ + eFile.getName() + ".tmp";
+
+ for ( int i = 1 ; i < 5 ; ++i )
+ {
+ File f = new File( pathStr );
+
+ if ( ! f.exists() )
+ break;
+
+ pathStr =
+ this.tempPath + File.separator
+ + eFile.getName() + "-" + i + ".tmp";
+ }
+
+ return pathStr;
+ }
+
+ /**
+ * Close the archive. This simply calls the underlying
+ * tar stream's close() method.
+ */
+
+ public void
+ closeArchive()
+ throws IOException
+ {
+ if ( this.tarIn != null )
+ {
+ this.tarIn.close();
+ }
+ else if ( this.tarOut != null )
+ {
+ this.tarOut.close();
+ }
+ }
+
+ /**
+ * Perform the "list" command and list the contents of the archive.
+ * NOTE That this method uses the progress display to actually list
+ * the conents. If the progress display is not set, nothing will be
+ * listed!
+ */
+
+ public void
+ listContents()
+ throws IOException, InvalidHeaderException
+ {
+ for ( ; ; )
+ {
+ TarEntry entry = this.tarIn.getNextEntry();
+
+
+ if ( entry == null )
+ {
+ if ( this.debug )
+ {
+ System.err.println( "READ EOF RECORD" );
+ }
+ break;
+ }
+
+ if ( this.progressDisplay != null )
+ this.progressDisplay.showTarProgressMessage
+ ( entry.getName() );
+ }
+ }
+
+ /**
+ * Perform the "extract" command and extract the contents of the archive.
+ *
+ * @param destDir The destination directory into which to extract.
+ */
+
+ public void
+ extractContents( File destDir )
+ throws IOException, InvalidHeaderException
+ {
+ for ( ; ; )
+ {
+ TarEntry entry = this.tarIn.getNextEntry();
+
+ if ( entry == null )
+ {
+ if ( this.debug )
+ {
+ System.err.println( "READ EOF RECORD" );
+ }
+ break;
+ }
+
+ this.extractEntry( destDir, entry );
+ }
+ }
+
+ /**
+ * Extract an entry from the archive. This method assumes that the
+ * tarIn stream has been properly set with a call to getNextEntry().
+ *
+ * @param destDir The destination directory into which to extract.
+ * @param entry The TarEntry returned by tarIn.getNextEntry().
+ */
+
+ private void
+ extractEntry( File destDir, TarEntry entry )
+ throws IOException
+ {
+ if ( this.verbose )
+ {
+ if ( this.progressDisplay != null )
+ this.progressDisplay.showTarProgressMessage
+ ( entry.getName() );
+ }
+
+ String name = entry.getName();
+ name = name.replace( '/', File.separatorChar );
+
+ File destFile = new File( destDir, name );
+
+ if ( entry.isDirectory() )
+ {
+ if ( ! destFile.exists() )
+ {
+ if ( ! destFile.mkdirs() )
+ {
+ throw new IOException
+ ( "error making directory path '"
+ + destFile.getPath() + "'" );
+ }
+ }
+ }
+ else
+ {
+ File subDir = new File( destFile.getParent() );
+
+ if ( ! subDir.exists() )
+ {
+ if ( ! subDir.mkdirs() )
+ {
+ throw new IOException
+ ( "error making directory path '"
+ + subDir.getPath() + "'" );
+ }
+ }
+
+ if ( this.keepOldFiles && destFile.exists() )
+ {
+ if ( this.verbose )
+ {
+ if ( this.progressDisplay != null )
+ this.progressDisplay.showTarProgressMessage
+ ( "not overwriting " + entry.getName() );
+ }
+ }
+ else
+ {
+ boolean asciiTrans = false;
+
+ FileOutputStream out =
+ new FileOutputStream( destFile );
+
+
+ PrintWriter outw = null;
+ if ( asciiTrans )
+ {
+ outw = new PrintWriter( out );
+ }
+
+ byte[] rdbuf = new byte[32 * 1024];
+
+ for ( ; ; )
+ {
+ int numRead = this.tarIn.read( rdbuf );
+
+ if ( numRead == -1 )
+ break;
+
+ if ( asciiTrans )
+ {
+ for ( int off = 0, b = 0 ; b < numRead ; ++b )
+ {
+ if ( rdbuf[ b ] == 10 )
+ {
+ String s = new String
+ ( rdbuf, off, (b - off) );
+
+ outw.println( s );
+
+ off = b + 1;
+ }
+ }
+ }
+ else
+ {
+ out.write( rdbuf, 0, numRead );
+ }
+ }
+
+ if ( asciiTrans )
+ outw.close();
+ else
+ out.close();
+ }
+ }
+ }
+
+ /**
+ * Write an entry to the archive. This method will call the putNextEntry()
+ * and then write the contents of the entry, and finally call closeEntry()
+ * for entries that are files. For directories, it will call putNextEntry(),
+ * and then, if the recurse flag is true, process each entry that is a
+ * child of the directory.
+ *
+ * @param entry The TarEntry representing the entry to write to the archive.
+ * @param recurse If true, process the children of directory entries.
+ */
+
+ public void
+ writeEntry( TarEntry oldEntry, boolean recurse )
+ throws IOException
+ {
+ boolean asciiTrans = false;
+ boolean unixArchiveFormat = oldEntry.isUnixTarFormat();
+
+ File tFile = null;
+ File eFile = oldEntry.getFile();
+
+ // Work on a copy of the entry so we can manipulate it.
+ // Note that we must distinguish how the entry was constructed.
+ //
+ TarEntry entry = (TarEntry) oldEntry.clone();
+
+ if ( this.verbose )
+ {
+ if ( this.progressDisplay != null )
+ this.progressDisplay.showTarProgressMessage
+ ( entry.getName() );
+ }
+
+
+ String newName = null;
+
+ if ( this.rootPath != null )
+ {
+ if ( entry.getName().startsWith( this.rootPath ) )
+ {
+ newName =
+ entry.getName().substring
+ ( this.rootPath.length() + 1 );
+ }
+ }
+
+ if ( this.pathPrefix != null )
+ {
+ newName = (newName == null)
+ ? this.pathPrefix + "/" + entry.getName()
+ : this.pathPrefix + "/" + newName;
+ }
+
+ if ( newName != null )
+ {
+ entry.setName( newName );
+ }
+
+ this.tarOut.putNextEntry( entry );
+
+ if ( entry.isDirectory() )
+ {
+ if ( recurse )
+ {
+ TarEntry[] list = entry.getDirectoryEntries();
+
+ for ( int i = 0 ; i < list.length ; ++i )
+ {
+ TarEntry dirEntry = list[i];
+
+ if ( unixArchiveFormat )
+ dirEntry.setUnixTarFormat();
+
+ this.writeEntry( dirEntry, recurse );
+ }
+ }
+ }
+ else
+ {
+ FileInputStream in =
+ new FileInputStream( eFile );
+
+ byte[] eBuf = new byte[ 32 * 1024 ];
+ for ( ; ; )
+ {
+ int numRead = in.read( eBuf, 0, eBuf.length );
+
+ if ( numRead == -1 )
+ break;
+
+ this.tarOut.write( eBuf, 0, numRead );
+ }
+
+ in.close();
+
+ if ( tFile != null )
+ {
+ tFile.delete();
+ }
+
+ this.tarOut.closeEntry();
+ }
+ }
+
+ }
+
+
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time@gjt.org> <http://www.trustice.com>
+**
+** This work has been placed into the public domain.
+** You may use this work in any way and for any purpose you wish.
+**
+** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
+** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
+** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
+** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
+** REDISTRIBUTION OF THIS SOFTWARE.
+**
+*/
+
+/**
+ * The TarBuffer class implements the tar archive concept
+ * of a buffered input stream. This concept goes back to the
+ * days of blocked tape drives and special io devices. In the
+ * Java universe, the only real function that this class
+ * performs is to ensure that files have the correct "block"
+ * size, or other tars will complain.
+ * <p>
+ * You should never have a need to access this class directly.
+ * TarBuffers are created by Tar IO Streams.
+ *
+ * @version $Revision: 1.10 $
+ * @author Timothy Gerard Endres,
+ * <a href="mailto:time@gjt.org">time@trustice.com</a>.
+ * @see TarArchive
+ */
+
+public
+static class TarBuffer
+extends Object
+ {
+ public static final int DEFAULT_RCDSIZE = ( 512 );
+ public static final int DEFAULT_BLKSIZE = ( DEFAULT_RCDSIZE * 20 );
+
+ private InputStream inStream;
+ private OutputStream outStream;
+
+ private byte[] blockBuffer;
+ private int currBlkIdx;
+ private int currRecIdx;
+ private int blockSize;
+ private int recordSize;
+ private int recsPerBlock;
+
+ private boolean debug;
+
+
+ public
+ TarBuffer( InputStream inStream )
+ {
+ this( inStream, TarBuffer.DEFAULT_BLKSIZE );
+ }
+
+ public
+ TarBuffer( InputStream inStream, int blockSize )
+ {
+ this( inStream, blockSize, TarBuffer.DEFAULT_RCDSIZE );
+ }
+
+ public
+ TarBuffer( InputStream inStream, int blockSize, int recordSize )
+ {
+ this.inStream = inStream;
+ this.outStream = null;
+ this.initialize( blockSize, recordSize );
+ }
+
+ public
+ TarBuffer( OutputStream outStream )
+ {
+ this( outStream, TarBuffer.DEFAULT_BLKSIZE );
+ }
+
+ public
+ TarBuffer( OutputStream outStream, int blockSize )
+ {
+ this( outStream, blockSize, TarBuffer.DEFAULT_RCDSIZE );
+ }
+
+ public
+ TarBuffer( OutputStream outStream, int blockSize, int recordSize )
+ {
+ this.inStream = null;
+ this.outStream = outStream;
+ this.initialize( blockSize, recordSize );
+ }
+
+ /**
+ * Initialization common to all constructors.
+ */
+ private void
+ initialize( int blockSize, int recordSize )
+ {
+ this.debug = false;
+ this.blockSize = blockSize;
+ this.recordSize = recordSize;
+ this.recsPerBlock = ( this.blockSize / this.recordSize );
+ this.blockBuffer = new byte[ this.blockSize ];
+
+ if ( this.inStream != null )
+ {
+ this.currBlkIdx = -1;
+ this.currRecIdx = this.recsPerBlock;
+ }
+ else
+ {
+ this.currBlkIdx = 0;
+ this.currRecIdx = 0;
+ }
+ }
+
+ /**
+ * Get the TAR Buffer's block size. Blocks consist of multiple records.
+ */
+ public int
+ getBlockSize()
+ {
+ return this.blockSize;
+ }
+
+ /**
+ * Get the TAR Buffer's record size.
+ */
+ public int
+ getRecordSize()
+ {
+ return this.recordSize;
+ }
+
+ /**
+ * Set the debugging flag for the buffer.
+ *
+ * @param debug If true, print debugging output.
+ */
+ public void
+ setDebug( boolean debug )
+ {
+ this.debug = debug;
+ }
+
+ /**
+ * Determine if an archive record indicate End of Archive. End of
+ * archive is indicated by a record that consists entirely of null bytes.
+ *
+ * @param record The record data to check.
+ */
+ public boolean
+ isEOFRecord( byte[] record )
+ {
+ for ( int i = 0, sz = this.getRecordSize() ; i < sz ; ++i )
+ if ( record[i] != 0 )
+ return false;
+
+ return true;
+ }
+
+ /**
+ * Skip over a record on the input stream.
+ */
+
+ public void
+ skipRecord()
+ throws IOException
+ {
+ if ( this.debug )
+ {
+ System.err.println
+ ( "SkipRecord: recIdx = " + this.currRecIdx
+ + " blkIdx = " + this.currBlkIdx );
+ }
+
+ if ( this.inStream == null )
+ throw new IOException
+ ( "reading (via skip) from an output buffer" );
+
+ if ( this.currRecIdx >= this.recsPerBlock )
+ {
+ if ( ! this.readBlock() )
+ return; // UNDONE
+ }
+
+ this.currRecIdx++;
+ }
+
+ /**
+ * Read a record from the input stream and return the data.
+ *
+ * @return The record data.
+ */
+
+ public byte[]
+ readRecord()
+ throws IOException
+ {
+ if ( this.debug )
+ {
+ System.err.println
+ ( "ReadRecord: recIdx = " + this.currRecIdx
+ + " blkIdx = " + this.currBlkIdx );
+ }
+
+ if ( this.inStream == null )
+ throw new IOException
+ ( "reading from an output buffer" );
+
+ if ( this.currRecIdx >= this.recsPerBlock )
+ {
+ if ( ! this.readBlock() )
+ return null;
+ }
+
+ byte[] result = new byte[ this.recordSize ];
+
+ System.arraycopy(
+ this.blockBuffer, (this.currRecIdx * this.recordSize),
+ result, 0, this.recordSize );
+
+ this.currRecIdx++;
+
+ return result;
+ }
+
+ /**
+ * @return false if End-Of-File, else true
+ */
+
+ private boolean
+ readBlock()
+ throws IOException
+ {
+ if ( this.debug )
+ {
+ System.err.println
+ ( "ReadBlock: blkIdx = " + this.currBlkIdx );
+ }
+
+ if ( this.inStream == null )
+ throw new IOException
+ ( "reading from an output buffer" );
+
+ this.currRecIdx = 0;
+
+ int offset = 0;
+ int bytesNeeded = this.blockSize;
+ for ( ; bytesNeeded > 0 ; )
+ {
+ long numBytes =
+ this.inStream.read
+ ( this.blockBuffer, offset, bytesNeeded );
+
+ //
+ // NOTE
+ // We have fit EOF, and the block is not full!
+ //
+ // This is a broken archive. It does not follow the standard
+ // blocking algorithm. However, because we are generous, and
+ // it requires little effort, we will simply ignore the error
+ // and continue as if the entire block were read. This does
+ // not appear to break anything upstream. We used to return
+ // false in this case.
+ //
+ // Thanks to 'Yohann.Roussel@alcatel.fr' for this fix.
+ //
+
+ if ( numBytes == -1 )
+ break;
+
+ offset += numBytes;
+ bytesNeeded -= numBytes;
+ if ( numBytes != this.blockSize )
+ {
+ if ( this.debug )
+ {
+ System.err.println
+ ( "ReadBlock: INCOMPLETE READ " + numBytes
+ + " of " + this.blockSize + " bytes read." );
+ }
+ }
+ }
+
+ this.currBlkIdx++;
+
+ return true;
+ }
+
+ /**
+ * Get the current block number, zero based.
+ *
+ * @return The current zero based block number.
+ */
+ public int
+ getCurrentBlockNum()
+ {
+ return this.currBlkIdx;
+ }
+
+ /**
+ * Get the current record number, within the current block, zero based.
+ * Thus, current offset = (currentBlockNum * recsPerBlk) + currentRecNum.
+ *
+ * @return The current zero based record number.
+ */
+ public int
+ getCurrentRecordNum()
+ {
+ return this.currRecIdx - 1;
+ }
+
+ /**
+ * Write an archive record to the archive.
+ *
+ * @param record The record data to write to the archive.
+ */
+
+ public void
+ writeRecord( byte[] record )
+ throws IOException
+ {
+ if ( this.debug )
+ {
+ System.err.println
+ ( "WriteRecord: recIdx = " + this.currRecIdx
+ + " blkIdx = " + this.currBlkIdx );
+ }
+
+ if ( this.outStream == null )
+ throw new IOException
+ ( "writing to an input buffer" );
+
+ if ( record.length != this.recordSize )
+ throw new IOException
+ ( "record to write has length '" + record.length
+ + "' which is not the record size of '"
+ + this.recordSize + "'" );
+
+ if ( this.currRecIdx >= this.recsPerBlock )
+ {
+ this.writeBlock();
+ }
+
+ System.arraycopy(
+ record, 0,
+ this.blockBuffer, (this.currRecIdx * this.recordSize),
+ this.recordSize );
+
+ this.currRecIdx++;
+ }
+
+ /**
+ * Write an archive record to the archive, where the record may be
+ * inside of a larger array buffer. The buffer must be "offset plus
+ * record size" long.
+ *
+ * @param buf The buffer containing the record data to write.
+ * @param offset The offset of the record data within buf.
+ */
+
+ public void
+ writeRecord( byte[] buf, int offset )
+ throws IOException
+ {
+ if ( this.debug )
+ {
+ System.err.println
+ ( "WriteRecord: recIdx = " + this.currRecIdx
+ + " blkIdx = " + this.currBlkIdx );
+ }
+
+ if ( this.outStream == null )
+ throw new IOException
+ ( "writing to an input buffer" );
+
+ if ( (offset + this.recordSize) > buf.length )
+ throw new IOException
+ ( "record has length '" + buf.length
+ + "' with offset '" + offset
+ + "' which is less than the record size of '"
+ + this.recordSize + "'" );
+
+ if ( this.currRecIdx >= this.recsPerBlock )
+ {
+ this.writeBlock();
+ }
+
+ System.arraycopy(
+ buf, offset,
+ this.blockBuffer, (this.currRecIdx * this.recordSize),
+ this.recordSize );
+
+ this.currRecIdx++;
+ }
+
+ /**
+ * Write a TarBuffer block to the archive.
+ */
+ private void
+ writeBlock()
+ throws IOException
+ {
+ if ( this.debug )
+ {
+ System.err.println
+ ( "WriteBlock: blkIdx = " + this.currBlkIdx );
+ }
+
+ if ( this.outStream == null )
+ throw new IOException
+ ( "writing to an input buffer" );
+
+ this.outStream.write( this.blockBuffer, 0, this.blockSize );
+ this.outStream.flush();
+
+ this.currRecIdx = 0;
+ this.currBlkIdx++;
+ }
+
+ /**
+ * Flush the current data block if it has any data in it.
+ */
+
+ private void
+ flushBlock()
+ throws IOException
+ {
+ if ( this.debug )
+ {
+ System.err.println( "TarBuffer.flushBlock() called." );
+ }
+
+ if ( this.outStream == null )
+ throw new IOException
+ ( "writing to an input buffer" );
+
+ // Thanks to 'Todd Kofford <tkofford@bigfoot.com>' for this patch.
+ // Use a buffer initialized with 0s to initialize everything in the
+ // blockBuffer after the last current, complete record. This prevents
+ // any previous data that might have previously existed in the
+ // blockBuffer from being written to the file.
+
+ if ( this.currRecIdx > 0 )
+ {
+ int offset = this.currRecIdx * this.recordSize;
+ byte[] zeroBuffer = new byte[ this.blockSize - offset ];
+
+ System.arraycopy
+ ( zeroBuffer, 0, this.blockBuffer, offset, zeroBuffer.length );
+
+ this.writeBlock();
+ }
+ }
+
+ /**
+ * Close the TarBuffer. If this is an output buffer, also flush the
+ * current block before closing.
+ */
+ public void
+ close()
+ throws IOException
+ {
+ if ( this.debug )
+ {
+ System.err.println( "TarBuffer.closeBuffer()." );
+ }
+
+ if ( this.outStream != null )
+ {
+ this.flushBlock();
+
+ if ( this.outStream != System.out
+ && this.outStream != System.err )
+ {
+ this.outStream.close();
+ this.outStream = null;
+ }
+ }
+ else if ( this.inStream != null )
+ {
+ if ( this.inStream != System.in )
+ {
+ this.inStream.close();
+ this.inStream = null;
+ }
+ }
+ }
+
+ }
+
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time@gjt.org> <http://www.trustice.com>
+**
+** This work has been placed into the public domain.
+** You may use this work in any way and for any purpose you wish.
+**
+** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
+** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
+** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
+** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
+** REDISTRIBUTION OF THIS SOFTWARE.
+**
+*/
+
+
+/**
+ *
+ * This class represents an entry in a Tar archive. It consists
+ * of the entry's header, as well as the entry's File. Entries
+ * can be instantiated in one of three ways, depending on how
+ * they are to be used.
+ * <p>
+ * TarEntries that are created from the header bytes read from
+ * an archive are instantiated with the TarEntry( byte[] )
+ * constructor. These entries will be used when extracting from
+ * or listing the contents of an archive. These entries have their
+ * header filled in using the header bytes. They also set the File
+ * to null, since they reference an archive entry not a file.
+ * <p>
+ * TarEntries that are created from Files that are to be written
+ * into an archive are instantiated with the TarEntry( File )
+ * constructor. These entries have their header filled in using
+ * the File's information. They also keep a reference to the File
+ * for convenience when writing entries.
+ * <p>
+ * Finally, TarEntries can be constructed from nothing but a name.
+ * This allows the programmer to construct the entry by hand, for
+ * instance when only an InputStream is available for writing to
+ * the archive, and the header information is constructed from
+ * other information. In this case the header fields are set to
+ * defaults and the File is set to null.
+ *
+ * <pre>
+ *
+ * Original Unix Tar Header:
+ *
+ * Field Field Field
+ * Width Name Meaning
+ * ----- --------- ---------------------------
+ * 100 name name of file
+ * 8 mode file mode
+ * 8 uid owner user ID
+ * 8 gid owner group ID
+ * 12 size length of file in bytes
+ * 12 mtime modify time of file
+ * 8 chksum checksum for header
+ * 1 link indicator for links
+ * 100 linkname name of linked file
+ *
+ * </pre>
+ *
+ * <pre>
+ *
+ * POSIX "ustar" Style Tar Header:
+ *
+ * Field Field Field
+ * Width Name Meaning
+ * ----- --------- ---------------------------
+ * 100 name name of file
+ * 8 mode file mode
+ * 8 uid owner user ID
+ * 8 gid owner group ID
+ * 12 size length of file in bytes
+ * 12 mtime modify time of file
+ * 8 chksum checksum for header
+ * 1 typeflag type of file
+ * 100 linkname name of linked file
+ * 6 magic USTAR indicator
+ * 2 version USTAR version
+ * 32 uname owner user name
+ * 32 gname owner group name
+ * 8 devmajor device major number
+ * 8 devminor device minor number
+ * 155 prefix prefix for file name
+ *
+ * struct posix_header
+ * { byte offset
+ * char name[100]; 0
+ * char mode[8]; 100
+ * char uid[8]; 108
+ * char gid[8]; 116
+ * char size[12]; 124
+ * char mtime[12]; 136
+ * char chksum[8]; 148
+ * char typeflag; 156
+ * char linkname[100]; 157
+ * char magic[6]; 257
+ * char version[2]; 263
+ * char uname[32]; 265
+ * char gname[32]; 297
+ * char devmajor[8]; 329
+ * char devminor[8]; 337
+ * char prefix[155]; 345
+ * }; 500
+ *
+ * </pre>
+ *
+ * Note that while the class does recognize GNU formatted headers,
+ * it does not perform proper processing of GNU archives. I hope
+ * to add the GNU support someday.
+ *
+ * Directory "size" fix contributed by:
+ * Bert Becker <becker@informatik.hu-berlin.de>
+ *
+ * @see TarHeader
+ * @author Timothy Gerard Endres, <time@gjt.org>
+ */
+
+public
+static class TarEntry
+extends Object
+implements Cloneable
+ {
+ /**
+ * If this entry represents a File, this references it.
+ */
+ protected File file;
+
+ /**
+ * This is the entry's header information.
+ */
+ protected TarHeader header;
+
+ /**
+ * Set to true if this is a "old-unix" format entry.
+ */
+ protected boolean unixFormat;
+
+ /**
+ * Set to true if this is a 'ustar' format entry.
+ */
+ protected boolean ustarFormat;
+
+ /**
+ * Set to true if this is a GNU 'ustar' format entry.
+ */
+ protected boolean gnuFormat;
+
+
+ /**
+ * The default constructor is protected for use only by subclasses.
+ */
+ protected
+ TarEntry()
+ {
+ }
+
+ /**
+ * Construct an entry with only a name. This allows the programmer
+ * to construct the entry's header "by hand". File is set to null.
+ */
+ public
+ TarEntry( String name )
+ {
+ this.initialize();
+ this.nameTarHeader( this.header, name );
+ }
+
+ /**
+ * Construct an entry for a file. File is set to file, and the
+ * header is constructed from information from the file.
+ *
+ * @param file The file that the entry represents.
+ */
+ public
+ TarEntry( File file )
+ throws InvalidHeaderException
+ {
+ this.initialize();
+ this.getFileTarHeader( this.header, file );
+ }
+
+ /**
+ * Construct an entry from an archive's header bytes. File is set
+ * to null.
+ *
+ * @param headerBuf The header bytes from a tar archive entry.
+ */
+ public
+ TarEntry( byte[] headerBuf )
+ throws InvalidHeaderException
+ {
+ this.initialize();
+ this.parseTarHeader( this.header, headerBuf );
+ }
+
+ /**
+ * Initialization code common to all constructors.
+ */
+ private void
+ initialize()
+ {
+ this.file = null;
+ this.header = new TarHeader();
+
+ this.gnuFormat = false;
+ this.ustarFormat = true; // REVIEW What we prefer to use...
+ this.unixFormat = false;
+ }
+
+ /**
+ * Clone the entry.
+ */
+ public Object
+ clone()
+ {
+ TarEntry entry = null;
+
+ try {
+ entry = (TarEntry) super.clone();
+
+ if ( this.header != null )
+ {
+ entry.header = (TarHeader) this.header.clone();
+ }
+
+ if ( this.file != null )
+ {
+ entry.file = new File( this.file.getAbsolutePath() );
+ }
+ }
+ catch ( CloneNotSupportedException ex )
+ {
+ ex.printStackTrace( System.err );
+ }
+
+ return entry;
+ }
+
+ /**
+ * Returns true if this entry's header is in "ustar" format.
+ *
+ * @return True if the entry's header is in "ustar" format.
+ */
+ public boolean
+ isUSTarFormat()
+ {
+ return this.ustarFormat;
+ }
+
+ /**
+ * Sets this entry's header format to "ustar".
+ */
+ public void
+ setUSTarFormat()
+ {
+ this.ustarFormat = true;
+ this.gnuFormat = false;
+ this.unixFormat = false;
+ }
+
+ /**
+ * Returns true if this entry's header is in the GNU 'ustar' format.
+ *
+ * @return True if the entry's header is in the GNU 'ustar' format.
+ */
+ public boolean
+ isGNUTarFormat()
+ {
+ return this.gnuFormat;
+ }
+
+ /**
+ * Sets this entry's header format to GNU "ustar".
+ */
+ public void
+ setGNUTarFormat()
+ {
+ this.gnuFormat = true;
+ this.ustarFormat = false;
+ this.unixFormat = false;
+ }
+
+ /**
+ * Returns true if this entry's header is in the old "unix-tar" format.
+ *
+ * @return True if the entry's header is in the old "unix-tar" format.
+ */
+ public boolean
+ isUnixTarFormat()
+ {
+ return this.unixFormat;
+ }
+
+ /**
+ * Sets this entry's header format to "unix-style".
+ */
+ public void
+ setUnixTarFormat()
+ {
+ this.unixFormat = true;
+ this.ustarFormat = false;
+ this.gnuFormat = false;
+ }
+
+ /**
+ * Determine if the two entries are equal. Equality is determined
+ * by the header names being equal.
+ *
+ * @return it Entry to be checked for equality.
+ * @return True if the entries are equal.
+ */
+ public boolean
+ equals( TarEntry it )
+ {
+ return
+ this.header.name.toString().equals
+ ( it.header.name.toString() );
+ }
+
+ /**
+ * Determine if the given entry is a descendant of this entry.
+ * Descendancy is determined by the name of the descendant
+ * starting with this entry's name.
+ *
+ * @param desc Entry to be checked as a descendent of this.
+ * @return True if entry is a descendant of this.
+ */
+ public boolean
+ isDescendent( TarEntry desc )
+ {
+ return
+ desc.header.name.toString().startsWith
+ ( this.header.name.toString() );
+ }
+
+ /**
+ * Get this entry's header.
+ *
+ * @return This entry's TarHeader.
+ */
+ public TarHeader
+ getHeader()
+ {
+ return this.header;
+ }
+
+ /**
+ * Get this entry's name.
+ *
+ * @return This entry's name.
+ */
+ public String
+ getName()
+ {
+ return this.header.name.toString();
+ }
+
+ /**
+ * Set this entry's name.
+ *
+ * @param name This entry's new name.
+ */
+ public void
+ setName( String name )
+ {
+ this.header.name =
+ new StringBuffer( name );
+ }
+
+ /**
+ * Get this entry's user id.
+ *
+ * @return This entry's user id.
+ */
+ public int
+ getUserId()
+ {
+ return this.header.userId;
+ }
+
+ /**
+ * Set this entry's user id.
+ *
+ * @param userId This entry's new user id.
+ */
+ public void
+ setUserId( int userId )
+ {
+ this.header.userId = userId;
+ }
+
+ /**
+ * Get this entry's group id.
+ *
+ * @return This entry's group id.
+ */
+ public int
+ getGroupId()
+ {
+ return this.header.groupId;
+ }
+
+ /**
+ * Set this entry's group id.
+ *
+ * @param groupId This entry's new group id.
+ */
+ public void
+ setGroupId( int groupId )
+ {
+ this.header.groupId = groupId;
+ }
+
+ /**
+ * Get this entry's user name.
+ *
+ * @return This entry's user name.
+ */
+ public String
+ getUserName()
+ {
+ return this.header.userName.toString();
+ }
+
+ /**
+ * Set this entry's user name.
+ *
+ * @param userName This entry's new user name.
+ */
+ public void
+ setUserName( String userName )
+ {
+ this.header.userName =
+ new StringBuffer( userName );
+ }
+
+ /**
+ * Get this entry's group name.
+ *
+ * @return This entry's group name.
+ */
+ public String
+ getGroupName()
+ {
+ return this.header.groupName.toString();
+ }
+
+ /**
+ * Set this entry's group name.
+ *
+ * @param groupName This entry's new group name.
+ */
+ public void
+ setGroupName( String groupName )
+ {
+ this.header.groupName =
+ new StringBuffer( groupName );
+ }
+
+ /**
+ * Convenience method to set this entry's group and user ids.
+ *
+ * @param userId This entry's new user id.
+ * @param groupId This entry's new group id.
+ */
+ public void
+ setIds( int userId, int groupId )
+ {
+ this.setUserId( userId );
+ this.setGroupId( groupId );
+ }
+
+ /**
+ * Convenience method to set this entry's group and user names.
+ *
+ * @param userName This entry's new user name.
+ * @param groupName This entry's new group name.
+ */
+ public void
+ setNames( String userName, String groupName )
+ {
+ this.setUserName( userName );
+ this.setGroupName( groupName );
+ }
+
+ /**
+ * Set this entry's modification time. The parameter passed
+ * to this method is in "Java time".
+ *
+ * @param time This entry's new modification time.
+ */
+ public void
+ setModTime( long time )
+ {
+ this.header.modTime = time / 1000;
+ }
+
+ /**
+ * Set this entry's modification time.
+ *
+ * @param time This entry's new modification time.
+ */
+ public void
+ setModTime( Date time )
+ {
+ this.header.modTime = time.getTime() / 1000;
+ }
+
+ /**
+ * Set this entry's modification time.
+ *
+ * @param time This entry's new modification time.
+ */
+ public Date
+ getModTime()
+ {
+ return new Date( this.header.modTime * 1000 );
+ }
+
+ /**
+ * Get this entry's file.
+ *
+ * @return This entry's file.
+ */
+ public File
+ getFile()
+ {
+ return this.file;
+ }
+
+ /**
+ * Get this entry's file size.
+ *
+ * @return This entry's file size.
+ */
+ public long
+ getSize()
+ {
+ return this.header.size;
+ }
+
+ /**
+ * Set this entry's file size.
+ *
+ * @param size This entry's new file size.
+ */
+ public void
+ setSize( long size )
+ {
+ this.header.size = size;
+ }
+
+ /**
+ * Return whether or not this entry represents a directory.
+ *
+ * @return True if this entry is a directory.
+ */
+ public boolean
+ isDirectory()
+ {
+ if ( this.file != null )
+ return this.file.isDirectory();
+
+ if ( this.header != null )
+ {
+ if ( this.header.linkFlag == TarHeader.LF_DIR )
+ return true;
+
+ if ( this.header.name.toString().endsWith( "/" ) )
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Fill in a TarHeader with information from a File.
+ *
+ * @param hdr The TarHeader to fill in.
+ * @param file The file from which to get the header information.
+ */
+ public void
+ getFileTarHeader( TarHeader hdr, File file )
+ throws InvalidHeaderException
+ {
+ this.file = file;
+
+ String name = file.getPath();
+ String osname = System.getProperty( "os.name" );
+ if ( osname != null )
+ {
+ // Strip off drive letters!
+ // REVIEW Would a better check be "(File.separator == '\')"?
+
+ // String Win32Prefix = "Windows";
+ // String prefix = osname.substring( 0, Win32Prefix.length() );
+ // if ( prefix.equalsIgnoreCase( Win32Prefix ) )
+
+ // if ( File.separatorChar == '\\' )
+
+ // Windows OS check was contributed by
+ // Patrick Beard <beard@netscape.com>
+ String Win32Prefix = "windows";
+ if ( osname.toLowerCase().startsWith( Win32Prefix ) )
+ {
+ if ( name.length() > 2 )
+ {
+ char ch1 = name.charAt(0);
+ char ch2 = name.charAt(1);
+ if ( ch2 == ':'
+ && ( (ch1 >= 'a' && ch1 <= 'z')
+ || (ch1 >= 'A' && ch1 <= 'Z') ) )
+ {
+ name = name.substring( 2 );
+ }
+ }
+ }
+ }
+
+ name = name.replace( File.separatorChar, '/' );
+
+ // No absolute pathnames
+ // Windows (and Posix?) paths can start with "\\NetworkDrive\",
+ // so we loop on starting /'s.
+
+ for ( ; name.startsWith( "/" ) ; )
+ name = name.substring( 1 );
+
+ hdr.linkName = new StringBuffer( "" );
+
+ hdr.name = new StringBuffer( name );
+
+ if ( file.isDirectory() )
+ {
+ hdr.size = 0;
+ hdr.mode = 040755;
+ hdr.linkFlag = TarHeader.LF_DIR;
+ if ( hdr.name.charAt( hdr.name.length() - 1 ) != '/' )
+ hdr.name.append( "/" );
+ }
+ else
+ {
+ hdr.size = file.length();
+ hdr.mode = 0100644;
+ hdr.linkFlag = TarHeader.LF_NORMAL;
+ }
+
+ // UNDONE When File lets us get the userName, use it!
+
+ hdr.modTime = file.lastModified() / 1000;
+ hdr.checkSum = 0;
+ hdr.devMajor = 0;
+ hdr.devMinor = 0;
+ }
+
+ /**
+ * If this entry represents a file, and the file is a directory, return
+ * an array of TarEntries for this entry's children.
+ *
+ * @return An array of TarEntry's for this entry's children.
+ */
+ public TarEntry[]
+ getDirectoryEntries()
+ throws InvalidHeaderException
+ {
+ if ( this.file == null
+ || ! this.file.isDirectory() )
+ {
+ return new TarEntry[0];
+ }
+
+ String[] list = this.file.list();
+
+ TarEntry[] result = new TarEntry[ list.length ];
+
+ for ( int i = 0 ; i < list.length ; ++i )
+ {
+ result[i] =
+ new TarEntry
+ ( new File( this.file, list[i] ) );
+ }
+
+ return result;
+ }
+
+ /**
+ * Compute the checksum of a tar entry header.
+ *
+ * @param buf The tar entry's header buffer.
+ * @return The computed checksum.
+ */
+ public long
+ computeCheckSum( byte[] buf )
+ {
+ long sum = 0;
+
+ for ( int i = 0 ; i < buf.length ; ++i )
+ {
+ sum += 255 & buf[ i ];
+ }
+
+ return sum;
+ }
+
+ /**
+ * Write an entry's header information to a header buffer.
+ * This method can throw an InvalidHeaderException
+ *
+ * @param outbuf The tar entry header buffer to fill in.
+ * @throws InvalidHeaderException If the name will not fit in the header.
+ */
+ public void
+ writeEntryHeader( byte[] outbuf )
+ throws InvalidHeaderException
+ {
+ int offset = 0;
+
+ if ( this.isUnixTarFormat() )
+ {
+ if ( this.header.name.length() > 100 )
+ throw new InvalidHeaderException
+ ( "file path is greater than 100 characters, "
+ + this.header.name );
+ }
+
+ offset = TarHeader.getFileNameBytes( this.header.name.toString(), outbuf );
+
+ offset = TarHeader.getOctalBytes
+ ( this.header.mode, outbuf, offset, TarHeader.MODELEN );
+
+ offset = TarHeader.getOctalBytes
+ ( this.header.userId, outbuf, offset, TarHeader.UIDLEN );
+
+ offset = TarHeader.getOctalBytes
+ ( this.header.groupId, outbuf, offset, TarHeader.GIDLEN );
+
+ long size = this.header.size;
+
+ offset = TarHeader.getLongOctalBytes
+ ( size, outbuf, offset, TarHeader.SIZELEN );
+
+ offset = TarHeader.getLongOctalBytes
+ ( this.header.modTime, outbuf, offset, TarHeader.MODTIMELEN );
+
+ int csOffset = offset;
+ for ( int c = 0 ; c < TarHeader.CHKSUMLEN ; ++c )
+ outbuf[ offset++ ] = (byte) ' ';
+
+ outbuf[ offset++ ] = this.header.linkFlag;
+
+ offset = TarHeader.getNameBytes
+ ( this.header.linkName, outbuf, offset, TarHeader.NAMELEN );
+
+ if ( this.unixFormat )
+ {
+ for ( int i = 0 ; i < TarHeader.MAGICLEN ; ++i )
+ outbuf[ offset++ ] = 0;
+ }
+ else
+ {
+ offset = TarHeader.getNameBytes
+ ( this.header.magic, outbuf, offset, TarHeader.MAGICLEN );
+ }
+
+ offset = TarHeader.getNameBytes
+ ( this.header.userName, outbuf, offset, TarHeader.UNAMELEN );
+
+ offset = TarHeader.getNameBytes
+ ( this.header.groupName, outbuf, offset, TarHeader.GNAMELEN );
+
+ offset = TarHeader.getOctalBytes
+ ( this.header.devMajor, outbuf, offset, TarHeader.DEVLEN );
+
+ offset = TarHeader.getOctalBytes
+ ( this.header.devMinor, outbuf, offset, TarHeader.DEVLEN );
+
+ for ( ; offset < outbuf.length ; )
+ outbuf[ offset++ ] = 0;
+
+ long checkSum = this.computeCheckSum( outbuf );
+
+ TarHeader.getCheckSumOctalBytes
+ ( checkSum, outbuf, csOffset, TarHeader.CHKSUMLEN );
+ }
+
+ /**
+ * Parse an entry's TarHeader information from a header buffer.
+ *
+ * Old unix-style code contributed by David Mehringer <dmehring@astro.uiuc.edu>.
+ *
+ * @param hdr The TarHeader to fill in from the buffer information.
+ * @param header The tar entry header buffer to get information from.
+ */
+ public void
+ parseTarHeader( TarHeader hdr, byte[] headerBuf )
+ throws InvalidHeaderException
+ {
+ int offset = 0;
+
+ //
+ // NOTE Recognize archive header format.
+ //
+ if ( headerBuf[257] == 0
+ && headerBuf[258] == 0
+ && headerBuf[259] == 0
+ && headerBuf[260] == 0
+ && headerBuf[261] == 0 )
+ {
+ this.unixFormat = true;
+ this.ustarFormat = false;
+ this.gnuFormat = false;
+ }
+ else if ( headerBuf[257] == 'u'
+ && headerBuf[258] == 's'
+ && headerBuf[259] == 't'
+ && headerBuf[260] == 'a'
+ && headerBuf[261] == 'r'
+ && headerBuf[262] == 0 )
+ {
+ this.ustarFormat = true;
+ this.gnuFormat = false;
+ this.unixFormat = false;
+ }
+ else if ( headerBuf[257] == 'u'
+ && headerBuf[258] == 's'
+ && headerBuf[259] == 't'
+ && headerBuf[260] == 'a'
+ && headerBuf[261] == 'r'
+ && headerBuf[262] != 0
+ && headerBuf[263] != 0 )
+ {
+ // REVIEW
+ this.gnuFormat = true;
+ this.unixFormat = false;
+ this.ustarFormat = false;
+ }
+ else
+ {
+ StringBuffer buf = new StringBuffer( 128 );
+
+ buf.append( "header magic is not 'ustar' or unix-style zeros, it is '" );
+ buf.append( headerBuf[257] );
+ buf.append( headerBuf[258] );
+ buf.append( headerBuf[259] );
+ buf.append( headerBuf[260] );
+ buf.append( headerBuf[261] );
+ buf.append( headerBuf[262] );
+ buf.append( headerBuf[263] );
+ buf.append( "', or (dec) " );
+ buf.append( (int)headerBuf[257] );
+ buf.append( ", " );
+ buf.append( (int)headerBuf[258] );
+ buf.append( ", " );
+ buf.append( (int)headerBuf[259] );
+ buf.append( ", " );
+ buf.append( (int)headerBuf[260] );
+ buf.append( ", " );
+ buf.append( (int)headerBuf[261] );
+ buf.append( ", " );
+ buf.append( (int)headerBuf[262] );
+ buf.append( ", " );
+ buf.append( (int)headerBuf[263] );
+
+ throw new InvalidHeaderException( buf.toString() );
+ }
+
+ hdr.name = TarHeader.parseFileName( headerBuf );
+
+ offset = TarHeader.NAMELEN;
+
+ hdr.mode = (int)
+ TarHeader.parseOctal( headerBuf, offset, TarHeader.MODELEN );
+
+ offset += TarHeader.MODELEN;
+
+ hdr.userId = (int)
+ TarHeader.parseOctal( headerBuf, offset, TarHeader.UIDLEN );
+
+ offset += TarHeader.UIDLEN;
+
+ hdr.groupId = (int)
+ TarHeader.parseOctal( headerBuf, offset, TarHeader.GIDLEN );
+
+ offset += TarHeader.GIDLEN;
+
+ hdr.size =
+ TarHeader.parseOctal( headerBuf, offset, TarHeader.SIZELEN );
+
+ offset += TarHeader.SIZELEN;
+
+ hdr.modTime =
+ TarHeader.parseOctal( headerBuf, offset, TarHeader.MODTIMELEN );
+
+ offset += TarHeader.MODTIMELEN;
+
+ hdr.checkSum = (int)
+ TarHeader.parseOctal( headerBuf, offset, TarHeader.CHKSUMLEN );
+
+ offset += TarHeader.CHKSUMLEN;
+
+ hdr.linkFlag = headerBuf[ offset++ ];
+
+ hdr.linkName =
+ TarHeader.parseName( headerBuf, offset, TarHeader.NAMELEN );
+
+ offset += TarHeader.NAMELEN;
+
+ if ( this.ustarFormat )
+ {
+ hdr.magic =
+ TarHeader.parseName( headerBuf, offset, TarHeader.MAGICLEN );
+
+ offset += TarHeader.MAGICLEN;
+
+ hdr.userName =
+ TarHeader.parseName( headerBuf, offset, TarHeader.UNAMELEN );
+
+ offset += TarHeader.UNAMELEN;
+
+ hdr.groupName =
+ TarHeader.parseName( headerBuf, offset, TarHeader.GNAMELEN );
+
+ offset += TarHeader.GNAMELEN;
+
+ hdr.devMajor = (int)
+ TarHeader.parseOctal( headerBuf, offset, TarHeader.DEVLEN );
+
+ offset += TarHeader.DEVLEN;
+
+ hdr.devMinor = (int)
+ TarHeader.parseOctal( headerBuf, offset, TarHeader.DEVLEN );
+ }
+ else
+ {
+ hdr.devMajor = 0;
+ hdr.devMinor = 0;
+ hdr.magic = new StringBuffer( "" );
+ hdr.userName = new StringBuffer( "" );
+ hdr.groupName = new StringBuffer( "" );
+ }
+ }
+
+ /**
+ * Fill in a TarHeader given only the entry's name.
+ *
+ * @param hdr The TarHeader to fill in.
+ * @param name The tar entry name.
+ */
+ public void
+ nameTarHeader( TarHeader hdr, String name )
+ {
+ boolean isDir = name.endsWith( "/" );
+
+ this.gnuFormat = false;
+ this.ustarFormat = true;
+ this.unixFormat = false;
+
+ hdr.checkSum = 0;
+ hdr.devMajor = 0;
+ hdr.devMinor = 0;
+
+ hdr.name = new StringBuffer( name );
+ hdr.mode = isDir ? 040755 : 0100644;
+ hdr.userId = 0;
+ hdr.groupId = 0;
+ hdr.size = 0;
+ hdr.checkSum = 0;
+
+ hdr.modTime =
+ (new java.util.Date()).getTime() / 1000;
+
+ hdr.linkFlag =
+ isDir ? TarHeader.LF_DIR : TarHeader.LF_NORMAL;
+
+ hdr.linkName = new StringBuffer( "" );
+ hdr.userName = new StringBuffer( "" );
+ hdr.groupName = new StringBuffer( "" );
+
+ hdr.devMajor = 0;
+ hdr.devMinor = 0;
+ }
+
+ public String
+ toString()
+ {
+ StringBuffer result = new StringBuffer( 128 );
+ return result.
+ append( "[TarEntry name=" ).
+ append( this.getName() ).
+ append( ", isDir=" ).
+ append( this.isDirectory() ).
+ append( ", size=" ).
+ append( this.getSize() ).
+ append( ", userId=" ).
+ append( this.getUserId() ).
+ append( ", user=" ).
+ append( this.getUserName() ).
+ append( ", groupId=" ).
+ append( this.getGroupId() ).
+ append( ", group=" ).
+ append( this.getGroupName() ).
+ append( "]" ).
+ toString();
+ }
+
+ }
+
+/*
+** Tim feel free to integrate this code here.
+**
+** This code has been placed into the Public Domain.
+** This code was written by David M. Gaskin in 1999.
+**
+*/
+
+/**
+ * Enumerate the contents of a "tar" file.
+ *
+ * Last updated 26th Mar 1999.
+ *
+ * @author David. M. Gaskin.
+ * @version Version 1.0 Mar 1999
+ * @since Version 1.0
+ */
+
+public
+static class TarEntryEnumerator
+implements Enumeration
+ {
+ /**
+ * The instance on which the enumeration works.
+ */
+ private TarInputStream tis = null;
+
+ /**
+ * Has EndOfFile been reached?
+ */
+ private boolean eof = false;
+
+ /**
+ * The read ahead entry (or <B><I>null</I></B> if no read ahead exists)
+ */
+ private TarEntry readAhead = null;
+
+ /**
+ * Construct an instance given a TarInputStream. This method is package
+ * private because it is not initially forseen that an instance of this class
+ * should be constructed from outside the package. Should it become necessary
+ * to construct an instance of this class from outside the package in which it
+ * exists then the constructor should be made <B>protected</B> and an empty
+ * subclass should be written in the other package.
+ *
+ * @param <B>tis</B> the <B>TarInputStream</B> on which this enumeration has
+ * to be based.
+ */
+ public
+ TarEntryEnumerator( TarInputStream tis )
+ {
+ this.tis = tis;
+ eof = false;
+ }
+
+ /**
+ * Return the next element in the enumeration. This is a required method
+ * for implementing <B>java.util.Enumeration</B>.
+ *
+ * @return the next Object in the enumeration
+ * @exception <B>NoSuchElementException</B> should an attempt be made to
+ * read beyond EOF
+ */
+ public Object
+ nextElement()
+ throws NoSuchElementException
+ {
+ if ( eof && ( readAhead == null ) )
+ throw new NoSuchElementException();
+
+ TarEntry rc = null;
+ if ( readAhead != null )
+ {
+ rc = readAhead;
+ readAhead = null;
+ }
+ else
+ {
+ rc = getNext();
+ }
+
+ return rc;
+ }
+
+ /**
+ * Return <B>true</B> if there are more elements in the enumeration.
+ *
+ * @return <B>true</B> if there are more elements in the enumeration.
+ */
+ public boolean
+ hasMoreElements()
+ {
+ if (eof)
+ return false;
+
+ boolean rc = false;
+ readAhead = getNext();
+ if ( readAhead != null )
+ rc = true;
+
+ return rc;
+ }
+
+ /**
+ * Return the next element of <B>null</B> if there is no next element or
+ * if an error occured.
+ *
+ * @return the next element of <B>null</B> if there is no next element or
+ * if an error occured.
+ */
+ private TarEntry
+ getNext()
+ {
+ TarEntry rc = null;
+ try {
+ rc = tis.getNextEntry();
+ }
+ catch ( IOException ex )
+ {
+ // null will be returned but should not occur
+ ex.printStackTrace();
+ }
+
+ if ( rc == null )
+ eof = true;
+
+ return rc;
+ }
+ }
+/*
+** Contributed by "Bay" <bayard@generationjava.com>
+**
+** This code has been placed into the public domain.
+*/
+
+
+// we extend TarOutputStream to have the same type,
+// BUT, we don't use ANY methods. It's all about
+// typing.
+
+/**
+ * Outputs tar.gz files. Added functionality that it
+ * doesn't need to know the size of an entry. If an
+ * entry has zero size when it is put in the Tar, then
+ * it buffers it until it's closed and it knows the size.
+ *
+ * @author "Bay" <bayard@generationjava.com>
+ */
+
+public
+static class TarGzOutputStream
+extends TarOutputStream
+ {
+ private TarOutputStream tos = null;
+ private GZIPOutputStream gzip = null;
+ private ByteArrayOutputStream bos = null;
+ private TarEntry currentEntry = null;
+
+ public
+ TarGzOutputStream( OutputStream out )
+ throws IOException
+ {
+ super( null );
+ this.gzip = new GZIPOutputStream( out );
+ this.tos = new TarOutputStream( this.gzip );
+ this.bos = new ByteArrayOutputStream();
+ }
+
+ // proxy all methods, but buffer if unknown size
+
+ public void
+ setDebug( boolean b )
+ {
+ this.tos.setDebug(b);
+ }
+
+ public void
+ setBufferDebug( boolean b )
+ {
+ this.tos.setBufferDebug(b);
+ }
+
+ public void
+ finish()
+ throws IOException
+ {
+ if ( this.currentEntry != null )
+ {
+ closeEntry();
+ }
+
+ this.tos.finish();
+ }
+
+ public void
+ close()
+ throws IOException
+ {
+ this.tos.close();
+ this.gzip.finish();
+ }
+
+ public int
+ getRecordSize()
+ {
+ return this.tos.getRecordSize();
+ }
+
+ public void
+ putNextEntry(TarEntry entry)
+ throws IOException
+ {
+ if ( entry.getSize() != 0 )
+ {
+ this.tos.putNextEntry( entry );
+ }
+ else
+ {
+ this.currentEntry = entry;
+ }
+ }
+
+ public void
+ closeEntry()
+ throws IOException
+ {
+ if(this.currentEntry == null)
+ {
+ this.tos.closeEntry();
+ }
+ else
+ {
+ this.currentEntry.setSize( bos.size() );
+ this.tos.putNextEntry( this.currentEntry );
+ this.bos.writeTo( this.tos );
+ this.tos.closeEntry();
+ this.currentEntry = null;
+ this.bos = new ByteArrayOutputStream();
+ }
+ }
+
+ public void
+ write( int b )
+ throws IOException
+ {
+ if ( this.currentEntry == null )
+ {
+ this.tos.write( b );
+ }
+ else
+ {
+ this.bos.write( b );
+ }
+ }
+
+ public void
+ write( byte[] b )
+ throws IOException
+ {
+ if ( this.currentEntry == null )
+ {
+ this.tos.write( b );
+ }
+ else
+ {
+ this.bos.write( b );
+ }
+ }
+
+ public void
+ write( byte[] b, int start, int length )
+ throws IOException
+ {
+ if ( this.currentEntry == null )
+ {
+ this.tos.write( b, start, length );
+ }
+ else
+ {
+ this.bos.write( b, start, length );
+ }
+ }
+
+ }
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time@gjt.org> <http://www.trustice.com>
+**
+** This work has been placed into the public domain.
+** You may use this work in any way and for any purpose you wish.
+**
+** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
+** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
+** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
+** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
+** REDISTRIBUTION OF THIS SOFTWARE.
+**
+*/
+
+/**
+ * This class encapsulates the Tar Entry Header used in Tar Archives.
+ * The class also holds a number of tar constants, used mostly in headers.
+ *
+ * @author Timothy Gerard Endres, <time@gjt.org>
+ */
+
+public
+static class TarHeader
+extends Object
+implements Cloneable
+ {
+ /**
+ * The length of the name field in a header buffer.
+ */
+ public static final int NAMELEN = 100;
+ /**
+ * The offset of the name field in a header buffer.
+ */
+ public static final int NAMEOFFSET = 0;
+ /**
+ * The length of the name prefix field in a header buffer.
+ */
+ public static final int PREFIXLEN = 155;
+ /**
+ * The offset of the name prefix field in a header buffer.
+ */
+ public static final int PREFIXOFFSET = 345;
+ /**
+ * The length of the mode field in a header buffer.
+ */
+ public static final int MODELEN = 8;
+ /**
+ * The length of the user id field in a header buffer.
+ */
+ public static final int UIDLEN = 8;
+ /**
+ * The length of the group id field in a header buffer.
+ */
+ public static final int GIDLEN = 8;
+ /**
+ * The length of the checksum field in a header buffer.
+ */
+ public static final int CHKSUMLEN = 8;
+ /**
+ * The length of the size field in a header buffer.
+ */
+ public static final int SIZELEN = 12;
+ /**
+ * The length of the magic field in a header buffer.
+ */
+ public static final int MAGICLEN = 8;
+ /**
+ * The length of the modification time field in a header buffer.
+ */
+ public static final int MODTIMELEN = 12;
+ /**
+ * The length of the user name field in a header buffer.
+ */
+ public static final int UNAMELEN = 32;
+ /**
+ * The length of the group name field in a header buffer.
+ */
+ public static final int GNAMELEN = 32;
+ /**
+ * The length of the devices field in a header buffer.
+ */
+ public static final int DEVLEN = 8;
+
+ /**
+ * LF_ constants represent the "link flag" of an entry, or more commonly,
+ * the "entry type". This is the "old way" of indicating a normal file.
+ */
+ public static final byte LF_OLDNORM = 0;
+ /**
+ * Normal file type.
+ */
+ public static final byte LF_NORMAL = (byte) '0';
+ /**
+ * Link file type.
+ */
+ public static final byte LF_LINK = (byte) '1';
+ /**
+ * Symbolic link file type.
+ */
+ public static final byte LF_SYMLINK = (byte) '2';
+ /**
+ * Character device file type.
+ */
+ public static final byte LF_CHR = (byte) '3';
+ /**
+ * Block device file type.
+ */
+ public static final byte LF_BLK = (byte) '4';
+ /**
+ * Directory file type.
+ */
+ public static final byte LF_DIR = (byte) '5';
+ /**
+ * FIFO (pipe) file type.
+ */
+ public static final byte LF_FIFO = (byte) '6';
+ /**
+ * Contiguous file type.
+ */
+ public static final byte LF_CONTIG = (byte) '7';
+
+ /**
+ * The magic tag representing a POSIX tar archive.
+ */
+ public static final String TMAGIC = "ustar";
+
+ /**
+ * The magic tag representing a GNU tar archive.
+ */
+ public static final String GNU_TMAGIC = "ustar ";
+
+ /**
+ * The entry's name.
+ */
+ public StringBuffer name;
+ /**
+ * The entry's permission mode.
+ */
+ public int mode;
+ /**
+ * The entry's user id.
+ */
+ public int userId;
+ /**
+ * The entry's group id.
+ */
+ public int groupId;
+ /**
+ * The entry's size.
+ */
+ public long size;
+ /**
+ * The entry's modification time.
+ */
+ public long modTime;
+ /**
+ * The entry's checksum.
+ */
+ public int checkSum;
+ /**
+ * The entry's link flag.
+ */
+ public byte linkFlag;
+ /**
+ * The entry's link name.
+ */
+ public StringBuffer linkName;
+ /**
+ * The entry's magic tag.
+ */
+ public StringBuffer magic;
+ /**
+ * The entry's user name.
+ */
+ public StringBuffer userName;
+ /**
+ * The entry's group name.
+ */
+ public StringBuffer groupName;
+ /**
+ * The entry's major device number.
+ */
+ public int devMajor;
+ /**
+ * The entry's minor device number.
+ */
+ public int devMinor;
+
+
+ public
+ TarHeader()
+ {
+ this.magic = new StringBuffer( TarHeader.TMAGIC );
+
+ this.name = new StringBuffer();
+ this.linkName = new StringBuffer();
+
+ String user =
+ System.getProperty( "user.name", "" );
+
+ if ( user.length() > 31 )
+ user = user.substring( 0, 31 );
+
+ this.userId = 0;
+ this.groupId = 0;
+ this.userName = new StringBuffer( user );
+ this.groupName = new StringBuffer( "" );
+ }
+
+ /**
+ * TarHeaders can be cloned.
+ */
+ public Object
+ clone()
+ {
+ TarHeader hdr = null;
+
+ try {
+ hdr = (TarHeader) super.clone();
+
+ hdr.name =
+ (this.name == null ) ? null
+ : new StringBuffer( this.name.toString() );
+ hdr.mode = this.mode;
+ hdr.userId = this.userId;
+ hdr.groupId = this.groupId;
+ hdr.size = this.size;
+ hdr.modTime = this.modTime;
+ hdr.checkSum = this.checkSum;
+ hdr.linkFlag = this.linkFlag;
+ hdr.linkName =
+ (this.linkName == null ) ? null
+ : new StringBuffer( this.linkName.toString() );
+ hdr.magic =
+ (this.magic == null ) ? null
+ : new StringBuffer( this.magic.toString() );
+ hdr.userName =
+ (this.userName == null ) ? null
+ : new StringBuffer( this.userName.toString() );
+ hdr.groupName =
+ (this.groupName == null ) ? null
+ : new StringBuffer( this.groupName.toString() );
+ hdr.devMajor = this.devMajor;
+ hdr.devMinor = this.devMinor;
+ }
+ catch ( CloneNotSupportedException ex )
+ {
+ ex.printStackTrace( System.err );
+ }
+
+ return hdr;
+ }
+
+ /**
+ * Get the name of this entry.
+ *
+ * @return Teh entry's name.
+ */
+ public String
+ getName()
+ {
+ return this.name.toString();
+ }
+
+ /**
+ * Parse an octal string from a header buffer. This is used for the
+ * file permission mode value.
+ *
+ * @param header The header buffer from which to parse.
+ * @param offset The offset into the buffer from which to parse.
+ * @param length The number of header bytes to parse.
+ * @return The long value of the octal string.
+ */
+ public static long
+ parseOctal( byte[] header, int offset, int length )
+ throws InvalidHeaderException
+ {
+ long result = 0;
+ boolean stillPadding = true;
+
+ int end = offset + length;
+ for ( int i = offset ; i < end ; ++i )
+ {
+ if ( header[i] == 0 )
+ break;
+
+ if ( header[i] == (byte) ' ' || header[i] == '0' )
+ {
+ if ( stillPadding )
+ continue;
+
+ if ( header[i] == (byte) ' ' )
+ break;
+ }
+
+ stillPadding = false;
+
+ result =
+ (result << 3)
+ + (header[i] - '0');
+ }
+
+ return result;
+ }
+
+ /**
+ * Parse a file name from a header buffer. This is different from
+ * parseName() in that is recognizes 'ustar' names and will handle
+ * adding on the "prefix" field to the name.
+ *
+ * Contributed by Dmitri Tikhonov <dxt2431@yahoo.com>
+ *
+ * @param header The header buffer from which to parse.
+ * @param offset The offset into the buffer from which to parse.
+ * @param length The number of header bytes to parse.
+ * @return The header's entry name.
+ */
+ public static StringBuffer
+ parseFileName( byte[] header )
+ {
+ StringBuffer result = new StringBuffer( 256 );
+
+ // If header[345] is not equal to zero, then it is the "prefix"
+ // that 'ustar' defines. It must be prepended to the "normal"
+ // name field. We are responsible for the separating '/'.
+ //
+ if ( header[345] != 0 )
+ {
+ for ( int i = 345 ; i < 500 && header[i] != 0 ; ++i )
+ {
+ result.append( (char)header[i] );
+ }
+
+ result.append( "/" );
+ }
+
+ for ( int i = 0 ; i < 100 && header[i] != 0 ; ++i )
+ {
+ result.append( (char)header[i] );
+ }
+
+ return result;
+ }
+
+ /**
+ * Parse an entry name from a header buffer.
+ *
+ * @param header The header buffer from which to parse.
+ * @param offset The offset into the buffer from which to parse.
+ * @param length The number of header bytes to parse.
+ * @return The header's entry name.
+ */
+ public static StringBuffer
+ parseName( byte[] header, int offset, int length )
+ throws InvalidHeaderException
+ {
+ StringBuffer result = new StringBuffer( length );
+
+ int end = offset + length;
+ for ( int i = offset ; i < end ; ++i )
+ {
+ if ( header[i] == 0 )
+ break;
+ result.append( (char)header[i] );
+ }
+
+ return result;
+ }
+
+ /**
+ * This method, like getNameBytes(), is intended to place a name
+ * into a TarHeader's buffer. However, this method is sophisticated
+ * enough to recognize long names (name.length() > NAMELEN). In these
+ * cases, the method will break the name into a prefix and suffix and
+ * place the name in the header in 'ustar' format. It is up to the
+ * TarEntry to manage the "entry header format". This method assumes
+ * the name is valid for the type of archive being generated.
+ *
+ * @param outbuf The buffer containing the entry header to modify.
+ * @param newName The new name to place into the header buffer.
+ * @return The current offset in the tar header (always TarHeader.NAMELEN).
+ * @throws InvalidHeaderException If the name will not fit in the header.
+ */
+ public static int
+ getFileNameBytes( String newName, byte[] outbuf )
+ throws InvalidHeaderException
+ {
+ if ( newName.length() > 100 )
+ {
+ // Locate a pathname "break" prior to the maximum name length...
+ int index = newName.indexOf( "/", newName.length() - 100 );
+ if ( index == -1 )
+ throw new InvalidHeaderException
+ ( "file name is greater than 100 characters, " + newName );
+
+ // Get the "suffix subpath" of the name.
+ String name = newName.substring( index + 1 );
+
+ // Get the "prefix subpath", or "prefix", of the name.
+ String prefix = newName.substring( 0, index );
+ if ( prefix.length() > TarHeader.PREFIXLEN )
+ throw new InvalidHeaderException
+ ( "file prefix is greater than 155 characters" );
+
+ TarHeader.getNameBytes
+ ( new StringBuffer( name ), outbuf,
+ TarHeader.NAMEOFFSET, TarHeader.NAMELEN );
+
+ TarHeader.getNameBytes
+ ( new StringBuffer( prefix ), outbuf,
+ TarHeader.PREFIXOFFSET, TarHeader.PREFIXLEN );
+ }
+ else
+ {
+ TarHeader.getNameBytes
+ ( new StringBuffer( newName ), outbuf,
+ TarHeader.NAMEOFFSET, TarHeader.NAMELEN );
+ }
+
+ // The offset, regardless of the format, is now the end of the
+ // original name field.
+ //
+ return TarHeader.NAMELEN;
+ }
+
+ /**
+ * Move the bytes from the name StringBuffer into the header's buffer.
+ *
+ * @param header The header buffer into which to copy the name.
+ * @param offset The offset into the buffer at which to store.
+ * @param length The number of header bytes to store.
+ * @return The new offset (offset + length).
+ */
+ public static int
+ getNameBytes( StringBuffer name, byte[] buf, int offset, int length )
+ {
+ int i;
+
+ for ( i = 0 ; i < length && i < name.length() ; ++i )
+ {
+ buf[ offset + i ] = (byte) name.charAt( i );
+ }
+
+ for ( ; i < length ; ++i )
+ {
+ buf[ offset + i ] = 0;
+ }
+
+ return offset + length;
+ }
+
+ /**
+ * Parse an octal integer from a header buffer.
+ *
+ * @param header The header buffer from which to parse.
+ * @param offset The offset into the buffer from which to parse.
+ * @param length The number of header bytes to parse.
+ * @return The integer value of the octal bytes.
+ */
+ public static int
+ getOctalBytes( long value, byte[] buf, int offset, int length )
+ {
+ byte[] result = new byte[ length ];
+
+ int idx = length - 1;
+
+ buf[ offset + idx ] = 0;
+ --idx;
+ buf[ offset + idx ] = (byte) ' ';
+ --idx;
+
+ if ( value == 0 )
+ {
+ buf[ offset + idx ] = (byte) '0';
+ --idx;
+ }
+ else
+ {
+ for ( long val = value ; idx >= 0 && val > 0 ; --idx )
+ {
+ buf[ offset + idx ] = (byte)
+ ( (byte) '0' + (byte) (val & 7) );
+ val = val >> 3;
+ }
+ }
+
+ for ( ; idx >= 0 ; --idx )
+ {
+ buf[ offset + idx ] = (byte) ' ';
+ }
+
+ return offset + length;
+ }
+
+ /**
+ * Parse an octal long integer from a header buffer.
+ *
+ * @param header The header buffer from which to parse.
+ * @param offset The offset into the buffer from which to parse.
+ * @param length The number of header bytes to parse.
+ * @return The long value of the octal bytes.
+ */
+ public static int
+ getLongOctalBytes( long value, byte[] buf, int offset, int length )
+ {
+ byte[] temp = new byte[ length + 1 ];
+ TarHeader.getOctalBytes( value, temp, 0, length + 1 );
+ System.arraycopy( temp, 0, buf, offset, length );
+ return offset + length;
+ }
+
+ /**
+ * Parse the checksum octal integer from a header buffer.
+ *
+ * @param header The header buffer from which to parse.
+ * @param offset The offset into the buffer from which to parse.
+ * @param length The number of header bytes to parse.
+ * @return The integer value of the entry's checksum.
+ */
+ public static int
+ getCheckSumOctalBytes( long value, byte[] buf, int offset, int length )
+ {
+ TarHeader.getOctalBytes( value, buf, offset, length );
+ buf[ offset + length - 1 ] = (byte) ' ';
+ buf[ offset + length - 2 ] = 0;
+ return offset + length;
+ }
+
+ }
+
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time@gjt.org> <http://www.trustice.com>
+**
+** This work has been placed into the public domain.
+** You may use this work in any way and for any purpose you wish.
+**
+** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
+** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
+** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
+** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
+** REDISTRIBUTION OF THIS SOFTWARE.
+**
+*/
+
+
+/**
+ * The TarInputStream reads a UNIX tar archive as an InputStream.
+ * methods are provided to position at each successive entry in
+ * the archive, and the read each entry as a normal input stream
+ * using read().
+ *
+ * Kerry Menzel <kmenzel@cfl.rr.com> Contributed the code to support
+ * file sizes greater than 2GB (longs versus ints).
+ *
+ *
+ * @version $Revision: 1.9 $
+ * @author Timothy Gerard Endres, <time@gjt.org>
+ * @see TarBuffer
+ * @see TarHeader
+ * @see TarEntry
+ */
+
+
+public
+static class TarInputStream
+extends FilterInputStream
+ {
+ protected boolean debug;
+ protected boolean hasHitEOF;
+
+ protected long entrySize;
+ protected long entryOffset;
+
+ protected byte[] oneBuf;
+ protected byte[] readBuf;
+
+ protected TarBuffer buffer;
+
+ protected TarEntry currEntry;
+
+ protected EntryFactory eFactory;
+
+
+ public
+ TarInputStream( InputStream is )
+ {
+ this( is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE );
+ }
+
+ public
+ TarInputStream( InputStream is, int blockSize )
+ {
+ this( is, blockSize, TarBuffer.DEFAULT_RCDSIZE );
+ }
+
+ public
+ TarInputStream( InputStream is, int blockSize, int recordSize )
+ {
+ super( is );
+
+ this.buffer = new TarBuffer( is, blockSize, recordSize );
+
+ this.readBuf = null;
+ this.oneBuf = new byte[1];
+ this.debug = false;
+ this.hasHitEOF = false;
+ this.eFactory = null;
+ }
+
+ /**
+ * Sets the debugging flag.
+ *
+ * @param debugF True to turn on debugging.
+ */
+ public void
+ setDebug( boolean debugF )
+ {
+ this.debug = debugF;
+ }
+
+ /**
+ * Sets the debugging flag.
+ *
+ * @param debugF True to turn on debugging.
+ */
+ public void
+ setEntryFactory( EntryFactory factory )
+ {
+ this.eFactory = factory;
+ }
+
+ /**
+ * Sets the debugging flag in this stream's TarBuffer.
+ *
+ * @param debugF True to turn on debugging.
+ */
+ public void
+ setBufferDebug( boolean debug )
+ {
+ this.buffer.setDebug( debug );
+ }
+
+ /**
+ * Closes this stream. Calls the TarBuffer's close() method.
+ */
+ public void
+ close()
+ throws IOException
+ {
+ this.buffer.close();
+ }
+
+ /**
+ * Get the record size being used by this stream's TarBuffer.
+ *
+ * @return The TarBuffer record size.
+ */
+ public int
+ getRecordSize()
+ {
+ return this.buffer.getRecordSize();
+ }
+
+ /**
+ * Get the available data that can be read from the current
+ * entry in the archive. This does not indicate how much data
+ * is left in the entire archive, only in the current entry.
+ * This value is determined from the entry's size header field
+ * and the amount of data already read from the current entry.
+ *
+ *
+ * @return The number of available bytes for the current entry.
+ */
+ public int
+ available()
+ throws IOException
+ {
+ return (int)(this.entrySize - this.entryOffset);
+ }
+
+ /**
+ * Skip bytes in the input buffer. This skips bytes in the
+ * current entry's data, not the entire archive, and will
+ * stop at the end of the current entry's data if the number
+ * to skip extends beyond that point.
+ *
+ * @param numToSkip The number of bytes to skip.
+ * @return The actual number of bytes skipped.
+ */
+ public long
+ skip( long numToSkip )
+ throws IOException
+ {
+ // REVIEW
+ // This is horribly inefficient, but it ensures that we
+ // properly skip over bytes via the TarBuffer...
+ //
+
+ byte[] skipBuf = new byte[ 8 * 1024 ];
+ long num = numToSkip;
+ for ( ; num > 0 ; )
+ {
+ int numRead =
+ this.read( skipBuf, 0,
+ ( num > skipBuf.length ? skipBuf.length : (int) num ) );
+
+ if ( numRead == -1 )
+ break;
+
+ num -= numRead;
+ }
+
+ return ( numToSkip - num );
+ }
+
+ /**
+ * Since we do not support marking just yet, we return false.
+ *
+ * @return False.
+ */
+ public boolean
+ markSupported()
+ {
+ return false;
+ }
+
+ /**
+ * Since we do not support marking just yet, we do nothing.
+ *
+ * @param markLimit The limit to mark.
+ */
+ public void
+ mark( int markLimit )
+ {
+ }
+
+ /**
+ * Since we do not support marking just yet, we do nothing.
+ */
+ public void
+ reset()
+ {
+ }
+
+ /**
+ * Get the number of bytes into the current TarEntry.
+ * This method returns the number of bytes that have been read
+ * from the current TarEntry's data.
+ *
+ * @returns The current entry offset.
+ */
+
+ public long
+ getEntryPosition()
+ {
+ return this.entryOffset;
+ }
+
+ /**
+ * Get the number of bytes into the stream we are currently at.
+ * This method accounts for the blocking stream that tar uses,
+ * so it represents the actual position in input stream, as
+ * opposed to the place where the tar archive parsing is.
+ *
+ * @returns The current file pointer.
+ */
+
+ public long
+ getStreamPosition()
+ {
+ return ( buffer.getBlockSize() * buffer.getCurrentBlockNum() )
+ + buffer.getCurrentRecordNum();
+ }
+
+ /**
+ * Get the next entry in this tar archive. This will skip
+ * over any remaining data in the current entry, if there
+ * is one, and place the input stream at the header of the
+ * next entry, and read the header and instantiate a new
+ * TarEntry from the header bytes and return that entry.
+ * If there are no more entries in the archive, null will
+ * be returned to indicate that the end of the archive has
+ * been reached.
+ *
+ * @return The next TarEntry in the archive, or null.
+ */
+ public TarEntry
+ getNextEntry()
+ throws IOException
+ {
+ if ( this.hasHitEOF )
+ return null;
+
+ if ( this.currEntry != null )
+ {
+ long numToSkip = (this.entrySize - this.entryOffset);
+
+ if ( this.debug )
+ System.err.println
+ ( "TarInputStream: SKIP currENTRY '"
+ + this.currEntry.getName() + "' SZ "
+ + this.entrySize + " OFF " + this.entryOffset
+ + " skipping " + numToSkip + " bytes" );
+
+ if ( numToSkip > 0 )
+ {
+ this.skip( numToSkip );
+ }
+
+ this.readBuf = null;
+ }
+
+ byte[] headerBuf = this.buffer.readRecord();
+
+ if ( headerBuf == null )
+ {
+ if ( this.debug )
+ {
+ System.err.println( "READ NULL RECORD" );
+ }
+
+ this.hasHitEOF = true;
+ }
+ else if ( this.buffer.isEOFRecord( headerBuf ) )
+ {
+ if ( this.debug )
+ {
+ System.err.println( "READ EOF RECORD" );
+ }
+
+ this.hasHitEOF = true;
+ }
+
+ if ( this.hasHitEOF )
+ {
+ this.currEntry = null;
+ }
+ else
+ {
+ try {
+ if ( this.eFactory == null )
+ {
+ this.currEntry = new TarEntry( headerBuf );
+ }
+ else
+ {
+ this.currEntry =
+ this.eFactory.createEntry( headerBuf );
+ }
+
+ if ( this.debug )
+ System.err.println
+ ( "TarInputStream: SET CURRENTRY '"
+ + this.currEntry.getName()
+ + "' size = " + this.currEntry.getSize() );
+
+ this.entryOffset = 0;
+ this.entrySize = this.currEntry.getSize();
+ }
+ catch ( InvalidHeaderException ex )
+ {
+ this.entrySize = 0;
+ this.entryOffset = 0;
+ this.currEntry = null;
+ throw new InvalidHeaderException
+ ( "bad header in block "
+ + this.buffer.getCurrentBlockNum()
+ + " record "
+ + this.buffer.getCurrentRecordNum()
+ + ", " + ex.getMessage() );
+ }
+ }
+
+ return this.currEntry;
+ }
+
+ /**
+ * Reads a byte from the current tar archive entry.
+ *
+ * This method simply calls read( byte[], int, int ).
+ *
+ * @return The byte read, or -1 at EOF.
+ */
+ public int
+ read()
+ throws IOException
+ {
+ int num = this.read( this.oneBuf, 0, 1 );
+ if ( num == -1 )
+ return num;
+ else
+ return (int) this.oneBuf[0];
+ }
+
+ /**
+ * Reads bytes from the current tar archive entry.
+ *
+ * This method simply calls read( byte[], int, int ).
+ *
+ * @param buf The buffer into which to place bytes read.
+ * @return The number of bytes read, or -1 at EOF.
+ */
+ public int
+ read( byte[] buf )
+ throws IOException
+ {
+ return this.read( buf, 0, buf.length );
+ }
+
+ /**
+ * Reads bytes from the current tar archive entry.
+ *
+ * This method is aware of the boundaries of the current
+ * entry in the archive and will deal with them as if they
+ * were this stream's start and EOF.
+ *
+ * @param buf The buffer into which to place bytes read.
+ * @param offset The offset at which to place bytes read.
+ * @param numToRead The number of bytes to read.
+ * @return The number of bytes read, or -1 at EOF.
+ */
+ public int
+ read( byte[] buf, int offset, int numToRead )
+ throws IOException
+ {
+ int totalRead = 0;
+
+ if ( this.entryOffset >= this.entrySize )
+ return -1;
+
+ if ( (numToRead + this.entryOffset) > this.entrySize )
+ {
+ numToRead = (int) (this.entrySize - this.entryOffset);
+ }
+
+ if ( this.readBuf != null )
+ {
+ int sz = ( numToRead > this.readBuf.length )
+ ? this.readBuf.length : numToRead;
+
+ System.arraycopy( this.readBuf, 0, buf, offset, sz );
+
+ if ( sz >= this.readBuf.length )
+ {
+ this.readBuf = null;
+ }
+ else
+ {
+ int newLen = this.readBuf.length - sz;
+ byte[] newBuf = new byte[ newLen ];
+ System.arraycopy( this.readBuf, sz, newBuf, 0, newLen );
+ this.readBuf = newBuf;
+ }
+
+ totalRead += sz;
+ numToRead -= sz;
+ offset += sz;
+ }
+
+ for ( ; numToRead > 0 ; )
+ {
+ byte[] rec = this.buffer.readRecord();
+ if ( rec == null )
+ {
+ // Unexpected EOF!
+ throw new IOException
+ ( "unexpected EOF with " + numToRead + " bytes unread" );
+ }
+
+ int sz = numToRead;
+ int recLen = rec.length;
+
+ if ( recLen > sz )
+ {
+ System.arraycopy( rec, 0, buf, offset, sz );
+ this.readBuf = new byte[ recLen - sz ];
+ System.arraycopy( rec, sz, this.readBuf, 0, recLen - sz );
+ }
+ else
+ {
+ sz = recLen;
+ System.arraycopy( rec, 0, buf, offset, recLen );
+ }
+
+ totalRead += sz;
+ numToRead -= sz;
+ offset += sz;
+ }
+
+ this.entryOffset += totalRead;
+
+ return totalRead;
+ }
+
+ /**
+ * Copies the contents of the current tar archive entry directly into
+ * an output stream.
+ *
+ * @param out The OutputStream into which to write the entry's data.
+ */
+ public void
+ copyEntryContents( OutputStream out )
+ throws IOException
+ {
+ byte[] buf = new byte[ 32 * 1024 ];
+
+ for ( ; ; )
+ {
+ int numRead = this.read( buf, 0, buf.length );
+ if ( numRead == -1 )
+ break;
+ out.write( buf, 0, numRead );
+ }
+ }
+
+ /**
+ * This interface is provided, with the method setEntryFactory(), to allow
+ * the programmer to have their own TarEntry subclass instantiated for the
+ * entries return from getNextEntry().
+ */
+
+ public
+ interface EntryFactory
+ {
+ public TarEntry
+ createEntry( String name );
+
+ public TarEntry
+ createEntry( File path )
+ throws InvalidHeaderException;
+
+ public TarEntry
+ createEntry( byte[] headerBuf )
+ throws InvalidHeaderException;
+ }
+
+ public
+ class EntryAdapter
+ implements EntryFactory
+ {
+ public TarEntry
+ createEntry( String name )
+ {
+ return new TarEntry( name );
+ }
+
+ public TarEntry
+ createEntry( File path )
+ throws InvalidHeaderException
+ {
+ return new TarEntry( path );
+ }
+
+ public TarEntry
+ createEntry( byte[] headerBuf )
+ throws InvalidHeaderException
+ {
+ return new TarEntry( headerBuf );
+ }
+ }
+
+ }
+
+
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time@gjt.org> <http://www.trustice.com>
+**
+** This work has been placed into the public domain.
+** You may use this work in any way and for any purpose you wish.
+**
+** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
+** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
+** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
+** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
+** REDISTRIBUTION OF THIS SOFTWARE.
+**
+*/
+
+
+/**
+ * The TarOutputStream writes a UNIX tar archive as an OutputStream.
+ * Methods are provided to put entries, and then write their contents
+ * by writing to this stream using write().
+ *
+ * Kerry Menzel <kmenzel@cfl.rr.com> Contributed the code to support
+ * file sizes greater than 2GB (longs versus ints).
+ *
+ * @version $Revision: 1.8 $
+ * @author Timothy Gerard Endres, <time@gjt.org>
+ * @see TarBuffer
+ * @see TarHeader
+ * @see TarEntry
+ */
+
+
+public
+static class TarOutputStream
+extends FilterOutputStream
+ {
+ protected boolean debug;
+ protected long currSize;
+ protected long currBytes;
+ protected byte[] oneBuf;
+ protected byte[] recordBuf;
+ protected int assemLen;
+ protected byte[] assemBuf;
+ protected TarBuffer buffer;
+
+
+ public
+ TarOutputStream( OutputStream os )
+ {
+ this( os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE );
+ }
+
+ public
+ TarOutputStream( OutputStream os, int blockSize )
+ {
+ this( os, blockSize, TarBuffer.DEFAULT_RCDSIZE );
+ }
+
+ public
+ TarOutputStream( OutputStream os, int blockSize, int recordSize )
+ {
+ super( os );
+
+ this.buffer = new TarBuffer( os, blockSize, recordSize );
+
+ this.debug = false;
+ this.assemLen = 0;
+ this.assemBuf = new byte[ recordSize ];
+ this.recordBuf = new byte[ recordSize ];
+ this.oneBuf = new byte[1];
+ }
+
+ /**
+ * Sets the debugging flag.
+ *
+ * @param debugF True to turn on debugging.
+ */
+ public void
+ setDebug( boolean debugF )
+ {
+ this.debug = debugF;
+ }
+
+ /**
+ * Sets the debugging flag in this stream's TarBuffer.
+ *
+ * @param debugF True to turn on debugging.
+ */
+ public void
+ setBufferDebug( boolean debug )
+ {
+ this.buffer.setDebug( debug );
+ }
+
+ /**
+ * Ends the TAR archive without closing the underlying OutputStream.
+ * The result is that the EOF record of nulls is written.
+ */
+
+ public void
+ finish()
+ throws IOException
+ {
+ this.writeEOFRecord();
+ }
+
+ /**
+ * Ends the TAR archive and closes the underlying OutputStream.
+ * This means that finish() is called followed by calling the
+ * TarBuffer's close().
+ */
+
+ public void
+ close()
+ throws IOException
+ {
+ this.finish();
+ this.buffer.close();
+ }
+
+ /**
+ * Get the record size being used by this stream's TarBuffer.
+ *
+ * @return The TarBuffer record size.
+ */
+ public int
+ getRecordSize()
+ {
+ return this.buffer.getRecordSize();
+ }
+
+ /**
+ * Put an entry on the output stream. This writes the entry's
+ * header record and positions the output stream for writing
+ * the contents of the entry. Once this method is called, the
+ * stream is ready for calls to write() to write the entry's
+ * contents. Once the contents are written, closeEntry()
+ * <B>MUST</B> be called to ensure that all buffered data
+ * is completely written to the output stream.
+ *
+ * @param entry The TarEntry to be written to the archive.
+ */
+ public void
+ putNextEntry( TarEntry entry )
+ throws IOException
+ {
+ StringBuffer name = entry.getHeader().name;
+
+ // NOTE
+ // This check is not adequate, because the maximum file length that
+ // can be placed into a POSIX (ustar) header depends on the precise
+ // locations of the path elements (slashes) within the file's full
+ // pathname. For this reason, writeEntryHeader() can still throw an
+ // InvalidHeaderException if the file's full pathname will not fit
+ // in the header.
+
+ if ( ( entry.isUnixTarFormat()
+ && name.length() > TarHeader.NAMELEN )
+ ||
+ ( ! entry.isUnixTarFormat()
+ && name.length() > (TarHeader.NAMELEN + TarHeader.PREFIXLEN) )
+ )
+ {
+ throw new InvalidHeaderException
+ ( "file name '"
+ + name
+ + "' is too long ( "
+ + name.length()
+ + " > "
+ + ( entry.isUnixTarFormat()
+ ? TarHeader.NAMELEN
+ : (TarHeader.NAMELEN + TarHeader.PREFIXLEN) )
+ + " bytes )" );
+ }
+
+ entry.writeEntryHeader( this.recordBuf );
+
+ this.buffer.writeRecord( this.recordBuf );
+
+ this.currBytes = 0;
+
+ if ( entry.isDirectory() )
+ this.currSize = 0;
+ else
+ this.currSize = entry.getSize();
+ }
+
+ /**
+ * Close an entry. This method MUST be called for all file
+ * entries that contain data. The reason is that we must
+ * buffer data written to the stream in order to satisfy
+ * the buffer's record based writes. Thus, there may be
+ * data fragments still being assembled that must be written
+ * to the output stream before this entry is closed and the
+ * next entry written.
+ */
+ public void
+ closeEntry()
+ throws IOException
+ {
+ if ( this.assemLen > 0 )
+ {
+ for ( int i = this.assemLen ; i < this.assemBuf.length ; ++i )
+ this.assemBuf[i] = 0;
+
+ this.buffer.writeRecord( this.assemBuf );
+
+ this.currBytes += this.assemLen;
+ this.assemLen = 0;
+ }
+
+ if ( this.currBytes < this.currSize )
+ throw new IOException
+ ( "entry closed at '" + this.currBytes
+ + "' before the '" + this.currSize
+ + "' bytes specified in the header were written" );
+ }
+
+ /**
+ * Writes a byte to the current tar archive entry.
+ *
+ * This method simply calls read( byte[], int, int ).
+ *
+ * @param b The byte written.
+ */
+ public void
+ write( int b )
+ throws IOException
+ {
+ this.oneBuf[0] = (byte) b;
+ this.write( this.oneBuf, 0, 1 );
+ }
+
+ /**
+ * Writes bytes to the current tar archive entry.
+ *
+ * This method simply calls read( byte[], int, int ).
+ *
+ * @param wBuf The buffer to write to the archive.
+ * @return The number of bytes read, or -1 at EOF.
+ */
+ public void
+ write( byte[] wBuf )
+ throws IOException
+ {
+ this.write( wBuf, 0, wBuf.length );
+ }
+
+ /**
+ * Writes bytes to the current tar archive entry. This method
+ * is aware of the current entry and will throw an exception if
+ * you attempt to write bytes past the length specified for the
+ * current entry. The method is also (painfully) aware of the
+ * record buffering required by TarBuffer, and manages buffers
+ * that are not a multiple of recordsize in length, including
+ * assembling records from small buffers.
+ *
+ * This method simply calls read( byte[], int, int ).
+ *
+ * @param wBuf The buffer to write to the archive.
+ * @param wOffset The offset in the buffer from which to get bytes.
+ * @param numToWrite The number of bytes to write.
+ */
+ public void
+ write( byte[] wBuf, int wOffset, int numToWrite )
+ throws IOException
+ {
+ if ( (this.currBytes + numToWrite) > this.currSize )
+ throw new IOException
+ ( "request to write '" + numToWrite
+ + "' bytes exceeds size in header of '"
+ + this.currSize + "' bytes" );
+
+ //
+ // We have to deal with assembly!!!
+ // The programmer can be writing little 32 byte chunks for all
+ // we know, and we must assemble complete records for writing.
+ // REVIEW Maybe this should be in TarBuffer? Could that help to
+ // eliminate some of the buffer copying.
+ //
+ if ( this.assemLen > 0 )
+ {
+ if ( (this.assemLen + numToWrite ) >= this.recordBuf.length )
+ {
+ int aLen = this.recordBuf.length - this.assemLen;
+
+ System.arraycopy
+ ( this.assemBuf, 0, this.recordBuf, 0, this.assemLen );
+
+ System.arraycopy
+ ( wBuf, wOffset, this.recordBuf, this.assemLen, aLen );
+
+ this.buffer.writeRecord( this.recordBuf );
+
+ this.currBytes += this.recordBuf.length;
+
+ wOffset += aLen;
+ numToWrite -= aLen;
+ this.assemLen = 0;
+ }
+ else // ( (this.assemLen + numToWrite ) < this.recordBuf.length )
+ {
+ System.arraycopy
+ ( wBuf, wOffset, this.assemBuf,
+ this.assemLen, numToWrite );
+ wOffset += numToWrite;
+ this.assemLen += numToWrite;
+ numToWrite -= numToWrite;
+ }
+ }
+
+ //
+ // When we get here we have EITHER:
+ // o An empty "assemble" buffer.
+ // o No bytes to write (numToWrite == 0)
+ //
+
+ for ( ; numToWrite > 0 ; )
+ {
+ if ( numToWrite < this.recordBuf.length )
+ {
+ System.arraycopy
+ ( wBuf, wOffset, this.assemBuf, this.assemLen, numToWrite );
+ this.assemLen += numToWrite;
+ break;
+ }
+
+ this.buffer.writeRecord( wBuf, wOffset );
+
+ long num = this.recordBuf.length;
+ this.currBytes += num;
+ numToWrite -= num;
+ wOffset += num;
+ }
+ }
+
+ /**
+ * Write an EOF (end of archive) record to the tar archive.
+ * An EOF record consists of a record of all zeros.
+ */
+ private void
+ writeEOFRecord()
+ throws IOException
+ {
+ for ( int i = 0 ; i < this.recordBuf.length ; ++i )
+ this.recordBuf[i] = 0;
+ this.buffer.writeRecord( this.recordBuf );
+ }
+
+ }
+
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time@gjt.org> <http://www.trustice.com>
+**
+** This work has been placed into the public domain.
+** You may use this work in any way and for any purpose you wish.
+**
+** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
+** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
+** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
+** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
+** REDISTRIBUTION OF THIS SOFTWARE.
+**
+*/
+
+/**
+ * This interface is provided to TarArchive to display progress
+ * information during operation. This is required to display the
+ * results of the 'list' operation.
+ */
+
+public interface
+TarProgressDisplay
+ {
+ /**
+ * Display a progress message.
+ *
+ * @param msg The message to display.
+ */
+
+ public void
+ showTarProgressMessage( String msg );
+ }
+
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time@gjt.org> <http://www.trustice.com>
+**
+** This work has been placed into the public domain.
+** You may use this work in any way and for any purpose you wish.
+**
+** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
+** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
+** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
+** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
+** REDISTRIBUTION OF THIS SOFTWARE.
+**
+*/
+
+/**
+ * This interface indicates if a file qualifies for ASCII translation.
+ * To support customization of TAR translation, this interface allows
+ * the programmer to provide an object that will check files that do
+ * not match the MIME types file's check for 'text/*' types. To provide
+ * your own typer, subclass this class and set the TarArchive's TransFileTyper
+ * via the method setTransFileTyper().
+ */
+
+public static class
+TarTransFileTyper
+ {
+ /**
+ * Return true if the file should be translated as ASCII.
+ *
+ * @param f The file to be checked to see if it need ASCII translation.
+ */
+
+ public boolean
+ isAsciiFile( File f )
+ {
+ return false;
+ }
+
+ /**
+ * Return true if the file should be translated as ASCII based on its name.
+ * The file DOES NOT EXIST. This is called during extract, so all we know
+ * is the file name.
+ *
+ * @param name The name of the file to be checked to see if it need ASCII
+ * translation.
+ */
+
+ public boolean
+ isAsciiFile( String name )
+ {
+ return false;
+ }
+
+ }
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time@gjt.org> <http://www.trustice.com>
+**
+** This work has been placed into the public domain.
+** You may use this work in any way and for any purpose you wish.
+**
+** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
+** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
+** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
+** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
+** REDISTRIBUTION OF THIS SOFTWARE.
+**
+*/
+
+/**
+ * The tar class implements a weak reproduction of the
+ * traditional UNIX tar command. It currently supports
+ * creating, listing, and extracting from archives. It
+ * also supports GZIP-ed archives with the '-z' flag.
+ * See the usage (-? or --usage) for option details.
+ *
+ * <pre>
+ * usage: com.ice.tar.tar has three basic modes:
+ *
+ * com.ice.tar -c [options] archive files...
+ * Create new archive containing files.
+ *
+ * com.ice.tar -t [options] archive
+ * List contents of tar archive
+ *
+ * com.ice.tar -x [options] archive
+ * Extract contents of tar archive.
+ *
+ * options:
+ * -f file, use 'file' as the tar archive
+ * -v, verbose mode
+ * -z, use GZIP compression
+ * -D, debug archive and buffer operation
+ * -b blks, set blocking size to (blks * 512) bytes
+ * -o, write a V7 format archive rather than ANSI
+ * -u name, set user name to 'name'
+ * -U id, set user id to 'id'
+ * -g name, set group name to 'name'
+ * -G id, set group id to 'id'
+ * -?, print usage information
+ * --trans, translate 'text/*' files
+ * --mime file, use this mime types file and translate
+ * --usage, print usage information
+ * --version, print version information
+ *
+ * The translation options will translate from local line
+ * endings to UNIX line endings of '\\n' when writing tar
+ * archives, and from UNIX line endings into local line endings
+ * when extracting archives.
+ *
+ * Written by Tim Endres
+ * This software has been placed into the public domain.
+ * </pre>
+ *
+ * @version $Revision: 1.10 $
+ * @author Timothy Gerard Endres, <time@gjt.org>
+ * @see TarArchive
+ */
+
+public static class
+tar extends Object
+ implements TarProgressDisplay
+ {
+ /**
+ * Flag that determines if debugging information is displayed.
+ */
+ private boolean debug;
+ /**
+ * Flag that determines if verbose feedback is provided.
+ */
+ private boolean verbose;
+ /**
+ * Flag that determines if IO is GZIP-ed ('-z' option).
+ */
+ private boolean compressed;
+ /**
+ * True if we are listing the archive. False if writing or extracting.
+ */
+ private boolean listingArchive;
+ /**
+ * True if we are writing the archive. False if we are extracting it.
+ */
+ private boolean writingArchive;
+ /**
+ * True if we are writing an old UNIX archive format (sets entry format).
+ */
+ private boolean unixArchiveFormat;
+ /**
+ * True if we are not to overwrite existing files.
+ */
+ private boolean keepOldFiles;
+ /**
+ * True if we are to convert ASCII text files from local line endings
+ * to the UNIX standard '\n'.
+ */
+ private boolean asciiTranslate;
+ /**
+ * True if a MIME file has been loaded with the '--mime' option.
+ */
+ private boolean mimeFileLoaded;
+
+ /**
+ * The archive name provided on the command line, null if stdio.
+ */
+ private String archiveName;
+
+ /**
+ * The blocksize to use for the tar archive IO. Set by the '-b' option.
+ */
+ private int blockSize;
+
+ /**
+ * The userId to use for files written to archives. Set by '-U' option.
+ */
+ private int userId;
+ /**
+ * The userName to use for files written to archives. Set by '-u' option.
+ */
+ private String userName;
+ /**
+ * The groupId to use for files written to archives. Set by '-G' option.
+ */
+ private int groupId;
+ /**
+ * The groupName to use for files written to archives. Set by '-g' option.
+ */
+ private String groupName;
+
+
+ /**
+ * The main entry point of the tar class.
+ */
+ public static void
+ main( String argv[] )
+ {
+ tar app = new tar();
+
+ app.instanceMain( argv );
+ }
+
+ /**
+ * Establishes the default userName with the 'user.name' property.
+ */
+ public
+ tar()
+ {
+ this.debug = false;
+ this.verbose = false;
+ this.compressed = false;
+ this.archiveName = null;
+ this.listingArchive = false;
+ this.writingArchive = true;
+ this.unixArchiveFormat = false;
+ this.keepOldFiles = false;
+ this.asciiTranslate = false;
+
+ this.blockSize = TarBuffer.DEFAULT_BLKSIZE;
+
+ String sysUserName =
+ System.getProperty( "user.name" );
+
+ this.userId = 0;
+ this.userName =
+ ( (sysUserName == null) ? "" : sysUserName );
+
+ this.groupId = 0;
+ this.groupName = "";
+ }
+
+ /**
+ * This is the "real" main. The class main() instantiates a tar object
+ * for the application and then calls this method. Process the arguments
+ * and perform the requested operation.
+ */
+ public void
+ instanceMain( String argv[] )
+ {
+ TarArchive archive = null;
+
+ int argIdx = this.processArguments( argv );
+
+ if ( writingArchive ) // WRITING
+ {
+ OutputStream outStream = System.out;
+
+ if ( this.archiveName != null
+ && ! this.archiveName.equals( "-" ) )
+ {
+ try {
+ outStream = new FileOutputStream( this.archiveName );
+ }
+ catch ( IOException ex )
+ {
+ outStream = null;
+ ex.printStackTrace( System.err );
+ }
+ }
+
+ if ( outStream != null )
+ {
+ if ( this.compressed )
+ {
+ try {
+ outStream = new GZIPOutputStream( outStream );
+ }
+ catch ( IOException ex )
+ {
+ outStream = null;
+ ex.printStackTrace( System.err );
+ }
+ }
+
+ archive = new TarArchive( outStream, this.blockSize );
+ }
+ }
+ else // EXTRACING OR LISTING
+ {
+ InputStream inStream = System.in;
+
+ if ( this.archiveName != null
+ && ! this.archiveName.equals( "-" ) )
+ {
+ try {
+ inStream = new FileInputStream( this.archiveName );
+ }
+ catch ( IOException ex )
+ {
+ inStream = null;
+ ex.printStackTrace( System.err );
+ }
+ }
+
+ if ( inStream != null )
+ {
+ if ( this.compressed )
+ {
+ try {
+ inStream = new GZIPInputStream( inStream );
+ }
+ catch ( IOException ex )
+ {
+ inStream = null;
+ ex.printStackTrace( System.err );
+ }
+ }
+
+ archive = new TarArchive( inStream, this.blockSize );
+ }
+ }
+
+ if ( archive != null ) // SET ARCHIVE OPTIONS
+ {
+ archive.setDebug( this.debug );
+ archive.setVerbose( this.verbose );
+ archive.setTarProgressDisplay( this );
+ archive.setKeepOldFiles( this.keepOldFiles );
+ archive.setAsciiTranslation( this.asciiTranslate );
+
+ archive.setUserInfo(
+ this.userId, this.userName,
+ this.groupId, this.groupName );
+ }
+
+ if ( archive == null )
+ {
+ System.err.println( "no processing due to errors" );
+ }
+ else if ( this.writingArchive ) // WRITING
+ {
+ for ( ; argIdx < argv.length ; ++argIdx )
+ {
+ try {
+ File f = new File( argv[ argIdx ] );
+
+ TarEntry entry = new TarEntry( f );
+
+ if ( this.unixArchiveFormat )
+ entry.setUnixTarFormat();
+ else
+ entry.setUSTarFormat();
+
+ archive.writeEntry( entry, true );
+ }
+ catch ( IOException ex )
+ {
+ ex.printStackTrace( System.err );
+ }
+ }
+ }
+ else if ( this.listingArchive ) // LISTING
+ {
+ try {
+ archive.listContents();
+ }
+ catch ( InvalidHeaderException ex )
+ {
+ ex.printStackTrace( System.err );
+ }
+ catch ( IOException ex )
+ {
+ ex.printStackTrace( System.err );
+ }
+ }
+ else // EXTRACTING
+ {
+ String userDir =
+ System.getProperty( "user.dir", null );
+
+ File destDir = new File( userDir );
+ if ( ! destDir.exists() )
+ {
+ if ( ! destDir.mkdirs() )
+ {
+ destDir = null;
+ Throwable ex = new Throwable
+ ( "ERROR, mkdirs() on '" + destDir.getPath()
+ + "' returned false." );
+ ex.printStackTrace( System.err );
+ }
+ }
+
+ if ( destDir != null )
+ {
+ try {
+ archive.extractContents( destDir );
+ }
+ catch ( InvalidHeaderException ex )
+ {
+ ex.printStackTrace( System.err );
+ }
+ catch ( IOException ex )
+ {
+ ex.printStackTrace( System.err );
+ }
+ }
+ }
+
+ if ( archive != null ) // CLOSE ARCHIVE
+ {
+ try {
+ archive.closeArchive();
+ }
+ catch ( IOException ex )
+ {
+ ex.printStackTrace( System.err );
+ }
+ }
+ }
+
+ /**
+ * Process arguments, handling options, and return the index of the
+ * first non-option argument.
+ *
+ * @return The index of the first non-option argument.
+ */
+
+ private int
+ processArguments( String args[] )
+ {
+ int idx = 0;
+ boolean gotOP = false;
+
+ for ( ; idx < args.length ; ++idx )
+ {
+ String arg = args[ idx ];
+
+ if ( ! arg.startsWith( "-" ) )
+ break;
+
+ if ( arg.startsWith( "--" ) )
+ {
+ if ( arg.equals( "--usage" ) )
+ {
+ this.usage();
+ System.exit(1);
+ }
+ else if ( arg.equals( "--version" ) )
+ {
+ this.version();
+ System.exit(1);
+ }
+ else
+ {
+ System.err.println
+ ( "unknown option: " + arg );
+ this.usage();
+ System.exit(1);
+ }
+ }
+ else for ( int cIdx = 1 ; cIdx < arg.length() ; ++cIdx )
+ {
+ char ch = arg.charAt( cIdx );
+
+ if ( ch == '?' )
+ {
+ this.usage();
+ System.exit(1);
+ }
+ else if ( ch == 'f' )
+ {
+ this.archiveName = args[ ++idx ];
+ }
+ else if ( ch == 'z' )
+ {
+ this.compressed = true;
+ }
+ else if ( ch == 'c' )
+ {
+ gotOP = true;
+ this.writingArchive = true;
+ this.listingArchive = false;
+ }
+ else if ( ch == 'x' )
+ {
+ gotOP = true;
+ this.writingArchive = false;
+ this.listingArchive = false;
+ }
+ else if ( ch == 't' )
+ {
+ gotOP = true;
+ this.writingArchive = false;
+ this.listingArchive = true;
+ }
+ else if ( ch == 'k' )
+ {
+ this.keepOldFiles = true;
+ }
+ else if ( ch == 'o' )
+ {
+ this.unixArchiveFormat = true;
+ }
+ else if ( ch == 'b' )
+ {
+ try {
+ int blks = Integer.parseInt( args[ ++idx ] );
+ this.blockSize =
+ ( blks * TarBuffer.DEFAULT_RCDSIZE );
+ }
+ catch ( NumberFormatException ex )
+ {
+ ex.printStackTrace( System.err );
+ }
+ }
+ else if ( ch == 'u' )
+ {
+ this.userName = args[ ++idx ];
+ }
+ else if ( ch == 'U' )
+ {
+ String idStr = args[ ++idx ];
+ try {
+ this.userId = Integer.parseInt( idStr );
+ }
+ catch ( NumberFormatException ex )
+ {
+ this.userId = 0;
+ ex.printStackTrace( System.err );
+ }
+ }
+ else if ( ch == 'g' )
+ {
+ this.groupName = args[ ++idx ];
+ }
+ else if ( ch == 'G' )
+ {
+ String idStr = args[ ++idx ];
+ try {
+ this.groupId = Integer.parseInt( idStr );
+ }
+ catch ( NumberFormatException ex )
+ {
+ this.groupId = 0;
+ ex.printStackTrace( System.err );
+ }
+ }
+ else if ( ch == 'v' )
+ {
+ this.verbose = true;
+ }
+ else if ( ch == 'D' )
+ {
+ this.debug = true;
+ }
+ else
+ {
+ System.err.println
+ ( "unknown option: " + ch );
+ this.usage();
+ System.exit(1);
+ }
+ }
+ }
+
+ if ( ! gotOP )
+ {
+ System.err.println
+ ( "you must specify an operation option (c, x, or t)" );
+ this.usage();
+ System.exit(1);
+ }
+
+ return idx;
+ }
+
+ // I N T E R F A C E TarProgressDisplay
+
+ /**
+ * Display progress information by printing it to System.out.
+ */
+
+ public void
+ showTarProgressMessage( String msg )
+ {
+ System.out.println( msg );
+ }
+
+ /**
+ * Print version information.
+ */
+
+ private void
+ version()
+ {
+ System.err.println
+ ( "Release 2.4 - $Revision: 1.10 $ $Name: $" );
+ }
+
+ /**
+ * Print usage information.
+ */
+
+ private void
+ usage()
+ {
+ System.err.println( "usage: com.ice.tar.tar has three basic modes:" );
+ System.err.println( " com.ice.tar -c [options] archive files..." );
+ System.err.println( " Create new archive containing files." );
+ System.err.println( " com.ice.tar -t [options] archive" );
+ System.err.println( " List contents of tar archive" );
+ System.err.println( " com.ice.tar -x [options] archive" );
+ System.err.println( " Extract contents of tar archive." );
+ System.err.println( "" );
+ System.err.println( "options:" );
+ System.err.println( " -f file, use 'file' as the tar archive" );
+ System.err.println( " -v, verbose mode" );
+ System.err.println( " -z, use GZIP compression" );
+ System.err.println( " -D, debug archive and buffer operation" );
+ System.err.println( " -b blks, set blocking size to (blks * 512) bytes" );
+ System.err.println( " -o, write a V7 format archive rather than ANSI" );
+ System.err.println( " -u name, set user name to 'name'" );
+ System.err.println( " -U id, set user id to 'id'" );
+ System.err.println( " -g name, set group name to 'name'" );
+ System.err.println( " -G id, set group id to 'id'" );
+ System.err.println( " -?, print usage information" );
+ System.err.println( " --trans, translate 'text/*' files" );
+ System.err.println( " --mime file, use this mime types file and translate" );
+ System.err.println( " --usage, print usage information" );
+ System.err.println( " --version, print version information" );
+ System.err.println( "" );
+ System.err.println( "The translation options will translate from local line" );
+ System.err.println( "endings to UNIX line endings of '\\n' when writing tar" );
+ System.err.println( "archives, and from UNIX line endings into local line endings" );
+ System.err.println( "when extracting archives." );
+ System.err.println( "" );
+ System.err.println( "Written by Tim Endres" );
+ System.err.println( "" );
+ System.err.println( "This software has been placed into the public domain." );
+ System.err.println( "" );
+
+ this.version();
+
+ System.exit( 1 );
+ }
+
+ }
+
+
+}