Click here to Skip to main content
15,886,137 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 145.3K   8.1K   146  
Ever wonder how programs like Nero work? They make their own SCSI libraries... like this!
using System;
using System.Diagnostics;
using System.IO;
using Helper.IO;
using Helper;

namespace FileSystems.Udf
{
	public partial class UdfLogicalVolume : IDisposable
	{
		private UdfPartition[] partitions;
		private LogicalVolumeDescriptor descriptor;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private LogicalVolumeIntegrityDescriptor _IntegrityDescriptor;
		private UdfVolume fs;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private bool leaveOpen;
		private UdfFileSet _FileSet;

		//IMPORTANT NOTE: You CANNOT change the Logical Volume Identifier or a Partition Number unless you notify UdfVolume first!

		internal UdfLogicalVolume(UdfVolume fs, LogicalVolumeDescriptor descriptor, UdfPartition[] partitions, bool leavePartitionsOpen)
		{
			this.fs = fs;
			this.partitions = partitions;
			this.descriptor = descriptor;
			this.leaveOpen = leavePartitionsOpen;
		}

		~UdfLogicalVolume() { this.Dispose(false); }

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

		protected virtual void Dispose(bool disposing)
		{
			if (disposing)
			{
				try
				{
					if (!this.leaveOpen)
					{
						for (int i = 0; i < this.partitions.Length; i++)
						{
							this.partitions[i].Dispose();
							this.partitions[i] = null;
						}
					}
				}
				finally { this.partitions = null; }
			}
		}

		//public LongAllocationDescriptor[] Allocate(ushort preferredPartition, ushort preferredBlock, long bytesToAllocate) { throw new NotImplementedException(); }

		public UdfFileSet FileSet
		{
			get
			{
				if (this._FileSet == null)
				{
					var extent = this.descriptor.FileSetDescriptorExtent;
					//Assume partition[i] is number i
					var data = new byte[extent.ShortAllocationDescriptor.ByteLength];
					int read = this.Read(extent, AllocationDescriptorType.ShortAllocationDescriptors, 0, data, 0, data.Length);
					Trace.Assert(read == data.Length);
					var converted = (FileSetDescriptor)this.fs.ConvertTaggedDescriptorToManaged(data, 0, true);
					if (converted.TagLocation != extent.ExtentLocation.LogicalBlockNumber / this.LogicalBlockSize) { throw new InvalidDataException("Invalid integrity descriptor."); }
					this._FileSet = new UdfFileSet(this, converted);
				}
				return this._FileSet;
			}
		}

		private LogicalVolumeIntegrityDescriptor IntegrityDescriptor
		{
			get
			{
				if (this._IntegrityDescriptor == null)
				{
					var extent = this.descriptor.IntegritySequenceExtent;
					//Assume partition[i] is number i
					var data = new byte[extent.ByteLength];
					long newPosition = this.fs.LogicalSectorSize * extent.LogicalSectorNumber;
					if (newPosition != this.fs.BaseStream.Position) { this.fs.BaseStream.Position = newPosition; }
					this.fs.BaseStream.ReadExactly(data, 0, data.Length);
					var converted = (LogicalVolumeIntegrityDescriptor)this.fs.ConvertTaggedDescriptorToManaged(data, 0, true);
					if (converted.TagLocation != newPosition / this.LogicalBlockSize) { throw new InvalidDataException("Invalid integrity descriptor."); }
					this._IntegrityDescriptor = converted;
				}
				return this._IntegrityDescriptor;
			}
		}

		//Actually unsigned, but no one uses 2-GB block sizes... making these signed prevents unnecessary casts elsewhere
		public int LogicalBlockSize { get { return this.descriptor.LogicalBlockSize; } }

		public int MaximumExtentLength { get { return (1 << 30) - this.LogicalBlockSize; } }

