View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  package org.apache.commons.vfs.provider.tar;
    18
    19  import java.io.IOException;
    20  import java.io.InputStream;
    21  import java.io.OutputStream;
    22  import java.util.Arrays;
    23
    24  /***
    25   * The TarBuffer class implements the tar archive concept of a buffered input
    26   * stream. This concept goes back to the days of blocked tape drives and special
    27   * io devices. In the Java universe, the only real function that this class
    28   * performs is to ensure that files have the correct "block" size, or other tars
    29   * will complain. <p>
    30   *
    31   * You should never have a need to access this class directly. TarBuffers are
    32   * created by Tar IO Streams.
    33   *
    34   * @author <a href="mailto:time@ice.com">Timothy Gerard Endres</a>
    35   * @author <a href="mailto:peter@apache.org">Peter Donald</a>
    36   * @version $Revision: 480428 $ $Date: 2006-11-29 07:15:24 +0100 (Mi, 29 Nov 2006) $
    37   */
    38  class TarBuffer
    39  {
    40      public static final int DEFAULT_RECORDSIZE = ( 512 );
    41      public static final int DEFAULT_BLOCKSIZE = ( DEFAULT_RECORDSIZE * 20 );
    42
    43      private byte[] m_blockBuffer;
    44      private int m_blockSize;
    45      private int m_currBlkIdx;
    46      private int m_currRecIdx;
    47      private boolean m_debug;
    48
    49      private InputStream m_input;
    50      private OutputStream m_output;
    51      private int m_recordSize;
    52      private int m_recsPerBlock;
    53
    54      TarBuffer( final InputStream input )
    55      {
    56          this( input, TarBuffer.DEFAULT_BLOCKSIZE );
    57      }
    58
    59      TarBuffer( final InputStream input, final int blockSize )
    60      {
    61          this( input, blockSize, TarBuffer.DEFAULT_RECORDSIZE );
    62      }
    63
    64      TarBuffer( final InputStream input,
    65                        final int blockSize,
    66                        final int recordSize )
    67      {
    68          m_input = input;
    69          initialize( blockSize, recordSize );
    70      }
    71
    72      TarBuffer( final OutputStream output )
    73      {
    74          this( output, TarBuffer.DEFAULT_BLOCKSIZE );
    75      }
    76
    77      TarBuffer( final OutputStream output, final int blockSize )
    78      {
    79          this( output, blockSize, TarBuffer.DEFAULT_RECORDSIZE );
    80      }
    81
    82      TarBuffer( final OutputStream output,
    83                        final int blockSize,
    84                        final int recordSize )
    85      {
    86          m_output = output;
    87          initialize( blockSize, recordSize );
    88      }
    89
    90      /***
    91       * Set the debugging flag for the buffer.
    92       *
    93       * @param debug If true, print debugging output.
    94       */
    95      public void setDebug( final boolean debug )
    96      {
    97          m_debug = debug;
    98      }
    99
    100     /***
    101      * Get the TAR Buffer's block size. Blocks consist of multiple records.
    102      *
    103      * @return The BlockSize value
    104      */
    105     public int getBlockSize()
    106     {
    107         return m_blockSize;
    108     }
    109
    110     /***
    111      * Get the current block number, zero based.
    112      *
    113      * @return The current zero based block number.
    114      */
    115     public int getCurrentBlockNum()
    116     {
    117         return m_currBlkIdx;
    118     }
    119
    120     /***
    121      * Get the current record number, within the current block, zero based.
    122      * Thus, current offset = (currentBlockNum * recsPerBlk) + currentRecNum.
    123      *
    124      * @return The current zero based record number.
    125      */
    126     public int getCurrentRecordNum()
    127     {
    128         return m_currRecIdx - 1;
    129     }
    130
    131     /***
    132      * Get the TAR Buffer's record size.
    133      *
    134      * @return The RecordSize value
    135      */
    136     public int getRecordSize()
    137     {
    138         return m_recordSize;
    139     }
    140
    141     /***
    142      * Determine if an archive record indicate End of Archive. End of archive is
    143      * indicated by a record that consists entirely of null bytes.
    144      *
    145      * @param record The record data to check.
    146      * @return The EOFRecord value
    147      */
    148     public boolean isEOFRecord( final byte[] record )
    149     {
    150         final int size = getRecordSize();
    151         for( int i = 0; i < size; ++i )
    152         {
    153             if( record[ i ] != 0 )
    154             {
    155                 return false;
    156             }
    157         }
    158
    159         return true;
    160     }
    161
    162     /***
    163      * Close the TarBuffer. If this is an output buffer, also flush the current
    164      * block before closing.
    165      */
    166     public void close()
    167         throws IOException
    168     {
    169         if( m_debug )
    170         {
    171             debug( "TarBuffer.closeBuffer()." );
    172         }
    173
    174         if( null != m_output )
    175         {
    176             flushBlock();
    177
    178             if( m_output != System.out && m_output != System.err )
    179             {
    180                 m_output.close();
    181                 m_output = null;
    182             }
    183         }
    184         else if( m_input != null )
    185         {
    186             if( m_input != System.in )
    187             {
    188                 m_input.close();
    189                 m_input = null;
    190             }
    191         }
    192     }
    193
    194     /***
    195      * Read a record from the input stream and return the data.
    196      *
    197      * @return The record data.
    198      * @exception IOException Description of Exception
    199      */
    200     public byte[] readRecord()
    201         throws IOException
    202     {
    203         if( m_debug )
    204         {
    205             final String message = "ReadRecord: recIdx = " + m_currRecIdx +
    206                 " blkIdx = " + m_currBlkIdx;
    207             debug( message );
    208         }
    209
    210         if( null == m_input )
    211         {
    212             final String message = "reading from an output buffer";
    213             throw new IOException( message );
    214         }
    215
    216         if( m_currRecIdx >= m_recsPerBlock )
    217         {
    218             if( !readBlock() )
    219             {
    220                 return null;
    221             }
    222         }
    223
    224         final byte[] result = new byte[ m_recordSize ];
    225         System.arraycopy( m_blockBuffer,
    226                           ( m_currRecIdx * m_recordSize ),
    227                           result,
    228                           0,
    229                           m_recordSize );
    230
    231         m_currRecIdx++;
    232
    233         return result;
    234     }
    235
    236     /***
    237      * Skip over a record on the input stream.
    238      */
    239     public void skipRecord()
    240         throws IOException
    241     {
    242         if( m_debug )
    243         {
    244             final String message = "SkipRecord: recIdx = " + m_currRecIdx +
    245                 " blkIdx = " + m_currBlkIdx;
    246             debug( message );
    247         }
    248
    249         if( null == m_input )
    250         {
    251             final String message = "reading (via skip) from an output buffer";
    252             throw new IOException( message );
    253         }
    254
    255         if( m_currRecIdx >= m_recsPerBlock )
    256         {
    257             if( !readBlock() )
    258             {
    259                 return;// UNDONE
    260             }
    261         }
    262
    263         m_currRecIdx++;
    264     }
    265
    266     /***
    267      * Write an archive record to the archive.
    268      *
    269      * @param record The record data to write to the archive.
    270      */
    271     public void writeRecord( final byte[] record )
    272         throws IOException
    273     {
    274         if( m_debug )
    275         {
    276             final String message = "WriteRecord: recIdx = " + m_currRecIdx +
    277                 " blkIdx = " + m_currBlkIdx;
    278             debug( message );
    279         }
    280
    281         if( null == m_output )
    282         {
    283             final String message = "writing to an input buffer";
    284             throw new IOException( message );
    285         }
    286
    287         if( record.length != m_recordSize )
    288         {
    289             final String message = "record to write has length '" +
    290                 record.length + "' which is not the record size of '" +
    291                 m_recordSize + "'";
    292             throw new IOException( message );
    293         }
    294
    295         if( m_currRecIdx >= m_recsPerBlock )
    296         {
    297             writeBlock();
    298         }
    299
    300         System.arraycopy( record,
    301                           0,
    302                           m_blockBuffer,
    303                           ( m_currRecIdx * m_recordSize ),
    304                           m_recordSize );
    305
    306         m_currRecIdx++;
    307     }
    308
    309     /***
    310      * Write an archive record to the archive, where the record may be inside of
    311      * a larger array buffer. The buffer must be "offset plus record size" long.
    312      *
    313      * @param buffer The buffer containing the record data to write.
    314      * @param offset The offset of the record data within buf.
    315      */
    316     public void writeRecord( final byte[] buffer, final int offset )
    317         throws IOException
    318     {
    319         if( m_debug )
    320         {
    321             final String message = "WriteRecord: recIdx = " + m_currRecIdx +
    322                 " blkIdx = " + m_currBlkIdx;
    323             debug( message );
    324         }
    325
    326         if( null == m_output )
    327         {
    328             final String message = "writing to an input buffer";
    329             throw new IOException( message );
    330         }
    331
    332         if( ( offset + m_recordSize ) > buffer.length )
    333         {
    334             final String message = "record has length '" + buffer.length +
    335                 "' with offset '" + offset + "' which is less than the record size of '" +
    336                 m_recordSize + "'";
    337             throw new IOException( message );
    338         }
    339
    340         if( m_currRecIdx >= m_recsPerBlock )
    341         {
    342             writeBlock();
    343         }
    344
    345         System.arraycopy( buffer,
    346                           offset,
    347                           m_blockBuffer,
    348                           ( m_currRecIdx * m_recordSize ),
    349                           m_recordSize );
    350
    351         m_currRecIdx++;
    352     }
    353
    354     /***
    355      * Flush the current data block if it has any data in it.
    356      */
    357     private void flushBlock()
    358         throws IOException
    359     {
    360         if( m_debug )
    361         {
    362             final String message = "TarBuffer.flushBlock() called.";
    363             debug( message );
    364         }
    365
    366         if( m_output == null )
    367         {
    368             final String message = "writing to an input buffer";
    369             throw new IOException( message );
    370         }
    371
    372         if( m_currRecIdx > 0 )
    373         {
    374             writeBlock();
    375         }
    376     }
    377
    378     /***
    379      * Initialization common to all constructors.
    380      */
    381     private void initialize( final int blockSize, final int recordSize )
    382     {
    383         m_debug = false;
    384         m_blockSize = blockSize;
    385         m_recordSize = recordSize;
    386         m_recsPerBlock = ( m_blockSize / m_recordSize );
    387         m_blockBuffer = new byte[ m_blockSize ];
    388
    389         if( null != m_input )
    390         {
    391             m_currBlkIdx = -1;
    392             m_currRecIdx = m_recsPerBlock;
    393         }
    394         else
    395         {
    396             m_currBlkIdx = 0;
    397             m_currRecIdx = 0;
    398         }
    399     }
    400
    401     /***
    402      * @return false if End-Of-File, else true
    403      */
    404     private boolean readBlock()
    405         throws IOException
    406     {
    407         if( m_debug )
    408         {
    409             final String message = "ReadBlock: blkIdx = " + m_currBlkIdx;
    410             debug( message );
    411         }
    412
    413         if( null == m_input )
    414         {
    415             final String message = "reading from an output buffer";
    416             throw new IOException( message );
    417         }
    418
    419         m_currRecIdx = 0;
    420
    421         int offset = 0;
    422         int bytesNeeded = m_blockSize;
    423
    424         while( bytesNeeded > 0 )
    425         {
    426             final long numBytes = m_input.read( m_blockBuffer, offset, bytesNeeded );
    427
    428             //
    429             // NOTE
    430             // We have fit EOF, and the block is not full!
    431             //
    432             // This is a broken archive. It does not follow the standard
    433             // blocking algorithm. However, because we are generous, and
    434             // it requires little effort, we will simply ignore the error
    435             // and continue as if the entire block were read. This does
    436             // not appear to break anything upstream. We used to return
    437             // false in this case.
    438             //
    439             // Thanks to 'Yohann.Roussel@alcatel.fr' for this fix.
    440             //
    441             if( numBytes == -1 )
    442             {
    443                 // However, just leaving the unread portion of the buffer dirty does
    444                 // cause problems in some cases.  This problem is described in
    445                 // http://issues.apache.org/bugzilla/show_bug.cgi?id=29877
    446                 //
    447                 // The solution is to fill the unused portion of the buffer with zeros.
    448
    449                 Arrays.fill(m_blockBuffer, offset, offset + bytesNeeded, (byte) 0);
    450
    451                 break;
    452             }
    453
    454             offset += numBytes;
    455             bytesNeeded -= numBytes;
    456
    457             if( numBytes != m_blockSize )
    458             {
    459                 if( m_debug )
    460                 {
    461                     System.err.println( "ReadBlock: INCOMPLETE READ "
    462                                         + numBytes + " of " + m_blockSize
    463                                         + " bytes read." );
    464                 }
    465             }
    466         }
    467
    468         m_currBlkIdx++;
    469
    470         return true;
    471     }
    472
    473     /***
    474      * Write a TarBuffer block to the archive.
    475      *
    476      * @exception IOException Description of Exception
    477      */
    478     private void writeBlock()
    479         throws IOException
    480     {
    481         if( m_debug )
    482         {
    483             final String message = "WriteBlock: blkIdx = " + m_currBlkIdx;
    484             debug( message );
    485         }
    486
    487         if( null == m_output )
    488         {
    489             final String message = "writing to an input buffer";
    490             throw new IOException( message );
    491         }
    492
    493         m_output.write( m_blockBuffer, 0, m_blockSize );
    494         m_output.flush();
    495
    496         m_currRecIdx = 0;
    497         m_currBlkIdx++;
    498     }
    499
    500     protected void debug( final String message )
    501     {
    502         if( m_debug )
    503         {
    504             System.err.println( message );
    505         }
    506     }
    507 }