2 * PROJECT: ReactOS Kernel
3 * LICENSE: GPL - See COPYING in the top level directory
4 * FILE: ntoskrnl/io/error.c
5 * PURPOSE: I/O Error Functions and Error Log Support
6 * PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
9 /* INCLUDES *****************************************************************/
15 /* TYPES *********************************************************************/
17 typedef struct _IOP_ERROR_LOG_WORKER_DPC
21 } IOP_ERROR_LOG_WORKER_DPC
, *PIOP_ERROR_LOG_WORKER_DPC
;
23 /* GLOBALS *******************************************************************/
26 LIST_ENTRY IopErrorLogListHead
;
27 KSPIN_LOCK IopLogListLock
;
29 BOOLEAN IopLogWorkerRunning
;
30 BOOLEAN IopLogPortConnected
;
32 WORK_QUEUE_ITEM IopErrorLogWorkItem
;
34 PDEVICE_OBJECT IopErrorLogObject
;
36 /* PRIVATE FUNCTIONS *********************************************************/
40 IopLogDpcRoutine(IN PKDPC Dpc
,
41 IN PVOID DeferredContext
,
42 IN PVOID SystemArgument1
,
43 IN PVOID SystemArgument2
)
45 /* If we have a DPC, free it */
46 if (Dpc
) ExFreePool(Dpc
);
48 /* Initialize and queue the work item */
49 ExInitializeWorkItem(&IopErrorLogWorkItem
, IopLogWorker
, NULL
);
50 ExQueueWorkItem(&IopErrorLogWorkItem
, DelayedWorkQueue
);
55 IopGetErrorLogEntry(VOID
)
58 PLIST_ENTRY ListEntry
;
60 /* Acquire the lock and check if the list is empty */
61 KeAcquireSpinLock(&IopLogListLock
, &OldIrql
);
62 if (IsListEmpty(&IopErrorLogListHead
))
64 /* List is empty, disable the worker and return NULL */
65 IopLogWorkerRunning
= FALSE
;
70 /* Otherwise, remove an entry */
71 ListEntry
= RemoveHeadList(&IopErrorLogListHead
);
74 /* Release the lock and return the entry */
75 KeReleaseSpinLock(&IopLogListLock
, OldIrql
);
81 IopRestartLogWorker(VOID
)
83 PIOP_ERROR_LOG_WORKER_DPC WorkerDpc
;
84 LARGE_INTEGER Timeout
;
86 /* Allocate a DPC Context */
87 WorkerDpc
= ExAllocatePool(NonPagedPool
, sizeof(IOP_ERROR_LOG_WORKER_DPC
));
91 IopLogWorkerRunning
= FALSE
;
95 /* Initialize DPC and Timer */
96 KeInitializeDpc(&WorkerDpc
->Dpc
, IopLogDpcRoutine
, WorkerDpc
);
97 KeInitializeTimer(&WorkerDpc
->Timer
);
99 /* Restart after 30 seconds */
100 Timeout
.QuadPart
= (LONGLONG
)-300000000;
101 KeSetTimer(&WorkerDpc
->Timer
, Timeout
, &WorkerDpc
->Dpc
);
106 IopConnectLogPort(VOID
)
108 UNICODE_STRING PortName
= RTL_CONSTANT_STRING(L
"\\ErrorLogPort");
111 /* Make sure we're not already connected */
112 if (IopLogPortConnected
) return TRUE
;
114 /* Connect the port */
115 Status
= ZwConnectPort(&IopLogPort
,
123 if (NT_SUCCESS(Status
))
125 /* Remember we're connected */
126 IopLogPortConnected
= TRUE
;
130 /* We failed, try again */
131 IopRestartLogWorker();
137 IopLogWorker(IN PVOID Parameter
)
139 PELF_API_MSG Message
;
140 PIO_ERROR_LOG_MESSAGE ErrorMessage
;
141 PLIST_ENTRY ListEntry
;
142 PERROR_LOG_ENTRY LogEntry
;
143 PIO_ERROR_LOG_PACKET Packet
;
145 ULONG RemainingLength
;
146 PDRIVER_OBJECT DriverObject
;
147 ULONG DriverNameLength
= 0, DeviceNameLength
;
148 UNICODE_STRING DriverNameString
;
151 POBJECT_NAME_INFORMATION ObjectNameInfo
= (POBJECT_NAME_INFORMATION
)&Buffer
;
152 POBJECT_NAME_INFORMATION PoolObjectNameInfo
= NULL
;
153 ULONG ReturnedLength
, MessageLength
;
155 ULONG ExtraStringLength
;
158 /* Connect to the port */
159 if (!IopConnectLogPort()) return;
161 /* Allocate the message */
162 Message
= ExAllocatePool(PagedPool
, IO_ERROR_LOG_MESSAGE_LENGTH
);
165 /* Couldn't allocate, try again */
166 IopRestartLogWorker();
170 /* Copy the message */
171 RtlZeroMemory(Message
, sizeof(ELF_API_MSG
));
173 /* Get the actual I/O Structure */
174 ErrorMessage
= &Message
->IoErrorMessage
;
180 ListEntry
= IopGetErrorLogEntry();
181 if (!ListEntry
) break;
182 LogEntry
= CONTAINING_RECORD(ListEntry
, ERROR_LOG_ENTRY
, ListEntry
);
184 /* Get pointer to the log packet */
185 Packet
= (PIO_ERROR_LOG_PACKET
)((ULONG_PTR
)LogEntry
+
186 sizeof(ERROR_LOG_ENTRY
));
188 /* Calculate the total length of the message only */
189 MessageLength
= sizeof(IO_ERROR_LOG_MESSAGE
) -
190 sizeof(ERROR_LOG_ENTRY
) -
191 sizeof(IO_ERROR_LOG_PACKET
) +
194 /* Copy the packet */
195 RtlCopyMemory(&ErrorMessage
->EntryData
,
197 LogEntry
->Size
- sizeof(ERROR_LOG_ENTRY
));
199 /* Set the timestamp and time */
200 ErrorMessage
->TimeStamp
= LogEntry
->TimeStamp
;
201 ErrorMessage
->Type
= IO_TYPE_ERROR_MESSAGE
;
203 /* Check if this message has any strings */
204 if (Packet
->NumberOfStrings
)
206 /* String buffer is after the current strings */
207 StringBuffer
= (PCHAR
)&ErrorMessage
->EntryData
+
208 Packet
->StringOffset
;
212 /* Otherwise, string buffer is at the end */
213 StringBuffer
= (PCHAR
)ErrorMessage
+ MessageLength
;
216 /* Align the buffer */
217 StringBuffer
= ALIGN_UP_POINTER(StringBuffer
, WCHAR
);
219 /* Set the offset for the driver's name to the current buffer */
220 ErrorMessage
->DriverNameOffset
= (ULONG
)(StringBuffer
-
221 (PCHAR
)ErrorMessage
);
223 /* Check how much space we have left for the device string */
224 RemainingLength
= (ULONG
)((ULONG_PTR
)Message
+
225 IO_ERROR_LOG_MESSAGE_LENGTH
-
226 (ULONG_PTR
)StringBuffer
);
228 /* Now check if there is a driver object */
229 DriverObject
= LogEntry
->DriverObject
;
232 /* Check if the driver has a name */
233 if (DriverObject
->DriverName
.Buffer
)
236 DriverNameString
.Buffer
= DriverObject
->DriverName
.Buffer
;
237 DriverNameLength
= DriverObject
->DriverName
.Length
;
240 DriverNameString
.Buffer
= NULL
;
242 /* Check if there isn't a valid name*/
243 if (!DriverNameLength
)
245 /* Query the name directly */
246 Status
= ObQueryNameString(DriverObject
,
250 if (!(NT_SUCCESS(Status
)) || !(ObjectNameInfo
->Name
.Length
))
252 /* We don't have a name */
253 DriverNameLength
= 0;
259 /* Use default name */
260 DriverNameString
.Buffer
= L
"Application Popup";
261 DriverNameLength
= (ULONG
)wcslen(DriverNameString
.Buffer
) * sizeof(WCHAR
);
264 /* Check if we have a driver name by here */
265 if (DriverNameLength
)
267 /* Skip to the end of the driver's name */
268 p
= &DriverNameString
.Buffer
[DriverNameLength
/ sizeof(WCHAR
)];
270 /* Now we'll walk backwards and assume the minimum size */
271 DriverNameLength
= sizeof(WCHAR
);
273 while ((*p
!= L
'\\') && (p
!= DriverNameString
.Buffer
))
275 /* No backslash found, keep going */
277 DriverNameLength
+= sizeof(WCHAR
);
280 /* Now we probably hit the backslash itself, skip past it */
284 DriverNameLength
-= sizeof(WCHAR
);
288 * Now make sure that the driver name fits in our buffer, minus 3
289 * NULL chars, and copy the name in our string buffer
291 DriverNameLength
= min(DriverNameLength
,
292 RemainingLength
- 3 * sizeof(UNICODE_NULL
));
293 RtlCopyMemory(StringBuffer
, p
, DriverNameLength
);
296 /* Null-terminate the driver name */
297 *((PWSTR
)(StringBuffer
+ DriverNameLength
)) = L
'\0';
298 DriverNameLength
+= sizeof(WCHAR
);
300 /* Go to the next string buffer position */
301 StringBuffer
+= DriverNameLength
;
302 RemainingLength
-= DriverNameLength
;
304 /* Update the string offset and check if we have a device object */
305 ErrorMessage
->EntryData
.StringOffset
= (USHORT
)
306 ((ULONG_PTR
)StringBuffer
-
307 (ULONG_PTR
)ErrorMessage
);
308 if (LogEntry
->DeviceObject
)
310 /* We do, query its name */
311 Status
= ObQueryNameString(LogEntry
->DeviceObject
,
313 sizeof(OBJECT_NAME_INFORMATION
) +
317 if ((!NT_SUCCESS(Status
)) || !(ObjectNameInfo
->Name
.Length
))
319 /* Setup an empty name */
320 ObjectNameInfo
->Name
.Length
= 0;
321 ObjectNameInfo
->Name
.Buffer
= L
"";
323 /* Check if we failed because our buffer wasn't large enough */
324 if (Status
== STATUS_INFO_LENGTH_MISMATCH
)
326 /* Then we'll allocate one... we really want this name! */
327 PoolObjectNameInfo
= ExAllocatePoolWithTag(PagedPool
,
330 if (PoolObjectNameInfo
)
333 ObjectNameInfo
= PoolObjectNameInfo
;
334 Status
= ObQueryNameString(LogEntry
->DeviceObject
,
338 if (NT_SUCCESS(Status
))
340 /* Success, update the information */
341 ObjectNameInfo
->Name
.Length
=
342 100 - (USHORT
)DriverNameLength
;
350 /* No device object, setup an empty name */
351 ObjectNameInfo
->Name
.Length
= 0;
352 ObjectNameInfo
->Name
.Buffer
= L
"";
356 * Now make sure that the device name fits in our buffer, minus 2
357 * NULL chars, and copy the name in our string buffer
359 DeviceNameLength
= min(ObjectNameInfo
->Name
.Length
,
360 RemainingLength
- 2 * sizeof(UNICODE_NULL
));
361 RtlCopyMemory(StringBuffer
,
362 ObjectNameInfo
->Name
.Buffer
,
365 /* Null-terminate the device name */
366 *((PWSTR
)(StringBuffer
+ DeviceNameLength
)) = L
'\0';
367 DeviceNameLength
+= sizeof(WCHAR
);
369 /* Free the buffer if we had one */
370 if (PoolObjectNameInfo
) ExFreePool(PoolObjectNameInfo
);
372 /* Go to the next string buffer position */
373 ErrorMessage
->EntryData
.NumberOfStrings
++;
374 StringBuffer
+= DeviceNameLength
;
375 RemainingLength
-= DeviceNameLength
;
377 /* Check if we have any extra strings */
378 if (Packet
->NumberOfStrings
)
380 /* Find out the size of the extra strings */
381 ExtraStringLength
= LogEntry
->Size
-
382 sizeof(ERROR_LOG_ENTRY
) -
383 Packet
->StringOffset
;
385 /* Make sure that the extra strings fit in our buffer */
386 if (ExtraStringLength
> (RemainingLength
- sizeof(UNICODE_NULL
)))
388 /* They wouldn't, so set normalize the length */
389 MessageLength
-= ExtraStringLength
- RemainingLength
;
390 ExtraStringLength
= RemainingLength
- sizeof(UNICODE_NULL
);
393 /* Now copy the extra strings */
394 RtlCopyMemory(StringBuffer
,
395 (PCHAR
)Packet
+ Packet
->StringOffset
,
398 /* Null-terminate them */
399 *((PWSTR
)(StringBuffer
+ ExtraStringLength
)) = L
'\0';
402 /* Set the driver name length */
403 ErrorMessage
->DriverNameLength
= (USHORT
)DriverNameLength
;
405 /* Update the message length to include the device and driver names */
406 MessageLength
+= DeviceNameLength
+ DriverNameLength
;
407 ErrorMessage
->Size
= (USHORT
)MessageLength
;
409 /* Now update it again, internally, for the size of the actual LPC */
410 MessageLength
+= (FIELD_OFFSET(ELF_API_MSG
, IoErrorMessage
) -
411 FIELD_OFFSET(ELF_API_MSG
, Unknown
[0]));
413 /* Set the total and data lengths */
414 Message
->h
.u1
.s1
.TotalLength
= (USHORT
)(sizeof(PORT_MESSAGE
) +
416 Message
->h
.u1
.s1
.DataLength
= (USHORT
)(MessageLength
);
418 /* Send the message */
419 Status
= NtRequestPort(IopLogPort
, (PPORT_MESSAGE
)Message
);
420 if (!NT_SUCCESS(Status
))
422 /* Requeue log message and restart the worker */
423 ExInterlockedInsertTailList(&IopErrorLogListHead
,
424 &LogEntry
->ListEntry
,
426 IopLogWorkerRunning
= FALSE
;
427 IopRestartLogWorker();
431 /* Derefernece the device object */
432 if (LogEntry
->DeviceObject
) ObDereferenceObject(LogEntry
->DeviceObject
);
433 if (DriverObject
) ObDereferenceObject(LogEntry
->DriverObject
);
436 InterlockedExchangeAdd(&IopTotalLogSize
,
437 -(LONG
)(LogEntry
->Size
-
438 sizeof(ERROR_LOG_ENTRY
)));
441 /* Free the LPC Message */
447 IopFreeApc(IN PKAPC Apc
,
448 IN PKNORMAL_ROUTINE
*NormalRoutine
,
449 IN PVOID
*NormalContext
,
450 IN PVOID
*SystemArgument1
,
451 IN PVOID
*SystemArgument2
)
459 IopRaiseHardError(IN PKAPC Apc
,
460 IN PKNORMAL_ROUTINE
*NormalRoutine
,
461 IN PVOID
*NormalContext
,
462 IN PVOID
*SystemArgument1
,
463 IN PVOID
*SystemArgument2
)
465 PIRP Irp
= (PIRP
)NormalContext
;
466 //PVPB Vpb = (PVPB)SystemArgument1;
467 //PDEVICE_OBJECT DeviceObject = (PDEVICE_OBJECT)SystemArgument2;
471 /* FIXME: UNIMPLEMENTED */
472 Irp
->IoStatus
.Status
= STATUS_NOT_IMPLEMENTED
;
473 Irp
->IoStatus
.Information
= 0;
474 IoCompleteRequest(Irp
, IO_DISK_INCREMENT
);
477 /* PUBLIC FUNCTIONS **********************************************************/
484 IoAllocateErrorLogEntry(IN PVOID IoObject
,
487 PERROR_LOG_ENTRY LogEntry
;
489 PDRIVER_OBJECT DriverObject
;
490 PDEVICE_OBJECT DeviceObject
;
492 /* Make sure we have an object */
493 if (!IoObject
) return NULL
;
495 /* Check if we're past our buffer */
496 if (IopTotalLogSize
> PAGE_SIZE
) return NULL
;
498 /* Calculate the total size and allocate it */
499 LogEntrySize
= sizeof(ERROR_LOG_ENTRY
) + EntrySize
;
500 LogEntry
= ExAllocatePoolWithTag(NonPagedPool
,
503 if (!LogEntry
) return NULL
;
505 /* Check if this is a device object or driver object */
506 if (((PDEVICE_OBJECT
)IoObject
)->Type
== IO_TYPE_DEVICE
)
508 /* It's a device, get the driver */
509 DeviceObject
= (PDEVICE_OBJECT
)IoObject
;
510 DriverObject
= DeviceObject
->DriverObject
;
512 else if (((PDEVICE_OBJECT
)IoObject
)->Type
== IO_TYPE_DRIVER
)
514 /* It's a driver, so we don' thave a device */
516 DriverObject
= IoObject
;
524 /* Reference the Objects */
525 if (DeviceObject
) ObReferenceObject(DeviceObject
);
526 if (DriverObject
) ObReferenceObject(DriverObject
);
528 /* Update log size */
529 InterlockedExchangeAdd(&IopTotalLogSize
, EntrySize
);
531 /* Clear the entry and set it up */
532 RtlZeroMemory(LogEntry
, EntrySize
);
533 LogEntry
->Type
= IO_TYPE_ERROR_LOG
;
534 LogEntry
->Size
= EntrySize
;
535 LogEntry
->DeviceObject
= DeviceObject
;
536 LogEntry
->DriverObject
= DriverObject
;
538 /* Return the entry data */
539 return (PVOID
)((ULONG_PTR
)LogEntry
+ sizeof(ERROR_LOG_ENTRY
));
547 IoFreeErrorLogEntry(IN PVOID ElEntry
)
549 PERROR_LOG_ENTRY LogEntry
;
551 /* Make sure there's an entry */
552 if (!ElEntry
) return;
554 /* Get the actual header */
555 LogEntry
= (PERROR_LOG_ENTRY
)((ULONG_PTR
)ElEntry
- sizeof(ERROR_LOG_ENTRY
));
557 /* Dereference both objects */
558 if (LogEntry
->DeviceObject
) ObDereferenceObject(LogEntry
->DeviceObject
);
559 if (LogEntry
->DriverObject
) ObDereferenceObject(LogEntry
->DriverObject
);
561 /* Decrease total allocation size and free the entry */
562 InterlockedExchangeAdd(&IopTotalLogSize
,
563 -(LONG
)(LogEntry
->Size
- sizeof(ERROR_LOG_ENTRY
)));
564 ExFreePool(LogEntry
);
572 IoWriteErrorLogEntry(IN PVOID ElEntry
)
574 PERROR_LOG_ENTRY LogEntry
;
577 /* Get the main header */
578 LogEntry
= (PERROR_LOG_ENTRY
)((ULONG_PTR
)ElEntry
-
579 sizeof(ERROR_LOG_ENTRY
));
582 KeQuerySystemTime(&LogEntry
->TimeStamp
);
584 /* Acquire the lock and insert this write in the list */
585 KeAcquireSpinLock(&IopLogListLock
, &Irql
);
586 InsertHeadList(&IopErrorLogListHead
, &LogEntry
->ListEntry
);
588 /* Check if the worker is running */
589 if (!IopLogWorkerRunning
)
592 /* It's not, initialize it and queue it */
593 ExInitializeWorkItem(&IopErrorLogWorkItem
,
595 &IopErrorLogWorkItem
);
596 ExQueueWorkItem(&IopErrorLogWorkItem
, DelayedWorkQueue
);
597 IopLogWorkerRunning
= TRUE
;
601 /* Release the lock and return */
602 KeReleaseSpinLock(&IopLogListLock
, Irql
);
610 IoRaiseHardError(IN PIRP Irp
,
612 IN PDEVICE_OBJECT RealDeviceObject
)
614 PETHREAD Thread
= (PETHREAD
)&Irp
->Tail
.Overlay
.Thread
;
617 /* Don't do anything if hard errors are disabled on the thread */
618 if (Thread
->HardErrorsAreDisabled
)
620 /* Complete the request */
621 Irp
->IoStatus
.Information
= 0;
622 IoCompleteRequest(Irp
, IO_DISK_INCREMENT
);
627 ErrorApc
= ExAllocatePoolWithTag(NonPagedPool
,
630 KeInitializeApc(ErrorApc
,
634 (PKRUNDOWN_ROUTINE
)IopFreeApc
,
635 (PKNORMAL_ROUTINE
)IopRaiseHardError
,
639 /* Queue an APC to deal with the error (see osr documentation) */
640 KeInsertQueueApc(ErrorApc
, Vpb
, RealDeviceObject
, 0);
648 IoRaiseInformationalHardError(IN NTSTATUS ErrorStatus
,
649 IN PUNICODE_STRING String
,
661 IoSetThreadHardErrorMode(IN BOOLEAN HardErrorEnabled
)
663 PETHREAD Thread
= PsGetCurrentThread();
666 /* Get the current value */
667 OldMode
= !Thread
->HardErrorsAreDisabled
;
669 /* Set the new one and return the old */
670 Thread
->HardErrorsAreDisabled
= !HardErrorEnabled
;