/* * Process Hacker - * file handle * * Copyright (C) 2009 wj32 * * This file is part of Process Hacker. * * Process Hacker is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Process Hacker is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Process Hacker. If not, see . */ using System; using System.Collections.Generic; using System.Runtime.InteropServices; using ProcessHacker.Common; using ProcessHacker.Common.Objects; using ProcessHacker.Native.Api; using ProcessHacker.Native.Security; namespace ProcessHacker.Native.Objects { /// /// Represents a handle to a file. /// public class FileHandle : NativeHandle { public delegate bool EnumFilesDelegate(FileEntry file); public delegate bool EnumStreamsDelegate(FileStreamEntry stream); public static FileHandle FromFileStream(System.IO.FileStream fileStream) { return FromHandle(fileStream.SafeFileHandle.DangerousGetHandle()); } public static FileHandle FromHandle(IntPtr handle) { return new FileHandle(handle, false); } /// /// Creates or opens a file. /// /// The desired access to the file. /// /// An object name identifying the file to open. To use a DOS format /// file name, prepend "\??\" to the file name. /// /// Options to use when creating the file. public static FileHandle Create(FileAccess access, string fileName, FileCreateOptions createOptions) { return Create(access, fileName, FileShareMode.Exclusive, FileCreationDisposition.OpenIf, createOptions); } /// /// Creates or opens a file. /// /// The desired access to the file. /// /// An object name identifying the file to open. To use a DOS format /// file name, prepend "\??\" to the file name. /// /// The types of access to the file to grant to other threads. /// Options to use when creating the file. public static FileHandle Create(FileAccess access, string fileName, FileShareMode shareMode, FileCreateOptions createOptions) { return Create(access, fileName, shareMode, FileCreationDisposition.OpenIf, createOptions); } public static FileHandle Create( FileAccess access, string fileName, FileShareMode shareMode, FileCreationDisposition creationDisposition, FileCreateOptions createOptions ) { return Create(access, fileName, null, shareMode, creationDisposition, createOptions); } public static FileHandle Create( FileAccess access, string fileName, FileHandle rootDirectory, FileShareMode shareMode, FileCreationDisposition creationDisposition, FileCreateOptions createOptions ) { FileIoStatus status; return Create( access, fileName, ObjectFlags.CaseInsensitive, rootDirectory, shareMode, creationDisposition, 0, FileAttributes.Normal, createOptions, out status ); } public static FileHandle Create( FileAccess access, string fileName, ObjectFlags objectFlags, FileHandle rootDirectory, FileShareMode shareMode, FileCreationDisposition creationDisposition, long allocationSize, FileAttributes attributes, FileCreateOptions createOptions, out FileIoStatus ioStatus ) { NtStatus status; ObjectAttributes oa = new ObjectAttributes(fileName, objectFlags, rootDirectory); IoStatusBlock isb; IntPtr handle; try { if ((status = Win32.NtCreateFile( out handle, access, ref oa, out isb, ref allocationSize, attributes, shareMode, creationDisposition, createOptions, IntPtr.Zero, 0 )) >= NtStatus.Error) Win32.ThrowLastError(status); ioStatus = (FileIoStatus)isb.Information.ToInt32(); } finally { oa.Dispose(); } return new FileHandle(handle, true); } public static FileHandle CreateWin32(string fileName, FileAccess desiredAccess) { return CreateWin32(fileName, desiredAccess, FileShareMode.Exclusive); } public static FileHandle CreateWin32(string fileName, FileAccess desiredAccess, FileShareMode shareMode) { return CreateWin32(fileName, desiredAccess, shareMode, FileCreationDispositionWin32.OpenAlways); } public static FileHandle CreateWin32(string fileName, FileAccess desiredAccess, FileShareMode shareMode, FileCreationDispositionWin32 creationDisposition) { IntPtr handle; handle = Win32.CreateFile(fileName, desiredAccess, shareMode, 0, creationDisposition, 0, IntPtr.Zero); if (handle == NativeHandle.MinusOne) Win32.ThrowLastError(); return new FileHandle(handle, true); } public static void Delete(string fileName, ObjectFlags objectFlags) { NtStatus status; ObjectAttributes oa = new ObjectAttributes(fileName, objectFlags, null); try { if ((status = Win32.NtDeleteFile(ref oa)) >= NtStatus.Error) Win32.ThrowLastError(status); } finally { oa.Dispose(); } } protected FileHandle() { } protected FileHandle(IntPtr handle, bool owned) : base(handle, owned) { } /// /// Opens an existing file for synchronous access. /// /// /// An object name identifying the file to open. To use a DOS format /// file name, prepend "\??\" to the file name. /// /// The desired access to the file. public FileHandle(string fileName, FileAccess access) : this(fileName, FileShareMode.Exclusive, access) { } /// /// Opens an existing file for synchronous access. /// /// /// An object name identifying the file to open. To use a DOS format /// file name, prepend "\??\" to the file name. /// /// The share mode to use. /// The desired access to the file. public FileHandle(string fileName, FileShareMode shareMode, FileAccess access) : this(fileName, shareMode, FileCreateOptions.NonDirectoryFile | FileCreateOptions.SynchronousIoNonAlert, access | (FileAccess)StandardRights.Synchronize) { } /// /// Opens an existing file. /// /// /// An object name identifying the file to open. To use a DOS format /// file name, prepend "\??\" to the file name. /// /// The share mode to use. /// Open options to use. /// The desired access to the file. public FileHandle(string fileName, FileShareMode shareMode, FileCreateOptions openOptions, FileAccess access) : this(fileName, ObjectFlags.CaseInsensitive, null, shareMode, openOptions, access) { } /// /// Opens an existing file. /// /// /// An object name identifying the file to open. To use a DOS format /// file name, prepend "\??\" to the file name. /// /// Flags to use when opening the object. /// The directory to open the file relative to. /// The share mode to use. /// Open options to use. /// The desired access to the file. public FileHandle( string fileName, ObjectFlags objectFlags, FileHandle rootDirectory, FileShareMode shareMode, FileCreateOptions openOptions, FileAccess access ) { NtStatus status; ObjectAttributes oa = new ObjectAttributes(fileName, objectFlags, rootDirectory); IoStatusBlock isb; IntPtr handle; try { if ((status = Win32.NtOpenFile( out handle, access, ref oa, out isb, shareMode, openOptions )) >= NtStatus.Error) Win32.ThrowLastError(status); } finally { oa.Dispose(); } this.Handle = handle; } public AsyncIoContext BeginFsControl( int controlCode, byte[] inBuffer, int inBufferOffset, int inBufferLength, byte[] outBuffer, int outBufferOffset, int outBufferLength ) { AsyncIoContext asyncContext; PinnedObject pinnedInBuffer = null; PinnedObject pinnedOutBuffer = null; Utils.ValidateBuffer(inBuffer, inBufferOffset, inBufferLength, true); Utils.ValidateBuffer(outBuffer, outBufferOffset, outBufferLength, true); asyncContext = new AsyncIoContext(this); if (inBuffer != null) { pinnedInBuffer = new PinnedObject(inBuffer); asyncContext.KeepAlive(pinnedInBuffer); } if (outBuffer != null) { pinnedOutBuffer = new PinnedObject(outBuffer); asyncContext.KeepAlive(pinnedOutBuffer); } this.BeginFsControl( asyncContext, controlCode, pinnedInBuffer != null ? pinnedInBuffer.Address.Increment(inBufferOffset) : IntPtr.Zero, inBufferLength, pinnedOutBuffer != null ? pinnedOutBuffer.Address.Increment(outBufferOffset) : IntPtr.Zero, outBufferLength ); return asyncContext; } public AsyncIoContext BeginFsControl(int controlCode, MemoryRegion inBuffer, MemoryRegion outBuffer) { AsyncIoContext asyncContext = new AsyncIoContext(this); asyncContext.KeepAlive(inBuffer); asyncContext.KeepAlive(outBuffer); this.BeginFsControl( asyncContext, controlCode, inBuffer ?? IntPtr.Zero, inBuffer != null ? inBuffer.Size : 0, outBuffer ?? IntPtr.Zero, outBuffer != null ? outBuffer.Size : 0 ); return asyncContext; } protected void BeginFsControl( AsyncIoContext asyncContext, int controlCode, IntPtr inBuffer, int inBufferLength, IntPtr outBuffer, int outBufferLength ) { NtStatus status; status = Win32.NtFsControlFile( this, asyncContext.EventHandle, null, IntPtr.Zero, asyncContext.StatusMemory, controlCode, inBuffer, inBufferLength, outBuffer, outBufferLength ); asyncContext.NotifyBegin(); if (status != NtStatus.Pending) { // The operation finished synchronously. asyncContext.CompletedSynchronously = true; asyncContext.Status = status; } } public AsyncIoContext BeginIoControl( int controlCode, byte[] inBuffer, int inBufferOffset, int inBufferLength, byte[] outBuffer, int outBufferOffset, int outBufferLength ) { AsyncIoContext asyncContext; PinnedObject pinnedInBuffer = null; PinnedObject pinnedOutBuffer = null; Utils.ValidateBuffer(inBuffer, inBufferOffset, inBufferLength, true); Utils.ValidateBuffer(outBuffer, outBufferOffset, outBufferLength, true); asyncContext = new AsyncIoContext(this); if (inBuffer != null) { pinnedInBuffer = new PinnedObject(inBuffer); asyncContext.KeepAlive(pinnedInBuffer); } if (outBuffer != null) { pinnedOutBuffer = new PinnedObject(outBuffer); asyncContext.KeepAlive(pinnedOutBuffer); } this.BeginIoControl( asyncContext, controlCode, pinnedInBuffer != null ? pinnedInBuffer.Address.Increment(inBufferOffset) : IntPtr.Zero, inBufferLength, pinnedOutBuffer != null ? pinnedOutBuffer.Address.Increment(outBufferOffset) : IntPtr.Zero, outBufferLength ); return asyncContext; } public AsyncIoContext BeginIoControl(int controlCode, MemoryRegion inBuffer, MemoryRegion outBuffer) { AsyncIoContext asyncContext = new AsyncIoContext(this); asyncContext.KeepAlive(inBuffer); asyncContext.KeepAlive(outBuffer); this.BeginIoControl( asyncContext, controlCode, inBuffer ?? IntPtr.Zero, inBuffer != null ? inBuffer.Size : 0, outBuffer ?? IntPtr.Zero, outBuffer != null ? outBuffer.Size : 0 ); return asyncContext; } protected void BeginIoControl( AsyncIoContext asyncContext, int controlCode, IntPtr inBuffer, int inBufferLength, IntPtr outBuffer, int outBufferLength ) { NtStatus status; status = Win32.NtDeviceIoControlFile( this, asyncContext.EventHandle, null, IntPtr.Zero, asyncContext.StatusMemory, controlCode, inBuffer, inBufferLength, outBuffer, outBufferLength ); asyncContext.NotifyBegin(); if (status != NtStatus.Pending) { // The operation finished synchronously. asyncContext.CompletedSynchronously = true; asyncContext.Status = status; } } public AsyncIoContext BeginLock(long offset, long length) { return this.BeginLock(offset, length, false); } public AsyncIoContext BeginLock(long offset, long length, bool wait) { return this.BeginLock(offset, length, wait, true); } public AsyncIoContext BeginLock(long offset, long length, bool wait, bool exclusive) { NtStatus status; AsyncIoContext asyncContext = new AsyncIoContext(this); status = Win32.NtLockFile( this, asyncContext.EventHandle, null, IntPtr.Zero, asyncContext.StatusMemory, ref offset, ref length, 0, !wait, exclusive ); asyncContext.NotifyBegin(); if (status != NtStatus.Pending) { // The operation finished synchronously. asyncContext.CompletedSynchronously = true; asyncContext.Status = status; } return asyncContext; } public AsyncIoContext BeginRead(byte[] buffer) { return this.BeginRead(buffer, 0, buffer.Length); } public AsyncIoContext BeginRead(byte[] buffer, int offset, int length) { AsyncIoContext asyncContext; PinnedObject pinnedBuffer; Utils.ValidateBuffer(buffer, offset, length); // Pin the buffer because the I/O system may be writing to it after // this call returns. pinnedBuffer = new PinnedObject(buffer); asyncContext = new AsyncIoContext(this); asyncContext.KeepAlive(pinnedBuffer); this.BeginRead(asyncContext, pinnedBuffer.Address.Increment(offset), length); return asyncContext; } public AsyncIoContext BeginRead(MemoryRegion buffer) { AsyncIoContext asyncContext = new AsyncIoContext(this); asyncContext.KeepAlive(buffer); this.BeginRead(asyncContext, buffer, buffer.Size); return asyncContext; } protected void BeginRead(AsyncIoContext asyncContext, IntPtr buffer, int length) { NtStatus status; status = Win32.NtReadFile( this, asyncContext.EventHandle, null, IntPtr.Zero, asyncContext.StatusMemory, buffer, length, IntPtr.Zero, IntPtr.Zero ); asyncContext.NotifyBegin(); if (status != NtStatus.Pending) { // The operation finished synchronously. asyncContext.CompletedSynchronously = true; asyncContext.Status = status; } } public AsyncIoContext BeginWrite(byte[] buffer) { return this.BeginWrite(buffer, 0, buffer.Length); } public AsyncIoContext BeginWrite(byte[] buffer, int offset, int length) { AsyncIoContext asyncContext; PinnedObject pinnedBuffer; Utils.ValidateBuffer(buffer, offset, length); pinnedBuffer = new PinnedObject(buffer); asyncContext = new AsyncIoContext(this); asyncContext.KeepAlive(pinnedBuffer); this.BeginWrite(asyncContext, pinnedBuffer.Address.Increment(offset), length); return asyncContext; } public AsyncIoContext BeginWrite(MemoryRegion buffer) { AsyncIoContext asyncContext = new AsyncIoContext(this); asyncContext.KeepAlive(buffer); this.BeginWrite(asyncContext, buffer, buffer.Size); return asyncContext; } protected void BeginWrite(AsyncIoContext asyncContext, IntPtr buffer, int length) { NtStatus status; status = Win32.NtWriteFile( this, asyncContext.EventHandle, null, IntPtr.Zero, asyncContext.StatusMemory, buffer, length, IntPtr.Zero, IntPtr.Zero ); asyncContext.NotifyBegin(); if (status != NtStatus.Pending) { // The operation finished synchronously. asyncContext.CompletedSynchronously = true; asyncContext.Status = status; } } public IoStatusBlock CancelIo() { NtStatus status; IoStatusBlock isb; if ((status = Win32.NtCancelIoFile(this, out isb)) >= NtStatus.Error) Win32.ThrowLastError(status); return isb; } internal void CancelIo(IntPtr isb) { NtStatus status; if ((status = Win32.NtCancelIoFile(this, isb)) >= NtStatus.Error) Win32.ThrowLastError(status); } /// /// Deletes the file when the handle is closed. /// public void Delete() { this.SetStruct( FileInformationClass.FileDispositionInformation, new FileDispositionInformation() { DeleteFile = true } ); } protected int EndCommonIo(AsyncIoContext asyncContext) { asyncContext.Wait(); asyncContext.NotifyEnd(); if (asyncContext.Status >= NtStatus.Error) Win32.ThrowLastError(asyncContext.Status); return asyncContext.StatusBlock.Information.ToInt32(); } public int EndFsControl(AsyncIoContext asyncContext) { return this.EndCommonIo(asyncContext); } public int EndIoControl(AsyncIoContext asyncContext) { return this.EndCommonIo(asyncContext); } public bool EndLock(AsyncIoContext asyncContext) { asyncContext.Wait(); asyncContext.NotifyEnd(); if (asyncContext.Status == NtStatus.LockNotGranted) return false; if (asyncContext.Status >= NtStatus.Error) Win32.ThrowLastError(asyncContext.Status); return true; } public int EndRead(AsyncIoContext asyncContext) { return this.EndCommonIo(asyncContext); } public int EndWrite(AsyncIoContext asyncContext) { return this.EndCommonIo(asyncContext); } public void EnumFiles(EnumFilesDelegate callback) { NtStatus status; IoStatusBlock isb; bool firstTime = true; using (var data = new MemoryAlloc(0x400)) { while (true) { // Query the directory, doubling the buffer size each // time NtQueryDirectoryFile fails. We will also handle // any pending status. while (true) { status = Win32.NtQueryDirectoryFile( this, IntPtr.Zero, null, IntPtr.Zero, out isb, data, data.Size, FileInformationClass.FileDirectoryInformation, false, IntPtr.Zero, firstTime ); // Our ISB is on the stack, so we have to wait for the operation to complete // before continuing. if (status == NtStatus.Pending) { this.Wait(); status = isb.Status; } if (status == NtStatus.BufferOverflow || status == NtStatus.InfoLengthMismatch) data.Resize(data.Size * 2); else break; } // If we don't have any entries to read, exit. if (status == NtStatus.NoMoreFiles) break; // Handle any errors. if (status >= NtStatus.Error) Win32.ThrowLastError(status); // Read the list of files we got in this batch. int i = 0; while (true) { FileDirectoryInformation info = data.ReadStruct(i, 0); string name = data.ReadUnicodeString( i + FileDirectoryInformation.FileNameOffset, info.FileNameLength / 2 ); if (!callback(new FileEntry( name, info.FileIndex, DateTime.FromFileTime(info.CreationTime), DateTime.FromFileTime(info.LastAccessTime), DateTime.FromFileTime(info.LastWriteTime), DateTime.FromFileTime(info.ChangeTime), info.EndOfFile, info.AllocationSize, info.FileAttributes ))) return; if (info.NextEntryOffset == 0) break; else i += info.NextEntryOffset; } firstTime = false; // Go back and get another batch of file entries. } } } public void EnumStreams(EnumStreamsDelegate callback) { using (var data = this.QueryVariableSize(FileInformationClass.FileStreamInformation)) { int i = 0; while (true) { FileStreamInformation info = data.ReadStruct(i, 0); string name = data.ReadUnicodeString( i + FileStreamInformation.StreamNameOffset, info.StreamNameLength / 2 ); if (!callback(new FileStreamEntry(name, info.StreamSize, info.StreamAllocationSize))) return; if (info.NextEntryOffset == 0) break; else i += info.NextEntryOffset; } } } public void Flush() { NtStatus status; IoStatusBlock isb; status = Win32.NtFlushBuffersFile( this, out isb ); if (status == NtStatus.Pending) { this.Wait(); status = isb.Status; } if (status >= NtStatus.Error) Win32.ThrowLastError(status); } public int FsControl(int controlCode, byte[] inBuffer, byte[] outBuffer) { return this.FsControl( controlCode, inBuffer, 0, inBuffer != null ? inBuffer.Length : 0, outBuffer, 0, outBuffer != null ? outBuffer.Length : 0 ); } public int FsControl( int controlCode, byte[] inBuffer, int inBufferOffset, int inBufferLength, byte[] outBuffer, int outBufferOffset, int outBufferLength ) { Utils.ValidateBuffer(inBuffer, inBufferOffset, inBufferLength, true); Utils.ValidateBuffer(outBuffer, outBufferOffset, outBufferLength, true); unsafe { fixed (byte* inBufferPtr = inBuffer) { fixed (byte* outBufferPtr = outBuffer) { return this.FsControl( controlCode, &inBufferPtr[inBufferOffset], inBuffer != null ? inBuffer.Length : 0, &outBufferPtr[outBufferOffset], outBuffer != null ? outBuffer.Length : 0 ); } } } } public unsafe int FsControl( int controlCode, void* inBuffer, int inBufferLength, void* outBuffer, int outBufferLength ) { return this.FsControl(controlCode, new IntPtr(inBuffer), inBufferLength, new IntPtr(outBuffer), outBufferLength); } public int FsControl( int controlCode, IntPtr inBuffer, int inBufferLength, IntPtr outBuffer, int outBufferLength ) { NtStatus status; int returnLength; status = this.FsControl(controlCode, inBuffer, inBufferLength, outBuffer, outBufferLength, out returnLength); if (status >= NtStatus.Error) Win32.ThrowLastError(status); return returnLength; } public NtStatus FsControl( int controlCode, IntPtr inBuffer, int inBufferLength, IntPtr outBuffer, int outBufferLength, out int returnLength ) { NtStatus status; IoStatusBlock isb; status = Win32.NtFsControlFile( this, IntPtr.Zero, null, IntPtr.Zero, out isb, controlCode, inBuffer, inBufferLength, outBuffer, outBufferLength ); if (status == NtStatus.Pending) { this.Wait(); status = isb.Status; } // Information contains the return length. returnLength = isb.Information.ToInt32(); return status; } public FileAttributes GetAttributes() { return this.GetBasicInformation().FileAttributes; } public FileBasicInformation GetBasicInformation() { return this.QueryStruct(FileInformationClass.FileBasicInformation); } public string GetFileName() { using (var data = this.QueryVariableSize(FileInformationClass.FileNameInformation)) { FileNameInformation info = data.ReadStruct(); return data.ReadUnicodeString( FileNameInformation.FileNameOffset, info.FileNameLength / 2 ); } } public FileEntry[] GetFiles() { List files = new List(); this.EnumFiles((file) => { files.Add(file); return true; }); return files.ToArray(); } public long GetPosition() { return this.QueryStruct(FileInformationClass.FilePositionInformation).CurrentByteOffset; } public FileStreamEntry[] GetStreams() { List streams = new List(); this.EnumStreams((file) => { streams.Add(file); return true; }); return streams.ToArray(); } public long GetSize() { return this.GetStandardInformation().EndOfFile; } public FileStandardInformation GetStandardInformation() { return this.QueryStruct(FileInformationClass.FileStandardInformation); } public string GetVolumeFsName() { NtStatus status; IoStatusBlock isb; using (var data = new MemoryAlloc(0x200)) { if ((status = Win32.NtQueryVolumeInformationFile( this, out isb, data, data.Size, FsInformationClass.FileFsAttributeInformation )) >= NtStatus.Error) Win32.ThrowLastError(status); FileFsAttributeInformation info = data.ReadStruct(); return Marshal.PtrToStringUni( data.Memory.Increment(Marshal.OffsetOf(typeof(FileFsAttributeInformation), "FileSystemName")), info.FileSystemNameLength / 2 ); } } public string GetVolumeLabel() { NtStatus status; IoStatusBlock isb; using (var data = new MemoryAlloc(0x200)) { if ((status = Win32.NtQueryVolumeInformationFile( this, out isb, data, data.Size, FsInformationClass.FileFsVolumeInformation )) >= NtStatus.Error) Win32.ThrowLastError(status); FileFsVolumeInformation info = data.ReadStruct(); return Marshal.PtrToStringUni( data.Memory.Increment(Marshal.OffsetOf(typeof(FileFsVolumeInformation), "VolumeLabel")), info.VolumeLabelLength / 2 ); } } /// /// Sends an I/O control message to the device's associated driver. /// /// The device-specific control code. /// The input. /// The output buffer. /// The bytes returned in the output buffer. public int IoControl(int controlCode, byte[] inBuffer, byte[] outBuffer) { return this.IoControl( controlCode, inBuffer, 0, inBuffer != null ? inBuffer.Length : 0, outBuffer, 0, outBuffer != null ? outBuffer.Length : 0 ); } public int IoControl( int controlCode, byte[] inBuffer, int inBufferOffset, int inBufferLength, byte[] outBuffer, int outBufferOffset, int outBufferLength ) { Utils.ValidateBuffer(inBuffer, inBufferOffset, inBufferLength, true); Utils.ValidateBuffer(outBuffer, outBufferOffset, outBufferLength, true); unsafe { fixed (byte* inBufferPtr = inBuffer) { fixed (byte* outBufferPtr = outBuffer) { return this.IoControl( controlCode, &inBufferPtr[inBufferOffset], inBuffer != null ? inBuffer.Length : 0, &outBufferPtr[outBufferOffset], outBuffer != null ? outBuffer.Length : 0 ); } } } } public unsafe int IoControl( int controlCode, byte* inBuffer, int inBufferLength, byte[] outBuffer ) { fixed (byte* outBufferPtr = outBuffer) { return this.IoControl( controlCode, inBuffer, inBufferLength, outBufferPtr, outBuffer != null ? outBuffer.Length : 0 ); } } public unsafe int IoControl( int controlCode, void* inBuffer, int inBufferLength, void* outBuffer, int outBufferLength ) { return this.IoControl(controlCode, new IntPtr(inBuffer), inBufferLength, new IntPtr(outBuffer), outBufferLength); } public int IoControl( int controlCode, IntPtr inBuffer, int inBufferLength, IntPtr outBuffer, int outBufferLength ) { NtStatus status; int returnLength; status = this.IoControl(controlCode, inBuffer, inBufferLength, outBuffer, outBufferLength, out returnLength); if (status >= NtStatus.Error) Win32.ThrowLastError(status); return returnLength; } public NtStatus IoControl( int controlCode, IntPtr inBuffer, int inBufferLength, IntPtr outBuffer, int outBufferLength, out int returnLength ) { NtStatus status; IoStatusBlock isb; status = Win32.NtDeviceIoControlFile( this, IntPtr.Zero, null, IntPtr.Zero, out isb, controlCode, inBuffer, inBufferLength, outBuffer, outBufferLength ); if (status == NtStatus.Pending) { this.Wait(); status = isb.Status; } // Information contains the return length. returnLength = isb.Information.ToInt32(); return status; } public bool Lock(long offset, long length) { return this.Lock(offset, length, false); } public bool Lock(long offset, long length, bool wait) { return this.Lock(offset, length, wait, true); } public bool Lock(long offset, long length, bool wait, bool exclusive) { NtStatus status; IoStatusBlock isb; status = Win32.NtLockFile( this, IntPtr.Zero, null, IntPtr.Zero, out isb, ref offset, ref length, 0, !wait, exclusive ); if (status == NtStatus.Pending) { this.Wait(); status = isb.Status; } if (status == NtStatus.LockNotGranted) return false; if (status >= NtStatus.Error) Win32.ThrowLastError(status); return true; } protected T QueryStruct(FileInformationClass infoClass) where T : struct { NtStatus status; IoStatusBlock isb; using (var data = new MemoryAlloc(Marshal.SizeOf(typeof(T)))) { if ((status = Win32.NtQueryInformationFile( this, out isb, data, data.Size, infoClass )) >= NtStatus.Error) Win32.ThrowLastError(status); return data.ReadStruct(); } } protected MemoryAlloc QueryVariableSize(FileInformationClass infoClass) { NtStatus status; IoStatusBlock isb; var data = new MemoryAlloc(0x200); while (true) { status = Win32.NtQueryInformationFile( this, out isb, data, data.Size, infoClass ); if ( status == NtStatus.BufferOverflow || status == NtStatus.BufferTooSmall || status == NtStatus.InfoLengthMismatch ) data.Resize(data.Size * 2); else break; } if (status >= NtStatus.Error) { data.Dispose(); Win32.ThrowLastError(status); } return data; } /// /// Reads data from the file. /// /// The length to read. /// The read data. public byte[] Read(int length) { byte[] buffer = new byte[length]; this.Read(buffer); return buffer; } /// /// Reads data from the file. /// /// The buffer to store the data in. /// The number of bytes read from the file. public int Read(byte[] buffer) { return this.Read(buffer, 0, buffer.Length); } public int Read(byte[] buffer, int offset, int length) { Utils.ValidateBuffer(buffer, offset, length); unsafe { fixed (byte* bufferPtr = buffer) { return this.Read(&bufferPtr[offset], length); } } } public unsafe int Read(void* buffer, int length) { return this.Read(new IntPtr(buffer), length); } public int Read(IntPtr buffer, int length) { NtStatus status; IoStatusBlock isb; status = Win32.NtReadFile( this, IntPtr.Zero, null, IntPtr.Zero, out isb, buffer, length, IntPtr.Zero, IntPtr.Zero ); if (status == NtStatus.Pending) { this.Wait(); status = isb.Status; } if (status >= NtStatus.Error) Win32.ThrowLastError(status); return isb.Information.ToInt32(); } public void SetEnd(long offset) { this.SetStruct( FileInformationClass.FileEndOfFileInformation, new FileEndOfFileInformation() { EndOfFile = offset } ); } public void SetIoCompletion(IoCompletionHandle ioCompletionHandle, IntPtr keyContext) { FileCompletionInformation info = new FileCompletionInformation(); info.Port = ioCompletionHandle; info.Key = keyContext; this.SetStruct(FileInformationClass.FileCompletionInformation, info); } public void SetPosition(long offset) { this.SetStruct( FileInformationClass.FilePositionInformation, new FilePositionInformation() { CurrentByteOffset = offset } ); } public long SetPosition(long offset, PositionOrigin origin) { long currentPosition; currentPosition = this.GetPosition(); switch (origin) { case PositionOrigin.Current: currentPosition += offset; break; case PositionOrigin.Start: currentPosition = offset; break; case PositionOrigin.End: currentPosition = this.GetSize() + offset; break; } this.SetPosition(currentPosition); return currentPosition; } protected void SetStruct(FileInformationClass infoClass, T info) where T : struct { NtStatus status; IoStatusBlock isb; using (var data = new MemoryAlloc(Marshal.SizeOf(typeof(T)))) { data.WriteStruct(info); if ((status = Win32.NtSetInformationFile( this, out isb, data, data.Size, infoClass )) >= NtStatus.Error) Win32.ThrowLastError(status); } } public void Unlock(long offset, long length) { NtStatus status; IoStatusBlock isb; status = Win32.NtUnlockFile( this, out isb, ref offset, ref length, 0 ); if (status >= NtStatus.Error) Win32.ThrowLastError(status); } /// /// Writes data to the file. /// /// The data. /// The number of bytes written to the file. public int Write(byte[] buffer) { return this.Write(buffer, 0, buffer.Length); } public int Write(byte[] buffer, int offset, int length) { Utils.ValidateBuffer(buffer, offset, length); unsafe { fixed (byte* bufferPtr = buffer) { return this.Write(&bufferPtr[offset], length); } } } public unsafe int Write(void* buffer, int length) { return this.Write(new IntPtr(buffer), length); } /// /// Writes data to the file. /// /// The data. /// The number of bytes to write. /// The number of bytes written to the file. public int Write(IntPtr buffer, int length) { NtStatus status; IoStatusBlock isb; status = Win32.NtWriteFile( this, IntPtr.Zero, null, IntPtr.Zero, out isb, buffer, length, IntPtr.Zero, IntPtr.Zero ); if (status == NtStatus.Pending) { this.Wait(); status = isb.Status; } if (status >= NtStatus.Error) Win32.ThrowLastError(status); return isb.Information.ToInt32(); } } public enum PositionOrigin { Start, Current, End } public sealed class AsyncIoContext : BaseObject, ISynchronizable { private unsafe sealed class UnmanagedIsb : BaseObject { private static readonly int _isbSize = Marshal.SizeOf(typeof(IoStatusBlock)); public static implicit operator IntPtr(UnmanagedIsb isb) { return isb.Memory; } private IoStatusBlock* _ioStatusBlock; public UnmanagedIsb() { _ioStatusBlock = (IoStatusBlock*)MemoryAlloc.PrivateHeap.Allocate(0, _isbSize); } protected override void DisposeObject(bool disposing) { if (_ioStatusBlock != null) MemoryAlloc.PrivateHeap.Free(0, new IntPtr(_ioStatusBlock)); } public IntPtr Information { get { return _ioStatusBlock->Information; } set { _ioStatusBlock->Information = value; } } public IntPtr Memory { get { return new IntPtr(_ioStatusBlock); } } public IntPtr Pointer { get { return _ioStatusBlock->Pointer; } set { _ioStatusBlock->Pointer = value; } } public NtStatus Status { get { return _ioStatusBlock->Status; } set { _ioStatusBlock->Status = value; } } public IoStatusBlock Struct { get { return *_ioStatusBlock; } set { *_ioStatusBlock = value; } } } private EventHandle _eventHandle; private FileHandle _fileHandle; private UnmanagedIsb _isb; private bool _completedSynchronously = false; private bool _started = false; private List _keepAliveList = new List(); private object _tag; public AsyncIoContext(FileHandle fileHandle) { _eventHandle = EventHandle.Create(EventAccess.All, EventType.NotificationEvent, false); _fileHandle = fileHandle; _isb = new UnmanagedIsb(); _isb.Status = NtStatus.Pending; _fileHandle.Reference(); } protected override void DisposeObject(bool disposing) { if (_started && !this.Completed) { throw new InvalidOperationException( "An attempt was made to dispose an asynchronous I/O context object " + "before the I/O operation has finished." ); } this.ClearKeepAlive(); if (_eventHandle != null) _eventHandle.Dispose(); if (_fileHandle != null) _fileHandle.Dereference(); if (_isb != null) _isb.Dispose(); } public bool Cancelled { get { return this.Status == NtStatus.Cancelled; } } public bool Completed { get { return _isb.Status != NtStatus.Pending; } } public bool CompletedSynchronously { get { return _completedSynchronously; } internal set { _completedSynchronously = value; _eventHandle.Set(); } } internal EventHandle EventHandle { get { return _eventHandle; } } public FileHandle FileHandle { get { return _fileHandle; } } public int Information { get { return _isb.Information.ToInt32(); } } public bool Started { get { return _started; } } public NtStatus Status { get { return _isb.Status; } internal set { _isb.Status = value; } } public IoStatusBlock StatusBlock { get { return _isb.Struct; } internal set { _isb.Struct = value; } } internal IntPtr StatusMemory { get { return _isb; } } public object Tag { get { return _tag; } set { _tag = value; } } public void Cancel() { if (!_started) return; _fileHandle.CancelIo(); this.Wait(); this.NotifyEnd(); } private void ClearKeepAlive() { foreach (var obj in _keepAliveList) obj.Dereference(); _keepAliveList.Clear(); } internal void KeepAlive(BaseObject obj) { _keepAliveList.Add(obj); obj.Reference(); } internal void NotifyBegin() { _started = true; } internal void NotifyEnd() { this.ClearKeepAlive(); } #region ISynchronizable Members [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] public IntPtr Handle { get { return _eventHandle.Handle; } } public NtStatus Wait() { return _eventHandle.Wait(); } public NtStatus Wait(bool alertable) { return _eventHandle.Wait(alertable); } public NtStatus Wait(bool alertable, long timeout) { return _eventHandle.Wait(alertable, timeout); } #endregion } public class FileEntry { public FileEntry( string name, int index, DateTime creationTime, DateTime lastAccessTime, DateTime lastWriteTime, DateTime changeTime, long size, long allocationSize, FileAttributes attributes ) { this.Name = name; this.Index = index; this.CreationTime = creationTime; this.LastAccessTime = lastAccessTime; this.LastWriteTime = lastWriteTime; this.ChangeTime = changeTime; this.Size = size; this.AllocationSize = allocationSize; this.Attributes = attributes; } public string Name { get; private set; } public int Index { get; private set; } public DateTime CreationTime { get; private set; } public DateTime LastAccessTime { get; private set; } public DateTime LastWriteTime { get; private set; } public DateTime ChangeTime { get; private set; } public long Size { get; private set; } public long AllocationSize { get; private set; } public FileAttributes Attributes { get; private set; } } public class FileStreamEntry { public FileStreamEntry(string name, long size, long allocationSize) { this.Name = name; this.Size = size; this.AllocationSize = allocationSize; } public string Name { get; private set; } public long Size { get; private set; } public long AllocationSize { get; private set; } } }