예제 #1
0
        /// <summary>
        ///     Creates a metadata sidecar for a block tape (e.g. scsi streaming)
        /// </summary>
        /// <param name="files">List of files</param>
        /// <param name="folderName">Dump path</param>
        /// <param name="blockSize">Expected block size in bytes</param>
        public CICMMetadataType BlockTape(string folderName, List <string> files, uint blockSize)
        {
            sidecar = new CICMMetadataType
            {
                BlockMedia = new[]
                {
                    new BlockMediaType
                    {
                        Image =
                            new ImageType
                        {
                            format = "Directory", offsetSpecified = false, Value = folderName
                        },
                        Sequence =
                            new SequenceType {
                            MediaTitle = folderName, MediaSequence = 1, TotalMedia = 1
                        },
                        PhysicalBlockSize = blockSize,
                        LogicalBlockSize  = blockSize,
                        TapeInformation   = new[]
                        {
                            new TapePartitionType
                            {
                                Image = new ImageType
                                {
                                    format          = "Directory",
                                    offsetSpecified = false,
                                    Value           = folderName
                                }
                            }
                        }
                    }
                }
            };

            if (aborted)
            {
                return(sidecar);
            }

            ulong               currentBlock = 0;
            ulong               totalSize    = 0;
            Checksum            tapeWorker   = new Checksum();
            List <TapeFileType> tapeFiles    = new List <TapeFileType>();

            UpdateStatus("Hashing files...");
            for (int i = 0; i < files.Count; i++)
            {
                if (aborted)
                {
                    return(sidecar);
                }

                fs = new FileStream(files[i], FileMode.Open, FileAccess.Read);
                Checksum     fileWorker = new Checksum();
                TapeFileType tapeFile   = new TapeFileType
                {
                    Image = new ImageType
                    {
                        format = "Raw disk image (sector by sector copy)",
                        offset = 0,
                        Value  = Path.GetFileName(files[i])
                    },
                    Size       = (ulong)fs.Length,
                    BlockSize  = blockSize,
                    StartBlock = currentBlock,
                    Sequence   = (ulong)i
                };

                const uint SECTORS_TO_READ = 512;
                ulong      sectors         = (ulong)fs.Length / blockSize;
                ulong      doneSectors     = 0;

                InitProgress2();
                while (doneSectors < sectors)
                {
                    if (aborted)
                    {
                        EndProgress2();
                        return(sidecar);
                    }

                    byte[] sector;

                    if (sectors - doneSectors >= SECTORS_TO_READ)
                    {
                        sector = new byte[SECTORS_TO_READ * blockSize];
                        fs.Read(sector, 0, sector.Length);
                        UpdateProgress2($"Hashing block {doneSectors} of {sectors} on file {i + 1} of {files.Count}",
                                        (long)doneSectors, (long)sectors);
                        doneSectors += SECTORS_TO_READ;
                    }
                    else
                    {
                        sector = new byte[(uint)(sectors - doneSectors) * blockSize];
                        fs.Read(sector, 0, sector.Length);
                        UpdateProgress2($"Hashing block {doneSectors} of {sectors} on file {i + 1} of {files.Count}",
                                        (long)doneSectors, (long)sectors);
                        doneSectors += sectors - doneSectors;
                    }

                    fileWorker.Update(sector);
                    tapeWorker.Update(sector);
                }

                tapeFile.EndBlock  = tapeFile.StartBlock + sectors - 1;
                currentBlock      += sectors;
                totalSize         += (ulong)fs.Length;
                tapeFile.Checksums = fileWorker.End().ToArray();
                tapeFiles.Add(tapeFile);

                EndProgress2();
            }

            UpdateStatus("Setting metadata...");
            sidecar.BlockMedia[0].Checksums                    = tapeWorker.End().ToArray();
            sidecar.BlockMedia[0].ContentChecksums             = sidecar.BlockMedia[0].Checksums;
            sidecar.BlockMedia[0].Size                         = totalSize;
            sidecar.BlockMedia[0].LogicalBlocks                = currentBlock;
            sidecar.BlockMedia[0].TapeInformation[0].EndBlock  = currentBlock - 1;
            sidecar.BlockMedia[0].TapeInformation[0].Size      = totalSize;
            sidecar.BlockMedia[0].TapeInformation[0].Checksums = sidecar.BlockMedia[0].Checksums;
            sidecar.BlockMedia[0].TapeInformation[0].File      = tapeFiles.ToArray();

            // This is purely for convenience, as typically these kind of data represents QIC tapes
            if (blockSize == 512)
            {
                sidecar.BlockMedia[0].DiskType = "Quarter-inch cartridge";

                if (totalSize <= 20 * 1048576)
                {
                    sidecar.BlockMedia[0].DiskSubType = "QIC-11";
                }
                else if (totalSize <= 40 * 1048576)
                {
                    sidecar.BlockMedia[0].DiskSubType = "QIC-40";
                }
                else if (totalSize <= 60 * 1048576)
                {
                    sidecar.BlockMedia[0].DiskSubType = "QIC-24";
                }
                else if (totalSize <= 80 * 1048576)
                {
                    sidecar.BlockMedia[0].DiskSubType = "QIC-80";
                }
                else if (totalSize <= 120 * 1048576)
                {
                    sidecar.BlockMedia[0].DiskSubType = "QIC-120";
                }
                else if (totalSize <= 150 * 1048576)
                {
                    sidecar.BlockMedia[0].DiskSubType = "QIC-150";
                }
                else if (totalSize <= 320 * 1048576)
                {
                    sidecar.BlockMedia[0].DiskSubType = "QIC-320";
                }
                else if (totalSize <= 340 * 1048576)
                {
                    sidecar.BlockMedia[0].DiskSubType = "QIC-3010";
                }
                else if (totalSize <= 525 * 1048576)
                {
                    sidecar.BlockMedia[0].DiskSubType = "QIC-525";
                }
                else if (totalSize <= 670 * 1048576)
                {
                    sidecar.BlockMedia[0].DiskSubType = "QIC-3020";
                }
                else if (totalSize <= 1200 * 1048576)
                {
                    sidecar.BlockMedia[0].DiskSubType = "QIC-3080";
                }
                else if (totalSize <= 1350 * 1048576)
                {
                    sidecar.BlockMedia[0].DiskSubType = "QIC-1350";
                }
                else if (totalSize <= (long)4000 * 1048576)
                {
                    sidecar.BlockMedia[0].DiskSubType = "QIC-3095";
                }
                else
                {
                    sidecar.BlockMedia[0].DiskType    = "Unknown tape";
                    sidecar.BlockMedia[0].DiskSubType = "Unknown tape";
                }
            }
            else
            {
                sidecar.BlockMedia[0].DiskType    = "Unknown tape";
                sidecar.BlockMedia[0].DiskSubType = "Unknown tape";
            }

            return(sidecar);
        }
