diff --git a/DiscImageChef.Decoders/ChangeLog b/DiscImageChef.Decoders/ChangeLog index d5df74e5..0efc4273 100644 --- a/DiscImageChef.Decoders/ChangeLog +++ b/DiscImageChef.Decoders/ChangeLog @@ -1,3 +1,12 @@ +2015-12-30 Natalia Portillo + + * SCSI/Inquiry.cs: + Fixes decoding for devices that follow old 5-byte SCSI + INQUIRY format. + + * SCSI/Sense.cs: + Fixes printing of sense block missing a newline. + 2015-12-14 Natalia Portillo * CD/ATIP.cs: diff --git a/DiscImageChef.Decoders/SCSI/Inquiry.cs b/DiscImageChef.Decoders/SCSI/Inquiry.cs index 8cd75661..258783cf 100644 --- a/DiscImageChef.Decoders/SCSI/Inquiry.cs +++ b/DiscImageChef.Decoders/SCSI/Inquiry.cs @@ -61,9 +61,9 @@ namespace DiscImageChef.Decoders.SCSI if (SCSIInquiryResponse == null) return null; - if (SCSIInquiryResponse.Length < 36) + if (SCSIInquiryResponse.Length < 36 && SCSIInquiryResponse.Length != 5) { - DicConsole.DebugWriteLine("SCSI INQUIRY decoder", "INQUIRY response is less than minimum of 36 bytes, decoded data can be incorrect, not decoding."); + DicConsole.DebugWriteLine("SCSI INQUIRY decoder", "INQUIRY response is {0} bytes, less than minimum of 36 bytes, decoded data can be incorrect, not decoding.", SCSIInquiryResponse.Length); return null; } diff --git a/DiscImageChef.Decoders/SCSI/Sense.cs b/DiscImageChef.Decoders/SCSI/Sense.cs index 02ba3cb1..2be7c39e 100644 --- a/DiscImageChef.Decoders/SCSI/Sense.cs +++ b/DiscImageChef.Decoders/SCSI/Sense.cs @@ -425,7 +425,7 @@ namespace DiscImageChef.Decoders.SCSI if (decoded.ILI) sb.AppendLine("Incorrect length indicator"); if (decoded.InformationValid) - sb.AppendFormat("On logical block {0}", decoded.Information); + sb.AppendFormat("On logical block {0}", decoded.Information).AppendLine(); if (decoded.AdditionalLength < 6) return sb.ToString(); diff --git a/DiscImageChef.Devices/ChangeLog b/DiscImageChef.Devices/ChangeLog index 919c206f..f8a9f7d9 100644 --- a/DiscImageChef.Devices/ChangeLog +++ b/DiscImageChef.Devices/ChangeLog @@ -1,3 +1,18 @@ +2015-12-30 Natalia Portillo + + * Device/Variables.cs: + * Device/Constructor.cs: + Added an IsRemovable field. + + * Device/ScsiCommands.cs: + Fixed SCSI READ CAPACITY CDB size. + Fixed READ CD-DA MSF method name. + Implemented SCSI SEEK (6) and SEEK (10) commands. + + * Linux/Command.cs: + * Windows/Command.cs: + Fixed memory leaking on unmanaged heap. + 2015-12-26 Natalia Portillo * Enums.cs: diff --git a/DiscImageChef.Devices/Device/Constructor.cs b/DiscImageChef.Devices/Device/Constructor.cs index b4e87fa8..0f5cf994 100644 --- a/DiscImageChef.Devices/Device/Constructor.cs +++ b/DiscImageChef.Devices/Device/Constructor.cs @@ -53,6 +53,7 @@ namespace DiscImageChef.Devices platformID = Interop.DetectOS.GetRealPlatformID(); Timeout = 15; error = false; + removable = false; switch (platformID) { @@ -115,6 +116,7 @@ namespace DiscImageChef.Devices revision = StringHandlers.SpacePaddedToString(Inquiry.Value.ProductRevisionLevel); model = StringHandlers.SpacePaddedToString(Inquiry.Value.ProductIdentification); manufacturer = StringHandlers.SpacePaddedToString(Inquiry.Value.VendorIdentification); + removable = Inquiry.Value.RMB; scsiType = (Decoders.SCSI.PeripheralDeviceTypes)Inquiry.Value.PeripheralDeviceType; } @@ -124,7 +126,7 @@ namespace DiscImageChef.Devices if (!sense) { type = DeviceType.ATAPI; - Decoders.ATA.Identify.IdentifyDevice? ATAID = Decoders.ATA.Identify.Decode(ataBuf); + Identify.IdentifyDevice? ATAID = Identify.Decode(ataBuf); if (ATAID.HasValue) serial = ATAID.Value.SerialNumber; @@ -137,7 +139,7 @@ namespace DiscImageChef.Devices if (!sense) { type = DeviceType.ATA; - Decoders.ATA.Identify.IdentifyDevice? ATAID = Decoders.ATA.Identify.Decode(ataBuf); + Identify.IdentifyDevice? ATAID = Identify.Decode(ataBuf); if (ATAID.HasValue) { @@ -155,6 +157,11 @@ namespace DiscImageChef.Devices serial = ATAID.Value.SerialNumber; scsiType = Decoders.SCSI.PeripheralDeviceTypes.DirectAccess; + + if ((ushort)ATAID.Value.GeneralConfiguration != 0x848A) + { + removable |= (ATAID.Value.GeneralConfiguration & Identify.GeneralConfigurationBit.Removable) == Identify.GeneralConfigurationBit.Removable; + } } } } diff --git a/DiscImageChef.Devices/Device/ScsiCommands.cs b/DiscImageChef.Devices/Device/ScsiCommands.cs index 6126a750..7754ab46 100644 --- a/DiscImageChef.Devices/Device/ScsiCommands.cs +++ b/DiscImageChef.Devices/Device/ScsiCommands.cs @@ -592,7 +592,7 @@ namespace DiscImageChef.Devices public bool ReadCapacity(out byte[] buffer, out byte[] senseBuffer, bool RelAddr, uint address, bool PMI, uint timeout, out double duration) { senseBuffer = new byte[32]; - byte[] cdb = new byte[12]; + byte[] cdb = new byte[10]; buffer = new byte[8]; bool sense; @@ -1398,7 +1398,7 @@ namespace DiscImageChef.Devices /// End MM:SS:FF of read encoded as 0x00MMSSFF. /// Block size. /// Subchannel selection. - public bool ReadCdMsf(out byte[] buffer, out byte[] senseBuffer, uint startMsf, uint endMsf, uint blockSize, PioneerSubchannel subchannel, uint timeout, out double duration) + public bool ReadCdDaMsf(out byte[] buffer, out byte[] senseBuffer, uint startMsf, uint endMsf, uint blockSize, PioneerSubchannel subchannel, uint timeout, out double duration) { senseBuffer = new byte[32]; byte[] cdb = new byte[12]; @@ -1568,6 +1568,61 @@ namespace DiscImageChef.Devices return sense; } + + /// + /// Moves the device reading element to the specified block address + /// + /// Sense buffer. + /// LBA. + /// Timeout. + /// Duration. + public bool Seek6(out byte[] senseBuffer, uint lba, uint timeout, out double duration) + { + senseBuffer = new byte[32]; + byte[] cdb = new byte[6]; + byte[] buffer = new byte[0]; + bool sense; + + cdb[0] = (byte)ScsiCommands.Seek6; + cdb[1] = (byte)((lba & 0x1F0000) >> 16); + cdb[2] = (byte)((lba & 0xFF00) >> 8); + cdb[3] = (byte)(lba & 0xFF); + + lastError = SendScsiCommand(cdb, ref buffer, out senseBuffer, timeout, ScsiDirection.None, out duration, out sense); + error = lastError != 0; + + DicConsole.DebugWriteLine("SCSI Device", "SEEK (6) took {0} ms.", duration); + + return sense; + } + + /// + /// Moves the device reading element to the specified block address + /// + /// Sense buffer. + /// LBA. + /// Timeout. + /// Duration. + public bool Seek10(out byte[] senseBuffer, uint lba, uint timeout, out double duration) + { + senseBuffer = new byte[32]; + byte[] cdb = new byte[10]; + byte[] buffer = new byte[0]; + bool sense; + + cdb[0] = (byte)ScsiCommands.Seek10; + cdb[2] = (byte)((lba & 0xFF000000) >> 24); + cdb[3] = (byte)((lba & 0xFF0000) >> 16); + cdb[4] = (byte)((lba & 0xFF00) >> 8); + cdb[5] = (byte)(lba & 0xFF); + + lastError = SendScsiCommand(cdb, ref buffer, out senseBuffer, timeout, ScsiDirection.None, out duration, out sense); + error = lastError != 0; + + DicConsole.DebugWriteLine("SCSI Device", "SEEK (10) took {0} ms.", duration); + + return sense; + } } } diff --git a/DiscImageChef.Devices/Device/Variables.cs b/DiscImageChef.Devices/Device/Variables.cs index 53c80a84..6a66f306 100644 --- a/DiscImageChef.Devices/Device/Variables.cs +++ b/DiscImageChef.Devices/Device/Variables.cs @@ -52,6 +52,7 @@ namespace DiscImageChef.Devices readonly string revision; readonly string serial; readonly Decoders.SCSI.PeripheralDeviceTypes scsiType; + readonly bool removable; /// /// Gets the Platform ID for this device @@ -178,6 +179,14 @@ namespace DiscImageChef.Devices return scsiType; } } + + public bool IsRemovable + { + get + { + return removable; + } + } } } diff --git a/DiscImageChef.Devices/Linux/Command.cs b/DiscImageChef.Devices/Linux/Command.cs index e23b66ce..085434b5 100644 --- a/DiscImageChef.Devices/Linux/Command.cs +++ b/DiscImageChef.Devices/Linux/Command.cs @@ -97,6 +97,10 @@ namespace DiscImageChef.Devices.Linux duration = (double)io_hdr.duration; + Marshal.FreeHGlobal(io_hdr.dxferp); + Marshal.FreeHGlobal(io_hdr.cmdp); + Marshal.FreeHGlobal(io_hdr.sbp); + return error; } diff --git a/DiscImageChef.Devices/Windows/Command.cs b/DiscImageChef.Devices/Windows/Command.cs index 804a20c7..84762793 100644 --- a/DiscImageChef.Devices/Windows/Command.cs +++ b/DiscImageChef.Devices/Windows/Command.cs @@ -100,6 +100,8 @@ namespace DiscImageChef.Devices.Windows duration = (end - start).TotalMilliseconds; + Marshal.FreeHGlobal(sptd_sb.sptd.DataBuffer); + return error; } } diff --git a/DiscImageChef.Helpers/ChangeLog b/DiscImageChef.Helpers/ChangeLog index 025495ff..9a709ca0 100644 --- a/DiscImageChef.Helpers/ChangeLog +++ b/DiscImageChef.Helpers/ChangeLog @@ -1,3 +1,8 @@ +2015-12-30 Natalia Portillo + + * StringHandlers.cs: + Fixed string conversion when input byte array is null. + 2015-12-04 Natalia Portillo * StringHandlers.cs: diff --git a/DiscImageChef.Helpers/StringHandlers.cs b/DiscImageChef.Helpers/StringHandlers.cs index a97d8d54..19816cf8 100644 --- a/DiscImageChef.Helpers/StringHandlers.cs +++ b/DiscImageChef.Helpers/StringHandlers.cs @@ -61,6 +61,9 @@ namespace DiscImageChef /// Encoding. public static string CToString(byte[] CString, Encoding encoding) { + if (CString == null) + return null; + StringBuilder sb = new StringBuilder(); for (int i = 0; i < CString.Length; i++) @@ -81,6 +84,9 @@ namespace DiscImageChef /// A length-prefixed (aka Pascal string) ASCII byte array public static string PascalToString(byte[] PascalString) { + if (PascalString == null) + return null; + StringBuilder sb = new StringBuilder(); byte length = PascalString[0]; @@ -100,6 +106,9 @@ namespace DiscImageChef /// A space (' ', 0x20, ASCII SPACE) padded ASCII byte array public static string SpacePaddedToString(byte[] SpacePaddedString) { + if (SpacePaddedString == null) + return null; + int length = 0; for (int i = SpacePaddedString.Length; i >= 0; i--) @@ -114,10 +123,7 @@ namespace DiscImageChef } } - if (length == 0) - return ""; - - return Encoding.ASCII.GetString(SpacePaddedString, 0, length); + return length == 0 ? "" : Encoding.ASCII.GetString(SpacePaddedString, 0, length); } } } diff --git a/DiscImageChef/ChangeLog b/DiscImageChef/ChangeLog index 8c6f5579..5dcecfbb 100644 --- a/DiscImageChef/ChangeLog +++ b/DiscImageChef/ChangeLog @@ -1,3 +1,14 @@ +2015-12-30 Natalia Portillo + + * Main.cs: + * Options.cs: + * DiscImageChef.csproj: + * Commands/MediaScan.cs: + Added media-scan command. + + * Commands/MediaInfo.cs: + Check for inserted medium only on removable media devices. + 2015-12-25 Natalia Portillo * Commands/CreateSidecar.cs: diff --git a/DiscImageChef/Commands/MediaInfo.cs b/DiscImageChef/Commands/MediaInfo.cs index 353a645c..83fee1c1 100644 --- a/DiscImageChef/Commands/MediaInfo.cs +++ b/DiscImageChef/Commands/MediaInfo.cs @@ -47,10 +47,10 @@ namespace DiscImageChef.Commands { public static void doMediaInfo(MediaInfoSubOptions options) { - DicConsole.DebugWriteLine("Device-Info command", "--debug={0}", options.Debug); - DicConsole.DebugWriteLine("Device-Info command", "--verbose={0}", options.Verbose); - DicConsole.DebugWriteLine("Device-Info command", "--device={0}", options.DevicePath); - DicConsole.DebugWriteLine("Device-Info command", "--output-prefix={0}", options.OutputPrefix); + DicConsole.DebugWriteLine("Media-Info command", "--debug={0}", options.Debug); + DicConsole.DebugWriteLine("Media-Info command", "--verbose={0}", options.Verbose); + DicConsole.DebugWriteLine("Media-Info command", "--device={0}", options.DevicePath); + DicConsole.DebugWriteLine("Media-Info command", "--output-prefix={0}", options.OutputPrefix); if (options.DevicePath.Length == 2 && options.DevicePath[1] == ':' && options.DevicePath[0] != '/' && Char.IsLetter(options.DevicePath[0])) @@ -112,33 +112,55 @@ namespace DiscImageChef.Commands ulong blocks = 0; uint blockSize = 0; - sense = dev.ScsiTestUnitReady(out senseBuf, dev.Timeout, out duration); - if (sense) + if (dev.IsRemovable) { - Decoders.SCSI.FixedSense? decSense = Decoders.SCSI.Sense.DecodeFixed(senseBuf); - if (decSense.HasValue) + sense = dev.ScsiTestUnitReady(out senseBuf, dev.Timeout, out duration); + if (sense) { - if (decSense.Value.ASC == 0x3A) + Decoders.SCSI.FixedSense? decSense = Decoders.SCSI.Sense.DecodeFixed(senseBuf); + if (decSense.HasValue) { - DicConsole.ErrorWriteLine("Please insert media in drive"); - return; - } - - if (decSense.Value.ASC == 0x04 && decSense.Value.ASCQ == 0x01) - { - int leftRetries = 10; - while (leftRetries > 0) + if (decSense.Value.ASC == 0x3A) { - DicConsole.WriteLine("\rWaiting for drive to become ready"); - System.Threading.Thread.Sleep(2000); - sense = dev.ScsiTestUnitReady(out senseBuf, dev.Timeout, out duration); - if (!sense) - break; + int leftRetries = 5; + while (leftRetries > 0) + { + DicConsole.WriteLine("\rWaiting for drive to become ready"); + System.Threading.Thread.Sleep(2000); + sense = dev.ScsiTestUnitReady(out senseBuf, dev.Timeout, out duration); + if (!sense) + break; - leftRetries--; + leftRetries--; + } + + if (sense) + { + DicConsole.ErrorWriteLine("Please insert media in drive"); + return; + } } + else if (decSense.Value.ASC == 0x04 && decSense.Value.ASCQ == 0x01) + { + int leftRetries = 10; + while (leftRetries > 0) + { + DicConsole.WriteLine("\rWaiting for drive to become ready"); + System.Threading.Thread.Sleep(2000); + sense = dev.ScsiTestUnitReady(out senseBuf, dev.Timeout, out duration); + if (!sense) + break; - if (sense) + leftRetries--; + } + + if (sense) + { + DicConsole.ErrorWriteLine("Error testing unit was ready:\n{0}", Decoders.SCSI.Sense.PrettifySense(senseBuf)); + return; + } + } + else { DicConsole.ErrorWriteLine("Error testing unit was ready:\n{0}", Decoders.SCSI.Sense.PrettifySense(senseBuf)); return; @@ -146,15 +168,10 @@ namespace DiscImageChef.Commands } else { - DicConsole.ErrorWriteLine("Error testing unit was ready:\n{0}", Decoders.SCSI.Sense.PrettifySense(senseBuf)); + DicConsole.ErrorWriteLine("Unknown testing unit was ready."); return; } } - else - { - DicConsole.ErrorWriteLine("Unknown testing unit was ready."); - return; - } } if (dev.SCSIType == DiscImageChef.Decoders.SCSI.PeripheralDeviceTypes.DirectAccess || @@ -923,7 +940,7 @@ namespace DiscImageChef.Commands { try { - DicConsole.DebugWriteLine("Device-Info command", "Writing " + whatWriting + " to {0}{1}", outputPrefix, outputSuffix); + DicConsole.DebugWriteLine("Media-Info command", "Writing " + whatWriting + " to {0}{1}", outputPrefix, outputSuffix); FileStream outputFs = new FileStream(outputPrefix + outputSuffix, FileMode.CreateNew); outputFs.Write(data, 0, data.Length); outputFs.Close(); diff --git a/DiscImageChef/Commands/MediaScan.cs b/DiscImageChef/Commands/MediaScan.cs new file mode 100644 index 00000000..cd5379e1 --- /dev/null +++ b/DiscImageChef/Commands/MediaScan.cs @@ -0,0 +1,805 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : MediaScan.cs +// Version : 1.0 +// Author(s) : Natalia Portillo +// +// Component : Component +// +// Revision : $Revision$ +// Last change by : $Author$ +// Date : $Date$ +// +// --[ Description ] ---------------------------------------------------------- +// +// Description +// +// --[ License ] -------------------------------------------------------------- +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// ---------------------------------------------------------------------------- +// Copyright (C) 2011-2015 Claunia.com +// ****************************************************************************/ +// //$Id$ +using System; +using DiscImageChef.Console; +using System.IO; +using DiscImageChef.Devices; +using System.Text; +using System.Collections.Generic; + +namespace DiscImageChef.Commands +{ + public static class MediaScan + { + static bool aborted; + static FileStream mhddFs; + + public static void doMediaScan(MediaScanSubOptions options) + { + DicConsole.DebugWriteLine("Media-Scan command", "--debug={0}", options.Debug); + DicConsole.DebugWriteLine("Media-Scan command", "--verbose={0}", options.Verbose); + DicConsole.DebugWriteLine("Media-Scan command", "--device={0}", options.DevicePath); + DicConsole.DebugWriteLine("Media-Scan command", "--mhdd-log={0}", options.MHDDLogPath); + + if (options.DevicePath.Length == 2 && options.DevicePath[1] == ':' && + options.DevicePath[0] != '/' && Char.IsLetter(options.DevicePath[0])) + { + options.DevicePath = "\\\\.\\" + Char.ToUpper(options.DevicePath[0]) + ':'; + } + + mhddFs = null; + + Device dev = new Device(options.DevicePath); + + if (dev.Error) + { + DicConsole.ErrorWriteLine("Error {0} opening device.", dev.LastError); + return; + } + + switch (dev.Type) + { + case DeviceType.ATA: + doATAMediaScan(options.MHDDLogPath, dev); + break; + case DeviceType.MMC: + case DeviceType.SecureDigital: + doSDMediaScan(options.MHDDLogPath, dev); + break; + case DeviceType.NVMe: + doNVMeMediaScan(options.MHDDLogPath, dev); + break; + case DeviceType.ATAPI: + case DeviceType.SCSI: + doSCSIMediaScan(options.MHDDLogPath, dev); + break; + default: + throw new NotSupportedException("Unknown device type."); + } + } + + static void doATAMediaScan(string MHDDLogPath, Device dev) + { + throw new NotImplementedException("ATA devices not yet supported."); + } + + static void doNVMeMediaScan(string MHDDLogPath, Device dev) + { + throw new NotImplementedException("NVMe devices not yet supported."); + } + + static void doSDMediaScan(string MHDDLogPath, Device dev) + { + throw new NotImplementedException("MMC/SD devices not yet supported."); + } + + static void doSCSIMediaScan(string MHDDLogPath, Device dev) + { + byte[] cmdBuf; + byte[] senseBuf; + bool sense = false; + double duration; + ulong blocks = 0; + uint blockSize = 0; + + if (dev.IsRemovable) + { + sense = dev.ScsiTestUnitReady(out senseBuf, dev.Timeout, out duration); + if (sense) + { + Decoders.SCSI.FixedSense? decSense = Decoders.SCSI.Sense.DecodeFixed(senseBuf); + if (decSense.HasValue) + { + if (decSense.Value.ASC == 0x3A) + { + int leftRetries = 5; + while (leftRetries > 0) + { + DicConsole.WriteLine("\rWaiting for drive to become ready"); + System.Threading.Thread.Sleep(2000); + sense = dev.ScsiTestUnitReady(out senseBuf, dev.Timeout, out duration); + if (!sense) + break; + + leftRetries--; + } + + if (sense) + { + DicConsole.ErrorWriteLine("Please insert media in drive"); + return; + } + } + else if (decSense.Value.ASC == 0x04 && decSense.Value.ASCQ == 0x01) + { + int leftRetries = 10; + while (leftRetries > 0) + { + DicConsole.WriteLine("\rWaiting for drive to become ready"); + System.Threading.Thread.Sleep(2000); + sense = dev.ScsiTestUnitReady(out senseBuf, dev.Timeout, out duration); + if (!sense) + break; + + leftRetries--; + } + + if (sense) + { + DicConsole.ErrorWriteLine("Error testing unit was ready:\n{0}", Decoders.SCSI.Sense.PrettifySense(senseBuf)); + return; + } + } + else + { + DicConsole.ErrorWriteLine("Error testing unit was ready:\n{0}", Decoders.SCSI.Sense.PrettifySense(senseBuf)); + return; + } + } + else + { + DicConsole.ErrorWriteLine("Unknown testing unit was ready."); + return; + } + } + } + + if (dev.SCSIType == DiscImageChef.Decoders.SCSI.PeripheralDeviceTypes.DirectAccess || + dev.SCSIType == DiscImageChef.Decoders.SCSI.PeripheralDeviceTypes.MultiMediaDevice || + dev.SCSIType == DiscImageChef.Decoders.SCSI.PeripheralDeviceTypes.OCRWDevice || + dev.SCSIType == DiscImageChef.Decoders.SCSI.PeripheralDeviceTypes.OpticalDevice || + dev.SCSIType == DiscImageChef.Decoders.SCSI.PeripheralDeviceTypes.SimplifiedDevice || + dev.SCSIType == DiscImageChef.Decoders.SCSI.PeripheralDeviceTypes.WriteOnceDevice) + { + sense = dev.ReadCapacity(out cmdBuf, out senseBuf, dev.Timeout, out duration); + if (!sense) + { + blocks = (ulong)((cmdBuf[0] << 24) + (cmdBuf[1] << 16) + (cmdBuf[2] << 8) + (cmdBuf[3])); + blockSize = (uint)((cmdBuf[5] << 24) + (cmdBuf[5] << 16) + (cmdBuf[6] << 8) + (cmdBuf[7])); + } + + if (sense || blocks == 0xFFFFFFFF) + { + sense = dev.ReadCapacity16(out cmdBuf, out senseBuf, dev.Timeout, out duration); + + if (sense && blocks == 0) + { + // Not all MMC devices support READ CAPACITY, as they have READ TOC + if (dev.SCSIType != DiscImageChef.Decoders.SCSI.PeripheralDeviceTypes.MultiMediaDevice) + { + DicConsole.ErrorWriteLine("Unable to get media capacity"); + DicConsole.ErrorWriteLine("{0}", Decoders.SCSI.Sense.PrettifySense(senseBuf)); + } + } + + if (!sense) + { + byte[] temp = new byte[8]; + + Array.Copy(cmdBuf, 0, temp, 0, 8); + Array.Reverse(temp); + blocks = BitConverter.ToUInt64(temp, 0); + blockSize = (uint)((cmdBuf[5] << 24) + (cmdBuf[5] << 16) + (cmdBuf[6] << 8) + (cmdBuf[7])); + } + } + + if (blocks != 0 && blockSize != 0) + { + blocks++; + DicConsole.WriteLine("Media has {0} blocks of {1} bytes/each. (for a total of {2} bytes)", + blocks, blockSize, blocks * (ulong)blockSize); + } + } + + if (dev.SCSIType == DiscImageChef.Decoders.SCSI.PeripheralDeviceTypes.SequentialAccess) + { + DicConsole.WriteLine("Scanning will never be supported on SCSI Streaming Devices."); + DicConsole.WriteLine("It has no sense to do it, and it will put too much strain on the tape."); + return; + } + + if (blocks == 0) + { + DicConsole.ErrorWriteLine("Unable to read medium or empty medium present..."); + return; + } + + bool compactDisc = true; + Decoders.CD.FullTOC.CDFullTOC? toc = null; + + if (dev.SCSIType == DiscImageChef.Decoders.SCSI.PeripheralDeviceTypes.MultiMediaDevice) + { + sense = dev.GetConfiguration(out cmdBuf, out senseBuf, 0, MmcGetConfigurationRt.Current, dev.Timeout, out duration); + if (!sense) + { + Decoders.SCSI.MMC.Features.SeparatedFeatures ftr = Decoders.SCSI.MMC.Features.Separate(cmdBuf); + + switch (ftr.CurrentProfile) + { + case 0x0005: + case 0x0008: + case 0x0009: + case 0x000A: + case 0x0020: + case 0x0021: + case 0x0022: + break; + default: + compactDisc = false; + break; + } + } + + if (compactDisc) + { + // We discarded all discs that falsify a TOC before requesting a real TOC + // No TOC, no CD (or an empty one) + bool tocSense = dev.ReadRawToc(out cmdBuf, out senseBuf, 1, dev.Timeout, out duration); + if (!tocSense) + toc = Decoders.CD.FullTOC.Decode(cmdBuf); + } + } + else + compactDisc = false; + + byte[] readBuffer; + uint blocksToRead = 255; + + ulong A = 0; // <3ms + ulong B = 0; // >=3ms, <10ms + ulong C = 0; // >=10ms, <50ms + ulong D = 0; // >=50ms, <150ms + ulong E = 0; // >=150ms, <500ms + ulong F = 0; // >=500ms + ulong errored = 0; + DateTime start; + DateTime end; + double totalDuration = 0; + double currentSpeed = 0; + double maxSpeed = double.MinValue; + double minSpeed = double.MaxValue; + List unreadableSectors = new List(); + + aborted = false; + System.Console.CancelKeyPress += (sender, e) => + { + e.Cancel = aborted = true; + }; + + bool read6 = false, read10 = false, read12 = false, read16 = false, readcd; + + if (compactDisc) + { + if (toc == null) + { + DicConsole.ErrorWriteLine("Error trying to decode TOC..."); + return; + } + + readcd = !dev.ReadCd(out readBuffer, out senseBuf, 0, 2352, 1, MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, + true, true, MmcErrorField.None, MmcSubchannel.None, dev.Timeout, out duration); + + if (readcd) + DicConsole.WriteLine("Using MMC READ CD command."); + + start = DateTime.UtcNow; + + while (true) + { + if (readcd) + { + sense = dev.ReadCd(out readBuffer, out senseBuf, 0, 2352, blocksToRead, MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, + true, true, MmcErrorField.None, MmcSubchannel.None, dev.Timeout, out duration); + if (dev.Error) + blocksToRead /= 2; + } + + if (!dev.Error || blocksToRead == 1) + break; + } + + if (dev.Error) + { + DicConsole.ErrorWriteLine("Device error {0} trying to guess ideal transfer length.", dev.LastError); + return; + } + + DicConsole.WriteLine("Reading {0} sectors at a time.", blocksToRead); + + initMHDDLogFile(MHDDLogPath, dev, blocks, blockSize, blocksToRead); + + for (ulong i = 0; i < blocks; i += blocksToRead) + { + if (aborted) + break; + + double cmdDuration = 0; + + if ((blocks - i) < blocksToRead) + blocksToRead = (uint)(blocks - i); + + if (currentSpeed > maxSpeed && currentSpeed != 0) + maxSpeed = currentSpeed; + if (currentSpeed < minSpeed && currentSpeed != 0) + minSpeed = currentSpeed; + + DicConsole.Write("\rReading sector {0} of {1} ({2:F3} MiB/sec.)", i, blocks, currentSpeed); + + if (readcd) + { + sense = dev.ReadCd(out readBuffer, out senseBuf, (uint)i, 2352, blocksToRead, MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, + true, true, MmcErrorField.None, MmcSubchannel.None, dev.Timeout, out cmdDuration); + totalDuration += cmdDuration; + } + + if (!sense) + { + if (cmdDuration >= 500) + { + F += blocksToRead; + } + else if (cmdDuration >= 150) + { + E += blocksToRead; + } + else if (cmdDuration >= 50) + { + D += blocksToRead; + } + else if (cmdDuration >= 10) + { + C += blocksToRead; + } + else if (cmdDuration >= 3) + { + B += blocksToRead; + } + else + { + A += blocksToRead; + } + + writeMHDDLogFile(i, cmdDuration); + } + else + { + DicConsole.DebugWriteLine("Media-Scan", "READ CD error:\n{0}", Decoders.SCSI.Sense.PrettifySense(senseBuf)); + + Decoders.SCSI.FixedSense? senseDecoded = Decoders.SCSI.Sense.DecodeFixed(senseBuf); + if (senseDecoded.HasValue) + { + // TODO: This error happens when changing from track type afaik. Need to solve that more cleanly + // LOGICAL BLOCK ADDRESS OUT OF RANGE + if ((senseDecoded.Value.ASC != 0x21 || senseDecoded.Value.ASCQ != 0x00) && + // ILLEGAL MODE FOR THIS TRACK (requesting sectors as-is, this is a firmware misconception when audio sectors + // are in a track where subchannel indicates data) + (senseDecoded.Value.ASC != 0x64 || senseDecoded.Value.ASCQ != 0x00)) + { + errored += blocksToRead; + unreadableSectors.Add(i); + if (cmdDuration < 500) + writeMHDDLogFile(i, 65535); + else + writeMHDDLogFile(i, cmdDuration); + } + } + else + { + errored += blocksToRead; + unreadableSectors.Add(i); + if (cmdDuration < 500) + writeMHDDLogFile(i, 65535); + else + writeMHDDLogFile(i, cmdDuration); + } + } + + currentSpeed = ((double)blockSize * blocksToRead / (double)1048576) / (cmdDuration / (double)1000); + GC.Collect(); + } + end = DateTime.UtcNow; + DicConsole.WriteLine(); + closeMHDDLogFile(); + } + else + { + read6 = !dev.Read6(out readBuffer, out senseBuf, 0, blockSize, dev.Timeout, out duration); + + read10 = !dev.Read10(out readBuffer, out senseBuf, 0, false, true, false, false, 0, blockSize, 0, 1, dev.Timeout, out duration); + + read12 = !dev.Read12(out readBuffer, out senseBuf, 0, false, true, false, false, 0, blockSize, 0, 1, false, dev.Timeout, out duration); + + read16 = !dev.Read16(out readBuffer, out senseBuf, 0, false, true, false, 0, blockSize, 0, 1, false, dev.Timeout, out duration); + + if (!read6 && !read10 && !read12 && !read16) + { + DicConsole.ErrorWriteLine("Cannot read medium, aborting scan..."); + return; + } + + if (read6 && !read10 && !read12 && !read16 && blocks > (0x001FFFFF + 1)) + { + DicConsole.ErrorWriteLine("Device only supports SCSI READ (6) but has more than {0} blocks ({1} blocks total)", 0x001FFFFF + 1, blocks); + return; + } + + if (!read16 && blocks > ((long)0xFFFFFFFF + (long)1)) + { + DicConsole.ErrorWriteLine("Device only supports SCSI READ (16) but has more than {0} blocks ({1} blocks total)", (long)0xFFFFFFFF + (long)1, blocks); + return; + } + + if (read16) + DicConsole.WriteLine("Using SCSI READ (16) command."); + else if (read12) + DicConsole.WriteLine("Using SCSI READ (12) command."); + else if (read10) + DicConsole.WriteLine("Using SCSI READ (10) command."); + else if (read6) + DicConsole.WriteLine("Using SCSI READ (6) command."); + + start = DateTime.UtcNow; + + while (true) + { + if (read16) + { + sense = dev.Read16(out readBuffer, out senseBuf, 0, false, true, false, 0, blockSize, 0, blocksToRead, false, dev.Timeout, out duration); + if (dev.Error) + blocksToRead /= 2; + } + else if (read12) + { + sense = dev.Read12(out readBuffer, out senseBuf, 0, false, false, false, false, 0, blockSize, 0, blocksToRead, false, dev.Timeout, out duration); + if (dev.Error) + blocksToRead /= 2; + } + else if (read10) + { + sense = dev.Read10(out readBuffer, out senseBuf, 0, false, true, false, false, 0, blockSize, 0, (ushort)blocksToRead, dev.Timeout, out duration); + if (dev.Error) + blocksToRead /= 2; + } + else if (read6) + { + sense = dev.Read6(out readBuffer, out senseBuf, 0, blockSize, (byte)blocksToRead, dev.Timeout, out duration); + if (dev.Error) + blocksToRead /= 2; + } + + if (!dev.Error || blocksToRead == 1) + break; + } + + if (dev.Error) + { + DicConsole.ErrorWriteLine("Device error {0} trying to guess ideal transfer length.", dev.LastError); + return; + } + + DicConsole.WriteLine("Reading {0} sectors at a time.", blocksToRead); + + initMHDDLogFile(MHDDLogPath, dev, blocks, blockSize, blocksToRead); + + for (ulong i = 0; i < blocks; i += blocksToRead) + { + if (aborted) + break; + + double cmdDuration = 0; + + if ((blocks - i) < blocksToRead) + blocksToRead = (uint)(blocks - i); + + if (currentSpeed > maxSpeed && currentSpeed != 0) + maxSpeed = currentSpeed; + if (currentSpeed < minSpeed && currentSpeed != 0) + minSpeed = currentSpeed; + + DicConsole.Write("\rReading sector {0} of {1} ({2:F3} MiB/sec.)", i, blocks, currentSpeed); + + if (read16) + { + sense = dev.Read16(out readBuffer, out senseBuf, 0, false, true, false, i, blockSize, 0, blocksToRead, false, dev.Timeout, out cmdDuration); + totalDuration += cmdDuration; + } + else if (read12) + { + sense = dev.Read12(out readBuffer, out senseBuf, 0, false, false, false, false, (uint)i, blockSize, 0, blocksToRead, false, dev.Timeout, out cmdDuration); + totalDuration += cmdDuration; + } + else if (read10) + { + sense = dev.Read10(out readBuffer, out senseBuf, 0, false, true, false, false, (uint)i, blockSize, 0, (ushort)blocksToRead, dev.Timeout, out cmdDuration); + totalDuration += cmdDuration; + } + else if (read6) + { + sense = dev.Read6(out readBuffer, out senseBuf, (uint)i, blockSize, (byte)blocksToRead, dev.Timeout, out cmdDuration); + totalDuration += cmdDuration; + } + + if (!sense && !dev.Error) + { + if (cmdDuration >= 500) + F += blocksToRead; + else if (cmdDuration >= 150) + E += blocksToRead; + else if (cmdDuration >= 50) + D += blocksToRead; + else if (cmdDuration >= 10) + C += blocksToRead; + else if (cmdDuration >= 3) + B += blocksToRead; + else + A += blocksToRead; + + writeMHDDLogFile(i, cmdDuration); + } + // TODO: Separate errors on kind of errors. + else + { + errored += blocksToRead; + unreadableSectors.Add(i); + DicConsole.DebugWriteLine("Media-Scan", "READ error:\n{0}", Decoders.SCSI.Sense.PrettifySense(senseBuf)); + if (cmdDuration < 500) + writeMHDDLogFile(i, 65535); + else + writeMHDDLogFile(i, cmdDuration); + } + + currentSpeed = ((double)blockSize * blocksToRead / (double)1048576) / (cmdDuration / (double)1000); + } + end = DateTime.UtcNow; + DicConsole.WriteLine(); + closeMHDDLogFile(); + } + + bool seek6, seek10; + + seek6 = !dev.Seek6(out senseBuf, 0, dev.Timeout, out duration); + + seek10 = !dev.Seek10(out senseBuf, 0, dev.Timeout, out duration); + + double seekMax = double.MinValue; + double seekMin = double.MaxValue; + double seekTotal = 0; + const int seekTimes = 1000; + + double seekCur = 0; + + Random rnd = new Random(); + + uint seekPos = (uint)rnd.Next((int)blocks); + + if (seek6) + { + dev.Seek6(out senseBuf, seekPos, dev.Timeout, out seekCur); + DicConsole.WriteLine("Using SCSI SEEK (6) command."); + } + else if (seek10) + { + dev.Seek10(out senseBuf, seekPos, dev.Timeout, out seekCur); + DicConsole.WriteLine("Using SCSI SEEK (10) command."); + } + else + { + if (read16) + DicConsole.WriteLine("Using SCSI READ (16) command for seeking."); + else if (read12) + DicConsole.WriteLine("Using SCSI READ (12) command for seeking."); + else if (read10) + DicConsole.WriteLine("Using SCSI READ (10) command for seeking."); + else if (read6) + DicConsole.WriteLine("Using SCSI READ (6) command for seeking."); + } + + for (int i = 0; i < seekTimes; i++) + { + if (aborted) + break; + + seekPos = (uint)rnd.Next((int)blocks); + + DicConsole.Write("\rSeeking to sector {0}...\t\t", seekPos); + + if (seek6) + dev.Seek6(out senseBuf, seekPos, dev.Timeout, out seekCur); + else if (seek10) + dev.Seek10(out senseBuf, seekPos, dev.Timeout, out seekCur); + else + { + if (read16) + { + dev.Read16(out readBuffer, out senseBuf, 0, false, true, false, seekPos, blockSize, 0, 1, false, dev.Timeout, out seekCur); + } + else if (read12) + { + dev.Read12(out readBuffer, out senseBuf, 0, false, false, false, false, seekPos, blockSize, 0, 1, false, dev.Timeout, out seekCur); + } + else if (read10) + { + dev.Read10(out readBuffer, out senseBuf, 0, false, true, false, false, seekPos, blockSize, 0, (ushort)1, dev.Timeout, out seekCur); + } + else if (read6) + { + dev.Read6(out readBuffer, out senseBuf, seekPos, blockSize, 1, dev.Timeout, out seekCur); + } + } + + if (seekCur > seekMax && seekCur != 0) + seekMax = seekCur; + if (seekCur < seekMin && seekCur != 0) + seekMin = seekCur; + + seekTotal += seekCur; + GC.Collect(); + } + + DicConsole.WriteLine(); + + DicConsole.WriteLine("Took a total of {0} seconds ({1} processing commands).", (end - start).TotalSeconds, totalDuration / 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); + DicConsole.WriteLine("Summary:"); + DicConsole.WriteLine("{0} sectors took less than 3 ms.", A); + DicConsole.WriteLine("{0} sectors took less than 10 ms but more than 3 ms.", B); + DicConsole.WriteLine("{0} sectors took less than 50 ms but more than 10 ms.", C); + DicConsole.WriteLine("{0} sectors took less than 150 ms but more than 50 ms.", D); + DicConsole.WriteLine("{0} sectors took less than 500 ms but more than 150 ms.", E); + DicConsole.WriteLine("{0} sectors took more than 500 ms.", F); + DicConsole.WriteLine("{0} sectors could not be read.", errored); + if (unreadableSectors.Count > 0) + { + foreach (ulong bad in unreadableSectors) + DicConsole.WriteLine("Sector {0} could not be read", bad); + } + DicConsole.WriteLine(); + + if (seekTotal != 0 || seekMin != double.MaxValue || seekMax != double.MinValue) + DicConsole.WriteLine("Testing {0} seeks, longest seek took {1} ms, fastest one took {2} ms. ({3} ms average)", + seekTimes, seekMax, seekMin, seekTotal / 1000); + } + + static void initMHDDLogFile(string outputFile, Device dev, ulong blocks, ulong blockSize, ulong blocksToRead) + { + if (dev != null && !string.IsNullOrEmpty(outputFile)) + { + mhddFs = new FileStream(outputFile, FileMode.Create); + + string device; + string mode; + string fw; + string sn; + string sectors; + string sectorsize; + string scanblocksize; + string ver; + + switch (dev.Type) + { + case DeviceType.ATA: + case DeviceType.ATAPI: + mode = "MODE: IDE"; + break; + case DeviceType.SCSI: + mode = "MODE: SCSI"; + break; + case DeviceType.MMC: + mode = "MODE: MMC"; + break; + case DeviceType.NVMe: + mode = "MODE: NVMe"; + break; + case DeviceType.SecureDigital: + mode = "MODE: SD"; + break; + default: + mode = "MODE: IDE"; + break; + } + + device = String.Format("DEVICE: {0} {1}", dev.Manufacturer, dev.Model); + fw = String.Format("F/W: {0}", dev.Revision); + sn = String.Format("S/N: {0}", dev.Serial); + sectors = String.Format(new System.Globalization.CultureInfo("en-US"), "SECTORS: {0:n0}", blocks); + sectorsize = String.Format(new System.Globalization.CultureInfo("en-US"), "SECTOR SIZE: {0:n0} bytes", blockSize); + scanblocksize = String.Format(new System.Globalization.CultureInfo("en-US"), "SCAN BLOCK SIZE: {0:n0} sectors", blocksToRead); + ver = "VER:2 "; + + byte[] deviceBytes = Encoding.ASCII.GetBytes(device); + byte[] modeBytes = Encoding.ASCII.GetBytes(mode); + byte[] fwBytes = Encoding.ASCII.GetBytes(fw); + byte[] snBytes = Encoding.ASCII.GetBytes(sn); + byte[] sectorsBytes = Encoding.ASCII.GetBytes(sectors); + byte[] sectorsizeBytes = Encoding.ASCII.GetBytes(sectorsize); + byte[] scanblocksizeBytes = Encoding.ASCII.GetBytes(scanblocksize); + byte[] verBytes = Encoding.ASCII.GetBytes(ver); + + uint Pointer = (uint)(deviceBytes.Length + modeBytes.Length + fwBytes.Length + + snBytes.Length + sectorsBytes.Length + sectorsizeBytes.Length + + scanblocksizeBytes.Length + verBytes.Length + + 2 * 9 + // New lines + 4); // Pointer + + byte[] newLine = new byte[2]; + newLine[0] = 0x0D; + newLine[1] = 0x0A; + + mhddFs.Write(BitConverter.GetBytes(Pointer), 0, 4); + mhddFs.Write(newLine, 0, 2); + mhddFs.Write(verBytes, 0, verBytes.Length); + mhddFs.Write(newLine, 0, 2); + mhddFs.Write(modeBytes, 0, modeBytes.Length); + mhddFs.Write(newLine, 0, 2); + mhddFs.Write(deviceBytes, 0, deviceBytes.Length); + mhddFs.Write(newLine, 0, 2); + mhddFs.Write(fwBytes, 0, fwBytes.Length); + mhddFs.Write(newLine, 0, 2); + mhddFs.Write(snBytes, 0, snBytes.Length); + mhddFs.Write(newLine, 0, 2); + mhddFs.Write(sectorsBytes, 0, sectorsBytes.Length); + mhddFs.Write(newLine, 0, 2); + mhddFs.Write(sectorsizeBytes, 0, sectorsizeBytes.Length); + mhddFs.Write(newLine, 0, 2); + mhddFs.Write(scanblocksizeBytes, 0, scanblocksizeBytes.Length); + mhddFs.Write(newLine, 0, 2); + } + } + + static void closeMHDDLogFile() + { + if (mhddFs != null) + mhddFs.Close(); + } + + static void writeMHDDLogFile(ulong sector, double duration) + { + if (mhddFs != null) + { + byte[] sectorBytes = BitConverter.GetBytes(sector); + byte[] durationBytes = BitConverter.GetBytes((ulong)(duration * 1000)); + + mhddFs.Write(sectorBytes, 0, 8); + mhddFs.Write(durationBytes, 0, 8); + } + } + } +} + diff --git a/DiscImageChef/DiscImageChef.csproj b/DiscImageChef/DiscImageChef.csproj index f24effaa..e30ef941 100644 --- a/DiscImageChef/DiscImageChef.csproj +++ b/DiscImageChef/DiscImageChef.csproj @@ -57,6 +57,7 @@ False + diff --git a/DiscImageChef/Main.cs b/DiscImageChef/Main.cs index ab5045f9..5a0d2c31 100644 --- a/DiscImageChef/Main.cs +++ b/DiscImageChef/Main.cs @@ -178,6 +178,14 @@ namespace DiscImageChef DicConsole.VerboseWriteLineEvent += System.Console.WriteLine; Commands.CreateSidecar.doSidecar(CreateSidecarOptions); break; + case "media-scan": + MediaScanSubOptions MediaScanOptions = (MediaScanSubOptions)invokedVerbInstance; + if (MediaScanOptions.Debug) + DicConsole.DebugWriteLineEvent += System.Console.Error.WriteLine; + if (MediaScanOptions.Verbose) + DicConsole.VerboseWriteLineEvent += System.Console.WriteLine; + Commands.MediaScan.doMediaScan(MediaScanOptions); + break; default: throw new ArgumentException("Should never arrive here!"); } diff --git a/DiscImageChef/Options.cs b/DiscImageChef/Options.cs index b54949a6..c6c06834 100644 --- a/DiscImageChef/Options.cs +++ b/DiscImageChef/Options.cs @@ -232,6 +232,15 @@ namespace DiscImageChef public string OutputPrefix { get; set; } } + public class MediaScanSubOptions : CommonSubOptions + { + [Option('i', "device", Required = true, HelpText = "Device path.")] + public string DevicePath { get; set; } + + [Option('m', "mhdd-log", Required = false, DefaultValue = "", HelpText = "Write a log of the scan in the format used by MHDD.")] + public string MHDDLogPath { get; set; } + } + public class FormatsSubOptions : CommonSubOptions { } @@ -267,6 +276,7 @@ namespace DiscImageChef MediaInfoVerb = new MediaInfoSubOptions(); BenchmarkVerb = new BenchmarkSubOptions(); CreateSidecarVerb = new CreateSidecarSubOptions(); + MediaScanVerb = new MediaScanSubOptions(); } [VerbOption("analyze", HelpText = "Analyzes a disc image and searches for partitions and/or filesystems.")] @@ -305,6 +315,9 @@ namespace DiscImageChef [VerbOption("create-sidecar", HelpText = "Creates CICM Metadata XML sidecar.")] public CreateSidecarSubOptions CreateSidecarVerb { get; set; } + [VerbOption("media-scan", HelpText = "Scans the media inserted on a device.")] + public MediaScanSubOptions MediaScanVerb { get; set; } + [HelpVerbOption] public string DoHelpForVerb(string verbName) {