You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2407 lines
75 KiB
2407 lines
75 KiB
/*++
|
|
|
|
Copyright (c) 1998-2000 Microsoft Corporation
|
|
|
|
Module Name :
|
|
|
|
device.cpp
|
|
|
|
Abstract:
|
|
|
|
Device object handles one redirected device
|
|
|
|
Revision History:
|
|
--*/
|
|
|
|
#include "precomp.hxx"
|
|
#define TRC_FILE "device"
|
|
#include "trc.h"
|
|
|
|
#if DBG
|
|
extern UCHAR IrpNames[IRP_MJ_MAXIMUM_FUNCTION + 1][40];
|
|
#endif // DBG
|
|
|
|
DrDevice::DrDevice(SmartPtr<DrSession> &Session, ULONG DeviceType, ULONG DeviceId, PUCHAR PreferredDosName)
|
|
{
|
|
unsigned len;
|
|
|
|
BEGIN_FN("DrDevice::DrDevice");
|
|
|
|
ASSERT(Session != NULL);
|
|
ASSERT(PreferredDosName != NULL);
|
|
|
|
TRC_NRM((TB, "Create Device (%p, session: %p, type: %d, id: %d, dosname: %s",
|
|
this, Session, DeviceType, DeviceId, PreferredDosName));
|
|
|
|
SetClassName("DrDevice");
|
|
_Session = Session;
|
|
_DeviceType = DeviceType;
|
|
_DeviceId = DeviceId;
|
|
_DeviceStatus = dsAvailable;
|
|
|
|
#if DBG
|
|
_VNetRootFinalized = FALSE;
|
|
_VNetRoot = NULL;
|
|
#endif
|
|
|
|
RtlCopyMemory(_PreferredDosName, PreferredDosName,
|
|
PREFERRED_DOS_NAME_SIZE);
|
|
|
|
//
|
|
// Review: We don't want to redirect any device name willy nilly,
|
|
// as I think that would be a security issue given a bad client
|
|
//
|
|
|
|
_PreferredDosName[PREFERRED_DOS_NAME_SIZE - 1] = 0;
|
|
|
|
//
|
|
// We don't want colon for end of DosName
|
|
//
|
|
len = strlen((CHAR*)_PreferredDosName);
|
|
if (len && _PreferredDosName[len-1] == ':') {
|
|
_PreferredDosName[len-1] = '\0';
|
|
}
|
|
}
|
|
|
|
DrDevice::~DrDevice()
|
|
{
|
|
|
|
BEGIN_FN("DrDevice::~DrDevice");
|
|
|
|
#if DBG
|
|
if (_VNetRoot != NULL && _VNetRootFinalized != TRUE) {
|
|
ASSERT(FALSE);
|
|
}
|
|
#endif
|
|
|
|
TRC_NRM((TB, "Delete Device %p for Session %p", this, _Session));
|
|
}
|
|
|
|
BOOL DrDevice::ShouldCreateDevice()
|
|
{
|
|
BEGIN_FN("DrDevice::ShouldCreateDevice");
|
|
|
|
//
|
|
// Default is to create the device
|
|
//
|
|
return TRUE;
|
|
}
|
|
|
|
NTSTATUS DrDevice::Initialize(PRDPDR_DEVICE_ANNOUNCE devAnnounceMsg, ULONG Length)
|
|
{
|
|
BEGIN_FN("DrDevice::Initialize");
|
|
|
|
// Can't assume devAnnouceMsg is not NULL, need to check if uses it
|
|
// ASSERT(devAnnounceMsg != NULL);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
VOID DrDevice::CreateReferenceString(
|
|
IN OUT PUNICODE_STRING refString)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Create the device reference string using information about the
|
|
client device. This string is in a form suitable for reparse
|
|
on IRP_MJ_CREATE and redirection to the minirdr DO.
|
|
|
|
The form of the resultant reference string is:
|
|
\;<DriveLetter>:<sessionid>\clientname\preferredDosName
|
|
|
|
Arguments:
|
|
|
|
DosDeviceName - Dos Device Name in UNICODE
|
|
refString - UNICODE structure large enough to hold
|
|
the entire resultant reference string. This
|
|
works out to be DRMAXREFSTRINGLEN bytes.
|
|
|
|
Return Value:
|
|
|
|
NONE
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status;
|
|
STRING string;
|
|
WCHAR numericBuf[RDPDRMAXULONGSTRING+1] = L"\0";
|
|
WCHAR ansiBuf[RDPDRMAXDOSNAMELEN+1] = L"\0";
|
|
UNICODE_STRING ansiUnc;
|
|
UNICODE_STRING numericUnc;
|
|
ULONG sessionID = _Session->GetSessionId();
|
|
PCSZ preferredDosName = (PCSZ)_PreferredDosName;
|
|
|
|
BEGIN_FN("DrDevice::CreateReferenceString");
|
|
ASSERT(refString != NULL);
|
|
|
|
// Sanity check the preferred DOS name.
|
|
TRC_ASSERT(preferredDosName != NULL, (TB, "Invalid DOS device name."));
|
|
|
|
// Make sure the reference string buf is big enough.
|
|
TRC_ASSERT(refString->MaximumLength >= (RDPDRMAXREFSTRINGLEN * sizeof(WCHAR)),
|
|
(TB, "Reference string buffer too small."));
|
|
|
|
// Zero it out.
|
|
refString->Length = 0;
|
|
refString->Buffer[0] = L'\0';
|
|
|
|
// Add a '\;'
|
|
RtlAppendUnicodeToString(refString, L"\\;");
|
|
|
|
// Initialize the ansi conversion buf.
|
|
ansiUnc.Length = 0;
|
|
ansiUnc.MaximumLength = RDPDRMAXDOSNAMELEN * sizeof(WCHAR);
|
|
ansiUnc.Buffer = ansiBuf;
|
|
|
|
// Add the preferred dos name.
|
|
RtlInitAnsiString(&string, preferredDosName);
|
|
RtlAnsiStringToUnicodeString(&ansiUnc, &string, FALSE);
|
|
RtlAppendUnicodeStringToString(refString, &ansiUnc);
|
|
|
|
// Add a ':'
|
|
RtlAppendUnicodeToString(refString, L":");
|
|
|
|
// Initialize the numeric buf.
|
|
numericUnc.Length = 0;
|
|
numericUnc.MaximumLength = RDPDRMAXULONGSTRING * sizeof(WCHAR);
|
|
numericUnc.Buffer = numericBuf;
|
|
|
|
// Add the session ID in base 10.
|
|
RtlIntegerToUnicodeString(sessionID, 10, &numericUnc);
|
|
RtlAppendUnicodeStringToString(refString, &numericUnc);
|
|
|
|
// Add the '\'
|
|
RtlAppendUnicodeToString(refString, L"\\");
|
|
|
|
// Add Client name
|
|
#if 0
|
|
RtlAppendUnicodeToString(refString, _Session->GetClientName());
|
|
#endif
|
|
RtlAppendUnicodeToString(refString, DRUNCSERVERNAME_U);
|
|
|
|
// Add a '\'
|
|
RtlAppendUnicodeToString(refString, L"\\");
|
|
|
|
// Add the preferred dos name.
|
|
RtlAppendUnicodeStringToString(refString, &ansiUnc);
|
|
|
|
TRC_NRM((TB, "Reference string = %wZ", refString));
|
|
}
|
|
|
|
NTSTATUS DrDevice::CreateDevicePath(PUNICODE_STRING DevicePath)
|
|
/*++
|
|
Create NT DeviceName compatible with RDBSS convention
|
|
|
|
Format is:
|
|
\device\rdpdr\;<DriveLetter>:<sessionid>\ClientName\DosDeviceName
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
UNICODE_STRING DevicePathTail;
|
|
|
|
BEGIN_FN("DrDevice::CreateDevicePath");
|
|
ASSERT(DevicePath != NULL);
|
|
|
|
DevicePath->Length = 0;
|
|
Status = RtlAppendUnicodeToString(DevicePath, RDPDR_DEVICE_NAME_U);
|
|
|
|
if (!NT_ERROR(Status)) {
|
|
// Add the reference string to the end:
|
|
// Format is: \;<DriveLetter>:<sessionid>\clientName\share
|
|
DevicePathTail.Length = 0;
|
|
DevicePathTail.MaximumLength = DevicePath->MaximumLength - DevicePath->Length;
|
|
DevicePathTail.Buffer = DevicePath->Buffer + (DevicePath->Length / sizeof(WCHAR));
|
|
|
|
CreateReferenceString(&DevicePathTail);
|
|
|
|
DevicePath->Length += DevicePathTail.Length;
|
|
}
|
|
|
|
TRC_NRM((TB, "DevicePath=%wZ", DevicePath));
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS DrDevice::CreateDosDevicePath(PUNICODE_STRING DosDevicePath,
|
|
PUNICODE_STRING DosDeviceName)
|
|
{
|
|
NTSTATUS Status;
|
|
UNICODE_STRING linkNameTail;
|
|
|
|
BEGIN_FN("DrDevice::CreateDosDevicePath");
|
|
ASSERT(DosDevicePath != NULL);
|
|
ASSERT(DosDeviceName != NULL);
|
|
|
|
//
|
|
// Create the "\\Sessions\\<sessionId>\\DosDevices\\<DosDeviceName>" string
|
|
//
|
|
DosDevicePath->Length = 0;
|
|
Status = RtlAppendUnicodeToString(DosDevicePath, L"\\Sessions\\");
|
|
|
|
if (!NT_ERROR(Status)) {
|
|
//
|
|
// Append the Session Number
|
|
//
|
|
linkNameTail.Buffer = (PWSTR)(((PBYTE)DosDevicePath->Buffer) +
|
|
DosDevicePath->Length);
|
|
linkNameTail.Length = 0;
|
|
linkNameTail.MaximumLength = DosDevicePath->MaximumLength -
|
|
DosDevicePath->Length;
|
|
Status = RtlIntegerToUnicodeString(_Session->GetSessionId(), 10, &linkNameTail);
|
|
}
|
|
|
|
if (!NT_ERROR(Status)) {
|
|
DosDevicePath->Length += linkNameTail.Length;
|
|
|
|
//
|
|
// Append DosDevices
|
|
//
|
|
Status = RtlAppendUnicodeToString(DosDevicePath, L"\\DosDevices\\");
|
|
}
|
|
|
|
if (!NT_ERROR(Status)) {
|
|
Status = RtlAppendUnicodeStringToString(DosDevicePath, DosDeviceName);
|
|
TRC_NRM((TB, "Created DosDevicePath: %wZ", DosDevicePath));
|
|
}
|
|
|
|
TRC_NRM((TB, "DosDevicePath=%wZ", DosDevicePath));
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS DrDevice::CreateDosSymbolicLink(PUNICODE_STRING DosDeviceName)
|
|
{
|
|
WCHAR NtDevicePathBuffer[RDPDRMAXNTDEVICENAMEGLEN];
|
|
UNICODE_STRING NtDevicePath;
|
|
WCHAR DosDevicePathBuffer[MAX_PATH];
|
|
UNICODE_STRING DosDevicePath;
|
|
NTSTATUS Status;
|
|
|
|
BEGIN_FN("DrDevice::CreateDosSymbolicLink");
|
|
ASSERT(DosDeviceName != NULL);
|
|
|
|
NtDevicePath.MaximumLength = sizeof(NtDevicePathBuffer);
|
|
NtDevicePath.Length = 0;
|
|
NtDevicePath.Buffer = &NtDevicePathBuffer[0];
|
|
|
|
DosDevicePath.MaximumLength = sizeof(DosDevicePathBuffer);
|
|
DosDevicePath.Length = 0;
|
|
DosDevicePath.Buffer = &DosDevicePathBuffer[0];
|
|
|
|
//
|
|
// Get the NT device path to this dr device
|
|
//
|
|
|
|
Status = CreateDevicePath(&NtDevicePath);
|
|
TRC_NRM((TB, "Nt Device path: %wZ", &NtDevicePath));
|
|
|
|
if (!NT_ERROR(Status)) {
|
|
|
|
//
|
|
// Build the dos device path for this session
|
|
//
|
|
|
|
Status = CreateDosDevicePath(&DosDevicePath, DosDeviceName);
|
|
TRC_NRM((TB, "Dos Device path: %wZ", &DosDevicePath));
|
|
} else {
|
|
TRC_ERR((TB, "Can't create nt device path: 0x%08lx", Status));
|
|
return Status;
|
|
}
|
|
|
|
if (!NT_ERROR(Status)) {
|
|
|
|
//
|
|
// Actually create the symbolic link
|
|
//
|
|
|
|
IoDeleteSymbolicLink(&DosDevicePath);
|
|
Status = IoCreateSymbolicLink(&DosDevicePath, &NtDevicePath);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
TRC_NRM((TB, "Successfully created Symbolic link"));
|
|
}
|
|
else {
|
|
TRC_NRM((TB, "Failed to create Symbolic link %x", Status));
|
|
}
|
|
} else {
|
|
TRC_ERR((TB, "Can't create dos device path: 0x%08lx", Status));
|
|
return Status;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS DrDevice::VerifyCreateSecurity(PRX_CONTEXT RxContext, ULONG CurrentSessionId)
|
|
{
|
|
NTSTATUS Status;
|
|
ULONG irpSessionId;
|
|
|
|
BEGIN_FN("DrDevice::VerifyCreateSecurity");
|
|
|
|
ASSERT(RxContext != NULL);
|
|
ASSERT(RxContext->MajorFunction == IRP_MJ_CREATE);
|
|
|
|
Status = IoGetRequestorSessionId(RxContext->CurrentIrp, &irpSessionId);
|
|
if (NT_SUCCESS(Status)) {
|
|
|
|
if (irpSessionId == CurrentSessionId) {
|
|
TRC_DBG((TB, "Access accepted in DrCreate."));
|
|
return STATUS_SUCCESS;
|
|
}
|
|
//
|
|
// If the request is from the console session, it needs to be from a system
|
|
// process.
|
|
//
|
|
else if (irpSessionId == CONSOLE_SESSIONID) {
|
|
TRC_NRM((TB, "Create request from console process."));
|
|
|
|
if (!DrIsSystemProcessRequest(RxContext->CurrentIrp,
|
|
RxContext->CurrentIrpSp)) {
|
|
TRC_ALT((TB, "Create request not from system process."));
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
else {
|
|
TRC_NRM((TB, "Create request from system. Access accepted."));
|
|
return STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If not from the console and doesn't match the client entry session
|
|
// ID then deny access.
|
|
//
|
|
else {
|
|
TRC_ALT((TB, "Create request from %ld mismatch with session %ld.",
|
|
irpSessionId, _Session->GetSessionId()));
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
}
|
|
else {
|
|
TRC_ERR((TB, "IoGetRequestorSessionId failed with %08X.", Status));
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
VOID DrDevice::FinishCreate(PRX_CONTEXT RxContext)
|
|
{
|
|
RxCaptureFcb;
|
|
RX_FILE_TYPE StorageType;
|
|
|
|
BEGIN_FN("DrDevice::FinishCreate");
|
|
|
|
ULONG Attributes = 0; // in the fcb this is DirentRxFlags;
|
|
ULONG NumLinks = 0; // in the fcb this is NumberOfLinks;
|
|
LARGE_INTEGER CreationTime; // these fields are the same as for the Fcb
|
|
LARGE_INTEGER LastAccessTime;
|
|
LARGE_INTEGER LastWriteTime;
|
|
LARGE_INTEGER LastChangeTime;
|
|
LARGE_INTEGER AllocationSize; // common header fields
|
|
LARGE_INTEGER FileSize;
|
|
LARGE_INTEGER ValidDataLength;
|
|
FCB_INIT_PACKET InitPacket = { &Attributes, &NumLinks, &CreationTime,
|
|
&LastAccessTime, &LastWriteTime, &LastChangeTime,
|
|
&AllocationSize, &FileSize, &ValidDataLength };
|
|
|
|
ASSERT(RxContext != NULL);
|
|
//
|
|
// Pretty sure this is Device specific, but maybe caching the information
|
|
// be generic? We might be able to fill in these values from member
|
|
// variables
|
|
//
|
|
|
|
CreationTime.QuadPart = 0;
|
|
LastAccessTime.QuadPart = 0;
|
|
LastWriteTime.QuadPart = 0;
|
|
LastChangeTime.QuadPart = 0;
|
|
AllocationSize.QuadPart = 0;
|
|
FileSize.QuadPart = 0x7FFFFFFF; // These need to be non-zero for reads to occur
|
|
ValidDataLength.QuadPart = 0x7FFFFFFF;
|
|
|
|
StorageType = RxInferFileType(RxContext);
|
|
|
|
if (StorageType == FileTypeNotYetKnown) {
|
|
StorageType = FileTypeFile;
|
|
}
|
|
RxFinishFcbInitialization(capFcb, (RX_FILE_TYPE)RDBSS_STORAGE_NTC(StorageType), &InitPacket);
|
|
}
|
|
|
|
NTSTATUS DrDevice::Create(IN OUT PRX_CONTEXT RxContext)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Opens a file (or device) across the network
|
|
|
|
Arguments:
|
|
|
|
RxContext - Context for the operation
|
|
|
|
Return Value:
|
|
|
|
Could return status success, cancelled, or pending.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
RxCaptureFcb;
|
|
PMRX_SRV_OPEN SrvOpen = RxContext->pRelevantSrvOpen;
|
|
PMRX_SRV_CALL SrvCall = RxContext->Create.pSrvCall;
|
|
PMRX_NET_ROOT NetRoot = RxContext->Create.pNetRoot;
|
|
SmartPtr<DrSession> Session = _Session;
|
|
PRDPDR_IOREQUEST_PACKET pIoPacket;
|
|
ULONG cbPacketSize;
|
|
PUNICODE_STRING FileName = GET_ALREADY_PREFIXED_NAME(SrvOpen, capFcb);
|
|
LARGE_INTEGER TimeOut;
|
|
|
|
BEGIN_FN("DrDevice::Create");
|
|
|
|
ASSERT(RxContext != NULL);
|
|
ASSERT(RxContext->MajorFunction == IRP_MJ_CREATE);
|
|
|
|
if (!Session->IsConnected()) {
|
|
TRC_ALT((TB, "Tried to open client device while not "
|
|
"connected. State: %ld", Session->GetState()));
|
|
return STATUS_DEVICE_NOT_CONNECTED;
|
|
}
|
|
|
|
//
|
|
// Security check the irp.
|
|
//
|
|
Status = VerifyCreateSecurity(RxContext, Session->GetSessionId());
|
|
|
|
if (NT_ERROR(Status)) {
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// We already have an exclusive lock on the fcb. Finish the create.
|
|
//
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
//
|
|
// JC: Worry about this when do buffering
|
|
//
|
|
SrvOpen->Flags |= SRVOPEN_FLAG_DONTUSE_WRITE_CACHING;
|
|
SrvOpen->Flags |= SRVOPEN_FLAG_DONTUSE_READ_CACHING;
|
|
|
|
RxContext->pFobx = RxCreateNetFobx(RxContext, RxContext->pRelevantSrvOpen);
|
|
|
|
if (RxContext->pFobx != NULL) {
|
|
// Fobx keeps a reference to the device so it won't go away
|
|
|
|
AddRef();
|
|
RxContext->pFobx->Context = (DrDevice *)this;
|
|
Status = STATUS_SUCCESS;
|
|
} else {
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
//
|
|
// Get the file name
|
|
//
|
|
// If the file name only has back slash at the end and rdbss didn't record it
|
|
// we need to pass this to the client
|
|
//
|
|
if (GetDeviceType() == RDPDR_DTYP_FILESYSTEM && FlagOn(RxContext->Create.Flags, RX_CONTEXT_CREATE_FLAG_STRIPPED_TRAILING_BACKSLASH) &&
|
|
FileName->Length == 0) {
|
|
FileName->Buffer = L"\\";
|
|
FileName->Length = FileName->MaximumLength = sizeof(WCHAR);
|
|
}
|
|
|
|
TRC_DBG((TB, "Attempt to open = %wZ", FileName));
|
|
|
|
//
|
|
// Build the create packet and send it to the client
|
|
// We add the string null terminator to the filename
|
|
//
|
|
if (FileName->Length) {
|
|
//
|
|
// FileName Length does not include string null terminator.
|
|
//
|
|
cbPacketSize = sizeof(RDPDR_IOREQUEST_PACKET) + FileName->Length + sizeof(WCHAR);
|
|
}
|
|
else {
|
|
cbPacketSize = sizeof(RDPDR_IOREQUEST_PACKET);
|
|
}
|
|
|
|
pIoPacket = (PRDPDR_IOREQUEST_PACKET)new(PagedPool) BYTE[cbPacketSize];
|
|
|
|
if (pIoPacket != NULL) {
|
|
memset(pIoPacket, 0, cbPacketSize);
|
|
|
|
pIoPacket->Header.Component = RDPDR_CTYP_CORE;
|
|
pIoPacket->Header.PacketId = DR_CORE_DEVICE_IOREQUEST;
|
|
pIoPacket->IoRequest.DeviceId = _DeviceId;
|
|
pIoPacket->IoRequest.MajorFunction = IRP_MJ_CREATE;
|
|
pIoPacket->IoRequest.MinorFunction = 0;
|
|
|
|
pIoPacket->IoRequest.Parameters.Create.DesiredAccess =
|
|
RxContext->Create.NtCreateParameters.DesiredAccess;
|
|
pIoPacket->IoRequest.Parameters.Create.AllocationSize =
|
|
RxContext->Create.NtCreateParameters.AllocationSize;
|
|
pIoPacket->IoRequest.Parameters.Create.FileAttributes =
|
|
RxContext->Create.NtCreateParameters.FileAttributes;
|
|
pIoPacket->IoRequest.Parameters.Create.ShareAccess =
|
|
RxContext->Create.NtCreateParameters.ShareAccess;
|
|
pIoPacket->IoRequest.Parameters.Create.Disposition =
|
|
RxContext->Create.NtCreateParameters.Disposition;
|
|
pIoPacket->IoRequest.Parameters.Create.CreateOptions =
|
|
RxContext->Create.NtCreateParameters.CreateOptions;
|
|
|
|
//
|
|
// File name path
|
|
//
|
|
if (FileName->Length) {
|
|
pIoPacket->IoRequest.Parameters.Create.PathLength = FileName->Length + sizeof(WCHAR);
|
|
RtlCopyMemory(pIoPacket + 1, FileName->Buffer, FileName->Length);
|
|
//
|
|
// Packet is already zero'd, so no need to null terminate the string
|
|
//
|
|
} else {
|
|
pIoPacket->IoRequest.Parameters.Create.PathLength = 0;
|
|
}
|
|
|
|
TRC_NRM((TB, "Sending Create IoRequest"));
|
|
TRC_NRM((TB, " DesiredAccess: %lx",
|
|
pIoPacket->IoRequest.Parameters.Create.DesiredAccess));
|
|
TRC_NRM((TB, " AllocationSize: %lx",
|
|
pIoPacket->IoRequest.Parameters.Create.AllocationSize));
|
|
TRC_NRM((TB, " FileAttributes: %lx",
|
|
pIoPacket->IoRequest.Parameters.Create.FileAttributes));
|
|
TRC_NRM((TB, " ShareAccess: %lx",
|
|
pIoPacket->IoRequest.Parameters.Create.ShareAccess));
|
|
TRC_NRM((TB, " Disposition: %lx",
|
|
pIoPacket->IoRequest.Parameters.Create.Disposition));
|
|
TRC_NRM((TB, " CreateOptions: %lx",
|
|
pIoPacket->IoRequest.Parameters.Create.CreateOptions));
|
|
|
|
//
|
|
// Always do create synchronously
|
|
// 30 seconds in hundreds of nano-seconds, in case client hangs,
|
|
// we don't want this create thread to wait infinitely.
|
|
//
|
|
TimeOut = RtlEnlargedIntegerMultiply( 300000, -1000 );
|
|
Status = SendIoRequest(RxContext, pIoPacket, cbPacketSize, TRUE, &TimeOut);
|
|
|
|
delete pIoPacket;
|
|
}
|
|
else {
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
FinishCreate(RxContext);
|
|
}
|
|
else {
|
|
// Release the Device Reference
|
|
if (RxContext->pFobx != NULL) {
|
|
((DrDevice *)RxContext->pFobx->Context)->Release();
|
|
RxContext->pFobx->Context = NULL;
|
|
|
|
}
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS DrDevice::Flush(IN OUT PRX_CONTEXT RxContext)
|
|
{
|
|
BEGIN_FN("DrDevice::Flush");
|
|
ASSERT(RxContext != NULL);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS DrDevice::Write(IN OUT PRX_CONTEXT RxContext, IN BOOL LowPrioSend)
|
|
{
|
|
NTSTATUS Status;
|
|
RxCaptureFcb;
|
|
RxCaptureFobx;
|
|
PMRX_NET_ROOT NetRoot = capFcb->pNetRoot;
|
|
PMRX_SRV_CALL SrvCall = NetRoot->pSrvCall;
|
|
SmartPtr<DrSession> Session = _Session;
|
|
DrFile *pFile = (DrFile *)RxContext->pFobx->Context2;
|
|
SmartPtr<DrFile> FileObj = pFile;
|
|
PRDPDR_IOREQUEST_PACKET pIoPacket;
|
|
ULONG cbPacketSize = sizeof(RDPDR_IOREQUEST_PACKET) +
|
|
RxContext->LowIoContext.ParamsFor.ReadWrite.ByteCount;
|
|
PVOID pv;
|
|
|
|
BEGIN_FN("DrDevice::Write");
|
|
|
|
//
|
|
// Make sure it's okay to access the Client at this time
|
|
// This is an optimization, we don't need to acquire the spin lock,
|
|
// because it is okay if we're not, we'll just catch it later
|
|
//
|
|
|
|
ASSERT(RxContext != NULL);
|
|
ASSERT(RxContext->MajorFunction == IRP_MJ_WRITE);
|
|
|
|
if (!Session->IsConnected()) {
|
|
return STATUS_DEVICE_NOT_CONNECTED;
|
|
}
|
|
|
|
if (FileObj == NULL) {
|
|
return STATUS_DEVICE_NOT_CONNECTED;
|
|
}
|
|
//
|
|
// Make sure the device is still enabled
|
|
//
|
|
|
|
if (_DeviceStatus != dsAvailable) {
|
|
TRC_ALT((TB, "Tried to write to client device which is not "
|
|
"available. State: %ld", _DeviceStatus));
|
|
return STATUS_DEVICE_NOT_CONNECTED;
|
|
}
|
|
|
|
if (RxContext->LowIoContext.ParamsFor.ReadWrite.ByteCount == 0) {
|
|
RxContext->IoStatusBlock.Information = 0;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
pIoPacket = (PRDPDR_IOREQUEST_PACKET)new(PagedPool) BYTE[cbPacketSize];
|
|
|
|
if (pIoPacket != NULL) {
|
|
|
|
memset(pIoPacket, 0, cbPacketSize);
|
|
|
|
pIoPacket->Header.Component = RDPDR_CTYP_CORE;
|
|
pIoPacket->Header.PacketId = DR_CORE_DEVICE_IOREQUEST;
|
|
pIoPacket->IoRequest.DeviceId = _DeviceId;
|
|
pIoPacket->IoRequest.FileId = FileObj->GetFileId();
|
|
pIoPacket->IoRequest.MajorFunction = IRP_MJ_WRITE;
|
|
pIoPacket->IoRequest.MinorFunction = 0;
|
|
pIoPacket->IoRequest.Parameters.Write.Length =
|
|
RxContext->LowIoContext.ParamsFor.ReadWrite.ByteCount;
|
|
//
|
|
// Get the low dword byte offset of where to write
|
|
//
|
|
pIoPacket->IoRequest.Parameters.Write.OffsetLow =
|
|
((LONG)((LONGLONG)(RxContext->LowIoContext.ParamsFor.ReadWrite.ByteOffset)
|
|
& 0xffffffff));
|
|
//
|
|
// Get the high dword by offset of where to write
|
|
//
|
|
pIoPacket->IoRequest.Parameters.Write.OffsetHigh =
|
|
((LONG)((LONGLONG)(RxContext->LowIoContext.ParamsFor.ReadWrite.ByteOffset)
|
|
>> 32));
|
|
|
|
TRC_DBG((TB, "ByteOffset to write = %x",
|
|
RxContext->LowIoContext.ParamsFor.ReadWrite.ByteOffset));
|
|
|
|
pv = MmGetSystemAddressForMdlSafe(RxContext->LowIoContext.ParamsFor.ReadWrite.Buffer,
|
|
NormalPagePriority);
|
|
|
|
if (pv != NULL) {
|
|
RtlCopyMemory(pIoPacket + 1, pv, // + RxContext->LowIoContext.ParamsFor.ReadWrite.ByteOffset?,
|
|
pIoPacket->IoRequest.Parameters.Write.Length);
|
|
|
|
TRC_DBG((TB, "Write packet length: 0x%lx",
|
|
pIoPacket->IoRequest.Parameters.Write.Length));
|
|
|
|
Status = SendIoRequest(RxContext, pIoPacket, cbPacketSize,
|
|
(BOOLEAN)!BooleanFlagOn(RxContext->Flags, RX_CONTEXT_FLAG_ASYNC_OPERATION),
|
|
NULL, LowPrioSend);
|
|
|
|
TRC_NRM((TB, "IoRequestWrite returned to DrWrite: %lx", Status));
|
|
}
|
|
else {
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
delete pIoPacket;
|
|
|
|
} else {
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS DrDevice::Read(IN OUT PRX_CONTEXT RxContext)
|
|
{
|
|
NTSTATUS Status = STATUS_NOT_IMPLEMENTED;
|
|
RxCaptureFcb;
|
|
RxCaptureFobx;
|
|
PMRX_NET_ROOT NetRoot = capFcb->pNetRoot;
|
|
PMRX_SRV_CALL SrvCall = NetRoot->pSrvCall;
|
|
SmartPtr<DrSession> Session = _Session;
|
|
DrFile *pFile = (DrFile *)RxContext->pFobx->Context2;
|
|
SmartPtr<DrFile> FileObj = pFile;
|
|
RDPDR_IOREQUEST_PACKET IoPacket;
|
|
|
|
BEGIN_FN("DrDevice::Read");
|
|
|
|
//
|
|
// Make sure it's okay to access the Client at this time
|
|
// This is an optimization, we don't need to acquire the spin lock,
|
|
// because it is okay if we're not, we'll just catch it later
|
|
//
|
|
|
|
ASSERT(Session != NULL);
|
|
ASSERT(RxContext != NULL);
|
|
ASSERT(RxContext->MajorFunction == IRP_MJ_READ);
|
|
|
|
if (!Session->IsConnected()) {
|
|
return STATUS_DEVICE_NOT_CONNECTED;
|
|
}
|
|
|
|
if (FileObj == NULL) {
|
|
return STATUS_DEVICE_NOT_CONNECTED;
|
|
}
|
|
|
|
//
|
|
// Make sure the device is still enabled
|
|
//
|
|
|
|
if (_DeviceStatus != dsAvailable) {
|
|
TRC_ALT((TB, "Tried to read from client device which is not "
|
|
"available. State: %ld", _DeviceStatus));
|
|
return STATUS_DEVICE_NOT_CONNECTED;
|
|
}
|
|
|
|
memset(&IoPacket, 0, sizeof(IoPacket));
|
|
|
|
IoPacket.Header.Component = RDPDR_CTYP_CORE;
|
|
IoPacket.Header.PacketId = DR_CORE_DEVICE_IOREQUEST;
|
|
IoPacket.IoRequest.DeviceId = _DeviceId;
|
|
IoPacket.IoRequest.FileId = FileObj->GetFileId();
|
|
IoPacket.IoRequest.MajorFunction = IRP_MJ_READ;
|
|
IoPacket.IoRequest.MinorFunction = 0;
|
|
IoPacket.IoRequest.Parameters.Read.Length =
|
|
RxContext->LowIoContext.ParamsFor.ReadWrite.ByteCount;
|
|
|
|
//
|
|
// Get low dword of read offset
|
|
//
|
|
IoPacket.IoRequest.Parameters.Read.OffsetLow =
|
|
((LONG)((LONGLONG)(RxContext->LowIoContext.ParamsFor.ReadWrite.ByteOffset)
|
|
& 0xffffffff));
|
|
//
|
|
// Get high dword of read offset
|
|
//
|
|
IoPacket.IoRequest.Parameters.Read.OffsetHigh =
|
|
((LONG)((LONGLONG)(RxContext->LowIoContext.ParamsFor.ReadWrite.ByteOffset)
|
|
>> 32));
|
|
|
|
TRC_NRM((TB, "DrRead reading length: %ld, at offset: %x",
|
|
IoPacket.IoRequest.Parameters.Read.Length,
|
|
RxContext->LowIoContext.ParamsFor.ReadWrite.ByteOffset));
|
|
|
|
Status = SendIoRequest(RxContext, &IoPacket, sizeof(IoPacket),
|
|
(BOOLEAN)!BooleanFlagOn(RxContext->Flags,RX_CONTEXT_FLAG_ASYNC_OPERATION));
|
|
|
|
TRC_NRM((TB, "IoRequestWrite returned to DrRead: %lx", Status));
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS DrDevice::IoControl(IN OUT PRX_CONTEXT RxContext)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
RxCaptureFcb;
|
|
RxCaptureFobx;
|
|
PMRX_NET_ROOT NetRoot = capFcb->pNetRoot;
|
|
PMRX_SRV_CALL SrvCall = NetRoot->pSrvCall;
|
|
SmartPtr<DrSession> Session = _Session;
|
|
DrFile *pFile = (DrFile *)RxContext->pFobx->Context2;
|
|
SmartPtr<DrFile> FileObj = pFile;
|
|
PRDPDR_IOREQUEST_PACKET pIoPacket = NULL;
|
|
PLOWIO_CONTEXT LowIoContext = &RxContext->LowIoContext;
|
|
ULONG cbPacketSize = sizeof(RDPDR_IOREQUEST_PACKET) +
|
|
LowIoContext->ParamsFor.IoCtl.InputBufferLength;
|
|
ULONG IoControlCode = LowIoContext->ParamsFor.IoCtl.IoControlCode;
|
|
ULONG InputBufferLength = LowIoContext->ParamsFor.IoCtl.InputBufferLength;
|
|
ULONG OutputBufferLength = LowIoContext->ParamsFor.IoCtl.OutputBufferLength;
|
|
PVOID InputBuffer = LowIoContext->ParamsFor.IoCtl.pInputBuffer;
|
|
PVOID OutputBuffer = LowIoContext->ParamsFor.IoCtl.pOutputBuffer;
|
|
|
|
BEGIN_FN("DrDevice::IoControl");
|
|
|
|
//
|
|
// Make sure it's okay to access the Client at this time
|
|
// This is an optimization, we don't need to acquire the spin lock,
|
|
// because it is okay if we're not, we'll just catch it later
|
|
//
|
|
|
|
ASSERT(Session != NULL);
|
|
ASSERT(RxContext != NULL);
|
|
ASSERT(RxContext->MajorFunction == IRP_MJ_DEVICE_CONTROL ||
|
|
RxContext->MajorFunction == IRP_MJ_INTERNAL_DEVICE_CONTROL ||
|
|
RxContext->MajorFunction == IRP_MJ_FILE_SYSTEM_CONTROL);
|
|
|
|
if (COMPARE_VERSION(Session->GetClientVersion().Minor,
|
|
Session->GetClientVersion().Major, 4, 1) < 0) {
|
|
TRC_ALT((TB, "Failing IoCtl for client that doesn't support it"));
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
if (!Session->IsConnected()) {
|
|
return STATUS_DEVICE_NOT_CONNECTED;
|
|
}
|
|
|
|
if (FileObj == NULL) {
|
|
return STATUS_DEVICE_NOT_CONNECTED;
|
|
}
|
|
|
|
//
|
|
// Make sure the device is still enabled
|
|
//
|
|
|
|
if (_DeviceStatus != dsAvailable) {
|
|
TRC_ALT((TB, "Tried to send IoControl to client device which is not "
|
|
"available. State: %ld", _DeviceStatus));
|
|
return STATUS_DEVICE_NOT_CONNECTED;
|
|
}
|
|
|
|
//
|
|
// Validate the buffer
|
|
//
|
|
|
|
__try {
|
|
if (RxContext->CurrentIrp->RequestorMode != KernelMode) {
|
|
// If the buffering method is METHOD_NEITHER or METHOD_IN_DIRECT
|
|
// then we need to probe the input buffer
|
|
if ((IoControlCode & 0x1) &&
|
|
InputBuffer != NULL && InputBufferLength != 0) {
|
|
ProbeForRead(InputBuffer, InputBufferLength, sizeof(UCHAR));
|
|
}
|
|
|
|
// If the buffering method is METHOD_NEITHER or METHOD_OUT_DIRECT
|
|
// then we need to probe the output buffer
|
|
if ((IoControlCode & 0x2) &&
|
|
OutputBuffer != NULL && OutputBufferLength != 0) {
|
|
ProbeForWrite(OutputBuffer, OutputBufferLength, sizeof(UCHAR));
|
|
}
|
|
}
|
|
|
|
pIoPacket = (PRDPDR_IOREQUEST_PACKET)new(PagedPool) BYTE[cbPacketSize];
|
|
|
|
if (pIoPacket != NULL) {
|
|
memset(pIoPacket, 0, cbPacketSize);
|
|
|
|
//
|
|
// FS Control uses the same path as IO Control.
|
|
//
|
|
pIoPacket->Header.Component = RDPDR_CTYP_CORE;
|
|
pIoPacket->Header.PacketId = DR_CORE_DEVICE_IOREQUEST;
|
|
pIoPacket->IoRequest.DeviceId = _DeviceId;
|
|
pIoPacket->IoRequest.FileId = FileObj->GetFileId();
|
|
pIoPacket->IoRequest.MajorFunction = IRP_MJ_DEVICE_CONTROL;
|
|
pIoPacket->IoRequest.MinorFunction =
|
|
LowIoContext->ParamsFor.IoCtl.MinorFunction;
|
|
pIoPacket->IoRequest.Parameters.DeviceIoControl.OutputBufferLength =
|
|
LowIoContext->ParamsFor.IoCtl.OutputBufferLength;
|
|
pIoPacket->IoRequest.Parameters.DeviceIoControl.InputBufferLength =
|
|
LowIoContext->ParamsFor.IoCtl.InputBufferLength;
|
|
pIoPacket->IoRequest.Parameters.DeviceIoControl.IoControlCode =
|
|
LowIoContext->ParamsFor.IoCtl.IoControlCode;
|
|
|
|
if (LowIoContext->ParamsFor.IoCtl.InputBufferLength != 0) {
|
|
|
|
TRC_NRM((TB, "DrIoControl inputbufferlength: %lx",
|
|
LowIoContext->ParamsFor.IoCtl.InputBufferLength));
|
|
|
|
RtlCopyMemory(pIoPacket + 1, LowIoContext->ParamsFor.IoCtl.pInputBuffer,
|
|
LowIoContext->ParamsFor.IoCtl.InputBufferLength);
|
|
} else {
|
|
TRC_NRM((TB, "DrIoControl with no inputbuffer"));
|
|
}
|
|
|
|
Status = SendIoRequest(RxContext, pIoPacket, cbPacketSize,
|
|
(BOOLEAN)!BooleanFlagOn(RxContext->Flags,RX_CONTEXT_FLAG_ASYNC_OPERATION));
|
|
TRC_NRM((TB, "IoRequestWrite returned to DrIoControl: %lx", Status));
|
|
delete pIoPacket;
|
|
} else {
|
|
TRC_ERR((TB, "DrIoControl unable to allocate packet: %lx", Status));
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
return Status;
|
|
|
|
}
|
|
__except (EXCEPTION_EXECUTE_HANDLER) {
|
|
TRC_ERR((TB, "Invalid buffer parameter(s)"));
|
|
|
|
if (pIoPacket) {
|
|
delete pIoPacket;
|
|
}
|
|
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
|
|
NTSTATUS DrDevice::Close(IN OUT PRX_CONTEXT RxContext)
|
|
{
|
|
NTSTATUS Status;
|
|
RxCaptureFcb;
|
|
RxCaptureFobx;
|
|
PMRX_NET_ROOT NetRoot = capFcb->pNetRoot;
|
|
PMRX_SRV_CALL SrvCall = NetRoot->pSrvCall;
|
|
SmartPtr<DrSession> Session = _Session;
|
|
DrFile *pFile = (DrFile *)RxContext->pFobx->Context2;
|
|
SmartPtr<DrFile> FileObj = pFile;
|
|
RDPDR_IOREQUEST_PACKET IoPacket;
|
|
|
|
BEGIN_FN("DrDevice::Close");
|
|
|
|
//
|
|
// Make sure it's okay to access the Client at this time
|
|
// This is an optimization, we don't need to acquire the spin lock,
|
|
// because it is okay if we're not, we'll just catch it later
|
|
//
|
|
|
|
ASSERT(Session != NULL);
|
|
ASSERT(RxContext != NULL);
|
|
ASSERT(RxContext->MajorFunction == IRP_MJ_CLOSE);
|
|
|
|
if (!Session->IsConnected()) {
|
|
// Review: Since we're not connected, there shouldn't be any reason
|
|
// to say it isn't closed, right?
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
if (FileObj == NULL) {
|
|
return STATUS_DEVICE_NOT_CONNECTED;
|
|
}
|
|
|
|
if (_DeviceStatus != dsAvailable) {
|
|
TRC_ALT((TB, "Tried to close a client device which is not "
|
|
"available. State: %ld", _DeviceStatus));
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
memset(&IoPacket, 0, sizeof(IoPacket));
|
|
|
|
IoPacket.Header.Component = RDPDR_CTYP_CORE;
|
|
IoPacket.Header.PacketId = DR_CORE_DEVICE_IOREQUEST;
|
|
IoPacket.IoRequest.DeviceId = _DeviceId;
|
|
IoPacket.IoRequest.FileId = FileObj->GetFileId();
|
|
IoPacket.IoRequest.MajorFunction = IRP_MJ_CLOSE;
|
|
IoPacket.IoRequest.MinorFunction = 0;
|
|
|
|
Status = SendIoRequest(RxContext, &IoPacket, sizeof(IoPacket),
|
|
(BOOLEAN)!BooleanFlagOn(RxContext->Flags,RX_CONTEXT_FLAG_ASYNC_OPERATION));
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS DrDevice::Cleanup(IN OUT PRX_CONTEXT RxContext)
|
|
{
|
|
NTSTATUS Status;
|
|
RxCaptureFcb;
|
|
RxCaptureFobx;
|
|
PMRX_NET_ROOT NetRoot = capFcb->pNetRoot;
|
|
PMRX_SRV_CALL SrvCall = NetRoot->pSrvCall;
|
|
SmartPtr<DrSession> Session = _Session;
|
|
DrFile *pFile = (DrFile *)RxContext->pFobx->Context2;
|
|
SmartPtr<DrFile> FileObj = pFile;
|
|
RDPDR_IOREQUEST_PACKET IoPacket;
|
|
|
|
BEGIN_FN("DrDevice::Cleanup");
|
|
|
|
//
|
|
// Make sure it's okay to access the Client at this time
|
|
// This is an optimization, we don't need to acquire the spin lock,
|
|
// because it is okay if we're not, we'll just catch it later
|
|
//
|
|
|
|
ASSERT(RxContext != NULL);
|
|
ASSERT(RxContext->MajorFunction == IRP_MJ_CLEANUP);
|
|
|
|
if (!Session->IsConnected()) {
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
if (FileObj == NULL) {
|
|
return STATUS_DEVICE_NOT_CONNECTED;
|
|
}
|
|
//
|
|
// Make sure the device is still enabled
|
|
//
|
|
|
|
if (_DeviceStatus != dsAvailable) {
|
|
TRC_ALT((TB, "Tried to cleanup a client device which is not "
|
|
"available. State: %ld", _DeviceStatus));
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
memset(&IoPacket, 0, sizeof(IoPacket));
|
|
|
|
IoPacket.Header.Component = RDPDR_CTYP_CORE;
|
|
IoPacket.Header.PacketId = DR_CORE_DEVICE_IOREQUEST;
|
|
IoPacket.IoRequest.DeviceId = _DeviceId;
|
|
IoPacket.IoRequest.FileId = FileObj->GetFileId();
|
|
IoPacket.IoRequest.MajorFunction = IRP_MJ_CLEANUP;
|
|
IoPacket.IoRequest.MinorFunction = 0;
|
|
|
|
Status = SendIoRequest(RxContext, &IoPacket, sizeof(IoPacket),
|
|
(BOOLEAN)!BooleanFlagOn(RxContext->Flags,RX_CONTEXT_FLAG_ASYNC_OPERATION));
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS DrDevice::SendIoRequest(IN OUT PRX_CONTEXT RxContext,
|
|
PRDPDR_IOREQUEST_PACKET IoRequest, ULONG Length,
|
|
BOOLEAN Synchronous, PLARGE_INTEGER TimeOut, BOOL LowPrioSend)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sends the request to the client, and manages the completion. This IO
|
|
can only be completed once, by returning non-STATUS_PENDING or by
|
|
calling RxLowIoCompletion.
|
|
|
|
Arguments:
|
|
|
|
RxContext - The IoRequest
|
|
IoRequest - The IoRequest packet
|
|
Length - size of IoRequest packet
|
|
Synchronous - duh
|
|
LowPrioSend - Packet should be sent to client at low priority.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
USHORT Mid = INVALID_MID;
|
|
BOOL ExchangeCreated = FALSE;
|
|
DrIoContext *Context = NULL;
|
|
SmartPtr<DrExchange> Exchange;
|
|
SmartPtr<DrDevice> Device(this);
|
|
|
|
BEGIN_FN("DrDevice::SendIoRequest");
|
|
|
|
ASSERT(RxContext != NULL);
|
|
ASSERT(IoRequest != NULL);
|
|
ASSERT(Length >= sizeof(RDPDR_IOREQUEST_PACKET));
|
|
|
|
Context = new DrIoContext(RxContext, Device);
|
|
|
|
if (Context != NULL) {
|
|
Status = STATUS_SUCCESS;
|
|
} else {
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
|
|
//
|
|
// Set up a mapping so the completion response handler can
|
|
// find this context
|
|
//
|
|
|
|
TRC_DBG((TB, "Create the context for this I/O"));
|
|
KeClearEvent(&RxContext->SyncEvent);
|
|
|
|
ExchangeCreated =
|
|
_Session->GetExchangeManager().CreateExchange(this, Context, Exchange);
|
|
|
|
if (ExchangeCreated) {
|
|
|
|
//
|
|
// No need to explicit Refcount for the RxContext
|
|
// The place it's been used is the cancel routine.
|
|
// Since CreateExchange holds the ref count. we are okay
|
|
//
|
|
|
|
//Exchange->AddRef();
|
|
RxContext->MRxContext[MRX_DR_CONTEXT] = (DrExchange *)Exchange;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
} else {
|
|
delete Context;
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
|
|
TRC_DBG((TB, "Writing IoRequest to the client channel"));
|
|
|
|
//
|
|
// Mark the IoRequest with the context mapper
|
|
//
|
|
|
|
IoRequest->IoRequest.CompletionId = Exchange->_Mid;
|
|
|
|
TRC_DBG((TB, "IO packet:"));
|
|
TRC_DBG((TB, " Component %c%c",
|
|
HIBYTE(IoRequest->Header.Component),
|
|
LOBYTE(IoRequest->Header.Component)));
|
|
TRC_DBG((TB, " PacketId %c%c",
|
|
HIBYTE(IoRequest->Header.PacketId),
|
|
LOBYTE(IoRequest->Header.PacketId)));
|
|
TRC_DBG((TB, " DeviceId 0x%lx",
|
|
IoRequest->IoRequest.DeviceId));
|
|
TRC_DBG((TB, " FileId 0x%lx",
|
|
IoRequest->IoRequest.FileId));
|
|
TRC_DBG((TB, " MajorFunction 0x%lx",
|
|
IoRequest->IoRequest.MajorFunction));
|
|
TRC_DBG((TB, " MinorFunction 0x%lx",
|
|
IoRequest->IoRequest.MinorFunction));
|
|
|
|
Status = _Session->GetExchangeManager().StartExchange(Exchange, this,
|
|
IoRequest, Length, LowPrioSend);
|
|
}
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
|
|
TRC_DBG((TB, "Setting cancel routine for Io"));
|
|
|
|
//
|
|
// Set this after sending the IO to the client
|
|
// if cancel was requested already, we can just call the
|
|
// cancel routine ourselves
|
|
//
|
|
|
|
Status = RxSetMinirdrCancelRoutine(RxContext,
|
|
MinirdrCancelRoutine);
|
|
|
|
if (Status == STATUS_CANCELLED) {
|
|
TRC_NRM((TB, "Io was already cancelled"));
|
|
|
|
MinirdrCancelRoutine(RxContext);
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
if (Synchronous) {
|
|
//
|
|
// Some failure is going to prevent our completions routine from
|
|
// being called. Do that work now.
|
|
//
|
|
if (!ExchangeCreated) {
|
|
//
|
|
// If we couldn't even create the exchange, we need to just
|
|
// complete the IO as failed
|
|
//
|
|
|
|
CompleteRxContext(RxContext, Status, 0);
|
|
}
|
|
else {
|
|
|
|
TRC_DBG((TB, "Waiting for IoResult for synchronous request"));
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
Status = KeWaitForSingleObject(&RxContext->SyncEvent, UserRequest,
|
|
KernelMode, FALSE, TimeOut);
|
|
|
|
if (Status == STATUS_TIMEOUT) {
|
|
RxContext->IoStatusBlock.Status = Status;
|
|
|
|
TRC_DBG((TB, "Wait timed out"));
|
|
MarkTimedOut(Exchange);
|
|
}
|
|
else {
|
|
Status = RxContext->IoStatusBlock.Status;
|
|
}
|
|
}
|
|
else {
|
|
|
|
//
|
|
// If we created the exchange and then got a transport failure
|
|
// we'll be disconnected, and the the I/O will be completed
|
|
// the same way all outstanding I/O is completed when we are
|
|
// disconnected.
|
|
//
|
|
if (MarkTimedOut(Exchange)) {
|
|
CompleteRxContext(RxContext, Status, 0);
|
|
}
|
|
else {
|
|
Status = KeWaitForSingleObject(&RxContext->SyncEvent, UserRequest,
|
|
KernelMode, FALSE, NULL);
|
|
Status = RxContext->IoStatusBlock.Status;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
TRC_DBG((TB, "Not waiting for IoResult for asynchronous request"));
|
|
|
|
//
|
|
// Some failure is going to prevent our completions routine from
|
|
// being called. Do that work now.
|
|
//
|
|
if (!ExchangeCreated) {
|
|
//
|
|
// If we couldn't even create the exchange, we need to just
|
|
// complete the IO as failed
|
|
//
|
|
|
|
CompleteRxContext(RxContext, Status, 0);
|
|
}
|
|
else {
|
|
//
|
|
// If we created the exchange and then got a transport failure
|
|
// we'll be disconnected, and the the I/O will be completed
|
|
// the same way all outstanding I/O is completed when we are
|
|
// disconnected.
|
|
//
|
|
}
|
|
|
|
Status = STATUS_PENDING;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
VOID DrDevice::CompleteBusyExchange(SmartPtr<DrExchange> &Exchange,
|
|
NTSTATUS Status, ULONG Information)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Takes an exchange which is already busy and
|
|
|
|
Arguments:
|
|
Mid - Id to find
|
|
ExchangeFound - Pointer to a storage for the pointer to the context
|
|
|
|
Return Value:
|
|
drexchBusy - Exchange provided, was marked busy
|
|
drexchCancelled - Exchange provided, was already cancelled
|
|
drexchUnavailable - Exchange not provided, disconnected
|
|
|
|
--*/
|
|
{
|
|
DrIoContext *Context;
|
|
PRX_CONTEXT RxContext;
|
|
|
|
BEGIN_FN("DrDevice::CompleteBusyExchange");
|
|
|
|
DrAcquireMutex();
|
|
Context = (DrIoContext *)Exchange->_Context;
|
|
ASSERT(Context != NULL);
|
|
ASSERT(Context->_Busy);
|
|
|
|
RxContext = Context->_RxContext;
|
|
Context->_RxContext = NULL;
|
|
Exchange->_Context = NULL;
|
|
DrReleaseMutex();
|
|
|
|
//
|
|
// Note: We've left the Mutex, and the Exchange with no
|
|
// context still exists and can be looked up until we Discard it.
|
|
//
|
|
|
|
if (RxContext != NULL) {
|
|
CompleteRxContext(RxContext, Status, Information);
|
|
}
|
|
_Session->GetExchangeManager().Discard(Exchange);
|
|
|
|
delete Context;
|
|
}
|
|
|
|
VOID DrDevice::DiscardBusyExchange(SmartPtr<DrExchange> &Exchange)
|
|
{
|
|
DrIoContext *Context;
|
|
|
|
BEGIN_FN("DrDevice::DiscardBusyExchange");
|
|
|
|
DrAcquireMutex();
|
|
Context = (DrIoContext *)Exchange->_Context;
|
|
ASSERT(Context != NULL);
|
|
ASSERT(Context->_Busy);
|
|
ASSERT(Context->_RxContext == NULL);
|
|
Exchange->_Context = NULL;
|
|
DrReleaseMutex();
|
|
|
|
//
|
|
// Note: We've left the Mutex, and the Exchange with no
|
|
// context still exists and can be looked up until we Discard it.
|
|
//
|
|
|
|
_Session->GetExchangeManager().Discard(Exchange);
|
|
|
|
delete Context;
|
|
}
|
|
|
|
BOOL DrDevice::MarkBusy(SmartPtr<DrExchange> &Exchange)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Marks an Exchange context as busy so it won't be cancelled
|
|
while we're copying in to its buffer
|
|
|
|
Arguments:
|
|
Exchange - Context
|
|
|
|
Return Value:
|
|
TRUE - if Marked Busy
|
|
FALSE - if Context was gone
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
BOOL rc;
|
|
DrIoContext *Context = NULL;
|
|
|
|
BEGIN_FN("DrDevice::MarkBusy");
|
|
ASSERT(Exchange != NULL);
|
|
|
|
DrAcquireMutex();
|
|
|
|
Context = (DrIoContext *)Exchange->_Context;
|
|
if (Context != NULL) {
|
|
ASSERT(!Context->_Busy);
|
|
Context->_Busy = TRUE;
|
|
rc = TRUE;
|
|
} else {
|
|
rc = FALSE;
|
|
}
|
|
DrReleaseMutex();
|
|
|
|
return rc;
|
|
}
|
|
|
|
VOID DrDevice::MarkIdle(SmartPtr<DrExchange> &Exchange)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Marks an Exchange context as idle. If it was cancelled
|
|
while we're copying in to its buffer, do the cancel now
|
|
|
|
Arguments:
|
|
The busy exchange
|
|
|
|
Return Value:
|
|
None
|
|
|
|
--*/
|
|
{
|
|
PRX_CONTEXT RxContext = NULL;
|
|
DrIoContext *Context = NULL;
|
|
|
|
BEGIN_FN("DrDevice::MarkIdle");
|
|
|
|
ASSERT(Exchange != NULL);
|
|
DrAcquireMutex();
|
|
Context = (DrIoContext *)Exchange->_Context;
|
|
TRC_ASSERT(Context != NULL, (TB, "Not allowed to delete context while "
|
|
"it is busy"));
|
|
ASSERT(Context->_Busy);
|
|
|
|
Context->_Busy = FALSE;
|
|
|
|
if (Context->_Cancelled && Context->_RxContext != NULL) {
|
|
TRC_DBG((TB, "Context was cancelled while busy, "
|
|
"completing"));
|
|
|
|
//
|
|
// If we were cancelled while busy, we do the work now,
|
|
// swap out the RxContext safely while in the Mutex and
|
|
// actually cancel it right after. Also set the state to
|
|
// indicate the cancelling work has been done
|
|
//
|
|
|
|
RxContext = Context->_RxContext;
|
|
TRC_ASSERT(RxContext != NULL, (TB, "Cancelled RxContext was NULL "
|
|
"going from busy to Idle"));
|
|
Context->_RxContext = NULL;
|
|
RxContext->MRxContext[MRX_DR_CONTEXT] = NULL;
|
|
}
|
|
|
|
if (Context->_Disconnected) {
|
|
|
|
//
|
|
// If the connection dropped while busy, clear that out
|
|
// in the Mutex for safety, and then delete it outside
|
|
//
|
|
|
|
Exchange->_Context = NULL;
|
|
}
|
|
|
|
DrReleaseMutex();
|
|
|
|
if (RxContext != NULL) {
|
|
|
|
//
|
|
// We only remove the RxContext because marking Idle means
|
|
// we expect to come back and look for it again later after we
|
|
// receive more data
|
|
//
|
|
|
|
CompleteRxContext(RxContext, STATUS_CANCELLED, 0);
|
|
|
|
if (Context->_Disconnected) {
|
|
|
|
//
|
|
// We got disconnected while busy, and will get no further
|
|
// notifications from the Exachnge manager. The Context must
|
|
// be deleted now
|
|
//
|
|
|
|
delete Context;
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL DrDevice::MarkTimedOut(SmartPtr<DrExchange> &Exchange)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Marks an Exchange context as timed out so it won't be processd
|
|
when the client later returns.
|
|
|
|
Arguments:
|
|
Exchange - Context
|
|
|
|
Return Value:
|
|
TRUE - if Marked TimedOut
|
|
FALSE - if Context was gone
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
BOOL rc;
|
|
DrIoContext *Context = NULL;
|
|
PRX_CONTEXT RxContext = NULL;
|
|
|
|
BEGIN_FN("DrDevice::MarkTimedOut");
|
|
ASSERT(Exchange != NULL);
|
|
|
|
DrAcquireMutex();
|
|
|
|
Context = (DrIoContext *)Exchange->_Context;
|
|
if (Context != NULL) {
|
|
ASSERT(!Context->_TimedOut);
|
|
Context->_TimedOut = TRUE;
|
|
|
|
if (Context->_RxContext != NULL) {
|
|
RxContext = Context->_RxContext;
|
|
Context->_RxContext = NULL;
|
|
RxContext->MRxContext[MRX_DR_CONTEXT] = NULL;
|
|
rc = TRUE;
|
|
}
|
|
else {
|
|
rc = FALSE;
|
|
}
|
|
} else {
|
|
rc = FALSE;
|
|
}
|
|
|
|
DrReleaseMutex();
|
|
|
|
return rc;
|
|
}
|
|
|
|
VOID DrDevice::CompleteRxContext(PRX_CONTEXT RxContext, NTSTATUS Status,
|
|
ULONG Information)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Completes the Io from the RDBSS perspective with the supplied information
|
|
|
|
Arguments:
|
|
RxContext - IFS kit context
|
|
Status - Completion status
|
|
Information - Completion information
|
|
|
|
Return Value:
|
|
None
|
|
|
|
--*/
|
|
{
|
|
BEGIN_FN_STATIC("DrDevice::CompleteRxContext");
|
|
ASSERT(RxContext != NULL);
|
|
|
|
RxContext->IoStatusBlock.Status = Status;
|
|
RxContext->IoStatusBlock.Information = Information;
|
|
|
|
if (((RxContext->LowIoContext.Flags & LOWIO_CONTEXT_FLAG_SYNCCALL) != 0) ||
|
|
(RxContext->MajorFunction == IRP_MJ_CREATE)) {
|
|
TRC_DBG((TB, "Setting event for synchronous Io"));
|
|
KeSetEvent(&RxContext->SyncEvent, 0, FALSE);
|
|
} else {
|
|
TRC_DBG((TB, "Calling RxLowIoCompletion for asynchronous Io"));
|
|
RxLowIoCompletion(RxContext);
|
|
}
|
|
}
|
|
|
|
NTSTATUS DrDevice::OnDeviceIoCompletion(
|
|
PRDPDR_IOCOMPLETION_PACKET CompletionPacket, ULONG cbPacket,
|
|
BOOL *DoDefaultRead, SmartPtr<DrExchange> &Exchange)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Callback from the Exchange manager to process an Io
|
|
|
|
Arguments:
|
|
CompletionPacket - The packet containing the completion
|
|
cbPacket - count of bytes in the packet
|
|
DoDefaultRead - Should be set to TRUE if read isn't explicitly called
|
|
Exchange - Context for the Io
|
|
|
|
Return Value:
|
|
NTSTATUS code. An error indicates a protocol error or need to disconnect
|
|
the client
|
|
|
|
--*/
|
|
{
|
|
DrIoContext *Context = NULL;
|
|
NTSTATUS Status;
|
|
PRX_CONTEXT RxContext;
|
|
|
|
BEGIN_FN("DrDevice::OnDeviceIoCompletion");
|
|
|
|
ASSERT(CompletionPacket != NULL);
|
|
ASSERT(DoDefaultRead != NULL);
|
|
|
|
if (MarkBusy(Exchange)) {
|
|
Context = (DrIoContext *)Exchange->_Context;
|
|
ASSERT(Context != NULL);
|
|
|
|
TRC_NRM((TB, "Client completed %s irp, Completion Status: %lx",
|
|
IrpNames[Context->_MajorFunction],
|
|
CompletionPacket->IoCompletion.IoStatus));
|
|
|
|
//
|
|
// If the IRP was timed out, then we just discard this exchange
|
|
//
|
|
if (Context->_TimedOut) {
|
|
TRC_NRM((TB, "Irp was timed out"));
|
|
DiscardBusyExchange(Exchange);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
switch (Context->_MajorFunction) {
|
|
case IRP_MJ_CREATE:
|
|
Status = OnCreateCompletion(CompletionPacket, cbPacket,
|
|
DoDefaultRead, Exchange);
|
|
break;
|
|
|
|
case IRP_MJ_WRITE:
|
|
Status = OnWriteCompletion(CompletionPacket, cbPacket,
|
|
DoDefaultRead, Exchange);
|
|
break;
|
|
|
|
case IRP_MJ_READ:
|
|
Status = OnReadCompletion(CompletionPacket, cbPacket,
|
|
DoDefaultRead, Exchange);
|
|
break;
|
|
|
|
case IRP_MJ_DEVICE_CONTROL:
|
|
case IRP_MJ_FILE_SYSTEM_CONTROL:
|
|
Status = OnDeviceControlCompletion(CompletionPacket, cbPacket,
|
|
DoDefaultRead, Exchange);
|
|
break;
|
|
|
|
case IRP_MJ_LOCK_CONTROL:
|
|
Status = OnLocksCompletion(CompletionPacket, cbPacket,
|
|
DoDefaultRead, Exchange);
|
|
break;
|
|
|
|
case IRP_MJ_DIRECTORY_CONTROL:
|
|
Status = OnDirectoryControlCompletion(CompletionPacket, cbPacket,
|
|
DoDefaultRead, Exchange);
|
|
break;
|
|
|
|
case IRP_MJ_QUERY_VOLUME_INFORMATION:
|
|
Status = OnQueryVolumeInfoCompletion(CompletionPacket, cbPacket,
|
|
DoDefaultRead, Exchange);
|
|
break;
|
|
|
|
case IRP_MJ_SET_VOLUME_INFORMATION:
|
|
Status = OnSetVolumeInfoCompletion(CompletionPacket, cbPacket,
|
|
DoDefaultRead, Exchange);
|
|
break;
|
|
|
|
case IRP_MJ_QUERY_INFORMATION:
|
|
Status = OnQueryFileInfoCompletion(CompletionPacket, cbPacket,
|
|
DoDefaultRead, Exchange);
|
|
break;
|
|
|
|
case IRP_MJ_SET_INFORMATION:
|
|
Status = OnSetFileInfoCompletion(CompletionPacket, cbPacket,
|
|
DoDefaultRead, Exchange);
|
|
break;
|
|
|
|
case IRP_MJ_QUERY_SECURITY:
|
|
Status = OnQuerySdInfoCompletion(CompletionPacket, cbPacket,
|
|
DoDefaultRead, Exchange);
|
|
break;
|
|
|
|
case IRP_MJ_SET_SECURITY:
|
|
Status = OnSetSdInfoCompletion(CompletionPacket, cbPacket,
|
|
DoDefaultRead, Exchange);
|
|
break;
|
|
|
|
case IRP_MJ_CLOSE:
|
|
NotifyClose();
|
|
// no break;
|
|
|
|
default:
|
|
|
|
RxContext = Context->_RxContext;
|
|
if (RxContext != NULL) {
|
|
TRC_NRM((TB, "Irp: %s, Completion Status: %lx",
|
|
IrpNames[RxContext->MajorFunction],
|
|
RxContext->IoStatusBlock.Status));
|
|
|
|
CompleteBusyExchange(Exchange, CompletionPacket->IoCompletion.IoStatus, 0);
|
|
} else {
|
|
TRC_NRM((TB, "Irp was cancelled"));
|
|
DiscardBusyExchange(Exchange);
|
|
}
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// We could have been disconnected between getting the callback and
|
|
// trying to mark it busy. So the only legitimate way for this to
|
|
// happen is if we were disconnected anyway.
|
|
//
|
|
|
|
TRC_ALT((TB, "Found no context in Io notification"));
|
|
*DoDefaultRead = FALSE;
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS DrDevice::OnCreateCompletion(PRDPDR_IOCOMPLETION_PACKET CompletionPacket, ULONG cbPacket,
|
|
BOOL *DoDefaultRead, SmartPtr<DrExchange> Exchange)
|
|
{
|
|
DrIoContext *Context = (DrIoContext *)Exchange->_Context;
|
|
PRX_CONTEXT RxContext;
|
|
SmartPtr<DrDevice> Device;
|
|
SmartPtr<DrFile> FileObj;
|
|
|
|
BEGIN_FN("DrDevice::OnCreateCompletion");
|
|
RxContext = Context->_RxContext;
|
|
|
|
if (cbPacket < (ULONG)FIELD_OFFSET(RDPDR_IOCOMPLETION_PACKET,
|
|
IoCompletion.Parameters.Create.Information)) {
|
|
|
|
//
|
|
// Bad packet. Bad. We've already claimed the RxContext in the
|
|
// atlas. Complete it as unsuccessful. Then shutdown the channel
|
|
// as this is a Bad Client.
|
|
//
|
|
TRC_ERR((TB, "Detected bad client CreateCompletion packet"));
|
|
|
|
if (RxContext != NULL) {
|
|
CompleteBusyExchange(Exchange, STATUS_DEVICE_PROTOCOL_ERROR, 0);
|
|
} else {
|
|
DiscardBusyExchange(Exchange);
|
|
}
|
|
|
|
//
|
|
// No point in starting a default read or anything, what with the
|
|
// channel being shut down and all.
|
|
//
|
|
|
|
*DoDefaultRead = FALSE;
|
|
return STATUS_DEVICE_PROTOCOL_ERROR;
|
|
}
|
|
|
|
if (RxContext != NULL) {
|
|
|
|
DrAcquireSpinLock();
|
|
Device = (DrDevice *)RxContext->Create.pVNetRoot->Context;
|
|
DrReleaseSpinLock();
|
|
|
|
ASSERT(Device != NULL);
|
|
|
|
//
|
|
// We are using a file object to keep track of file open instance
|
|
// and any information stored in the mini-redir for this instance
|
|
//
|
|
|
|
FileObj = new(NonPagedPool) DrFile(Device,
|
|
CompletionPacket->IoCompletion.Parameters.Create.FileId);
|
|
|
|
if (FileObj) {
|
|
//
|
|
// Explicit reference the file object here
|
|
//
|
|
FileObj->AddRef();
|
|
RxContext->pFobx->Context2 = (VOID *)(FileObj);
|
|
|
|
TRC_NRM((TB, "CreateCompletion: status =%d, information=%d",
|
|
CompletionPacket->IoCompletion.IoStatus,
|
|
CompletionPacket->IoCompletion.Parameters.Create.Information));
|
|
|
|
if (cbPacket >= sizeof(RDPDR_IOCOMPLETION_PACKET)) {
|
|
RxContext->Create.ReturnedCreateInformation =
|
|
CompletionPacket->IoCompletion.Parameters.Create.Information;
|
|
|
|
CompleteBusyExchange(Exchange, CompletionPacket->IoCompletion.IoStatus,
|
|
CompletionPacket->IoCompletion.Parameters.Create.Information);
|
|
}
|
|
else {
|
|
// For printer creat completion packet, the cbPacket is less than
|
|
// sizeof(RDPDR_IOCOMPLETION_PACKET). We don't want to access information beyond its length
|
|
|
|
RxContext->Create.ReturnedCreateInformation = 0;
|
|
|
|
CompleteBusyExchange(Exchange, CompletionPacket->IoCompletion.IoStatus, 0);
|
|
}
|
|
}
|
|
else {
|
|
CompleteBusyExchange(Exchange, STATUS_INSUFFICIENT_RESOURCES, 0);
|
|
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// Was cancelled but Context wasn't cleaned up
|
|
//
|
|
|
|
DiscardBusyExchange(Exchange);
|
|
}
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS DrDevice::OnWriteCompletion(PRDPDR_IOCOMPLETION_PACKET CompletionPacket, ULONG cbPacket,
|
|
BOOL *DoDefaultRead, SmartPtr<DrExchange> Exchange)
|
|
{
|
|
PRX_CONTEXT RxContext;
|
|
DrIoContext *Context = (DrIoContext *)Exchange->_Context;
|
|
|
|
BEGIN_FN("DrDevice::OnWriteCompletion");
|
|
|
|
RxContext = Context->_RxContext;
|
|
|
|
if (cbPacket < sizeof(RDPDR_IOCOMPLETION_PACKET)) {
|
|
|
|
//
|
|
// Bad packet. Bad. We've already claimed the RxContext in the
|
|
// atlas. Complete it as unsuccessful. Then shutdown the channel
|
|
// as this is a Bad Client.
|
|
//
|
|
TRC_ERR((TB, "Detected bad client WriteCompletion packet"));
|
|
|
|
if (RxContext != NULL) {
|
|
CompleteBusyExchange(Exchange, STATUS_DEVICE_PROTOCOL_ERROR, 0);
|
|
} else {
|
|
DiscardBusyExchange(Exchange);
|
|
}
|
|
|
|
//
|
|
// No point in starting a default read or anything, what with the
|
|
// channel being shut down and all.
|
|
//
|
|
|
|
*DoDefaultRead = FALSE;
|
|
return STATUS_DEVICE_PROTOCOL_ERROR;
|
|
}
|
|
|
|
if (RxContext != NULL) {
|
|
ASSERT(RxContext->MajorFunction == IRP_MJ_WRITE);
|
|
|
|
TRC_NRM((TB, "Irp: %s, Completion Status: %lx",
|
|
IrpNames[RxContext->MajorFunction],
|
|
RxContext->IoStatusBlock.Status));
|
|
|
|
CompleteBusyExchange(Exchange, CompletionPacket->IoCompletion.IoStatus,
|
|
CompletionPacket->IoCompletion.Parameters.Write.Length);
|
|
} else {
|
|
|
|
//
|
|
// Was cancelled but Context wasn't cleaned up
|
|
//
|
|
|
|
DiscardBusyExchange(Exchange);
|
|
}
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS DrDevice::OnReadCompletion(PRDPDR_IOCOMPLETION_PACKET CompletionPacket, ULONG cbPacket,
|
|
BOOL *DoDefaultRead, SmartPtr<DrExchange> Exchange)
|
|
{
|
|
PRX_CONTEXT RxContext;
|
|
PVOID pData = CompletionPacket->IoCompletion.Parameters.Read.Buffer;
|
|
ULONG cbWantData; // Amount of actual Read data in this packet
|
|
ULONG cbHaveData; // Amount of data available so far
|
|
DrIoContext *Context = (DrIoContext *)Exchange->_Context;
|
|
NTSTATUS Status;
|
|
PVOID pv;
|
|
|
|
BEGIN_FN("DrDevice::OnReadCompletion");
|
|
|
|
//
|
|
// Even if the IO was cancelled we need to correctly parse
|
|
// this data.
|
|
//
|
|
// Check to make sure this is up to size before accessing
|
|
// further portions of the packet
|
|
//
|
|
|
|
RxContext = Context->_RxContext;
|
|
|
|
if (cbPacket < (ULONG)FIELD_OFFSET(RDPDR_IOCOMPLETION_PACKET,
|
|
IoCompletion.Parameters.Read.Buffer)) {
|
|
|
|
//
|
|
// Bad packet. Bad. We've already claimed the RxContext in the
|
|
// atlas. Complete it as unsuccessful. Then shutdown the channel
|
|
// as this is a Bad Client.
|
|
//
|
|
|
|
TRC_ERR((TB, "Detected bad client read packet"));
|
|
|
|
if (RxContext != NULL) {
|
|
CompleteBusyExchange(Exchange, STATUS_DEVICE_PROTOCOL_ERROR, 0);
|
|
} else {
|
|
DiscardBusyExchange(Exchange);
|
|
}
|
|
|
|
//
|
|
// No point in starting a default read or anything, what with the
|
|
// channel being shut down and all.
|
|
//
|
|
|
|
*DoDefaultRead = FALSE;
|
|
return STATUS_DEVICE_PROTOCOL_ERROR;
|
|
}
|
|
|
|
//
|
|
// Calculate how much data is available immediately and how much data
|
|
// is coming
|
|
//
|
|
|
|
if (NT_SUCCESS(CompletionPacket->IoCompletion.IoStatus)) {
|
|
|
|
//
|
|
// Successful IO at the client end
|
|
//
|
|
|
|
TRC_DBG((TB, "Successful Read at the client end"));
|
|
TRC_DBG((TB, "Read Length: 0x%d, DataCopied 0x%d",
|
|
CompletionPacket->IoCompletion.Parameters.Read.Length,
|
|
Context->_DataCopied));
|
|
cbWantData = CompletionPacket->IoCompletion.Parameters.Read.Length -
|
|
Context->_DataCopied;
|
|
cbHaveData = cbPacket - (ULONG)FIELD_OFFSET(RDPDR_IOCOMPLETION_PACKET,
|
|
IoCompletion.Parameters.Read.Buffer);
|
|
|
|
if (cbHaveData > cbWantData) {
|
|
//
|
|
// Sounds like a bad client to me
|
|
//
|
|
|
|
TRC_ERR((TB, "Read returned more data than "
|
|
"advertised cbHaveData 0x%d cbWantData 0x%d",
|
|
cbHaveData, cbWantData));
|
|
|
|
if (RxContext != NULL) {
|
|
CompleteBusyExchange(Exchange, STATUS_DEVICE_PROTOCOL_ERROR, 0);
|
|
} else {
|
|
DiscardBusyExchange(Exchange);
|
|
}
|
|
|
|
*DoDefaultRead = FALSE;
|
|
return STATUS_DEVICE_PROTOCOL_ERROR;
|
|
}
|
|
|
|
if (RxContext != NULL) { // And not drexchCancelled
|
|
|
|
TRC_DBG((TB, "Copying data for Read"));
|
|
ASSERT(RxContext != NULL);
|
|
|
|
if (cbWantData > RxContext->LowIoContext.ParamsFor.ReadWrite.ByteCount) {
|
|
|
|
TRC_ERR((TB, "Read returned more data than "
|
|
"requested"));
|
|
|
|
CompleteBusyExchange(Exchange, STATUS_DEVICE_PROTOCOL_ERROR, 0);
|
|
*DoDefaultRead = FALSE;
|
|
return STATUS_DEVICE_PROTOCOL_ERROR;
|
|
}
|
|
|
|
//
|
|
// Copy the actual size of the read, and check to see if we have all
|
|
// the data. The information field tells us what to expect.
|
|
//
|
|
|
|
RxContext->IoStatusBlock.Information =
|
|
CompletionPacket->IoCompletion.Parameters.Read.Length;
|
|
|
|
if (RxContext->IoStatusBlock.Information && cbHaveData) {
|
|
|
|
pv = MmGetSystemAddressForMdl(RxContext->LowIoContext.ParamsFor.ReadWrite.Buffer);
|
|
|
|
RtlCopyMemory(((BYTE *)pv) + Context->_DataCopied, pData, cbHaveData);
|
|
|
|
//
|
|
// Keep track of how much data we've copied in case this is a
|
|
// multi chunk completion
|
|
//
|
|
|
|
Context->_DataCopied += cbHaveData;
|
|
}
|
|
}
|
|
|
|
if (cbHaveData == cbWantData) {
|
|
|
|
//
|
|
// There is exactly as much data as we need to satisfy the read,
|
|
// I like it.
|
|
//
|
|
|
|
if (RxContext != NULL) {
|
|
CompleteBusyExchange(Exchange,
|
|
CompletionPacket->IoCompletion.IoStatus,
|
|
CompletionPacket->IoCompletion.Parameters.Read.Length);
|
|
} else {
|
|
DiscardBusyExchange(Exchange);
|
|
}
|
|
|
|
//
|
|
// Go with a default channel read now
|
|
//
|
|
|
|
*DoDefaultRead = TRUE;
|
|
return STATUS_SUCCESS;
|
|
} else {
|
|
|
|
//
|
|
// We don't have all the data yet, release the DrExchange and
|
|
// read more data
|
|
//
|
|
|
|
MarkIdle(Exchange);
|
|
|
|
_Session->GetExchangeManager().ReadMore(
|
|
(ULONG)FIELD_OFFSET(RDPDR_IOCOMPLETION_PACKET,
|
|
IoCompletion.Parameters.Read.Buffer));
|
|
|
|
*DoDefaultRead = FALSE;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// Unsuccessful IO at the client end
|
|
//
|
|
|
|
TRC_DBG((TB, "Unsuccessful Read at the client end"));
|
|
if (cbPacket >= FIELD_OFFSET(RDPDR_IOCOMPLETION_PACKET,
|
|
IoCompletion.Parameters.Read.Buffer)) {
|
|
if (RxContext != NULL) {
|
|
CompleteBusyExchange(Exchange,
|
|
CompletionPacket->IoCompletion.IoStatus,
|
|
0);
|
|
}
|
|
else {
|
|
DiscardBusyExchange(Exchange);
|
|
}
|
|
*DoDefaultRead = TRUE;
|
|
return STATUS_SUCCESS;
|
|
} else {
|
|
TRC_ERR((TB, "Read returned invalid data"));
|
|
|
|
if (RxContext != NULL) {
|
|
CompleteBusyExchange(Exchange, STATUS_DEVICE_PROTOCOL_ERROR, 0);
|
|
} else {
|
|
DiscardBusyExchange(Exchange);
|
|
}
|
|
|
|
*DoDefaultRead = FALSE;
|
|
return STATUS_DEVICE_PROTOCOL_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
NTSTATUS DrDevice::OnDeviceControlCompletion(PRDPDR_IOCOMPLETION_PACKET CompletionPacket, ULONG cbPacket,
|
|
BOOL *DoDefaultRead, SmartPtr<DrExchange> Exchange)
|
|
{
|
|
PRX_CONTEXT RxContext;
|
|
DrIoContext *Context = (DrIoContext *)Exchange->_Context;
|
|
PVOID pData = CompletionPacket->IoCompletion.Parameters.DeviceIoControl.OutputBuffer;
|
|
ULONG cbWantData; // Amount of actual Read data in this packet
|
|
ULONG cbHaveData; // Amount of data available so far
|
|
NTSTATUS Status;
|
|
PVOID pv;
|
|
|
|
BEGIN_FN("DrDevice::OnDeviceControlCompletion");
|
|
|
|
//
|
|
// Even if the IO was cancelled we need to correctly parse
|
|
// this data.
|
|
//
|
|
// Check to make sure this is up to size before accessing
|
|
// further portions of the packet
|
|
//
|
|
|
|
RxContext = Context->_RxContext;
|
|
|
|
if (cbPacket < (ULONG)FIELD_OFFSET(RDPDR_IOCOMPLETION_PACKET,
|
|
IoCompletion.Parameters.DeviceIoControl.OutputBuffer)) {
|
|
|
|
//
|
|
// Bad packet. Bad. We've already claimed the RxContext in the
|
|
// atlas. Complete it as unsuccessful. Then shutdown the channel
|
|
// as this is a Bad Client.
|
|
//
|
|
|
|
TRC_ERR((TB, "Detected bad client DeviceControl packet"));
|
|
|
|
if (RxContext != NULL) {
|
|
CompleteBusyExchange(Exchange, STATUS_DEVICE_PROTOCOL_ERROR, 0);
|
|
} else {
|
|
DiscardBusyExchange(Exchange);
|
|
}
|
|
|
|
//
|
|
// No point in starting a default read or anything, what with the
|
|
// channel being shut down and all.
|
|
//
|
|
|
|
*DoDefaultRead = FALSE;
|
|
return STATUS_DEVICE_PROTOCOL_ERROR;
|
|
}
|
|
|
|
//
|
|
// Calculate how much data is available immediately and how much data
|
|
// is coming
|
|
//
|
|
|
|
if (NT_SUCCESS(CompletionPacket->IoCompletion.IoStatus)) {
|
|
|
|
//
|
|
// Successful IO at the client end
|
|
//
|
|
|
|
TRC_DBG((TB, "Successful DeviceControl at the client end"));
|
|
|
|
cbWantData = CompletionPacket->IoCompletion.Parameters.DeviceIoControl.OutputBufferLength -
|
|
Context->_DataCopied;
|
|
cbHaveData = cbPacket - (ULONG)FIELD_OFFSET(RDPDR_IOCOMPLETION_PACKET,
|
|
IoCompletion.Parameters.DeviceIoControl.OutputBuffer);
|
|
|
|
if (cbHaveData > cbWantData) {
|
|
//
|
|
// Sounds like a bad client to me
|
|
//
|
|
|
|
TRC_ERR((TB, "DeviceControl returned more data than "
|
|
"advertised, cbHaveData: %ld cbWantData: %ld", cbHaveData,
|
|
cbWantData));
|
|
if (RxContext != NULL) {
|
|
CompleteBusyExchange(Exchange, STATUS_DEVICE_PROTOCOL_ERROR, 0);
|
|
} else {
|
|
DiscardBusyExchange(Exchange);
|
|
}
|
|
*DoDefaultRead = FALSE;
|
|
return STATUS_DEVICE_PROTOCOL_ERROR;
|
|
}
|
|
|
|
if (RxContext != NULL) { // And not drexchCancelled
|
|
|
|
TRC_DBG((TB, "Copying data for DeviceControl"));
|
|
ASSERT(RxContext != NULL);
|
|
|
|
if (cbWantData > RxContext->LowIoContext.ParamsFor.IoCtl.OutputBufferLength) {
|
|
|
|
TRC_ERR((TB, "DeviceControl returned more data than "
|
|
"requested"));
|
|
|
|
CompleteBusyExchange(Exchange, STATUS_DEVICE_PROTOCOL_ERROR, 0);
|
|
*DoDefaultRead = FALSE;
|
|
return STATUS_DEVICE_PROTOCOL_ERROR;
|
|
}
|
|
|
|
//
|
|
// Copy the actual size of the read, and check to see if we have all
|
|
// the data. The information field tells us what to expect.
|
|
//
|
|
|
|
RxContext->IoStatusBlock.Information =
|
|
CompletionPacket->IoCompletion.Parameters.DeviceIoControl.OutputBufferLength;
|
|
|
|
__try {
|
|
if (RxContext->IoStatusBlock.Information && cbHaveData) {
|
|
RtlCopyMemory(((BYTE *)RxContext->LowIoContext.ParamsFor.IoCtl.pOutputBuffer) +
|
|
Context->_DataCopied, pData, cbHaveData);
|
|
|
|
//
|
|
// Keep track of how much data we've copied in case this is a
|
|
// multi chunk completion
|
|
//
|
|
|
|
Context->_DataCopied += cbHaveData;
|
|
}
|
|
}
|
|
__except (EXCEPTION_EXECUTE_HANDLER) {
|
|
|
|
TRC_ERR((TB, "Invalid buffer parameter(s)"));
|
|
|
|
CompleteBusyExchange(Exchange, STATUS_INVALID_PARAMETER, 0);
|
|
*DoDefaultRead = FALSE;
|
|
|
|
// This is the status returned back to HandlePacket, not the status
|
|
// returned back to the caller of IoControl.
|
|
return STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
if (cbHaveData == cbWantData) {
|
|
|
|
//
|
|
// There is exactly as much data as we need to satisfy the io,
|
|
// I like it.
|
|
//
|
|
|
|
TRC_NRM((TB, "DeviceControl, read %d bytes", Context->_DataCopied));
|
|
|
|
if (RxContext != NULL) {
|
|
CompleteBusyExchange(Exchange,
|
|
CompletionPacket->IoCompletion.IoStatus,
|
|
CompletionPacket->IoCompletion.Parameters.DeviceIoControl.OutputBufferLength);
|
|
} else {
|
|
DiscardBusyExchange(Exchange);
|
|
}
|
|
|
|
//
|
|
// Go with a default channel read now
|
|
//
|
|
|
|
*DoDefaultRead = TRUE;
|
|
return STATUS_SUCCESS;
|
|
} else {
|
|
|
|
//
|
|
// We don't have all the data yet, release the DrExchange and
|
|
// read more data
|
|
//
|
|
|
|
MarkIdle(Exchange);
|
|
|
|
_Session->GetExchangeManager().ReadMore(
|
|
(ULONG)FIELD_OFFSET(RDPDR_IOCOMPLETION_PACKET,
|
|
IoCompletion.Parameters.DeviceIoControl.OutputBuffer));
|
|
|
|
*DoDefaultRead = FALSE;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// Unsuccessful IO at the client end
|
|
//
|
|
|
|
TRC_DBG((TB, "Unsuccessful DeviceControl at the client end"));
|
|
|
|
if (cbPacket >= FIELD_OFFSET(RDPDR_IOCOMPLETION_PACKET,
|
|
IoCompletion.Parameters.DeviceIoControl.OutputBuffer)) {
|
|
if (RxContext != NULL) {
|
|
CompleteBusyExchange(Exchange,
|
|
CompletionPacket->IoCompletion.IoStatus,
|
|
0);
|
|
}
|
|
else {
|
|
DiscardBusyExchange(Exchange);
|
|
}
|
|
|
|
*DoDefaultRead = TRUE;
|
|
return STATUS_SUCCESS;
|
|
} else {
|
|
TRC_ERR((TB, "DeviceControl returned invalid data "));
|
|
|
|
if (RxContext != NULL) {
|
|
CompleteBusyExchange(Exchange, STATUS_DEVICE_PROTOCOL_ERROR, 0);
|
|
} else {
|
|
DiscardBusyExchange(Exchange);
|
|
}
|
|
|
|
*DoDefaultRead = FALSE;
|
|
return STATUS_DEVICE_PROTOCOL_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
NTSTATUS NTAPI DrDevice::MinirdrCancelRoutine(PRX_CONTEXT RxContext)
|
|
{
|
|
SmartPtr<DrExchange> Exchange;
|
|
DrIoContext *Context;
|
|
BOOL bFound = FALSE;
|
|
|
|
BEGIN_FN_STATIC("DrDevice::MinirdrCancelRoutine");
|
|
DrAcquireMutex();
|
|
Exchange = (DrExchange *)RxContext->MRxContext[MRX_DR_CONTEXT];
|
|
|
|
if (Exchange == NULL) {
|
|
DrReleaseMutex();
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
ASSERT(Exchange->IsValid());
|
|
Context = (DrIoContext *)Exchange->_Context;
|
|
|
|
if (Context != NULL) {
|
|
TRC_DBG((TB, "Marking Exchange cancelled"));
|
|
|
|
//
|
|
// Mark it as cancelled, if it is busy, it will be cancelled
|
|
// when it goes back to idle
|
|
//
|
|
|
|
Context->_Cancelled = TRUE;
|
|
|
|
if (!Context->_Busy) {
|
|
|
|
ASSERT(Context->_RxContext == RxContext);
|
|
|
|
//
|
|
// Wasn't busy, cancelling work should be done here
|
|
//
|
|
|
|
Context->_RxContext = NULL;
|
|
TRC_DBG((TB, "Found context to cancel"));
|
|
bFound = TRUE;
|
|
} else {
|
|
TRC_DBG((TB, "DrExchange was busy or RxContext "
|
|
"not found"));
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// This could happened if we destroyed the atlas
|
|
//
|
|
|
|
TRC_NRM((TB, "DrExchange was already cancelled"));
|
|
}
|
|
|
|
DrReleaseMutex();
|
|
|
|
if (bFound) {
|
|
|
|
//
|
|
// Do the cancelling outside the mutex
|
|
//
|
|
|
|
CompleteRxContext(RxContext, STATUS_CANCELLED, 0);
|
|
}
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
VOID DrDevice::OnIoDisconnected(SmartPtr<DrExchange> &Exchange)
|
|
{
|
|
DrIoContext *Context, *DeleteContext = NULL;
|
|
PRX_CONTEXT RxContext = NULL;
|
|
BOOL bFound = FALSE;
|
|
|
|
|
|
BEGIN_FN("DrDevice::OnIoDisconnected");
|
|
DrAcquireMutex();
|
|
ASSERT(Exchange->IsValid());
|
|
Context = (DrIoContext *)Exchange->_Context;
|
|
|
|
if (Context != NULL) {
|
|
TRC_DBG((TB, "Marking Exchange cancelled"));
|
|
|
|
//
|
|
// Mark it as cancelled, if it is busy, it will be cancelled
|
|
// when it goes back to idle
|
|
//
|
|
// Also mark it disconnected, so we know to completely clean
|
|
// up the Context
|
|
//
|
|
|
|
Context->_Cancelled = TRUE;
|
|
Context->_Disconnected = TRUE;
|
|
|
|
if (!Context->_Busy) {
|
|
|
|
RxContext = Context->_RxContext;
|
|
Exchange->_Context = NULL;
|
|
|
|
// Need to delete the context when the exchange is already cancelled or
|
|
// about to be cancelled. Also deletion needs to happen outside the mutex
|
|
DeleteContext = Context;
|
|
|
|
//
|
|
// Wasn't busy, cancelling work should be done here
|
|
//
|
|
|
|
if (RxContext) {
|
|
RxContext->MRxContext[MRX_DR_CONTEXT] = NULL;
|
|
|
|
TRC_DBG((TB, "Found context to cancel"));
|
|
bFound = TRUE;
|
|
} else {
|
|
TRC_DBG((TB, "RxContext was already cancelled "));
|
|
}
|
|
} else {
|
|
TRC_DBG((TB, "DrExchange was busy or RxContext "
|
|
"not found"));
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// This could happened if we destroyed the atlas right after
|
|
// the IO was completed, but before we discarded it
|
|
//
|
|
|
|
TRC_NRM((TB, "DrExchange was already cancelled"));
|
|
}
|
|
|
|
DrReleaseMutex();
|
|
|
|
if (bFound) {
|
|
|
|
//
|
|
// Do the cancelling outside the mutex
|
|
//
|
|
|
|
CompleteRxContext(RxContext, STATUS_CANCELLED, 0);
|
|
|
|
}
|
|
|
|
if (DeleteContext != NULL) {
|
|
delete DeleteContext;
|
|
}
|
|
}
|
|
|
|
NTSTATUS DrDevice::OnStartExchangeCompletion(SmartPtr<DrExchange> &Exchange,
|
|
PIO_STATUS_BLOCK IoStatusBlock)
|
|
{
|
|
BEGIN_FN("DrDevice::OnStartExchangeCompletion");
|
|
//
|
|
// if an error is returned, the connection should be dropped, and that
|
|
// is correct when an error comes in
|
|
//
|
|
|
|
return IoStatusBlock->Status;
|
|
}
|
|
|
|
VOID DrDevice::Remove()
|
|
{
|
|
BEGIN_FN("DrDevice::Remove");
|
|
_DeviceStatus = dsDisabled;
|
|
}
|
|
|
|
|
|
DrIoContext::DrIoContext(PRX_CONTEXT RxContext, SmartPtr<DrDevice> &Device)
|
|
{
|
|
BEGIN_FN("DrIoContext::DrIoContext");
|
|
SetClassName("DrIoContext");
|
|
_Device = Device;
|
|
_MajorFunction = RxContext->MajorFunction;
|
|
_MinorFunction = RxContext->MinorFunction;
|
|
_Busy = FALSE;
|
|
_Cancelled = FALSE;
|
|
_Disconnected = FALSE;
|
|
_TimedOut = FALSE;
|
|
_DataCopied = 0;
|
|
_RxContext = RxContext;
|
|
}
|
|
|
|
VOID DrDevice::NotifyClose()
|
|
{
|
|
BEGIN_FN("DrDevice::NotifyClose");
|
|
|
|
// This was added for ports, which need to track exclusivity
|
|
}
|