Summary

Class:ICSharpCode.SharpZipLib.Tar.TarBuffer
Assembly:ICSharpCode.SharpZipLib
File(s):C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Tar\TarBuffer.cs
Covered lines:96
Uncovered lines:50
Coverable lines:146
Total lines:551
Line coverage:65.7%
Branch coverage:50%

Metrics

MethodCyclomatic ComplexitySequence CoverageBranch Coverage
GetRecordSize()100
GetBlockFactor()100
.ctor()1100100
CreateInputTarBuffer(...)200
CreateInputTarBuffer(...)377.7860
CreateOutputTarBuffer(...)200
CreateOutputTarBuffer(...)377.7860
Initialize(...)2100100
IsEOFBlock(...)500
IsEndOfArchiveBlock(...)58077.78
SkipBlock()400
ReadBlock()477.7857.14
ReadRecord()491.6771.43
GetCurrentBlockNum()100
GetCurrentRecordNum()100
WriteBlock(...)566.6766.67
WriteBlock(...)764.2961.54
WriteRecord()285.7166.67
WriteFinalRecord()387.560
Close()510088.89

File(s)

C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Tar\TarBuffer.cs

#LineLine coverage
 1using System;
 2using System.IO;
 3
 4namespace ICSharpCode.SharpZipLib.Tar
 5{
 6  /// <summary>
 7  /// The TarBuffer class implements the tar archive concept
 8  /// of a buffered input stream. This concept goes back to the
 9  /// days of blocked tape drives and special io devices. In the
 10  /// C# universe, the only real function that this class
 11  /// performs is to ensure that files have the correct "record"
 12  /// size, or other tars will complain.
 13  /// <p>
 14  /// You should never have a need to access this class directly.
 15  /// TarBuffers are created by Tar IO Streams.
 16  /// </p>
 17  /// </summary>
 18  public class TarBuffer
 19  {
 20
 21    /* A quote from GNU tar man file on blocking and records
 22       A `tar' archive file contains a series of blocks.  Each block
 23    contains `BLOCKSIZE' bytes.  Although this format may be thought of as
 24    being on magnetic tape, other media are often used.
 25
 26       Each file archived is represented by a header block which describes
 27    the file, followed by zero or more blocks which give the contents of
 28    the file.  At the end of the archive file there may be a block filled
 29    with binary zeros as an end-of-file marker.  A reasonable system should
 30    write a block of zeros at the end, but must not assume that such a
 31    block exists when reading an archive.
 32
 33       The blocks may be "blocked" for physical I/O operations.  Each
 34    record of N blocks is written with a single 'write ()'
 35    operation.  On magnetic tapes, the result of such a write is a single
 36    record.  When writing an archive, the last record of blocks should be
 37    written at the full size, with blocks after the zero block containing
 38    all zeros.  When reading an archive, a reasonable system should
 39    properly handle an archive whose last record is shorter than the rest,
 40    or which contains garbage records after a zero block.
 41    */
 42
 43    #region Constants
 44    /// <summary>
 45    /// The size of a block in a tar archive in bytes.
 46    /// </summary>
 47    /// <remarks>This is 512 bytes.</remarks>
 48    public const int BlockSize = 512;
 49
 50    /// <summary>
 51    /// The number of blocks in a default record.
 52    /// </summary>
 53    /// <remarks>
 54    /// The default value is 20 blocks per record.
 55    /// </remarks>
 56    public const int DefaultBlockFactor = 20;
 57
 58    /// <summary>
 59    /// The size in bytes of a default record.
 60    /// </summary>
 61    /// <remarks>
 62    /// The default size is 10KB.
 63    /// </remarks>
 64    public const int DefaultRecordSize = BlockSize * DefaultBlockFactor;
 65    #endregion
 66
 67    /// <summary>
 68    /// Get the record size for this buffer
 69    /// </summary>
 70    /// <value>The record size in bytes.
 71    /// This is equal to the <see cref="BlockFactor"/> multiplied by the <see cref="BlockSize"/></value>
 72    public int RecordSize {
 73      get {
 36074        return recordSize;
 75      }
 76    }
 77
 78    /// <summary>
 79    /// Get the TAR Buffer's record size.
 80    /// </summary>
 81    /// <returns>The record size in bytes.
 82    /// This is equal to the <see cref="BlockFactor"/> multiplied by the <see cref="BlockSize"/></returns>
 83    [Obsolete("Use RecordSize property instead")]
 84    public int GetRecordSize()
 85    {
 086      return recordSize;
 87    }
 88
 89    /// <summary>
 90    /// Get the Blocking factor for the buffer
 91    /// </summary>
 92    /// <value>This is the number of blocks in each record.</value>
 93    public int BlockFactor {
 94      get {
 419895        return blockFactor;
 96      }
 97    }
 98
 99    /// <summary>
 100    /// Get the TAR Buffer's block factor
 101    /// </summary>
 102    /// <returns>The block factor; the number of blocks per record.</returns>
 103    [Obsolete("Use BlockFactor property instead")]
 104    public int GetBlockFactor()
 105    {
 0106      return blockFactor;
 107    }
 108
 109    /// <summary>
 110    /// Construct a default TarBuffer
 111    /// </summary>
 78112    protected TarBuffer()
 113    {
 78114    }
 115
 116    /// <summary>
 117    /// Create TarBuffer for reading with default BlockFactor
 118    /// </summary>
 119    /// <param name="inputStream">Stream to buffer</param>
 120    /// <returns>A new <see cref="TarBuffer"/> suitable for input.</returns>
 121    public static TarBuffer CreateInputTarBuffer(Stream inputStream)
 122    {
 0123       if (inputStream == null) {
 0124        throw new ArgumentNullException(nameof(inputStream));
 125      }
 126
 0127      return CreateInputTarBuffer(inputStream, DefaultBlockFactor);
 128    }
 129
 130    /// <summary>
 131    /// Construct TarBuffer for reading inputStream setting BlockFactor
 132    /// </summary>
 133    /// <param name="inputStream">Stream to buffer</param>
 134    /// <param name="blockFactor">Blocking factor to apply</param>
 135    /// <returns>A new <see cref="TarBuffer"/> suitable for input.</returns>
 136    public static TarBuffer CreateInputTarBuffer(Stream inputStream, int blockFactor)
 137    {
 5138       if (inputStream == null) {
 0139        throw new ArgumentNullException(nameof(inputStream));
 140      }
 141
 5142       if (blockFactor <= 0) {
 0143        throw new ArgumentOutOfRangeException(nameof(blockFactor), "Factor cannot be negative");
 144      }
 145
 5146      var tarBuffer = new TarBuffer();
 5147      tarBuffer.inputStream = inputStream;
 5148      tarBuffer.outputStream = null;
 5149      tarBuffer.Initialize(blockFactor);
 150
 5151      return tarBuffer;
 152    }
 153
 154    /// <summary>
 155    /// Construct TarBuffer for writing with default BlockFactor
 156    /// </summary>
 157    /// <param name="outputStream">output stream for buffer</param>
 158    /// <returns>A new <see cref="TarBuffer"/> suitable for output.</returns>
 159    public static TarBuffer CreateOutputTarBuffer(Stream outputStream)
 160    {
 0161       if (outputStream == null) {
 0162        throw new ArgumentNullException(nameof(outputStream));
 163      }
 164
 0165      return CreateOutputTarBuffer(outputStream, DefaultBlockFactor);
 166    }
 167
 168    /// <summary>
 169    /// Construct TarBuffer for writing Tar output to streams.
 170    /// </summary>
 171    /// <param name="outputStream">Output stream to write to.</param>
 172    /// <param name="blockFactor">Blocking factor to apply</param>
 173    /// <returns>A new <see cref="TarBuffer"/> suitable for output.</returns>
 174    public static TarBuffer CreateOutputTarBuffer(Stream outputStream, int blockFactor)
 175    {
 73176       if (outputStream == null) {
 0177        throw new ArgumentNullException(nameof(outputStream));
 178      }
 179
 73180       if (blockFactor <= 0) {
 0181        throw new ArgumentOutOfRangeException(nameof(blockFactor), "Factor cannot be negative");
 182      }
 183
 73184      var tarBuffer = new TarBuffer();
 73185      tarBuffer.inputStream = null;
 73186      tarBuffer.outputStream = outputStream;
 73187      tarBuffer.Initialize(blockFactor);
 188
 73189      return tarBuffer;
 190    }
 191
 192    /// <summary>
 193    /// Initialization common to all constructors.
 194    /// </summary>
 195    void Initialize(int archiveBlockFactor)
 196    {
 78197      blockFactor = archiveBlockFactor;
 78198      recordSize = archiveBlockFactor * BlockSize;
 78199      recordBuffer = new byte[RecordSize];
 200
 78201       if (inputStream != null) {
 5202        currentRecordIndex = -1;
 5203        currentBlockIndex = BlockFactor;
 5204      } else {
 73205        currentRecordIndex = 0;
 73206        currentBlockIndex = 0;
 207      }
 73208    }
 209
 210    /// <summary>
 211    /// Determine if an archive block indicates End of Archive. End of
 212    /// archive is indicated by a block that consists entirely of null bytes.
 213    /// All remaining blocks for the record should also be null's
 214    /// However some older tars only do a couple of null blocks (Old GNU tar for one)
 215    /// and also partial records
 216    /// </summary>
 217    /// <param name = "block">The data block to check.</param>
 218    /// <returns>Returns true if the block is an EOF block; false otherwise.</returns>
 219    [Obsolete("Use IsEndOfArchiveBlock instead")]
 220    public bool IsEOFBlock(byte[] block)
 221    {
 0222       if (block == null) {
 0223        throw new ArgumentNullException(nameof(block));
 224      }
 225
 0226       if (block.Length != BlockSize) {
 0227        throw new ArgumentException("block length is invalid");
 228      }
 229
 0230       for (int i = 0; i < BlockSize; ++i) {
 0231         if (block[i] != 0) {
 0232          return false;
 233        }
 234      }
 235
 0236      return true;
 237    }
 238
 239
 240    /// <summary>
 241    /// Determine if an archive block indicates the End of an Archive has been reached.
 242    /// End of archive is indicated by a block that consists entirely of null bytes.
 243    /// All remaining blocks for the record should also be null's
 244    /// However some older tars only do a couple of null blocks (Old GNU tar for one)
 245    /// and also partial records
 246    /// </summary>
 247    /// <param name = "block">The data block to check.</param>
 248    /// <returns>Returns true if the block is an EOF block; false otherwise.</returns>
 249    public static bool IsEndOfArchiveBlock(byte[] block)
 250    {
 3251       if (block == null) {
 0252        throw new ArgumentNullException(nameof(block));
 253      }
 254
 3255       if (block.Length != BlockSize) {
 0256        throw new ArgumentException("block length is invalid");
 257      }
 258
 1030259       for (int i = 0; i < BlockSize; ++i) {
 514260         if (block[i] != 0) {
 2261          return false;
 262        }
 263      }
 264
 1265      return true;
 266    }
 267
 268    /// <summary>
 269    /// Skip over a block on the input stream.
 270    /// </summary>
 271    public void SkipBlock()
 272    {
 0273       if (inputStream == null) {
 0274        throw new TarException("no input stream defined");
 275      }
 276
 0277       if (currentBlockIndex >= BlockFactor) {
 0278         if (!ReadRecord()) {
 0279          throw new TarException("Failed to read a record");
 280        }
 281      }
 282
 0283      currentBlockIndex++;
 0284    }
 285
 286    /// <summary>
 287    /// Read a block from the input stream.
 288    /// </summary>
 289    /// <returns>
 290    /// The block of data read.
 291    /// </returns>
 292    public byte[] ReadBlock()
 293    {
 3294       if (inputStream == null) {
 0295        throw new TarException("TarBuffer.ReadBlock - no input stream defined");
 296      }
 297
 3298       if (currentBlockIndex >= BlockFactor) {
 3299         if (!ReadRecord()) {
 0300          throw new TarException("Failed to read a record");
 301        }
 302      }
 303
 3304      byte[] result = new byte[BlockSize];
 305
 3306      Array.Copy(recordBuffer, (currentBlockIndex * BlockSize), result, 0, BlockSize);
 3307      currentBlockIndex++;
 3308      return result;
 309    }
 310
 311    /// <summary>
 312    /// Read a record from data stream.
 313    /// </summary>
 314    /// <returns>
 315    /// false if End-Of-File, else true.
 316    /// </returns>
 317    bool ReadRecord()
 318    {
 3319       if (inputStream == null) {
 0320        throw new TarException("no input stream stream defined");
 321      }
 322
 3323      currentBlockIndex = 0;
 324
 3325      int offset = 0;
 3326      int bytesNeeded = RecordSize;
 327
 6328       while (bytesNeeded > 0) {
 3329        long numBytes = inputStream.Read(recordBuffer, offset, bytesNeeded);
 330
 331        //
 332        // NOTE
 333        // We have found EOF, and the record is not full!
 334        //
 335        // This is a broken archive. It does not follow the standard
 336        // blocking algorithm. However, because we are generous, and
 337        // it requires little effort, we will simply ignore the error
 338        // and continue as if the entire record were read. This does
 339        // not appear to break anything upstream. We used to return
 340        // false in this case.
 341        //
 342        // Thanks to 'Yohann.Roussel@alcatel.fr' for this fix.
 343        //
 3344         if (numBytes <= 0) {
 345          break;
 346        }
 347
 3348        offset += (int)numBytes;
 3349        bytesNeeded -= (int)numBytes;
 350      }
 351
 3352      currentRecordIndex++;
 3353      return true;
 354    }
 355
 356    /// <summary>
 357    /// Get the current block number, within the current record, zero based.
 358    /// </summary>
 359    /// <remarks>Block numbers are zero based values</remarks>
 360    /// <seealso cref="RecordSize"/>
 361    public int CurrentBlock {
 0362      get { return currentBlockIndex; }
 363    }
 364
 365    /// <summary>
 366    /// Get/set flag indicating ownership of the underlying stream.
 367    /// When the flag is true <see cref="Close"></see> will close the underlying stream also.
 368    /// </summary>
 369    public bool IsStreamOwner {
 0370      get { return isStreamOwner_; }
 4371      set { isStreamOwner_ = value; }
 372    }
 373
 374    /// <summary>
 375    /// Get the current block number, within the current record, zero based.
 376    /// </summary>
 377    /// <returns>
 378    /// The current zero based block number.
 379    /// </returns>
 380    /// <remarks>
 381    /// The absolute block number = (<see cref="GetCurrentRecordNum">record number</see> * <see cref="BlockFactor">block
 382    /// </remarks>
 383    [Obsolete("Use CurrentBlock property instead")]
 384    public int GetCurrentBlockNum()
 385    {
 0386      return currentBlockIndex;
 387    }
 388
 389    /// <summary>
 390    /// Get the current record number.
 391    /// </summary>
 392    /// <returns>
 393    /// The current zero based record number.
 394    /// </returns>
 395    public int CurrentRecord {
 0396      get { return currentRecordIndex; }
 397    }
 398
 399    /// <summary>
 400    /// Get the current record number.
 401    /// </summary>
 402    /// <returns>
 403    /// The current zero based record number.
 404    /// </returns>
 405    [Obsolete("Use CurrentRecord property instead")]
 406    public int GetCurrentRecordNum()
 407    {
 0408      return currentRecordIndex;
 409    }
 410
 411    /// <summary>
 412    /// Write a block of data to the archive.
 413    /// </summary>
 414    /// <param name="block">
 415    /// The data to write to the archive.
 416    /// </param>
 417    public void WriteBlock(byte[] block)
 418    {
 148419       if (block == null) {
 0420        throw new ArgumentNullException(nameof(block));
 421      }
 422
 148423       if (outputStream == null) {
 0424        throw new TarException("TarBuffer.WriteBlock - no output stream defined");
 425      }
 426
 148427       if (block.Length != BlockSize) {
 0428        string errorText = string.Format("TarBuffer.WriteBlock - block to write has length '{0}' which is not the block 
 0429          block.Length, BlockSize);
 0430        throw new TarException(errorText);
 431      }
 432
 148433       if (currentBlockIndex >= BlockFactor) {
 4434        WriteRecord();
 435      }
 436
 148437      Array.Copy(block, 0, recordBuffer, (currentBlockIndex * BlockSize), BlockSize);
 148438      currentBlockIndex++;
 148439    }
 440
 441    /// <summary>
 442    /// Write an archive record to the archive, where the record may be
 443    /// inside of a larger array buffer. The buffer must be "offset plus
 444    /// record size" long.
 445    /// </summary>
 446    /// <param name="buffer">
 447    /// The buffer containing the record data to write.
 448    /// </param>
 449    /// <param name="offset">
 450    /// The offset of the record data within buffer.
 451    /// </param>
 452    public void WriteBlock(byte[] buffer, int offset)
 453    {
 4042454       if (buffer == null) {
 0455        throw new ArgumentNullException(nameof(buffer));
 456      }
 457
 4042458       if (outputStream == null) {
 0459        throw new TarException("TarBuffer.WriteBlock - no output stream stream defined");
 460      }
 461
 4042462       if ((offset < 0) || (offset >= buffer.Length)) {
 0463        throw new ArgumentOutOfRangeException(nameof(offset));
 464      }
 465
 4042466       if ((offset + BlockSize) > buffer.Length) {
 0467        string errorText = string.Format("TarBuffer.WriteBlock - record has length '{0}' with offset '{1}' which is less
 0468          buffer.Length, offset, recordSize);
 0469        throw new TarException(errorText);
 470      }
 471
 4042472       if (currentBlockIndex >= BlockFactor) {
 128473        WriteRecord();
 474      }
 475
 4042476      Array.Copy(buffer, offset, recordBuffer, (currentBlockIndex * BlockSize), BlockSize);
 477
 4042478      currentBlockIndex++;
 4042479    }
 480
 481    /// <summary>
 482    /// Write a TarBuffer record to the archive.
 483    /// </summary>
 484    void WriteRecord()
 485    {
 205486       if (outputStream == null) {
 0487        throw new TarException("TarBuffer.WriteRecord no output stream defined");
 488      }
 489
 205490      outputStream.Write(recordBuffer, 0, RecordSize);
 205491      outputStream.Flush();
 492
 205493      currentBlockIndex = 0;
 205494      currentRecordIndex++;
 205495    }
 496
 497    /// <summary>
 498    /// WriteFinalRecord writes the current record buffer to output any unwritten data is present.
 499    /// </summary>
 500    /// <remarks>Any trailing bytes are set to zero which is by definition correct behaviour
 501    /// for the end of a tar stream.</remarks>
 502    void WriteFinalRecord()
 503    {
 73504       if (outputStream == null) {
 0505        throw new TarException("TarBuffer.WriteFinalRecord no output stream defined");
 506      }
 507
 73508       if (currentBlockIndex > 0) {
 73509        int dataBytes = currentBlockIndex * BlockSize;
 73510        Array.Clear(recordBuffer, dataBytes, RecordSize - dataBytes);
 73511        WriteRecord();
 512      }
 513
 73514      outputStream.Flush();
 73515    }
 516
 517    /// <summary>
 518    /// Close the TarBuffer. If this is an output buffer, also flush the
 519    /// current block before closing.
 520    /// </summary>
 521    public void Close()
 522    {
 78523       if (outputStream != null) {
 73524        WriteFinalRecord();
 525
 73526         if (isStreamOwner_) {
 72527          outputStream.Close();
 528        }
 73529        outputStream = null;
 78530       } else if (inputStream != null) {
 5531         if (isStreamOwner_) {
 4532          inputStream.Close();
 533        }
 5534        inputStream = null;
 535      }
 5536    }
 537
 538    #region Instance Fields
 539    Stream inputStream;
 540    Stream outputStream;
 541
 542    byte[] recordBuffer;
 543    int currentBlockIndex;
 544    int currentRecordIndex;
 545
 78546    int recordSize = DefaultRecordSize;
 78547    int blockFactor = DefaultBlockFactor;
 78548    bool isStreamOwner_ = true;
 549    #endregion
 550  }
 551}