		/// <param name="type">The type of allocation descriptors to read if <see cref="allocationDescriptor.ShortAllocationDescriptor.ExtentType"/> is <see cref="ExtentType.ExtentOfNextAllocationDescriptors"/>. In other cases, this value has no meaning and is ignored.</param>
		public int Read(LongAllocationDescriptor allocationDescriptor, AllocationDescriptorType type, long byteOffset, byte[] buffer, int bufferOffset, int length)
		{
			if (bufferOffset < 0) { throw new ArgumentOutOfRangeException("bufferOffset", bufferOffset, "Nonnegative number required."); }
			if (length < 0) { throw new ArgumentOutOfRangeException("length", length, "Nonnegative number required."); }
			if (byteOffset < 0) { throw new ArgumentOutOfRangeException("byteOffset", byteOffset, "Nonnegative number required."); }
			if (buffer.Length < bufferOffset + length) { 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."); }
			int read;
			if (allocationDescriptor.ShortAllocationDescriptor.AllocationType != ExtentType.NextAllocationDescriptorsExtent)
			{ if (allocationDescriptor.ShortAllocationDescriptor.ByteLength < length) { throw new ArgumentException("Cannot read more data than the allocation descriptor describes.", "length"); } }
			var partition = this.partitions[allocationDescriptor.ExtentLocation.PartitionReferenceNumber];
			switch (allocationDescriptor.ShortAllocationDescriptor.AllocationType)
			{
				case ExtentType.Recorded:
					long newPosition = allocationDescriptor.ExtentLocation.LogicalBlockNumber * this.LogicalBlockSize;
					if (newPosition != partition.Position) { partition.Position = newPosition; }
					read = partition.ReadFully(buffer, bufferOffset, allocationDescriptor.ShortAllocationDescriptor.ByteLength);
					break;
				case ExtentType.Allocated:
				case ExtentType.Unallocated:
					if (buffer.Length - bufferOffset == length) { buffer.Initialize(); }
					else { unsafe { fixed (byte* pBuffer = &buffer[bufferOffset]) { for (uint i = 0; i < length; i++) { pBuffer[i] = 0; } } } }
					read = length;
					break;
				case ExtentType.NextAllocationDescriptorsExtent:
					AllocationExtentDescriptor allocExtDesc;
					{
						var allocExtDescBin = new byte[allocationDescriptor.ShortAllocationDescriptor.ByteLength];
						partition.ReadExactly(allocExtDescBin, 0, allocExtDescBin.Length);
						allocExtDesc = (AllocationExtentDescriptor)partition.Volume.ConvertTaggedDescriptorToManaged(allocExtDescBin, 0, true);
					}
					read = 0;
					int allocExtDescOffset = 0;
					while (allocExtDescOffset < allocExtDesc.AllocationDescriptors.Length && length > 0)
					{
						LongAllocationDescriptor longAd;
						switch (type)
						{
							case AllocationDescriptorType.ShortAllocationDescriptors:
								var shortAd = Marshaler.PtrToStructure<ShortAllocationDescriptor>(allocExtDesc.AllocationDescriptors, allocExtDescOffset);
								longAd = new LongAllocationDescriptor(shortAd, allocationDescriptor.ExtentLocation.PartitionReferenceNumber, ExtentFlags.None, 0);
								allocExtDescOffset += Marshaler.SizeOf(shortAd);
								break;
							case AllocationDescriptorType.LongAllocationDescriptors:
								longAd = Marshaler.PtrToStructure<LongAllocationDescriptor>(allocExtDesc.AllocationDescriptors, allocExtDescOffset);
								allocExtDescOffset += Marshaler.SizeOf(longAd);
								break;
							case AllocationDescriptorType.ExtendedAllocationDescriptors:
								throw new NotSupportedException("Extended Allocation Descriptors not valid in UDF.");
							case AllocationDescriptorType.DataResidentInAllocationDescriptors: //If it were resident then it wouldn't be here
							default:
								throw new ArgumentOutOfRangeException("type", type, "Invalid extent type.");
						}
						int r = this.Read(longAd, type, byteOffset, buffer, bufferOffset, length);
						if (r != length) { throw new InvalidOperationException("Expected to read entire length."); }
						byteOffset += r;
						bufferOffset += r;
						read += r;
						length -= r;
					}
					break;
				default:
					throw new ArgumentOutOfRangeException("extent", allocationDescriptor, "Invalid extent allocation type.");
			}
			return read;
		}

		/// <param name="type">The type of allocation descriptors to read if <see cref="allocationDescriptor.ShortAllocationDescriptor.ExtentType"/> is <see cref="ExtentType.ExtentOfNextAllocationDescriptors"/>. In other cases, this value has no meaning and is ignored.</param>
		public void Write(LongAllocationDescriptor allocationDescriptor, AllocationDescriptorType type, long byteOffset, byte[] buffer, int bufferOffset, int length)
		{
			if (bufferOffset < 0) { throw new ArgumentOutOfRangeException("bufferOffset", bufferOffset, "Nonnegative number required."); }
			if (length < 0) { throw new ArgumentOutOfRangeException("length", length, "Nonnegative number required."); }
			if (byteOffset < 0) { throw new ArgumentOutOfRangeException("byteOffset", byteOffset, "Nonnegative number required."); }
			if (buffer.Length < bufferOffset + length) { 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 ((this.DomainIdentifier.Flags & (DomainFlags.SoftWriteProtect | DomainFlags.HardWriteProtect)) != 0)
			{ throw new IOException("The logical volume is write-protected.", new UnauthorizedAccessException()); }

			throw new NotImplementedException();
		}

		public DomainIdentifierSuffix DomainIdentifier { get { return this.descriptor.DomainIdentifier.DomainSuffix; } }
	}
}

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