/* * 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, NativeHandle 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, NativeHandle rootDirectory, FileShareMode shareMode, FileCreationDisposition creationDisposition, long allocationSize, FileAttributes attributes, FileCreateOptions createOptions, out FileIoStatus ioStatus ) { ObjectAttributes oa = new ObjectAttributes(fileName, objectFlags, rootDirectory); IoStatusBlock isb; IntPtr handle; try { Win32.NtCreateFile( out handle, access, ref oa, out isb, ref allocationSize, attributes, shareMode, creationDisposition, createOptions, IntPtr.Zero, 0 ).ThrowIf(); 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 = Win32.CreateFile(fileName, desiredAccess, shareMode, 0, creationDisposition, 0, IntPtr.Zero); if (handle != MinusOne) Win32.Throw(); return new FileHandle(handle, true); } public static void Delete(string fileName, ObjectFlags objectFlags) { ObjectAttributes oa = new ObjectAttributes(fileName, objectFlags, null); try { Win32.NtDeleteFile(ref oa).ThrowIf(); } 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, NativeHandle rootDirectory, FileShareMode shareMode, FileCreateOptions openOptions, FileAccess access ) { ObjectAttributes oa = new ObjectAttributes(fileName, objectFlags, rootDirectory); IoStatusBlock isb; IntPtr handle; try { Win32.NtOpenFile( out handle, access, ref oa, out isb, shareMode, openOptions ).ThrowIf(); } finally { oa.Dispose(); } this.Handle = handle; } public void BeginFsControl( AsyncIoContext asyncContext, int controlCode, byte[] inBuffer, int inBufferOffset, int inBufferLength, byte[] outBuffer, int outBufferOffset, int outBufferLength ) { PinnedObject pinnedInBuffer = null; PinnedObject pinnedOutBuffer = null; Utils.ValidateBuffer(inBuffer, inBufferOffset, inBufferLength, true); Utils.ValidateBuffer(outBuffer, outBufferOffset, outBufferLength, true); asyncContext.NotifyPreBegin(); 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 ); } public void BeginFsControl(AsyncIoContext asyncContext, int controlCode, MemoryRegion inBuffer, MemoryRegion outBuffer) { asyncContext.NotifyPreBegin(); 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 ); } protected void BeginFsControl( AsyncIoContext asyncContext, int controlCode, IntPtr inBuffer, int inBufferLength, IntPtr outBuffer, int outBufferLength ) { NtStatus status; status = Win32.NtFsControlFile( this, asyncContext.EventHandle ?? IntPtr.Zero, null, asyncContext.Context, asyncContext.StatusMemory, controlCode, inBuffer, inBufferLength, outBuffer, outBufferLength ); asyncContext.NotifyBegin(); if (status != NtStatus.Pending) { // The operation finished synchronously. asyncContext.CompletedSynchronously = true; asyncContext.Status = status; } } public void BeginIoControl( AsyncIoContext asyncContext, int controlCode, byte[] inBuffer, int inBufferOffset, int inBufferLength, byte[] outBuffer, int outBufferOffset, int outBufferLength ) { PinnedObject pinnedInBuffer = null; PinnedObject pinnedOutBuffer = null; Utils.ValidateBuffer(inBuffer, inBufferOffset, inBufferLength, true); Utils.ValidateBuffer(outBuffer, outBufferOffset, outBufferLength, true); asyncContext.NotifyPreBegin(); 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 ); } public void BeginIoControl(AsyncIoContext asyncContext, int controlCode, MemoryRegion inBuffer, MemoryRegion outBuffer) { asyncContext.NotifyPreBegin(); 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 ); } protected void BeginIoControl( AsyncIoContext asyncContext, int controlCode, IntPtr inBuffer, int inBufferLength, IntPtr outBuffer, int outBufferLength ) { NtStatus status; status = Win32.NtDeviceIoControlFile( this, asyncContext.EventHandle ?? IntPtr.Zero, null, asyncContext.Context, asyncContext.StatusMemory, controlCode, inBuffer, inBufferLength, outBuffer, outBufferLength ); asyncContext.NotifyBegin(); if (status != NtStatus.Pending) { // The operation finished synchronously. asyncContext.CompletedSynchronously = true; asyncContext.Status = status; } } public void BeginLock(AsyncIoContext asyncContext, long offset, long length) { this.BeginLock(asyncContext, offset, length, false); } public void BeginLock(AsyncIoContext asyncContext, long offset, long length, bool wait) { this.BeginLock(asyncContext, offset, length, wait, true); } public void BeginLock(AsyncIoContext asyncContext, long offset, long length, bool wait, bool exclusive) { asyncContext.NotifyPreBegin(); NtStatus status = Win32.NtLockFile( this, asyncContext.EventHandle ?? IntPtr.Zero, null, asyncContext.Context, 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; } } public void BeginRead(AsyncIoContext asyncContext, long fileOffset, byte[] buffer) { this.BeginRead(asyncContext, fileOffset, buffer, 0, buffer.Length); } public void BeginRead(AsyncIoContext asyncContext, long fileOffset, byte[] buffer, int offset, int length) { Utils.ValidateBuffer(buffer, offset, length); asyncContext.NotifyPreBegin(); // Pin the buffer because the I/O system may be writing to it after // this call returns. PinnedObject pinnedBuffer = new PinnedObject(buffer); asyncContext.KeepAlive(pinnedBuffer); this.BeginRead(asyncContext, fileOffset, pinnedBuffer.Address.Increment(offset), length); } public void BeginRead(AsyncIoContext asyncContext, long fileOffset, MemoryRegion buffer) { asyncContext.NotifyPreBegin(); asyncContext.KeepAlive(buffer); this.BeginRead(asyncContext, fileOffset, buffer, buffer.Size); } protected void BeginRead(AsyncIoContext asyncContext, long fileOffset, IntPtr buffer, int length) { NtStatus status; unsafe { status = Win32.NtReadFile( this, asyncContext.EventHandle ?? IntPtr.Zero, null, asyncContext.Context, asyncContext.StatusMemory, buffer, length, fileOffset != -1 ? new IntPtr(&fileOffset) : IntPtr.Zero, IntPtr.Zero ); } asyncContext.NotifyBegin(); if (status != NtStatus.Pending) { // The operation finished synchronously. asyncContext.CompletedSynchronously = true; asyncContext.Status = status; } } public void BeginWrite(AsyncIoContext asyncContext, long fileOffset, byte[] buffer) { this.BeginWrite(asyncContext, fileOffset, buffer, 0, buffer.Length); } public void BeginWrite(AsyncIoContext asyncContext, long fileOffset, byte[] buffer, int offset, int length) { Utils.ValidateBuffer(buffer, offset, length); asyncContext.NotifyPreBegin(); PinnedObject pinnedBuffer = new PinnedObject(buffer); asyncContext.KeepAlive(pinnedBuffer); this.BeginWrite(asyncContext, fileOffset, pinnedBuffer.Address.Increment(offset), length); } public void BeginWrite(AsyncIoContext asyncContext, long fileOffset, MemoryRegion buffer) { asyncContext.NotifyPreBegin(); asyncContext.KeepAlive(buffer); this.BeginWrite(asyncContext, fileOffset, buffer, buffer.Size); } protected void BeginWrite(AsyncIoContext asyncContext, long fileOffset, IntPtr buffer, int length) { NtStatus status; unsafe { status = Win32.NtWriteFile( this, asyncContext.EventHandle ?? IntPtr.Zero, null, asyncContext.Context, asyncContext.StatusMemory, buffer, length, fileOffset != -1 ? new IntPtr(&fileOffset) : IntPtr.Zero, IntPtr.Zero ); } asyncContext.NotifyBegin(); if (status != NtStatus.Pending) { // The operation finished synchronously. asyncContext.CompletedSynchronously = true; asyncContext.Status = status; } } /// /// Cancels all asynchronous file operations initiated on the file object by the current thread. /// /// An I/O status block. public IoStatusBlock CancelIo() { IoStatusBlock isb; Win32.NtCancelIoFile(this, out isb).ThrowIf(); return isb; } /// /// Deletes the file when the handle is closed. /// public void Delete() { this.SetStruct(FileInformationClass.FileDispositionInformation, FileDispositionInformation.SizeOf, new FileDispositionInformation { DeleteFile = true }); } /// /// Waits for an asynchronous file operation to complete. /// /// An asynchronous I/O context object representing the operation. /// The operation-specific result. protected int EndCommonIo(AsyncIoContext asyncContext) { asyncContext.Wait(); asyncContext.NotifyEnd(); asyncContext.Status.ThrowIf(); return asyncContext.StatusBlock.Information.ToInt32(); } /// /// Waits for an asynchronous file system control operation to complete. /// /// An asynchronous I/O context object representing the operation. /// The bytes returned in the output buffer. public int EndFsControl(AsyncIoContext asyncContext) { return this.EndCommonIo(asyncContext); } /// /// Waits for an asynchronous I/O control operation to complete. /// /// An asynchronous I/O context object representing the operation. /// The bytes returned in the output buffer. public int EndIoControl(AsyncIoContext asyncContext) { return this.EndCommonIo(asyncContext); } /// /// Waits for an asynchronous lock operation to complete. /// /// An asynchronous I/O context object representing the operation. /// True if the lock was acquired, otherwise false. public bool EndLock(AsyncIoContext asyncContext) { asyncContext.Wait(); asyncContext.NotifyEnd(); if (asyncContext.Status == NtStatus.LockNotGranted) return false; asyncContext.Status.ThrowIf(); return true; } /// /// Waits for an asynchronous read operation to complete. /// /// An asynchronous I/O context object representing the operation. /// The number of bytes read as a result of the operation. public int EndRead(AsyncIoContext asyncContext) { return this.EndCommonIo(asyncContext); } /// /// Waits for an asynchronous write operation to complete. /// /// An asynchronous I/O context object representing the operation. /// The number of bytes written as a result of the operation. public int EndWrite(AsyncIoContext asyncContext) { return this.EndCommonIo(asyncContext); } /// /// Enumerates the files contained in the directory. /// /// The callback function to use. public void EnumFiles(EnumFilesDelegate callback) { this.EnumFiles(callback, null); } /// /// Enumerates the files contained in the directory. /// /// The callback function to use. /// A search pattern to use. For example, "*.txt". /// /// If a search pattern is specified, it will be used for all future /// enumerations performed on this file handle. Any search patterns /// specified in future enumerations will be ignored. /// public void EnumFiles(EnumFilesDelegate callback, string searchPattern) { NtStatus status; IoStatusBlock isb; UnicodeString searchPatternStr = new UnicodeString(); bool firstTime = true; if (searchPattern != null) searchPatternStr = new UnicodeString(searchPattern); try { 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) { unsafe { status = Win32.NtQueryDirectoryFile( this, IntPtr.Zero, null, IntPtr.Zero, out isb, data, data.Size, FileInformationClass.FileDirectoryInformation, false, searchPattern == null ? IntPtr.Zero : new IntPtr(&searchPatternStr), 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.ResizeNew(data.Size * 2); else break; } // If we don't have any entries to read, exit. if (status == NtStatus.NoMoreFiles) break; // Handle any errors. status.ThrowIf(); // Read the list of files we got in this batch. int i = 0; while (true) { FileDirectoryInformation info = data.ReadStruct(i, FileDirectoryInformation.SizeOf, 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; i += info.NextEntryOffset; } firstTime = false; // Go back and get another batch of file entries. } } } finally { if (searchPattern != null) searchPatternStr.Dispose(); } } /// /// Enumerates the streams contained in the file. /// /// The callback function to use. public void EnumStreams(EnumStreamsDelegate callback) { using (MemoryAlloc data = this.QueryVariableSize(FileInformationClass.FileStreamInformation)) { int i = 0; while (true) { FileStreamInformation info = data.ReadStruct(i, FileStreamInformation.SizeOf, 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; i += info.NextEntryOffset; } } } /// /// Flushes the buffers for the file. /// public void Flush() { IoStatusBlock isb; NtStatus status = Win32.NtFlushBuffersFile( this, out isb ); if (status == NtStatus.Pending) { this.Wait(); status = isb.Status; } status.ThrowIf(); } /// /// Sends a file system control message to the device's associated driver. /// /// The device-specific control code. /// The input buffer. /// The output buffer. /// The bytes returned in the output buffer. 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 ) { int returnLength; this.FsControl(controlCode, inBuffer, inBufferLength, outBuffer, outBufferLength, out returnLength).ThrowIf(); return returnLength; } public NtStatus FsControl( int controlCode, IntPtr inBuffer, int inBufferLength, IntPtr outBuffer, int outBufferLength, out int returnLength ) { IoStatusBlock isb; NtStatus 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; } /// /// Gets the attributes of the file. /// /// The attributes of the file. public FileAttributes Attributes { get { return this.BasicInformation.FileAttributes; } } /// /// Gets basic information about the file. /// /// Basic information about the file. public FileBasicInformation BasicInformation { get { return this.QueryStruct(FileInformationClass.FileBasicInformation, FileBasicInformation.SizeOf); } } /// /// Gets the partial name of the file. /// /// The name of the file, relative to its volume. public string FileName { get { using (MemoryAlloc data = this.QueryVariableSize(FileInformationClass.FileNameInformation)) { FileNameInformation info = data.ReadStruct(0, FileNameInformation.SizeOf, 0); return data.ReadUnicodeString( FileNameInformation.FileNameOffset, info.FileNameLength/2 ); } } } /// /// Gets a list of the files contained in the directory. /// /// An array of file information structures. public FileEntry[] Files { get { return this.GetFiles(null); } } /// /// Gets a list of the files contained in the directory. /// /// A search pattern to use. For example, "*.txt". /// An array of file information structures. /// /// If a search pattern is specified, it will be used for all future /// enumerations performed on this file handle. Any search patterns /// specified in future enumerations will be ignored. /// public FileEntry[] GetFiles(string searchPattern) { List files = new List(); this.EnumFiles(file => { files.Add(file); return true; }, searchPattern); return files.ToArray(); } /// /// Gets the current position. /// /// A byte offset from the beginning of the file. public long Position { get { return this.QueryStruct( FileInformationClass.FilePositionInformation, FilePositionInformation.SizeOf ).CurrentByteOffset; } } /// /// Gets a list of the streams contained in the file. /// /// An array of stream information structures. public FileStreamEntry[] GetStreams() { List streams = new List(); this.EnumStreams(file => { streams.Add(file); return true; }); return streams.ToArray(); } /// /// Gets the size of the file. /// /// The size of the file. public long FileSize { get { return this.StandardInformation.EndOfFile; } } /// /// Gets standard information about the file. /// /// Standard information about the file. public FileStandardInformation StandardInformation { get { return this.QueryStruct( FileInformationClass.FileStandardInformation, FileStandardInformation.SizeOf ); } } /// /// Gets the file system name of the file's associated volume. /// /// The volume's file system name. public string VolumeFsName { get { IoStatusBlock isb; using (MemoryAlloc data = new MemoryAlloc(0x200)) { Win32.NtQueryVolumeInformationFile( this, out isb, data, data.Size, FsInformationClass.FileFsAttributeInformation ).ThrowIf(); FileFsAttributeInformation info = data.ReadStruct(0, FileFsAttributeInformation.SizeOf, 0); return Marshal.PtrToStringUni( data.Memory.Increment(FileFsAttributeInformation.FileSystemNameOffset), info.FileSystemNameLength/2 ); } } } /// /// Gets the label of the file's associated volume. /// /// The volume label. public string VolumeLabel { get { using (MemoryAlloc data = new MemoryAlloc(0x200)) { IoStatusBlock isb; Win32.NtQueryVolumeInformationFile( this, out isb, data, data.Size, FsInformationClass.FileFsVolumeInformation ).ThrowIf(); FileFsVolumeInformation info = data.ReadStruct(); return Marshal.PtrToStringUni( data.Memory.Increment(FileFsVolumeInformation.VolumeLabelOffset), info.VolumeLabelLength/2 ); } } } /// /// Sends an I/O control message to the device's associated driver. /// /// The device-specific control code. /// The input buffer. /// 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 ) { int returnLength; this.IoControl(controlCode, inBuffer, inBufferLength, outBuffer, outBufferLength, out returnLength).ThrowIf(); return returnLength; } public NtStatus IoControl( int controlCode, IntPtr inBuffer, int inBufferLength, IntPtr outBuffer, int outBufferLength, out int returnLength ) { IoStatusBlock isb; NtStatus 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; } /// /// Locks a byte range in the file. /// /// The starting offset of the byte range. /// The length of the byte range. /// True if the lock was acquired, otherwise false. public bool Lock(long offset, long length) { return this.Lock(offset, length, false); } /// /// Locks a byte range in the file. /// /// The starting offset of the byte range. /// The length of the byte range. /// /// True to wait for the lock to be acquired, false to return if the /// lock cannot be acquired immediately. /// /// True if the lock was acquired, otherwise false. public bool Lock(long offset, long length, bool wait) { return this.Lock(offset, length, wait, true); } /// /// Locks a byte range in the file. /// /// The starting offset of the byte range. /// The length of the byte range. /// /// True to wait for the lock to be acquired, false to return if the /// lock cannot be acquired immediately. /// /// /// True to acquire an exclusive lock, false to acquire a shared lock. /// /// True if the lock was acquired, otherwise false. public bool Lock(long offset, long length, bool wait, bool exclusive) { IoStatusBlock isb; NtStatus 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; status.ThrowIf(); return true; } protected T QueryStruct(FileInformationClass infoClass, int size) where T : struct { using (MemoryAlloc data = new MemoryAlloc(size)) { IoStatusBlock isb; Win32.NtQueryInformationFile( this, out isb, data, data.Size, infoClass ).ThrowIf(); return data.ReadStruct(0, size, 0); } } protected MemoryAlloc QueryVariableSize(FileInformationClass infoClass) { NtStatus status; IoStatusBlock isb; MemoryAlloc 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.ResizeNew(data.Size * 2); else break; } if (status.IsError()) { data.Dispose(); Win32.Throw(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); } /// /// Reads data from the file. /// /// The data. /// The offset into the buffer to use. /// The number of bytes to read. /// The number of bytes read from the file. public int Read(byte[] buffer, int offset, int length) { return this.Read(-1, buffer, offset, length); } /// /// Reads data from the file. /// /// /// The offset into the file to start reading from. Specify -1 to /// use the file object's current position. /// /// The data. /// The offset into the buffer to use. /// The number of bytes to read. /// The number of bytes read from the file. public int Read(long fileOffset, byte[] buffer, int offset, int length) { Utils.ValidateBuffer(buffer, offset, length); unsafe { fixed (byte* bufferPtr = buffer) { return this.Read(fileOffset, &bufferPtr[offset], length); } } } /// /// Reads data from the file. /// /// The data. /// The number of bytes to read. /// The number of bytes read from the file. public unsafe int Read(void* buffer, int length) { return this.Read(-1, buffer, length); } /// /// Reads data from the file. /// /// /// The offset into the file to start reading from. Specify -1 to /// use the file object's current position. /// /// The data. /// The number of bytes to read. /// The number of bytes read from the file. public unsafe int Read(long fileOffset, void* buffer, int length) { return this.Read(fileOffset, new IntPtr(buffer), length); } /// /// Reads data from the file. /// /// The data. /// The number of bytes to read. /// The number of bytes read from the file. public int Read(IntPtr buffer, int length) { return this.Read(-1, buffer, length); } /// /// Reads data from the file. /// /// /// The offset into the file to start reading from. Specify -1 to /// use the file object's current position. /// /// The data. /// The number of bytes to read. /// The number of bytes read from the file. public int Read(long fileOffset, IntPtr buffer, int length) { NtStatus status; IoStatusBlock isb; unsafe { status = Win32.NtReadFile( this, IntPtr.Zero, null, IntPtr.Zero, out isb, buffer, length, fileOffset != -1 ? new IntPtr(&fileOffset) : IntPtr.Zero, IntPtr.Zero ); } if (status == NtStatus.Pending) { this.Wait(); status = isb.Status; } status.ThrowIf(); return isb.Information.ToInt32(); } /// /// Truncates or extends the file. /// /// A byte offset from the beginning of the file. public void SetEnd(long offset) { this.SetStruct(FileInformationClass.FileEndOfFileInformation, FileEndOfFileInformation.SizeOf, new FileEndOfFileInformation { EndOfFile = offset }); } /// /// Associates an I/O completion port with the file object. /// /// An asynchronous I/O completion port. public void SetIoCompletion(AsyncIoCompletionPort asyncCompletionPort) { this.SetIoCompletion(asyncCompletionPort.Handle); } /// /// Associates an I/O completion port with the file object. /// /// A handle to an I/O completion port. public void SetIoCompletion(IoCompletionHandle ioCompletionHandle) { this.SetIoCompletion(ioCompletionHandle, IntPtr.Zero); } /// /// Associates an I/O completion port with the file object. /// /// A handle to an I/O completion port. /// A key to associate with the file object. public void SetIoCompletion(IoCompletionHandle ioCompletionHandle, IntPtr keyContext) { FileCompletionInformation info = new FileCompletionInformation { Port = ioCompletionHandle, Key = keyContext }; this.SetStruct(FileInformationClass.FileCompletionInformation, FileCompletionInformation.SizeOf, info); } /// /// Sets the current file position. /// /// A byte offset from the beginning of the file. public void SetPosition(long offset) { this.SetStruct(FileInformationClass.FilePositionInformation, FilePositionInformation.SizeOf, new FilePositionInformation { CurrentByteOffset = offset }); } /// /// Sets the current file position. /// /// A byte offset. /// The origin from which the offset is calculated. public long SetPosition(long offset, PositionOrigin origin) { long currentPosition; currentPosition = this.Position; switch (origin) { case PositionOrigin.Current: currentPosition += offset; break; case PositionOrigin.Start: currentPosition = offset; break; case PositionOrigin.End: currentPosition = this.FileSize + offset; break; } this.SetPosition(currentPosition); return currentPosition; } protected void SetStruct(FileInformationClass infoClass, int size, T info) where T : struct { using (MemoryAlloc data = new MemoryAlloc(size)) { data.WriteStruct(info); IoStatusBlock isb; Win32.NtSetInformationFile( this, out isb, data, data.Size, infoClass ).ThrowIf(); } } /// /// Unlocks a byte range in the file. /// /// The starting offset of the byte range. /// The length of the byte range. public void Unlock(long offset, long length) { IoStatusBlock isb; Win32.NtUnlockFile( this, out isb, ref offset, ref length, 0 ).ThrowIf(); } /// /// 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); } /// /// Writes data to the file. /// /// The data. /// The offset into the buffer to use. /// The number of bytes to write. /// The number of bytes written to the file. public int Write(byte[] buffer, int offset, int length) { return this.Write(-1, buffer, offset, length); } /// /// Writes data to the file. /// /// /// The offset into the file to start writing at. Specify -1 to /// use the file object's current position. /// /// The data. /// The offset into the buffer to use. /// The number of bytes to write. /// The number of bytes written to the file. public int Write(long fileOffset, byte[] buffer, int offset, int length) { Utils.ValidateBuffer(buffer, offset, length); unsafe { fixed (byte* bufferPtr = buffer) { return this.Write(fileOffset, &bufferPtr[offset], length); } } } /// /// Writes data to the file. /// /// The data. /// The number of bytes to write. /// The number of bytes written to the file. public unsafe int Write(void* buffer, int length) { return this.Write(-1, buffer, length); } /// /// Writes data to the file. /// /// /// The offset into the file to start writing at. Specify -1 to /// use the file object's current position. /// /// The data. /// The number of bytes to write. /// The number of bytes written to the file. public unsafe int Write(long fileOffset, void* buffer, int length) { return this.Write(fileOffset, 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) { return this.Write(-1, buffer, length); } /// /// Writes data to the file. /// /// /// The offset into the file to start writing at. Specify -1 to /// use the file object's current position. /// /// The data. /// The number of bytes to write. /// The number of bytes written to the file. public int Write(long fileOffset, IntPtr buffer, int length) { NtStatus status; IoStatusBlock isb; unsafe { status = Win32.NtWriteFile( this, IntPtr.Zero, null, IntPtr.Zero, out isb, buffer, length, fileOffset != -1 ? new IntPtr(&fileOffset) : IntPtr.Zero, IntPtr.Zero ); } if (status == NtStatus.Pending) { this.Wait(); status = isb.Status; } status.ThrowIf(); return isb.Information.ToInt32(); } } public enum AsyncIoMethod { Event, IoCompletion } public enum PositionOrigin { Start, Current, End } public sealed class AsyncIoCompletionPort : IDisposable { private readonly IoCompletionHandle _ioCompletionHandle; public AsyncIoCompletionPort() : this(0) { } public AsyncIoCompletionPort(int count) { _ioCompletionHandle = IoCompletionHandle.Create(IoCompletionAccess.All, count); } public void Dispose() { _ioCompletionHandle.Dispose(); } internal IoCompletionHandle Handle { get { return _ioCompletionHandle; } } public AsyncIoContext Remove() { return this.Remove(long.MinValue, false); } public AsyncIoContext Remove(long timeout, bool relative) { IoStatusBlock isb; IntPtr keyContext; IntPtr apcContext; bool result = this._ioCompletionHandle.Remove(out isb, out keyContext, out apcContext, relative ? -timeout : timeout, false); // Fail? if (!result) return null; AsyncIoContext asyncContext = AsyncIoContext.GetAsyncIoContext(apcContext); asyncContext.NotifyEnd(); return asyncContext; } public void Set(AsyncIoContext asyncContext, NtStatus ioStatus, IntPtr ioInformation) { if (asyncContext.Method != AsyncIoMethod.IoCompletion) throw new InvalidOperationException("The asynchronous I/O context is not I/O-completion-port-based."); _ioCompletionHandle.Set(IntPtr.Zero, asyncContext.Context, ioStatus, ioInformation); } } public sealed class AsyncIoContext : BaseObject, ISynchronizable { private unsafe sealed class UnmanagedIsb : BaseObject { public static implicit operator IntPtr(UnmanagedIsb isb) { return isb.Memory; } private readonly IoStatusBlock* _ioStatusBlock; public UnmanagedIsb() { // Allocate an ISB. _ioStatusBlock = (IoStatusBlock*)MemoryAlloc.PrivateHeap.Allocate(IoStatusBlock.SizeOf); // Zero the ISB. _ioStatusBlock->Pointer = IntPtr.Zero; _ioStatusBlock->Information = IntPtr.Zero; _ioStatusBlock->Status = 0; } protected override void DisposeObject(bool disposing) { if (_ioStatusBlock != null) MemoryAlloc.PrivateHeap.Free(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; } } } public static AsyncIoContext GetAsyncIoContext(IntPtr context) { return GCHandle.FromIntPtr(context).Target as AsyncIoContext; } private IntPtr _context = IntPtr.Zero; private readonly EventHandle _eventHandle; private readonly UnmanagedIsb _isb; private bool _completedSynchronously; private bool _started; private List _keepAliveList; private object _tag; public AsyncIoContext() : this(AsyncIoMethod.Event) { } public AsyncIoContext(AsyncIoMethod method) { _isb = new UnmanagedIsb { Status = NtStatus.Pending }; if (method == AsyncIoMethod.Event) { _eventHandle = EventHandle.Create(EventAccess.All, EventType.NotificationEvent, false); } else { _context = GCHandle.ToIntPtr(GCHandle.Alloc(this, GCHandleType.Normal)); } } protected override void DisposeObject(bool disposing) { if (_started && !this.Completed) { // Can't dispose, since the ISB memory is still in use. 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 (_isb != null) _isb.Dispose(); if (_context != IntPtr.Zero) { GCHandle handle = GCHandle.FromIntPtr(_context); if (handle.IsAllocated) handle.Free(); _context = IntPtr.Zero; } } 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; if (_completedSynchronously && _eventHandle != null) _eventHandle.Set(); } } public IntPtr Context { get { return _context; } } public EventHandle EventHandle { get { return _eventHandle; } } public int Information { get { return _isb.Information.ToInt32(); } } public AsyncIoMethod Method { get { return _eventHandle != null ? AsyncIoMethod.Event : AsyncIoMethod.IoCompletion; } } 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; } } public IntPtr StatusMemory { get { return _isb; } } public object Tag { get { return _tag; } set { _tag = value; } } private void ClearKeepAlive() { if (_keepAliveList != null) { foreach (var obj in _keepAliveList) obj.Dereference(); _keepAliveList.Clear(); } } public void KeepAlive(BaseObject obj) { if (_keepAliveList == null) _keepAliveList = new List(); _keepAliveList.Add(obj); obj.Reference(); } public void NotifyBegin() { _started = true; } public void NotifyPreBegin() { if (_started) { throw new InvalidOperationException( "The asynchronous I/O context object is already associated with an operation." ); } } public void NotifyEnd() { this.ClearKeepAlive(); if (_context != IntPtr.Zero) { GCHandle.FromIntPtr(_context).Free(); _context = IntPtr.Zero; } } #region ISynchronizable Members [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] public IntPtr Handle { get { return _eventHandle.Handle; } } public NtStatus Wait() { // Shortcut if (this.Status != NtStatus.Pending) return NtStatus.Success; if (this.Method != AsyncIoMethod.Event) throw new InvalidOperationException("The asynchronous I/O context object is not event-based."); return _eventHandle.Wait(); } public NtStatus Wait(bool alertable) { // Shortcut if (this.Status != NtStatus.Pending) return NtStatus.Success; if (this.Method != AsyncIoMethod.Event) throw new InvalidOperationException("The asynchronous I/O context object is not event-based."); return _eventHandle.Wait(alertable); } public NtStatus Wait(bool alertable, long timeout) { // Shortcut if (this.Status != NtStatus.Pending) return NtStatus.Success; if (this.Method != AsyncIoMethod.Event) throw new InvalidOperationException("The asynchronous I/O context object is not event-based."); 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; } } }