/* * PROJECT: ReactOS Kernel * LICENSE: GPL - See COPYING in the top level directory * FILE: ntoskrnl/io/error.c * PURPOSE: I/O Error Functions and Error Log Support * PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org) * Eric Kohl */ /* INCLUDES *****************************************************************/ #include #define NDEBUG #include /* TYPES *********************************************************************/ typedef struct _IOP_ERROR_LOG_WORKER_DPC { KDPC Dpc; KTIMER Timer; } IOP_ERROR_LOG_WORKER_DPC, *PIOP_ERROR_LOG_WORKER_DPC; /* GLOBALS *******************************************************************/ LONG IopTotalLogSize; LIST_ENTRY IopErrorLogListHead; KSPIN_LOCK IopLogListLock; BOOLEAN IopLogWorkerRunning; BOOLEAN IopLogPortConnected; HANDLE IopLogPort; WORK_QUEUE_ITEM IopErrorLogWorkItem; PDEVICE_OBJECT IopErrorLogObject; /* PRIVATE FUNCTIONS *********************************************************/ VOID NTAPI IopLogDpcRoutine(IN PKDPC Dpc, IN PVOID DeferredContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2) { /* If we have a DPC, free it */ if (Dpc) ExFreePool(Dpc); /* Initialize and queue the work item */ ExInitializeWorkItem(&IopErrorLogWorkItem, IopLogWorker, NULL); ExQueueWorkItem(&IopErrorLogWorkItem, DelayedWorkQueue); } PLIST_ENTRY NTAPI IopGetErrorLogEntry(VOID) { KIRQL OldIrql; PLIST_ENTRY ListEntry; /* Acquire the lock and check if the list is empty */ KeAcquireSpinLock(&IopLogListLock, &OldIrql); if (IsListEmpty(&IopErrorLogListHead)) { /* List is empty, disable the worker and return NULL */ IopLogWorkerRunning = FALSE; ListEntry = NULL; } else { /* Otherwise, remove an entry */ ListEntry = RemoveHeadList(&IopErrorLogListHead); } /* Release the lock and return the entry */ KeReleaseSpinLock(&IopLogListLock, OldIrql); return ListEntry; } VOID NTAPI IopRestartLogWorker(VOID) { PIOP_ERROR_LOG_WORKER_DPC WorkerDpc; LARGE_INTEGER Timeout; /* Allocate a DPC Context */ WorkerDpc = ExAllocatePool(NonPagedPool, sizeof(IOP_ERROR_LOG_WORKER_DPC)); if (!WorkerDpc) { /* Fail */ IopLogWorkerRunning = FALSE; return; } /* Initialize DPC and Timer */ KeInitializeDpc(&WorkerDpc->Dpc, IopLogDpcRoutine, WorkerDpc); KeInitializeTimer(&WorkerDpc->Timer); /* Restart after 30 seconds */ Timeout.QuadPart = (LONGLONG)-300000000; KeSetTimer(&WorkerDpc->Timer, Timeout, &WorkerDpc->Dpc); } BOOLEAN NTAPI IopConnectLogPort(VOID) { UNICODE_STRING PortName = RTL_CONSTANT_STRING(L"\\ErrorLogPort"); NTSTATUS Status; /* Make sure we're not already connected */ if (IopLogPortConnected) return TRUE; /* Connect the port */ Status = ZwConnectPort(&IopLogPort, &PortName, NULL, NULL, NULL, NULL, NULL, NULL); if (NT_SUCCESS(Status)) { /* Remember we're connected */ IopLogPortConnected = TRUE; return TRUE; } /* We failed, try again */ IopRestartLogWorker(); return FALSE; } VOID NTAPI IopLogWorker(IN PVOID Parameter) { PELF_API_MSG Message; PIO_ERROR_LOG_MESSAGE ErrorMessage; PLIST_ENTRY ListEntry; PERROR_LOG_ENTRY LogEntry; PIO_ERROR_LOG_PACKET Packet; PCHAR StringBuffer; ULONG RemainingLength; PDRIVER_OBJECT DriverObject; ULONG DriverNameLength = 0, DeviceNameLength; UNICODE_STRING DriverNameString; NTSTATUS Status; UCHAR Buffer[256]; POBJECT_NAME_INFORMATION ObjectNameInfo = (POBJECT_NAME_INFORMATION)&Buffer; POBJECT_NAME_INFORMATION PoolObjectNameInfo = NULL; ULONG ReturnedLength, MessageLength; PWCHAR p; ULONG ExtraStringLength; PAGED_CODE(); /* Connect to the port */ if (!IopConnectLogPort()) return; /* Allocate the message */ Message = ExAllocatePool(PagedPool, IO_ERROR_LOG_MESSAGE_LENGTH); if (!Message) { /* Couldn't allocate, try again */ IopRestartLogWorker(); return; } /* Copy the message */ RtlZeroMemory(Message, sizeof(ELF_API_MSG)); /* Get the actual I/O Structure */ ErrorMessage = &Message->IoErrorMessage; /* Start loop */ while (TRUE) { /* Get an entry */ ListEntry = IopGetErrorLogEntry(); if (!ListEntry) break; LogEntry = CONTAINING_RECORD(ListEntry, ERROR_LOG_ENTRY, ListEntry); /* Get pointer to the log packet */ Packet = (PIO_ERROR_LOG_PACKET)((ULONG_PTR)LogEntry + sizeof(ERROR_LOG_ENTRY)); /* Calculate the total length of the message only */ MessageLength = sizeof(IO_ERROR_LOG_MESSAGE) - sizeof(ERROR_LOG_ENTRY) - sizeof(IO_ERROR_LOG_PACKET) + LogEntry->Size; /* Copy the packet */ RtlCopyMemory(&ErrorMessage->EntryData, Packet, LogEntry->Size - sizeof(ERROR_LOG_ENTRY)); /* Set the timestamp and time */ ErrorMessage->TimeStamp = LogEntry->TimeStamp; ErrorMessage->Type = IO_TYPE_ERROR_MESSAGE; /* Check if this message has any strings */ if (Packet->NumberOfStrings) { /* String buffer is after the current strings */ StringBuffer = (PCHAR)&ErrorMessage->EntryData + Packet->StringOffset; } else { /* Otherwise, string buffer is at the end */ StringBuffer = (PCHAR)ErrorMessage + MessageLength; } /* Align the buffer */ StringBuffer = ALIGN_UP_POINTER(StringBuffer, WCHAR); /* Set the offset for the driver's name to the current buffer */ ErrorMessage->DriverNameOffset = (ULONG_PTR)(StringBuffer - (ULONG_PTR)ErrorMessage); /* Check how much space we have left for the device string */ RemainingLength = (ULONG)((ULONG_PTR)Message + IO_ERROR_LOG_MESSAGE_LENGTH - (ULONG_PTR)StringBuffer); /* Now check if there is a driver object */ DriverObject = LogEntry->DriverObject; if (DriverObject) { /* Check if the driver has a name */ if (DriverObject->DriverName.Buffer) { /* Use its name */ DriverNameString.Buffer = DriverObject->DriverName.Buffer; DriverNameLength = DriverObject->DriverName.Length; } else DriverNameString.Buffer = NULL; /* Check if there isn't a valid name*/ if (!DriverNameLength) { /* Query the name directly */ Status = ObQueryNameString(DriverObject, ObjectNameInfo, sizeof(Buffer), &ReturnedLength); if (!(NT_SUCCESS(Status)) || !(ObjectNameInfo->Name.Length)) { /* We don't have a name */ DriverNameLength = 0; } } } else { /* Use default name */ DriverNameString.Buffer = L"Application Popup"; DriverNameLength = wcslen(DriverNameString.Buffer) * sizeof(WCHAR); } /* Check if we have a driver name by here */ if (DriverNameLength) { /* Skip to the end of the driver's name */ p = &DriverNameString.Buffer[DriverNameLength / sizeof(WCHAR)]; /* Now we'll walk backwards and assume the minimum size */ DriverNameLength = sizeof(WCHAR); p--; while ((*p != L'\\') && (p != DriverNameString.Buffer)) { /* No backslash found, keep going */ p--; DriverNameLength += sizeof(WCHAR); } /* Now we probably hit the backslash itself, skip past it */ if (*p == L'\\') { p++; DriverNameLength -= sizeof(WCHAR); } /* * Now make sure that the driver name fits in our buffer, minus 3 * NULL chars, and copy the name in our string buffer */ DriverNameLength = min(DriverNameLength, RemainingLength - 3 * sizeof(UNICODE_NULL)); RtlCopyMemory(StringBuffer, p, DriverNameLength); } /* Null-terminate the driver name */ *((PWSTR)(StringBuffer + DriverNameLength)) = L'\0'; DriverNameLength += sizeof(WCHAR); /* Go to the next string buffer position */ StringBuffer += DriverNameLength; RemainingLength -= DriverNameLength; /* Update the string offset and check if we have a device object */ ErrorMessage->EntryData.StringOffset = (USHORT) ((ULONG_PTR)StringBuffer - (ULONG_PTR)ErrorMessage); if (LogEntry->DeviceObject) { /* We do, query its name */ Status = ObQueryNameString(LogEntry->DeviceObject, ObjectNameInfo, sizeof(OBJECT_NAME_INFORMATION) + 100 - DriverNameLength, &ReturnedLength); if ((!NT_SUCCESS(Status)) || !(ObjectNameInfo->Name.Length)) { /* Setup an empty name */ ObjectNameInfo->Name.Length = 0; ObjectNameInfo->Name.Buffer = L""; /* Check if we failed because our buffer wasn't large enough */ if (Status == STATUS_INFO_LENGTH_MISMATCH) { /* Then we'll allocate one... we really want this name! */ PoolObjectNameInfo = ExAllocatePoolWithTag(PagedPool, ReturnedLength, TAG_IO); if (PoolObjectNameInfo) { /* Query it again */ ObjectNameInfo = PoolObjectNameInfo; Status = ObQueryNameString(LogEntry->DeviceObject, ObjectNameInfo, ReturnedLength, &ReturnedLength); if (NT_SUCCESS(Status)) { /* Success, update the information */ ObjectNameInfo->Name.Length = 100 - (USHORT)DriverNameLength; } } } } } else { /* No device object, setup an empty name */ ObjectNameInfo->Name.Length = 0; ObjectNameInfo->Name.Buffer = L""; } /* * Now make sure that the device name fits in our buffer, minus 2 * NULL chars, and copy the name in our string buffer */ DeviceNameLength = min(ObjectNameInfo->Name.Length, RemainingLength - 2 * sizeof(UNICODE_NULL)); RtlCopyMemory(StringBuffer, ObjectNameInfo->Name.Buffer, DeviceNameLength); /* Null-terminate the device name */ *((PWSTR)(StringBuffer + DeviceNameLength)) = L'\0'; DeviceNameLength += sizeof(WCHAR); /* Free the buffer if we had one */ if (PoolObjectNameInfo) ExFreePool(PoolObjectNameInfo); /* Go to the next string buffer position */ ErrorMessage->EntryData.NumberOfStrings++; StringBuffer += DeviceNameLength; RemainingLength -= DeviceNameLength; /* Check if we have any extra strings */ if (Packet->NumberOfStrings) { /* Find out the size of the extra strings */ ExtraStringLength = LogEntry->Size - sizeof(ERROR_LOG_ENTRY) - Packet->StringOffset; /* Make sure that the extra strings fit in our buffer */ if (ExtraStringLength > (RemainingLength - sizeof(UNICODE_NULL))) { /* They wouldn't, so set normalize the length */ MessageLength -= ExtraStringLength - RemainingLength; ExtraStringLength = RemainingLength - sizeof(UNICODE_NULL); } /* Now copy the extra strings */ RtlCopyMemory(StringBuffer, (PCHAR)Packet + Packet->StringOffset, ExtraStringLength); /* Null-terminate them */ *((PWSTR)(StringBuffer + ExtraStringLength)) = L'\0'; } /* Set the driver name length */ ErrorMessage->DriverNameLength = (USHORT)DriverNameLength; /* Update the message length to include the device and driver names */ MessageLength += DeviceNameLength + DriverNameLength; ErrorMessage->Size = (USHORT)MessageLength; /* Now update it again, internally, for the size of the actual LPC */ MessageLength += (FIELD_OFFSET(ELF_API_MSG, IoErrorMessage) - FIELD_OFFSET(ELF_API_MSG, Unknown[0])); /* Set the total and data lengths */ Message->h.u1.s1.TotalLength = (USHORT)(sizeof(PORT_MESSAGE) + MessageLength); Message->h.u1.s1.DataLength = (USHORT)(MessageLength); /* Send the message */ Status = NtRequestPort(IopLogPort, (PPORT_MESSAGE)Message); if (!NT_SUCCESS(Status)) { /* Requeue log message and restart the worker */ ExInterlockedInsertTailList(&IopErrorLogListHead, &LogEntry->ListEntry, &IopLogListLock); IopLogWorkerRunning = FALSE; IopRestartLogWorker(); break; } /* Derefernece the device object */ if (LogEntry->DeviceObject) ObDereferenceObject(LogEntry->DeviceObject); if (DriverObject) ObDereferenceObject(LogEntry->DriverObject); /* Update size */ InterlockedExchangeAdd(&IopTotalLogSize, -(LONG)(LogEntry->Size - sizeof(ERROR_LOG_ENTRY))); } /* Free the LPC Message */ ExFreePool(Message); } VOID NTAPI IopFreeApc(IN PKAPC Apc, IN PKNORMAL_ROUTINE *NormalRoutine, IN PVOID *NormalContext, IN PVOID *SystemArgument1, IN PVOID *SystemArgument2) { /* Free the APC */ ExFreePool(Apc); } VOID NTAPI IopRaiseHardError(IN PKAPC Apc, IN PKNORMAL_ROUTINE *NormalRoutine, IN PVOID *NormalContext, IN PVOID *SystemArgument1, IN PVOID *SystemArgument2) { PIRP Irp = (PIRP)NormalContext; //PVPB Vpb = (PVPB)SystemArgument1; //PDEVICE_OBJECT DeviceObject = (PDEVICE_OBJECT)SystemArgument2; UNIMPLEMENTED; /* FIXME: UNIMPLEMENTED */ Irp->IoStatus.Status = STATUS_NOT_IMPLEMENTED; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_DISK_INCREMENT); } /* PUBLIC FUNCTIONS **********************************************************/ /* * @implemented */ PVOID NTAPI IoAllocateErrorLogEntry(IN PVOID IoObject, IN UCHAR EntrySize) { PERROR_LOG_ENTRY LogEntry; ULONG LogEntrySize; PDRIVER_OBJECT DriverObject; PDEVICE_OBJECT DeviceObject; /* Make sure we have an object */ if (!IoObject) return NULL; /* Check if we're past our buffer */ if (IopTotalLogSize > PAGE_SIZE) return NULL; /* Calculate the total size and allocate it */ LogEntrySize = sizeof(ERROR_LOG_ENTRY) + EntrySize; LogEntry = ExAllocatePoolWithTag(NonPagedPool, LogEntrySize, TAG_ERROR_LOG); if (!LogEntry) return NULL; /* Check if this is a device object or driver object */ if (((PDEVICE_OBJECT)IoObject)->Type == IO_TYPE_DEVICE) { /* It's a device, get the driver */ DeviceObject = (PDEVICE_OBJECT)IoObject; DriverObject = DeviceObject->DriverObject; } else if (((PDEVICE_OBJECT)IoObject)->Type == IO_TYPE_DRIVER) { /* It's a driver, so we don' thave a device */ DeviceObject = NULL; DriverObject = IoObject; } else { /* Fail */ return NULL; } /* Reference the Objects */ if (DeviceObject) ObReferenceObject(DeviceObject); if (DriverObject) ObReferenceObject(DriverObject); /* Update log size */ InterlockedExchangeAdd(&IopTotalLogSize, EntrySize); /* Clear the entry and set it up */ RtlZeroMemory(LogEntry, EntrySize); LogEntry->Type = IO_TYPE_ERROR_LOG; LogEntry->Size = EntrySize; LogEntry->DeviceObject = DeviceObject; LogEntry->DriverObject = DriverObject; /* Return the entry data */ return (PVOID)((ULONG_PTR)LogEntry + sizeof(ERROR_LOG_ENTRY)); } /* * @implemented */ VOID NTAPI IoFreeErrorLogEntry(IN PVOID ElEntry) { PERROR_LOG_ENTRY LogEntry; /* Make sure there's an entry */ if (!ElEntry) return; /* Get the actual header */ LogEntry = (PERROR_LOG_ENTRY)((ULONG_PTR)ElEntry - sizeof(ERROR_LOG_ENTRY)); /* Dereference both objects */ if (LogEntry->DeviceObject) ObDereferenceObject(LogEntry->DeviceObject); if (LogEntry->DriverObject) ObDereferenceObject(LogEntry->DriverObject); /* Decrease total allocation size and free the entry */ InterlockedExchangeAdd(&IopTotalLogSize, -(LONG)(LogEntry->Size - sizeof(ERROR_LOG_ENTRY))); ExFreePool(LogEntry); } /* * @implemented */ VOID NTAPI IoWriteErrorLogEntry(IN PVOID ElEntry) { PERROR_LOG_ENTRY LogEntry; KIRQL Irql; /* Get the main header */ LogEntry = (PERROR_LOG_ENTRY)((ULONG_PTR)ElEntry - sizeof(ERROR_LOG_ENTRY)); /* Get time stamp */ KeQuerySystemTime(&LogEntry->TimeStamp); /* Acquire the lock and insert this write in the list */ KeAcquireSpinLock(&IopLogListLock, &Irql); InsertHeadList(&IopErrorLogListHead, &LogEntry->ListEntry); /* Check if the worker is running */ if (!IopLogWorkerRunning) { #if 0 /* It's not, initialize it and queue it */ ExInitializeWorkItem(&IopErrorLogWorkItem, IopLogWorker, &IopErrorLogWorkItem); ExQueueWorkItem(&IopErrorLogWorkItem, DelayedWorkQueue); IopLogWorkerRunning = TRUE; #endif } /* Release the lock and return */ KeReleaseSpinLock(&IopLogListLock, Irql); } /* * @implemented */ VOID NTAPI IoRaiseHardError(IN PIRP Irp, IN PVPB Vpb, IN PDEVICE_OBJECT RealDeviceObject) { PETHREAD Thread = (PETHREAD)&Irp->Tail.Overlay.Thread; PKAPC ErrorApc; /* Don't do anything if hard errors are disabled on the thread */ if (Thread->HardErrorsAreDisabled) { /* Complete the request */ Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_DISK_INCREMENT); return; } /* Setup an APC */ ErrorApc = ExAllocatePoolWithTag(NonPagedPool, sizeof(KAPC), TAG_APC); KeInitializeApc(ErrorApc, &Thread->Tcb, Irp->ApcEnvironment, NULL, (PKRUNDOWN_ROUTINE)IopFreeApc, (PKNORMAL_ROUTINE)IopRaiseHardError, KernelMode, Irp); /* Queue an APC to deal with the error (see osr documentation) */ KeInsertQueueApc(ErrorApc, Vpb, RealDeviceObject, 0); } /* * @unimplemented */ BOOLEAN NTAPI IoRaiseInformationalHardError(IN NTSTATUS ErrorStatus, IN PUNICODE_STRING String, IN PKTHREAD Thread) { UNIMPLEMENTED; return FALSE; } /* * @implemented */ BOOLEAN NTAPI IoSetThreadHardErrorMode(IN BOOLEAN HardErrorEnabled) { PETHREAD Thread = PsGetCurrentThread(); BOOLEAN OldMode; /* Get the current value */ OldMode = !Thread->HardErrorsAreDisabled; /* Set the new one and return the old */ Thread->HardErrorsAreDisabled = !HardErrorEnabled; return OldMode; }