2 * PROJECT: ReactOS Kernel
3 * LICENSE: GPL - See COPYING in the top level directory
4 * FILE: ntoskrnl/io/iomgr/error.c
5 * PURPOSE: I/O Error Functions and Error Log Support
6 * PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
10 /* INCLUDES *****************************************************************/
13 #include <subsys/iolog/iolog.h>
18 /* TYPES *********************************************************************/
20 typedef struct _IOP_ERROR_LOG_WORKER_DPC
24 } IOP_ERROR_LOG_WORKER_DPC
, *PIOP_ERROR_LOG_WORKER_DPC
;
26 /* GLOBALS *******************************************************************/
28 #define IOP_MAXIMUM_LOG_SIZE (100 * PAGE_SIZE)
30 LIST_ENTRY IopErrorLogListHead
;
31 KSPIN_LOCK IopLogListLock
;
33 BOOLEAN IopLogWorkerRunning
;
34 BOOLEAN IopLogPortConnected
;
36 WORK_QUEUE_ITEM IopErrorLogWorkItem
;
38 PDEVICE_OBJECT IopErrorLogObject
;
40 /* PRIVATE FUNCTIONS *********************************************************/
44 IopLogDpcRoutine(IN PKDPC Dpc
,
45 IN PVOID DeferredContext
,
46 IN PVOID SystemArgument1
,
47 IN PVOID SystemArgument2
)
49 /* If we have a DPC, free it */
50 if (Dpc
) ExFreePool(Dpc
);
52 /* Initialize and queue the work item */
53 ExInitializeWorkItem(&IopErrorLogWorkItem
, IopLogWorker
, NULL
);
54 ExQueueWorkItem(&IopErrorLogWorkItem
, DelayedWorkQueue
);
59 IopGetErrorLogEntry(VOID
)
62 PLIST_ENTRY ListEntry
;
64 /* Acquire the lock and check if the list is empty */
65 KeAcquireSpinLock(&IopLogListLock
, &OldIrql
);
66 if (IsListEmpty(&IopErrorLogListHead
))
68 /* List is empty, disable the worker and return NULL */
69 IopLogWorkerRunning
= FALSE
;
74 /* Otherwise, remove an entry */
75 ListEntry
= RemoveHeadList(&IopErrorLogListHead
);
78 /* Release the lock and return the entry */
79 KeReleaseSpinLock(&IopLogListLock
, OldIrql
);
85 IopRestartLogWorker(VOID
)
87 PIOP_ERROR_LOG_WORKER_DPC WorkerDpc
;
88 LARGE_INTEGER Timeout
;
90 /* Allocate a DPC Context */
91 WorkerDpc
= ExAllocatePool(NonPagedPool
, sizeof(IOP_ERROR_LOG_WORKER_DPC
));
95 IopLogWorkerRunning
= FALSE
;
99 /* Initialize DPC and Timer */
100 KeInitializeDpc(&WorkerDpc
->Dpc
, IopLogDpcRoutine
, WorkerDpc
);
101 KeInitializeTimer(&WorkerDpc
->Timer
);
103 /* Restart after 30 seconds */
104 Timeout
.QuadPart
= (LONGLONG
)-300000000;
105 KeSetTimer(&WorkerDpc
->Timer
, Timeout
, &WorkerDpc
->Dpc
);
110 IopConnectLogPort(VOID
)
113 UNICODE_STRING PortName
= RTL_CONSTANT_STRING(ELF_PORT_NAME
);
114 SECURITY_QUALITY_OF_SERVICE SecurityQos
;
116 /* Make sure we're not already connected */
117 if (IopLogPortConnected
) return TRUE
;
119 /* Setup the QoS structure */
120 SecurityQos
.Length
= sizeof(SecurityQos
);
121 SecurityQos
.ImpersonationLevel
= SecurityIdentification
;
122 SecurityQos
.ContextTrackingMode
= SECURITY_DYNAMIC_TRACKING
;
123 SecurityQos
.EffectiveOnly
= TRUE
;
125 /* Connect the port */
126 Status
= ZwConnectPort(&IopLogPort
,
134 if (NT_SUCCESS(Status
))
136 /* Remember we're connected */
137 IopLogPortConnected
= TRUE
;
141 /* We failed, try again */
142 IopRestartLogWorker();
148 IopLogWorker(IN PVOID Parameter
)
150 #define IO_ERROR_OBJECT_NAMES_LENGTH 100
153 PELF_API_MSG Message
;
154 PIO_ERROR_LOG_MESSAGE ErrorMessage
;
155 PLIST_ENTRY ListEntry
;
156 PERROR_LOG_ENTRY LogEntry
;
157 PIO_ERROR_LOG_PACKET Packet
;
159 ULONG RemainingLength
;
160 PDRIVER_OBJECT DriverObject
;
162 ULONG DriverNameLength
, DeviceNameLength
;
163 UCHAR Buffer
[sizeof(OBJECT_NAME_INFORMATION
) + IO_ERROR_OBJECT_NAMES_LENGTH
];
164 POBJECT_NAME_INFORMATION ObjectNameInfo
= (POBJECT_NAME_INFORMATION
)&Buffer
;
165 POBJECT_NAME_INFORMATION PoolObjectNameInfo
;
166 ULONG ReturnedLength
, MessageLength
;
167 ULONG ExtraStringLength
;
172 UNREFERENCED_PARAMETER(Parameter
);
174 /* Connect to the port */
175 if (!IopConnectLogPort()) return;
177 /* Allocate the message */
178 Message
= ExAllocatePoolWithTag(PagedPool
, IO_ERROR_LOG_MESSAGE_LENGTH
, TAG_IO
);
181 /* Couldn't allocate, try again */
182 IopRestartLogWorker();
186 /* Zero out the message and get the actual I/O structure */
187 RtlZeroMemory(Message
, sizeof(*Message
));
188 ErrorMessage
= &Message
->IoErrorMessage
;
194 ListEntry
= IopGetErrorLogEntry();
195 if (!ListEntry
) break;
196 LogEntry
= CONTAINING_RECORD(ListEntry
, ERROR_LOG_ENTRY
, ListEntry
);
198 /* Get pointer to the log packet */
199 Packet
= (PIO_ERROR_LOG_PACKET
)((ULONG_PTR
)LogEntry
+
200 sizeof(ERROR_LOG_ENTRY
));
202 /* Calculate the total length of the message only */
203 MessageLength
= sizeof(IO_ERROR_LOG_MESSAGE
) -
204 sizeof(ERROR_LOG_ENTRY
) -
205 sizeof(IO_ERROR_LOG_PACKET
) +
208 /* Copy the packet */
209 RtlCopyMemory(&ErrorMessage
->EntryData
,
211 LogEntry
->Size
- sizeof(ERROR_LOG_ENTRY
));
213 /* Set the timestamp and time */
214 ErrorMessage
->TimeStamp
= LogEntry
->TimeStamp
;
215 ErrorMessage
->Type
= IO_TYPE_ERROR_MESSAGE
;
217 /* Check if this message has any strings */
218 if (Packet
->NumberOfStrings
)
220 /* String buffer is after the current strings */
221 StringBuffer
= (PCHAR
)&ErrorMessage
->EntryData
+
222 Packet
->StringOffset
;
226 /* Otherwise, string buffer is at the end */
227 StringBuffer
= (PCHAR
)ErrorMessage
+ MessageLength
;
230 /* Align the buffer */
231 StringBuffer
= ALIGN_UP_POINTER(StringBuffer
, WCHAR
);
233 /* Set the offset for the driver's name to the current buffer */
234 ErrorMessage
->DriverNameOffset
= (ULONG
)(StringBuffer
-
235 (PCHAR
)ErrorMessage
);
237 /* Check how much space we have left for the device string */
238 RemainingLength
= (ULONG
)((ULONG_PTR
)Message
+
239 IO_ERROR_LOG_MESSAGE_LENGTH
-
240 (ULONG_PTR
)StringBuffer
);
243 DriverNameLength
= 0; DeviceNameLength
= 0;
244 PoolObjectNameInfo
= NULL
;
245 ObjectNameInfo
= (POBJECT_NAME_INFORMATION
)&Buffer
;
247 /* Now check if there is a driver object */
248 DriverObject
= LogEntry
->DriverObject
;
251 /* Check if the driver has a name, and use it if so */
252 if (DriverObject
->DriverName
.Buffer
)
254 NameString
= DriverObject
->DriverName
.Buffer
;
255 DriverNameLength
= DriverObject
->DriverName
.Length
;
260 DriverNameLength
= 0;
263 /* Check if there isn't a valid name */
264 if (!DriverNameLength
)
266 /* Query the name directly */
267 Status
= ObQueryNameString(DriverObject
,
271 if (!NT_SUCCESS(Status
) || (ObjectNameInfo
->Name
.Length
== 0))
273 /* We don't have a name */
274 DriverNameLength
= 0;
278 NameString
= ObjectNameInfo
->Name
.Buffer
;
279 DriverNameLength
= ObjectNameInfo
->Name
.Length
;
285 /* Use default name */
286 NameString
= L
"Application Popup";
287 DriverNameLength
= (ULONG
)wcslen(NameString
) * sizeof(WCHAR
);
290 /* Check if we have a driver name */
291 if (DriverNameLength
)
293 /* Skip to the end of the driver's name */
294 p
= &NameString
[DriverNameLength
/ sizeof(WCHAR
)];
296 /* Now we'll walk backwards and assume the minimum size */
297 DriverNameLength
= sizeof(WCHAR
);
299 while ((*p
!= L
'\\') && (p
!= NameString
))
301 /* No backslash found, keep going */
303 DriverNameLength
+= sizeof(WCHAR
);
306 /* Now we probably hit the backslash itself, skip past it */
310 DriverNameLength
-= sizeof(WCHAR
);
314 * Now make sure that the driver name fits in the buffer, minus
315 * 3 NULL chars (driver name, device name, and remaining strings),
316 * and copy the driver name in the string buffer.
318 DriverNameLength
= min(DriverNameLength
,
319 RemainingLength
- 3 * sizeof(UNICODE_NULL
));
320 RtlCopyMemory(StringBuffer
, p
, DriverNameLength
);
323 /* Null-terminate the driver name */
324 *((PWSTR
)(StringBuffer
+ DriverNameLength
)) = UNICODE_NULL
;
325 DriverNameLength
+= sizeof(WCHAR
);
327 /* Go to the next string buffer position */
328 StringBuffer
+= DriverNameLength
;
329 RemainingLength
-= DriverNameLength
;
331 /* Update the string offset */
332 ErrorMessage
->EntryData
.StringOffset
=
333 (USHORT
)((ULONG_PTR
)StringBuffer
- (ULONG_PTR
)ErrorMessage
);
335 /* Check if we have a device object */
336 if (LogEntry
->DeviceObject
)
338 /* We do, query its name */
339 Status
= ObQueryNameString(LogEntry
->DeviceObject
,
341 sizeof(Buffer
) - DriverNameLength
,
343 if (!NT_SUCCESS(Status
) || (ObjectNameInfo
->Name
.Length
== 0))
345 /* Setup an empty name */
346 RtlInitEmptyUnicodeString(&ObjectNameInfo
->Name
, L
"", 0);
348 /* Check if we failed because our buffer wasn't large enough */
349 if (Status
== STATUS_INFO_LENGTH_MISMATCH
)
351 /* Then we'll allocate one... we really want this name! */
352 PoolObjectNameInfo
= ExAllocatePoolWithTag(PagedPool
,
355 if (PoolObjectNameInfo
)
358 ObjectNameInfo
= PoolObjectNameInfo
;
359 Status
= ObQueryNameString(LogEntry
->DeviceObject
,
363 if (NT_SUCCESS(Status
))
365 /* Success, update the information */
366 ObjectNameInfo
->Name
.Length
=
367 IO_ERROR_OBJECT_NAMES_LENGTH
- (USHORT
)DriverNameLength
;
373 NameString
= ObjectNameInfo
->Name
.Buffer
;
374 DeviceNameLength
= ObjectNameInfo
->Name
.Length
;
378 /* No device object, setup an empty name */
380 DeviceNameLength
= 0;
384 * Now make sure that the device name fits in the buffer, minus
385 * 2 NULL chars (device name, and remaining strings), and copy
386 * the device name in the string buffer.
388 DeviceNameLength
= min(DeviceNameLength
,
389 RemainingLength
- 2 * sizeof(UNICODE_NULL
));
390 RtlCopyMemory(StringBuffer
, NameString
, DeviceNameLength
);
392 /* Null-terminate the device name */
393 *((PWSTR
)(StringBuffer
+ DeviceNameLength
)) = UNICODE_NULL
;
394 DeviceNameLength
+= sizeof(WCHAR
);
396 /* Free the buffer if we had one */
397 if (PoolObjectNameInfo
)
399 ExFreePoolWithTag(PoolObjectNameInfo
, TAG_IO
);
400 PoolObjectNameInfo
= NULL
;
403 /* Go to the next string buffer position */
404 ErrorMessage
->EntryData
.NumberOfStrings
++;
405 StringBuffer
+= DeviceNameLength
;
406 RemainingLength
-= DeviceNameLength
;
408 /* Check if we have any extra strings */
409 if (Packet
->NumberOfStrings
)
411 /* Find out the size of the extra strings */
412 ExtraStringLength
= LogEntry
->Size
-
413 sizeof(ERROR_LOG_ENTRY
) -
414 Packet
->StringOffset
;
416 /* Round up the length */
417 ExtraStringLength
= ROUND_UP(ExtraStringLength
, sizeof(WCHAR
));
419 /* Make sure that the extra strings fit in our buffer */
420 if (ExtraStringLength
> (RemainingLength
- sizeof(UNICODE_NULL
)))
422 /* They wouldn't, so set normalize the length */
423 MessageLength
-= ExtraStringLength
- RemainingLength
;
424 ExtraStringLength
= RemainingLength
- sizeof(UNICODE_NULL
);
427 /* Now copy the extra strings */
428 RtlCopyMemory(StringBuffer
,
429 (PCHAR
)Packet
+ Packet
->StringOffset
,
432 /* Null-terminate them */
433 *((PWSTR
)(StringBuffer
+ ExtraStringLength
)) = UNICODE_NULL
;
436 /* Set the driver name length */
437 ErrorMessage
->DriverNameLength
= (USHORT
)DriverNameLength
;
439 /* Update the message length to include the driver and device names */
440 MessageLength
+= DriverNameLength
+ DeviceNameLength
;
441 ErrorMessage
->Size
= (USHORT
)MessageLength
;
443 /* Now update it again for the size of the actual LPC */
444 MessageLength
+= (FIELD_OFFSET(ELF_API_MSG
, IoErrorMessage
) -
445 FIELD_OFFSET(ELF_API_MSG
, Unknown
[0]));
447 /* Set the total and data lengths */
448 Message
->Header
.u1
.s1
.TotalLength
=
449 (USHORT
)(sizeof(PORT_MESSAGE
) + MessageLength
);
450 Message
->Header
.u1
.s1
.DataLength
= (USHORT
)MessageLength
;
452 /* Send the message */
453 Status
= ZwRequestPort(IopLogPort
, &Message
->Header
);
454 if (!NT_SUCCESS(Status
))
457 * An error happened while sending the message on the port.
458 * Close the port, requeue the log message on top of the list
459 * and restart the worker.
462 IopLogPortConnected
= FALSE
;
464 ExInterlockedInsertHeadList(&IopErrorLogListHead
,
465 &LogEntry
->ListEntry
,
468 IopRestartLogWorker();
472 /* NOTE: The following is basically 'IoFreeErrorLogEntry(Packet)' */
474 /* Dereference both objects */
475 if (LogEntry
->DeviceObject
) ObDereferenceObject(LogEntry
->DeviceObject
);
476 if (LogEntry
->DriverObject
) ObDereferenceObject(LogEntry
->DriverObject
);
478 /* Decrease the total allocation size and free the entry */
479 InterlockedExchangeAdd(&IopTotalLogSize
, -(LONG
)LogEntry
->Size
);
480 ExFreePoolWithTag(LogEntry
, TAG_ERROR_LOG
);
483 /* Free the LPC Message */
484 ExFreePoolWithTag(Message
, TAG_IO
);
489 IopFreeApc(IN PKAPC Apc
,
490 IN PKNORMAL_ROUTINE
*NormalRoutine
,
491 IN PVOID
*NormalContext
,
492 IN PVOID
*SystemArgument1
,
493 IN PVOID
*SystemArgument2
)
501 IopRaiseHardError(IN PKAPC Apc
,
502 IN PKNORMAL_ROUTINE
*NormalRoutine
,
503 IN PVOID
*NormalContext
,
504 IN PVOID
*SystemArgument1
,
505 IN PVOID
*SystemArgument2
)
507 PIRP Irp
= (PIRP
)NormalContext
;
508 //PVPB Vpb = (PVPB)SystemArgument1;
509 //PDEVICE_OBJECT DeviceObject = (PDEVICE_OBJECT)SystemArgument2;
513 /* FIXME: UNIMPLEMENTED */
514 Irp
->IoStatus
.Status
= STATUS_NOT_IMPLEMENTED
;
515 Irp
->IoStatus
.Information
= 0;
516 IoCompleteRequest(Irp
, IO_DISK_INCREMENT
);
519 /* PUBLIC FUNCTIONS **********************************************************/
526 IoAllocateErrorLogEntry(IN PVOID IoObject
,
529 PERROR_LOG_ENTRY LogEntry
;
531 PDEVICE_OBJECT DeviceObject
;
532 PDRIVER_OBJECT DriverObject
;
534 /* Make sure we have an object */
535 if (!IoObject
) return NULL
;
537 /* Check if this is a device object or driver object */
538 DeviceObject
= (PDEVICE_OBJECT
)IoObject
;
539 if (DeviceObject
->Type
== IO_TYPE_DEVICE
)
541 /* It's a device, get the driver */
542 // DeviceObject = (PDEVICE_OBJECT)IoObject;
543 DriverObject
= DeviceObject
->DriverObject
;
545 else if (DeviceObject
->Type
== IO_TYPE_DRIVER
)
547 /* It's a driver, so we don't have a device */
549 DriverObject
= (PDRIVER_OBJECT
)IoObject
;
557 /* Check whether the size is too small or too large */
558 if ((EntrySize
< sizeof(IO_ERROR_LOG_PACKET
)) ||
559 (EntrySize
> ERROR_LOG_MAXIMUM_SIZE
))
565 /* Round up the size and calculate the total size */
566 EntrySize
= ROUND_UP(EntrySize
, sizeof(PVOID
));
567 LogEntrySize
= sizeof(ERROR_LOG_ENTRY
) + EntrySize
;
569 /* Check if we're past our buffer */
570 // TODO: Improve (what happens in case of concurrent calls?)
571 if (IopTotalLogSize
+ LogEntrySize
> IOP_MAXIMUM_LOG_SIZE
) return NULL
;
573 /* Allocate the entry */
574 LogEntry
= ExAllocatePoolWithTag(NonPagedPool
,
577 if (!LogEntry
) return NULL
;
579 /* Reference the Objects */
580 if (DeviceObject
) ObReferenceObject(DeviceObject
);
581 if (DriverObject
) ObReferenceObject(DriverObject
);
583 /* Update log size */
584 InterlockedExchangeAdd(&IopTotalLogSize
, LogEntrySize
);
586 /* Clear the entry and set it up */
587 RtlZeroMemory(LogEntry
, LogEntrySize
);
588 LogEntry
->Type
= IO_TYPE_ERROR_LOG
;
589 LogEntry
->Size
= LogEntrySize
;
590 LogEntry
->DeviceObject
= DeviceObject
;
591 LogEntry
->DriverObject
= DriverObject
;
593 /* Return the entry data */
594 return (PVOID
)((ULONG_PTR
)LogEntry
+ sizeof(ERROR_LOG_ENTRY
));
602 IoFreeErrorLogEntry(IN PVOID ElEntry
)
604 PERROR_LOG_ENTRY LogEntry
;
606 /* Make sure there is an entry */
607 if (!ElEntry
) return;
609 /* Get the actual header */
610 LogEntry
= (PERROR_LOG_ENTRY
)((ULONG_PTR
)ElEntry
- sizeof(ERROR_LOG_ENTRY
));
612 /* Dereference both objects */
613 if (LogEntry
->DeviceObject
) ObDereferenceObject(LogEntry
->DeviceObject
);
614 if (LogEntry
->DriverObject
) ObDereferenceObject(LogEntry
->DriverObject
);
616 /* Decrease the total allocation size and free the entry */
617 InterlockedExchangeAdd(&IopTotalLogSize
, -(LONG
)LogEntry
->Size
);
618 ExFreePoolWithTag(LogEntry
, TAG_ERROR_LOG
);
626 IoWriteErrorLogEntry(IN PVOID ElEntry
)
628 PERROR_LOG_ENTRY LogEntry
;
631 /* Make sure there is an entry */
632 if (!ElEntry
) return;
634 /* Get the actual header */
635 LogEntry
= (PERROR_LOG_ENTRY
)((ULONG_PTR
)ElEntry
- sizeof(ERROR_LOG_ENTRY
));
638 KeQuerySystemTime(&LogEntry
->TimeStamp
);
640 /* Acquire the lock and insert this write in the list */
641 KeAcquireSpinLock(&IopLogListLock
, &Irql
);
642 InsertHeadList(&IopErrorLogListHead
, &LogEntry
->ListEntry
);
644 /* Check if the worker is running */
645 if (!IopLogWorkerRunning
)
647 /* It's not, initialize it and queue it */
648 IopLogWorkerRunning
= TRUE
;
649 ExInitializeWorkItem(&IopErrorLogWorkItem
, IopLogWorker
, NULL
);
650 ExQueueWorkItem(&IopErrorLogWorkItem
, DelayedWorkQueue
);
653 /* Release the lock and return */
654 KeReleaseSpinLock(&IopLogListLock
, Irql
);
662 IoRaiseHardError(IN PIRP Irp
,
664 IN PDEVICE_OBJECT RealDeviceObject
)
666 PETHREAD Thread
= (PETHREAD
)&Irp
->Tail
.Overlay
.Thread
;
669 /* Don't do anything if hard errors are disabled on the thread */
670 if (Thread
->HardErrorsAreDisabled
)
672 /* Complete the request */
673 Irp
->IoStatus
.Information
= 0;
674 IoCompleteRequest(Irp
, IO_DISK_INCREMENT
);
679 ErrorApc
= ExAllocatePoolWithTag(NonPagedPool
,
682 KeInitializeApc(ErrorApc
,
686 (PKRUNDOWN_ROUTINE
)IopFreeApc
,
687 (PKNORMAL_ROUTINE
)IopRaiseHardError
,
691 /* Queue an APC to deal with the error (see osr documentation) */
692 KeInsertQueueApc(ErrorApc
, Vpb
, RealDeviceObject
, 0);
700 IoRaiseInformationalHardError(IN NTSTATUS ErrorStatus
,
701 IN PUNICODE_STRING String
,
713 IoSetThreadHardErrorMode(IN BOOLEAN HardErrorEnabled
)
715 PETHREAD Thread
= PsGetCurrentThread();
718 /* Get the current value */
719 OldMode
= !Thread
->HardErrorsAreDisabled
;
721 /* Set the new one and return the old */
722 Thread
->HardErrorsAreDisabled
= !HardErrorEnabled
;