You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ant.apache.org by st...@locus.apache.org on 2000/02/10 19:04:30 UTC

cvs commit: jakarta-ant/src/main/org/apache/tools/tar TarBuffer.java TarConstants.java TarEntry.java TarInputStream.java TarOutputStream.java TarUtils.java

stefano     00/02/10 10:04:30

  Modified:    .        bootstrap.bat bootstrap.sh build.xml
               src/main/org/apache/tools/ant/taskdefs Zip.java
                        defaults.properties
  Added:       src/main/org/apache/tools/ant/taskdefs Tar.java
               src/main/org/apache/tools/tar TarBuffer.java
                        TarConstants.java TarEntry.java TarInputStream.java
                        TarOutputStream.java TarUtils.java
  Log:
  added Tar task + implementation classes
  
  Revision  Changes    Path
  1.5       +9 -5      jakarta-ant/bootstrap.bat
  
  Index: bootstrap.bat
  ===================================================================
  RCS file: /home/cvs/jakarta-ant/bootstrap.bat,v
  retrieving revision 1.4
  retrieving revision 1.5
  diff -u -r1.4 -r1.5
  --- bootstrap.bat	2000/02/08 03:43:09	1.4
  +++ bootstrap.bat	2000/02/10 18:04:28	1.5
  @@ -2,7 +2,7 @@
   echo BOOTSTRAPPING ANT DISTRIBUTION
   
   set C=%CLASSPATH%;lib/xml.jar
  -set SRCDIR=src\main\org\apache\tools\ant
  +set SRCDIR=src\main\org\apache\tools
   set TMPDIR=tmp
   
   if "%OS%" == "Windows_NT" goto nt
  @@ -18,14 +18,18 @@
   
   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
   set C=%TMPDIR%;%C%
   
   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
   
  
  
  
  1.8       +4 -3      jakarta-ant/bootstrap.sh
  
  Index: bootstrap.sh
  ===================================================================
  RCS file: /home/cvs/jakarta-ant/bootstrap.sh,v
  retrieving revision 1.7
  retrieving revision 1.8
  diff -u -r1.7 -r1.8
  --- bootstrap.sh	2000/02/08 23:45:48	1.7
  +++ bootstrap.sh	2000/02/10 18:04:28	1.8
  @@ -2,7 +2,7 @@
     . $HOME/.antrc
   fi
   
  -SRCDIR=src/main/org/apache/tools/ant
  +SRCDIR=src/main/org/apache/tools
   CLASSDIR=classes
   CLASSPATH=${CLASSPATH}:${JAVA_HOME}/lib/classes.zip:${JAVA_HOME}/lib/tools.jar
   CLASSPATH=${CLASSPATH}:lib/xml.jar:src/main:${CLASSDIR}
  @@ -12,8 +12,9 @@
   export 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/parser.properties ${CLASSDIR}/org/apache/tools/ant
  
  
  
  1.8       +18 -5     jakarta-ant/build.xml
  
  Index: build.xml
  ===================================================================
  RCS file: /home/cvs/jakarta-ant/build.xml,v
  retrieving revision 1.7
  retrieving revision 1.8
  diff -u -r1.7 -r1.8
  --- build.xml	2000/02/06 21:29:23	1.7
  +++ build.xml	2000/02/10 18:04:28	1.8
  @@ -7,6 +7,7 @@
   <project name="Ant" default="main" basedir=".">
   
     <target name="init">
  +    <property name="Name" value="Ant"/>
       <property name="name" value="ant"/>
       <property name="version" value="1.0-rc1"/>
   
  @@ -22,7 +23,7 @@
       <property name="dist.dir" value="../dist/ant"/>
      
       <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="build.compiler" value="classic"/>
  @@ -79,9 +80,8 @@
                destdir="${build.javadocs}"
                author="true"
                version="true"
  -             use="true"
  -             windowtitle="${name} API"
  -             doctitle="${name}"
  +             windowtitle="${Name} API"
  +             doctitle="${Name}"
                bottom="Copyright &#169; 2000 Apache Software Foundation. All Rights Reserved."
       />
     </target>
  @@ -111,10 +111,23 @@
        <copyfile src="README" dest="${dist.dir}/README"/>
        <copyfile src="TODO" dest="${dist.dir}/TODO"/>
        <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>
   
  +  <!-- =================================================================== -->
  +  <!-- 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                                           -->
     <!-- =================================================================== -->
  
  
  
  1.4       +7 -6      jakarta-ant/src/main/org/apache/tools/ant/taskdefs/Zip.java
  
  Index: Zip.java
  ===================================================================
  RCS file: /home/cvs/jakarta-ant/src/main/org/apache/tools/ant/taskdefs/Zip.java,v
  retrieving revision 1.3
  retrieving revision 1.4
  diff -u -r1.3 -r1.4
  --- Zip.java	2000/02/09 20:51:48	1.3
  +++ Zip.java	2000/02/10 18:04:29	1.4
  @@ -76,16 +76,17 @@
       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) {
           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) {
           baseDir = project.resolveFile(baseDirname);
       }
  
  
  
  1.4       +1 -0      jakarta-ant/src/main/org/apache/tools/ant/taskdefs/defaults.properties
  
  Index: defaults.properties
  ===================================================================
  RCS file: /home/cvs/jakarta-ant/src/main/org/apache/tools/ant/taskdefs/defaults.properties,v
  retrieving revision 1.3
  retrieving revision 1.4
  diff -u -r1.3 -r1.4
  --- defaults.properties	2000/01/27 03:51:14	1.3
  +++ defaults.properties	2000/02/10 18:04:29	1.4
  @@ -22,5 +22,6 @@
   taskdef=org.apache.tools.ant.taskdefs.Taskdef
   ant=org.apache.tools.ant.taskdefs.Ant
   exec=org.apache.tools.ant.taskdefs.Exec
  +tar=org.apache.tools.ant.taskdefs.Tar
   # remove the task below once everyone has migrated
   javadoc2=org.apache.tools.ant.taskdefs.Javadoc
  
  
  
  1.1                  jakarta-ant/src/main/org/apache/tools/ant/taskdefs/Tar.java
  
  Index: Tar.java
  ===================================================================
  /*
   * 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();
      }
  }
  
  
  
  1.1                  jakarta-ant/src/main/org/apache/tools/tar/TarBuffer.java
  
  Index: TarBuffer.java
  ===================================================================
  /*
   * 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;
              } 
          } 
      } 
  }
  
  
  
  1.1                  jakarta-ant/src/main/org/apache/tools/tar/TarConstants.java
  
  Index: TarConstants.java
  ===================================================================
  /*
   * 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  ";
  
  }
  
  
  
  1.1                  jakarta-ant/src/main/org/apache/tools/tar/TarEntry.java
  
  Index: TarEntry.java
  ===================================================================
  /*
   * 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);
      }        
  }       
  
  
  
  1.1                  jakarta-ant/src/main/org/apache/tools/tar/TarInputStream.java
  
  Index: TarInputStream.java
  ===================================================================
  /*
   * 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);
          } 
      } 
  }
  
  
  
  1.1                  jakarta-ant/src/main/org/apache/tools/tar/TarOutputStream.java
  
  Index: TarOutputStream.java
  ===================================================================
  /*
   * 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);
      } 
  }
  
  
  
  
  
  1.1                  jakarta-ant/src/main/org/apache/tools/tar/TarUtils.java
  
  Index: TarUtils.java
  ===================================================================
  /*
   * 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;
      }    
  }