Click here to Skip to main content
15,897,704 members
Articles / Programming Languages / C#

SCSI Library in C# - Burn CDs and DVDs, Access Hard Disks, etc.

Rate me:
Please Sign up or sign in to vote.
4.77/5 (48 votes)
19 Jun 2017Ms-PL6 min read 146.4K   8.1K   146  
Ever wonder how programs like Nero work? They make their own SCSI libraries... like this!
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
using Helper;

namespace Scsi
{
	/// <summary>Represents a generic SCSI device. This class should not be instantiated directly unless no subclasses exist that are appropriate.</summary>
	[Description("Represents a generic SCSI device. This class should not be instantiated directly unless no subclasses exist that are appropriate.")]
	public class ScsiDevice : IScsiDevice
	{
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private const int MAX_DATA_SIZE = 64 << 10;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private SenseData lastSenseData;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private bool leaveOpen;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private Read10Command read10CommandTemp = new Read10Command();
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private Read12Command read12CommandTemp = new Read12Command();
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private Read16Command read16CommandTemp = new Read16Command();
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private Write10Command write10CommandTemp = new Write10Command();
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private Write12Command write12CommandTemp = new Write12Command();
		[DebuggerBrowsableAttribute(DebuggerBrowsableState.Never)]
		private uint _TimeoutSeconds = 60;
		//private byte portNumber;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private int _PathId = -1;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private int _TargetId = -1;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private int _Lun = -1;
		private uint _MaxBlockTransferCount = 16; //It seems like 16 sectors is most optimal... I don't know why
		private ReadCapacityInfo capacityInfo;
		private bool _CanWrite = true; //TODO: Query write protection

		/// <summary>Initializes a new instance of the <see cref="ScsiDevice"/> class.</summary>
		/// <param name="interface">The pass-through interface to use.</param>
		/// <param name="leaveOpen"><c>true</c> to leave the interface open, or <c>false</c> to dispose along with this object.</param>
		public ScsiDevice(IScsiPassThrough @interface, bool leaveOpen)
		{
			this.AutoSense = true;
			this.leaveOpen = leaveOpen;
			this.Interface = @interface;
			this.DefaultPollingInterval = 1;
		}

		public virtual bool AutoSense { get; set; }

		private byte PathId { get { if (this._PathId == -1) { int val; try { val = this.Interface.PathId; } catch { val = 0; } Interlocked.CompareExchange(ref this._PathId, val, -1); } return (byte)this._PathId; } }
		private byte TargetId { get { if (this._TargetId == -1) { int val; try { val = this.Interface.TargetId; } catch { val = 0; } Interlocked.CompareExchange(ref this._TargetId, val, -1); } return (byte)this._TargetId; } }
		private byte LogicalUnitNumber { get { if (this._Lun == -1) { int val; try { val = this.Interface.LogicalUnitNumber; } catch { val = 0; } Interlocked.CompareExchange(ref this._Lun, val, -1); } return (byte)this._Lun; } }

		[Obsolete("Do not use. This is only for viewing the data in the debugger, because manipulating the returned data has no effect. Use the appropriate Get and Set methods instead.", true)]
		private CachingModePage Caching { get { return this.GetCachingInformation(new ModeSense10Command(PageControl.CurrentValues)); } }

		public virtual uint BlockSize { get { return this.GetCapacity().BlockLength; } }

		/// <summary>The capacity of the medium, in bytes.</summary>
		[Description("The capacity of the medium, in bytes.")]
		public long Capacity { get { var cap = this.GetCapacity(); return ((long)cap.LogicalBlockAddress + 1) * (long)cap.BlockLength; } }

		/// <summary>The default polling interval for the drive, in units of 100 ms.</summary>
		public ushort DefaultPollingInterval { get; set; }

