You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@avalon.apache.org by do...@apache.org on 2002/03/21 14:34:46 UTC

cvs commit: jakarta-avalon-excalibur/tar/src/java/org/apache/avalon/excalibur/tar TarBuffer.java TarConstants.java TarEntry.java TarInputStream.java TarOutputStream.java TarUtils.java

donaldp     02/03/21 05:34:46

  Added:       tar/src/java/org/apache/avalon/excalibur/tar TarBuffer.java
                        TarConstants.java TarEntry.java TarInputStream.java
                        TarOutputStream.java TarUtils.java
  Log:
  Add the tar classes previously of ant
  
  Revision  Changes    Path
  1.1                  jakarta-avalon-excalibur/tar/src/java/org/apache/avalon/excalibur/tar/TarBuffer.java
  
  Index: TarBuffer.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.avalon.excalibur.tar;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  
  /**
   * 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 <a href="mailto:time@ice.com">Timothy Gerard Endres</a>
   * @author <a href="mailto:peter@apache.org">Peter Donald</a>
   * @version $Revision: 1.1 $ $Date: 2002/03/21 13:34:46 $
   */
  class TarBuffer
  {
      public static final int DEFAULT_RECORDSIZE = ( 512 );
      public static final int DEFAULT_BLOCKSIZE = ( DEFAULT_RECORDSIZE * 20 );
  
      private byte[] m_blockBuffer;
      private int m_blockSize;
      private int m_currBlkIdx;
      private int m_currRecIdx;
      private boolean m_debug;
  
      private InputStream m_input;
      private OutputStream m_output;
      private int m_recordSize;
      private int m_recsPerBlock;
  
      public TarBuffer( final InputStream input )
      {
          this( input, TarBuffer.DEFAULT_BLOCKSIZE );
      }
  
      public TarBuffer( final InputStream input, final int blockSize )
      {
          this( input, blockSize, TarBuffer.DEFAULT_RECORDSIZE );
      }
  
      public TarBuffer( final InputStream input,
                        final int blockSize,
                        final int recordSize )
      {
          m_input = input;
          initialize( blockSize, recordSize );
      }
  
      public TarBuffer( final OutputStream output )
      {
          this( output, TarBuffer.DEFAULT_BLOCKSIZE );
      }
  
      public TarBuffer( final OutputStream output, final int blockSize )
      {
          this( output, blockSize, TarBuffer.DEFAULT_RECORDSIZE );
      }
  
      public TarBuffer( final OutputStream output,
                        final int blockSize,
                        final int recordSize )
      {
          m_output = output;
          initialize( blockSize, recordSize );
      }
  
      /**
       * Set the debugging flag for the buffer.
       *
       * @param debug If true, print debugging output.
       */
      public void setDebug( final boolean debug )
      {
          m_debug = debug;
      }
  
      /**
       * Get the TAR Buffer's block size. Blocks consist of multiple records.
       *
       * @return The BlockSize value
       */
      public int getBlockSize()
      {
          return m_blockSize;
      }
  
      /**
       * Get the current block number, zero based.
       *
       * @return The current zero based block number.
       */
      public int getCurrentBlockNum()
      {
          return m_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 m_currRecIdx - 1;
      }
  
      /**
       * Get the TAR Buffer's record size.
       *
       * @return The RecordSize value
       */
      public int getRecordSize()
      {
          return m_recordSize;
      }
  
      /**
       * 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.
       * @return The EOFRecord value
       */
      public boolean isEOFRecord( final byte[] record )
      {
          final int size = getRecordSize();
          for( int i = 0; i < size; ++i )
          {
              if( record[ i ] != 0 )
              {
                  return false;
              }
          }
  
          return true;
      }
  
      /**
       * Close the TarBuffer. If this is an output buffer, also flush the current
       * block before closing.
       */
      public void close()
          throws IOException
      {
          if( m_debug )
          {
              debug( "TarBuffer.closeBuffer()." );
          }
  
          if( null != m_output )
          {
              flushBlock();
  
              if( m_output != System.out && m_output != System.err )
              {
                  m_output.close();
                  m_output = null;
              }
          }
          else if( m_input != null )
          {
              if( m_input != System.in )
              {
                  m_input.close();
                  m_input = null;
              }
          }
      }
  
      /**
       * Read a record from the input stream and return the data.
       *
       * @return The record data.
       * @exception IOException Description of Exception
       */
      public byte[] readRecord()
          throws IOException
      {
          if( m_debug )
          {
              final String message = "ReadRecord: recIdx = " + m_currRecIdx +
                  " blkIdx = " + m_currBlkIdx;
              debug( message );
          }
  
          if( null == m_input )
          {
              final String message = "reading from an output buffer";
              throw new IOException( message );
          }
  
          if( m_currRecIdx >= m_recsPerBlock )
          {
              if( !readBlock() )
              {
                  return null;
              }
          }
  
          final byte[] result = new byte[ m_recordSize ];
          System.arraycopy( m_blockBuffer,
                            ( m_currRecIdx * m_recordSize ),
                            result,
                            0,
                            m_recordSize );
  
          m_currRecIdx++;
  
          return result;
      }
  
      /**
       * Skip over a record on the input stream.
       */
      public void skipRecord()
          throws IOException
      {
          if( m_debug )
          {
              final String message = "SkipRecord: recIdx = " + m_currRecIdx +
                  " blkIdx = " + m_currBlkIdx;
              debug( message );
          }
  
          if( null == m_input )
          {
              final String message = "reading (via skip) from an output buffer";
              throw new IOException( message );
          }
  
          if( m_currRecIdx >= m_recsPerBlock )
          {
              if( !readBlock() )
              {
                  return;// UNDONE
              }
          }
  
          m_currRecIdx++;
      }
  
      /**
       * Write an archive record to the archive.
       *
       * @param record The record data to write to the archive.
       */
      public void writeRecord( final byte[] record )
          throws IOException
      {
          if( m_debug )
          {
              final String message = "WriteRecord: recIdx = " + m_currRecIdx +
                  " blkIdx = " + m_currBlkIdx;
              debug( message );
          }
  
          if( null == m_output )
          {
              final String message = "writing to an input buffer";
              throw new IOException( message );
          }
  
          if( record.length != m_recordSize )
          {
              final String message = "record to write has length '" +
                  record.length + "' which is not the record size of '" +
                  m_recordSize + "'";
              throw new IOException( message );
          }
  
          if( m_currRecIdx >= m_recsPerBlock )
          {
              writeBlock();
          }
  
          System.arraycopy( record,
                            0,
                            m_blockBuffer,
                            ( m_currRecIdx * m_recordSize ),
                            m_recordSize );
  
          m_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 buffer The buffer containing the record data to write.
       * @param offset The offset of the record data within buf.
       */
      public void writeRecord( final byte[] buffer, final int offset )
          throws IOException
      {
          if( m_debug )
          {
              final String message = "WriteRecord: recIdx = " + m_currRecIdx +
                  " blkIdx = " + m_currBlkIdx;
              debug( message );
          }
  
          if( null == m_output )
          {
              final String message = "writing to an input buffer";
              throw new IOException( message );
          }
  
          if( ( offset + m_recordSize ) > buffer.length )
          {
              final String message = "record has length '" + buffer.length +
                  "' with offset '" + offset + "' which is less than the record size of '" +
                  m_recordSize + "'";
              throw new IOException( message );
          }
  
          if( m_currRecIdx >= m_recsPerBlock )
          {
              writeBlock();
          }
  
          System.arraycopy( buffer,
                            offset,
                            m_blockBuffer,
                            ( m_currRecIdx * m_recordSize ),
                            m_recordSize );
  
          m_currRecIdx++;
      }
  
      /**
       * Flush the current data block if it has any data in it.
       */
      private void flushBlock()
          throws IOException
      {
          if( m_debug )
          {
              final String message = "TarBuffer.flushBlock() called.";
              debug( message );
          }
  
          if( m_output == null )
          {
              final String message = "writing to an input buffer";
              throw new IOException( message );
          }
  
          if( m_currRecIdx > 0 )
          {
              writeBlock();
          }
      }
  
      /**
       * Initialization common to all constructors.
       */
      private void initialize( final int blockSize, final int recordSize )
      {
          m_debug = false;
          m_blockSize = blockSize;
          m_recordSize = recordSize;
          m_recsPerBlock = ( m_blockSize / m_recordSize );
          m_blockBuffer = new byte[ m_blockSize ];
  
          if( null != m_input )
          {
              m_currBlkIdx = -1;
              m_currRecIdx = m_recsPerBlock;
          }
          else
          {
              m_currBlkIdx = 0;
              m_currRecIdx = 0;
          }
      }
  
      /**
       * @return false if End-Of-File, else true
       */
      private boolean readBlock()
          throws IOException
      {
          if( m_debug )
          {
              final String message = "ReadBlock: blkIdx = " + m_currBlkIdx;
              debug( message );
          }
  
          if( null == m_input )
          {
              final String message = "reading from an output buffer";
              throw new IOException( message );
          }
  
          m_currRecIdx = 0;
  
          int offset = 0;
          int bytesNeeded = m_blockSize;
  
          while( bytesNeeded > 0 )
          {
              final long numBytes = m_input.read( m_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 != m_blockSize )
              {
                  if( m_debug )
                  {
                      System.err.println( "ReadBlock: INCOMPLETE READ "
                                          + numBytes + " of " + m_blockSize
                                          + " bytes read." );
                  }
              }
          }
  
          m_currBlkIdx++;
  
          return true;
      }
  
      /**
       * Write a TarBuffer block to the archive.
       *
       * @exception IOException Description of Exception
       */
      private void writeBlock()
          throws IOException
      {
          if( m_debug )
          {
              final String message = "WriteBlock: blkIdx = " + m_currBlkIdx;
              debug( message );
          }
  
          if( null == m_output )
          {
              final String message = "writing to an input buffer";
              throw new IOException( message );
          }
  
          m_output.write( m_blockBuffer, 0, m_blockSize );
          m_output.flush();
  
          m_currRecIdx = 0;
          m_currBlkIdx++;
      }
  
      protected void debug( final String message )
      {
          if( m_debug )
          {
              System.err.println( message );
          }
      }
  }
  
  
  
  1.1                  jakarta-avalon-excalibur/tar/src/java/org/apache/avalon/excalibur/tar/TarConstants.java
  
  Index: TarConstants.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.avalon.excalibur.tar;
  
  /**
   * This interface contains all the definitions used in the package.
   *
   * @author <a href="mailto:time@ice.com">Timothy Gerard Endres</a>
   * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
   * @version $Revision: 1.1 $ $Date: 2002/03/21 13:34:46 $
   */
  interface TarConstants
  {
      /**
       * The length of the mode field in a header buffer.
       */
      int MODELEN = 8;
  
      /**
       * The length of the user id field in a header buffer.
       */
      int UIDLEN = 8;
  
      /**
       * The length of the group id field in a header buffer.
       */
      int GIDLEN = 8;
  
      /**
       * The length of the checksum field in a header buffer.
       */
      int CHKSUMLEN = 8;
  
      /**
       * The length of the size field in a header buffer.
       */
      int SIZELEN = 12;
  
      /**
       * The length of the magic field in a header buffer.
       */
      int MAGICLEN = 8;
  
      /**
       * The length of the modification time field in a header buffer.
       */
      int MODTIMELEN = 12;
  
      /**
       * The length of the user name field in a header buffer.
       */
      int UNAMELEN = 32;
  
      /**
       * The length of the group name field in a header buffer.
       */
      int GNAMELEN = 32;
  
      /**
       * The length of the devices field in a header buffer.
       */
      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.
       */
      byte LF_OLDNORM = 0;
  
      /**
       * Normal file type.
       */
      byte LF_NORMAL = (byte)'0';
  
      /**
       * Link file type.
       */
      byte LF_LINK = (byte)'1';
  
      /**
       * Symbolic link file type.
       */
      byte LF_SYMLINK = (byte)'2';
  
      /**
       * Character device file type.
       */
      byte LF_CHR = (byte)'3';
  
      /**
       * Block device file type.
       */
      byte LF_BLK = (byte)'4';
  
      /**
       * Directory file type.
       */
      byte LF_DIR = (byte)'5';
  
      /**
       * FIFO (pipe) file type.
       */
      byte LF_FIFO = (byte)'6';
  
      /**
       * Contiguous file type.
       */
      byte LF_CONTIG = (byte)'7';
  
      /**
       * The magic tag representing a POSIX tar archive.
       */
      String TMAGIC = "ustar";
  
      /**
       * The magic tag representing a GNU tar archive.
       */
      String GNU_TMAGIC = "ustar  ";
  
      /**
       * The namr of the GNU tar entry which contains a long name.
       */
      String GNU_LONGLINK = "././@LongLink";
  
      /**
       * Identifies the *next* file on the tape as having a long name.
       */
      byte LF_GNUTYPE_LONGNAME = (byte)'L';
  }
  
  
  
  1.1                  jakarta-avalon-excalibur/tar/src/java/org/apache/avalon/excalibur/tar/TarEntry.java
  
  Index: TarEntry.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.avalon.excalibur.tar;
  
  import java.io.File;
  import java.util.Date;
  import java.util.Locale;
  
  /**
   * 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 <a href="mailto:time@ice.com">Timothy Gerard Endres</a>
   * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
   * @author <a href="mailto:peter@apache.org">Peter Donald</a>
   * @version $Revision: 1.1 $ $Date: 2002/03/21 13:34:46 $
   * @see TarInputStream
   * @see TarOutputStream
   */
  public class TarEntry
  {
      /**
       * The length of the name field in a header buffer.
       */
      public static final int NAMELEN = 100;
  
      /**
       * The entry's modification time.
       */
      private int m_checkSum;
  
      /**
       * The entry's group name.
       */
      private int m_devMajor;
  
      /**
       * The entry's major device number.
       */
      private int m_devMinor;
  
      /**
       * The entry's minor device number.
       */
      private File m_file;
  
      /**
       * The entry's user id.
       */
      private int m_groupID;
  
      /**
       * The entry's user name.
       */
      private StringBuffer m_groupName;
  
      /**
       * The entry's checksum.
       */
      private byte m_linkFlag;
  
      /**
       * The entry's link flag.
       */
      private StringBuffer m_linkName;
  
      /**
       * The entry's link name.
       */
      private StringBuffer m_magic;
  
      /**
       * The entry's size.
       */
      private long m_modTime;
  
      /**
       * The entry's name.
       */
      private int m_mode;
  
      private StringBuffer m_name;
  
      /**
       * The entry's group id.
       */
      private long m_size;
  
      /**
       * The entry's permission mode.
       */
      private int m_userID;
  
      /**
       * The entry's magic tag.
       */
      private StringBuffer m_userName;
  
      /**
       * Construct an entry with only a name. This allows the programmer to
       * construct the entry's header "by hand". File is set to null.
       *
       * @param name the name of the entry
       */
      public TarEntry( final String name )
      {
          this();
  
          final boolean isDir = name.endsWith( "/" );
  
          m_name = new StringBuffer( name );
          m_mode = isDir ? 040755 : 0100644;
          m_linkFlag = isDir ? TarConstants.LF_DIR : TarConstants.LF_NORMAL;
          m_modTime = ( new Date() ).getTime() / 1000;
          m_linkName = new StringBuffer( "" );
          m_userName = new StringBuffer( "" );
          m_groupName = new StringBuffer( "" );
      }
  
      /**
       * Construct an entry with a name an a link flag.
       *
       * @param name Description of Parameter
       * @param linkFlag Description of Parameter
       */
      public TarEntry( final String name, final byte linkFlag )
      {
          this( name );
          m_linkFlag = linkFlag;
      }
  
      /**
       * 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( final File file )
      {
          this();
  
          m_file = file;
  
          String name = file.getPath();
  
          // Strip off drive letters!
          final String osName =
              System.getProperty( "os.name" ).toLowerCase( Locale.US );
          if( -1 != osName.indexOf( "netware" ) )
          {
              if( name.length() > 2 )
              {
                  final char ch1 = name.charAt( 0 );
                  final char ch2 = name.charAt( 1 );
  
                  if( ch2 == ':' &&
                      ( ( ch1 >= 'a' && ch1 <= 'z' ) ||
                      ( ch1 >= 'A' && ch1 <= 'Z' ) ) )
                  {
                      name = name.substring( 2 );
                  }
              }
          }
          else if( -1 != osName.indexOf( "netware" ) )
          {
              final int colon = name.indexOf( ':' );
              if( colon != -1 )
              {
                  name = name.substring( colon + 1 );
              }
          }
  
          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 );
          }
  
          m_linkName = new StringBuffer( "" );
          m_name = new StringBuffer( name );
  
          if( file.isDirectory() )
          {
              m_mode = 040755;
              m_linkFlag = TarConstants.LF_DIR;
  
              if( m_name.charAt( m_name.length() - 1 ) != '/' )
              {
                  m_name.append( "/" );
              }
          }
          else
          {
              m_mode = 0100644;
              m_linkFlag = TarConstants.LF_NORMAL;
          }
  
          m_size = file.length();
          m_modTime = file.lastModified() / 1000;
          m_checkSum = 0;
          m_devMajor = 0;
          m_devMinor = 0;
      }
  
      /**
       * Construct an entry from an archive's header bytes. File is set to null.
       *
       * @param header The header bytes from a tar archive entry.
       */
      public TarEntry( final byte[] header )
      {
          this();
          parseTarHeader( header );
      }
  
      /**
       * Construct an empty entry and prepares the header values.
       */
      private TarEntry()
      {
          m_magic = new StringBuffer( TarConstants.TMAGIC );
          m_name = new StringBuffer();
          m_linkName = new StringBuffer();
  
          String user = System.getProperty( "user.name", "" );
          if( user.length() > 31 )
          {
              user = user.substring( 0, 31 );
          }
  
          m_userName = new StringBuffer( user );
          m_groupName = new StringBuffer( "" );
      }
  
      /**
       * Set this entry's group id.
       *
       * @param groupId This entry's new group id.
       */
      public void setGroupID( final int groupId )
      {
          m_groupID = groupId;
      }
  
      /**
       * Set this entry's group id.
       *
       * @param groupId This entry's new group id.
       * @deprecated Use setGroupID() instead
       * @see #setGroupID(int)
       */
      public void setGroupId( final int groupId )
      {
          m_groupID = groupId;
      }
  
      /**
       * Set this entry's group name.
       *
       * @param groupName This entry's new group name.
       */
      public void setGroupName( final String groupName )
      {
          m_groupName = new StringBuffer( 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( final long time )
      {
          m_modTime = time / 1000;
      }
  
      /**
       * Set this entry's modification time.
       *
       * @param time This entry's new modification time.
       */
      public void setModTime( final Date time )
      {
          m_modTime = time.getTime() / 1000;
      }
  
      /**
       * Set the mode for this entry
       *
       * @param mode The new Mode value
       */
      public void setMode( final int mode )
      {
          m_mode = mode;
      }
  
      /**
       * Set this entry's name.
       *
       * @param name This entry's new name.
       */
      public void setName( final String name )
      {
          m_name = new StringBuffer( name );
      }
  
      /**
       * Set this entry's file size.
       *
       * @param size This entry's new file size.
       */
      public void setSize( final long size )
      {
          m_size = size;
      }
  
      /**
       * Set this entry's user id.
       *
       * @param userId This entry's new user id.
       */
      public void setUserID( final int userId )
      {
          m_userID = userId;
      }
  
      /**
       * Set this entry's user id.
       *
       * @param userId This entry's new user id.
       * @deprecated Use setUserID() instead
       * @see #setUserID(int)
       */
      public void setUserId( final int userId )
      {
          m_userID = userId;
      }
  
      /**
       * Set this entry's user name.
       *
       * @param userName This entry's new user name.
       */
      public void setUserName( final String userName )
      {
          m_userName = new StringBuffer( userName );
      }
  
      /**
       * 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( null == m_file || !m_file.isDirectory() )
          {
              return new TarEntry[ 0 ];
          }
  
          final String[] list = m_file.list();
          final TarEntry[] result = new TarEntry[ list.length ];
  
          for( int i = 0; i < list.length; ++i )
          {
              result[ i ] = new TarEntry( new File( m_file, list[ i ] ) );
          }
  
          return result;
      }
  
      /**
       * Get this entry's file.
       *
       * @return This entry's file.
       */
      public File getFile()
      {
          return m_file;
      }
  
      /**
       * Get this entry's group id.
       *
       * @return This entry's group id.
       * @deprecated Use getGroupID() instead
       * @see #getGroupID()
       */
      public int getGroupId()
      {
          return m_groupID;
      }
  
      /**
       * Get this entry's group id.
       *
       * @return This entry's group id.
       */
      public int getGroupID()
      {
          return m_groupID;
      }
  
      /**
       * Get this entry's group name.
       *
       * @return This entry's group name.
       */
      public String getGroupName()
      {
          return m_groupName.toString();
      }
  
      /**
       * Set this entry's modification time.
       *
       * @return The ModTime value
       */
      public Date getModTime()
      {
          return new Date( m_modTime * 1000 );
      }
  
      /**
       * Get this entry's mode.
       *
       * @return This entry's mode.
       */
      public int getMode()
      {
          return m_mode;
      }
  
      /**
       * Get this entry's name.
       *
       * @return This entry's name.
       */
      public String getName()
      {
          return m_name.toString();
      }
  
      /**
       * Get this entry's file size.
       *
       * @return This entry's file size.
       */
      public long getSize()
      {
          return m_size;
      }
  
      /**
       * Get this entry's checksum.
       *
       * @return This entry's checksum.
       */
      public int getCheckSum()
      {
          return m_checkSum;
      }
  
      /**
       * Get this entry's user id.
       *
       * @return This entry's user id.
       * @deprecated Use getUserID() instead
       * @see #getUserID()
       */
      public int getUserId()
      {
          return m_userID;
      }
  
      /**
       * Get this entry's user id.
       *
       * @return This entry's user id.
       */
      public int getUserID()
      {
          return m_userID;
      }
  
      /**
       * Get this entry's user name.
       *
       * @return This entry's user name.
       */
      public String getUserName()
      {
          return m_userName.toString();
      }
  
      /**
       * Determine if the given entry is a descendant of this entry. Descendancy
       * is determined by the name of the descendant starting with this entry's
       * name.
       *
       * @param desc Entry to be checked as a descendent of
       * @return True if entry is a descendant of
       */
      public boolean isDescendent( final TarEntry desc )
      {
          return desc.getName().startsWith( getName() );
      }
  
      /**
       * Return whether or not this entry represents a directory.
       *
       * @return True if this entry is a directory.
       */
      public boolean isDirectory()
      {
          if( m_file != null )
          {
              return m_file.isDirectory();
          }
  
          if( m_linkFlag == TarConstants.LF_DIR )
          {
              return true;
          }
  
          if( getName().endsWith( "/" ) )
          {
              return true;
          }
  
          return false;
      }
  
      /**
       * Indicate if this entry is a GNU long name block
       *
       * @return true if this is a long name extension provided by GNU tar
       */
      public boolean isGNULongNameEntry()
      {
          return m_linkFlag == TarConstants.LF_GNUTYPE_LONGNAME &&
              m_name.toString().equals( TarConstants.GNU_LONGLINK );
      }
  
      /**
       * Determine if the two entries are equal. Equality is determined by the
       * header names being equal.
       *
       * @param other Entry to be checked for equality.
       * @return True if the entries are equal.
       */
      public boolean equals( final TarEntry other )
      {
          return getName().equals( other.getName() );
      }
  
      /**
       * Parse an entry's header information from a header buffer.
       *
       * @param header The tar entry header buffer to get information from.
       */
      private void parseTarHeader( final byte[] header )
      {
          int offset = 0;
  
          m_name = TarUtils.parseName( header, offset, NAMELEN );
          offset += NAMELEN;
          m_mode = (int)TarUtils.parseOctal( header, offset, TarConstants.MODELEN );
          offset += TarConstants.MODELEN;
          m_userID = (int)TarUtils.parseOctal( header, offset, TarConstants.UIDLEN );
          offset += TarConstants.UIDLEN;
          m_groupID = (int)TarUtils.parseOctal( header, offset, TarConstants.GIDLEN );
          offset += TarConstants.GIDLEN;
          m_size = TarUtils.parseOctal( header, offset, TarConstants.SIZELEN );
          offset += TarConstants.SIZELEN;
          m_modTime = TarUtils.parseOctal( header, offset, TarConstants.MODTIMELEN );
          offset += TarConstants.MODTIMELEN;
          m_checkSum = (int)TarUtils.parseOctal( header, offset, TarConstants.CHKSUMLEN );
          offset += TarConstants.CHKSUMLEN;
          m_linkFlag = header[ offset++ ];
          m_linkName = TarUtils.parseName( header, offset, NAMELEN );
          offset += NAMELEN;
          m_magic = TarUtils.parseName( header, offset, TarConstants.MAGICLEN );
          offset += TarConstants.MAGICLEN;
          m_userName = TarUtils.parseName( header, offset, TarConstants.UNAMELEN );
          offset += TarConstants.UNAMELEN;
          m_groupName = TarUtils.parseName( header, offset, TarConstants.GNAMELEN );
          offset += TarConstants.GNAMELEN;
          m_devMajor = (int)TarUtils.parseOctal( header, offset, TarConstants.DEVLEN );
          offset += TarConstants.DEVLEN;
          m_devMinor = (int)TarUtils.parseOctal( header, offset, TarConstants.DEVLEN );
      }
  
      /**
       * Write an entry's header information to a header buffer.
       *
       * @param buffer The tar entry header buffer to fill in.
       */
      public void writeEntryHeader( final byte[] buffer )
      {
          int offset = 0;
  
          offset = TarUtils.getNameBytes( m_name, buffer, offset, NAMELEN );
          offset = TarUtils.getOctalBytes( m_mode, buffer, offset, TarConstants.MODELEN );
          offset = TarUtils.getOctalBytes( m_userID, buffer, offset, TarConstants.UIDLEN );
          offset = TarUtils.getOctalBytes( m_groupID, buffer, offset, TarConstants.GIDLEN );
          offset = TarUtils.getLongOctalBytes( m_size, buffer, offset, TarConstants.SIZELEN );
          offset = TarUtils.getLongOctalBytes( m_modTime, buffer, offset, TarConstants.MODTIMELEN );
  
          final int checkSumOffset = offset;
          for( int i = 0; i < TarConstants.CHKSUMLEN; ++i )
          {
              buffer[ offset++ ] = (byte)' ';
          }
  
          buffer[ offset++ ] = m_linkFlag;
          offset = TarUtils.getNameBytes( m_linkName, buffer, offset, NAMELEN );
          offset = TarUtils.getNameBytes( m_magic, buffer, offset, TarConstants.MAGICLEN );
          offset = TarUtils.getNameBytes( m_userName, buffer, offset, TarConstants.UNAMELEN );
          offset = TarUtils.getNameBytes( m_groupName, buffer, offset, TarConstants.GNAMELEN );
          offset = TarUtils.getOctalBytes( m_devMajor, buffer, offset, TarConstants.DEVLEN );
          offset = TarUtils.getOctalBytes( m_devMinor, buffer, offset, TarConstants.DEVLEN );
  
          while( offset < buffer.length )
          {
              buffer[ offset++ ] = 0;
          }
  
          final long checkSum = TarUtils.computeCheckSum( buffer );
          TarUtils.getCheckSumOctalBytes( checkSum, buffer, checkSumOffset, TarConstants.CHKSUMLEN );
      }
  }
  
  
  
  1.1                  jakarta-avalon-excalibur/tar/src/java/org/apache/avalon/excalibur/tar/TarInputStream.java
  
  Index: TarInputStream.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.avalon.excalibur.tar;
  
  import java.io.FilterInputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  
  /**
   * 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 <a href="mailto:time@ice.com">Timothy Gerard Endres</a>
   * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
   * @author <a href="mailto:peter@apache.org">Peter Donald</a>
   * @version $Revision: 1.1 $ $Date: 2002/03/21 13:34:46 $
   * @see TarInputStream
   * @see TarEntry
   */
  public class TarInputStream
      extends FilterInputStream
  {
      private TarBuffer m_buffer;
      private TarEntry m_currEntry;
      private boolean m_debug;
      private int m_entryOffset;
      private int m_entrySize;
      private boolean m_hasHitEOF;
      private byte[] m_oneBuf;
      private byte[] m_readBuf;
  
      /**
       * Construct a TarInputStream using specified input
       * stream and default block and record sizes.
       *
       * @param input stream to create TarInputStream from
       * @see TarBuffer#DEFAULT_BLOCKSIZE
       * @see TarBuffer#DEFAULT_RECORDSIZE
       */
      public TarInputStream( final InputStream input )
      {
          this( input, TarBuffer.DEFAULT_BLOCKSIZE, TarBuffer.DEFAULT_RECORDSIZE );
      }
  
      /**
       * Construct a TarInputStream using specified input
       * stream, block size and default record sizes.
       *
       * @param input stream to create TarInputStream from
       * @param blockSize the block size to use
       * @see TarBuffer#DEFAULT_RECORDSIZE
       */
      public TarInputStream( final InputStream input,
                             final int blockSize )
      {
          this( input, blockSize, TarBuffer.DEFAULT_RECORDSIZE );
      }
  
      /**
       * Construct a TarInputStream using specified input
       * stream, block size and record sizes.
       *
       * @param input stream to create TarInputStream from
       * @param blockSize the block size to use
       * @param recordSize the record size to use
       */
      public TarInputStream( final InputStream input,
                             final int blockSize,
                             final int recordSize )
      {
          super( input );
  
          m_buffer = new TarBuffer( input, blockSize, recordSize );
          m_oneBuf = new byte[ 1 ];
      }
  
      /**
       * Sets the debugging flag.
       *
       * @param debug The new Debug value
       */
      public void setDebug( final boolean debug )
      {
          m_debug = debug;
          m_buffer.setDebug( debug );
      }
  
      /**
       * 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.
       * @exception IOException Description of Exception
       */
      public TarEntry getNextEntry()
          throws IOException
      {
          if( m_hasHitEOF )
          {
              return null;
          }
  
          if( m_currEntry != null )
          {
              final int numToSkip = m_entrySize - m_entryOffset;
  
              if( m_debug )
              {
                  final String message = "TarInputStream: SKIP currENTRY '" +
                      m_currEntry.getName() + "' SZ " + m_entrySize +
                      " OFF " + m_entryOffset + "  skipping " + numToSkip + " bytes";
                  debug( message );
              }
  
              if( numToSkip > 0 )
              {
                  skip( numToSkip );
              }
  
              m_readBuf = null;
          }
  
          final byte[] headerBuf = m_buffer.readRecord();
          if( headerBuf == null )
          {
              if( m_debug )
              {
                  debug( "READ NULL RECORD" );
              }
              m_hasHitEOF = true;
          }
          else if( m_buffer.isEOFRecord( headerBuf ) )
          {
              if( m_debug )
              {
                  debug( "READ EOF RECORD" );
              }
              m_hasHitEOF = true;
          }
  
          if( m_hasHitEOF )
          {
              m_currEntry = null;
          }
          else
          {
              m_currEntry = new TarEntry( headerBuf );
  
              if( !( headerBuf[ 257 ] == 'u' && headerBuf[ 258 ] == 's' &&
                  headerBuf[ 259 ] == 't' && headerBuf[ 260 ] == 'a' &&
                  headerBuf[ 261 ] == 'r' ) )
              {
                  //Must be v7Format
              }
  
              if( m_debug )
              {
                  final String message = "TarInputStream: SET CURRENTRY '" +
                      m_currEntry.getName() + "' size = " + m_currEntry.getSize();
                  debug( message );
              }
  
              m_entryOffset = 0;
  
              // REVIEW How do we resolve this discrepancy?!
              m_entrySize = (int)m_currEntry.getSize();
          }
  
          if( null != m_currEntry && m_currEntry.isGNULongNameEntry() )
          {
              // read in the name
              final StringBuffer longName = new StringBuffer();
              final byte[] buffer = new byte[ 256 ];
              int length = 0;
              while( ( length = read( buffer ) ) >= 0 )
              {
                  final String str = new String( buffer, 0, length );
                  longName.append( str );
              }
              getNextEntry();
              m_currEntry.setName( longName.toString() );
          }
  
          return m_currEntry;
      }
  
      /**
       * Get the record size being used by this stream's TarBuffer.
       *
       * @return The TarBuffer record size.
       */
      public int getRecordSize()
      {
          return m_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.
       * @exception IOException when an IO error causes operation to fail
       */
      public int available()
          throws IOException
      {
          return m_entrySize - m_entryOffset;
      }
  
      /**
       * Closes this stream. Calls the TarBuffer's close() method.
       *
       * @exception IOException when an IO error causes operation to fail
       */
      public void close()
          throws IOException
      {
          m_buffer.close();
      }
  
      /**
       * Copies the contents of the current tar archive entry directly into an
       * output stream.
       *
       * @param output The OutputStream into which to write the entry's data.
       * @exception IOException when an IO error causes operation to fail
       */
      public void copyEntryContents( final OutputStream output )
          throws IOException
      {
          final byte[] buffer = new byte[ 32 * 1024 ];
          while( true )
          {
              final int numRead = read( buffer, 0, buffer.length );
              if( numRead == -1 )
              {
                  break;
              }
  
              output.write( buffer, 0, numRead );
          }
      }
  
      /**
       * 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 return false.
       *
       * @return False.
       */
      public boolean markSupported()
      {
          return false;
      }
  
      /**
       * 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.
       * @exception IOException when an IO error causes operation to fail
       */
      public int read()
          throws IOException
      {
          final int num = read( m_oneBuf, 0, 1 );
          if( num == -1 )
          {
              return num;
          }
          else
          {
              return (int)m_oneBuf[ 0 ];
          }
      }
  
      /**
       * Reads bytes from the current tar archive entry. This method simply calls
       * read( byte[], int, int ).
       *
       * @param buffer The buffer into which to place bytes read.
       * @return The number of bytes read, or -1 at EOF.
       * @exception IOException when an IO error causes operation to fail
       */
      public int read( final byte[] buffer )
          throws IOException
      {
          return read( buffer, 0, buffer.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 buffer The buffer into which to place bytes read.
       * @param offset The offset at which to place bytes read.
       * @param count The number of bytes to read.
       * @return The number of bytes read, or -1 at EOF.
       * @exception IOException when an IO error causes operation to fail
       */
      public int read( final byte[] buffer,
                       final int offset,
                       final int count )
          throws IOException
      {
          int position = offset;
          int numToRead = count;
          int totalRead = 0;
  
          if( m_entryOffset >= m_entrySize )
          {
              return -1;
          }
  
          if( ( numToRead + m_entryOffset ) > m_entrySize )
          {
              numToRead = ( m_entrySize - m_entryOffset );
          }
  
          if( null != m_readBuf )
          {
              final int size =
                  ( numToRead > m_readBuf.length ) ? m_readBuf.length : numToRead;
  
              System.arraycopy( m_readBuf, 0, buffer, position, size );
  
              if( size >= m_readBuf.length )
              {
                  m_readBuf = null;
              }
              else
              {
                  final int newLength = m_readBuf.length - size;
                  final byte[] newBuffer = new byte[ newLength ];
  
                  System.arraycopy( m_readBuf, size, newBuffer, 0, newLength );
  
                  m_readBuf = newBuffer;
              }
  
              totalRead += size;
              numToRead -= size;
              position += size;
          }
  
          while( numToRead > 0 )
          {
              final byte[] rec = m_buffer.readRecord();
              if( null == rec )
              {
                  // Unexpected EOF!
                  final String message =
                      "unexpected EOF with " + numToRead + " bytes unread";
                  throw new IOException( message );
              }
  
              int size = numToRead;
              final int recordLength = rec.length;
  
              if( recordLength > size )
              {
                  System.arraycopy( rec, 0, buffer, position, size );
  
                  m_readBuf = new byte[ recordLength - size ];
  
                  System.arraycopy( rec, size, m_readBuf, 0, recordLength - size );
              }
              else
              {
                  size = recordLength;
  
                  System.arraycopy( rec, 0, buffer, position, recordLength );
              }
  
              totalRead += size;
              numToRead -= size;
              position += size;
          }
  
          m_entryOffset += totalRead;
  
          return totalRead;
      }
  
      /**
       * Since we do not support marking just yet, we do nothing.
       */
      public void reset()
      {
      }
  
      /**
       * 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.
       * @exception IOException when an IO error causes operation to fail
       */
      public void skip( final int numToSkip )
          throws IOException
      {
          // REVIEW
          // This is horribly inefficient, but it ensures that we
          // properly skip over bytes via the TarBuffer...
          //
          final byte[] skipBuf = new byte[ 8 * 1024 ];
          int num = numToSkip;
          while( num > 0 )
          {
              final int count = ( num > skipBuf.length ) ? skipBuf.length : num;
              final int numRead = read( skipBuf, 0, count );
              if( numRead == -1 )
              {
                  break;
              }
  
              num -= numRead;
          }
      }
  
      /**
       * Utility method to do debugging.
       * Capable of being overidden in sub-classes.
       *
       * @param message the message to use in debugging
       */
      protected void debug( final String message )
      {
          if( m_debug )
          {
              System.err.println( message );
          }
      }
  }
  
  
  
  1.1                  jakarta-avalon-excalibur/tar/src/java/org/apache/avalon/excalibur/tar/TarOutputStream.java
  
  Index: TarOutputStream.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.avalon.excalibur.tar;
  
  import java.io.FilterOutputStream;
  import java.io.IOException;
  import java.io.OutputStream;
  import java.io.InputStream;
  
  /**
   * 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>
   * @author <a href="mailto:peter@apache.org">Peter Donald</a>
   * @version $Revision: 1.1 $ $Date: 2002/03/21 13:34:46 $
   * @see TarInputStream
   * @see TarEntry
   */
  public class TarOutputStream
      extends FilterOutputStream
  {
      /**
       * Flag to indicate that an error should be generated if
       * an attempt is made to write an entry that exceeds the 100 char
       * POSIX limit.
       */
      public static final int LONGFILE_ERROR = 0;
  
      /**
       * Flag to indicate that entry name should be truncated if
       * an attempt is made to write an entry that exceeds the 100 char
       * POSIX limit.
       */
      public static final int LONGFILE_TRUNCATE = 1;
  
      /**
       * Flag to indicate that entry name should be formatted
       * according to GNU tar extension if an attempt is made
       * to write an entry that exceeds the 100 char POSIX
       * limit. Note that this makes the jar unreadable by
       * non-GNU tar commands.
       */
      public static final int LONGFILE_GNU = 2;
  
      private int m_longFileMode = LONGFILE_ERROR;
      private byte[] m_assemBuf;
      private int m_assemLen;
      private TarBuffer m_buffer;
      private int m_currBytes;
      private int m_currSize;
  
      private byte[] m_oneBuf;
      private byte[] m_recordBuf;
  
      /**
       * Construct a TarOutputStream using specified input
       * stream and default block and record sizes.
       *
       * @param output stream to create TarOutputStream from
       * @see TarBuffer#DEFAULT_BLOCKSIZE
       * @see TarBuffer#DEFAULT_RECORDSIZE
       */
      public TarOutputStream( final OutputStream output )
      {
          this( output, TarBuffer.DEFAULT_BLOCKSIZE, TarBuffer.DEFAULT_RECORDSIZE );
      }
  
      /**
       * Construct a TarOutputStream using specified input
       * stream, block size and default record sizes.
       *
       * @param output stream to create TarOutputStream from
       * @param blockSize the block size
       * @see TarBuffer#DEFAULT_RECORDSIZE
       */
      public TarOutputStream( final OutputStream output,
                              final int blockSize )
      {
          this( output, blockSize, TarBuffer.DEFAULT_RECORDSIZE );
      }
  
      /**
       * Construct a TarOutputStream using specified input
       * stream, block size and record sizes.
       *
       * @param output stream to create TarOutputStream from
       * @param blockSize the block size
       * @param recordSize the record size
       */
      public TarOutputStream( final OutputStream output,
                              final int blockSize,
                              final int recordSize )
      {
          super( output );
  
          m_buffer = new TarBuffer( output, blockSize, recordSize );
          m_assemLen = 0;
          m_assemBuf = new byte[ recordSize ];
          m_recordBuf = new byte[ recordSize ];
          m_oneBuf = new byte[ 1 ];
      }
  
      /**
       * Sets the debugging flag in this stream's TarBuffer.
       *
       * @param debug The new BufferDebug value
       */
      public void setBufferDebug( boolean debug )
      {
          m_buffer.setDebug( debug );
      }
  
      /**
       * Set the mode used to work with entrys exceeding
       * 100 chars (and thus break the POSIX standard).
       * Must be one of the LONGFILE_* constants.
       *
       * @param longFileMode the mode
       */
      public void setLongFileMode( final int longFileMode )
      {
          if( LONGFILE_ERROR != longFileMode &&
              LONGFILE_GNU != longFileMode &&
              LONGFILE_TRUNCATE != longFileMode )
          {
              throw new IllegalArgumentException( "longFileMode" );
          }
          m_longFileMode = longFileMode;
      }
  
      /**
       * Get the record size being used by this stream's TarBuffer.
       *
       * @return The TarBuffer record size.
       */
      public int getRecordSize()
      {
          return m_buffer.getRecordSize();
      }
  
      /**
       * Ends the TAR archive and closes the underlying OutputStream. This means
       * that finish() is called followed by calling the TarBuffer's close().
       *
       * @exception IOException when an IO error causes operation to fail
       */
      public void close()
          throws IOException
      {
          finish();
          m_buffer.close();
      }
  
      /**
       * 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.
       *
       * @exception IOException when an IO error causes operation to fail
       */
      public void closeEntry()
          throws IOException
      {
          if( m_assemLen > 0 )
          {
              for( int i = m_assemLen; i < m_assemBuf.length; ++i )
              {
                  m_assemBuf[ i ] = 0;
              }
  
              m_buffer.writeRecord( m_assemBuf );
  
              m_currBytes += m_assemLen;
              m_assemLen = 0;
          }
  
          if( m_currBytes < m_currSize )
          {
              final String message = "entry closed at '" + m_currBytes +
                  "' before the '" + m_currSize +
                  "' bytes specified in the header were written";
              throw new IOException( message );
          }
      }
  
      /**
       * Ends the TAR archive without closing the underlying OutputStream. The
       * result is that the EOF record of nulls is written.
       *
       * @exception IOException when an IO error causes operation to fail
       */
      public void finish()
          throws IOException
      {
          writeEOFRecord();
      }
  
      /**
       * 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.
       * @exception IOException when an IO error causes operation to fail
       */
      public void putNextEntry( final TarEntry entry )
          throws IOException
      {
          if( entry.getName().length() >= TarEntry.NAMELEN )
          {
              if( m_longFileMode == LONGFILE_GNU )
              {
                  // create a TarEntry for the LongLink, the contents
                  // of which are the entry's name
                  final TarEntry longLinkEntry =
                      new TarEntry( TarConstants.GNU_LONGLINK,
                                    TarConstants.LF_GNUTYPE_LONGNAME );
  
                  longLinkEntry.setSize( entry.getName().length() );
                  putNextEntry( longLinkEntry );
                  write( entry.getName().getBytes() );
                  //write( 0 );
                  closeEntry();
              }
              else if( m_longFileMode != LONGFILE_TRUNCATE )
              {
                  final String message = "file name '" + entry.getName() +
                      "' is too long ( > " + TarEntry.NAMELEN + " bytes)";
                  throw new IOException( message );
              }
          }
  
          entry.writeEntryHeader( m_recordBuf );
          m_buffer.writeRecord( m_recordBuf );
  
          m_currBytes = 0;
  
          if( entry.isDirectory() )
          {
              m_currSize = 0;
          }
          else
          {
              m_currSize = (int)entry.getSize();
          }
      }
  
      /**
       * Copies the contents of the specified stream into current tar
       * archive entry.
       *
       * @param input The InputStream from which to read entrys data
       * @exception IOException when an IO error causes operation to fail
       */
      public void copyEntryContents( final InputStream input )
          throws IOException
      {
          final byte[] buffer = new byte[ 32 * 1024 ];
          while( true )
          {
              final int numRead = input.read( buffer, 0, buffer.length );
              if( numRead == -1 )
              {
                  break;
              }
  
              write( buffer, 0, numRead );
          }
      }
  
      /**
       * Writes a byte to the current tar archive entry. This method simply calls
       * read( byte[], int, int ).
       *
       * @param data The byte written.
       * @exception IOException when an IO error causes operation to fail
       */
      public void write( final int data )
          throws IOException
      {
          m_oneBuf[ 0 ] = (byte)data;
  
          write( m_oneBuf, 0, 1 );
      }
  
      /**
       * Writes bytes to the current tar archive entry. This method simply calls
       * write( byte[], int, int ).
       *
       * @param buffer The buffer to write to the archive.
       * @exception IOException when an IO error causes operation to fail
       */
      public void write( final byte[] buffer )
          throws IOException
      {
          write( buffer, 0, buffer.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.
       *
       * @param buffer The buffer to write to the archive.
       * @param offset The offset in the buffer from which to get bytes.
       * @param count The number of bytes to write.
       * @exception IOException when an IO error causes operation to fail
       */
      public void write( final byte[] buffer,
                         final int offset,
                         final int count )
          throws IOException
      {
          int position = offset;
          int numToWrite = count;
          if( ( m_currBytes + numToWrite ) > m_currSize )
          {
              final String message = "request to write '" + numToWrite +
                  "' bytes exceeds size in header of '" + m_currSize + "' bytes";
              throw new IOException( message );
              //
              // 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( m_assemLen > 0 )
          {
              if( ( m_assemLen + numToWrite ) >= m_recordBuf.length )
              {
                  final int length = m_recordBuf.length - m_assemLen;
  
                  System.arraycopy( m_assemBuf, 0, m_recordBuf, 0,
                                    m_assemLen );
                  System.arraycopy( buffer, position, m_recordBuf,
                                    m_assemLen, length );
                  m_buffer.writeRecord( m_recordBuf );
  
                  m_currBytes += m_recordBuf.length;
                  position += length;
                  numToWrite -= length;
                  m_assemLen = 0;
              }
              else
              {
                  System.arraycopy( buffer, position, m_assemBuf, m_assemLen,
                                    numToWrite );
  
                  position += numToWrite;
                  m_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 < m_recordBuf.length )
              {
                  System.arraycopy( buffer, position, m_assemBuf, m_assemLen,
                                    numToWrite );
  
                  m_assemLen += numToWrite;
  
                  break;
              }
  
              m_buffer.writeRecord( buffer, position );
  
              int num = m_recordBuf.length;
  
              m_currBytes += num;
              numToWrite -= num;
              position += num;
          }
      }
  
      /**
       * Write an EOF (end of archive) record to the tar archive. An EOF record
       * consists of a record of all zeros.
       *
       * @exception IOException when an IO error causes operation to fail
       */
      private void writeEOFRecord()
          throws IOException
      {
          for( int i = 0; i < m_recordBuf.length; ++i )
          {
              m_recordBuf[ i ] = 0;
          }
  
          m_buffer.writeRecord( m_recordBuf );
      }
  }
  
  
  
  1.1                  jakarta-avalon-excalibur/tar/src/java/org/apache/avalon/excalibur/tar/TarUtils.java
  
  Index: TarUtils.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.avalon.excalibur.tar;
  
  /**
   * This class provides static utility methods to work with byte streams.
   *
   * @author <a href="mailto:time@ice.com">Timothy Gerard Endres</a>
   * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
   * @version $Revision: 1.1 $ $Date: 2002/03/21 13:34:46 $
   */
  class TarUtils
  {
      /**
       * Parse the checksum octal integer from a header buffer.
       *
       * @param offset The offset into the buffer from which to parse.
       * @param length The number of header bytes to parse.
       * @param value Description of Parameter
       * @param buf Description of Parameter
       * @return The integer value of the entry's checksum.
       */
      public static int getCheckSumOctalBytes( final long value,
                                               final byte[] buf,
                                               final int offset,
                                               final int length )
      {
          getOctalBytes( value, buf, offset, length );
  
          buf[ offset + length - 1 ] = (byte)' ';
          buf[ offset + length - 2 ] = 0;
  
          return offset + length;
      }
  
      /**
       * Parse an octal long integer from a header buffer.
       *
       * @param offset The offset into the buffer from which to parse.
       * @param length The number of header bytes to parse.
       * @param value Description of Parameter
       * @param buf Description of Parameter
       * @return The long value of the octal bytes.
       */
      public static int getLongOctalBytes( final long value,
                                           final byte[] buf,
                                           final int offset,
                                           final int length )
      {
          byte[] temp = new byte[ length + 1 ];
  
          getOctalBytes( value, temp, 0, length + 1 );
          System.arraycopy( temp, 0, buf, offset, length );
  
          return offset + length;
      }
  
      /**
       * Determine the number of bytes in an entry name.
       *
       * @param offset The offset into the buffer from which to parse.
       * @param length The number of header bytes to parse.
       * @param name Description of Parameter
       * @param buffer Description of Parameter
       * @return The number of bytes in a header's entry name.
       */
      public static int getNameBytes( final StringBuffer name,
                                      final byte[] buffer,
                                      final int offset,
                                      final int length )
      {
          int i;
  
          for( i = 0; i < length && i < name.length(); ++i )
          {
              buffer[ offset + i ] = (byte)name.charAt( i );
          }
  
          for( ; i < length; ++i )
          {
              buffer[ offset + i ] = 0;
          }
  
          return offset + length;
      }
  
      /**
       * Parse an octal integer from a header buffer.
       *
       * @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( final long value,
                                       final byte[] buffer,
                                       final int offset,
                                       final int length )
      {
          int idx = length - 1;
  
          buffer[ offset + idx ] = 0;
          --idx;
          buffer[ offset + idx ] = (byte)' ';
          --idx;
  
          if( value == 0 )
          {
              buffer[ offset + idx ] = (byte)'0';
              --idx;
          }
          else
          {
              long val = value;
              while( idx >= 0 && val > 0 )
              {
                  buffer[ offset + idx ] = (byte)( (byte)'0' + (byte)( val & 7 ) );
                  val = val >> 3;
                  idx--;
              }
          }
  
          while( idx >= 0 )
          {
              buffer[ offset + idx ] = (byte)' ';
              idx--;
          }
  
          return offset + length;
      }
  
      /**
       * Compute the checksum of a tar entry header.
       *
       * @param buffer The tar entry's header buffer.
       * @return The computed checksum.
       */
      public static long computeCheckSum( final byte[] buffer )
      {
          long sum = 0;
  
          for( int i = 0; i < buffer.length; ++i )
          {
              sum += 255 & buffer[ i ];
          }
  
          return sum;
      }
  
      /**
       * 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( final byte[] header,
                                            final int offset,
                                            final 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;
      }
  
      /**
       * 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( final byte[] header,
                                     final int offset,
                                     final 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;
      }
  }
  
  
  

--
To unsubscribe, e-mail:   <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>