예제 #2
0
        /// <summary>
        ///     Dumps the tape from a SCSI Streaming device
        /// </summary>
        /// <param name="dev">Device</param>
        /// <param name="devicePath">Path to the device</param>
        /// <param name="outputPrefix">Prefix for output data files</param>
        /// <param name="resume">Information for dump resuming</param>
        /// <param name="dumpLog">Dump logger</param>
        internal static void Dump(Device dev, string outputPrefix, string devicePath,
                                  ref Resume resume,
                                  ref DumpLog dumpLog, CICMMetadataType preSidecar)
        {
            FixedSense?      fxSense;
            bool             aborted;
            bool             sense;
            ulong            blocks = 0;
            uint             blockSize;
            MediaType        dskType = MediaType.Unknown;
            DateTime         start;
            DateTime         end;
            double           totalDuration    = 0;
            double           totalChkDuration = 0;
            double           currentSpeed     = 0;
            double           maxSpeed         = double.MinValue;
            double           minSpeed         = double.MaxValue;
            CICMMetadataType sidecar          = preSidecar ?? new CICMMetadataType();

            dev.RequestSense(out byte[] senseBuf, dev.Timeout, out double duration);
            fxSense = Sense.DecodeFixed(senseBuf, out string strSense);

            if (fxSense.HasValue && fxSense.Value.SenseKey != SenseKeys.NoSense)
            {
                dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", fxSense.Value.SenseKey,
                                  fxSense.Value.ASC, fxSense.Value.ASCQ);
                DicConsole.ErrorWriteLine("Drive has status error, please correct. Sense follows...");
                DicConsole.ErrorWriteLine("{0}", strSense);
                return;
            }

            // Not in BOM/P
            if (fxSense.HasValue && fxSense.Value.ASC == 0x00 && fxSense.Value.ASCQ != 0x00 &&
                fxSense.Value.ASCQ != 0x04 && fxSense.Value.SenseKey != SenseKeys.IllegalRequest)
            {
                dumpLog.WriteLine("Rewinding, please wait...");
                DicConsole.Write("Rewinding, please wait...");
                // Rewind, let timeout apply
                dev.Rewind(out senseBuf, dev.Timeout, out duration);

                // Still rewinding?
                // TODO: Pause?
                do
                {
                    DicConsole.Write("\rRewinding, please wait...");
                    dev.RequestSense(out senseBuf, dev.Timeout, out duration);
                    fxSense = Sense.DecodeFixed(senseBuf, out strSense);
                }while(fxSense.HasValue && fxSense.Value.ASC == 0x00 &&
                       (fxSense.Value.ASCQ == 0x1A || fxSense.Value.ASCQ != 0x04));

                dev.RequestSense(out senseBuf, dev.Timeout, out duration);
                fxSense = Sense.DecodeFixed(senseBuf, out strSense);

                // And yet, did not rewind!
                if (fxSense.HasValue &&
                    (fxSense.Value.ASC == 0x00 && fxSense.Value.ASCQ != 0x04 || fxSense.Value.ASC != 0x00))
                {
                    DicConsole.WriteLine();
                    DicConsole.ErrorWriteLine("Drive could not rewind, please correct. Sense follows...");
                    DicConsole.ErrorWriteLine("{0}", strSense);
                    dumpLog.WriteLine("Drive could not rewind, please correct. Sense follows...");
                    dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", fxSense.Value.SenseKey,
                                      fxSense.Value.ASC, fxSense.Value.ASCQ);
                    return;
                }

                DicConsole.WriteLine();
            }

            // Check position
            sense = dev.ReadPosition(out byte[] cmdBuf, out senseBuf, SscPositionForms.Short, dev.Timeout,
                                     out duration);

            if (sense)
            {
                // READ POSITION is mandatory starting SCSI-2, so do not cry if the drive does not recognize the command (SCSI-1 or earlier)
                // Anyway, <=SCSI-1 tapes do not support partitions
                fxSense = Sense.DecodeFixed(senseBuf, out strSense);

                if (fxSense.HasValue && (fxSense.Value.ASC == 0x20 && fxSense.Value.ASCQ != 0x00 ||
                                         fxSense.Value.ASC != 0x20 &&
                                         fxSense.Value.SenseKey != SenseKeys.IllegalRequest))
                {
                    DicConsole.ErrorWriteLine("Could not get position. Sense follows...");
                    DicConsole.ErrorWriteLine("{0}", strSense);
                    dumpLog.WriteLine("Could not get position. Sense follows...");
                    dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", fxSense.Value.SenseKey,
                                      fxSense.Value.ASC, fxSense.Value.ASCQ);
                    return;
                }
            }
            else
            {
                // Not in partition 0
                if (cmdBuf[1] != 0)
                {
                    DicConsole.Write("Drive not in partition 0. Rewinding, please wait...");
                    dumpLog.WriteLine("Drive not in partition 0. Rewinding, please wait...");
                    // Rewind, let timeout apply
                    sense = dev.Locate(out senseBuf, false, 0, 0, dev.Timeout, out duration);
                    if (sense)
                    {
                        DicConsole.WriteLine();
                        DicConsole.ErrorWriteLine("Drive could not rewind, please correct. Sense follows...");
                        DicConsole.ErrorWriteLine("{0}", strSense);
                        dumpLog.WriteLine("Drive could not rewind, please correct. Sense follows...");
                        dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h",
                                          fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ);
                        return;
                    }

                    // Still rewinding?
                    // TODO: Pause?
                    do
                    {
                        Thread.Sleep(1000);
                        DicConsole.Write("\rRewinding, please wait...");
                        dev.RequestSense(out senseBuf, dev.Timeout, out duration);
                        fxSense = Sense.DecodeFixed(senseBuf, out strSense);
                    }while(fxSense.HasValue && fxSense.Value.ASC == 0x00 &&
                           (fxSense.Value.ASCQ == 0x1A || fxSense.Value.ASCQ == 0x19));

                    // And yet, did not rewind!
                    if (fxSense.HasValue && (fxSense.Value.ASC == 0x00 && fxSense.Value.ASCQ != 0x04 ||
                                             fxSense.Value.ASC != 0x00))
                    {
                        DicConsole.WriteLine();
                        DicConsole.ErrorWriteLine("Drive could not rewind, please correct. Sense follows...");
                        DicConsole.ErrorWriteLine("{0}", strSense);
                        dumpLog.WriteLine("Drive could not rewind, please correct. Sense follows...");
                        dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h",
                                          fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ);
                        return;
                    }

                    sense = dev.ReadPosition(out cmdBuf, out senseBuf, SscPositionForms.Short, dev.Timeout,
                                             out duration);
                    if (sense)
                    {
                        fxSense = Sense.DecodeFixed(senseBuf, out strSense);
                        DicConsole.ErrorWriteLine("Drive could not rewind, please correct. Sense follows...");
                        DicConsole.ErrorWriteLine("{0}", strSense);
                        dumpLog.WriteLine("Drive could not rewind, please correct. Sense follows...");
                        dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h",
                                          fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ);
                        return;
                    }

                    // Still not in partition 0!!!?
                    if (cmdBuf[1] != 0)
                    {
                        DicConsole.ErrorWriteLine("Drive could not rewind to partition 0 but no error occurred...");
                        dumpLog.WriteLine("Drive could not rewind to partition 0 but no error occurred...");
                        return;
                    }

                    DicConsole.WriteLine();
                }
            }

            sidecar.BlockMedia    = new BlockMediaType[1];
            sidecar.BlockMedia[0] = new BlockMediaType {
                SCSI = new SCSIType()
            };
            byte scsiMediumTypeTape  = 0;
            byte scsiDensityCodeTape = 0;

            dumpLog.WriteLine("Requesting MODE SENSE (10).");
            sense = dev.ModeSense10(out cmdBuf, out senseBuf, false, true, ScsiModeSensePageControl.Current, 0x3F, 0xFF,
                                    5, out duration);
            if (!sense || dev.Error)
            {
                sense = dev.ModeSense10(out cmdBuf, out senseBuf, false, true, ScsiModeSensePageControl.Current, 0x3F,
                                        0x00, 5, out duration);
            }

            Modes.DecodedMode?decMode = null;

            if (!sense && !dev.Error)
            {
                if (Modes.DecodeMode10(cmdBuf, dev.ScsiType).HasValue)
                {
                    decMode = Modes.DecodeMode10(cmdBuf, dev.ScsiType);
                    sidecar.BlockMedia[0].SCSI.ModeSense10 = new DumpType
                    {
                        Image     = outputPrefix + ".modesense10.bin",
                        Size      = cmdBuf.Length,
                        Checksums = Checksum.GetChecksums(cmdBuf).ToArray()
                    };
                    DataFile.WriteTo("SCSI Dump", sidecar.BlockMedia[0].SCSI.ModeSense10.Image, cmdBuf);
                }
            }

            dumpLog.WriteLine("Requesting MODE SENSE (6).");
            sense = dev.ModeSense6(out cmdBuf, out senseBuf, false, ScsiModeSensePageControl.Current, 0x3F, 0x00, 5,
                                   out duration);
            if (sense || dev.Error)
            {
                sense = dev.ModeSense6(out cmdBuf, out senseBuf, false, ScsiModeSensePageControl.Current, 0x3F, 0x00, 5,
                                       out duration);
            }
            if (sense || dev.Error)
            {
                sense = dev.ModeSense(out cmdBuf, out senseBuf, 5, out duration);
            }

            if (!sense && !dev.Error)
            {
                if (Modes.DecodeMode6(cmdBuf, dev.ScsiType).HasValue)
                {
                    decMode = Modes.DecodeMode6(cmdBuf, dev.ScsiType);
                    sidecar.BlockMedia[0].SCSI.ModeSense = new DumpType
                    {
                        Image     = outputPrefix + ".modesense.bin",
                        Size      = cmdBuf.Length,
                        Checksums = Checksum.GetChecksums(cmdBuf).ToArray()
                    };
                    DataFile.WriteTo("SCSI Dump", sidecar.BlockMedia[0].SCSI.ModeSense.Image, cmdBuf);
                }
            }

            // TODO: Check partitions page
            if (decMode.HasValue)
            {
                scsiMediumTypeTape = (byte)decMode.Value.Header.MediumType;
                if (decMode.Value.Header.BlockDescriptors != null && decMode.Value.Header.BlockDescriptors.Length >= 1)
                {
                    scsiDensityCodeTape = (byte)decMode.Value.Header.BlockDescriptors[0].Density;
                }
                blockSize = decMode.Value.Header.BlockDescriptors[0].BlockLength;
                dumpLog.WriteLine("Device reports {0} blocks ({1} bytes).", blocks, blocks * blockSize);
            }
            else
            {
                blockSize = 1;
            }

            if (dskType == MediaType.Unknown)
            {
                dskType = MediaTypeFromScsi.Get((byte)dev.ScsiType, dev.Manufacturer, dev.Model, scsiMediumTypeTape,
                                                scsiDensityCodeTape, blocks, blockSize);
            }

            DicConsole.WriteLine("Media identified as {0}", dskType);

            dumpLog.WriteLine("SCSI device type: {0}.", dev.ScsiType);
            dumpLog.WriteLine("SCSI medium type: {0}.", scsiMediumTypeTape);
            dumpLog.WriteLine("SCSI density type: {0}.", scsiDensityCodeTape);
            dumpLog.WriteLine("Media identified as {0}.", dskType);

            bool  endOfMedia           = false;
            ulong currentBlock         = 0;
            ulong currentFile          = 0;
            byte  currentPartition     = 0;
            byte  totalPartitions      = 1; // TODO: Handle partitions.
            ulong currentSize          = 0;
            ulong currentPartitionSize = 0;
            ulong currentFileSize      = 0;

            bool fixedLen    = false;
            uint transferLen = blockSize;

            sense = dev.Read6(out cmdBuf, out senseBuf, false, fixedLen, transferLen, blockSize, dev.Timeout,
                              out duration);
            if (sense)
            {
                fxSense = Sense.DecodeFixed(senseBuf, out strSense);
                if (fxSense.HasValue)
                {
                    if (fxSense.Value.SenseKey == SenseKeys.IllegalRequest)
                    {
                        sense = dev.Space(out senseBuf, SscSpaceCodes.LogicalBlock, -1, dev.Timeout, out duration);
                        if (sense)
                        {
                            fxSense = Sense.DecodeFixed(senseBuf, out strSense);
                            if (!fxSense.HasValue || !fxSense.Value.EOM)
                            {
                                DicConsole.WriteLine();
                                DicConsole.ErrorWriteLine("Drive could not return back. Sense follows...");
                                DicConsole.ErrorWriteLine("{0}", strSense);
                                dumpLog.WriteLine("Drive could not return back. Sense follows...");
                                dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h",
                                                  fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ);
                                return;
                            }
                        }

                        fixedLen    = true;
                        transferLen = 1;
                        sense       = dev.Read6(out cmdBuf, out senseBuf, false, fixedLen, transferLen, blockSize,
                                                dev.Timeout, out duration);
                        if (sense)
                        {
                            DicConsole.WriteLine();
                            DicConsole.ErrorWriteLine("Drive could not read. Sense follows...");
                            DicConsole.ErrorWriteLine("{0}", strSense);
                            dumpLog.WriteLine("Drive could not read. Sense follows...");
                            dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h",
                                              fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ);
                            return;
                        }
                    }
                    else
                    {
                        DicConsole.WriteLine();
                        DicConsole.ErrorWriteLine("Drive could not read. Sense follows...");
                        DicConsole.ErrorWriteLine("{0}", strSense);
                        dumpLog.WriteLine("Drive could not read. Sense follows...");
                        dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h",
                                          fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ);
                        return;
                    }
                }
                else
                {
                    DicConsole.WriteLine();
                    DicConsole.ErrorWriteLine("Cannot read device, don't know why, exiting...");
                    dumpLog.WriteLine("Cannot read device, don't know why, exiting...");
                    return;
                }
            }

            sense = dev.Space(out senseBuf, SscSpaceCodes.LogicalBlock, -1, dev.Timeout, out duration);
            if (sense)
            {
                fxSense = Sense.DecodeFixed(senseBuf, out strSense);
                if (!fxSense.HasValue || !fxSense.Value.EOM)
                {
                    DicConsole.WriteLine();
                    DicConsole.ErrorWriteLine("Drive could not return back. Sense follows...");
                    DicConsole.ErrorWriteLine("{0}", strSense);
                    dumpLog.WriteLine("Drive could not return back. Sense follows...");
                    dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", fxSense.Value.SenseKey,
                                      fxSense.Value.ASC, fxSense.Value.ASCQ);
                    return;
                }
            }

            List <TapePartitionType> partitions = new List <TapePartitionType>();
            List <TapeFileType>      files      = new List <TapeFileType>();

            DicConsole.WriteLine();
            DataFile dumpFile = new DataFile(outputPrefix + ".bin");
            Checksum dataChk  = new Checksum();

            start = DateTime.UtcNow;
            MhddLog mhddLog = new MhddLog(outputPrefix + ".mhddlog.bin", dev, blocks, blockSize, 1);
            IbgLog  ibgLog  = new IbgLog(outputPrefix + ".ibg", 0x0008);

            TapeFileType currentTapeFile = new TapeFileType
            {
                Image = new ImageType
                {
                    format          = "BINARY",
                    offset          = (long)currentSize,
                    offsetSpecified = true,
                    Value           = outputPrefix + ".bin"
                },
                Sequence   = (long)currentFile,
                StartBlock = (long)currentBlock,
                BlockSize  = blockSize
            };
            Checksum          fileChk = new Checksum();
            TapePartitionType currentTapePartition = new TapePartitionType
            {
                Image = new ImageType
                {
                    format          = "BINARY",
                    offset          = (long)currentSize,
                    offsetSpecified = true,
                    Value           = outputPrefix + ".bin"
                },
                Sequence   = currentPartition,
                StartBlock = (long)currentBlock
            };
            Checksum partitionChk = new Checksum();

            aborted = false;
            System.Console.CancelKeyPress += (sender, e) => e.Cancel = aborted = true;

            while (currentPartition < totalPartitions)
            {
                if (aborted)
                {
                    dumpLog.WriteLine("Aborted!");
                    break;
                }

                if (endOfMedia)
                {
                    DicConsole.WriteLine();
                    DicConsole.WriteLine("Finished partition {0}", currentPartition);
                    dumpLog.WriteLine("Finished partition {0}", currentPartition);
                    currentTapePartition.File      = files.ToArray();
                    currentTapePartition.Checksums = partitionChk.End().ToArray();
                    currentTapePartition.EndBlock  = (long)(currentBlock - 1);
                    currentTapePartition.Size      = (long)currentPartitionSize;
                    partitions.Add(currentTapePartition);

                    currentPartition++;

                    if (currentPartition < totalPartitions)
                    {
                        currentFile++;
                        currentTapeFile = new TapeFileType
                        {
                            Image = new ImageType
                            {
                                format          = "BINARY",
                                offset          = (long)currentSize,
                                offsetSpecified = true,
                                Value           = outputPrefix + ".bin"
                            },
                            Sequence   = (long)currentFile,
                            StartBlock = (long)currentBlock,
                            BlockSize  = blockSize
                        };
                        currentFileSize      = 0;
                        fileChk              = new Checksum();
                        files                = new List <TapeFileType>();
                        currentTapePartition = new TapePartitionType
                        {
                            Image = new ImageType
                            {
                                format          = "BINARY",
                                offset          = (long)currentSize,
                                offsetSpecified = true,
                                Value           = outputPrefix + ".bin"
                            },
                            Sequence   = currentPartition,
                            StartBlock = (long)currentBlock
                        };
                        currentPartitionSize = 0;
                        partitionChk         = new Checksum();
                        DicConsole.WriteLine("Seeking to partition {0}", currentPartition);
                        dev.Locate(out senseBuf, false, currentPartition, 0, dev.Timeout, out duration);
                        totalDuration += duration;
                    }

                    continue;
                }

                #pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator
                if (currentSpeed > maxSpeed && currentSpeed != 0)
                {
                    maxSpeed = currentSpeed;
                }
                if (currentSpeed < minSpeed && currentSpeed != 0)
                {
                    minSpeed = currentSpeed;
                }
                #pragma warning restore RECS0018 // Comparison of floating point numbers with equality operator

                DicConsole.Write("\rReading block {0} ({1:F3} MiB/sec.)", currentBlock, currentSpeed);

                sense = dev.Read6(out cmdBuf, out senseBuf, false, fixedLen, transferLen, blockSize, dev.Timeout,
                                  out duration);
                totalDuration += duration;

                if (sense)
                {
                    fxSense = Sense.DecodeFixed(senseBuf, out strSense);
                    if (fxSense.Value.ASC == 0x00 && fxSense.Value.ASCQ == 0x00 && fxSense.Value.ILI &&
                        fxSense.Value.InformationValid)
                    {
                        blockSize = (uint)((int)blockSize -
                                           BitConverter.ToInt32(BitConverter.GetBytes(fxSense.Value.Information), 0));
                        currentTapeFile.BlockSize = blockSize;

                        DicConsole.WriteLine();
                        DicConsole.WriteLine("Blocksize changed to {0} bytes at block {1}", blockSize, currentBlock);
                        dumpLog.WriteLine("Blocksize changed to {0} bytes at block {1}", blockSize, currentBlock);

                        sense = dev.Space(out senseBuf, SscSpaceCodes.LogicalBlock, -1, dev.Timeout,
                                          out duration);
                        totalDuration += duration;

                        if (sense)
                        {
                            fxSense = Sense.DecodeFixed(senseBuf, out strSense);
                            DicConsole.WriteLine();
                            DicConsole.ErrorWriteLine("Drive could not go back one block. Sense follows...");
                            DicConsole.ErrorWriteLine("{0}", strSense);
                            dumpFile.Close();
                            dumpLog.WriteLine("Drive could not go back one block. Sense follows...");
                            dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h",
                                              fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ);
                            return;
                        }

                        continue;
                    }

                    switch (fxSense.Value.SenseKey)
                    {
                    case SenseKeys.BlankCheck when currentBlock == 0:
                        DicConsole.WriteLine();
                        DicConsole.ErrorWriteLine("Cannot dump a blank tape...");
                        dumpFile.Close();
                        dumpLog.WriteLine("Cannot dump a blank tape...");
                        return;

                    // For sure this is an end-of-tape/partition
                    case SenseKeys.BlankCheck when fxSense.Value.ASC == 0x00 &&
                        (fxSense.Value.ASCQ == 0x02 || fxSense.Value.ASCQ == 0x05 ||
                         fxSense.Value.EOM):
                        // TODO: Detect end of partition
                        endOfMedia = true;
                        dumpLog.WriteLine("Found end-of-tape/partition...");
                        continue;

                    case SenseKeys.BlankCheck:
                        DicConsole.WriteLine();
                        DicConsole.WriteLine("Blank block found, end of tape?");
                        endOfMedia = true;
                        dumpLog.WriteLine("Blank block found, end of tape?...");
                        continue;
                    }

                    if ((fxSense.Value.SenseKey == SenseKeys.NoSense ||
                         fxSense.Value.SenseKey == SenseKeys.RecoveredError) &&
                        (fxSense.Value.ASCQ == 0x02 || fxSense.Value.ASCQ == 0x05 || fxSense.Value.EOM))
                    {
                        // TODO: Detect end of partition
                        endOfMedia = true;
                        dumpLog.WriteLine("Found end-of-tape/partition...");
                        continue;
                    }

                    if ((fxSense.Value.SenseKey == SenseKeys.NoSense ||
                         fxSense.Value.SenseKey == SenseKeys.RecoveredError) &&
                        (fxSense.Value.ASCQ == 0x01 || fxSense.Value.Filemark))
                    {
                        currentTapeFile.Checksums = fileChk.End().ToArray();
                        currentTapeFile.EndBlock  = (long)(currentBlock - 1);
                        currentTapeFile.Size      = (long)currentFileSize;
                        files.Add(currentTapeFile);

                        currentFile++;
                        currentTapeFile = new TapeFileType
                        {
                            Image = new ImageType
                            {
                                format          = "BINARY",
                                offset          = (long)currentSize,
                                offsetSpecified = true,
                                Value           = outputPrefix + ".bin"
                            },
                            Sequence   = (long)currentFile,
                            StartBlock = (long)currentBlock,
                            BlockSize  = blockSize
                        };
                        currentFileSize = 0;
                        fileChk         = new Checksum();

                        DicConsole.WriteLine();
                        DicConsole.WriteLine("Changed to file {0} at block {1}", currentFile, currentBlock);
                        dumpLog.WriteLine("Changed to file {0} at block {1}", currentFile, currentBlock);
                        continue;
                    }

                    // TODO: Add error recovering for tapes
                    fxSense = Sense.DecodeFixed(senseBuf, out strSense);
                    DicConsole.ErrorWriteLine("Drive could not read block. Sense follows...");
                    DicConsole.ErrorWriteLine("{0} {1}", fxSense.Value.SenseKey, strSense);
                    dumpLog.WriteLine("Drive could not read block. Sense follows...");
                    dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", fxSense.Value.SenseKey,
                                      fxSense.Value.ASC, fxSense.Value.ASCQ);
                    return;
                }

                mhddLog.Write(currentBlock, duration);
                ibgLog.Write(currentBlock, currentSpeed * 1024);
                dumpFile.Write(cmdBuf);

                DateTime chkStart = DateTime.UtcNow;
                dataChk.Update(cmdBuf);
                fileChk.Update(cmdBuf);
                partitionChk.Update(cmdBuf);
                DateTime chkEnd      = DateTime.UtcNow;
                double   chkDuration = (chkEnd - chkStart).TotalMilliseconds;
                totalChkDuration += chkDuration;

                if (currentBlock % 10 == 0)
                {
                    double newSpeed = blockSize / (double)1048576 / (duration / 1000);
                    if (!double.IsInfinity(newSpeed))
                    {
                        currentSpeed = newSpeed;
                    }
                }

                currentBlock++;
                currentSize          += blockSize;
                currentFileSize      += blockSize;
                currentPartitionSize += blockSize;
            }

            blocks = currentBlock + 1;
            DicConsole.WriteLine();
            end = DateTime.UtcNow;
            mhddLog.Close();
            ibgLog.Close(dev, blocks, blockSize, (end - start).TotalSeconds, currentSpeed * 1024,
                         blockSize * (double)(blocks + 1) / 1024 / (totalDuration / 1000),
                         devicePath);
            dumpLog.WriteLine("Dump finished in {0} seconds.", (end - start).TotalSeconds);
            dumpLog.WriteLine("Average dump speed {0:F3} KiB/sec.",
                              (double)blockSize * (double)(blocks + 1) / 1024 / (totalDuration / 1000));
            dumpLog.WriteLine("Average checksum speed {0:F3} KiB/sec.",
                              (double)blockSize * (double)(blocks + 1) / 1024 / (totalChkDuration / 1000));

            DicConsole.WriteLine("Took a total of {0:F3} seconds ({1:F3} processing commands, {2:F3} checksumming).",
                                 (end - start).TotalSeconds, totalDuration / 1000, totalChkDuration / 1000);
            DicConsole.WriteLine("Avegare speed: {0:F3} MiB/sec.",
                                 (double)blockSize * (double)(blocks + 1) / 1048576 / (totalDuration / 1000));
            DicConsole.WriteLine("Fastest speed burst: {0:F3} MiB/sec.", maxSpeed);
            DicConsole.WriteLine("Slowest speed burst: {0:F3} MiB/sec.", minSpeed);

            sidecar.BlockMedia[0].Checksums  = dataChk.End().ToArray();
            sidecar.BlockMedia[0].Dimensions = Dimensions.DimensionsFromMediaType(dskType);
            CommonTypes.Metadata.MediaType.MediaTypeToString(dskType, out string xmlDskTyp, out string xmlDskSubTyp);
            sidecar.BlockMedia[0].DiskType    = xmlDskTyp;
            sidecar.BlockMedia[0].DiskSubType = xmlDskSubTyp;
            // TODO: Implement device firmware revision
            sidecar.BlockMedia[0].Image = new ImageType
            {
                format = "Raw disk image (sector by sector copy)",
                Value  = outputPrefix + ".bin"
            };
            sidecar.BlockMedia[0].LogicalBlocks        = (long)blocks;
            sidecar.BlockMedia[0].Size                 = (long)currentSize;
            sidecar.BlockMedia[0].DumpHardwareArray    = new DumpHardwareType[1];
            sidecar.BlockMedia[0].DumpHardwareArray[0] =
                new DumpHardwareType {
                Extents = new ExtentType[1]
            };
            sidecar.BlockMedia[0].DumpHardwareArray[0].Extents[0] =
                new ExtentType {
                Start = 0, End = blocks - 1
            };
            sidecar.BlockMedia[0].DumpHardwareArray[0].Manufacturer = dev.Manufacturer;
            sidecar.BlockMedia[0].DumpHardwareArray[0].Model        = dev.Model;
            sidecar.BlockMedia[0].DumpHardwareArray[0].Revision     = dev.Revision;
            sidecar.BlockMedia[0].DumpHardwareArray[0].Serial       = dev.Serial;
            sidecar.BlockMedia[0].DumpHardwareArray[0].Software     = Version.GetSoftwareType();
            sidecar.BlockMedia[0].TapeInformation = partitions.ToArray();

            if (!aborted)
            {
                DicConsole.WriteLine("Writing metadata sidecar");

                FileStream xmlFs = new FileStream(outputPrefix + ".cicm.xml", FileMode.Create);

                XmlSerializer xmlSer = new XmlSerializer(typeof(CICMMetadataType));
                xmlSer.Serialize(xmlFs, sidecar);
                xmlFs.Close();
            }

            Statistics.AddMedia(dskType, true);
        }