From: adam Date: Fri, 25 Mar 2005 06:31:34 +0000 (+0000) Subject: added destination option for GetDep X-Git-Url: http://git.megacz.com/?p=org.ibex.util.git;a=commitdiff_plain;h=5e6a448eddcedce2c4ee3433966dbe72e325f7c7 added destination option for GetDep darcs-hash:20050325063134-5007d-91143057b315f538ac156da065821859efc57aba.gz --- diff --git a/src/org/ibex/util/GetDep.java b/src/org/ibex/util/GetDep.java new file mode 100644 index 0000000..9e0e67a --- /dev/null +++ b/src/org/ibex/util/GetDep.java @@ -0,0 +1,63 @@ +// Copyright 2000-2005 the Contributors, as shown in the revision logs. +// Licensed under the Apache Public Source License 2.0 ("the License"). +// You may not use this file except in compliance with the License. + +package org.ibex.util; +import java.util.*; +import java.net.*; +import java.io.*; +import java.util.zip.*; + +public final class GetDep { + + public static void main(String[] s) throws Exception { + if (s.length < 2) { + System.out.println("usage: java "+GetDep.class.getName()+" "); + return; + } + fetch(s[1], s[0]); + } + + public static void fetch(String path, String url) throws Exception { + InputStream is = fetch(url); + FileOutputStream fos = new FileOutputStream(path); + while(true) { + byte[] buf = new byte[1024 * 16]; + int numread = is.read(buf, 0, buf.length); + if (numread == -1) break; + fos.write(buf, 0, numread); + } + fos.close(); + } + + public static InputStream fetch(String url) throws Exception { + String scheme = url.substring(0, url.indexOf(':')); + if (scheme.equals("zip") || scheme.equals("tgz")) { + int bang = url.lastIndexOf('!'); + String path = url.substring(bang + 1); + url = url.substring(url.indexOf(':')+1, bang); + InputStream rest = fetch(url); + if (scheme.equals("zip")) { + ZipInputStream zis = new ZipInputStream(rest); + while(true) { + ZipEntry ze = zis.getNextEntry(); + if (ze == null) break; + if (ze.getName().equals(path)) return zis; + } + return null; + } else { + Tar.TarInputStream tis = new Tar.TarInputStream(new GZIPInputStream(rest)); + while(true) { + Tar.TarEntry te = tis.getNextEntry(); + if (te == null) break; + if (te.getName().equals(path)) return tis; + } + return null; + } + } else { + URL u = new URL(url); + return u.openConnection().getInputStream(); + } + } + +} diff --git a/src/org/ibex/util/Tar.java b/src/org/ibex/util/Tar.java new file mode 100644 index 0000000..6828beb --- /dev/null +++ b/src/org/ibex/util/Tar.java @@ -0,0 +1,4923 @@ +/* +** Authored by Timothy Gerard Endres +** +** +** 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, + * + */ + +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 +** +** +** 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 +** +** +** 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, + * @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 +** +** +** 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. + *

+ * 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, + * time@trustice.com. + * @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 ' 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 +** +** +** 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + * + *

+ *
+ * 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
+ *
+ * 
+ * + *
+ *
+ * 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
+ *
+ * 
+ * + * 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 + * + * @see TarHeader + * @author Timothy Gerard Endres, + */ + +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 + 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 . + * + * @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 null 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 protected and an empty + * subclass should be written in the other package. + * + * @param tis the TarInputStream 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 java.util.Enumeration. + * + * @return the next Object in the enumeration + * @exception NoSuchElementException 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 true if there are more elements in the enumeration. + * + * @return true 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 null if there is no next element or + * if an error occured. + * + * @return the next element of null 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" +** +** 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" + */ + +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 +** +** +** 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, + */ + +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 + * + * @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 +** +** +** 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 Contributed the code to support + * file sizes greater than 2GB (longs versus ints). + * + * + * @version $Revision: 1.9 $ + * @author Timothy Gerard Endres, + * @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 +** +** +** 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 Contributed the code to support + * file sizes greater than 2GB (longs versus ints). + * + * @version $Revision: 1.8 $ + * @author Timothy Gerard Endres, + * @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() + * MUST 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 +** +** +** 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 +** +** +** 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 +** +** +** 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. + * + *
+ * 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.
+ * 
+ * + * @version $Revision: 1.10 $ + * @author Timothy Gerard Endres, + * @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 ); + } + + } + + +}