2017-05-19 20:28:49 +01:00
|
|
|
// /***************************************************************************
|
2015-10-12 19:55:00 +01:00
|
|
|
// The Disc Image Chef
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
//
|
2016-07-28 18:13:49 +01:00
|
|
|
// Filename : Constructor.cs
|
|
|
|
|
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
2015-10-12 19:55:00 +01:00
|
|
|
//
|
2016-07-28 18:13:49 +01:00
|
|
|
// Component : Direct device access.
|
2015-10-12 19:55:00 +01:00
|
|
|
//
|
|
|
|
|
// --[ Description ] ----------------------------------------------------------
|
|
|
|
|
//
|
2016-07-28 18:13:49 +01:00
|
|
|
// Prepares a device for direct access.
|
2015-10-12 19:55:00 +01:00
|
|
|
//
|
|
|
|
|
// --[ License ] --------------------------------------------------------------
|
|
|
|
|
//
|
2016-07-28 18:13:49 +01:00
|
|
|
// This library is free software; you can redistribute it and/or modify
|
|
|
|
|
// it under the terms of the GNU Lesser General Public License as
|
|
|
|
|
// published by the Free Software Foundation; either version 2.1 of the
|
2015-10-12 19:55:00 +01:00
|
|
|
// License, or (at your option) any later version.
|
|
|
|
|
//
|
2016-07-28 18:13:49 +01:00
|
|
|
// This library 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
|
|
|
|
|
// Lesser General Public License for more details.
|
2015-10-12 19:55:00 +01:00
|
|
|
//
|
2016-07-28 18:13:49 +01:00
|
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
|
|
|
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
2015-10-12 19:55:00 +01:00
|
|
|
//
|
|
|
|
|
// ----------------------------------------------------------------------------
|
2018-12-29 17:34:38 +00:00
|
|
|
// Copyright © 2011-2019 Natalia Portillo
|
2015-10-12 19:55:00 +01:00
|
|
|
// ****************************************************************************/
|
2016-07-28 18:13:49 +01:00
|
|
|
|
2015-10-12 19:55:00 +01:00
|
|
|
using System;
|
2017-12-21 14:30:38 +00:00
|
|
|
using System.Globalization;
|
|
|
|
|
using System.IO;
|
2017-12-21 07:08:26 +00:00
|
|
|
using System.Linq;
|
2015-10-13 01:45:07 +01:00
|
|
|
using System.Runtime.InteropServices;
|
2018-11-27 00:09:53 +00:00
|
|
|
using DiscImageChef.CommonTypes.Enums;
|
2018-06-25 19:08:16 +01:00
|
|
|
using DiscImageChef.CommonTypes.Interop;
|
2015-11-23 04:26:53 +00:00
|
|
|
using DiscImageChef.Decoders.ATA;
|
2017-12-21 14:30:38 +00:00
|
|
|
using DiscImageChef.Decoders.SCSI;
|
2019-02-12 23:04:53 +00:00
|
|
|
using DiscImageChef.Decoders.SCSI.MMC;
|
2017-12-21 14:30:38 +00:00
|
|
|
using DiscImageChef.Devices.FreeBSD;
|
|
|
|
|
using DiscImageChef.Devices.Windows;
|
2017-12-19 19:33:46 +00:00
|
|
|
using Microsoft.Win32.SafeHandles;
|
2017-12-21 14:30:38 +00:00
|
|
|
using Extern = DiscImageChef.Devices.Windows.Extern;
|
|
|
|
|
using FileAccess = DiscImageChef.Devices.Windows.FileAccess;
|
|
|
|
|
using FileAttributes = DiscImageChef.Devices.Windows.FileAttributes;
|
|
|
|
|
using FileFlags = DiscImageChef.Devices.Linux.FileFlags;
|
|
|
|
|
using FileMode = DiscImageChef.Devices.Windows.FileMode;
|
|
|
|
|
using FileShare = DiscImageChef.Devices.Windows.FileShare;
|
2018-06-25 19:08:16 +01:00
|
|
|
using PlatformID = DiscImageChef.CommonTypes.Interop.PlatformID;
|
2017-12-21 14:30:38 +00:00
|
|
|
using VendorString = DiscImageChef.Decoders.SecureDigital.VendorString;
|
2015-10-12 19:55:00 +01:00
|
|
|
|
|
|
|
|
namespace DiscImageChef.Devices
|
|
|
|
|
{
|
|
|
|
|
public partial class Device
|
|
|
|
|
{
|
2015-10-12 20:08:56 +01:00
|
|
|
/// <summary>
|
2017-12-23 20:04:36 +00:00
|
|
|
/// Opens the device for sending direct commands
|
2015-10-12 20:08:56 +01:00
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="devicePath">Device path</param>
|
2015-10-12 19:55:00 +01:00
|
|
|
public Device(string devicePath)
|
|
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
PlatformId = DetectOS.GetRealPlatformID();
|
|
|
|
|
Timeout = 15;
|
|
|
|
|
Error = false;
|
2017-12-21 07:19:46 +00:00
|
|
|
IsRemovable = false;
|
2015-10-12 19:55:00 +01:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (devicePath.StartsWith("dic://"))
|
|
|
|
|
{
|
|
|
|
|
devicePath = devicePath.Substring(6);
|
|
|
|
|
var pieces = devicePath.Split('/');
|
|
|
|
|
var host = pieces[0];
|
|
|
|
|
devicePath = devicePath.Substring(host.Length);
|
|
|
|
|
|
|
|
|
|
remote = new Remote.Remote(host);
|
|
|
|
|
|
2019-10-13 22:39:47 +01:00
|
|
|
Error = !remote.Open(devicePath, out var errno);
|
2019-10-13 22:29:09 +01:00
|
|
|
LastError = errno;
|
2019-10-13 21:17:38 +01:00
|
|
|
}
|
2019-10-13 22:29:09 +01:00
|
|
|
else
|
2015-10-12 19:55:00 +01:00
|
|
|
{
|
2019-10-13 22:29:09 +01:00
|
|
|
switch (PlatformId)
|
2017-12-19 20:33:03 +00:00
|
|
|
{
|
2019-10-13 22:29:09 +01:00
|
|
|
case PlatformID.Win32NT:
|
2015-10-12 19:55:00 +01:00
|
|
|
{
|
2019-10-13 22:29:09 +01:00
|
|
|
FileHandle = Extern.CreateFile(devicePath, FileAccess.GenericRead | FileAccess.GenericWrite,
|
|
|
|
|
FileShare.Read | FileShare.Write, IntPtr.Zero,
|
|
|
|
|
FileMode.OpenExisting,
|
|
|
|
|
FileAttributes.Normal, IntPtr.Zero);
|
2015-10-13 01:45:07 +01:00
|
|
|
|
2019-10-13 22:29:09 +01:00
|
|
|
if (((SafeFileHandle) FileHandle).IsInvalid)
|
|
|
|
|
{
|
|
|
|
|
Error = true;
|
|
|
|
|
LastError = Marshal.GetLastWin32Error();
|
|
|
|
|
}
|
2015-10-12 19:55:00 +01:00
|
|
|
|
2019-10-13 22:29:09 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case PlatformID.Linux:
|
2017-12-19 20:33:03 +00:00
|
|
|
{
|
2019-10-13 22:29:09 +01:00
|
|
|
FileHandle =
|
|
|
|
|
Linux.Extern.open(devicePath,
|
|
|
|
|
FileFlags.ReadWrite | FileFlags.NonBlocking | FileFlags.CreateNew);
|
2018-04-02 23:09:18 +01:00
|
|
|
|
2019-10-13 22:29:09 +01:00
|
|
|
if ((int) FileHandle < 0)
|
2018-04-02 23:09:18 +01:00
|
|
|
{
|
2019-10-13 22:29:09 +01:00
|
|
|
LastError = Marshal.GetLastWin32Error();
|
|
|
|
|
|
|
|
|
|
if (LastError == 13 || LastError == 30) // EACCES or EROFS
|
|
|
|
|
{
|
|
|
|
|
FileHandle = Linux.Extern.open(devicePath, FileFlags.Readonly | FileFlags.NonBlocking);
|
|
|
|
|
if ((int) FileHandle < 0)
|
|
|
|
|
{
|
|
|
|
|
Error = true;
|
|
|
|
|
LastError = Marshal.GetLastWin32Error();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
2018-04-02 23:09:18 +01:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
Error = true;
|
2018-04-02 23:09:18 +01:00
|
|
|
}
|
2019-10-13 22:29:09 +01:00
|
|
|
|
|
|
|
|
LastError = Marshal.GetLastWin32Error();
|
2018-04-02 23:09:18 +01:00
|
|
|
}
|
2019-10-13 22:29:09 +01:00
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case PlatformID.FreeBSD:
|
|
|
|
|
{
|
|
|
|
|
FileHandle = FreeBSD.Extern.cam_open_device(devicePath, FreeBSD.FileFlags.ReadWrite);
|
|
|
|
|
|
|
|
|
|
if (((IntPtr) FileHandle).ToInt64() == 0)
|
2019-10-13 21:17:38 +01:00
|
|
|
{
|
|
|
|
|
Error = true;
|
2019-10-13 22:29:09 +01:00
|
|
|
LastError = Marshal.GetLastWin32Error();
|
2019-10-13 21:17:38 +01:00
|
|
|
}
|
2018-04-02 23:09:18 +01:00
|
|
|
|
2019-10-13 22:29:09 +01:00
|
|
|
var camDevice = (CamDevice) Marshal.PtrToStructure((IntPtr) FileHandle, typeof(CamDevice));
|
2017-12-19 20:33:03 +00:00
|
|
|
|
2019-10-13 22:29:09 +01:00
|
|
|
if (StringHandlers.CToString(camDevice.SimName) == "ata")
|
|
|
|
|
throw new
|
|
|
|
|
InvalidOperationException(
|
|
|
|
|
"Parallel ATA devices are not supported on FreeBSD due to upstream bug #224250.");
|
2017-12-10 21:00:20 +00:00
|
|
|
|
2019-10-13 22:29:09 +01:00
|
|
|
break;
|
2017-12-10 21:00:20 +00:00
|
|
|
}
|
2019-10-13 22:29:09 +01:00
|
|
|
default: throw new InvalidOperationException($"Platform {PlatformId} not yet supported.");
|
2017-12-10 21:00:20 +00:00
|
|
|
}
|
2015-10-12 19:55:00 +01:00
|
|
|
}
|
2015-10-19 04:46:09 +01:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (Error) throw new SystemException($"Error {LastError} opening device.");
|
* DiscImageChef.CommonTypes/MediaTypeFromSCSI.cs:
* DiscImageChef.CommonTypes/DiscImageChef.CommonTypes.csproj:
Added method to calculate MediaType from SCSI parameters
(mode, density, medium type, device type, etc).
* DiscImageChef.Metadata/DeviceReport.cs:
Added command to guess drive and media parameters and output
an XML report of them.
* DiscImageChef/Commands/DeviceReport.cs:
* DiscImageChef.Metadata/DiscImageChef.Metadata.csproj:
Added command to guess drive and media parameters and output
an XML report of them.
* DiscImageChef/Commands/DumpMedia.cs:
Added preliminary command to dump media. Only SCSI for now.
CDs and tapes are not supported. Errors are blalanty
ignored. Options are incomplete. Not yet usable.
* DiscImageChef/Core/Checksum.cs:
* DiscImageChef/Commands/CreateSidecar.cs:
Moved checksum generation to a separate class.
* CICMMetadata:
Added support for ADIP.
* DiscImageChef.CommonTypes/MediaType.cs:
Added parameters of UDO media.
Moved DataPlay outside of Iomega, as it's not from that
manufacturer.
Added missing Exatape media and corrected 160m XL one.
Added SyJet media.
Added all ECMA defined magneto-optical (sectors calculated
from specifications, unchecked).
Added PD media.
Added Imation 320Gb RDX.
Added generic USB flash drives.
* DiscImageChef.Decoders/SCSI/Enums.cs:
Make enumerations public.
* DiscImageChef.Decoders/SCSI/Inquiry.cs:
* DiscImageChef.Devices/Device/Constructor.cs:
Trim space padded strings on SCSI INQUIRY.
* DiscImageChef.Devices/Device/ScsiCommands/MMC.cs:
Added PREVENT ALLOW MEDIUM REMOVAL.
Added START STOP UNIT.
* DiscImageChef.Devices/Device/ScsiCommands/NEC.cs:
Rename NEC methods.
* DiscImageChef.Devices/Device/ScsiCommands/Pioneer.cs:
Corrected Pioneer transfer length calculation.
* DiscImageChef.Devices/Device/ScsiCommands/Plextor.cs:
Renamed Plextor methods.
* DiscImageChef.Devices/Device/ScsiCommands/SPC.cs:
Renamed SSC PREVENT ALLOW MEDIUM REMOVAL to uncollide with
MMC same name but different command.
* DiscImageChef.Devices/DiscImageChef.Devices.csproj:
Set platform target to x86 (does it really matter?).
* DiscImageChef.Devices/Linux/Command.cs:
Reduced allocation for readlink() to current kernel
MAX_PATH.
* DiscImageChef.Devices/Linux/Enums.cs:
Modified Linux ioctl to 32-bit. Works on 64-bit also. Solves
commands not working on 32-bit environments.
* DiscImageChef.DiscImages/ZZZRawImage.cs:
Changed ECMA-184 and ECMA-183 enums.
* DiscImageChef.Metadata/Dimensions.cs:
Added all ECMA defined magneto-opticals.
Added PD media.
Added 320Gb RDX.
Corrected Exatape 160m XL.
Added Exatape 22m and 28m.
* DiscImageChef.Metadata/MediaType.cs:
Added 356mm magneto-optical media.
Changed ECMA-184 and ECMA-183 enums.
Added USB generic flash drive.
* DiscImageChef/Commands/DeviceInfo.cs:
Corrected SCSI INQUIRY naming.
Corrected SCSI MODE SENSE (6) parameters.
Reduced SCSI MODE SENSE timeout, some devices just get stuck
with unsupported MODE SENSE commanda and must be left to
timeout.
Changed FUJITSU vendor string comparison.
* DiscImageChef/Commands/MediaInfo.cs:
Added method to calculate MediaType from SCSI parameters
(mode, density, medium type, device type, etc).
Changed some error WriteLine() to debug ones. Too much
verbosity.
Added DVD media type decoding from PFI.
Found a drive that dumps ADIP, enabling it again (not
decoded).
* DiscImageChef/Commands/MediaScan.cs:
Added option to generate ImgBurn compatible log to
media-scan command.
* DiscImageChef/DiscImageChef.csproj:
Moved checksum generation to a separate class.
Added command to guess drive and media parameters and output
an XML report of them.
Added preliminary command to dump media. Only SCSI for now.
CDs and tapes are not supported. Errors are blalanty
ignored. Options are incomplete. Not yet usable.
* DiscImageChef/Main.cs:
Added command to guess drive and media parameters and output
an XML report of them.
Added preliminary command to dump media. Only SCSI for now.
CDs and tapes are not supported. Errors are blalanty
ignored. Options are incomplete. Not yet usable.
* DiscImageChef/Options.cs:
Added command to guess drive and media parameters and output
an XML report of them.
Added preliminary command to dump media. Only SCSI for now.
CDs and tapes are not supported. Errors are blalanty
ignored. Options are incomplete. Not yet usable.
Added option to generate ImgBurn compatible log to media-scan
command.
2016-01-31 08:05:56 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
Type = DeviceType.Unknown;
|
2017-12-21 14:30:38 +00:00
|
|
|
ScsiType = PeripheralDeviceTypes.UnknownDevice;
|
2015-10-19 05:11:28 +01:00
|
|
|
|
|
|
|
|
byte[] ataBuf;
|
2017-09-07 16:47:06 +01:00
|
|
|
byte[] inqBuf = null;
|
2015-10-19 05:11:28 +01:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (Error) throw new SystemException($"Error {LastError} trying device.");
|
* DiscImageChef.CommonTypes/MediaTypeFromSCSI.cs:
* DiscImageChef.CommonTypes/DiscImageChef.CommonTypes.csproj:
Added method to calculate MediaType from SCSI parameters
(mode, density, medium type, device type, etc).
* DiscImageChef.Metadata/DeviceReport.cs:
Added command to guess drive and media parameters and output
an XML report of them.
* DiscImageChef/Commands/DeviceReport.cs:
* DiscImageChef.Metadata/DiscImageChef.Metadata.csproj:
Added command to guess drive and media parameters and output
an XML report of them.
* DiscImageChef/Commands/DumpMedia.cs:
Added preliminary command to dump media. Only SCSI for now.
CDs and tapes are not supported. Errors are blalanty
ignored. Options are incomplete. Not yet usable.
* DiscImageChef/Core/Checksum.cs:
* DiscImageChef/Commands/CreateSidecar.cs:
Moved checksum generation to a separate class.
* CICMMetadata:
Added support for ADIP.
* DiscImageChef.CommonTypes/MediaType.cs:
Added parameters of UDO media.
Moved DataPlay outside of Iomega, as it's not from that
manufacturer.
Added missing Exatape media and corrected 160m XL one.
Added SyJet media.
Added all ECMA defined magneto-optical (sectors calculated
from specifications, unchecked).
Added PD media.
Added Imation 320Gb RDX.
Added generic USB flash drives.
* DiscImageChef.Decoders/SCSI/Enums.cs:
Make enumerations public.
* DiscImageChef.Decoders/SCSI/Inquiry.cs:
* DiscImageChef.Devices/Device/Constructor.cs:
Trim space padded strings on SCSI INQUIRY.
* DiscImageChef.Devices/Device/ScsiCommands/MMC.cs:
Added PREVENT ALLOW MEDIUM REMOVAL.
Added START STOP UNIT.
* DiscImageChef.Devices/Device/ScsiCommands/NEC.cs:
Rename NEC methods.
* DiscImageChef.Devices/Device/ScsiCommands/Pioneer.cs:
Corrected Pioneer transfer length calculation.
* DiscImageChef.Devices/Device/ScsiCommands/Plextor.cs:
Renamed Plextor methods.
* DiscImageChef.Devices/Device/ScsiCommands/SPC.cs:
Renamed SSC PREVENT ALLOW MEDIUM REMOVAL to uncollide with
MMC same name but different command.
* DiscImageChef.Devices/DiscImageChef.Devices.csproj:
Set platform target to x86 (does it really matter?).
* DiscImageChef.Devices/Linux/Command.cs:
Reduced allocation for readlink() to current kernel
MAX_PATH.
* DiscImageChef.Devices/Linux/Enums.cs:
Modified Linux ioctl to 32-bit. Works on 64-bit also. Solves
commands not working on 32-bit environments.
* DiscImageChef.DiscImages/ZZZRawImage.cs:
Changed ECMA-184 and ECMA-183 enums.
* DiscImageChef.Metadata/Dimensions.cs:
Added all ECMA defined magneto-opticals.
Added PD media.
Added 320Gb RDX.
Corrected Exatape 160m XL.
Added Exatape 22m and 28m.
* DiscImageChef.Metadata/MediaType.cs:
Added 356mm magneto-optical media.
Changed ECMA-184 and ECMA-183 enums.
Added USB generic flash drive.
* DiscImageChef/Commands/DeviceInfo.cs:
Corrected SCSI INQUIRY naming.
Corrected SCSI MODE SENSE (6) parameters.
Reduced SCSI MODE SENSE timeout, some devices just get stuck
with unsupported MODE SENSE commanda and must be left to
timeout.
Changed FUJITSU vendor string comparison.
* DiscImageChef/Commands/MediaInfo.cs:
Added method to calculate MediaType from SCSI parameters
(mode, density, medium type, device type, etc).
Changed some error WriteLine() to debug ones. Too much
verbosity.
Added DVD media type decoding from PFI.
Found a drive that dumps ADIP, enabling it again (not
decoded).
* DiscImageChef/Commands/MediaScan.cs:
Added option to generate ImgBurn compatible log to
media-scan command.
* DiscImageChef/DiscImageChef.csproj:
Moved checksum generation to a separate class.
Added command to guess drive and media parameters and output
an XML report of them.
Added preliminary command to dump media. Only SCSI for now.
CDs and tapes are not supported. Errors are blalanty
ignored. Options are incomplete. Not yet usable.
* DiscImageChef/Main.cs:
Added command to guess drive and media parameters and output
an XML report of them.
Added preliminary command to dump media. Only SCSI for now.
CDs and tapes are not supported. Errors are blalanty
ignored. Options are incomplete. Not yet usable.
* DiscImageChef/Options.cs:
Added command to guess drive and media parameters and output
an XML report of them.
Added preliminary command to dump media. Only SCSI for now.
CDs and tapes are not supported. Errors are blalanty
ignored. Options are incomplete. Not yet usable.
Added option to generate ImgBurn compatible log to media-scan
command.
2016-01-31 08:05:56 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
var scsiSense = true;
|
2017-09-07 16:47:06 +01:00
|
|
|
|
|
|
|
|
// Windows is answering SCSI INQUIRY for all device types so it needs to be detected first
|
2019-10-13 21:17:38 +01:00
|
|
|
switch (PlatformId)
|
2017-12-23 20:04:36 +00:00
|
|
|
{
|
2017-12-21 14:30:38 +00:00
|
|
|
case PlatformID.Win32NT:
|
2019-10-13 21:17:38 +01:00
|
|
|
var query = new StoragePropertyQuery();
|
|
|
|
|
query.PropertyId = StoragePropertyId.Device;
|
|
|
|
|
query.QueryType = StorageQueryType.Standard;
|
2017-12-21 04:43:29 +00:00
|
|
|
query.AdditionalParameters = new byte[1];
|
2017-09-07 16:47:06 +01:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
var descriptorPtr = Marshal.AllocHGlobal(1000);
|
|
|
|
|
var descriptorB = new byte[1000];
|
2017-09-07 16:47:06 +01:00
|
|
|
|
2017-12-21 04:43:29 +00:00
|
|
|
uint returned = 0;
|
2019-10-13 21:17:38 +01:00
|
|
|
var error = 0;
|
2017-09-07 16:47:06 +01:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
var hasError = !Extern.DeviceIoControlStorageQuery((SafeFileHandle) FileHandle,
|
|
|
|
|
WindowsIoctl.IoctlStorageQueryProperty,
|
|
|
|
|
ref query, (uint) Marshal.SizeOf(query),
|
|
|
|
|
descriptorPtr, 1000, ref returned, IntPtr.Zero);
|
2017-09-07 16:47:06 +01:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (hasError) error = Marshal.GetLastWin32Error();
|
2017-09-07 16:47:06 +01:00
|
|
|
|
2017-12-21 04:43:29 +00:00
|
|
|
Marshal.Copy(descriptorPtr, descriptorB, 0, 1000);
|
2017-09-07 16:47:06 +01:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (!hasError && error == 0)
|
2017-09-07 16:47:06 +01:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
var descriptor = new StorageDeviceDescriptor
|
2017-12-22 03:13:43 +00:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
Version = BitConverter.ToUInt32(descriptorB, 0),
|
|
|
|
|
Size = BitConverter.ToUInt32(descriptorB, 4),
|
|
|
|
|
DeviceType = descriptorB[8],
|
|
|
|
|
DeviceTypeModifier = descriptorB[9],
|
|
|
|
|
RemovableMedia = descriptorB[10] > 0,
|
|
|
|
|
CommandQueueing = descriptorB[11] > 0,
|
|
|
|
|
VendorIdOffset = BitConverter.ToInt32(descriptorB, 12),
|
|
|
|
|
ProductIdOffset = BitConverter.ToInt32(descriptorB, 16),
|
2017-12-22 03:13:43 +00:00
|
|
|
ProductRevisionOffset = BitConverter.ToInt32(descriptorB, 20),
|
2019-10-13 21:17:38 +01:00
|
|
|
SerialNumberOffset = BitConverter.ToInt32(descriptorB, 24),
|
|
|
|
|
BusType = (StorageBusType) BitConverter.ToUInt32(descriptorB, 28),
|
|
|
|
|
RawPropertiesLength = BitConverter.ToUInt32(descriptorB, 32)
|
2017-12-22 03:13:43 +00:00
|
|
|
};
|
2017-12-21 04:43:29 +00:00
|
|
|
descriptor.RawDeviceProperties = new byte[descriptor.RawPropertiesLength];
|
|
|
|
|
Array.Copy(descriptorB, 36, descriptor.RawDeviceProperties, 0, descriptor.RawPropertiesLength);
|
|
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
switch (descriptor.BusType)
|
2017-12-21 04:43:29 +00:00
|
|
|
{
|
2017-12-21 14:30:38 +00:00
|
|
|
case StorageBusType.SCSI:
|
|
|
|
|
case StorageBusType.SSA:
|
|
|
|
|
case StorageBusType.Fibre:
|
|
|
|
|
case StorageBusType.iSCSI:
|
|
|
|
|
case StorageBusType.SAS:
|
2017-12-21 07:19:46 +00:00
|
|
|
Type = DeviceType.SCSI;
|
2017-12-21 04:43:29 +00:00
|
|
|
break;
|
2017-12-21 14:30:38 +00:00
|
|
|
case StorageBusType.FireWire:
|
2017-12-21 07:19:46 +00:00
|
|
|
IsFireWire = true;
|
2019-10-13 21:17:38 +01:00
|
|
|
Type = DeviceType.SCSI;
|
2017-12-21 04:43:29 +00:00
|
|
|
break;
|
2017-12-21 14:30:38 +00:00
|
|
|
case StorageBusType.USB:
|
2017-12-21 07:19:46 +00:00
|
|
|
IsUsb = true;
|
2019-10-13 21:17:38 +01:00
|
|
|
Type = DeviceType.SCSI;
|
2017-12-21 04:43:29 +00:00
|
|
|
break;
|
2017-12-21 14:30:38 +00:00
|
|
|
case StorageBusType.ATAPI:
|
2017-12-21 07:19:46 +00:00
|
|
|
Type = DeviceType.ATAPI;
|
2017-12-21 04:43:29 +00:00
|
|
|
break;
|
2017-12-21 14:30:38 +00:00
|
|
|
case StorageBusType.ATA:
|
|
|
|
|
case StorageBusType.SATA:
|
2017-12-21 07:19:46 +00:00
|
|
|
Type = DeviceType.ATA;
|
2017-12-21 04:43:29 +00:00
|
|
|
break;
|
2017-12-21 14:30:38 +00:00
|
|
|
case StorageBusType.MultiMediaCard:
|
2017-12-21 07:19:46 +00:00
|
|
|
Type = DeviceType.MMC;
|
2017-12-21 04:43:29 +00:00
|
|
|
break;
|
2017-12-21 14:30:38 +00:00
|
|
|
case StorageBusType.SecureDigital:
|
2017-12-21 07:19:46 +00:00
|
|
|
Type = DeviceType.SecureDigital;
|
2017-12-21 04:43:29 +00:00
|
|
|
break;
|
2017-12-21 14:30:38 +00:00
|
|
|
case StorageBusType.NVMe:
|
2017-12-21 07:19:46 +00:00
|
|
|
Type = DeviceType.NVMe;
|
2017-12-21 04:43:29 +00:00
|
|
|
break;
|
|
|
|
|
}
|
2017-09-07 16:47:06 +01:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
switch (Type)
|
2017-12-23 20:04:36 +00:00
|
|
|
{
|
2017-12-21 04:43:29 +00:00
|
|
|
case DeviceType.SCSI:
|
2017-12-23 20:04:36 +00:00
|
|
|
case DeviceType.ATAPI:
|
|
|
|
|
scsiSense = ScsiInquiry(out inqBuf, out _);
|
2017-12-21 04:43:29 +00:00
|
|
|
break;
|
|
|
|
|
case DeviceType.ATA:
|
2019-10-13 21:17:38 +01:00
|
|
|
var atapiSense = AtapiIdentify(out ataBuf, out _);
|
2017-09-07 16:47:06 +01:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (!atapiSense)
|
2017-12-21 04:43:29 +00:00
|
|
|
{
|
2017-12-21 07:19:46 +00:00
|
|
|
Type = DeviceType.ATAPI;
|
2019-10-13 21:17:38 +01:00
|
|
|
var ataid = Identify.Decode(ataBuf);
|
2017-09-07 16:47:06 +01:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (ataid.HasValue) scsiSense = ScsiInquiry(out inqBuf, out _);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Manufacturer = "ATA";
|
2017-12-21 04:43:29 +00:00
|
|
|
}
|
2018-04-02 23:09:18 +01:00
|
|
|
|
2017-12-21 04:43:29 +00:00
|
|
|
break;
|
2017-09-07 16:47:06 +01:00
|
|
|
}
|
|
|
|
|
}
|
2017-12-06 23:05:59 +00:00
|
|
|
|
2017-12-21 04:43:29 +00:00
|
|
|
Marshal.FreeHGlobal(descriptorPtr);
|
2017-12-06 23:05:59 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (Windows.Command.IsSdhci((SafeFileHandle) FileHandle))
|
2017-12-06 23:05:59 +00:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
var sdBuffer = new byte[16];
|
2017-12-06 23:05:59 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
LastError = Windows.Command.SendMmcCommand((SafeFileHandle) FileHandle, MmcCommands.SendCsd,
|
|
|
|
|
false, false,
|
|
|
|
|
MmcFlags.ResponseSpiR2 | MmcFlags.ResponseR2 |
|
|
|
|
|
MmcFlags.CommandAc, 0, 16, 1, ref sdBuffer, out _,
|
|
|
|
|
out _, out var sense);
|
2017-12-06 23:05:59 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (!sense)
|
2017-12-21 04:43:29 +00:00
|
|
|
{
|
|
|
|
|
cachedCsd = new byte[16];
|
|
|
|
|
Array.Copy(sdBuffer, 0, cachedCsd, 0, 16);
|
|
|
|
|
}
|
2017-12-06 23:05:59 +00:00
|
|
|
|
2017-12-21 04:43:29 +00:00
|
|
|
sdBuffer = new byte[16];
|
2017-12-06 23:05:59 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
LastError = Windows.Command.SendMmcCommand((SafeFileHandle) FileHandle, MmcCommands.SendCid,
|
|
|
|
|
false, false,
|
|
|
|
|
MmcFlags.ResponseSpiR2 | MmcFlags.ResponseR2 |
|
|
|
|
|
MmcFlags.CommandAc, 0, 16, 1, ref sdBuffer, out _,
|
|
|
|
|
out _, out sense);
|
2017-12-06 23:05:59 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (!sense)
|
2017-12-21 04:43:29 +00:00
|
|
|
{
|
|
|
|
|
cachedCid = new byte[16];
|
|
|
|
|
Array.Copy(sdBuffer, 0, cachedCid, 0, 16);
|
|
|
|
|
}
|
2017-12-06 23:05:59 +00:00
|
|
|
|
2017-12-21 04:43:29 +00:00
|
|
|
sdBuffer = new byte[8];
|
2017-12-06 23:05:59 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
LastError = Windows.Command.SendMmcCommand((SafeFileHandle) FileHandle,
|
|
|
|
|
(MmcCommands) SecureDigitalCommands.SendScr, false,
|
|
|
|
|
true,
|
|
|
|
|
MmcFlags.ResponseSpiR1 | MmcFlags.ResponseR1 |
|
|
|
|
|
MmcFlags.CommandAdtc, 0, 8, 1, ref sdBuffer, out _,
|
|
|
|
|
out _, out sense);
|
2017-12-06 23:05:59 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (!sense)
|
2017-12-06 23:05:59 +00:00
|
|
|
{
|
2017-12-21 04:43:29 +00:00
|
|
|
cachedScr = new byte[8];
|
|
|
|
|
Array.Copy(sdBuffer, 0, cachedScr, 0, 8);
|
2017-12-06 23:05:59 +00:00
|
|
|
}
|
|
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (cachedScr != null)
|
2017-12-21 04:43:29 +00:00
|
|
|
{
|
|
|
|
|
sdBuffer = new byte[4];
|
2017-12-06 23:05:59 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
LastError = Windows.Command.SendMmcCommand((SafeFileHandle) FileHandle,
|
|
|
|
|
(MmcCommands) SecureDigitalCommands
|
|
|
|
|
.SendOperatingCondition, false, true,
|
|
|
|
|
MmcFlags.ResponseSpiR3 | MmcFlags.ResponseR3 |
|
|
|
|
|
MmcFlags.CommandBcr, 0, 4, 1, ref sdBuffer,
|
|
|
|
|
out _, out _, out sense);
|
2017-12-21 04:43:29 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (!sense)
|
2017-12-21 04:43:29 +00:00
|
|
|
{
|
|
|
|
|
cachedScr = new byte[4];
|
|
|
|
|
Array.Copy(sdBuffer, 0, cachedScr, 0, 4);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
2017-12-06 23:05:59 +00:00
|
|
|
{
|
2017-12-21 04:43:29 +00:00
|
|
|
sdBuffer = new byte[4];
|
|
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
LastError = Windows.Command.SendMmcCommand((SafeFileHandle) FileHandle,
|
|
|
|
|
MmcCommands.SendOpCond, false, true,
|
|
|
|
|
MmcFlags.ResponseSpiR3 | MmcFlags.ResponseR3 |
|
|
|
|
|
MmcFlags.CommandBcr, 0, 4, 1, ref sdBuffer,
|
|
|
|
|
out _, out _, out sense);
|
2017-12-21 04:43:29 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (!sense)
|
2017-12-21 04:43:29 +00:00
|
|
|
{
|
|
|
|
|
cachedScr = new byte[4];
|
|
|
|
|
Array.Copy(sdBuffer, 0, cachedScr, 0, 4);
|
|
|
|
|
}
|
2017-12-06 23:05:59 +00:00
|
|
|
}
|
|
|
|
|
}
|
2018-04-02 23:09:18 +01:00
|
|
|
|
2017-12-21 04:43:29 +00:00
|
|
|
break;
|
2017-12-21 14:30:38 +00:00
|
|
|
case PlatformID.Linux:
|
2019-10-13 21:17:38 +01:00
|
|
|
if (devicePath.StartsWith("/dev/sd", StringComparison.Ordinal) ||
|
|
|
|
|
devicePath.StartsWith("/dev/sr", StringComparison.Ordinal) ||
|
|
|
|
|
devicePath.StartsWith("/dev/st", StringComparison.Ordinal))
|
|
|
|
|
{
|
2017-12-22 03:13:43 +00:00
|
|
|
scsiSense = ScsiInquiry(out inqBuf, out _);
|
2019-10-13 21:17:38 +01:00
|
|
|
}
|
2017-12-21 04:43:29 +00:00
|
|
|
// MultiMediaCard and SecureDigital go here
|
2019-10-13 21:17:38 +01:00
|
|
|
else if (devicePath.StartsWith("/dev/mmcblk", StringComparison.Ordinal))
|
2017-09-29 13:05:50 +00:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
var devPath = devicePath.Substring(5);
|
|
|
|
|
if (File.Exists("/sys/block/" + devPath + "/device/csd"))
|
2017-12-21 04:43:29 +00:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
var len =
|
2018-04-02 23:09:18 +01:00
|
|
|
ConvertFromHexAscii("/sys/block/" + devPath + "/device/csd", out cachedCsd);
|
2019-10-13 21:17:38 +01:00
|
|
|
if (len == 0) cachedCsd = null;
|
2017-12-21 04:43:29 +00:00
|
|
|
}
|
2018-04-02 23:09:18 +01:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (File.Exists("/sys/block/" + devPath + "/device/cid"))
|
2017-12-21 04:43:29 +00:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
var len =
|
2018-04-02 23:09:18 +01:00
|
|
|
ConvertFromHexAscii("/sys/block/" + devPath + "/device/cid", out cachedCid);
|
2019-10-13 21:17:38 +01:00
|
|
|
if (len == 0) cachedCid = null;
|
2017-12-21 04:43:29 +00:00
|
|
|
}
|
2018-04-02 23:09:18 +01:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (File.Exists("/sys/block/" + devPath + "/device/scr"))
|
2017-12-21 04:43:29 +00:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
var len =
|
2018-04-02 23:09:18 +01:00
|
|
|
ConvertFromHexAscii("/sys/block/" + devPath + "/device/scr", out cachedScr);
|
2019-10-13 21:17:38 +01:00
|
|
|
if (len == 0) cachedScr = null;
|
2017-12-21 04:43:29 +00:00
|
|
|
}
|
2018-04-02 23:09:18 +01:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (File.Exists("/sys/block/" + devPath + "/device/ocr"))
|
2017-12-21 04:43:29 +00:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
var len =
|
2018-04-02 23:09:18 +01:00
|
|
|
ConvertFromHexAscii("/sys/block/" + devPath + "/device/ocr", out cachedOcr);
|
2019-10-13 21:17:38 +01:00
|
|
|
if (len == 0) cachedOcr = null;
|
2017-12-21 04:43:29 +00:00
|
|
|
}
|
2017-09-29 13:05:50 +00:00
|
|
|
}
|
2018-04-02 23:09:18 +01:00
|
|
|
|
2017-12-21 04:43:29 +00:00
|
|
|
break;
|
2017-12-23 20:04:36 +00:00
|
|
|
default:
|
|
|
|
|
scsiSense = ScsiInquiry(out inqBuf, out _);
|
2017-12-21 04:43:29 +00:00
|
|
|
break;
|
2017-12-06 23:05:59 +00:00
|
|
|
}
|
2017-09-28 19:14:50 +00:00
|
|
|
|
2017-12-06 23:05:59 +00:00
|
|
|
#region SecureDigital / MultiMediaCard
|
2019-10-13 21:17:38 +01:00
|
|
|
|
|
|
|
|
if (cachedCid != null)
|
2017-12-06 23:05:59 +00:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
ScsiType = PeripheralDeviceTypes.DirectAccess;
|
2017-12-21 07:19:46 +00:00
|
|
|
IsRemovable = false;
|
2017-09-28 19:14:50 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (cachedScr != null)
|
2017-12-06 23:05:59 +00:00
|
|
|
{
|
2017-12-21 07:19:46 +00:00
|
|
|
Type = DeviceType.SecureDigital;
|
2019-10-13 21:17:38 +01:00
|
|
|
var decoded = Decoders.SecureDigital.Decoders.DecodeCID(cachedCid);
|
2017-12-21 14:30:38 +00:00
|
|
|
Manufacturer = VendorString.Prettify(decoded.Manufacturer);
|
2019-10-13 21:17:38 +01:00
|
|
|
Model = decoded.ProductName;
|
|
|
|
|
Revision = $"{(decoded.ProductRevision & 0xF0) >> 4:X2}.{decoded.ProductRevision & 0x0F:X2}";
|
|
|
|
|
Serial = $"{decoded.ProductSerialNumber}";
|
2017-12-06 23:05:59 +00:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2017-12-21 07:19:46 +00:00
|
|
|
Type = DeviceType.MMC;
|
2019-10-13 21:17:38 +01:00
|
|
|
var decoded = Decoders.MMC.Decoders.DecodeCID(cachedCid);
|
2017-12-21 07:19:46 +00:00
|
|
|
Manufacturer = Decoders.MMC.VendorString.Prettify(decoded.Manufacturer);
|
2019-10-13 21:17:38 +01:00
|
|
|
Model = decoded.ProductName;
|
|
|
|
|
Revision = $"{(decoded.ProductRevision & 0xF0) >> 4:X2}.{decoded.ProductRevision & 0x0F:X2}";
|
|
|
|
|
Serial = $"{decoded.ProductSerialNumber}";
|
2017-09-28 19:14:50 +00:00
|
|
|
}
|
2017-09-28 17:54:07 +00:00
|
|
|
}
|
2019-10-13 21:17:38 +01:00
|
|
|
|
2017-12-06 23:05:59 +00:00
|
|
|
#endregion SecureDigital / MultiMediaCard
|
2017-12-06 13:46:35 +00:00
|
|
|
|
2017-12-19 20:33:03 +00:00
|
|
|
#region USB
|
2019-10-13 21:17:38 +01:00
|
|
|
|
|
|
|
|
switch (PlatformId)
|
2017-12-23 20:04:36 +00:00
|
|
|
{
|
2017-12-21 14:30:38 +00:00
|
|
|
case PlatformID.Linux:
|
2019-10-13 21:17:38 +01:00
|
|
|
if (devicePath.StartsWith("/dev/sd", StringComparison.Ordinal) ||
|
|
|
|
|
devicePath.StartsWith("/dev/sr", StringComparison.Ordinal) ||
|
|
|
|
|
devicePath.StartsWith("/dev/st", StringComparison.Ordinal))
|
2015-12-31 16:12:22 +00:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
var devPath = devicePath.Substring(5);
|
|
|
|
|
if (Directory.Exists("/sys/block/" + devPath))
|
2017-12-21 04:43:29 +00:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
var resolvedLink = Linux.Command.ReadLink("/sys/block/" + devPath);
|
2017-12-21 04:43:29 +00:00
|
|
|
resolvedLink = "/sys" + resolvedLink.Substring(2);
|
2019-10-13 21:17:38 +01:00
|
|
|
if (!string.IsNullOrEmpty(resolvedLink))
|
|
|
|
|
while (resolvedLink.Contains("usb"))
|
2015-12-31 16:12:22 +00:00
|
|
|
{
|
2017-12-21 14:30:38 +00:00
|
|
|
resolvedLink = Path.GetDirectoryName(resolvedLink);
|
2019-10-13 21:17:38 +01:00
|
|
|
if (!File.Exists(resolvedLink + "/descriptors") ||
|
|
|
|
|
!File.Exists(resolvedLink + "/idProduct") ||
|
|
|
|
|
!File.Exists(resolvedLink + "/idVendor")) continue;
|
|
|
|
|
|
|
|
|
|
var usbFs = new FileStream(resolvedLink + "/descriptors",
|
|
|
|
|
System.IO.FileMode.Open,
|
|
|
|
|
System.IO.FileAccess.Read);
|
|
|
|
|
var usbBuf = new byte[65536];
|
|
|
|
|
var usbCount = usbFs.Read(usbBuf, 0, 65536);
|
2017-12-21 07:19:46 +00:00
|
|
|
UsbDescriptors = new byte[usbCount];
|
|
|
|
|
Array.Copy(usbBuf, 0, UsbDescriptors, 0, usbCount);
|
2017-12-21 06:06:19 +00:00
|
|
|
usbFs.Close();
|
|
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
var usbSr = new StreamReader(resolvedLink + "/idProduct");
|
|
|
|
|
var usbTemp = usbSr.ReadToEnd();
|
2017-12-23 20:04:36 +00:00
|
|
|
ushort.TryParse(usbTemp, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
|
2019-10-13 21:17:38 +01:00
|
|
|
out usbProduct);
|
2017-12-21 06:06:19 +00:00
|
|
|
usbSr.Close();
|
|
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
usbSr = new StreamReader(resolvedLink + "/idVendor");
|
2017-12-21 06:06:19 +00:00
|
|
|
usbTemp = usbSr.ReadToEnd();
|
2017-12-23 20:04:36 +00:00
|
|
|
ushort.TryParse(usbTemp, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
|
2019-10-13 21:17:38 +01:00
|
|
|
out usbVendor);
|
2017-12-21 06:06:19 +00:00
|
|
|
usbSr.Close();
|
|
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (File.Exists(resolvedLink + "/manufacturer"))
|
2015-12-31 16:12:22 +00:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
usbSr = new StreamReader(resolvedLink + "/manufacturer");
|
2017-12-21 07:19:46 +00:00
|
|
|
UsbManufacturerString = usbSr.ReadToEnd().Trim();
|
2015-12-31 16:12:22 +00:00
|
|
|
usbSr.Close();
|
2017-12-21 06:06:19 +00:00
|
|
|
}
|
2015-12-31 16:12:22 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (File.Exists(resolvedLink + "/product"))
|
2017-12-21 06:06:19 +00:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
usbSr = new StreamReader(resolvedLink + "/product");
|
2017-12-21 07:19:46 +00:00
|
|
|
UsbProductString = usbSr.ReadToEnd().Trim();
|
2016-04-19 02:11:47 +01:00
|
|
|
usbSr.Close();
|
2017-12-21 06:06:19 +00:00
|
|
|
}
|
2015-12-31 16:12:22 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (File.Exists(resolvedLink + "/serial"))
|
2017-12-21 06:06:19 +00:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
usbSr = new StreamReader(resolvedLink + "/serial");
|
2017-12-21 07:19:46 +00:00
|
|
|
UsbSerialString = usbSr.ReadToEnd().Trim();
|
2017-12-21 06:06:19 +00:00
|
|
|
usbSr.Close();
|
2015-12-31 16:12:22 +00:00
|
|
|
}
|
2017-12-21 06:06:19 +00:00
|
|
|
|
2017-12-21 07:19:46 +00:00
|
|
|
IsUsb = true;
|
2017-12-21 06:06:19 +00:00
|
|
|
break;
|
2015-12-31 16:12:22 +00:00
|
|
|
}
|
2017-12-21 04:43:29 +00:00
|
|
|
}
|
2015-12-31 16:12:22 +00:00
|
|
|
}
|
2017-12-19 20:33:03 +00:00
|
|
|
|
2017-12-21 04:43:29 +00:00
|
|
|
break;
|
2017-12-21 14:30:38 +00:00
|
|
|
case PlatformID.Win32NT:
|
|
|
|
|
Usb.UsbDevice usbDevice = null;
|
2017-12-06 19:30:03 +00:00
|
|
|
|
2017-12-21 04:43:29 +00:00
|
|
|
// I have to search for USB disks, floppies and CD-ROMs as separate device types
|
2019-10-13 21:17:38 +01:00
|
|
|
foreach (var devGuid in new[]
|
2018-11-27 00:09:53 +00:00
|
|
|
{
|
|
|
|
|
Usb.GuidDevinterfaceFloppy, Usb.GuidDevinterfaceCdrom, Usb.GuidDevinterfaceDisk
|
|
|
|
|
})
|
2017-12-21 04:43:29 +00:00
|
|
|
{
|
2017-12-21 14:30:38 +00:00
|
|
|
usbDevice = Usb.FindDrivePath(devicePath, devGuid);
|
2019-10-13 21:17:38 +01:00
|
|
|
if (usbDevice != null) break;
|
2017-12-21 04:43:29 +00:00
|
|
|
}
|
|
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (usbDevice != null)
|
2017-12-21 04:43:29 +00:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
UsbDescriptors = usbDevice.BinaryDescriptors;
|
|
|
|
|
usbVendor = (ushort) usbDevice.DeviceDescriptor.idVendor;
|
|
|
|
|
usbProduct = (ushort) usbDevice.DeviceDescriptor.idProduct;
|
2017-12-21 07:19:46 +00:00
|
|
|
UsbManufacturerString = usbDevice.Manufacturer;
|
2019-10-13 21:17:38 +01:00
|
|
|
UsbProductString = usbDevice.Product;
|
2017-12-21 07:19:46 +00:00
|
|
|
UsbSerialString =
|
2017-12-21 04:43:29 +00:00
|
|
|
usbDevice.SerialNumber; // This is incorrect filled by Windows with SCSI/ATA serial number
|
|
|
|
|
}
|
2018-04-02 23:09:18 +01:00
|
|
|
|
2017-12-21 04:43:29 +00:00
|
|
|
break;
|
2017-12-23 20:04:36 +00:00
|
|
|
default:
|
|
|
|
|
IsUsb = false;
|
2017-12-21 04:43:29 +00:00
|
|
|
break;
|
2015-12-31 16:12:22 +00:00
|
|
|
}
|
2019-10-13 21:17:38 +01:00
|
|
|
|
2015-12-31 16:12:22 +00:00
|
|
|
#endregion USB
|
|
|
|
|
|
2015-12-31 16:33:20 +00:00
|
|
|
#region FireWire
|
2019-10-13 21:17:38 +01:00
|
|
|
|
|
|
|
|
if (PlatformId == PlatformID.Linux)
|
2015-12-31 16:33:20 +00:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
if (devicePath.StartsWith("/dev/sd", StringComparison.Ordinal) ||
|
|
|
|
|
devicePath.StartsWith("/dev/sr", StringComparison.Ordinal) ||
|
|
|
|
|
devicePath.StartsWith("/dev/st", StringComparison.Ordinal))
|
2015-12-31 16:33:20 +00:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
var devPath = devicePath.Substring(5);
|
|
|
|
|
if (Directory.Exists("/sys/block/" + devPath))
|
2015-12-31 16:33:20 +00:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
var resolvedLink = Linux.Command.ReadLink("/sys/block/" + devPath);
|
2015-12-31 16:33:20 +00:00
|
|
|
resolvedLink = "/sys" + resolvedLink.Substring(2);
|
2019-10-13 21:17:38 +01:00
|
|
|
if (!string.IsNullOrEmpty(resolvedLink))
|
|
|
|
|
while (resolvedLink.Contains("firewire"))
|
2015-12-31 16:33:20 +00:00
|
|
|
{
|
2017-12-21 14:30:38 +00:00
|
|
|
resolvedLink = Path.GetDirectoryName(resolvedLink);
|
2019-10-13 21:17:38 +01:00
|
|
|
if (!File.Exists(resolvedLink + "/model") || !File.Exists(resolvedLink + "/vendor") ||
|
|
|
|
|
!File.Exists(resolvedLink + "/guid")) continue;
|
2017-12-21 06:06:19 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
var fwSr = new StreamReader(resolvedLink + "/model");
|
|
|
|
|
var fwTemp = fwSr.ReadToEnd();
|
2017-12-23 20:04:36 +00:00
|
|
|
uint.TryParse(fwTemp, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
|
2019-10-13 21:17:38 +01:00
|
|
|
out firewireModel);
|
2017-12-21 06:06:19 +00:00
|
|
|
fwSr.Close();
|
|
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
fwSr = new StreamReader(resolvedLink + "/vendor");
|
2017-12-21 06:06:19 +00:00
|
|
|
fwTemp = fwSr.ReadToEnd();
|
2017-12-23 20:04:36 +00:00
|
|
|
uint.TryParse(fwTemp, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
|
2019-10-13 21:17:38 +01:00
|
|
|
out firewireVendor);
|
2017-12-21 06:06:19 +00:00
|
|
|
fwSr.Close();
|
|
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
fwSr = new StreamReader(resolvedLink + "/guid");
|
2017-12-21 06:06:19 +00:00
|
|
|
fwTemp = fwSr.ReadToEnd();
|
2017-12-23 20:04:36 +00:00
|
|
|
ulong.TryParse(fwTemp, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
|
2019-10-13 21:17:38 +01:00
|
|
|
out firewireGuid);
|
2017-12-21 06:06:19 +00:00
|
|
|
fwSr.Close();
|
|
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (File.Exists(resolvedLink + "/model_name"))
|
2015-12-31 16:33:20 +00:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
fwSr = new StreamReader(resolvedLink + "/model_name");
|
2017-12-21 07:19:46 +00:00
|
|
|
FireWireModelName = fwSr.ReadToEnd().Trim();
|
2015-12-31 16:33:20 +00:00
|
|
|
fwSr.Close();
|
2017-12-21 06:06:19 +00:00
|
|
|
}
|
2015-12-31 16:33:20 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (File.Exists(resolvedLink + "/vendor_name"))
|
2017-12-21 06:06:19 +00:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
fwSr = new StreamReader(resolvedLink + "/vendor_name");
|
2017-12-21 07:19:46 +00:00
|
|
|
FireWireVendorName = fwSr.ReadToEnd().Trim();
|
2015-12-31 16:33:20 +00:00
|
|
|
fwSr.Close();
|
|
|
|
|
}
|
2017-12-21 06:06:19 +00:00
|
|
|
|
2017-12-21 07:19:46 +00:00
|
|
|
IsFireWire = true;
|
2017-12-21 06:06:19 +00:00
|
|
|
break;
|
2015-12-31 16:33:20 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// TODO: Implement for other operating systems
|
2019-10-13 21:17:38 +01:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
IsFireWire = false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-31 16:33:20 +00:00
|
|
|
#endregion FireWire
|
|
|
|
|
|
2016-10-17 04:41:27 +01:00
|
|
|
#region PCMCIA
|
2019-10-13 21:17:38 +01:00
|
|
|
|
|
|
|
|
if (PlatformId == PlatformID.Linux)
|
2016-10-17 04:41:27 +01:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
if (devicePath.StartsWith("/dev/sd", StringComparison.Ordinal) ||
|
|
|
|
|
devicePath.StartsWith("/dev/sr", StringComparison.Ordinal) ||
|
|
|
|
|
devicePath.StartsWith("/dev/st", StringComparison.Ordinal))
|
2016-10-17 04:41:27 +01:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
var devPath = devicePath.Substring(5);
|
|
|
|
|
if (Directory.Exists("/sys/block/" + devPath))
|
2016-10-17 04:41:27 +01:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
var resolvedLink = Linux.Command.ReadLink("/sys/block/" + devPath);
|
2016-10-17 04:41:27 +01:00
|
|
|
resolvedLink = "/sys" + resolvedLink.Substring(2);
|
2019-10-13 21:17:38 +01:00
|
|
|
if (!string.IsNullOrEmpty(resolvedLink))
|
|
|
|
|
while (resolvedLink.Contains("/sys/devices"))
|
2016-10-17 04:41:27 +01:00
|
|
|
{
|
2017-12-21 14:30:38 +00:00
|
|
|
resolvedLink = Path.GetDirectoryName(resolvedLink);
|
2019-10-13 21:17:38 +01:00
|
|
|
if (!Directory.Exists(resolvedLink + "/pcmcia_socket")) continue;
|
2016-10-17 04:41:27 +01:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
var subdirs = Directory.GetDirectories(resolvedLink + "/pcmcia_socket",
|
|
|
|
|
"pcmcia_socket*",
|
|
|
|
|
SearchOption.TopDirectoryOnly);
|
2017-12-21 06:06:19 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (subdirs.Length <= 0) continue;
|
2017-12-21 06:06:19 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
var possibleDir = Path.Combine(resolvedLink, "pcmcia_socket", subdirs[0]);
|
|
|
|
|
if (!File.Exists(possibleDir + "/card_type") || !File.Exists(possibleDir + "/cis"))
|
2017-12-23 20:04:36 +00:00
|
|
|
continue;
|
2017-12-21 06:06:19 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
var cisFs = new FileStream(possibleDir + "/cis", System.IO.FileMode.Open,
|
|
|
|
|
System.IO.FileAccess.Read);
|
|
|
|
|
var cisBuf = new byte[65536];
|
|
|
|
|
var cisCount = cisFs.Read(cisBuf, 0, 65536);
|
2017-12-21 07:19:46 +00:00
|
|
|
Cis = new byte[cisCount];
|
|
|
|
|
Array.Copy(cisBuf, 0, Cis, 0, cisCount);
|
2017-12-21 06:06:19 +00:00
|
|
|
cisFs.Close();
|
|
|
|
|
|
2017-12-21 07:19:46 +00:00
|
|
|
IsPcmcia = true;
|
2017-12-21 06:06:19 +00:00
|
|
|
break;
|
2016-10-17 04:41:27 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// TODO: Implement for other operating systems
|
2019-10-13 21:17:38 +01:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
IsPcmcia = false;
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-17 04:41:27 +01:00
|
|
|
#endregion PCMCIA
|
|
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (!scsiSense)
|
2015-11-05 06:50:02 +00:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
var inquiry = Inquiry.Decode(inqBuf);
|
2015-10-19 05:11:28 +01:00
|
|
|
|
2017-12-21 07:19:46 +00:00
|
|
|
Type = DeviceType.SCSI;
|
2019-10-13 21:17:38 +01:00
|
|
|
var serialSense = ScsiInquiry(out inqBuf, out _, 0x80);
|
|
|
|
|
if (!serialSense) Serial = EVPD.DecodePage80(inqBuf);
|
2016-04-19 02:11:47 +01:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (inquiry.HasValue)
|
2015-11-05 06:50:02 +00:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
var tmp = StringHandlers.CToString(inquiry.Value.ProductRevisionLevel);
|
|
|
|
|
if (tmp != null) Revision = tmp.Trim();
|
2017-12-20 17:15:26 +00:00
|
|
|
tmp = StringHandlers.CToString(inquiry.Value.ProductIdentification);
|
2019-10-13 21:17:38 +01:00
|
|
|
if (tmp != null) Model = tmp.Trim();
|
2017-12-20 17:15:26 +00:00
|
|
|
tmp = StringHandlers.CToString(inquiry.Value.VendorIdentification);
|
2019-10-13 21:17:38 +01:00
|
|
|
if (tmp != null) Manufacturer = tmp.Trim();
|
2017-12-21 07:19:46 +00:00
|
|
|
IsRemovable = inquiry.Value.RMB;
|
2015-10-19 05:20:42 +01:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
ScsiType = (PeripheralDeviceTypes) inquiry.Value.PeripheralDeviceType;
|
2015-10-19 05:11:28 +01:00
|
|
|
}
|
2015-11-05 06:50:02 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
var atapiSense = AtapiIdentify(out ataBuf, out _);
|
2015-10-19 05:11:28 +01:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (!atapiSense)
|
2015-10-19 05:11:28 +01:00
|
|
|
{
|
2017-12-21 07:19:46 +00:00
|
|
|
Type = DeviceType.ATAPI;
|
2019-10-13 21:17:38 +01:00
|
|
|
var ataId = Identify.Decode(ataBuf);
|
2015-10-19 05:11:28 +01:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (ataId.HasValue) Serial = ataId.Value.SerialNumber;
|
2015-10-19 05:11:28 +01:00
|
|
|
}
|
2018-09-01 23:49:36 +01:00
|
|
|
|
|
|
|
|
LastError = 0;
|
2019-10-13 21:17:38 +01:00
|
|
|
Error = false;
|
2015-11-05 06:50:02 +00:00
|
|
|
}
|
2015-10-19 05:11:28 +01:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (scsiSense && (IsUsb || IsFireWire) || Manufacturer == "ATA")
|
2015-11-05 06:50:02 +00:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
var ataSense = AtaIdentify(out ataBuf, out _);
|
|
|
|
|
if (!ataSense)
|
2015-10-19 05:11:28 +01:00
|
|
|
{
|
2017-12-21 07:19:46 +00:00
|
|
|
Type = DeviceType.ATA;
|
2019-10-13 21:17:38 +01:00
|
|
|
var ataid = Identify.Decode(ataBuf);
|
2015-10-19 05:11:28 +01:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (ataid.HasValue)
|
2015-10-19 05:11:28 +01:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
var separated = ataid.Value.Model.Split(' ');
|
2015-10-19 05:11:28 +01:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (separated.Length == 1)
|
|
|
|
|
{
|
|
|
|
|
Model = separated[0];
|
|
|
|
|
}
|
2015-11-05 06:50:02 +00:00
|
|
|
else
|
|
|
|
|
{
|
2017-12-21 07:19:46 +00:00
|
|
|
Manufacturer = separated[0];
|
2019-10-13 21:17:38 +01:00
|
|
|
Model = separated[separated.Length - 1];
|
2015-11-05 06:50:02 +00:00
|
|
|
}
|
|
|
|
|
|
2017-12-21 07:19:46 +00:00
|
|
|
Revision = ataid.Value.FirmwareRevision;
|
2019-10-13 21:17:38 +01:00
|
|
|
Serial = ataid.Value.SerialNumber;
|
2015-10-19 05:20:42 +01:00
|
|
|
|
2017-12-21 14:30:38 +00:00
|
|
|
ScsiType = PeripheralDeviceTypes.DirectAccess;
|
2015-12-30 11:45:27 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if ((ushort) ataid.Value.GeneralConfiguration != 0x848A)
|
2017-12-21 07:19:46 +00:00
|
|
|
IsRemovable |=
|
2017-12-20 17:15:26 +00:00
|
|
|
(ataid.Value.GeneralConfiguration & Identify.GeneralConfigurationBit.Removable) ==
|
2017-12-19 20:33:03 +00:00
|
|
|
Identify.GeneralConfigurationBit.Removable;
|
2017-12-21 07:19:46 +00:00
|
|
|
else IsCompactFlash = true;
|
2015-10-19 05:11:28 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (Type == DeviceType.Unknown)
|
2015-10-19 05:11:28 +01:00
|
|
|
{
|
2017-12-21 07:19:46 +00:00
|
|
|
Manufacturer = null;
|
2019-10-13 21:17:38 +01:00
|
|
|
Model = null;
|
|
|
|
|
Revision = null;
|
|
|
|
|
Serial = null;
|
2015-10-19 05:11:28 +01:00
|
|
|
}
|
2015-12-31 16:12:22 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (IsUsb)
|
2015-12-31 16:12:22 +00:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
if (string.IsNullOrEmpty(Manufacturer)) Manufacturer = UsbManufacturerString;
|
|
|
|
|
if (string.IsNullOrEmpty(Model)) Model = UsbProductString;
|
|
|
|
|
if (string.IsNullOrEmpty(Serial)) Serial = UsbSerialString;
|
2018-04-02 23:09:18 +01:00
|
|
|
else
|
2019-10-13 21:17:38 +01:00
|
|
|
foreach (var c in Serial.Where(char.IsControl))
|
2018-04-02 23:09:18 +01:00
|
|
|
Serial = UsbSerialString;
|
2015-12-31 16:12:22 +00:00
|
|
|
}
|
2015-12-31 16:33:20 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (IsFireWire)
|
2019-02-12 23:04:53 +00:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
if (string.IsNullOrEmpty(Manufacturer)) Manufacturer = FireWireVendorName;
|
|
|
|
|
if (string.IsNullOrEmpty(Model)) Model = FireWireModelName;
|
|
|
|
|
if (string.IsNullOrEmpty(Serial)) Serial = $"{firewireGuid:X16}";
|
2019-02-12 23:04:53 +00:00
|
|
|
else
|
2019-10-13 21:17:38 +01:00
|
|
|
foreach (var c in Serial.Where(char.IsControl))
|
2019-02-12 23:04:53 +00:00
|
|
|
Serial = $"{firewireGuid:X16}";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Some optical drives are not getting the correct serial, and IDENTIFY PACKET DEVICE is blocked without
|
|
|
|
|
// administrator privileges
|
2019-10-13 21:17:38 +01:00
|
|
|
if (ScsiType != PeripheralDeviceTypes.MultiMediaDevice) return;
|
2019-02-12 23:04:53 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
var featureSense = GetConfiguration(out var featureBuffer, out _, 0x0108, MmcGetConfigurationRt.Single,
|
|
|
|
|
Timeout, out _);
|
2019-02-12 23:04:53 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (featureSense) return;
|
2019-02-12 23:04:53 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
var features = Features.Separate(featureBuffer);
|
|
|
|
|
if (features.Descriptors?.Length != 1 || features.Descriptors[0].Code != 0x0108) return;
|
2019-02-12 23:04:53 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
var serialFeature = Features.Decode_0108(features.Descriptors[0].Data);
|
2019-02-12 23:04:53 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
if (serialFeature is null) return;
|
2017-12-21 06:06:19 +00:00
|
|
|
|
2019-02-12 23:04:53 +00:00
|
|
|
Serial = serialFeature.Value.Serial;
|
2015-10-12 19:55:00 +01:00
|
|
|
}
|
2017-09-28 19:14:50 +00:00
|
|
|
|
2019-10-13 21:17:38 +01:00
|
|
|
private static int ConvertFromHexAscii(string file, out byte[] outBuf)
|
2017-09-28 19:14:50 +00:00
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
var sr = new StreamReader(file);
|
|
|
|
|
var ins = sr.ReadToEnd().Trim();
|
2017-09-28 19:14:50 +00:00
|
|
|
outBuf = new byte[ins.Length / 2];
|
2019-10-13 21:17:38 +01:00
|
|
|
var count = 0;
|
2017-09-28 19:14:50 +00:00
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2019-10-13 21:17:38 +01:00
|
|
|
for (var i = 0; i < ins.Length; i += 2)
|
2017-09-28 19:14:50 +00:00
|
|
|
{
|
2017-12-19 20:33:03 +00:00
|
|
|
outBuf[i / 2] = Convert.ToByte(ins.Substring(i, 2), 16);
|
2017-09-28 19:14:50 +00:00
|
|
|
count++;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-10-13 21:17:38 +01:00
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
count = 0;
|
|
|
|
|
}
|
2017-09-28 19:14:50 +00:00
|
|
|
|
|
|
|
|
sr.Close();
|
|
|
|
|
return count;
|
|
|
|
|
}
|
2015-10-12 19:55:00 +01:00
|
|
|
}
|
2017-12-19 20:33:03 +00:00
|
|
|
}
|