mirror of
https://github.com/TalAloni/SMBLibrary.git
synced 2025-05-06 21:47:50 +02:00
526 lines
28 KiB
C#
526 lines
28 KiB
C#
/* Copyright (C) 2017-2020 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
|
|
*
|
|
* You can redistribute this program and/or modify it under the terms of
|
|
* the GNU Lesser Public License as published by the Free Software Foundation,
|
|
* either version 3 of the License, or (at your option) any later version.
|
|
*/
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading;
|
|
using Microsoft.Win32.SafeHandles;
|
|
using Utilities;
|
|
|
|
namespace SMBLibrary.Win32
|
|
{
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
public struct UNICODE_STRING : IDisposable
|
|
{
|
|
public ushort Length;
|
|
public ushort MaximumLength;
|
|
private IntPtr Buffer;
|
|
|
|
public UNICODE_STRING(string value)
|
|
{
|
|
Length = (ushort)(value.Length * 2);
|
|
MaximumLength = (ushort)(value.Length + 2);
|
|
Buffer = Marshal.StringToHGlobalUni(value);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Marshal.FreeHGlobal(Buffer);
|
|
Buffer = IntPtr.Zero;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return Marshal.PtrToStringUni(Buffer);
|
|
}
|
|
}
|
|
|
|
[StructLayoutAttribute(LayoutKind.Sequential)]
|
|
public struct OBJECT_ATTRIBUTES
|
|
{
|
|
public int Length;
|
|
public IntPtr RootDirectory;
|
|
public IntPtr ObjectName;
|
|
public uint Attributes;
|
|
public IntPtr SecurityDescriptor;
|
|
public IntPtr SecurityQualityOfService;
|
|
}
|
|
|
|
[StructLayoutAttribute(LayoutKind.Sequential)]
|
|
public struct IO_STATUS_BLOCK
|
|
{
|
|
public UInt32 Status;
|
|
public IntPtr Information;
|
|
}
|
|
|
|
internal class PendingRequest
|
|
{
|
|
public IntPtr FileHandle;
|
|
public uint ThreadID;
|
|
public IO_STATUS_BLOCK IOStatusBlock;
|
|
public bool Cleanup;
|
|
}
|
|
|
|
public class NTDirectoryFileSystem : INTFileStore
|
|
{
|
|
[DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
|
|
private static extern NTStatus NtCreateFile(out IntPtr handle, uint desiredAccess, ref OBJECT_ATTRIBUTES objectAttributes, out IO_STATUS_BLOCK ioStatusBlock, ref long allocationSize, FileAttributes fileAttributes, ShareAccess shareAccess, CreateDisposition createDisposition, CreateOptions createOptions, IntPtr eaBuffer, uint eaLength);
|
|
|
|
[DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
|
|
private static extern NTStatus NtClose(IntPtr handle);
|
|
|
|
[DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
|
|
private static extern NTStatus NtReadFile(IntPtr handle, IntPtr evt, IntPtr apcRoutine, IntPtr apcContext, out IO_STATUS_BLOCK ioStatusBlock, byte[] buffer, uint length, ref long byteOffset, IntPtr key);
|
|
|
|
[DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
|
|
private static extern NTStatus NtWriteFile(IntPtr handle, IntPtr evt, IntPtr apcRoutine, IntPtr apcContext, out IO_STATUS_BLOCK ioStatusBlock, byte[] buffer, uint length, ref long byteOffset, IntPtr key);
|
|
|
|
[DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
|
|
private static extern NTStatus NtFlushBuffersFile(IntPtr handle, out IO_STATUS_BLOCK ioStatusBlock);
|
|
|
|
[DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
|
|
private static extern NTStatus NtLockFile(IntPtr handle, IntPtr evt, IntPtr apcRoutine, IntPtr apcContext, out IO_STATUS_BLOCK ioStatusBlock, ref long byteOffset, ref long length, uint key, bool failImmediately, bool exclusiveLock);
|
|
|
|
[DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
|
|
private static extern NTStatus NtUnlockFile(IntPtr handle, out IO_STATUS_BLOCK ioStatusBlock, ref long byteOffset, ref long length, uint key);
|
|
|
|
[DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
|
|
private static extern NTStatus NtQueryDirectoryFile(IntPtr handle, IntPtr evt, IntPtr apcRoutine, IntPtr apcContext, out IO_STATUS_BLOCK ioStatusBlock, byte[] fileInformation, uint length, uint fileInformationClass, bool returnSingleEntry, ref UNICODE_STRING fileName, bool restartScan);
|
|
|
|
[DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
|
|
private static extern NTStatus NtQueryInformationFile(IntPtr handle, out IO_STATUS_BLOCK ioStatusBlock, byte[] fileInformation, uint length, uint fileInformationClass);
|
|
|
|
[DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
|
|
private static extern NTStatus NtSetInformationFile(IntPtr handle, out IO_STATUS_BLOCK ioStatusBlock, byte[] fileInformation, uint length, uint fileInformationClass);
|
|
|
|
[DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
|
|
private static extern NTStatus NtQueryVolumeInformationFile(IntPtr handle, out IO_STATUS_BLOCK ioStatusBlock, byte[] fsInformation, uint length, uint fsInformationClass);
|
|
|
|
[DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
|
|
private static extern NTStatus NtSetVolumeInformationFile(IntPtr handle, out IO_STATUS_BLOCK ioStatusBlock, byte[] fsInformation, uint length, uint fsInformationClass);
|
|
|
|
[DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
|
|
private static extern NTStatus NtQuerySecurityObject(IntPtr handle, SecurityInformation securityInformation, byte[] securityDescriptor, uint length, out uint lengthNeeded);
|
|
|
|
[DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
|
|
private static extern NTStatus NtSetSecurityObject(IntPtr handle, SecurityInformation securityInformation, byte[] securityDescriptor);
|
|
|
|
[DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
|
|
private static extern NTStatus NtNotifyChangeDirectoryFile(IntPtr handle, IntPtr evt, IntPtr apcRoutine, IntPtr apcContext, out IO_STATUS_BLOCK ioStatusBlock, byte[] buffer, uint bufferSize, NotifyChangeFilter completionFilter, bool watchTree);
|
|
|
|
[DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
|
|
private static extern NTStatus NtFsControlFile(IntPtr handle, IntPtr evt, IntPtr apcRoutine, IntPtr apcContext, out IO_STATUS_BLOCK ioStatusBlock, uint ioControlCode, byte[] inputBuffer, uint inputBufferLength, byte[] outputBuffer, uint outputBufferLength);
|
|
|
|
[DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
|
|
private static extern NTStatus NtAlertThread(IntPtr threadHandle);
|
|
|
|
// Available starting from Windows Vista.
|
|
[DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
|
|
private static extern NTStatus NtCancelSynchronousIoFile(IntPtr threadHandle, IntPtr ioRequestToCancel, out IO_STATUS_BLOCK ioStatusBlock);
|
|
|
|
private static readonly int QueryDirectoryBufferSize = 4096;
|
|
private static readonly int FileInformationBufferSize = 8192;
|
|
private static readonly int FileSystemInformationBufferSize = 4096;
|
|
|
|
private DirectoryInfo m_directory;
|
|
private PendingRequestCollection m_pendingRequests = new PendingRequestCollection();
|
|
|
|
public NTDirectoryFileSystem(string path) : this(new DirectoryInfo(path))
|
|
{
|
|
}
|
|
|
|
public NTDirectoryFileSystem(DirectoryInfo directory)
|
|
{
|
|
m_directory = directory;
|
|
}
|
|
|
|
private OBJECT_ATTRIBUTES InitializeObjectAttributes(UNICODE_STRING objectName)
|
|
{
|
|
OBJECT_ATTRIBUTES objectAttributes = new OBJECT_ATTRIBUTES();
|
|
objectAttributes.RootDirectory = IntPtr.Zero;
|
|
objectAttributes.ObjectName = Marshal.AllocHGlobal(Marshal.SizeOf(objectName));
|
|
Marshal.StructureToPtr(objectName, objectAttributes.ObjectName, false);
|
|
objectAttributes.SecurityDescriptor = IntPtr.Zero;
|
|
objectAttributes.SecurityQualityOfService = IntPtr.Zero;
|
|
|
|
objectAttributes.Length = Marshal.SizeOf(objectAttributes);
|
|
return objectAttributes;
|
|
}
|
|
|
|
private NTStatus CreateFile(out IntPtr handle, out FileStatus fileStatus, string nativePath, AccessMask desiredAccess, long allocationSize, FileAttributes fileAttributes, ShareAccess shareAccess, CreateDisposition createDisposition, CreateOptions createOptions)
|
|
{
|
|
UNICODE_STRING objectName = new UNICODE_STRING(nativePath);
|
|
OBJECT_ATTRIBUTES objectAttributes = InitializeObjectAttributes(objectName);
|
|
IO_STATUS_BLOCK ioStatusBlock;
|
|
NTStatus status = NtCreateFile(out handle, (uint)desiredAccess, ref objectAttributes, out ioStatusBlock, ref allocationSize, fileAttributes, shareAccess, createDisposition, createOptions, IntPtr.Zero, 0);
|
|
fileStatus = (FileStatus)ioStatusBlock.Information;
|
|
return status;
|
|
}
|
|
|
|
private string ToNativePath(string path)
|
|
{
|
|
if (!path.StartsWith(@"\"))
|
|
{
|
|
path = @"\" + path;
|
|
}
|
|
return @"\??\" + m_directory.FullName + path;
|
|
}
|
|
|
|
public NTStatus CreateFile(out object handle, out FileStatus fileStatus, string path, AccessMask desiredAccess, FileAttributes fileAttributes, ShareAccess shareAccess, CreateDisposition createDisposition, CreateOptions createOptions, SecurityContext securityContext)
|
|
{
|
|
IntPtr fileHandle;
|
|
string nativePath = ToNativePath(path);
|
|
// NtQueryDirectoryFile will return STATUS_PENDING if the directory handle was not opened with SYNCHRONIZE and FILE_SYNCHRONOUS_IO_ALERT or FILE_SYNCHRONOUS_IO_NONALERT.
|
|
// Our usage of NtNotifyChangeDirectoryFile assumes the directory handle is opened with SYNCHRONIZE and FILE_SYNCHRONOUS_IO_ALERT (or FILE_SYNCHRONOUS_IO_NONALERT starting from Windows Vista).
|
|
// Note: Sometimes a directory will be opened without specifying FILE_DIRECTORY_FILE.
|
|
desiredAccess |= AccessMask.SYNCHRONIZE;
|
|
createOptions &= ~CreateOptions.FILE_SYNCHRONOUS_IO_NONALERT;
|
|
createOptions |= CreateOptions.FILE_SYNCHRONOUS_IO_ALERT;
|
|
|
|
if ((createOptions & CreateOptions.FILE_NO_INTERMEDIATE_BUFFERING) > 0 &&
|
|
((FileAccessMask)desiredAccess & FileAccessMask.FILE_APPEND_DATA) > 0)
|
|
{
|
|
// FILE_NO_INTERMEDIATE_BUFFERING is incompatible with FILE_APPEND_DATA
|
|
// [MS-SMB2] 3.3.5.9 suggests setting FILE_APPEND_DATA to zero in this case.
|
|
desiredAccess = (AccessMask)((uint)desiredAccess & (uint)~FileAccessMask.FILE_APPEND_DATA);
|
|
}
|
|
|
|
NTStatus status = CreateFile(out fileHandle, out fileStatus, nativePath, desiredAccess, 0, fileAttributes, shareAccess, createDisposition, createOptions);
|
|
handle = fileHandle;
|
|
return status;
|
|
}
|
|
|
|
public NTStatus CloseFile(object handle)
|
|
{
|
|
// [MS-FSA] 2.1.5.4 The close operation has to complete any pending ChangeNotify request with STATUS_NOTIFY_CLEANUP.
|
|
// - When closing a synchronous handle we must explicitly cancel any pending ChangeNotify request, otherwise the call to NtClose will hang.
|
|
// We use request.Cleanup to tell that we should complete such ChangeNotify request with STATUS_NOTIFY_CLEANUP.
|
|
// - When closing an asynchronous handle Windows will implicitly complete any pending ChangeNotify request with STATUS_NOTIFY_CLEANUP as required.
|
|
List<PendingRequest> pendingRequests = m_pendingRequests.GetRequestsByHandle((IntPtr)handle);
|
|
foreach (PendingRequest request in pendingRequests)
|
|
{
|
|
request.Cleanup = true;
|
|
Cancel(request);
|
|
}
|
|
return NtClose((IntPtr)handle);
|
|
}
|
|
|
|
public NTStatus ReadFile(out byte[] data, object handle, long offset, int maxCount)
|
|
{
|
|
IO_STATUS_BLOCK ioStatusBlock;
|
|
data = new byte[maxCount];
|
|
NTStatus status = NtReadFile((IntPtr)handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out ioStatusBlock, data, (uint)maxCount, ref offset, IntPtr.Zero);
|
|
if (status == NTStatus.STATUS_SUCCESS)
|
|
{
|
|
int bytesRead = (int)ioStatusBlock.Information;
|
|
if (bytesRead < maxCount)
|
|
{
|
|
data = ByteReader.ReadBytes(data, 0, bytesRead);
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
public NTStatus WriteFile(out int numberOfBytesWritten, object handle, long offset, byte[] data)
|
|
{
|
|
IO_STATUS_BLOCK ioStatusBlock;
|
|
NTStatus status = NtWriteFile((IntPtr)handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out ioStatusBlock, data, (uint)data.Length, ref offset, IntPtr.Zero);
|
|
if (status == NTStatus.STATUS_SUCCESS)
|
|
{
|
|
numberOfBytesWritten = (int)ioStatusBlock.Information;
|
|
}
|
|
else
|
|
{
|
|
numberOfBytesWritten = 0;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
public NTStatus FlushFileBuffers(object handle)
|
|
{
|
|
IO_STATUS_BLOCK ioStatusBlock;
|
|
return NtFlushBuffersFile((IntPtr)handle, out ioStatusBlock);
|
|
}
|
|
|
|
public NTStatus LockFile(object handle, long byteOffset, long length, bool exclusiveLock)
|
|
{
|
|
IO_STATUS_BLOCK ioStatusBlock;
|
|
return NtLockFile((IntPtr)handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out ioStatusBlock, ref byteOffset, ref length, 0, true, exclusiveLock);
|
|
}
|
|
|
|
public NTStatus UnlockFile(object handle, long byteOffset, long length)
|
|
{
|
|
IO_STATUS_BLOCK ioStatusBlock;
|
|
return NtUnlockFile((IntPtr)handle, out ioStatusBlock, ref byteOffset, ref length, 0);
|
|
}
|
|
|
|
public NTStatus QueryDirectory(out List<QueryDirectoryFileInformation> result, object handle, string fileName, FileInformationClass informationClass)
|
|
{
|
|
IO_STATUS_BLOCK ioStatusBlock;
|
|
byte[] buffer = new byte[QueryDirectoryBufferSize];
|
|
UNICODE_STRING fileNameStructure = new UNICODE_STRING(fileName);
|
|
result = new List<QueryDirectoryFileInformation>();
|
|
bool restartScan = true;
|
|
while (true)
|
|
{
|
|
NTStatus status = NtQueryDirectoryFile((IntPtr)handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out ioStatusBlock, buffer, (uint)buffer.Length, (byte)informationClass, false, ref fileNameStructure, restartScan);
|
|
if (status == NTStatus.STATUS_NO_MORE_FILES)
|
|
{
|
|
break;
|
|
}
|
|
else if (status != NTStatus.STATUS_SUCCESS)
|
|
{
|
|
return status;
|
|
}
|
|
int numberOfBytesWritten = (int)ioStatusBlock.Information;
|
|
List<QueryDirectoryFileInformation> page = QueryDirectoryFileInformation.ReadFileInformationList(buffer, 0, informationClass);
|
|
result.AddRange(page);
|
|
restartScan = false;
|
|
}
|
|
fileNameStructure.Dispose();
|
|
return NTStatus.STATUS_SUCCESS;
|
|
}
|
|
|
|
public NTStatus GetFileInformation(out FileInformation result, object handle, FileInformationClass informationClass)
|
|
{
|
|
IO_STATUS_BLOCK ioStatusBlock;
|
|
byte[] buffer = new byte[FileInformationBufferSize];
|
|
NTStatus status = NtQueryInformationFile((IntPtr)handle, out ioStatusBlock, buffer, (uint)buffer.Length, (uint)informationClass);
|
|
if (status == NTStatus.STATUS_SUCCESS)
|
|
{
|
|
int numberOfBytesWritten = (int)ioStatusBlock.Information;
|
|
buffer = ByteReader.ReadBytes(buffer, 0, numberOfBytesWritten);
|
|
result = FileInformation.GetFileInformation(buffer, 0, informationClass);
|
|
}
|
|
else
|
|
{
|
|
result = null;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
public NTStatus SetFileInformation(object handle, FileInformation information)
|
|
{
|
|
IO_STATUS_BLOCK ioStatusBlock;
|
|
if (information is FileRenameInformationType2)
|
|
{
|
|
FileRenameInformationType2 fileRenameInformationRemote = (FileRenameInformationType2)information;
|
|
if (ProcessHelper.Is64BitProcess)
|
|
{
|
|
// We should not modify the FileRenameInformationType2 instance we received - the caller may use it later.
|
|
FileRenameInformationType2 fileRenameInformationLocal = new FileRenameInformationType2();
|
|
fileRenameInformationLocal.ReplaceIfExists = fileRenameInformationRemote.ReplaceIfExists;
|
|
fileRenameInformationLocal.FileName = ToNativePath(fileRenameInformationRemote.FileName);
|
|
information = fileRenameInformationLocal;
|
|
}
|
|
else
|
|
{
|
|
// Note: WOW64 process should use FILE_RENAME_INFORMATION_TYPE_1.
|
|
// Note: Server 2003 x64 has issues with using FILE_RENAME_INFORMATION under WOW64.
|
|
FileRenameInformationType1 fileRenameInformationLocal = new FileRenameInformationType1();
|
|
fileRenameInformationLocal.ReplaceIfExists = fileRenameInformationRemote.ReplaceIfExists;
|
|
fileRenameInformationLocal.FileName = ToNativePath(fileRenameInformationRemote.FileName);
|
|
information = fileRenameInformationLocal;
|
|
}
|
|
}
|
|
else if (information is FileLinkInformationType2)
|
|
{
|
|
FileLinkInformationType2 fileLinkInformationRemote = (FileLinkInformationType2)information;
|
|
if (ProcessHelper.Is64BitProcess)
|
|
{
|
|
FileRenameInformationType2 fileLinkInformationLocal = new FileRenameInformationType2();
|
|
fileLinkInformationLocal.ReplaceIfExists = fileLinkInformationRemote.ReplaceIfExists;
|
|
fileLinkInformationLocal.FileName = ToNativePath(fileLinkInformationRemote.FileName);
|
|
information = fileLinkInformationRemote;
|
|
}
|
|
else
|
|
{
|
|
FileLinkInformationType1 fileLinkInformationLocal = new FileLinkInformationType1();
|
|
fileLinkInformationLocal.ReplaceIfExists = fileLinkInformationRemote.ReplaceIfExists;
|
|
fileLinkInformationLocal.FileName = ToNativePath(fileLinkInformationRemote.FileName);
|
|
information = fileLinkInformationRemote;
|
|
}
|
|
}
|
|
byte[] buffer = information.GetBytes();
|
|
return NtSetInformationFile((IntPtr)handle, out ioStatusBlock, buffer, (uint)buffer.Length, (uint)information.FileInformationClass);
|
|
}
|
|
|
|
public NTStatus GetFileSystemInformation(out FileSystemInformation result, FileSystemInformationClass informationClass)
|
|
{
|
|
IO_STATUS_BLOCK ioStatusBlock;
|
|
byte[] buffer = new byte[FileSystemInformationBufferSize];
|
|
IntPtr volumeHandle;
|
|
FileStatus fileStatus;
|
|
string nativePath = @"\??\" + m_directory.FullName.Substring(0, 3);
|
|
NTStatus status = CreateFile(out volumeHandle, out fileStatus, nativePath, AccessMask.GENERIC_READ, 0, (FileAttributes)0, ShareAccess.Read, CreateDisposition.FILE_OPEN, (CreateOptions)0);
|
|
result = null;
|
|
if (status != NTStatus.STATUS_SUCCESS)
|
|
{
|
|
return status;
|
|
}
|
|
status = NtQueryVolumeInformationFile((IntPtr)volumeHandle, out ioStatusBlock, buffer, (uint)buffer.Length, (uint)informationClass);
|
|
CloseFile(volumeHandle);
|
|
if (status == NTStatus.STATUS_SUCCESS)
|
|
{
|
|
int numberOfBytesWritten = (int)ioStatusBlock.Information;
|
|
buffer = ByteReader.ReadBytes(buffer, 0, numberOfBytesWritten);
|
|
result = FileSystemInformation.GetFileSystemInformation(buffer, 0, informationClass);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
public NTStatus SetFileSystemInformation(FileSystemInformation information)
|
|
{
|
|
return NTStatus.STATUS_NOT_SUPPORTED;
|
|
}
|
|
|
|
public NTStatus GetSecurityInformation(out SecurityDescriptor result, object handle, SecurityInformation securityInformation)
|
|
{
|
|
result = null;
|
|
return NTStatus.STATUS_INVALID_DEVICE_REQUEST;
|
|
}
|
|
|
|
public NTStatus SetSecurityInformation(object handle, SecurityInformation securityInformation, SecurityDescriptor securityDescriptor)
|
|
{
|
|
// [MS-FSA] If the object store does not implement security, the operation MUST be failed with STATUS_INVALID_DEVICE_REQUEST.
|
|
return NTStatus.STATUS_INVALID_DEVICE_REQUEST;
|
|
}
|
|
|
|
public NTStatus NotifyChange(out object ioRequest, object handle, NotifyChangeFilter completionFilter, bool watchTree, int outputBufferSize, OnNotifyChangeCompleted onNotifyChangeCompleted, object context)
|
|
{
|
|
byte[] buffer = new byte[outputBufferSize];
|
|
ManualResetEvent requestAddedEvent = new ManualResetEvent(false);
|
|
PendingRequest request = new PendingRequest();
|
|
Thread m_thread = new Thread(delegate()
|
|
{
|
|
request.FileHandle = (IntPtr)handle;
|
|
request.ThreadID = ThreadingHelper.GetCurrentThreadId();
|
|
m_pendingRequests.Add(request);
|
|
// The request has been added, we can now return STATUS_PENDING.
|
|
requestAddedEvent.Set();
|
|
// There is a possibility of race condition if the caller will wait for STATUS_PENDING and then immediate call Cancel, but this scenario is very unlikely.
|
|
NTStatus status = NtNotifyChangeDirectoryFile((IntPtr)handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out request.IOStatusBlock, buffer, (uint)buffer.Length, completionFilter, watchTree);
|
|
if (status == NTStatus.STATUS_SUCCESS)
|
|
{
|
|
int length = (int)request.IOStatusBlock.Information;
|
|
buffer = ByteReader.ReadBytes(buffer, 0, length);
|
|
}
|
|
else
|
|
{
|
|
const NTStatus STATUS_ALERTED = (NTStatus)0x00000101;
|
|
const NTStatus STATUS_OBJECT_TYPE_MISMATCH = (NTStatus)0xC0000024;
|
|
|
|
buffer = new byte[0];
|
|
if (status == STATUS_OBJECT_TYPE_MISMATCH)
|
|
{
|
|
status = NTStatus.STATUS_INVALID_HANDLE;
|
|
}
|
|
else if (status == STATUS_ALERTED)
|
|
{
|
|
status = NTStatus.STATUS_CANCELLED;
|
|
}
|
|
|
|
// If the handle is closing and we had to cancel a ChangeNotify request as part of a cleanup,
|
|
// we return STATUS_NOTIFY_CLEANUP as specified in [MS-FSA] 2.1.5.4.
|
|
if (status == NTStatus.STATUS_CANCELLED && request.Cleanup)
|
|
{
|
|
status = NTStatus.STATUS_NOTIFY_CLEANUP;
|
|
}
|
|
}
|
|
onNotifyChangeCompleted(status, buffer, context);
|
|
m_pendingRequests.Remove((IntPtr)handle, request.ThreadID);
|
|
});
|
|
m_thread.Start();
|
|
|
|
// We must wait for the request to be added in order for Cancel to function properly.
|
|
requestAddedEvent.WaitOne();
|
|
ioRequest = request;
|
|
return NTStatus.STATUS_PENDING;
|
|
}
|
|
|
|
public NTStatus Cancel(object ioRequest)
|
|
{
|
|
PendingRequest request = (PendingRequest)ioRequest;
|
|
const uint THREAD_TERMINATE = 0x00000001;
|
|
const uint THREAD_ALERT = 0x00000004;
|
|
uint threadID = request.ThreadID;
|
|
IntPtr threadHandle = ThreadingHelper.OpenThread(THREAD_TERMINATE | THREAD_ALERT, false, threadID);
|
|
if (threadHandle == IntPtr.Zero)
|
|
{
|
|
Win32Error error = (Win32Error)Marshal.GetLastWin32Error();
|
|
if (error == Win32Error.ERROR_INVALID_PARAMETER)
|
|
{
|
|
return NTStatus.STATUS_INVALID_HANDLE;
|
|
}
|
|
else
|
|
{
|
|
throw new Exception("OpenThread failed, Win32 error: " + error.ToString("D"));
|
|
}
|
|
}
|
|
|
|
NTStatus status;
|
|
if (Environment.OSVersion.Version.Major >= 6)
|
|
{
|
|
IO_STATUS_BLOCK ioStatusBlock;
|
|
status = NtCancelSynchronousIoFile(threadHandle, IntPtr.Zero, out ioStatusBlock);
|
|
}
|
|
else
|
|
{
|
|
// The handle was opened for synchronous operation so NtNotifyChangeDirectoryFile is blocking.
|
|
// We MUST use NtAlertThread to send a signal to stop the wait. The handle cannot be closed otherwise.
|
|
// Note: The handle was opened with CreateOptions.FILE_SYNCHRONOUS_IO_ALERT as required.
|
|
status = NtAlertThread(threadHandle);
|
|
}
|
|
|
|
ThreadingHelper.CloseHandle(threadHandle);
|
|
m_pendingRequests.Remove(request.FileHandle, request.ThreadID);
|
|
return status;
|
|
}
|
|
|
|
public NTStatus DeviceIOControl(object handle, uint ctlCode, byte[] input, out byte[] output, int maxOutputLength)
|
|
{
|
|
switch ((IoControlCode)ctlCode)
|
|
{
|
|
case IoControlCode.FSCTL_IS_PATHNAME_VALID:
|
|
case IoControlCode.FSCTL_GET_COMPRESSION:
|
|
case IoControlCode.FSCTL_GET_RETRIEVAL_POINTERS:
|
|
case IoControlCode.FSCTL_SET_OBJECT_ID:
|
|
case IoControlCode.FSCTL_GET_OBJECT_ID:
|
|
case IoControlCode.FSCTL_DELETE_OBJECT_ID:
|
|
case IoControlCode.FSCTL_SET_OBJECT_ID_EXTENDED:
|
|
case IoControlCode.FSCTL_CREATE_OR_GET_OBJECT_ID:
|
|
case IoControlCode.FSCTL_SET_SPARSE:
|
|
case IoControlCode.FSCTL_READ_FILE_USN_DATA:
|
|
case IoControlCode.FSCTL_SET_DEFECT_MANAGEMENT:
|
|
case IoControlCode.FSCTL_SET_COMPRESSION:
|
|
case IoControlCode.FSCTL_QUERY_SPARING_INFO:
|
|
case IoControlCode.FSCTL_QUERY_ON_DISK_VOLUME_INFO:
|
|
case IoControlCode.FSCTL_SET_ZERO_ON_DEALLOCATION:
|
|
case IoControlCode.FSCTL_QUERY_FILE_REGIONS:
|
|
case IoControlCode.FSCTL_QUERY_ALLOCATED_RANGES:
|
|
case IoControlCode.FSCTL_SET_ZERO_DATA:
|
|
{
|
|
IO_STATUS_BLOCK ioStatusBlock;
|
|
output = new byte[maxOutputLength];
|
|
NTStatus status = NtFsControlFile((IntPtr)handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out ioStatusBlock, ctlCode, input, (uint)input.Length, output, (uint)maxOutputLength);
|
|
if (status == NTStatus.STATUS_SUCCESS)
|
|
{
|
|
int numberOfBytesWritten = (int)ioStatusBlock.Information;
|
|
output = ByteReader.ReadBytes(output, 0, numberOfBytesWritten);
|
|
}
|
|
return status;
|
|
}
|
|
default:
|
|
{
|
|
output = null;
|
|
return NTStatus.STATUS_NOT_SUPPORTED;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|