git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@267597 13f79535-47bb-0310-9956-ffa450edef68master
| @@ -2,7 +2,7 @@ | |||||
| echo BOOTSTRAPPING ANT DISTRIBUTION | echo BOOTSTRAPPING ANT DISTRIBUTION | ||||
| set C=%CLASSPATH%;lib/xml.jar | set C=%CLASSPATH%;lib/xml.jar | ||||
| set SRCDIR=src\main\org\apache\tools\ant | |||||
| set SRCDIR=src\main\org\apache\tools | |||||
| set TMPDIR=tmp | set TMPDIR=tmp | ||||
| if "%OS%" == "Windows_NT" goto nt | if "%OS%" == "Windows_NT" goto nt | ||||
| @@ -18,14 +18,18 @@ mkdir %TMPDIR% | |||||
| echo ** COMPILING ANT CLASSES | echo ** COMPILING ANT CLASSES | ||||
| rem Compile the classes into the temp directory | |||||
| javac -classpath "%C%" -d %TMPDIR% %SRCDIR%\*.java | |||||
| rem Reset classpath to include base ant class files | rem Reset classpath to include base ant class files | ||||
| set C=%TMPDIR%;%C% | set C=%TMPDIR%;%C% | ||||
| rem Compile sub classes into the temp directory | rem Compile sub classes into the temp directory | ||||
| javac -classpath "%C%" -d %TMPDIR% %SRCDIR%\taskdefs\*.java | |||||
| javac -classpath "%C%" -d %TMPDIR% %SRCDIR%\tar\*.java | |||||
| rem Compile the classes into the temp directory | |||||
| javac -classpath "%C%" -d %TMPDIR% %SRCDIR%\ant\*.java | |||||
| rem Compile sub classes into the temp directory | |||||
| javac -classpath "%C%" -d %TMPDIR% %SRCDIR%\ant\taskdefs\*.java | |||||
| echo ** COPYING REQUIRED FILES | echo ** COPYING REQUIRED FILES | ||||
| @@ -2,7 +2,7 @@ if [ -f $HOME/.antrc ] ; then | |||||
| . $HOME/.antrc | . $HOME/.antrc | ||||
| fi | fi | ||||
| SRCDIR=src/main/org/apache/tools/ant | |||||
| SRCDIR=src/main/org/apache/tools | |||||
| CLASSDIR=classes | CLASSDIR=classes | ||||
| CLASSPATH=${CLASSPATH}:${JAVA_HOME}/lib/classes.zip:${JAVA_HOME}/lib/tools.jar | CLASSPATH=${CLASSPATH}:${JAVA_HOME}/lib/classes.zip:${JAVA_HOME}/lib/tools.jar | ||||
| CLASSPATH=${CLASSPATH}:lib/xml.jar:src/main:${CLASSDIR} | CLASSPATH=${CLASSPATH}:lib/xml.jar:src/main:${CLASSDIR} | ||||
| @@ -12,8 +12,9 @@ mkdir -p ${CLASSDIR} | |||||
| export CLASSPATH | export CLASSPATH | ||||
| echo $CLASSPATH | echo $CLASSPATH | ||||
| javac -d ${CLASSDIR} ${SRCDIR}/*.java | |||||
| javac -d ${CLASSDIR} ${SRCDIR}/taskdefs/*.java | |||||
| javac -d ${CLASSDIR} ${SRCDIR}/tar/*.java | |||||
| javac -d ${CLASSDIR} ${SRCDIR}/ant/*.java | |||||
| javac -d ${CLASSDIR} ${SRCDIR}/ant/taskdefs/*.java | |||||
| cp src/main/org/apache/tools/ant/taskdefs/defaults.properties ${CLASSDIR}/org/apache/tools/ant/taskdefs | cp src/main/org/apache/tools/ant/taskdefs/defaults.properties ${CLASSDIR}/org/apache/tools/ant/taskdefs | ||||
| cp src/main/org/apache/tools/ant/parser.properties ${CLASSDIR}/org/apache/tools/ant | cp src/main/org/apache/tools/ant/parser.properties ${CLASSDIR}/org/apache/tools/ant | ||||
| @@ -7,6 +7,7 @@ | |||||
| <project name="Ant" default="main" basedir="."> | <project name="Ant" default="main" basedir="."> | ||||
| <target name="init"> | <target name="init"> | ||||
| <property name="Name" value="Ant"/> | |||||
| <property name="name" value="ant"/> | <property name="name" value="ant"/> | ||||
| <property name="version" value="1.0-rc1"/> | <property name="version" value="1.0-rc1"/> | ||||
| @@ -22,7 +23,7 @@ | |||||
| <property name="dist.dir" value="../dist/ant"/> | <property name="dist.dir" value="../dist/ant"/> | ||||
| <property name="classpath" value="lib/xml.jar"/> | <property name="classpath" value="lib/xml.jar"/> | ||||
| <property name="packages" value="org.apache.tools.ant.*"/> | |||||
| <property name="packages" value="org.apache.tools.*"/> | |||||
| <property name="manifest" value="src/etc/manifest"/> | <property name="manifest" value="src/etc/manifest"/> | ||||
| <property name="build.compiler" value="classic"/> | <property name="build.compiler" value="classic"/> | ||||
| @@ -79,9 +80,8 @@ | |||||
| destdir="${build.javadocs}" | destdir="${build.javadocs}" | ||||
| author="true" | author="true" | ||||
| version="true" | version="true" | ||||
| use="true" | |||||
| windowtitle="${name} API" | |||||
| doctitle="${name}" | |||||
| windowtitle="${Name} API" | |||||
| doctitle="${Name}" | |||||
| bottom="Copyright © 2000 Apache Software Foundation. All Rights Reserved." | bottom="Copyright © 2000 Apache Software Foundation. All Rights Reserved." | ||||
| /> | /> | ||||
| </target> | </target> | ||||
| @@ -111,10 +111,23 @@ | |||||
| <copyfile src="README" dest="${dist.dir}/README"/> | <copyfile src="README" dest="${dist.dir}/README"/> | ||||
| <copyfile src="TODO" dest="${dist.dir}/TODO"/> | <copyfile src="TODO" dest="${dist.dir}/TODO"/> | ||||
| <copyfile src="LICENSE" dest="${dist.dir}/LICENSE"/> | <copyfile src="LICENSE" dest="${dist.dir}/LICENSE"/> | ||||
| </target> | |||||
| <jar jarfile="${name}.jar" basedir="${dist.dir}" includes="**"/> | |||||
| <!-- =================================================================== --> | |||||
| <!-- Packages the distribution with ZIP --> | |||||
| <!-- =================================================================== --> | |||||
| <target name="dist-zip" depends="dist"> | |||||
| <zip zipfile="${Name}-${version}.zip" basedir="${dist.dir}" includes="**"/> | |||||
| </target> | </target> | ||||
| <!-- =================================================================== --> | |||||
| <!-- Packages the distribution with TAR-GZIP --> | |||||
| <!-- =================================================================== --> | |||||
| <target name="dist-tgz" depends="dist"> | |||||
| <tar tarfile="${Name}-${version}.tar" basedir="${dist.dir}" includes="**"/> | |||||
| <gzip zipfile="${Name}-${version}.tar.gz" src="${Name}-${version}.tar"/> | |||||
| </target> | |||||
| <!-- =================================================================== --> | <!-- =================================================================== --> | ||||
| <!-- Cleans up generated stuff --> | <!-- Cleans up generated stuff --> | ||||
| <!-- =================================================================== --> | <!-- =================================================================== --> | ||||
| @@ -0,0 +1,139 @@ | |||||
| /* | |||||
| * The Apache Software License, Version 1.1 | |||||
| * | |||||
| * Copyright (c) 1999 The Apache Software Foundation. All rights | |||||
| * reserved. | |||||
| * | |||||
| * Redistribution and use in source and binary forms, with or without | |||||
| * modification, are permitted provided that the following conditions | |||||
| * are met: | |||||
| * | |||||
| * 1. Redistributions of source code must retain the above copyright | |||||
| * notice, this list of conditions and the following disclaimer. | |||||
| * | |||||
| * 2. Redistributions in binary form must reproduce the above copyright | |||||
| * notice, this list of conditions and the following disclaimer in | |||||
| * the documentation and/or other materials provided with the | |||||
| * distribution. | |||||
| * | |||||
| * 3. The end-user documentation included with the redistribution, if | |||||
| * any, must include the following acknowlegement: | |||||
| * "This product includes software developed by the | |||||
| * Apache Software Foundation (http://www.apache.org/)." | |||||
| * Alternately, this acknowlegement may appear in the software itself, | |||||
| * if and wherever such third-party acknowlegements normally appear. | |||||
| * | |||||
| * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software | |||||
| * Foundation" must not be used to endorse or promote products derived | |||||
| * from this software without prior written permission. For written | |||||
| * permission, please contact apache@apache.org. | |||||
| * | |||||
| * 5. Products derived from this software may not be called "Apache" | |||||
| * nor may "Apache" appear in their names without prior written | |||||
| * permission of the Apache Group. | |||||
| * | |||||
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED | |||||
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||||
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||||
| * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR | |||||
| * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||||
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||||
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF | |||||
| * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |||||
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||||
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT | |||||
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||||
| * SUCH DAMAGE. | |||||
| * ==================================================================== | |||||
| * | |||||
| * This software consists of voluntary contributions made by many | |||||
| * individuals on behalf of the Apache Software Foundation. For more | |||||
| * information on the Apache Software Foundation, please see | |||||
| * <http://www.apache.org/>. | |||||
| */ | |||||
| package org.apache.tools.ant.taskdefs; | |||||
| import java.io.*; | |||||
| import org.apache.tools.ant.*; | |||||
| import org.apache.tools.tar.*; | |||||
| /** | |||||
| * Creates a TAR archive. | |||||
| * | |||||
| * @author Stefano Mazzocchi <a href="mailto:stefano@apache.org">stefano@apache.org</a> | |||||
| */ | |||||
| public class Tar extends MatchingTask { | |||||
| File tarFile; | |||||
| File baseDir; | |||||
| /** | |||||
| * This is the name/location of where to create the tar file. | |||||
| */ | |||||
| public void setTarfile(String tarFilename) { | |||||
| tarFile = project.resolveFile(tarFilename); | |||||
| } | |||||
| /** | |||||
| * This is the base directory to look in for things to tar. | |||||
| */ | |||||
| public void setBasedir(String baseDirname) { | |||||
| baseDir = project.resolveFile(baseDirname); | |||||
| } | |||||
| public void execute() throws BuildException { | |||||
| project.log("Building tar: "+ tarFile.getAbsolutePath()); | |||||
| if (baseDir == null) { | |||||
| throw new BuildException("basedir attribute must be set!"); | |||||
| } | |||||
| if (!baseDir.exists()) { | |||||
| throw new BuildException("basedir does not exist!"); | |||||
| } | |||||
| DirectoryScanner ds = super.getDirectoryScanner(baseDir); | |||||
| String[] files = ds.getIncludedFiles(); | |||||
| try { | |||||
| TarOutputStream tOut = new TarOutputStream(new FileOutputStream(tarFile)); | |||||
| tOut.setDebug(true); | |||||
| for (int i = 0; i < files.length; i++) { | |||||
| File f = new File(baseDir,files[i]); | |||||
| String name = files[i].replace(File.separatorChar,'/'); | |||||
| tarFile(f, tOut, name); | |||||
| } | |||||
| // close up | |||||
| tOut.close(); | |||||
| } catch (IOException ioe) { | |||||
| String msg = "Problem creating TAR: " + ioe.getMessage(); | |||||
| throw new BuildException(msg); | |||||
| } | |||||
| } | |||||
| protected void tarFile(File file, TarOutputStream tOut, String vPath) | |||||
| throws IOException | |||||
| { | |||||
| FileInputStream fIn = new FileInputStream(file); | |||||
| TarEntry te = new TarEntry(vPath); | |||||
| te.setSize(file.length()); | |||||
| te.setModTime(file.lastModified() / 1000); | |||||
| tOut.putNextEntry(te); | |||||
| byte[] buffer = new byte[8 * 1024]; | |||||
| int count = 0; | |||||
| do { | |||||
| tOut.write(buffer, 0, count); | |||||
| count = fIn.read(buffer, 0, buffer.length); | |||||
| } while (count != -1); | |||||
| tOut.closeEntry(); | |||||
| fIn.close(); | |||||
| } | |||||
| } | |||||
| @@ -76,16 +76,17 @@ public class Zip extends MatchingTask { | |||||
| protected String archiveType = "zip"; | protected String archiveType = "zip"; | ||||
| /** | /** | ||||
| This is the name/location of where to | |||||
| create the .zip file. | |||||
| */ | |||||
| * This is the name/location of where to | |||||
| * create the .zip file. | |||||
| */ | |||||
| public void setZipfile(String zipFilename) { | public void setZipfile(String zipFilename) { | ||||
| zipFile = project.resolveFile(zipFilename); | zipFile = project.resolveFile(zipFilename); | ||||
| } | } | ||||
| /** | /** | ||||
| This is the base directory to look in for | |||||
| things to zip. | |||||
| */ | |||||
| * This is the base directory to look in for | |||||
| * things to zip. | |||||
| */ | |||||
| public void setBasedir(String baseDirname) { | public void setBasedir(String baseDirname) { | ||||
| baseDir = project.resolveFile(baseDirname); | baseDir = project.resolveFile(baseDirname); | ||||
| } | } | ||||
| @@ -22,5 +22,6 @@ property=org.apache.tools.ant.taskdefs.Property | |||||
| taskdef=org.apache.tools.ant.taskdefs.Taskdef | taskdef=org.apache.tools.ant.taskdefs.Taskdef | ||||
| ant=org.apache.tools.ant.taskdefs.Ant | ant=org.apache.tools.ant.taskdefs.Ant | ||||
| exec=org.apache.tools.ant.taskdefs.Exec | exec=org.apache.tools.ant.taskdefs.Exec | ||||
| tar=org.apache.tools.ant.taskdefs.Tar | |||||
| # remove the task below once everyone has migrated | # remove the task below once everyone has migrated | ||||
| javadoc2=org.apache.tools.ant.taskdefs.Javadoc | javadoc2=org.apache.tools.ant.taskdefs.Javadoc | ||||
| @@ -0,0 +1,440 @@ | |||||
| /* | |||||
| * The Apache Software License, Version 1.1 | |||||
| * | |||||
| * Copyright (c) 1999 The Apache Software Foundation. All rights | |||||
| * reserved. | |||||
| * | |||||
| * Redistribution and use in source and binary forms, with or without | |||||
| * modification, are permitted provided that the following conditions | |||||
| * are met: | |||||
| * | |||||
| * 1. Redistributions of source code must retain the above copyright | |||||
| * notice, this list of conditions and the following disclaimer. | |||||
| * | |||||
| * 2. Redistributions in binary form must reproduce the above copyright | |||||
| * notice, this list of conditions and the following disclaimer in | |||||
| * the documentation and/or other materials provided with the | |||||
| * distribution. | |||||
| * | |||||
| * 3. The end-user documentation included with the redistribution, if | |||||
| * any, must include the following acknowlegement: | |||||
| * "This product includes software developed by the | |||||
| * Apache Software Foundation (http://www.apache.org/)." | |||||
| * Alternately, this acknowlegement may appear in the software itself, | |||||
| * if and wherever such third-party acknowlegements normally appear. | |||||
| * | |||||
| * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software | |||||
| * Foundation" must not be used to endorse or promote products derived | |||||
| * from this software without prior written permission. For written | |||||
| * permission, please contact apache@apache.org. | |||||
| * | |||||
| * 5. Products derived from this software may not be called "Apache" | |||||
| * nor may "Apache" appear in their names without prior written | |||||
| * permission of the Apache Group. | |||||
| * | |||||
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED | |||||
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||||
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||||
| * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR | |||||
| * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||||
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||||
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF | |||||
| * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |||||
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||||
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT | |||||
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||||
| * SUCH DAMAGE. | |||||
| * ==================================================================== | |||||
| * | |||||
| * This software consists of voluntary contributions made by many | |||||
| * individuals on behalf of the Apache Software Foundation. For more | |||||
| * information on the Apache Software Foundation, please see | |||||
| * <http://www.apache.org/>. | |||||
| */ | |||||
| /* | |||||
| * This package is based on the work done by Timothy Gerard Endres | |||||
| * (time@ice.com) to whom the Ant project is very grateful for his great code. | |||||
| */ | |||||
| package org.apache.tools.tar; | |||||
| import java.io.*; | |||||
| /** | |||||
| * The TarBuffer class implements the tar archive concept | |||||
| * of a buffered input stream. This concept goes back to the | |||||
| * days of blocked tape drives and special io devices. In the | |||||
| * Java universe, the only real function that this class | |||||
| * performs is to ensure that files have the correct "block" | |||||
| * size, or other tars will complain. | |||||
| * <p> | |||||
| * You should never have a need to access this class directly. | |||||
| * TarBuffers are created by Tar IO Streams. | |||||
| * | |||||
| * @author Timothy Gerard Endres <a href="mailto:time@ice.com">time@ice.com</a> | |||||
| */ | |||||
| public class TarBuffer { | |||||
| 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; | |||||
| while (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"); | |||||
| } | |||||
| if (this.currRecIdx > 0) { | |||||
| 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; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,182 @@ | |||||
| /* | |||||
| * The Apache Software License, Version 1.1 | |||||
| * | |||||
| * Copyright (c) 1999 The Apache Software Foundation. All rights | |||||
| * reserved. | |||||
| * | |||||
| * Redistribution and use in source and binary forms, with or without | |||||
| * modification, are permitted provided that the following conditions | |||||
| * are met: | |||||
| * | |||||
| * 1. Redistributions of source code must retain the above copyright | |||||
| * notice, this list of conditions and the following disclaimer. | |||||
| * | |||||
| * 2. Redistributions in binary form must reproduce the above copyright | |||||
| * notice, this list of conditions and the following disclaimer in | |||||
| * the documentation and/or other materials provided with the | |||||
| * distribution. | |||||
| * | |||||
| * 3. The end-user documentation included with the redistribution, if | |||||
| * any, must include the following acknowlegement: | |||||
| * "This product includes software developed by the | |||||
| * Apache Software Foundation (http://www.apache.org/)." | |||||
| * Alternately, this acknowlegement may appear in the software itself, | |||||
| * if and wherever such third-party acknowlegements normally appear. | |||||
| * | |||||
| * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software | |||||
| * Foundation" must not be used to endorse or promote products derived | |||||
| * from this software without prior written permission. For written | |||||
| * permission, please contact apache@apache.org. | |||||
| * | |||||
| * 5. Products derived from this software may not be called "Apache" | |||||
| * nor may "Apache" appear in their names without prior written | |||||
| * permission of the Apache Group. | |||||
| * | |||||
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED | |||||
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||||
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||||
| * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR | |||||
| * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||||
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||||
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF | |||||
| * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |||||
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||||
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT | |||||
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||||
| * SUCH DAMAGE. | |||||
| * ==================================================================== | |||||
| * | |||||
| * This software consists of voluntary contributions made by many | |||||
| * individuals on behalf of the Apache Software Foundation. For more | |||||
| * information on the Apache Software Foundation, please see | |||||
| * <http://www.apache.org/>. | |||||
| */ | |||||
| /* | |||||
| * This package is based on the work done by Timothy Gerard Endres | |||||
| * (time@ice.com) to whom the Ant project is very grateful for his great code. | |||||
| */ | |||||
| package org.apache.tools.tar; | |||||
| /** | |||||
| * This interface contains all the definitions used in the package. | |||||
| * | |||||
| * @author Timothy Gerard Endres <a href="mailto:time@ice.com">time@ice.com</a> | |||||
| * @author Stefano Mazzocchi <a href="mailto:stefano@apache.org">stefano@apache.org</a> | |||||
| */ | |||||
| public interface TarConstants { | |||||
| /** | |||||
| * The length of the name field in a header buffer. | |||||
| */ | |||||
| public static final int NAMELEN = 100; | |||||
| /** | |||||
| * 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 "; | |||||
| } | |||||
| @@ -0,0 +1,572 @@ | |||||
| /* | |||||
| * The Apache Software License, Version 1.1 | |||||
| * | |||||
| * Copyright (c) 1999 The Apache Software Foundation. All rights | |||||
| * reserved. | |||||
| * | |||||
| * Redistribution and use in source and binary forms, with or without | |||||
| * modification, are permitted provided that the following conditions | |||||
| * are met: | |||||
| * | |||||
| * 1. Redistributions of source code must retain the above copyright | |||||
| * notice, this list of conditions and the following disclaimer. | |||||
| * | |||||
| * 2. Redistributions in binary form must reproduce the above copyright | |||||
| * notice, this list of conditions and the following disclaimer in | |||||
| * the documentation and/or other materials provided with the | |||||
| * distribution. | |||||
| * | |||||
| * 3. The end-user documentation included with the redistribution, if | |||||
| * any, must include the following acknowlegement: | |||||
| * "This product includes software developed by the | |||||
| * Apache Software Foundation (http://www.apache.org/)." | |||||
| * Alternately, this acknowlegement may appear in the software itself, | |||||
| * if and wherever such third-party acknowlegements normally appear. | |||||
| * | |||||
| * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software | |||||
| * Foundation" must not be used to endorse or promote products derived | |||||
| * from this software without prior written permission. For written | |||||
| * permission, please contact apache@apache.org. | |||||
| * | |||||
| * 5. Products derived from this software may not be called "Apache" | |||||
| * nor may "Apache" appear in their names without prior written | |||||
| * permission of the Apache Group. | |||||
| * | |||||
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED | |||||
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||||
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||||
| * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR | |||||
| * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||||
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||||
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF | |||||
| * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |||||
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||||
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT | |||||
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||||
| * SUCH DAMAGE. | |||||
| * ==================================================================== | |||||
| * | |||||
| * This software consists of voluntary contributions made by many | |||||
| * individuals on behalf of the Apache Software Foundation. For more | |||||
| * information on the Apache Software Foundation, please see | |||||
| * <http://www.apache.org/>. | |||||
| */ | |||||
| /* | |||||
| * This package is based on the work done by Timothy Gerard Endres | |||||
| * (time@ice.com) to whom the Ant project is very grateful for his great code. | |||||
| */ | |||||
| package org.apache.tools.tar; | |||||
| import java.io.*; | |||||
| import java.util.*; | |||||
| /** | |||||
| * This class represents an entry in a Tar archive. It consists | |||||
| * of the entry's header, as well as the entry's File. Entries | |||||
| * can be instantiated in one of three ways, depending on how | |||||
| * they are to be used. | |||||
| * <p> | |||||
| * TarEntries that are created from the header bytes read from | |||||
| * an archive are instantiated with the TarEntry( byte[] ) | |||||
| * constructor. These entries will be used when extracting from | |||||
| * or listing the contents of an archive. These entries have their | |||||
| * header filled in using the header bytes. They also set the File | |||||
| * to null, since they reference an archive entry not a file. | |||||
| * <p> | |||||
| * TarEntries that are created from Files that are to be written | |||||
| * into an archive are instantiated with the TarEntry( File ) | |||||
| * constructor. These entries have their header filled in using | |||||
| * the File's information. They also keep a reference to the File | |||||
| * for convenience when writing entries. | |||||
| * <p> | |||||
| * Finally, TarEntries can be constructed from nothing but a name. | |||||
| * This allows the programmer to construct the entry by hand, for | |||||
| * instance when only an InputStream is available for writing to | |||||
| * the archive, and the header information is constructed from | |||||
| * other information. In this case the header fields are set to | |||||
| * defaults and the File is set to null. | |||||
| * | |||||
| * <p> | |||||
| * The C structure for a Tar Entry's header is: | |||||
| * <pre> | |||||
| * struct header { | |||||
| * char name[NAMSIZ]; | |||||
| * char mode[8]; | |||||
| * char uid[8]; | |||||
| * char gid[8]; | |||||
| * char size[12]; | |||||
| * char mtime[12]; | |||||
| * char chksum[8]; | |||||
| * char linkflag; | |||||
| * char linkname[NAMSIZ]; | |||||
| * char magic[8]; | |||||
| * char uname[TUNMLEN]; | |||||
| * char gname[TGNMLEN]; | |||||
| * char devmajor[8]; | |||||
| * char devminor[8]; | |||||
| * } header; | |||||
| * </pre> | |||||
| * | |||||
| * @author Timothy Gerard Endres <a href="mailto:time@ice.com">time@ice.com</a> | |||||
| * @author Stefano Mazzocchi <a href="mailto:stefano@apache.org">stefano@apache.org</a> | |||||
| */ | |||||
| public class TarEntry implements TarConstants { | |||||
| private StringBuffer name; /** The entry's name. */ | |||||
| private int mode; /** The entry's permission mode. */ | |||||
| private int userId; /** The entry's user id. */ | |||||
| private int groupId; /** The entry's group id. */ | |||||
| private long size; /** The entry's size. */ | |||||
| private long modTime; /** The entry's modification time. */ | |||||
| private int checkSum; /** The entry's checksum. */ | |||||
| private byte linkFlag; /** The entry's link flag. */ | |||||
| private StringBuffer linkName; /** The entry's link name. */ | |||||
| private StringBuffer magic; /** The entry's magic tag. */ | |||||
| private StringBuffer userName; /** The entry's user name. */ | |||||
| private StringBuffer groupName; /** The entry's group name. */ | |||||
| private int devMajor; /** The entry's major device number. */ | |||||
| private int devMinor; /** The entry's minor device number. */ | |||||
| private File file; /** The entry's file reference */ | |||||
| /** | |||||
| * Construct an empty entry and prepares the header values. | |||||
| */ | |||||
| private TarEntry () { | |||||
| this.magic = new StringBuffer(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(""); | |||||
| this.file = null; | |||||
| } | |||||
| /** | |||||
| * 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(); | |||||
| boolean isDir = name.endsWith("/"); | |||||
| this.checkSum = 0; | |||||
| this.devMajor = 0; | |||||
| this.devMinor = 0; | |||||
| this.name = new StringBuffer(name); | |||||
| this.mode = isDir ? 040755 : 0100644; | |||||
| this.linkFlag = isDir ? LF_DIR : LF_NORMAL; | |||||
| this.userId = 0; | |||||
| this.groupId = 0; | |||||
| this.size = 0; | |||||
| this.checkSum = 0; | |||||
| this.modTime = (new Date()).getTime() / 1000; | |||||
| this.linkName = new StringBuffer(""); | |||||
| this.userName = new StringBuffer(""); | |||||
| this.groupName = new StringBuffer(""); | |||||
| this.devMajor = 0; | |||||
| this.devMinor = 0; | |||||
| } | |||||
| /** | |||||
| * 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) { | |||||
| this(); | |||||
| 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 (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. | |||||
| while (name.startsWith("/")) { | |||||
| name = name.substring(1); | |||||
| } | |||||
| this.linkName = new StringBuffer(""); | |||||
| this.name = new StringBuffer(name); | |||||
| if (file.isDirectory()) { | |||||
| this.mode = 040755; | |||||
| this.linkFlag = LF_DIR; | |||||
| if (this.name.charAt(this.name.length() - 1) != '/') { | |||||
| this.name.append("/"); | |||||
| } | |||||
| } else { | |||||
| this.mode = 0100644; | |||||
| this.linkFlag = LF_NORMAL; | |||||
| } | |||||
| if (this.name.length() > NAMELEN) { | |||||
| throw new RuntimeException("file name '" + this.name | |||||
| + "' is too long ( > " | |||||
| + NAMELEN + " bytes)"); | |||||
| // UNDONE When File lets us get the userName, use it! | |||||
| } | |||||
| this.size = file.length(); | |||||
| this.modTime = file.lastModified() / 1000; | |||||
| this.checkSum = 0; | |||||
| this.devMajor = 0; | |||||
| this.devMinor = 0; | |||||
| } | |||||
| /** | |||||
| * 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) { | |||||
| this(); | |||||
| this.parseTarHeader(headerBuf); | |||||
| } | |||||
| /** | |||||
| * 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.getName().equals(it.getName()); | |||||
| } | |||||
| /** | |||||
| * 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.getName().startsWith(this.getName()); | |||||
| } | |||||
| /** | |||||
| * Get this entry's name. | |||||
| * | |||||
| * @return This entry's name. | |||||
| */ | |||||
| public String getName() { | |||||
| return this.name.toString(); | |||||
| } | |||||
| /** | |||||
| * Set this entry's name. | |||||
| * | |||||
| * @param name This entry's new name. | |||||
| */ | |||||
| public void setName(String name) { | |||||
| this.name = new StringBuffer(name); | |||||
| } | |||||
| /** | |||||
| * Get this entry's user id. | |||||
| * | |||||
| * @return This entry's user id. | |||||
| */ | |||||
| public int getUserId() { | |||||
| return this.userId; | |||||
| } | |||||
| /** | |||||
| * Set this entry's user id. | |||||
| * | |||||
| * @param userId This entry's new user id. | |||||
| */ | |||||
| public void setUserId(int userId) { | |||||
| this.userId = userId; | |||||
| } | |||||
| /** | |||||
| * Get this entry's group id. | |||||
| * | |||||
| * @return This entry's group id. | |||||
| */ | |||||
| public int getGroupId() { | |||||
| return this.groupId; | |||||
| } | |||||
| /** | |||||
| * Set this entry's group id. | |||||
| * | |||||
| * @param groupId This entry's new group id. | |||||
| */ | |||||
| public void setGroupId(int groupId) { | |||||
| this.groupId = groupId; | |||||
| } | |||||
| /** | |||||
| * Get this entry's user name. | |||||
| * | |||||
| * @return This entry's user name. | |||||
| */ | |||||
| public String getUserName() { | |||||
| return this.userName.toString(); | |||||
| } | |||||
| /** | |||||
| * Set this entry's user name. | |||||
| * | |||||
| * @param userName This entry's new user name. | |||||
| */ | |||||
| public void setUserName(String userName) { | |||||
| this.userName = new StringBuffer(userName); | |||||
| } | |||||
| /** | |||||
| * Get this entry's group name. | |||||
| * | |||||
| * @return This entry's group name. | |||||
| */ | |||||
| public String getGroupName() { | |||||
| return this.groupName.toString(); | |||||
| } | |||||
| /** | |||||
| * Set this entry's group name. | |||||
| * | |||||
| * @param groupName This entry's new group name. | |||||
| */ | |||||
| public void setGroupName(String groupName) { | |||||
| this.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.modTime = time / 1000; | |||||
| } | |||||
| /** | |||||
| * Set this entry's modification time. | |||||
| * | |||||
| * @param time This entry's new modification time. | |||||
| */ | |||||
| public void setModTime(Date time) { | |||||
| this.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.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.size; | |||||
| } | |||||
| /** | |||||
| * Set this entry's file size. | |||||
| * | |||||
| * @param size This entry's new file size. | |||||
| */ | |||||
| public void setSize(long size) { | |||||
| this.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.linkFlag == LF_DIR) { | |||||
| return true; | |||||
| } | |||||
| if (this.getName().endsWith("/")) { | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| /** | |||||
| * 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() { | |||||
| 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; | |||||
| } | |||||
| /** | |||||
| * Write an entry's header information to a header buffer. | |||||
| * | |||||
| * @param outbuf The tar entry header buffer to fill in. | |||||
| */ | |||||
| public void writeEntryHeader(byte[] outbuf) { | |||||
| int offset = 0; | |||||
| offset = TarUtils.getNameBytes(this.name, outbuf, offset, NAMELEN); | |||||
| offset = TarUtils.getOctalBytes(this.mode, outbuf, offset, MODELEN); | |||||
| offset = TarUtils.getOctalBytes(this.userId, outbuf, offset, UIDLEN); | |||||
| offset = TarUtils.getOctalBytes(this.groupId, outbuf, offset, GIDLEN); | |||||
| offset = TarUtils.getLongOctalBytes(this.size, outbuf, offset, SIZELEN); | |||||
| offset = TarUtils.getLongOctalBytes(this.modTime, outbuf, offset, MODTIMELEN); | |||||
| int csOffset = offset; | |||||
| for (int c = 0; c < CHKSUMLEN; ++c) { | |||||
| outbuf[offset++] = (byte) ' '; | |||||
| } | |||||
| outbuf[offset++] = this.linkFlag; | |||||
| offset = TarUtils.getNameBytes(this.linkName, outbuf, offset, NAMELEN); | |||||
| offset = TarUtils.getNameBytes(this.magic, outbuf, offset, MAGICLEN); | |||||
| offset = TarUtils.getNameBytes(this.userName, outbuf, offset, UNAMELEN); | |||||
| offset = TarUtils.getNameBytes(this.groupName, outbuf, offset, GNAMELEN); | |||||
| offset = TarUtils.getOctalBytes(this.devMajor, outbuf, offset, DEVLEN); | |||||
| offset = TarUtils.getOctalBytes(this.devMinor, outbuf, offset, DEVLEN); | |||||
| while (offset < outbuf.length) { | |||||
| outbuf[offset++] = 0; | |||||
| } | |||||
| long checkSum = TarUtils.computeCheckSum(outbuf); | |||||
| TarUtils.getCheckSumOctalBytes(checkSum, outbuf, csOffset, CHKSUMLEN); | |||||
| } | |||||
| /** | |||||
| * Parse an entry's header information from a header buffer. | |||||
| * | |||||
| * @param header The tar entry header buffer to get information from. | |||||
| */ | |||||
| public void parseTarHeader(byte[] header) { | |||||
| int offset = 0; | |||||
| this.name = TarUtils.parseName(header, offset, NAMELEN); | |||||
| offset += NAMELEN; | |||||
| this.mode = (int) TarUtils.parseOctal(header, offset, MODELEN); | |||||
| offset += MODELEN; | |||||
| this.userId = (int) TarUtils.parseOctal(header, offset, UIDLEN); | |||||
| offset += UIDLEN; | |||||
| this.groupId = (int) TarUtils.parseOctal(header, offset, GIDLEN); | |||||
| offset += GIDLEN; | |||||
| this.size = TarUtils.parseOctal(header, offset, SIZELEN); | |||||
| offset += SIZELEN; | |||||
| this.modTime = TarUtils.parseOctal(header, offset, MODTIMELEN); | |||||
| offset += MODTIMELEN; | |||||
| this.checkSum = (int) TarUtils.parseOctal(header, offset, CHKSUMLEN); | |||||
| offset += CHKSUMLEN; | |||||
| this.linkFlag = header[offset++]; | |||||
| this.linkName = TarUtils.parseName(header, offset, NAMELEN); | |||||
| offset += NAMELEN; | |||||
| this.magic = TarUtils.parseName(header, offset, MAGICLEN); | |||||
| offset += MAGICLEN; | |||||
| this.userName = TarUtils.parseName(header, offset, UNAMELEN); | |||||
| offset += UNAMELEN; | |||||
| this.groupName = TarUtils.parseName(header, offset, GNAMELEN); | |||||
| offset += GNAMELEN; | |||||
| this.devMajor = (int) TarUtils.parseOctal(header, offset, DEVLEN); | |||||
| offset += DEVLEN; | |||||
| this.devMinor = (int) TarUtils.parseOctal(header, offset, DEVLEN); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,419 @@ | |||||
| /* | |||||
| * The Apache Software License, Version 1.1 | |||||
| * | |||||
| * Copyright (c) 1999 The Apache Software Foundation. All rights | |||||
| * reserved. | |||||
| * | |||||
| * Redistribution and use in source and binary forms, with or without | |||||
| * modification, are permitted provided that the following conditions | |||||
| * are met: | |||||
| * | |||||
| * 1. Redistributions of source code must retain the above copyright | |||||
| * notice, this list of conditions and the following disclaimer. | |||||
| * | |||||
| * 2. Redistributions in binary form must reproduce the above copyright | |||||
| * notice, this list of conditions and the following disclaimer in | |||||
| * the documentation and/or other materials provided with the | |||||
| * distribution. | |||||
| * | |||||
| * 3. The end-user documentation included with the redistribution, if | |||||
| * any, must include the following acknowlegement: | |||||
| * "This product includes software developed by the | |||||
| * Apache Software Foundation (http://www.apache.org/)." | |||||
| * Alternately, this acknowlegement may appear in the software itself, | |||||
| * if and wherever such third-party acknowlegements normally appear. | |||||
| * | |||||
| * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software | |||||
| * Foundation" must not be used to endorse or promote products derived | |||||
| * from this software without prior written permission. For written | |||||
| * permission, please contact apache@apache.org. | |||||
| * | |||||
| * 5. Products derived from this software may not be called "Apache" | |||||
| * nor may "Apache" appear in their names without prior written | |||||
| * permission of the Apache Group. | |||||
| * | |||||
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED | |||||
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||||
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||||
| * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR | |||||
| * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||||
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||||
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF | |||||
| * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |||||
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||||
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT | |||||
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||||
| * SUCH DAMAGE. | |||||
| * ==================================================================== | |||||
| * | |||||
| * This software consists of voluntary contributions made by many | |||||
| * individuals on behalf of the Apache Software Foundation. For more | |||||
| * information on the Apache Software Foundation, please see | |||||
| * <http://www.apache.org/>. | |||||
| */ | |||||
| /* | |||||
| * This package is based on the work done by Timothy Gerard Endres | |||||
| * (time@ice.com) to whom the Ant project is very grateful for his great code. | |||||
| */ | |||||
| package org.apache.tools.tar; | |||||
| import java.io.*; | |||||
| /** | |||||
| * 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(). | |||||
| * | |||||
| * @author Timothy Gerard Endres <a href="mailto:time@ice.com">time@ice.com</a> | |||||
| * @author Stefano Mazzocchi <a href="mailto:stefano@apache.org">stefano@apache.org</a> | |||||
| */ | |||||
| public class TarInputStream extends FilterInputStream { | |||||
| protected boolean debug; | |||||
| protected boolean hasHitEOF; | |||||
| protected int entrySize; | |||||
| protected int entryOffset; | |||||
| protected byte[] oneBuf; | |||||
| protected byte[] readBuf; | |||||
| protected TarBuffer buffer; | |||||
| protected TarEntry currEntry; | |||||
| 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; | |||||
| } | |||||
| /** | |||||
| * Sets the debugging flag. | |||||
| * | |||||
| * @param debugF True to turn on debugging. | |||||
| */ | |||||
| public void setDebug(boolean debug) { | |||||
| this.debug = 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 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. | |||||
| */ | |||||
| public void skip(int 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]; | |||||
| for (int num = numToSkip; num > 0; ) { | |||||
| int numRead = this.read(skipBuf, 0, | |||||
| (num > skipBuf.length ? skipBuf.length | |||||
| : num)); | |||||
| if (numRead == -1) { | |||||
| break; | |||||
| } | |||||
| num -= numRead; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 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 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) { | |||||
| int 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 { | |||||
| this.currEntry = new TarEntry(headerBuf); | |||||
| if (!(headerBuf[257] == 'u' && headerBuf[258] == 's' | |||||
| && headerBuf[259] == 't' && headerBuf[260] == 'a' | |||||
| && headerBuf[261] == 'r')) { | |||||
| this.entrySize = 0; | |||||
| this.entryOffset = 0; | |||||
| this.currEntry = null; | |||||
| throw new IOException("bad header in block " | |||||
| + this.buffer.getCurrentBlockNum() | |||||
| + " record " | |||||
| + this.buffer.getCurrentRecordNum() | |||||
| + ", " + | |||||
| "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])); | |||||
| } | |||||
| if (this.debug) { | |||||
| System.err.println("TarInputStream: SET CURRENTRY '" | |||||
| + this.currEntry.getName() | |||||
| + "' size = " | |||||
| + this.currEntry.getSize()); | |||||
| } | |||||
| this.entryOffset = 0; | |||||
| // REVIEW How do we resolve this discrepancy?! | |||||
| this.entrySize = (int) this.currEntry.getSize(); | |||||
| } | |||||
| 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 = (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; | |||||
| } | |||||
| while (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]; | |||||
| while (true) { | |||||
| int numRead = this.read(buf, 0, buf.length); | |||||
| if (numRead == -1) { | |||||
| break; | |||||
| } | |||||
| out.write(buf, 0, numRead); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,314 @@ | |||||
| /* | |||||
| * The Apache Software License, Version 1.1 | |||||
| * | |||||
| * Copyright (c) 1999 The Apache Software Foundation. All rights | |||||
| * reserved. | |||||
| * | |||||
| * Redistribution and use in source and binary forms, with or without | |||||
| * modification, are permitted provided that the following conditions | |||||
| * are met: | |||||
| * | |||||
| * 1. Redistributions of source code must retain the above copyright | |||||
| * notice, this list of conditions and the following disclaimer. | |||||
| * | |||||
| * 2. Redistributions in binary form must reproduce the above copyright | |||||
| * notice, this list of conditions and the following disclaimer in | |||||
| * the documentation and/or other materials provided with the | |||||
| * distribution. | |||||
| * | |||||
| * 3. The end-user documentation included with the redistribution, if | |||||
| * any, must include the following acknowlegement: | |||||
| * "This product includes software developed by the | |||||
| * Apache Software Foundation (http://www.apache.org/)." | |||||
| * Alternately, this acknowlegement may appear in the software itself, | |||||
| * if and wherever such third-party acknowlegements normally appear. | |||||
| * | |||||
| * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software | |||||
| * Foundation" must not be used to endorse or promote products derived | |||||
| * from this software without prior written permission. For written | |||||
| * permission, please contact apache@apache.org. | |||||
| * | |||||
| * 5. Products derived from this software may not be called "Apache" | |||||
| * nor may "Apache" appear in their names without prior written | |||||
| * permission of the Apache Group. | |||||
| * | |||||
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED | |||||
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||||
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||||
| * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR | |||||
| * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||||
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||||
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF | |||||
| * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |||||
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||||
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT | |||||
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||||
| * SUCH DAMAGE. | |||||
| * ==================================================================== | |||||
| * | |||||
| * This software consists of voluntary contributions made by many | |||||
| * individuals on behalf of the Apache Software Foundation. For more | |||||
| * information on the Apache Software Foundation, please see | |||||
| * <http://www.apache.org/>. | |||||
| */ | |||||
| /* | |||||
| * This package is based on the work done by Timothy Gerard Endres | |||||
| * (time@ice.com) to whom the Ant project is very grateful for his great code. | |||||
| */ | |||||
| package org.apache.tools.tar; | |||||
| import java.io.*; | |||||
| /** | |||||
| * 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(). | |||||
| * | |||||
| * @author Timothy Gerard Endres <a href="mailto:time@ice.com">time@ice.com</a> | |||||
| */ | |||||
| public class TarOutputStream extends FilterOutputStream { | |||||
| protected boolean debug; | |||||
| protected int currSize; | |||||
| protected int currBytes; | |||||
| protected byte[] oneBuf; | |||||
| protected byte[] recordBuf; | |||||
| protected int assemLen; | |||||
| protected byte[] assemBuf; | |||||
| protected TarBuffer buffer; | |||||
| public TarOutputStream(OutputStream os) { | |||||
| this(os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE); | |||||
| } | |||||
| public TarOutputStream(OutputStream os, int blockSize) { | |||||
| this(os, blockSize, TarBuffer.DEFAULT_RCDSIZE); | |||||
| } | |||||
| public TarOutputStream(OutputStream os, int blockSize, int recordSize) { | |||||
| super(os); | |||||
| this.buffer = new TarBuffer(os, blockSize, recordSize); | |||||
| this.debug = false; | |||||
| this.assemLen = 0; | |||||
| this.assemBuf = new byte[recordSize]; | |||||
| this.recordBuf = new byte[recordSize]; | |||||
| this.oneBuf = new byte[1]; | |||||
| } | |||||
| /** | |||||
| * Sets the debugging flag. | |||||
| * | |||||
| * @param debugF True to turn on debugging. | |||||
| */ | |||||
| public void setDebug(boolean debugF) { | |||||
| this.debug = debugF; | |||||
| } | |||||
| /** | |||||
| * Sets the debugging flag in this stream's TarBuffer. | |||||
| * | |||||
| * @param debugF True to turn on debugging. | |||||
| */ | |||||
| public void setBufferDebug(boolean debug) { | |||||
| this.buffer.setDebug(debug); | |||||
| } | |||||
| /** | |||||
| * Ends the TAR archive without closing the underlying OutputStream. | |||||
| * The result is that the EOF record of nulls is written. | |||||
| */ | |||||
| public void finish() throws IOException { | |||||
| this.writeEOFRecord(); | |||||
| } | |||||
| /** | |||||
| * Ends the TAR archive and closes the underlying OutputStream. | |||||
| * This means that finish() is called followed by calling the | |||||
| * TarBuffer's close(). | |||||
| */ | |||||
| public void close() throws IOException { | |||||
| this.finish(); | |||||
| this.buffer.close(); | |||||
| } | |||||
| /** | |||||
| * Get the record size being used by this stream's TarBuffer. | |||||
| * | |||||
| * @return The TarBuffer record size. | |||||
| */ | |||||
| public int getRecordSize() { | |||||
| return this.buffer.getRecordSize(); | |||||
| } | |||||
| /** | |||||
| * Put an entry on the output stream. This writes the entry's | |||||
| * header record and positions the output stream for writing | |||||
| * the contents of the entry. Once this method is called, the | |||||
| * stream is ready for calls to write() to write the entry's | |||||
| * contents. Once the contents are written, closeEntry() | |||||
| * <B>MUST</B> be called to ensure that all buffered data | |||||
| * is completely written to the output stream. | |||||
| * | |||||
| * @param entry The TarEntry to be written to the archive. | |||||
| */ | |||||
| public void putNextEntry(TarEntry entry) throws IOException { | |||||
| entry.writeEntryHeader(this.recordBuf); | |||||
| this.buffer.writeRecord(this.recordBuf); | |||||
| this.currBytes = 0; | |||||
| if (entry.isDirectory()) { | |||||
| this.currSize = 0; | |||||
| } else { | |||||
| this.currSize = (int) 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 { | |||||
| 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) | |||||
| // | |||||
| while (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); | |||||
| int 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); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,234 @@ | |||||
| /* | |||||
| * The Apache Software License, Version 1.1 | |||||
| * | |||||
| * Copyright (c) 1999 The Apache Software Foundation. All rights | |||||
| * reserved. | |||||
| * | |||||
| * Redistribution and use in source and binary forms, with or without | |||||
| * modification, are permitted provided that the following conditions | |||||
| * are met: | |||||
| * | |||||
| * 1. Redistributions of source code must retain the above copyright | |||||
| * notice, this list of conditions and the following disclaimer. | |||||
| * | |||||
| * 2. Redistributions in binary form must reproduce the above copyright | |||||
| * notice, this list of conditions and the following disclaimer in | |||||
| * the documentation and/or other materials provided with the | |||||
| * distribution. | |||||
| * | |||||
| * 3. The end-user documentation included with the redistribution, if | |||||
| * any, must include the following acknowlegement: | |||||
| * "This product includes software developed by the | |||||
| * Apache Software Foundation (http://www.apache.org/)." | |||||
| * Alternately, this acknowlegement may appear in the software itself, | |||||
| * if and wherever such third-party acknowlegements normally appear. | |||||
| * | |||||
| * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software | |||||
| * Foundation" must not be used to endorse or promote products derived | |||||
| * from this software without prior written permission. For written | |||||
| * permission, please contact apache@apache.org. | |||||
| * | |||||
| * 5. Products derived from this software may not be called "Apache" | |||||
| * nor may "Apache" appear in their names without prior written | |||||
| * permission of the Apache Group. | |||||
| * | |||||
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED | |||||
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||||
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||||
| * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR | |||||
| * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||||
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||||
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF | |||||
| * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |||||
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||||
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT | |||||
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||||
| * SUCH DAMAGE. | |||||
| * ==================================================================== | |||||
| * | |||||
| * This software consists of voluntary contributions made by many | |||||
| * individuals on behalf of the Apache Software Foundation. For more | |||||
| * information on the Apache Software Foundation, please see | |||||
| * <http://www.apache.org/>. | |||||
| */ | |||||
| /* | |||||
| * This package is based on the work done by Timothy Gerard Endres | |||||
| * (time@ice.com) to whom the Ant project is very grateful for his great code. | |||||
| */ | |||||
| package org.apache.tools.tar; | |||||
| /** | |||||
| * This class provides static utility methods to work with byte streams. | |||||
| * | |||||
| * @author Timothy Gerard Endres <a href="mailto:time@ice.com">time@ice.com</a> | |||||
| * @author Stefano Mazzocchi <a href="mailto:stefano@apache.org">stefano@apache.org</a> | |||||
| */ | |||||
| public class TarUtils { | |||||
| /** | |||||
| * 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) { | |||||
| 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 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) { | |||||
| 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; | |||||
| } | |||||
| /** | |||||
| * Determine the number of bytes in an entry name. | |||||
| * | |||||
| * @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 number of bytes in a header's entry name. | |||||
| */ | |||||
| 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]; | |||||
| 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) { | |||||
| getOctalBytes(value, buf, offset, length); | |||||
| buf[offset + length - 1] = (byte) ' '; | |||||
| buf[offset + length - 2] = 0; | |||||
| return offset + length; | |||||
| } | |||||
| /** | |||||
| * Compute the checksum of a tar entry header. | |||||
| * | |||||
| * @param buf The tar entry's header buffer. | |||||
| * @return The computed checksum. | |||||
| */ | |||||
| public static long computeCheckSum(byte[] buf) { | |||||
| long sum = 0; | |||||
| for (int i = 0; i < buf.length; ++i) { | |||||
| sum += 255 & buf[i]; | |||||
| } | |||||
| return sum; | |||||
| } | |||||
| } | |||||