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
,
315 if (!NT_SUCCESS(Status
) || (ObjectNameInfo
->Name
.Length
== 0))
317 /* Setup an empty name */
318 ObjectNameInfo
->Name
.Length
= 0;
319 ObjectNameInfo
->Name
.Buffer
= L
"";
321 /* Check if we failed because our buffer wasn't large enough */
322 if (Status
== STATUS_INFO_LENGTH_MISMATCH
)
324 /* Then we'll allocate one... we really want this name! */
325 PoolObjectNameInfo
= ExAllocatePoolWithTag(PagedPool
,
328 if (PoolObjectNameInfo
)
331 ObjectNameInfo
= PoolObjectNameInfo
;
332 Status
= ObQueryNameString(LogEntry
->DeviceObject
,
336 if (NT_SUCCESS(Status
))
338 /* Success, update the information */
339 ObjectNameInfo
->Name
.Length
=
340 100 - (USHORT
)DriverNameLength
;
348 /* No device object, setup an empty name */
349 ObjectNameInfo
->Name
.Length
= 0;
350 ObjectNameInfo
->Name
.Buffer
= L
"";
354 * Now make sure that the device name fits in our buffer, minus 2
355 * NULL chars, and copy the name in our string buffer
357 DeviceNameLength
= min(ObjectNameInfo
->Name
.Length
,
358 RemainingLength
- 2 * sizeof(UNICODE_NULL
));
359 RtlCopyMemory(StringBuffer
,
360 ObjectNameInfo
->Name
.Buffer
,
363 /* Null-terminate the device name */
364 *((PWSTR
)(StringBuffer
+ DeviceNameLength
)) = L
'\0';
365 DeviceNameLength
+= sizeof(WCHAR
);
367 /* Free the buffer if we had one */
368 if (PoolObjectNameInfo
)
370 ExFreePool(PoolObjectNameInfo
);
371 PoolObjectNameInfo
= NULL
;
372 ObjectNameInfo
= (POBJECT_NAME_INFORMATION
)&Buffer
;
375 /* Go to the next string buffer position */
376 ErrorMessage
->EntryData
.NumberOfStrings
++;
377 StringBuffer
+= DeviceNameLength
;
378 RemainingLength
-= DeviceNameLength
;
380 /* Check if we have any extra strings */
381 if (Packet
->NumberOfStrings
)
383 /* Find out the size of the extra strings */
384 ExtraStringLength
= LogEntry
->Size
-
385 sizeof(ERROR_LOG_ENTRY
) -
386 Packet
->StringOffset
;
388 /* Make sure that the extra strings fit in our buffer */
389 if (ExtraStringLength
> (RemainingLength
- sizeof(UNICODE_NULL
)))
391 /* They wouldn't, so set normalize the length */
392 MessageLength
-= ExtraStringLength
- RemainingLength
;
393 ExtraStringLength
= RemainingLength
- sizeof(UNICODE_NULL
);
396 /* Now copy the extra strings */
397 RtlCopyMemory(StringBuffer
,
398 (PCHAR
)Packet
+ Packet
->StringOffset
,
401 /* Null-terminate them */
402 *((PWSTR
)(StringBuffer
+ ExtraStringLength
)) = L
'\0';
405 /* Set the driver name length */
406 ErrorMessage
->DriverNameLength
= (USHORT
)DriverNameLength
;
408 /* Update the message length to include the device and driver names */
409 MessageLength
+= DeviceNameLength
+ DriverNameLength
;
410 ErrorMessage
->Size
= (USHORT
)MessageLength
;
412 /* Now update it again, internally, for the size of the actual LPC */
413 MessageLength
+= (FIELD_OFFSET(ELF_API_MSG
, IoErrorMessage
) -
414 FIELD_OFFSET(ELF_API_MSG
, Unknown
[0]));
416 /* Set the total and data lengths */
417 Message
->h
.u1
.s1
.TotalLength
= (USHORT
)(sizeof(PORT_MESSAGE
) +
419 Message
->h
.u1
.s1
.DataLength
= (USHORT
)(MessageLength
);
421 /* Send the message */
422 Status
= NtRequestPort(IopLogPort
, (PPORT_MESSAGE
)Message
);
423 if (!NT_SUCCESS(Status
))
425 /* Requeue log message and restart the worker */
426 ExInterlockedInsertTailList(&IopErrorLogListHead
,
427 &LogEntry
->ListEntry
,
429 IopLogWorkerRunning
= FALSE
;
430 IopRestartLogWorker();
434 /* Dereference the device object */
435 if (LogEntry
->DeviceObject
) ObDereferenceObject(LogEntry
->DeviceObject
);
436 if (DriverObject
) ObDereferenceObject(LogEntry
->DriverObject
);
439 InterlockedExchangeAdd(&IopTotalLogSize
,
440 -(LONG
)(LogEntry
->Size
-
441 sizeof(ERROR_LOG_ENTRY
)));
444 /* Free the LPC Message */
450 IopFreeApc(IN PKAPC Apc
,
451 IN PKNORMAL_ROUTINE
*NormalRoutine
,
452 IN PVOID
*NormalContext
,
453 IN PVOID
*SystemArgument1
,
454 IN PVOID
*SystemArgument2
)
462 IopRaiseHardError(IN PKAPC Apc
,
463 IN PKNORMAL_ROUTINE
*NormalRoutine
,
464 IN PVOID
*NormalContext
,
465 IN PVOID
*SystemArgument1
,
466 IN PVOID
*SystemArgument2
)
468 PIRP Irp
= (PIRP
)NormalContext
;
469 //PVPB Vpb = (PVPB)SystemArgument1;
470 //PDEVICE_OBJECT DeviceObject = (PDEVICE_OBJECT)SystemArgument2;
474 /* FIXME: UNIMPLEMENTED */
475 Irp
->IoStatus
.Status
= STATUS_NOT_IMPLEMENTED
;
476 Irp
->IoStatus
.Information
= 0;
477 IoCompleteRequest(Irp
, IO_DISK_INCREMENT
);
480 /* PUBLIC FUNCTIONS **********************************************************/
487 IoAllocateErrorLogEntry(IN PVOID IoObject
,
490 PERROR_LOG_ENTRY LogEntry
;
492 PDEVICE_OBJECT DeviceObject
;
493 PDRIVER_OBJECT DriverObject
;
495 /* Make sure we have an object */
496 if (!IoObject
) return NULL
;
498 /* Check if we're past our buffer */
499 if (IopTotalLogSize
> PAGE_SIZE
) return NULL
;
501 /* Check if this is a device object or driver object */
502 if (((PDEVICE_OBJECT
)IoObject
)->Type
== IO_TYPE_DEVICE
)
504 /* It's a device, get the driver */
505 DeviceObject
= (PDEVICE_OBJECT
)IoObject
;
506 DriverObject
= DeviceObject
->DriverObject
;
508 else if (((PDEVICE_OBJECT
)IoObject
)->Type
== IO_TYPE_DRIVER
)
510 /* It's a driver, so we don't have a device */
512 DriverObject
= (PDRIVER_OBJECT
)IoObject
;
520 /* Calculate the total size and allocate it */
521 LogEntrySize
= sizeof(ERROR_LOG_ENTRY
) + EntrySize
;
522 LogEntry
= ExAllocatePoolWithTag(NonPagedPool
,
525 if (!LogEntry
) return NULL
;
527 /* Reference the Objects */
528 if (DeviceObject
) ObReferenceObject(DeviceObject
);
529 if (DriverObject
) ObReferenceObject(DriverObject
);
531 /* Update log size */
532 InterlockedExchangeAdd(&IopTotalLogSize
, EntrySize
);
534 /* Clear the entry and set it up */
535 RtlZeroMemory(LogEntry
, EntrySize
);
536 LogEntry
->Type
= IO_TYPE_ERROR_LOG
;
537 LogEntry
->Size
= EntrySize
;
538 LogEntry
->DeviceObject
= DeviceObject
;
539 LogEntry
->DriverObject
= DriverObject
;
541 /* Return the entry data */
542 return (PVOID
)((ULONG_PTR
)LogEntry
+ sizeof(ERROR_LOG_ENTRY
));
550 IoFreeErrorLogEntry(IN PVOID ElEntry
)
552 PERROR_LOG_ENTRY LogEntry
;
554 /* Make sure there's an entry */
555 if (!ElEntry
) return;
557 /* Get the actual header */
558 LogEntry
= (PERROR_LOG_ENTRY
)((ULONG_PTR
)ElEntry
- sizeof(ERROR_LOG_ENTRY
));
560 /* Dereference both objects */
561 if (LogEntry
->DeviceObject
) ObDereferenceObject(LogEntry
->DeviceObject
);
562 if (LogEntry
->DriverObject
) ObDereferenceObject(LogEntry
->DriverObject
);
564 /* Decrease total allocation size and free the entry */
565 InterlockedExchangeAdd(&IopTotalLogSize
,
566 -(LONG
)(LogEntry
->Size
- sizeof(ERROR_LOG_ENTRY
)));
567 ExFreePool(LogEntry
);
575 IoWriteErrorLogEntry(IN PVOID ElEntry
)
577 PERROR_LOG_ENTRY LogEntry
;
580 /* Get the main header */
581 LogEntry
= (PERROR_LOG_ENTRY
)((ULONG_PTR
)ElEntry
-
582 sizeof(ERROR_LOG_ENTRY
));
585 KeQuerySystemTime(&LogEntry
->TimeStamp
);
587 /* Acquire the lock and insert this write in the list */
588 KeAcquireSpinLock(&IopLogListLock
, &Irql
);
589 InsertHeadList(&IopErrorLogListHead
, &LogEntry
->ListEntry
);
591 /* Check if the worker is running */
592 if (!IopLogWorkerRunning
)
595 /* It's not, initialize it and queue it */
596 ExInitializeWorkItem(&IopErrorLogWorkItem
,
598 &IopErrorLogWorkItem
);
599 ExQueueWorkItem(&IopErrorLogWorkItem
, DelayedWorkQueue
);
600 IopLogWorkerRunning
= TRUE
;
604 /* Release the lock and return */
605 KeReleaseSpinLock(&IopLogListLock
, Irql
);
613 IoRaiseHardError(IN PIRP Irp
,
615 IN PDEVICE_OBJECT RealDeviceObject
)
617 PETHREAD Thread
= (PETHREAD
)&Irp
->Tail
.Overlay
.Thread
;
620 /* Don't do anything if hard errors are disabled on the thread */
621 if (Thread
->HardErrorsAreDisabled
)
623 /* Complete the request */
624 Irp
->IoStatus
.Information
= 0;
625 IoCompleteRequest(Irp
, IO_DISK_INCREMENT
);
630 ErrorApc
= ExAllocatePoolWithTag(NonPagedPool
,
633 KeInitializeApc(ErrorApc
,
637 (PKRUNDOWN_ROUTINE
)IopFreeApc
,
638 (PKNORMAL_ROUTINE
)IopRaiseHardError
,
642 /* Queue an APC to deal with the error (see osr documentation) */
643 KeInsertQueueApc(ErrorApc
, Vpb
, RealDeviceObject
, 0);
651 IoRaiseInformationalHardError(IN NTSTATUS ErrorStatus
,
652 IN PUNICODE_STRING String
,
664 IoSetThreadHardErrorMode(IN BOOLEAN HardErrorEnabled
)
666 PETHREAD Thread
= PsGetCurrentThread();
669 /* Get the current value */
670 OldMode
= !Thread
->HardErrorsAreDisabled
;
672 /* Set the new one and return the old */
673 Thread
->HardErrorsAreDisabled
= !HardErrorEnabled
;