Click here to Skip to main content
15,896,557 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.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Threading;
using Microsoft.Win32.SafeHandles;

namespace Helper.IO
{
	/// <summary>
	/// A number of API functions from the Windows Driver Kit are used here, as well as some "undocumented" ones, like NtQueryObject().
	/// However, it is safe to assume that they will not change for a while, since they have been here since Windows NT up to Windows 7, and they have no particular deficiencies that would make Microsoft want to remove them.
	/// I use them because you can do some things with those functions that you can't do with the Win32 API (for example, get a file path from its handle) -- or at least, not before Vista.
	/// </summary>
	//[DebuggerStepThrough]
	public class Win32FileStream : Stream
	{
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private static unsafe IOCompletionCallback IOCompletionCallback = AsyncFSCallback;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private bool _CanRead;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private bool _CanWrite;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private long _Position;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private SafeFileHandle _SafeFileHandle;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private bool modeInformationQueried;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private bool sizeInformationQueried;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private UnsafeNativeMethods.FileFsSizeInformation sizeInformation;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private bool _IsSynchronous;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private bool _NoIntermediateBuffering;

		[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true), SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode = true)]
		public Win32FileStream(string path, FileAccess access, FileShare share, FileMode mode, FileOptions options, Win32FileOptions extraOptions)
			: this(UnsafeNativeMethods.CreateFile(path, UnsafeNativeMethods.GetAccess(access), share, mode, options, extraOptions), -1, false) { }

		[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true), SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode = true)]
		public Win32FileStream(string path, int access, FileShare share, FileMode mode, FileOptions options, Win32FileOptions extraOptions)
			: this(UnsafeNativeMethods.CreateFile(path, access, share, mode, options, extraOptions), -1, false) { }

		[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true), SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode = true)]
		public Win32FileStream(SafeFileHandle safeFileHandle) : this(safeFileHandle, -1, true) { }

		[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true), SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode = true)]
		public Win32FileStream(SafeFileHandle safeFileHandle, FileAccess additionalAccessRestrictions) : this(safeFileHandle, UnsafeNativeMethods.GetAccess(additionalAccessRestrictions), true) { }

		[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true), SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode = true)]
		private Win32FileStream(SafeFileHandle safeFileHandle, int accessMask, bool cloneHandle)
		{
			if (!cloneHandle && accessMask != -1) { throw new ArgumentException("Access mask must be -1 if not cloning handle."); }
			if (cloneHandle) { safeFileHandle = UnsafeNativeMethods.DuplicateHandle(safeFileHandle, accessMask, false); }

			this.SafeFileHandle = safeFileHandle;
			this._CanRead = (this.AccessMask & 1) != 0;
			this._CanWrite = (this.AccessMask & 2) != 0;
			//if (!ThreadPool.BindHandle(safeFileHandle)) { throw new IOException("BindHandle for ThreadPool failed on this handle."); }
		}

		#region Async Result
		private sealed class AsyncResult : IAsyncResult
		{
			private bool _IsComplete;
			public AsyncResult(SafeFileHandle handle, byte[] userBuffer, int userBufferOffset, int length, object state, AsyncCallback userCallback, bool write)
			{
				this.AsyncWaitHandle = new ManualResetEvent(false);
				this.AsyncState = state;
				this.UserCallback = userCallback;
				this.UserBuffer = userBuffer;
				this.UserBufferOffset = userBufferOffset;
				this.Length = length;
				this.SafeFileHandle = handle;
				this.Write = write;
			}
			public AsyncCallback UserCallback { get; private set; }
			public object AsyncState { get; private set; }
			public ManualResetEvent AsyncWaitHandle { get; private set; }
			public bool CompletedSynchronously { get; internal set; }
			public bool IsCompleted { get { return this._IsComplete || this.CompletedSynchronously || this.AsyncWaitHandle.WaitOne(0, false); } internal set { this._IsComplete = value; } }
			public unsafe NativeOverlapped* Overlapped { get; internal set; }
			public byte[] UserBuffer { get; private set; }
			public int UserBufferOffset { get; private set; }
			public int Length { get; private set; }
			public int OffsetInHGlobalBufferDueToAlignment { get; internal set; }
			public IntPtr HGlobalBufferAddress { get; internal set; }
			public IntPtr HGlobalBufferLength { get; internal set; }
			public SafeFileHandle SafeFileHandle { get; private set; }
			WaitHandle IAsyncResult.AsyncWaitHandle { get { return this.AsyncWaitHandle; } }
			public bool Write { get; private set; }
			public int ErrorCode { get; set; }
		}
		#endregion

		public int AccessMask { get { return UnsafeNativeMethods.GetAccessMask(this.SafeFileHandle); } }

		public long AllocatedSize { get { UnsafeNativeMethods.FileStandardInformation info; UnsafeNativeMethods.GetStandardInfo(this.SafeFileHandle, true, out info); return info.AllocationSize; } }

		private static unsafe void AsyncFSCallback(uint errorCode, uint numBytes, NativeOverlapped* pOverlapped)
		{
			var unpacked = Overlapped.Unpack(pOverlapped);
			var asyncResult = (AsyncResult)unpacked.AsyncResult;
			if ((errorCode == 0x6d) || (errorCode == 0xe8)) { errorCode = 0; }
			asyncResult.ErrorCode = (int)errorCode;
			asyncResult.CompletedSynchronously = false;
			asyncResult.IsCompleted = true;
			ManualResetEvent @event = asyncResult.AsyncWaitHandle;
			if ((@event != null) && !@event.Set()) { Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); }
			var callback = asyncResult.UserCallback;
			if (callback != null) { callback(asyncResult); }
		}

		public FileAttributes Attributes { get { return (FileAttributes)this.AttributesWin32; } }

		public Win32FileAttributes AttributesWin32 { get { return UnsafeNativeMethods.GetBasicInfo(this.SafeFileHandle).FileAttributes; } }

		/// <summary>Total number of free allocation units on the volume that are available to the user associated with the calling thread.</summary>
		public long AvailableClusters { get { this.RefreshSizeInformation(false); return this.sizeInformation.AvailableAllocationUnits; } }

		private IAsyncResult BeginProcessing(byte[] buffer, int offset, int count, AsyncCallback callback, object state, bool write)
		{
			if (write) { throw new NotImplementedException("Read-Modify-Write is not implemented."); }

			//return write ? base.BeginWrite(buffer, offset, count, callback, state) : base.BeginRead(buffer, offset, count, callback, state);
			if (callback != null) { throw new NotImplementedException("BindHandle does not always work correctly (throws an exception in Release mode when not debugging) and using a callback is not yet supported."); }
			long position = this.Position;

			var bps = this.BytesPerSector;
			bool aligned = position % bps == 0 && count % bps == 0;
			long alignedPosition = position / bps * bps;
			bool success = false;
			var fileHandle = this.SafeFileHandle;
			try
			{
				fileHandle.DangerousAddRef(ref success);
				Debug.Assert(success);
				IntPtr len = aligned ? IntPtr.Zero : (IntPtr)((position + count - alignedPosition + bps - 1) / bps * bps);
				var async = new AsyncResult(fileHandle, buffer, offset, count, state, callback, write)
				{
					HGlobalBufferAddress = aligned ? IntPtr.Zero : Marshal.AllocHGlobal(len),
					HGlobalBufferLength = len,
					OffsetInHGlobalBufferDueToAlignment = checked((int)(position - alignedPosition)),
				};
				var waitHandle = async.AsyncWaitHandle.SafeWaitHandle;
				try
				{
					waitHandle.DangerousAddRef(ref success);
					Debug.Assert(success);
					var overlapped = new Overlapped(unchecked((int)position), (int)(position >> 32), waitHandle.DangerousGetHandle(), async);
					unsafe
					{
						NativeOverlapped* pOverlapped;
						IOCompletionCallback nativeCallback;
						if (callback != null) { nativeCallback = IOCompletionCallback; pOverlapped = overlapped.Pack(nativeCallback, buffer); }
						else { nativeCallback = null; pOverlapped = overlapped.UnsafePack(nativeCallback, buffer); }
						async.Overlapped = pOverlapped;
						fixed (byte* pBuffer = &buffer[offset]) //Overlapped already pinned it, so it's ok to do this
						{
							if (write ?
								!UnsafeNativeMethods.WriteFileAsync(this.SafeFileHandle, aligned ? (IntPtr)pBuffer : async.HGlobalBufferAddress, aligned ? count : (int)async.HGlobalBufferLength, pOverlapped, nativeCallback) :
								!UnsafeNativeMethods.ReadFileAsync(this.SafeFileHandle, aligned ? (IntPtr)pBuffer : async.HGlobalBufferAddress, aligned ? count : (int)async.HGlobalBufferLength, pOverlapped, nativeCallback)
								)
							{
								//Async IO
							}
							else
							{
								//IO was actually done synchronously
								async.CompletedSynchronously = true;
							}
						}
						return async;
					}
				}
				catch { if (success) { waitHandle.DangerousRelease(); } throw; }
			}
			catch { if (success) { fileHandle.DangerousRelease(); } throw; }
		}

		public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { return this.BeginProcessing(buffer, offset, count, callback, state, false); }

		//public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { return this.BeginProcessing(buffer, offset, count, callback, state, true); }

		public long BytesPerCluster { get { return Math.BigMul(this.BytesPerSector, this.SectorsPerCluster); } }
		public int BytesPerSector { get { this.RefreshSizeInformation(false); return this.sizeInformation.BytesPerSector; } }
		public override bool CanRead { get { return this._CanRead; } }
		public override bool CanSeek { get { return true; } }
		public override bool CanWrite { get { return this._CanWrite; } }

		public bool DeleteOnClose { get { UnsafeNativeMethods.FileStandardInformation info; UnsafeNativeMethods.GetStandardInfo(this.SafeFileHandle, true, out info); return info.DeletePending; } set { UnsafeNativeMethods.SetFileDispositionInfo(this.SafeFileHandle, value); } }
		public Win32DeviceType DeviceType { get { return UnsafeNativeMethods.GetDeviceInfo(this.SafeFileHandle).DeviceType; } }

		protected override void Dispose(bool disposing) { try { if (disposing) { if (this.CanWrite) { try { this.Flush(); } finally { this.SafeFileHandle.Close(); } } } } finally { this._SafeFileHandle = null; base.Dispose(disposing); } }

		public char DriveLetter
		{
			get
			{
				string drPath = this.TryGetDriveRelativePath();
				string ntName = this.FullObjectName;
				return UnsafeNativeMethods.GetWin32DriveLetter(ntName.Substring(0, ntName.Length - (drPath == null ? 0 : drPath.Length)));
			}
		}

		public string DriveRelativePath { get { return UnsafeNativeMethods.GetDriveRelativePath(this.SafeFileHandle, true); } }

		private int EndProcessing(IAsyncResult asyncResult, bool write)
		{
			var result = asyncResult as AsyncResult;
			if (result != null)
			{
				if (result.Write != write) { throw new ArgumentException("IAsyncResult object did not come from the corresponding async method on this type."); }
				result.SafeFileHandle.DangerousRelease();
				result.AsyncWaitHandle.SafeWaitHandle.DangerousRelease();
				try
				{
					//Overlapped overlapped;
					//unsafe { overlapped = Overlapped.Unpack(result.Overlapped); }
					int bytesTransferred;
					using (result.AsyncWaitHandle)
					{
						if (!asyncResult.CompletedSynchronously)
						{ unsafe { UnsafeNativeMethods.GetOverlappedResult(result.SafeFileHandle, result.Overlapped, out bytesTransferred, true); /*result.AsyncWaitHandle.WaitOne();*/ } }
					}
				}
				finally { unsafe { Overlapped.Free(result.Overlapped); result.Overlapped = null; } }
				if (result.ErrorCode != 0) { Marshal.ThrowExceptionForHR(result.ErrorCode | unchecked((int)0x80070000)); }
				if (result.HGlobalBufferAddress != IntPtr.Zero)
				{
					try { if (!write) { unsafe { Marshal.Copy((IntPtr)((byte*)result.HGlobalBufferAddress + result.OffsetInHGlobalBufferDueToAlignment), result.UserBuffer, result.UserBufferOffset, result.Length); } } }
					finally { Marshal.FreeHGlobal(result.HGlobalBufferAddress); }
				}
				return result.Length;
			}
			else { if (write) { base.EndWrite(asyncResult); return result.Length; } else { return base.EndRead(asyncResult); } }
		}

		public override int EndRead(IAsyncResult asyncResult) { return this.EndProcessing(asyncResult, false); }

		//public override void EndWrite(IAsyncResult asyncResult) { this.EndProcessing(asyncResult, true); }

		public long FileObjectPosition { get { return UnsafeNativeMethods.GetFilePosition(this.SafeFileHandle); } set { UnsafeNativeMethods.SetFilePointer(this.SafeFileHandle, value, SeekOrigin.Begin); } }
		public override void Flush() { UnsafeNativeMethods.TryFlushFileBuffers(this.SafeFileHandle); }
		public string FullObjectName { get { return UnsafeNativeMethods.GetObjectName(this.SafeFileHandle); } }
		public long IndexNumber { get { return UnsafeNativeMethods.GetIndexNumber(this.SafeFileHandle); } }
		public bool IsDirectory { get { UnsafeNativeMethods.FileStandardInformation info; UnsafeNativeMethods.GetStandardInfo(this.SafeFileHandle, true, out info); return info.Directory; } }
		public bool IsSynchronous { get { this.RefreshModeInformation(false); return this._IsSynchronous; } private set { this._IsSynchronous = value; } }
		public override long Length { get { UnsafeNativeMethods.FileStandardInformation info; if (!UnsafeNativeMethods.GetStandardInfo(this.SafeFileHandle, false, out info)) { return this.TotalClusters * this.BytesPerCluster; } else { return info.EndOfFile; } } }
		public int LinkCount { get { UnsafeNativeMethods.FileStandardInformation info; UnsafeNativeMethods.GetStandardInfo(this.SafeFileHandle, true, out info); return info.NumberOfLinks; } }
		public string Name { get { return Path.GetFileName(this.DriveRelativePath); } }
		public bool NoIntermediateBuffering { get { this.RefreshModeInformation(false); return this._NoIntermediateBuffering; } set { this._NoIntermediateBuffering = value; } }
		/// <summary>Use only ONCE per method, to ensure asynchronous calls can work!</summary>
		public override long Position { get { return /*!this.NoIntermediateBuffering && this.IsSynchronous ? UnsafeNativeMethods.GetFilePosition(this.SafeFileHandle) :*/ this._Position; } set { if (value < 0) { throw new ArgumentOutOfRangeException("value", value, "Value must be nonnegative."); } this.Seek(value, SeekOrigin.Begin); } }

		/// <summary>The position must be updated manually!</summary>
		private int Process(long position, byte[] buffer, int bufferOffset, int count, bool write)
		{
			int processed;
			if (count > 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."); }
			var bps = this.BytesPerSector;
			if ((this.IsSynchronous & !this.NoIntermediateBuffering) || (position % bps == 0 && count % bps == 0))
			{
				unsafe
				{
					fixed (byte* pData = &buffer[bufferOffset])
					{
						processed = write
							? UnsafeNativeMethods.WriteFile(this.SafeFileHandle, (IntPtr)pData, count, position)
							: UnsafeNativeMethods.ReadFile(this.SafeFileHandle, (IntPtr)pData, count, position);
					}
				}
			}
			else
			{
				long alignedPosition = position / bps * bps;

#if NEW_METHOD
				unsafe
				{
					byte* pSector = stackalloc byte[bps];

					//Copy bytes in first sector
					UnsafeNativeMethods.ReadFile(this.SafeFileHandle, (IntPtr)pSector, bps, alignedPosition);
					int toCopy = Math.Min(count, bps - (int)(position - alignedPosition));
					if (write)
					{
						Marshal.Copy(buffer, bufferOffset, (IntPtr)(pSector + (int)(position - alignedPosition)), toCopy);
						UnsafeNativeMethods.WriteFile(this.SafeFileHandle, (IntPtr)pSector, bps, alignedPosition);
					}
					else { Marshal.Copy((IntPtr)(pSector + (int)(position - alignedPosition)), buffer, bufferOffset, toCopy); }
					processed = toCopy;
					bufferOffset += toCopy;
					count -= toCopy;
					alignedPosition += bps;
					position = alignedPosition;

					//Copy middle sectors
					int alignedCount = count / bps * bps;
					if (alignedCount > 0)
					{
						fixed (byte* pData = &buffer[bufferOffset])
						{
							if (write) { UnsafeNativeMethods.WriteFile(this.SafeFileHandle, (IntPtr)pData, alignedCount, alignedPosition); }
							else { UnsafeNativeMethods.ReadFile(this.SafeFileHandle, (IntPtr)pData, alignedCount, alignedPosition); }
							processed += alignedCount;
							count -= alignedCount;
							bufferOffset += alignedCount;
							alignedPosition += alignedCount;
							position = alignedPosition;
						}
					}

					//Copy bytes in last sector
					if (count > 0)
					{
						UnsafeNativeMethods.ReadFile(this.SafeFileHandle, (IntPtr)pSector, bps, alignedPosition);
						if (write)
						{
							Marshal.Copy(buffer, bufferOffset, (IntPtr)(pSector), count);
							UnsafeNativeMethods.WriteFile(this.SafeFileHandle, (IntPtr)pSector, bps, alignedPosition);
						}
						else { Marshal.Copy((IntPtr)pSector, buffer, bufferOffset, count); }
						processed += count;
					}
				}
#else
				var alignedData = new byte[(position + count - alignedPosition + bps - 1) / bps * bps];
				unsafe
				{
					fixed (byte* pData = alignedData)
					{
						processed = UnsafeNativeMethods.ReadFile(this.SafeFileHandle, (IntPtr)pData, alignedData.Length, alignedPosition);
						if (write)
						{
							Buffer.BlockCopy(buffer, bufferOffset, alignedData, (int)(position - alignedPosition), count);
							processed = UnsafeNativeMethods.WriteFile(this.SafeFileHandle, (IntPtr)pData, alignedData.Length, alignedPosition);
						}
					}
				}
				if (!write) { checked { Buffer.BlockCopy(alignedData, (int)(position - alignedPosition), buffer, bufferOffset, count); } }
				processed = Math.Min(processed, count);
#endif
			}
			return processed;
		}

		public void RefreshModeInformation(bool force)
		{
			if (force || !this.modeInformationQueried)
			{
				this.IsSynchronous = (UnsafeNativeMethods.GetFileMode(this.SafeFileHandle) & 0x00000030) != 0;
				this.NoIntermediateBuffering = (UnsafeNativeMethods.GetFileMode(this.SafeFileHandle) & 0x00000008) != 0;
				this.sizeInformation = UnsafeNativeMethods.GetSizeInfo(this.SafeFileHandle);
				this.modeInformationQueried = true;
			}
		}

		public void RefreshSizeInformation(bool force)
		{
			if (force || !this.sizeInformationQueried)
			{
				this.sizeInformation = UnsafeNativeMethods.GetSizeInfo(this.SafeFileHandle);
				this.sizeInformationQueried = true;
			}
		}

		public override int Read(byte[] buffer, int offset, int count) { var pos = this.Position; int processed = this.Process(pos, buffer, offset, count, false); this.Position = pos + processed; return processed; }

		public override int ReadByte()
		{
			var bps = this.BytesPerSector;
			long position = this.Position;
			int b;
			if (this.IsSynchronous & !this.NoIntermediateBuffering) { unsafe { UnsafeNativeMethods.ReadFile(this.SafeFileHandle, (IntPtr)(&b), 1, position); } }
			else
			{
				long alignedPosition = position / bps * bps;
				int alignedDataLength = checked((int)((position + 1 - alignedPosition + bps - 1) / bps * bps));
				unsafe
				{
					byte* pAlignedData = stackalloc byte[alignedDataLength];
					int bytesRead = UnsafeNativeMethods.ReadFile(this.SafeFileHandle, (IntPtr)pAlignedData, alignedDataLength, alignedPosition);
					b = bytesRead > 0 ? pAlignedData[position - alignedPosition] : -1;
				}
			}
			this.Position = position + (b >= 0 ? 1 : 0);
			return b;
		}

		public SafeFileHandle SafeFileHandle { get { if (this._SafeFileHandle == null) { throw new InvalidOperationException("The handle is null."); } return this._SafeFileHandle; } private set { if (value == null) { throw new ArgumentNullException("value"); } this._SafeFileHandle = value; } }

		public int SectorsPerCluster { get { this.RefreshSizeInformation(false); return this.sizeInformation.SectorsPerAllocationUnit; } }

		public override long Seek(long offset, SeekOrigin origin)
		{
			//if (!this.CanSeek) { throw new InvalidOperationException(); }
			//if (!this.NoIntermediateBuffering && this.IsSynchronous) { this._Position = UnsafeNativeMethods.SetFilePointerEx(this.SafeFileHandle, offset, origin); }
			//else
			{
				switch (origin)
				{
					case SeekOrigin.Begin:
						this._Position = 0 + offset;
						break;
					case SeekOrigin.Current:
						this._Position = this._Position + offset;
						break;
					case SeekOrigin.End:
						this._Position = this.Length + offset;
						break;
					default:
						throw new ArgumentOutOfRangeException("origin", origin, "Invalid seek origin.");
				}
			}
			return this._Position;
		}

		public override void SetLength(long value) { UnsafeNativeMethods.SetFileEndOfFile(this.SafeFileHandle, value); }

		public override string ToString() { return this.Win32PathName; }

		public long TotalClusters { get { this.RefreshSizeInformation(false); return this.sizeInformation.TotalAllocationUnits; } }

		public char? TryGetDriveLetter()
		{
			string drPath = this.TryGetDriveRelativePath();
			string ntName = this.FullObjectName;
			char driveLetter;
			int result = UnsafeNativeMethods.GetWin32DriveLetter(ntName.Substring(0, ntName.Length - (drPath == null ? 0 : drPath.Length)), out driveLetter);
			if (result == 0) { return driveLetter; }
			return null;
		}

		public string TryGetDriveRelativePath() { return UnsafeNativeMethods.GetDriveRelativePath(this.SafeFileHandle, false); }

		public string TryGetWin32PathName()
		{
			string drPath = this.TryGetDriveRelativePath();
			string ntName = this.FullObjectName;
			char driveLetter;
			int result = UnsafeNativeMethods.GetWin32DriveLetter(ntName.Substring(0, ntName.Length - (drPath == null ? 0 : drPath.Length)), out driveLetter);
			if (result == 0) { return driveLetter + ":" + drPath; }
			return null;
		}

		public string Win32PathName
		{
			get
			{
				string drPath = this.TryGetDriveRelativePath();
				string ntName = this.FullObjectName;
				return UnsafeNativeMethods.GetWin32DriveLetter(ntName.Substring(0, ntName.Length - (drPath == null ? 0 : drPath.Length))) + ":" + drPath;
			}
		}

		public override void Write(byte[] buffer, int offset, int count) { var pos = this.Position; this.Position = pos + this.Process(pos, buffer, offset, count, true); }

		public override void WriteByte(byte value)
		{
			var bps = this.BytesPerSector;
			long position = this.Position;
			byte b;
			if (this.IsSynchronous & !this.NoIntermediateBuffering) { unsafe { UnsafeNativeMethods.WriteFile(this.SafeFileHandle, (IntPtr)(&b), 1, position); } }
			else
			{
				long alignedPosition = position / bps * bps;
				int alignedDataLength = checked((int)((position + 1 - alignedPosition + bps - 1) / bps * bps));
				unsafe
				{
					byte* pAlignedData = stackalloc byte[alignedDataLength];
					if (alignedPosition < this.Length)
					{ UnsafeNativeMethods.ReadFile(this.SafeFileHandle, (IntPtr)pAlignedData, alignedDataLength, alignedPosition); }
					pAlignedData[position - alignedPosition] = value;
					int bytesWritten = UnsafeNativeMethods.WriteFile(this.SafeFileHandle, (IntPtr)pAlignedData, alignedDataLength, alignedPosition);
					Debug.Assert(bytesWritten == alignedDataLength);
				}
			}
			this.Position = position + 1;
		}



		public static Win32FileStream OpenKernel(string kernelPath, FileAccess access, FileShare share, FileOptions options) { return OpenKernel(kernelPath, UnsafeNativeMethods.GetAccess(access), share, options); }

		public static Win32FileStream OpenKernel(string kernelPath, int access, FileShare share, FileOptions options) { var h = UnsafeNativeMethods.NtOpenFile(kernelPath, ref access, share, options); return new Win32FileStream(h, -1, false); }

		public static Win32FileStream OpenKernelById(SafeFileHandle volumeHandle, long fileID, FileAccess fileAccess, FileShare share, FileOptions options, bool throwOnError) { return OpenKernelById(volumeHandle, fileID, UnsafeNativeMethods.GetAccess(fileAccess), share, options, throwOnError); }

		public static Win32FileStream OpenKernelById(SafeFileHandle volumeHandle, long fileID, int fileAccess, FileShare share, FileOptions options, bool throwOnError) { var handle = UnsafeNativeMethods.OpenKernelById(volumeHandle, fileID, fileAccess, share, options, throwOnError); return handle != null ? new Win32FileStream(handle, -1, false) : null; }

		public static string ConvertDosToNtPathName(string path) { return UnsafeNativeMethods.RtlDosPathNameToNtPathName(path); }

		#region Native
		//[DebuggerStepThrough]
		[SuppressUnmanagedCodeSecurity]
		private static class UnsafeNativeMethods
		{
			[DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
			private static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, FileShare dwShareMode, IntPtr securityAttrs, FileMode dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);

			internal static SafeFileHandle CreateFile(string fileName, int access, FileShare shareMode, FileMode mode, FileOptions options, Win32FileOptions extraOptions)
			{
				//var noAccess = FileIOPermissionAccess.NoAccess;
				//if ((access & FileAccess.Read) != 0) { if (mode == FileMode.Append) { throw new ArgumentException("Append access can be requested only in write-only mode."); } noAccess |= FileIOPermissionAccess.Read; }
				//if ((access & FileAccess.Write) != 0) { if (mode == FileMode.Append) { noAccess |= FileIOPermissionAccess.Append; } else { noAccess |= FileIOPermissionAccess.Write; } }
				//var control = AccessControlActions.None;
				//new FileIOPermission(noAccess, control, new string[] { fileName }).Demand();
				int desiredAccess = access | ((options & FileOptions.Asynchronous) == 0 ? 0x00100000 : 0);
				var handle = CreateFile(fileName, desiredAccess, shareMode, IntPtr.Zero, mode, (int)options | (int)extraOptions, IntPtr.Zero);
				if (handle.IsInvalid) { Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); }
				return handle;
			}

			internal static SafeFileHandle NtOpenFile(string kernelFileName, ref int access, FileShare shareMode, FileOptions options)
			{
				access = access | 0x00100000 /*Synchronize*/ | ((options & FileOptions.Asynchronous) == 0 ? 0x00100000 : 0);

				int nativeOptions = 0;
				if ((options & FileOptions.Asynchronous) == 0)
				{ nativeOptions |= 0x00000020; }
				if ((options & FileOptions.WriteThrough) != 0)
				{ nativeOptions |= 0x00000002; }
				if ((options & FileOptions.RandomAccess) != 0)
				{ nativeOptions |= 0x00000800; }
				if ((options & FileOptions.SequentialScan) != 0)
				{ nativeOptions |= 0x00000004; }
				if ((options & FileOptions.DeleteOnClose) != 0)
				{ nativeOptions |= 0x00001000; }

				unsafe
				{
					fixed (char* pName = kernelFileName)
					{
						var name = new UnicodeString() { Buffer = pName, ByteLength = (short)(sizeof(char) * kernelFileName.Length), MaximumLength = (short)(sizeof(char) * (kernelFileName.Length + 1)) };
						var oa = new ObjectAttributes() { ObjectName = &name, Length = Marshal.SizeOf(typeof(ObjectAttributes)) };
						IntPtr* pIOSB = stackalloc IntPtr[2];
						SafeFileHandle handle;
						int status = NtOpenFile(out handle, access, ref oa, (IntPtr)pIOSB, shareMode, (int)options);
						CheckAndThrow(status);
						return handle;
					}
				}
			}

			//[DllImport("Kernel32.dll", SetLastError = true)]
			//[return: MarshalAs(UnmanagedType.Bool)]
			//private static extern unsafe bool ReadFileEx(SafeFileHandle handle, [In] IntPtr bytes, int numBytesToRead, [In] NativeOverlapped* overlapped, IOCompletionCallback callback);

			//[DllImport("Kernel32.dll", SetLastError = true)]
			//[return: MarshalAs(UnmanagedType.Bool)]
			//private static extern unsafe bool WriteFileEx(SafeFileHandle handle, [Out] IntPtr bytes, int numBytesToRead, [In] NativeOverlapped* overlapped, IOCompletionCallback callback);

			[DllImport("Kernel32.dll", SetLastError = true)]
			[return: MarshalAs(UnmanagedType.Bool)]
			private static extern bool FlushFileBuffers(SafeFileHandle handle);

			[DllImport("Kernel32.dll", SetLastError = true)]
			[return: MarshalAs(UnmanagedType.Bool)]
			private static extern bool SetFilePointerEx([In] SafeFileHandle fileHandle, [In] long liDistanceToMove, [Out, Optional] out long lpNewFilePointer, [In] SeekOrigin dwMoveMethod /*I hope the enumeration's values are correct...*/);

			[DllImport("Kernel32.dll", SetLastError = true)]
			[return: MarshalAs(UnmanagedType.Bool)]
			private static extern unsafe bool ReadFile(SafeFileHandle handle, [Out] IntPtr bytes, int numBytesToRead, int* numBytesRead, [In] NativeOverlapped* mustBeZero);

			[DllImport("Kernel32.dll", SetLastError = true)]
			[return: MarshalAs(UnmanagedType.Bool)]
			private static extern unsafe bool WriteFile(SafeFileHandle handle, [In] IntPtr bytes, int numBytesToRead, int* numBytesRead, [In] NativeOverlapped* mustBeZero);

			[DllImport("Kernel32.dll", SetLastError = true)]
			[return: MarshalAs(UnmanagedType.Bool)]
			public static extern unsafe bool GetOverlappedResult(SafeFileHandle fileHandle, NativeOverlapped* lpOverlapped, out int lpNumberOfBytesTransferred, [MarshalAs(UnmanagedType.Bool)] bool bWait);

			//[DllImport("Kernel32.dll", SetLastError = true)]
			//[return: MarshalAs(UnmanagedType.Bool)]
			//private static extern bool DuplicateHandle([In] IntPtr hSourceProcessHandle, [In] IntPtr hSourceHandle, [In] IntPtr hTargetProcessHandle, [Out] out IntPtr lpTargetHandle, [In] int dwDesiredAccess, [In, MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, [In] int dwOptions);

			[DllImport("Kernel32.dll", SetLastError = true)]
			[return: MarshalAs(UnmanagedType.Bool)]
			private static extern bool DuplicateHandle([In] IntPtr hSourceProcessHandle, [In] SafeFileHandle hSourceHandle, [In] IntPtr hTargetProcessHandle, [Out] out SafeFileHandle lpTargetHandle, [In] int dwDesiredAccess, [In, MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, [In] int dwOptions);

			[DllImport("NTDLL.dll", ThrowOnUnmappableChar = true, BestFitMapping = false, SetLastError = false)]
			private static extern int NtOpenFile([Out] out SafeFileHandle FileHandle, [In] int DesiredAccess, [In] ref ObjectAttributes ObjectAttributes, [Out] IntPtr IoStatusBlock, [In] FileShare ShareAccess, [In] int OpenOptions);

			[DllImport("NTDLL.dll", ThrowOnUnmappableChar = true, BestFitMapping = false, SetLastError = false)]
			private static extern int NtQueryInformationFile([In] SafeFileHandle FileHandle, [Out] out IOStatusBlock IoStatusBlock, [Out] IntPtr FileInformation, [In] int Length, [In] FileInformationClass FileInformationClass);

			[DllImport("NTDLL.dll", ThrowOnUnmappableChar = true, BestFitMapping = false, SetLastError = false)]
			private static extern int NtQueryVolumeInformationFile([In] SafeFileHandle FileHandle, [Out] out IOStatusBlock IoStatusBlock, [Out] out FileFsSizeInformation FsInformation, [In] int Length, [In] FSInformationClass FsInformationClass);

			[DllImport("NTDLL.dll", ThrowOnUnmappableChar = true, BestFitMapping = false, SetLastError = false)]
			private static extern int NtQueryVolumeInformationFile([In] SafeFileHandle FileHandle, [Out] out IOStatusBlock IoStatusBlock, [Out] out FileFsDeviceInformation FsInformation, [In] int Length, [In] FSInformationClass FsInformationClass);

			[DllImport("NTDLL.dll", ThrowOnUnmappableChar = true, BestFitMapping = false, SetLastError = false)]
			private static extern int NtSetInformationFile([In] SafeFileHandle FileHandle, [Out] out IOStatusBlock IoStatusBlock, [In] IntPtr FileInformation, [In] int Length, [In] FileInformationClass FileInformationClass);

			[DllImport("NTDLL.dll", ThrowOnUnmappableChar = true, BestFitMapping = false, SetLastError = false)]
			private static extern int RtlNtStatusToDosError([In] int NtStatus);

			[DllImport("NTDLL.dll", ThrowOnUnmappableChar = true, BestFitMapping = false, SetLastError = false)]
			private static extern int NtQueryObject([In] SafeHandle ObjectHandle, [In, Optional] ObjectInformationClass ObjectInformationClass, [Out] IntPtr ObjectInformation, [In] int Length, [Out] out int ResultLength);

			[DllImport("Kernel32.dll", ThrowOnUnmappableChar = true, BestFitMapping = false, SetLastError = true, CharSet = CharSet.Unicode)]
			private static extern unsafe int QueryDosDevice([In, MarshalAs(UnmanagedType.LPTStr)] string lpDeviceName, char* lpTargetPath, [In] int ucchMax);

			[DllImport("NTDLL.dll", ThrowOnUnmappableChar = true, BestFitMapping = false, SetLastError = false)]
			private static extern bool RtlDosPathNameToNtPathName_U([In, MarshalAs(UnmanagedType.LPWStr)] string DosPathName, [Out] out UnicodeString NtPathName, [Out] IntPtr NtFileNamePart, [Out] IntPtr DirectoryInfo);

			[DllImport("Kernel32.dll", ThrowOnUnmappableChar = true, BestFitMapping = false, SetLastError = true, CharSet = CharSet.Unicode)]
			private static extern unsafe int QueryDosDevice([In] char* lpDeviceName, char* lpTargetPath, [In] int ucchMax);


			private delegate int WaitOneNativeDelegate(SafeWaitHandle waitHandle, uint millisecondsTimeout, bool hasThreadAffinity, bool exitContext);
			[DebuggerBrowsable(DebuggerBrowsableState.Never)]
			private static WaitOneNativeDelegate __WaitOneNativeDelegate;
			[DebuggerBrowsable(DebuggerBrowsableState.Never)]
			private static WaitOneNativeDelegate WaitOneNative { get { if (__WaitOneNativeDelegate == null) { Interlocked.CompareExchange(ref __WaitOneNativeDelegate, (WaitOneNativeDelegate)Delegate.CreateDelegate(typeof(WaitOneNativeDelegate), typeof(WaitHandle).GetMethod("WaitOneNative", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic)), null); } return __WaitOneNativeDelegate; } }

			public static string RtlDosPathNameToNtPathName(string dosPath) { UnicodeString pathName; bool result = RtlDosPathNameToNtPathName_U(dosPath, out pathName, IntPtr.Zero, IntPtr.Zero); return result ? pathName.ToString() : null; }

			public static string QueryDosDevice(string deviceName)
			{
				unsafe
				{
					int capacity = 260;
					var pChars = stackalloc char[capacity + 1];
					const int ERROR_INSUFFICIENT_BUFFER = 122;
					int result;
					do
					{
						capacity <<= 1;
						unsafe { char* pChars2 = stackalloc char[capacity + 1]; pChars = pChars2; }
						result = QueryDosDevice(deviceName, pChars, capacity);
					} while (result == 0 && Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_BUFFER);
					if (result == 0) { Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); }
					return Marshal.PtrToStringAuto((IntPtr)pChars);
				}
			}

			public static char GetWin32DriveLetter(string deviceName) { char name; int result = GetWin32DriveLetter(deviceName, out name); if (result != 0) { Marshal.ThrowExceptionForHR(result | unchecked((int)0x80070000)); } return name; }

			public static int GetWin32DriveLetter(string deviceName, out char driveLetter)
			{
				unsafe
				{
					driveLetter = '\0';
					int capacity = 260;
					var pChars = stackalloc char[capacity + 1];
					char* pDriveLetter = stackalloc char[3]; // 'X' ':' '\0'
					pDriveLetter[1] = ':';
					pDriveLetter[2] = '\0'; //redundant, but just to make sure
					int result = 0;
					for (pDriveLetter[0] = 'A'; pDriveLetter[0] <= 'Z'; pDriveLetter[0]++)
					{
						const int ERROR_INSUFFICIENT_BUFFER = 122;
						bool firstTime = true;
						do
						{
							if (!firstTime)
							{
								capacity <<= 1;
								unsafe { char* pChars2 = stackalloc char[capacity + 1]; pChars = pChars2; }
							}
							result = QueryDosDevice(pDriveLetter, pChars, capacity);
							firstTime = false;
						} while (result == 0 && Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_BUFFER);

						int count = result;
						if (result == 0) { result = Marshal.GetLastWin32Error(); } else { result = 0; }

						if (result == 0)
						{
							bool equal = true;
							for (int i = 0; i < deviceName.Length; i++)
							{
								if (pChars[i] != deviceName[i])
								{
									equal = false;
									break;
								}
							}
							if (equal)
							{
								driveLetter = pDriveLetter[0];
								break;
							}
						}
						else { }
					}
					return result;
				}
			}

			public static SafeFileHandle DuplicateHandle(SafeFileHandle fileHandle, int newAccess, bool inherit)
			{
				SafeFileHandle duplicated;
				const int DUPLICATE_SAME_ACCESS = 0x00000002;
				if (!DuplicateHandle((IntPtr)(-1), fileHandle, (IntPtr)(-1), out duplicated, newAccess, inherit, newAccess == -1 ? DUPLICATE_SAME_ACCESS : 0))
				{
					GC.SuppressFinalize(duplicated);
					Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
				}
				return duplicated;
			}

			[StructLayout(LayoutKind.Sequential, Pack = 8)]
			public struct FileBasicInformation { public long CreationTime; public long LastAccessTime; public long LastWriteTime; public long ChangeTime; public Win32FileAttributes FileAttributes; }
			[StructLayout(LayoutKind.Sequential, Pack = 8)]
			public struct FileStandardInformation { public long AllocationSize; public long EndOfFile; public int NumberOfLinks; [MarshalAs(UnmanagedType.I1)] public bool DeletePending; [MarshalAs(UnmanagedType.I1)] public bool Directory; }
			[StructLayout(LayoutKind.Sequential, Pack = 8)]
			public struct FileFsSizeInformation { public long TotalAllocationUnits; public long AvailableAllocationUnits; public int SectorsPerAllocationUnit; public int BytesPerSector; }
			[StructLayout(LayoutKind.Sequential, Pack = 8)]
			public struct FileFsDeviceInformation { public Win32DeviceType DeviceType; public int Characteristics; }
			private enum ObjectInformationClass { ObjectBasicInformation = 0, ObjectNameInformation = 1, ObjectTypeInformation = 2, ObjectAllInformation = 3, ObjectDataInformation = 4 }
			private enum FSInformationClass { FileFsVolumeInformation = 1, FileFsSizeInformation = 3, FileFsDeviceInformation = 4, FileFsAttributeInformation = 5, FileFsControlInformation = 6, FileFsFullSizeInformation = 7 }
			private enum FileInformationClass { FileBasicInformation = 4, FileStandardInformation = 5, FileInternalInformation = 6, FileAccessInformation = 8, FileNameInformation = 9, FileDispositionInformation = 13, FilePositionInformation = 14, FileModeInformation = 16, FileAllocationInformation = 19, FileEndOfFileInformation = 20 }
			private struct UnicodeString { public short ByteLength; public short MaximumLength; public unsafe char* Buffer; public unsafe UnicodeString(short length, short maxLength, char* @string) { this.ByteLength = length; this.MaximumLength = maxLength; this.Buffer = @string; } public override string ToString() { unsafe { return this.Buffer != null ? Marshal.PtrToStringUni(new IntPtr(this.Buffer), this.ByteLength / 2) : null; } } }
			private struct ObjectAttributes { public unsafe ObjectAttributes(IntPtr rootDirectory, UnicodeString* objectName, int attributes) : this() { this.Length = sizeof(ObjectAttributes); this.RootDirectory = rootDirectory; this.ObjectName = objectName; this.Attributes = attributes; this.SecurityDescriptor = IntPtr.Zero; this.SecurityQualityOfService = IntPtr.Zero; } public int Length; public IntPtr RootDirectory; public unsafe UnicodeString* ObjectName; public int Attributes; public IntPtr SecurityDescriptor; public IntPtr SecurityQualityOfService; }
			private struct IOStatusBlock { public IntPtr Status; public IntPtr Information; }

			private static void CheckAndThrow(int ntstatus) { if (ntstatus < 0) { Marshal.ThrowExceptionForHR(RtlNtStatusToDosError(ntstatus) | unchecked((int)0x80070000)); } }

			public static SafeFileHandle OpenKernelById(SafeFileHandle hVolume, long fileID, int fileAccess, FileShare share, FileOptions options, bool throwOnError)
			{
				bool success = false;
				try
				{
					const int MAXIMUM_ALLOWED = 0x02000000, OPEN_BY_FILE_ID = 0x00002000;
					hVolume.DangerousAddRef(ref success);
					Debug.Assert(success);
					SafeFileHandle fileHandle;
					unsafe
					{
						var name = new UnicodeString(sizeof(long), sizeof(long), (char*)&fileID);
						var oa = new ObjectAttributes(hVolume.DangerousGetHandle(), &name, /*CaseInsensitive*/ 0x00000040);
						int openOptions = OPEN_BY_FILE_ID;
						if ((options & FileOptions.Asynchronous) == /*This is EQUAL, not NOT EQUAL!!*/ 0) { fileAccess |= 0x00100000 /*Synchronize*/; openOptions |= 0x00000020 /*SyncIONonAlert*/; }
						else { fileAccess &= ~0x00100000 /*No Synchronize*/; }
						if ((options & FileOptions.DeleteOnClose) != 0) { openOptions |= 0x00001000; }
						//if ((options & FileOptions.Encrypted) != 0) { openOptions |= ???; }
						if ((options & FileOptions.RandomAccess) != 0) { openOptions |= 0x00000800; }
						if ((options & FileOptions.SequentialScan) != 0) { openOptions |= 0x00000004; }
						if ((options & FileOptions.WriteThrough) != 0) { openOptions |= 0x00000002; }
						IOStatusBlock iosb;
						int ntStatus = UnsafeNativeMethods.NtOpenFile(out fileHandle, fileAccess, ref oa, (IntPtr)(&iosb), share, openOptions);
						if ((fileAccess & MAXIMUM_ALLOWED) != 0 && ntStatus < 0)
						{ ntStatus = UnsafeNativeMethods.NtOpenFile(out fileHandle, fileAccess & ~MAXIMUM_ALLOWED, ref oa, (IntPtr)(&iosb), share, openOptions); }
						if (ntStatus < 0)
						{
							if (throwOnError) { Marshal.ThrowExceptionForHR(unchecked((int)0x80070000) | UnsafeNativeMethods.RtlNtStatusToDosError(ntStatus)); }
							else { fileHandle = null; }
						}
						return fileHandle;
					}
				}
				finally { if (success) { hVolume.DangerousRelease(); } }

			}

			internal static FileFsDeviceInformation GetDeviceInfo(SafeFileHandle handle)
			{
				IOStatusBlock iosb;
				FileFsDeviceInformation fsInfo;
				int status = NtQueryVolumeInformationFile(handle, out iosb, out fsInfo, Marshal.SizeOf(typeof(FileFsDeviceInformation)), FSInformationClass.FileFsDeviceInformation);
				CheckAndThrow(status);
				return fsInfo;
			}

			public static string GetDriveRelativePath(SafeFileHandle handle, bool throwOnError)
			{
				IOStatusBlock iosb;
				unsafe
				{
					int nameInfoLength = 1 << (sizeof(short) * 8 - 1);
					int* pNameInfo = stackalloc int[nameInfoLength / sizeof(int)];
					int status = NtQueryInformationFile(handle, out iosb, (IntPtr)pNameInfo, nameInfoLength, FileInformationClass.FileNameInformation);
					if (status < 0 && !throwOnError) { return null; }
					CheckAndThrow(status);
					return Marshal.PtrToStringUni((IntPtr)(pNameInfo + 1), *pNameInfo / sizeof(char));
				}
			}

			public static string GetObjectName(SafeFileHandle handle)
			{
				unsafe
				{
					int nameInfoLength = 1 << (sizeof(short) * 8 - 1);
					short* pNameInfo = stackalloc short[nameInfoLength / sizeof(short)];
					int resultLength;
					int status = NtQueryObject(handle, ObjectInformationClass.ObjectNameInformation, (IntPtr)pNameInfo, nameInfoLength, out resultLength);
					CheckAndThrow(status);
					var pUnicodeString = (UnicodeString*)pNameInfo;
					return pUnicodeString->ToString();
				}
			}

			public static int GetAccessMask(SafeHandle handle) { unsafe { const int SIZE = 56; int* pData = stackalloc int[SIZE / sizeof(int)]; int resultLength; int status = NtQueryObject(handle, ObjectInformationClass.ObjectBasicInformation, (IntPtr)pData, SIZE, out resultLength); CheckAndThrow(status); return pData[1]; } }
			public static FileBasicInformation GetBasicInfo(SafeFileHandle handle) { IOStatusBlock iosb; FileBasicInformation info; unsafe { int status = NtQueryInformationFile(handle, out iosb, (IntPtr)(&info), sizeof(FileBasicInformation), FileInformationClass.FileBasicInformation); CheckAndThrow(status); return info; } }
			public static bool GetStandardInfo(SafeFileHandle handle, bool throwOnError, out FileStandardInformation info) { IOStatusBlock iosb; unsafe { fixed (FileStandardInformation* pInfo = &info) { unsafe { int status = NtQueryInformationFile(handle, out iosb, (IntPtr)pInfo, sizeof(FileStandardInformation), FileInformationClass.FileStandardInformation); if (throwOnError) { CheckAndThrow(status); } return status >= 0; } } } }
			public static FileFsSizeInformation GetSizeInfo(SafeFileHandle handle) { IOStatusBlock iosb; FileFsSizeInformation fsInfo; int status = NtQueryVolumeInformationFile(handle, out iosb, out fsInfo, Marshal.SizeOf(typeof(FileFsSizeInformation)), FSInformationClass.FileFsSizeInformation); CheckAndThrow(status); return fsInfo; }
			public static long GetIndexNumber(SafeFileHandle handle) { IOStatusBlock iosb; long indexNumber; unsafe { int status = NtQueryInformationFile(handle, out iosb, (IntPtr)(&indexNumber), sizeof(long), FileInformationClass.FileInternalInformation); CheckAndThrow(status); } return indexNumber; }
			public static int GetFileMode(SafeFileHandle handle) { IOStatusBlock iosb; int mode; unsafe { int status = NtQueryInformationFile(handle, out iosb, (IntPtr)(&mode), sizeof(int), FileInformationClass.FileModeInformation); CheckAndThrow(status); } return mode; }
			public static long GetFilePosition(SafeFileHandle handle) { IOStatusBlock iosb; long position; unsafe { int status = NtQueryInformationFile(handle, out iosb, (IntPtr)(&position), sizeof(long), FileInformationClass.FilePositionInformation); CheckAndThrow(status); } return position; }
			//public static void SetFilePosition(SafeFileHandle handle, long position) { IOStatusBlock iosb; unsafe { int status = NtSetInformationFile(handle, out iosb, (IntPtr)(&position), sizeof(long), FileInformationClass.FilePositionInformation); CheckAndThrow(status); } }
			//public static long GetFileEndOfFile(SafeFileHandle handle) { IOStatusBlock iosb; long eof; unsafe { int status = NtQueryInformationFile(handle, out iosb, (IntPtr)(&eof), sizeof(long), FileInformationClass.FileEndOfFileInformation); CheckAndThrow(status); } return eof; }
			public static void SetFileEndOfFile(SafeFileHandle handle, long endOfFile) { IOStatusBlock iosb; unsafe { int status = NtSetInformationFile(handle, out iosb, (IntPtr)(&endOfFile), sizeof(long), FileInformationClass.FileEndOfFileInformation); CheckAndThrow(status); } }
			public static void SetFileDispositionInfo(SafeFileHandle handle, bool deleteOnClose) { IOStatusBlock iosb; unsafe { int status = NtSetInformationFile(handle, out iosb, (IntPtr)(&deleteOnClose), sizeof(bool), FileInformationClass.FileDispositionInformation); CheckAndThrow(status); } }

			public static int ReadFile(SafeFileHandle handle, IntPtr pData, int bytesToRead, long? offset)
			{
				const int ERROR_INSUFFICIENT_RESOURCES = 1450, ERROR_IO_PENDING = 997, ERROR_HANDLE_EOF = 38;
				var overlapped = new NativeOverlapped() { OffsetLow = unchecked((int)offset.GetValueOrDefault()), OffsetHigh = unchecked((int)(offset.GetValueOrDefault() >> 32)) };
				unsafe
				{
					var pOverlapped = offset != null ? &overlapped : null;
					int bytesRead;
					bool success = ReadFile(handle, pData, bytesToRead, &bytesRead, pOverlapped);
					if (!success)
					{
						if (Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_RESOURCES && offset != null)
						{
							long offsetValue = offset.Value;
							int timesRead = 0;
							int chunkSize = bytesToRead;
							do
							{
								chunkSize >>= 1;
								success = ReadFile(handle, (IntPtr)((byte*)pData + timesRead * chunkSize), chunkSize, null, pOverlapped);
								if (success) { success = GetOverlappedResult(handle, pOverlapped, out bytesRead, true); }
							} while (!success && Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_RESOURCES);
							if (success & chunkSize > 0 & pOverlapped != null)
							{
								timesRead++;
								while (chunkSize * timesRead < bytesToRead && success)
								{
									int actualChunkSize = Math.Min(chunkSize, bytesToRead - chunkSize * timesRead);
									offsetValue += chunkSize;
									pOverlapped->OffsetLow = unchecked((int)offsetValue);
									pOverlapped->OffsetHigh = unchecked((int)(offsetValue >> 32));
									success = ReadFile(handle, (IntPtr)((byte*)pData + timesRead * chunkSize), actualChunkSize, null, pOverlapped);
									if (success) { success = GetOverlappedResult(handle, pOverlapped, out bytesRead, true); }
									else if (Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_RESOURCES) { }
									timesRead++;
								}
							}
						}
					}
					if (!success && Marshal.GetLastWin32Error() == ERROR_IO_PENDING)
					{
						success = GetOverlappedResult(handle, pOverlapped, out bytesRead, true);
					}
					if (!success)
					{
						int le = Marshal.GetLastWin32Error();
						if (le == ERROR_HANDLE_EOF) { bytesRead = 0; }
						else { Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); }
					}
					System.Diagnostics.Debug.Assert(bytesRead <= bytesToRead);
					return bytesRead;
				}
			}

			/// <summary>See the return values!</summary>
			/// <returns>Returns <c>true</c> if the file was read synchronously, or <c>false</c> if the read is pending.</returns>
			public static unsafe bool ReadFileAsync(SafeFileHandle handle, IntPtr pData, int bytesToRead, NativeOverlapped* pOverlapped, IOCompletionCallback completionCallback)
			{
				const int ERROR_IO_PENDING = 0x3E5;
				unsafe
				{
					int errorCode = 0;
					bool success;
#if FILE_EX
				success = ReadFileEx(handle, pData, bytesToRead, pOverlapped, completionCallback);
				int le = Marshal.GetLastWin32Error();
				// Does ReadFileEx() work as intended?
				if (!success || le != 0) { System.Diagnostics.Debugger.Break(); }
#else
					if (completionCallback != null) { throw new NotSupportedException("Completion callbacks are not supported."); }
					success = ReadFile(handle, pData, bytesToRead, &bytesToRead, pOverlapped);
#endif
					if (!success) { errorCode = Marshal.GetLastWin32Error(); }
					if (errorCode == 0x57 && bytesToRead == 0) { throw new ArgumentOutOfRangeException("bytesToRead", bytesToRead, "Length must be positive."); }
					switch (errorCode)
					{
						case 0:
							return true;
						case ERROR_IO_PENDING:
							return false;
						default:
							throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
					}
				}
			}

			public static int WriteFile(SafeFileHandle handle, IntPtr pData, int bytesToWrite, long? offset)
			{
				var overlapped = new NativeOverlapped() { OffsetLow = unchecked((int)offset.GetValueOrDefault()), OffsetHigh = unchecked((int)(offset.GetValueOrDefault() >> 32)) };
				unsafe
				{
					var pOverlapped = offset != null ? &overlapped : null;
					int bytesWritten;
					bool success = WriteFile(handle, pData, bytesToWrite, &bytesWritten, pOverlapped);
					if (!success)
					{
						const int ERROR_INSUFFICIENT_RESOURCES = 1450;
						if (Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_RESOURCES && offset != null)
						{
							long offsetValue = offset.Value;
							int timesWritten = 0;
							int chunkSize = bytesToWrite;
							do
							{
								chunkSize >>= 1;
								success = WriteFile(handle, (IntPtr)((byte*)pData + timesWritten * chunkSize), chunkSize, null, pOverlapped);
								if (success) { success = GetOverlappedResult(handle, pOverlapped, out bytesWritten, true); }
							} while (!success && Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_RESOURCES);
							if (success & chunkSize > 0 & pOverlapped != null)
							{
								timesWritten++;
								while (chunkSize * timesWritten < bytesToWrite && success)
								{
									int actualChunkSize = Math.Min(chunkSize, bytesToWrite - chunkSize * timesWritten);
									offsetValue += chunkSize;
									pOverlapped->OffsetLow = unchecked((int)offsetValue);
									pOverlapped->OffsetHigh = unchecked((int)(offsetValue >> 32));
									success = WriteFile(handle, (IntPtr)((byte*)pData + timesWritten * chunkSize), actualChunkSize, null, pOverlapped);
									if (success) { success = GetOverlappedResult(handle, pOverlapped, out bytesWritten, true); }
									else if (Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_RESOURCES) { }
									timesWritten++;
								}
							}
						}
					}
					if (!success && (Marshal.GetLastWin32Error() != 997 || !GetOverlappedResult(handle, pOverlapped, out bytesWritten, true)))
					{ Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); }
					System.Diagnostics.Debug.Assert(bytesWritten <= bytesToWrite);
					return bytesWritten;
				}
			}

			/// <summary>See the return values!</summary>
			/// <returns>Returns <c>true</c> if the file was Write synchronously, or <c>false</c> if the Write is pending.</returns>
			public static unsafe bool WriteFileAsync(SafeFileHandle handle, IntPtr pData, int bytesToWrite, NativeOverlapped* pOverlapped, IOCompletionCallback completionCallback)
			{
				if (completionCallback != null) { throw new NotSupportedException("Completion callbacks are not supported."); }
				const int ERROR_IO_PENDING = 0x3E5;
				unsafe
				{
					int errorCode = 0;
					bool success;
					//success = WriteFileEx(handle, pData, bytesToWrite, pOverlapped, completionCallback);
					success = WriteFile(handle, pData, bytesToWrite, &bytesToWrite, pOverlapped);
					if (!success) { errorCode = Marshal.GetLastWin32Error(); }
					switch (errorCode)
					{
						case 0:
							return true;
						case ERROR_IO_PENDING:
							return false;
						default:
							throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
					}
				}
			}

			public static bool TryFlushFileBuffers(SafeFileHandle handle) { return FlushFileBuffers(handle); }

			public static long SetFilePointer(SafeFileHandle handle, long position, SeekOrigin origin) { if (!SetFilePointerEx(handle, position, out position, origin)) { Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); } return position; }

			internal static int GetAccess(FileAccess access) { int result = 0; if ((access & FileAccess.Read) != 0) { result |= 1; } if ((access & FileAccess.Write) != 0) { result |= 2; } return result; }
		}
		#endregion
	}

	public enum Win32FileOptions : int { None = 0x00000000, NoIntermediateBuffering = 0x20000000 }

	[Flags]
	public enum Win32FileAttributes : int
	{
		None = 0x00000000,
		/// <summary>
		/// <para>A file or directory that is read-only.</para>
		/// <para>For a file, applications can read the file, but cannot write to it or delete it.</para>
		/// <para>For a directory, applications cannot delete it.</para>
		/// </summary>
		ReadOnly = 0x00000001,
		/// <summary>The file or directory is hidden. It is not included in an ordinary directory listing.</summary>
		Hidden = 0x00000002,
		/// <summary>A file or directory that the operating system uses a part of, or uses exclusively.</summary>
		System = 0x00000004,
		Unknown8 = 0x00000008,
		/// <summary>The handle that identifies a directory.</summary>
		Directory = 0x00000010,
		/// <summary>
		/// <para>A file or directory that is an archive file or directory.</para>
		/// <para>Applications use this attribute to mark files for backup or removal.</para>
		/// </summary>
		Archive = 0x00000020,
		/// <summary>Reserved; do not use.</summary>
		Device = 0x00000040,
		/// <summary>
		/// <para>A file or directory that does not have other attributes set. </para>
		/// <para>This attribute is valid only when used alone.</para>
		/// </summary>
		Normal = 0x00000080,
		/// <summary>
		/// <para>A file that is being used for temporary storage.</para>
		/// <para>File systems avoid writing data back to mass storage if sufficient cache memory is available, because typically, an application deletes a temporary file after the handle is closed. In that scenario, the system can entirely avoid writing the data. Otherwise, the data is written after the handle is closed.</para>
		/// </summary>
		Temporary = 0x00000100,
		/// <summary>A file that is a sparse file.</summary>
		SparseFile = 0x00000200,
		/// <summary>A file or directory that has an associated reparse point, or a file that is a symbolic link.</summary>
		ReparsePoint = 0x00000400,
		/// <summary>
		/// <para>A file or directory that is compressed.</para>
		/// <para>For a file, all of the data in the file is compressed.</para>
		/// <para>For a directory, compression is the default for newly created files and subdirectories.</para>
		/// </summary>
		Compressed = 0x00000800,
		/// <summary>
		/// <para>The data of a file is not available immediately.</para>
		/// <para>This attribute indicates that the file data is physically moved to offline storage. This attribute is used by Remote Storage, which is the hierarchical storage management software. Applications should not arbitrarily change this attribute.</para>
		/// </summary>
		Offline = 0x00001000,
		/// <summary>The file is not to be indexed by the content indexing service.</summary>
		NotContentIndexed = 0x00002000,
		/// <summary>
		/// <para>A file or directory that is encrypted.</para>
		/// <para>For a file, all data streams in the file are encrypted.</para>
		/// <para>For a directory, encryption is the default for newly created files and subdirectories.</para>
		/// </summary>
		Encrypted = 0x00004000,
		/// <summary>A file is a virtual file.</summary>
		Virtual = 0x00010000,
		I30IndexPresent = 0x10000000,
		ViewIndexPresent = 0x20000000,
	}

	public enum Win32DeviceType : int
	{
		ACPI = 0x00000032,
		BATTERY = 0x00000029,
		BEEP = 0x00000001,
		BUS_EXTENDER = 0x0000002a,
		CD_ROM = 0x00000002,
		CD_ROM_FILE_SYSTEM = 0x00000003,
		CHANGER = 0x00000030,
		CONTROLLER = 0x00000004,
		DATALINK = 0x00000005,
		DFS = 0x00000006,
		DFS_FILE_SYSTEM = 0x00000035,
		DFS_VOLUME = 0x00000036,
		DISK = 0x00000007,
		DISK_FILE_SYSTEM = 0x00000008,
		DVD = 0x00000033,
		FILE_SYSTEM = 0x00000009,
		FIPS = 0x0000003a,
		FULLSCREEN_VIDEO = 0x00000034,
		INPORT_PORT = 0x0000000a,
		KEYBOARD = 0x0000000b,
		KS = 0x0000002f,
		KSEC = 0x00000039,
		MAILSLOT = 0x0000000c,
		MASS_STORAGE = 0x0000002d,
		MIDI_IN = 0x0000000d,
		MIDI_OUT = 0x0000000e,
		MODEM = 0x0000002b,
		MOUSE = 0x0000000f,
		MULTI_UNC_PROVIDER = 0x00000010,
		NAMED_PIPE = 0x00000011,
		NETWORK = 0x00000012,
		NETWORK_BROWSER = 0x00000013,
		NETWORK_FILE_SYSTEM = 0x00000014,
		NETWORK_REDIRECTOR = 0x00000028,
		NULL = 0x00000015,
		PARALLEL_PORT = 0x00000016,
		PHYSICAL_NETCARD = 0x00000017,
		PORT_8042 = 0x00000027,
		PRINTER = 0x00000018,
		SCANNER = 0x00000019,
		SCREEN = 0x0000001c,
		SERENUM = 0x00000037,
		SERIAL_MOUSE_PORT = 0x0000001a,
		SERIAL_PORT = 0x0000001b,
		SMARTCARD = 0x00000031,
		SMB = 0x0000002e,
		SOUND = 0x0000001d,
		STREAMS = 0x0000001e,
		TAPE = 0x0000001f,
		TAPE_FILE_SYSTEM = 0x00000020,
		TERMSRV = 0x00000038,
		TRANSPORT = 0x00000021,
		UNKNOWN = 0x00000022,
		VDM = 0x0000002c,
		VIDEO = 0x00000023,
		VIRTUAL_DISK = 0x00000024,
		WAVE_IN = 0x00000025,
		WAVE_OUT = 0x00000026,
	}
}

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