		public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); }

		protected virtual void Dispose(bool disposing) { if (disposing) { try { if (!this.leaveOpen) { this.Interface.Dispose(); } } finally { this.Interface = null; } } }

		[DebuggerHidden]
		protected void ExecuteCommand(ScsiCommand command, DataTransferDirection direction, BufferWithSize buffer) { this.ExecuteCommand(command, direction, buffer, true); }

		//[DebuggerHidden]
		protected ScsiStatus ExecuteCommand(ScsiCommand command, DataTransferDirection direction, BufferWithSize buffer, bool throwOnError)
		{
			bool heapAlloc;
			bool reallocated;

			ScsiStatus status;
			BufferWithSize cdb;
			unsafe
			{
				int cmdSize = Marshaler.SizeOf(command);
				byte* pBuffer = stackalloc byte[cmdSize];
				cdb = new BufferWithSize(pBuffer, cmdSize);
				Marshaler.StructureToPtr(command, cdb);
			}

			BufferWithSize entireAlignedBuffer, portionOfAlignedBuffer;

			unsafe
			{
				var alignment = this.Interface.AlignmentMask;
				if (((ulong)(void*)buffer.Address & ((uint)alignment - 1)) != 0)
				{
					var alignedLen = buffer.Length32 + alignment;
					bool stackAlloc = Marshaler.ShouldStackAlloc(alignedLen);
					if (stackAlloc)
					{
						byte* pData = stackalloc byte[alignedLen];
						entireAlignedBuffer = new BufferWithSize(pData, alignedLen);
						heapAlloc = false;
					}
					else
					{
						entireAlignedBuffer = BufferWithSize.AllocHGlobal(alignedLen);
						heapAlloc = true;
					}
					portionOfAlignedBuffer = entireAlignedBuffer.ExtractSegment((UIntPtr)((byte*)((ulong)((byte*)entireAlignedBuffer.Address + alignment - 1) / (uint)alignment * (uint)alignment) - (byte*)entireAlignedBuffer.Address), buffer.Length);
					if (direction == DataTransferDirection.SendData) { BufferWithSize.Copy(buffer, UIntPtr.Zero, portionOfAlignedBuffer, UIntPtr.Zero, buffer.Length); }
					heapAlloc = !stackAlloc;
					reallocated = true;
				}
				else
				{
					entireAlignedBuffer = portionOfAlignedBuffer = buffer;
					heapAlloc = false;
					reallocated = false;
				}
			}
			try
			{
				status = this.Interface.ExecuteCommand(cdb, direction, this.PathId, this.TargetId, this.LogicalUnitNumber, portionOfAlignedBuffer, this.TimeoutSeconds, this.AutoSense, out this.lastSenseData);
				if (throwOnError && status != ScsiStatus.Good) { throw ScsiException.CreateException(this.lastSenseData, false); }
			}
			finally
			{
				if (reallocated) { if (direction == DataTransferDirection.ReceiveData) { BufferWithSize.Copy(portionOfAlignedBuffer, UIntPtr.Zero, buffer, UIntPtr.Zero, buffer.Length); } }
				if (reallocated && heapAlloc) { BufferWithSize.FreeHGlobal(entireAlignedBuffer); }
			}

			return status;
		}

		public CachingModePage GetCachingInformation(ModeSense10Command command) { return this.ModeSense10<CachingModePage>(command); }

		private ReadCapacityInfo GetCapacity() { if (this.capacityInfo.BlockLength == 0 || this.HasMediumChanged()) { this.capacityInfo = this.ReadCapacity(); } return this.capacityInfo; }

		public SenseData GetLastSenseData() { return this.lastSenseData.Clone(); }

		public PowerConditionsModePage GetPowerConditions(ModeSense10Command command) { return this.ModeSense10<PowerConditionsModePage>(command); }

		public ReadWriteErrorRecoveryParametersPage GetReadWriteErrorRecoveryParameters(ModeSense10Command command)
		{ return this.ModeSense10<ReadWriteErrorRecoveryParametersPage>(command); }

		protected virtual bool HasMediumChanged() { return false; }

		public StandardInquiryData Inquiry() { return (StandardInquiryData)this.Inquiry(new InquiryCommand(null)); }

		public virtual InquiryData Inquiry(InquiryCommand command)
		{
			if (command.PageCode == null)
			{
				unsafe
				{
					int bufferSize = StandardInquiryData.MinimumSize;
					byte* pData1 = stackalloc byte[(int)bufferSize];
					BufferWithSize buffer = new BufferWithSize(pData1, bufferSize);
					command.AllocationLength = (ushort)buffer.Length;
					this.ExecuteCommand(command, DataTransferDirection.ReceiveData, buffer);
					byte additionalLength = command.PageCode == null ? StandardInquiryData.ReadAdditionalLength(buffer) : (byte)0;
					var requiredSize = additionalLength + (int)StandardInquiryData.ADDITIONAL_LENGTH_OFFSET;
					if (bufferSize < requiredSize)
					{
						bufferSize = requiredSize;
						byte* pData2 = stackalloc byte[(int)bufferSize];
						buffer = new BufferWithSize(pData2, bufferSize);
						command.AllocationLength = (ushort)buffer.Length;
						this.ExecuteCommand(command, DataTransferDirection.ReceiveData, buffer);
					}
					return Marshaler.PtrToStructure<StandardInquiryData>(buffer);
				}
			}
			else if (command.PageCode == VitalProductDataPageCode.SupportedVitalProductDataPages)
			{
				unsafe
				{
					int bufferSize = Marshaler.DefaultSizeOf<SupportedVitalProductDataPagesDataPage>();
					byte* pData1 = stackalloc byte[(int)bufferSize];
					BufferWithSize buffer = new BufferWithSize(pData1, bufferSize);
					command.AllocationLength = (ushort)buffer.Length;
					this.ExecuteCommand(command, DataTransferDirection.ReceiveData, buffer);
					byte additionalLength = SupportedVitalProductDataPagesDataPage.ReadPageLength(buffer);
					command.AllocationLength += additionalLength;
					this.ExecuteCommand(command, DataTransferDirection.ReceiveData, buffer);
					return Marshaler.PtrToStructure<SupportedVitalProductDataPagesDataPage>(buffer);
				}
			}
			else { throw new InvalidOperationException("Vital product data not implemented."); }
		}

		public IScsiPassThrough Interface { get; private set; }

		[Obsolete("Do not use. This is only for viewing the data in the debugger, because manipulating the returned data has no effect. Use the appropriate Get and Set methods instead.", true)]
		private SenseData LastSenseData { get { return this.GetLastSenseData(); } }

		public void ModeSense10(ModeSense10Command command, BufferWithSize buffer) { command.AllocationLength = (ushort)buffer.Length; this.ExecuteCommand(command, DataTransferDirection.ReceiveData, buffer); }

		public TModePage ModeSense10<TModePage>(ModeSense10Command command)
			where TModePage : ModePage, new()
		{
			var result = Helper.Objects.CreateInstance<TModePage>();
			command.PageCode = result.PageCode;
			unsafe
			{
				int bufferSize = Marshaler.DefaultSizeOf<Mode10ParametersHeader>() + Marshaler.DefaultSizeOf<TModePage>();
				byte* pBuffer1 = stackalloc byte[bufferSize];
				BufferWithSize buffer = new BufferWithSize(pBuffer1, bufferSize);
				var pHeader = (Mode10ParametersHeader*)buffer.Address;
				command.AllocationLength = (ushort)buffer.LengthU32;
				pHeader->ModeDataLength = (ushort)(buffer.LengthU32 - sizeof(ushort));
				this.ModeSense10(command, buffer);
				byte requiredLength = (byte)(2 + pHeader->ModeDataLength);
				if (buffer.LengthU32 < requiredLength)
				{
					bufferSize = requiredLength;
					byte* pBuffer2 = stackalloc byte[bufferSize];
					buffer = new BufferWithSize(pBuffer2, bufferSize);
					pHeader = (Mode10ParametersHeader*)buffer.Address;
					command.AllocationLength = (ushort)buffer.LengthU32;
					pHeader->ModeDataLength = (ushort)(buffer.LengthU32 - sizeof(ushort));
					this.ModeSense10(command, buffer);
				}
				BufferWithSize newBuf = buffer.ExtractSegment(Marshaler.DefaultSizeOf<Mode10ParametersHeader>());
				Marshaler.PtrToStructure(newBuf, ref result);
			}
			return result;
		}

		public void ModeSelect10(ModeSelect10Command command, BufferWithSize buffer)
		{
			if (command.ParameterListLength == 0) { command.ParameterListLength = (ushort)buffer.Length; }
			else if (command.ParameterListLength > buffer.LengthU32) { throw new ArgumentException("Parameter list length exceeds buffer size.", "command"); }
			this.ExecuteCommand(command, DataTransferDirection.SendData, buffer);
		}

		public void ModeSelect10(ModeSelect10Command command, ModePage modePage)
		{
			unsafe
			{
				int bufferSize = (int)Marshaler.SizeOf(modePage) + Marshaler.DefaultSizeOf<Mode10ParametersHeader>();
				byte* pBuffer = stackalloc byte[(int)bufferSize];
				BufferWithSize buffer = new BufferWithSize(pBuffer, bufferSize);
				var pHeader = (Mode10ParametersHeader*)buffer.Address;
				pHeader->ModeDataLength = (ushort)Marshaler.SizeOf(modePage);
				var bufferModePage = buffer.ExtractSegment(Marshaler.DefaultSizeOf<Mode10ParametersHeader>());
				Marshaler.StructureToPtr(modePage, bufferModePage);
				this.ModeSelect10(command, buffer);
			}
		}

		private PowerConditionsModePage PowerConditions { get { return this.GetPowerConditions(new ModeSense10Command(PageControl.CurrentValues)); } }

		//public byte[] Read(bool forceUnitAccess, ulong logicalBlockAddress, uint lengthInBlocks) { var bytes = new byte[lengthInBlocks * this.BlockSize]; this.Read(forceUnitAccess, logicalBlockAddress, lengthInBlocks, bytes, 0); return bytes; }

		public void Read(bool forceUnitAccess, ulong logicalBlockAddress, uint lengthInBlocks, byte[] buffer, int bufferOffset)
		{
			//if (lengthInBlocks * this.BlockSize > buffer.Length - bufferOffset) { throw new ArgumentOutOfRangeException("buffer", buffer, "Buffer given was too small."); }
			if (logicalBlockAddress < uint.MaxValue && lengthInBlocks < ushort.MaxValue)
			{
				this.read10CommandTemp.LogicalBlockAddress = (uint)logicalBlockAddress;
				this.read10CommandTemp.TransferBlockCount = (ushort)lengthInBlocks;
				this.read10CommandTemp.ForceUnitAccess = forceUnitAccess;
				this.Read10(this.read10CommandTemp, buffer, bufferOffset);
			}
			else if (logicalBlockAddress < uint.MaxValue && lengthInBlocks < uint.MaxValue)
			{
				this.read12CommandTemp.LogicalBlockAddress = (uint)logicalBlockAddress;
				this.read12CommandTemp.TransferBlockCount = lengthInBlocks;
				this.read12CommandTemp.ForceUnitAccess = forceUnitAccess;
				this.Read12(this.read12CommandTemp, buffer, bufferOffset);
			}
			else
			{
				this.read16CommandTemp.LogicalBlockAddress = logicalBlockAddress;
				this.read16CommandTemp.TransferBlockCount = lengthInBlocks;
				this.read16CommandTemp.ForceUnitAccess = forceUnitAccess;
				this.Read16(this.read16CommandTemp, buffer, bufferOffset);
			}
		}

		//public byte[] Read06(Read06Command command) { var result = new byte[command.TransferBlockCount * this.BlockSize]; this.Read06(command, result, 0); return result; }

		public void Read06(Read06Command command, byte[] buffer, int bufferOffset) { unsafe { fixed (byte* pBuffer = &buffer[bufferOffset]) { this.Read06(command, new BufferWithSize(pBuffer, buffer.Length - bufferOffset)); } } }

		public void Read06(Read06Command command, BufferWithSize buffer)
		{
			var blockSize = this.BlockSize;
			if ((ulong)command.TransferBlockCount * blockSize > buffer.LengthU32) { throw new ArgumentException("Buffer was too small for the given transfer length.", "buffer"); }
			var totalTransferBlockCount = command.TransferBlockCount;
			uint maximumTransferBlockCount = this.MaxBlockTransferCount;

			var blocksLeft = totalTransferBlockCount;
			while (blocksLeft > 0)
			{
				var blockCountToProcess = (byte)Math.Min(blocksLeft, maximumTransferBlockCount);
				command.TransferBlockCount = blockCountToProcess;
				this.ExecuteCommand(command, DataTransferDirection.ReceiveData, buffer.ExtractSegment(blockSize * (totalTransferBlockCount - blocksLeft), blockSize * blockCountToProcess));
				blocksLeft -= blockCountToProcess;
				unchecked { command.LogicalBlockAddress += blockCountToProcess; }
			}
		}

		//public byte[] Read10(Read10Command command) { var result = new byte[command.TransferBlockCount * this.BlockSize]; this.Read10(command, result, 0); return result; }

		public void Read10(Read10Command command, byte[] buffer, int bufferOffset) { unsafe { fixed (byte* pBuffer = &buffer[bufferOffset]) { this.Read10(command, new BufferWithSize(pBuffer, buffer.Length - bufferOffset)); } } }

		public void Read10(Read10Command command, BufferWithSize buffer)
		{
			var blockSize = this.BlockSize;
			if ((ulong)command.TransferBlockCount * blockSize > buffer.LengthU32) { throw new ArgumentException("Buffer was too small for the given transfer length.", "buffer"); }
			var totalTransferBlockCount = command.TransferBlockCount;
			uint maximumTransferBlockCount = this.MaxBlockTransferCount;

			var blocksLeft = totalTransferBlockCount;
			while (blocksLeft > 0)
			{
				var blockCountToProcess = (ushort)Math.Min(blocksLeft, maximumTransferBlockCount);
				command.TransferBlockCount = blockCountToProcess;
				this.ExecuteCommand(command, DataTransferDirection.ReceiveData, buffer.ExtractSegment(blockSize * (totalTransferBlockCount - blocksLeft), blockSize * blockCountToProcess));
				blocksLeft -= blockCountToProcess;
				unchecked { command.LogicalBlockAddress += blockCountToProcess; }
			}
		}

		//public byte[] Read12(Read12Command command) { byte[] result = new byte[command.TransferBlockCount * this.BlockSize]; this.Read12(command, result, 0); return result; }

		public void Read12(Read12Command command, byte[] buffer, int bufferOffset) { unsafe { fixed (byte* pBuffer = &buffer[bufferOffset]) { this.Read12(command, new BufferWithSize(pBuffer, buffer.Length - bufferOffset)); } } }

		public void Read12(Read12Command command, BufferWithSize buffer)
		{
			var blockSize = this.BlockSize;
			if ((ulong)command.TransferBlockCount * blockSize > buffer.LengthU32) { throw new ArgumentException("Buffer was too small for the given transfer length.", "buffer"); }
			var totalTransferBlockCount = command.TransferBlockCount;
			uint maximumTransferBlockCount = this.MaxBlockTransferCount;

			var blocksLeft = totalTransferBlockCount;
			while (blocksLeft > 0)
			{
				var blockCountToProcess = Math.Min(blocksLeft, maximumTransferBlockCount);
				command.TransferBlockCount = blockCountToProcess;
				this.ExecuteCommand(command, DataTransferDirection.ReceiveData, buffer.ExtractSegment(blockSize * (totalTransferBlockCount - blocksLeft), blockSize * blockCountToProcess));
				blocksLeft -= blockCountToProcess;
				unchecked { command.LogicalBlockAddress += blockCountToProcess; }
			}
		}

		//public byte[] Read16(Read16Command command) { var result = new byte[command.TransferBlockCount * this.BlockSize]; this.Read16(command, result, 0); return result; }

		public void Read16(Read16Command command, byte[] buffer, int bufferOffset) { unsafe { fixed (byte* pBuffer = &buffer[bufferOffset]) { this.Read16(command, new BufferWithSize(pBuffer, buffer.Length - bufferOffset)); } } }

		public void Read16(Read16Command command, BufferWithSize buffer)
		{
			var blockSize = this.BlockSize;
			if ((ulong)command.TransferBlockCount * blockSize > buffer.LengthU32) { throw new ArgumentException("Buffer was too small for the given transfer length.", "buffer"); }
			var totalTransferBlockCount = command.TransferBlockCount;
			uint maximumTransferBlockCount = this.MaxBlockTransferCount;

			var blocksLeft = totalTransferBlockCount;
			while (blocksLeft > 0)
			{
				var blockCountToProcess = Math.Min(blocksLeft, maximumTransferBlockCount);
				command.TransferBlockCount = blockCountToProcess;
				this.ExecuteCommand(command, DataTransferDirection.ReceiveData, buffer.ExtractSegment(blockSize * (totalTransferBlockCount - blocksLeft), blockSize * blockCountToProcess));
				blocksLeft -= blockCountToProcess;
				unchecked { command.LogicalBlockAddress += blockCountToProcess; }
			}
		}

		//public byte[] Read32(Read32Command command) { var result = new byte[command.TransferBlockCount * this.BlockSize]; this.Read32(command, result, 0); return result; }

		public void Read32(Read32Command command, byte[] buffer, int bufferOffset) { unsafe { fixed (byte* pBuffer = &buffer[bufferOffset]) { this.Read32(command, new BufferWithSize(pBuffer, buffer.Length - bufferOffset)); } } }

		public void Read32(Read32Command command, BufferWithSize buffer)
		{
			var blockSize = this.BlockSize;
			if ((ulong)command.TransferBlockCount * blockSize > buffer.LengthU32) { throw new ArgumentException("Buffer was too small for the given transfer length.", "buffer"); }
			var totalTransferBlockCount = command.TransferBlockCount;
			uint maximumTransferBlockCount = this.MaxBlockTransferCount;

			var blocksLeft = totalTransferBlockCount;
			while (blocksLeft > 0)
			{
				var blockCountToProcess = Math.Min(blocksLeft, maximumTransferBlockCount);
				command.TransferBlockCount = blockCountToProcess;
				this.ExecuteCommand(command, DataTransferDirection.ReceiveData, buffer.ExtractSegment(blockSize * (totalTransferBlockCount - blocksLeft), blockSize * blockCountToProcess));
				blocksLeft -= blockCountToProcess;
				unchecked { command.LogicalBlockAddress += blockCountToProcess; }
			}
		}

		public byte[] ReadBufferData(ReadBufferCommand command, int length) { var result = new byte[length]; this.ReadBufferData(command, result, 0); return result; }

		public void ReadBufferData(ReadBufferCommand command, byte[] buffer, int resultOffset) { this.ReadBufferData(command, buffer, resultOffset, buffer.Length - resultOffset); }

		public void ReadBufferData(ReadBufferCommand command, byte[] buffer, int resultOffset, int resultLength)
		{
			if (resultLength > buffer.Length - resultOffset)
			{ throw new ArgumentOutOfRangeException("resultLength", resultLength, "Result length cannot overflow buffer."); }
			unsafe
			{
				fixed (byte* pBuffer = buffer)
				{ this.ReadBufferData(command, new BufferWithSize(pBuffer + resultOffset, resultLength)); }
			}
		}

		public void ReadBufferData(ReadBufferCommand command, BufferWithSize buffer) { command.Mode = ReadBufferMode.Data; this.ReadBuffer(command, buffer); }

		public ReadBufferDescriptor ReadBufferDescriptor(ReadBufferCommand command)
		{
			ReadBufferDescriptor result = new ReadBufferDescriptor();
			command.Mode = ReadBufferMode.Descriptor;
			unsafe { this.ReadBuffer(command, new BufferWithSize((IntPtr)(&result), Marshaler.DefaultSizeOf<ReadBufferDescriptor>())); }
			return result;
		}

		public BufferCombinedHeaderAndData ReadBufferCombinedHeaderAndData(ReadBufferCommand command)
		{
			command.Mode = ReadBufferMode.CombinedHeaderAndData;
			unsafe
			{
				BufferWithSize buffer;
				int bufferSize = Marshaler.DefaultSizeOf<BufferCombinedHeaderAndData>();
				bool stackAlloc = Marshaler.ShouldStackAlloc(bufferSize);
				if (stackAlloc)
				{
					byte* pResult1 = stackalloc byte[bufferSize];
					buffer = new BufferWithSize(pResult1, bufferSize);
				}
				else { buffer = BufferWithSize.AllocHGlobal(bufferSize); }
				try
				{
					this.ReadBuffer(command, buffer);
					int requiredSize = (int)BufferCombinedHeaderAndData.ReadBufferCapacity(buffer.Address) + Marshaler.DefaultSizeOf<BufferCombinedHeaderAndData>();
					if (bufferSize < requiredSize)
					{
						bufferSize = requiredSize;
						bool stackAlloc2 = Marshaler.ShouldStackAlloc(bufferSize);
						if (stackAlloc)
						{
							if (stackAlloc2)
							{
								byte* pResult2 = stackalloc byte[bufferSize];
								buffer = new BufferWithSize(pResult2, bufferSize);
							}
							else { buffer = BufferWithSize.AllocHGlobal(bufferSize); }
						}
						else
						{
							if (stackAlloc2)
							{
								BufferWithSize.FreeHGlobal(buffer);
								byte* pResult2 = stackalloc byte[bufferSize];
								buffer = new BufferWithSize(pResult2, bufferSize);
							}
							else { buffer = BufferWithSize.ReAllocHGlobal(buffer, bufferSize); }
						}
						stackAlloc = stackAlloc2;
						this.ReadBuffer(command, buffer);
					}
					return Marshaler.PtrToStructure<BufferCombinedHeaderAndData>(buffer);
				}
				finally { if (!stackAlloc) { BufferWithSize.FreeHGlobal(buffer); } }
			}
		}

		private void ReadBuffer(ReadBufferCommand command, BufferWithSize buffer) { command.AllocationLength = (ushort)buffer.Length; this.ExecuteCommand(command, DataTransferDirection.ReceiveData, buffer); }

		public ReadCapacityInfo ReadCapacity() { return this.ReadCapacity(new ReadCapacityCommand()); }

		public ReadCapacityInfo ReadCapacity(ReadCapacityCommand command)
		{
			var result = new ReadCapacityInfo();
			unsafe { this.ExecuteCommand(command, DataTransferDirection.ReceiveData, new BufferWithSize((IntPtr)(&result), Marshaler.DefaultSizeOf<ReadCapacityInfo>())); }
			return result;
		}

		[Obsolete("Do not use. This is only for viewing the data in the debugger, because manipulating the returned data has no effect. Use the appropriate Get and Set methods instead.", true)]
		private ReadWriteErrorRecoveryParametersPage ReadWriteErrorRecoveryParameters { get { return this.GetReadWriteErrorRecoveryParameters(new ModeSense10Command(PageControl.CurrentValues)); } }

		public SenseData RequestSense() { return this.RequestSense(new RequestSenseCommand()); }

		public SenseData RequestSense(RequestSenseCommand command)
		{
			SenseData result;
			unsafe
			{
				int bufferSize = 252;
				byte* pSenseData = stackalloc byte[bufferSize];
				BufferWithSize buffer = new BufferWithSize(pSenseData, bufferSize);
				command.AllocationLength = (byte)buffer.Length;
				this.ExecuteCommand(command, DataTransferDirection.ReceiveData, buffer);
				result = Marshaler.PtrToStructure<SenseData>(buffer);
			}
			return result;
		}

		public void Seek10(Seek10Command command) { this.ExecuteCommand(command, DataTransferDirection.NoData, BufferWithSize.Zero); }

		public void SendDiagnostic(SendDiagnosticCommand command) { this.SendDiagnostic(command, BufferWithSize.Zero); }

		public void SendDiagnostic(SendDiagnosticCommand command, BufferWithSize buffer)
		{
			command.ParameterListLength = (byte)buffer.Length;
			switch (command.SelfTestCode)
			{
				case SelfTestCode.BackgroundShortSelfTest:
					if (command.ParameterListLength != 0) { throw new InvalidOperationException(); }
					break;
				case SelfTestCode.BackgroundExtendedSelfTest:
					if (command.ParameterListLength != 0) { throw new InvalidOperationException(); }
					break;
				case SelfTestCode.AbortBackgroundSelfTest:
					if (command.ParameterListLength != 0) { throw new InvalidOperationException(); }
					break;
				case SelfTestCode.ForegroundShortSelfTest:
					if (command.ParameterListLength != 0) { throw new InvalidOperationException(); }
					break;
				case SelfTestCode.ForegroundExtendedSelfTest:
					if (command.ParameterListLength != 0) { throw new InvalidOperationException(); }
					break;
				case null:
					if (command.ParameterListLength != 0) { throw new InvalidOperationException(); }
					break;
			}
			this.ExecuteCommand(command, DataTransferDirection.SendData, buffer);
		}

		public void SetCachingInformation(ModeSelect10Command command, CachingModePage modePage) { this.ModeSelect10(command, modePage); }

		public void SetPowerConditions(ModeSelect10Command command, PowerConditionsModePage modePage) { this.ModeSelect10(command, modePage); }

		public void SetReadWriteErrorRecoveryParameters(ModeSelect10Command command, ReadWriteErrorRecoveryParametersPage modePage) { this.ModeSelect10(command, modePage); }

		public void SetRemovableMediaBit(SetRemovableMediaBitCommand command)
		{
			unsafe
			{
				const byte BUFFER_SIZE = 36;
				byte* pBuffer = stackalloc byte[BUFFER_SIZE];
				this.ExecuteCommand(command, DataTransferDirection.SendData, new BufferWithSize(pBuffer, BUFFER_SIZE));
			}
		}

		public void SynchronizeCache() { this.SynchronizeCache(new SynchronizeCache10Command()); }

		public void SynchronizeCache(SynchronizeCache10Command command) { this.ExecuteCommand(command, DataTransferDirection.NoData, BufferWithSize.Zero); }

		public ScsiStatus TestUnitReady() { return this.TestUnitReady(new TestUnitReadyCommand()); }

		public ScsiStatus TestUnitReady(TestUnitReadyCommand command) { return this.ExecuteCommand(command, DataTransferDirection.NoData, BufferWithSize.Zero, false); }

		public uint TimeoutSeconds { get { return this._TimeoutSeconds; } set { this._TimeoutSeconds = value; } }

		public uint MaxBlockTransferCount { get { return this._MaxBlockTransferCount; } }

		public void Write(bool forceUnitAccess, ulong logicalBlockAddress, uint lengthInBlocks, byte[] buffer, int bufferOffset)
		{
			//if (lengthInBlocks * this.BlockSize > buffer.Length - bufferOffset) { throw new ArgumentOutOfRangeException("buffer", buffer, "Buffer given was too small."); }
			if (logicalBlockAddress < uint.MaxValue && lengthInBlocks < ushort.MaxValue)
			{
				this.write10CommandTemp.LogicalBlockAddress = (uint)logicalBlockAddress;
				this.write10CommandTemp.TransferBlockCount = (ushort)lengthInBlocks;
				this.write10CommandTemp.ForceUnitAccess = forceUnitAccess;
				this.Write10(this.write10CommandTemp, buffer, bufferOffset);
			}
			else if (logicalBlockAddress < uint.MaxValue && lengthInBlocks < uint.MaxValue)
			{
				this.write12CommandTemp.LogicalBlockAddress = (uint)logicalBlockAddress;
				this.write12CommandTemp.TransferBlockCount = lengthInBlocks;
				this.write12CommandTemp.ForceUnitAccess = forceUnitAccess;
				this.Write12(this.write12CommandTemp, buffer, bufferOffset);
			}
			else { throw new NotSupportedException(); }
		}

		public void Write10(Write10Command command, byte[] buffer, int bufferOffset) { unsafe { fixed (byte* pBuffer = &buffer[bufferOffset]) { this.Write10(command, new BufferWithSize(pBuffer, buffer.Length - bufferOffset)); } } }

		public void Write10(Write10Command command, BufferWithSize buffer)
		{
			var blockSize = this.BlockSize;
			if ((ulong)command.TransferBlockCount * blockSize > buffer.LengthU32) { throw new ArgumentException("Buffer was too small for the given transfer length.", "buffer"); }
			var totalTransferBlockCount = command.TransferBlockCount;
			uint maximumTransferBlockCount = this.MaxBlockTransferCount;

			var blocksLeft = totalTransferBlockCount;
			while (blocksLeft > 0)
			{
				var blockCountToProcess = (ushort)Math.Min(blocksLeft, maximumTransferBlockCount);
				command.TransferBlockCount = blockCountToProcess;
				this.ExecuteCommand(command, DataTransferDirection.SendData, buffer.ExtractSegment(blockSize * (totalTransferBlockCount - blocksLeft), blockSize * blockCountToProcess));
				blocksLeft -= blockCountToProcess;
				unchecked { command.LogicalBlockAddress += blockCountToProcess; }
			}
		}

		public void Write12(Write12Command command, byte[] buffer, int bufferOffset) { unsafe { fixed (byte* pBuffer = &buffer[bufferOffset]) { this.Write12(command, new BufferWithSize(pBuffer, buffer.Length - bufferOffset)); } } }

		public void Write12(Write12Command command, BufferWithSize buffer)
		{
			var blockSize = this.BlockSize;
			if ((ulong)command.TransferBlockCount * blockSize > buffer.LengthU32) { throw new ArgumentException("Buffer was too small for the given transfer length.", "buffer"); }
			var totalTransferBlockCount = command.TransferBlockCount;
			uint maximumTransferBlockCount = this.MaxBlockTransferCount;

			var blocksLeft = totalTransferBlockCount;
			while (blocksLeft > 0)
			{
				var blockCountToProcess = Math.Min(blocksLeft, maximumTransferBlockCount);
				command.TransferBlockCount = blockCountToProcess;
				this.ExecuteCommand(command, DataTransferDirection.SendData, buffer.ExtractSegment(blockSize * (totalTransferBlockCount - blocksLeft), blockSize * blockCountToProcess));
				blocksLeft -= blockCountToProcess;
				unchecked { command.LogicalBlockAddress += blockCountToProcess; }
			}
		}

		#region IScsiDevice
		protected virtual void Read(long position, byte[] buffer, int bufferOffset, int length, bool forceUnitAccess)
		{
			var blockSize = this.BlockSize;
			long rem;
			long logicalBlockAddress = Math.DivRem(position, blockSize, out rem);
			if (rem != 0) { var ex = new DataMisalignedException(); throw new ArgumentException(null, "position", ex); }
			var blockLength = (uint)Math.DivRem(length, blockSize, out rem);
			if (rem != 0) { var ex = new DataMisalignedException(); throw new ArgumentException(null, "length", ex); }
			this.Read(forceUnitAccess, (ulong)logicalBlockAddress, blockLength, buffer, bufferOffset);
		}

		protected virtual void Write(long position, byte[] buffer, int bufferOffset, int length, bool forceUnitAccess)
		{
			bool unaligned = false;
			long rem;
			var blockSize = this.BlockSize;
			long logicalBlockAddress = Math.DivRem(position, blockSize, out rem);
			//if (rem != 0) { var ex = new DataMisalignedException(); throw new ArgumentException("Value is not aligned to the correct boundary.", "position", ex); }
			unaligned |= rem != 0;
			var blockLength = (uint)Math.DivRem(length, blockSize, out rem);
			//if (rem != 0) { var ex = new DataMisalignedException(); throw new ArgumentException("Value is not aligned to the correct boundary.", "length", ex); }
			unaligned |= rem != 0;
			if (length > buffer.Length - bufferOffset) { throw new ArgumentException("Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection."); }
			if (!unaligned)
			{
				this.Write(forceUnitAccess, (ulong)logicalBlockAddress, blockLength, buffer, bufferOffset);
			}
			else
			{
				var bpsRead = blockSize;
				var bpsWrite = blockSize;
				if (bpsRead != bpsWrite) { throw new NotSupportedException("Read-Modify-Write not supported for unequal block lengths."); }
				long alignedPosition = position / bpsWrite * bpsWrite;

				var alignedData = new byte[(position + length - alignedPosition + bpsWrite - 1) / bpsWrite * bpsWrite];
				this.Read(alignedPosition, alignedData, 0, alignedData.Length, forceUnitAccess);
				Buffer.BlockCopy(buffer, bufferOffset, alignedData, (int)(position - alignedPosition), length);
				this.Write(forceUnitAccess, (ulong)alignedPosition / bpsWrite, (uint)alignedData.Length / bpsWrite, alignedData, 0);
			}
		}

		protected virtual void Flush() { this.SynchronizeCache(new SynchronizeCache10Command()); }
		public virtual ScsiStatus Status { get { return this.TestUnitReady(); } }
		void IScsiDevice.Read(long position, byte[] buffer, int bufferOffset, int length, bool forceUnitAccess) { this.Read(position, buffer, bufferOffset, length, forceUnitAccess); }
		void IScsiDevice.Write(long position, byte[] buffer, int bufferOffset, int length, bool forceUnitAccess) { this.Write(position, buffer, bufferOffset, length, forceUnitAccess); }
		void IScsiDevice.Flush() { this.Flush(); }
		#endregion

		public virtual bool CanRead { get { return true; } }
		public virtual bool CanSeek { get { return true; } }
		public virtual bool CanWrite { get { return this._CanWrite; } }



		public static ScsiDevice Create(IScsiPassThrough passThrough, bool leaveOpen)
		{
			var inq = passThrough.ScsiInquiry(true);
			ScsiDevice result;
			switch (inq.PeripheralDeviceType)
			{
				case PeripheralDeviceType.DirectAccessBlockDevice:
					result = new Block.BlockDevice(passThrough, leaveOpen);
					break;
				case PeripheralDeviceType.CDDvdDevice:
					result = new Multimedia.MultimediaDevice(passThrough, leaveOpen);
					break;
				default:
					result = new ScsiDevice(passThrough, leaveOpen);
					break;
			}
			return result;
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions