xref: /reactos/ntoskrnl/io/iomgr/error.c (revision 8a978a17)
1 /*
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)
7  *                  Eric Kohl
8  */
9 
10 /* INCLUDES *****************************************************************/
11 
12 #include <ntoskrnl.h>
13 #include <subsys/iolog/iolog.h>
14 
15 #define NDEBUG
16 #include <debug.h>
17 
18 /* TYPES *********************************************************************/
19 
20 typedef struct _IOP_ERROR_LOG_WORKER_DPC
21 {
22     KDPC Dpc;
23     KTIMER Timer;
24 } IOP_ERROR_LOG_WORKER_DPC, *PIOP_ERROR_LOG_WORKER_DPC;
25 
26 /* GLOBALS *******************************************************************/
27 
28 #define IOP_MAXIMUM_LOG_SIZE    (100 * PAGE_SIZE)
29 LONG IopTotalLogSize;
30 LIST_ENTRY IopErrorLogListHead;
31 KSPIN_LOCK IopLogListLock;
32 
33 BOOLEAN IopLogWorkerRunning;
34 BOOLEAN IopLogPortConnected;
35 HANDLE IopLogPort;
36 WORK_QUEUE_ITEM IopErrorLogWorkItem;
37 
38 PDEVICE_OBJECT IopErrorLogObject;
39 
40 /* PRIVATE FUNCTIONS *********************************************************/
41 
42 VOID
43 NTAPI
44 IopLogDpcRoutine(IN PKDPC Dpc,
45                  IN PVOID DeferredContext,
46                  IN PVOID SystemArgument1,
47                  IN PVOID SystemArgument2)
48 {
49     /* If we have a DPC, free it */
50     if (Dpc) ExFreePool(Dpc);
51 
52     /* Initialize and queue the work item */
53     ExInitializeWorkItem(&IopErrorLogWorkItem, IopLogWorker, NULL);
54     ExQueueWorkItem(&IopErrorLogWorkItem, DelayedWorkQueue);
55 }
56 
57 PLIST_ENTRY
58 NTAPI
59 IopGetErrorLogEntry(VOID)
60 {
61     KIRQL OldIrql;
62     PLIST_ENTRY ListEntry;
63 
64     /* Acquire the lock and check if the list is empty */
65     KeAcquireSpinLock(&IopLogListLock, &OldIrql);
66     if (IsListEmpty(&IopErrorLogListHead))
67     {
68         /* List is empty, disable the worker and return NULL */
69         IopLogWorkerRunning = FALSE;
70         ListEntry = NULL;
71     }
72     else
73     {
74         /* Otherwise, remove an entry */
75         ListEntry = RemoveHeadList(&IopErrorLogListHead);
76     }
77 
78     /* Release the lock and return the entry */
79     KeReleaseSpinLock(&IopLogListLock, OldIrql);
80     return ListEntry;
81 }
82 
83 VOID
84 NTAPI
85 IopRestartLogWorker(VOID)
86 {
87     PIOP_ERROR_LOG_WORKER_DPC WorkerDpc;
88     LARGE_INTEGER Timeout;
89 
90     /* Allocate a DPC Context */
91     WorkerDpc = ExAllocatePool(NonPagedPool, sizeof(IOP_ERROR_LOG_WORKER_DPC));
92     if (!WorkerDpc)
93     {
94         /* Fail */
95         IopLogWorkerRunning = FALSE;
96         return;
97     }
98 
99     /* Initialize DPC and Timer */
100     KeInitializeDpc(&WorkerDpc->Dpc, IopLogDpcRoutine, WorkerDpc);
101     KeInitializeTimer(&WorkerDpc->Timer);
102 
103     /* Restart after 30 seconds */
104     Timeout.QuadPart = (LONGLONG)-300000000;
105     KeSetTimer(&WorkerDpc->Timer, Timeout, &WorkerDpc->Dpc);
106 }
107 
108 BOOLEAN
109 NTAPI
110 IopConnectLogPort(VOID)
111 {
112     NTSTATUS Status;
113     UNICODE_STRING PortName = RTL_CONSTANT_STRING(ELF_PORT_NAME);
114     SECURITY_QUALITY_OF_SERVICE SecurityQos;
115 
116     /* Make sure we're not already connected */
117     if (IopLogPortConnected) return TRUE;
118 
119     /* Setup the QoS structure */
120     SecurityQos.Length = sizeof(SecurityQos);
121     SecurityQos.ImpersonationLevel = SecurityIdentification;
122     SecurityQos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
123     SecurityQos.EffectiveOnly = TRUE;
124 
125     /* Connect the port */
126     Status = ZwConnectPort(&IopLogPort,
127                            &PortName,
128                            &SecurityQos,
129                            NULL,
130                            NULL,
131                            NULL,
132                            NULL,
133                            NULL);
134     if (NT_SUCCESS(Status))
135     {
136         /* Remember we're connected */
137         IopLogPortConnected = TRUE;
138         return TRUE;
139     }
140 
141     /* We failed, try again */
142     IopRestartLogWorker();
143     return FALSE;
144 }
145 
146 VOID
147 NTAPI
148 IopLogWorker(IN PVOID Parameter)
149 {
150 #define IO_ERROR_OBJECT_NAMES_LENGTH    100
151 
152     NTSTATUS Status;
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;
158     PCHAR StringBuffer;
159     ULONG RemainingLength;
160     PDRIVER_OBJECT DriverObject;
161     PWCHAR NameString;
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;
168     PWCHAR p;
169 
170     PAGED_CODE();
171 
172     UNREFERENCED_PARAMETER(Parameter);
173 
174     /* Connect to the port */
175     if (!IopConnectLogPort()) return;
176 
177     /* Allocate the message */
178     Message = ExAllocatePoolWithTag(PagedPool, IO_ERROR_LOG_MESSAGE_LENGTH, TAG_IO);
179     if (!Message)
180     {
181         /* Couldn't allocate, try again */
182         IopRestartLogWorker();
183         return;
184     }
185 
186     /* Zero out the message and get the actual I/O structure */
187     RtlZeroMemory(Message, sizeof(*Message));
188     ErrorMessage = &Message->IoErrorMessage;
189 
190     /* Start loop */
191     while (TRUE)
192     {
193         /* Get an entry */
194         ListEntry = IopGetErrorLogEntry();
195         if (!ListEntry) break;
196         LogEntry = CONTAINING_RECORD(ListEntry, ERROR_LOG_ENTRY, ListEntry);
197 
198         /* Get pointer to the log packet */
199         Packet = (PIO_ERROR_LOG_PACKET)((ULONG_PTR)LogEntry +
200                                         sizeof(ERROR_LOG_ENTRY));
201 
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) +
206                         LogEntry->Size;
207 
208         /* Copy the packet */
209         RtlCopyMemory(&ErrorMessage->EntryData,
210                       Packet,
211                       LogEntry->Size - sizeof(ERROR_LOG_ENTRY));
212 
213         /* Set the timestamp and time */
214         ErrorMessage->TimeStamp = LogEntry->TimeStamp;
215         ErrorMessage->Type = IO_TYPE_ERROR_MESSAGE;
216 
217         /* Check if this message has any strings */
218         if (Packet->NumberOfStrings)
219         {
220             /* String buffer is after the current strings */
221             StringBuffer = (PCHAR)&ErrorMessage->EntryData +
222                             Packet->StringOffset;
223         }
224         else
225         {
226             /* Otherwise, string buffer is at the end */
227             StringBuffer = (PCHAR)ErrorMessage + MessageLength;
228         }
229 
230         /* Align the buffer */
231         StringBuffer = ALIGN_UP_POINTER(StringBuffer, WCHAR);
232 
233         /* Set the offset for the driver's name to the current buffer */
234         ErrorMessage->DriverNameOffset = (ULONG)(StringBuffer -
235                                                 (PCHAR)ErrorMessage);
236 
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);
241 
242         NameString = NULL;
243         DriverNameLength = 0; DeviceNameLength = 0;
244         PoolObjectNameInfo = NULL;
245         ObjectNameInfo = (POBJECT_NAME_INFORMATION)&Buffer;
246 
247         /* Now check if there is a driver object */
248         DriverObject = LogEntry->DriverObject;
249         if (DriverObject)
250         {
251             /* Check if the driver has a name, and use it if so */
252             if (DriverObject->DriverName.Buffer)
253             {
254                 NameString = DriverObject->DriverName.Buffer;
255                 DriverNameLength = DriverObject->DriverName.Length;
256             }
257             else
258             {
259                 NameString = NULL;
260                 DriverNameLength = 0;
261             }
262 
263             /* Check if there isn't a valid name */
264             if (!DriverNameLength)
265             {
266                 /* Query the name directly */
267                 Status = ObQueryNameString(DriverObject,
268                                            ObjectNameInfo,
269                                            sizeof(Buffer),
270                                            &ReturnedLength);
271                 if (!NT_SUCCESS(Status) || (ObjectNameInfo->Name.Length == 0))
272                 {
273                     /* We don't have a name */
274                     DriverNameLength = 0;
275                 }
276                 else
277                 {
278                     NameString = ObjectNameInfo->Name.Buffer;
279                     DriverNameLength = ObjectNameInfo->Name.Length;
280                 }
281             }
282         }
283         else
284         {
285             /* Use default name */
286             NameString = L"Application Popup";
287             DriverNameLength = (ULONG)wcslen(NameString) * sizeof(WCHAR);
288         }
289 
290         /* Check if we have a driver name */
291         if (DriverNameLength)
292         {
293             /* Skip to the end of the driver's name */
294             p = &NameString[DriverNameLength / sizeof(WCHAR)];
295 
296             /* Now we'll walk backwards and assume the minimum size */
297             DriverNameLength = sizeof(WCHAR);
298             p--;
299             while ((*p != L'\\') && (p != NameString))
300             {
301                 /* No backslash found, keep going */
302                 p--;
303                 DriverNameLength += sizeof(WCHAR);
304             }
305 
306             /* Now we probably hit the backslash itself, skip past it */
307             if (*p == L'\\')
308             {
309                 p++;
310                 DriverNameLength -= sizeof(WCHAR);
311             }
312 
313             /*
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.
317              */
318             DriverNameLength = min(DriverNameLength,
319                                    RemainingLength - 3 * sizeof(UNICODE_NULL));
320             RtlCopyMemory(StringBuffer, p, DriverNameLength);
321         }
322 
323         /* Null-terminate the driver name */
324         *((PWSTR)(StringBuffer + DriverNameLength)) = UNICODE_NULL;
325         DriverNameLength += sizeof(WCHAR);
326 
327         /* Go to the next string buffer position */
328         StringBuffer += DriverNameLength;
329         RemainingLength -= DriverNameLength;
330 
331         /* Update the string offset */
332         ErrorMessage->EntryData.StringOffset =
333             (USHORT)((ULONG_PTR)StringBuffer - (ULONG_PTR)ErrorMessage);
334 
335         /* Check if we have a device object */
336         if (LogEntry->DeviceObject)
337         {
338             /* We do, query its name */
339             Status = ObQueryNameString(LogEntry->DeviceObject,
340                                        ObjectNameInfo,
341                                        sizeof(Buffer) - DriverNameLength,
342                                        &ReturnedLength);
343             if (!NT_SUCCESS(Status) || (ObjectNameInfo->Name.Length == 0))
344             {
345                 /* Setup an empty name */
346                 RtlInitEmptyUnicodeString(&ObjectNameInfo->Name, L"", 0);
347 
348                 /* Check if we failed because our buffer wasn't large enough */
349                 if (Status == STATUS_INFO_LENGTH_MISMATCH)
350                 {
351                     /* Then we'll allocate one... we really want this name! */
352                     PoolObjectNameInfo = ExAllocatePoolWithTag(PagedPool,
353                                                                ReturnedLength,
354                                                                TAG_IO);
355                     if (PoolObjectNameInfo)
356                     {
357                         /* Query it again */
358                         ObjectNameInfo = PoolObjectNameInfo;
359                         Status = ObQueryNameString(LogEntry->DeviceObject,
360                                                    ObjectNameInfo,
361                                                    ReturnedLength,
362                                                    &ReturnedLength);
363                         if (NT_SUCCESS(Status))
364                         {
365                             /* Success, update the information */
366                             ObjectNameInfo->Name.Length =
367                                 IO_ERROR_OBJECT_NAMES_LENGTH - (USHORT)DriverNameLength;
368                         }
369                     }
370                 }
371             }
372 
373             NameString = ObjectNameInfo->Name.Buffer;
374             DeviceNameLength = ObjectNameInfo->Name.Length;
375         }
376         else
377         {
378             /* No device object, setup an empty name */
379             NameString = L"";
380             DeviceNameLength = 0;
381         }
382 
383         /*
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.
387          */
388         DeviceNameLength = min(DeviceNameLength,
389                                RemainingLength - 2 * sizeof(UNICODE_NULL));
390         RtlCopyMemory(StringBuffer, NameString, DeviceNameLength);
391 
392         /* Null-terminate the device name */
393         *((PWSTR)(StringBuffer + DeviceNameLength)) = UNICODE_NULL;
394         DeviceNameLength += sizeof(WCHAR);
395 
396         /* Free the buffer if we had one */
397         if (PoolObjectNameInfo)
398         {
399             ExFreePoolWithTag(PoolObjectNameInfo, TAG_IO);
400             PoolObjectNameInfo = NULL;
401         }
402 
403         /* Go to the next string buffer position */
404         ErrorMessage->EntryData.NumberOfStrings++;
405         StringBuffer += DeviceNameLength;
406         RemainingLength -= DeviceNameLength;
407 
408         /* Check if we have any extra strings */
409         if (Packet->NumberOfStrings)
410         {
411             /* Find out the size of the extra strings */
412             ExtraStringLength = LogEntry->Size -
413                                 sizeof(ERROR_LOG_ENTRY) -
414                                 Packet->StringOffset;
415 
416             /* Round up the length */
417             ExtraStringLength = ROUND_UP(ExtraStringLength, sizeof(WCHAR));
418 
419             /* Make sure that the extra strings fit in our buffer */
420             if (ExtraStringLength > (RemainingLength - sizeof(UNICODE_NULL)))
421             {
422                 /* They wouldn't, so set normalize the length */
423                 MessageLength -= ExtraStringLength - RemainingLength;
424                 ExtraStringLength = RemainingLength - sizeof(UNICODE_NULL);
425             }
426 
427             /* Now copy the extra strings */
428             RtlCopyMemory(StringBuffer,
429                           (PCHAR)Packet + Packet->StringOffset,
430                           ExtraStringLength);
431 
432             /* Null-terminate them */
433             *((PWSTR)(StringBuffer + ExtraStringLength)) = UNICODE_NULL;
434         }
435 
436         /* Set the driver name length */
437         ErrorMessage->DriverNameLength = (USHORT)DriverNameLength;
438 
439         /* Update the message length to include the driver and device names */
440         MessageLength += DriverNameLength + DeviceNameLength;
441         ErrorMessage->Size = (USHORT)MessageLength;
442 
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]));
446 
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;
451 
452         /* Send the message */
453         Status = ZwRequestPort(IopLogPort, &Message->Header);
454         if (!NT_SUCCESS(Status))
455         {
456             /*
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.
460              */
461             ZwClose(IopLogPort);
462             IopLogPortConnected = FALSE;
463 
464             ExInterlockedInsertHeadList(&IopErrorLogListHead,
465                                         &LogEntry->ListEntry,
466                                         &IopLogListLock);
467 
468             IopRestartLogWorker();
469             break;
470         }
471 
472         /* NOTE: The following is basically 'IoFreeErrorLogEntry(Packet)' */
473 
474         /* Dereference both objects */
475         if (LogEntry->DeviceObject) ObDereferenceObject(LogEntry->DeviceObject);
476         if (LogEntry->DriverObject) ObDereferenceObject(LogEntry->DriverObject);
477 
478         /* Decrease the total allocation size and free the entry */
479         InterlockedExchangeAdd(&IopTotalLogSize, -(LONG)LogEntry->Size);
480         ExFreePoolWithTag(LogEntry, TAG_ERROR_LOG);
481     }
482 
483     /* Free the LPC Message */
484     ExFreePoolWithTag(Message, TAG_IO);
485 }
486 
487 static
488 VOID
489 NTAPI
490 IopFreeApc(IN PKAPC Apc,
491            IN OUT PKNORMAL_ROUTINE* NormalRoutine,
492            IN OUT PVOID* NormalContext,
493            IN OUT PVOID* SystemArgument1,
494            IN OUT PVOID* SystemArgument2)
495 {
496     PAGED_CODE();
497 
498     /* Free the APC */
499     ExFreePoolWithTag(Apc, TAG_APC);
500 }
501 
502 static
503 VOID
504 NTAPI
505 IopRaiseHardError(IN PVOID NormalContext,
506                   IN PVOID SystemArgument1,
507                   IN PVOID SystemArgument2)
508 {
509     PIRP Irp = NormalContext;
510     //PVPB Vpb = SystemArgument1;
511     //PDEVICE_OBJECT DeviceObject = SystemArgument2;
512 
513     UNIMPLEMENTED;
514 
515     /* FIXME: UNIMPLEMENTED */
516     Irp->IoStatus.Status = STATUS_NOT_IMPLEMENTED;
517     Irp->IoStatus.Information = 0;
518     IoCompleteRequest(Irp, IO_DISK_INCREMENT);
519 }
520 
521 /* PUBLIC FUNCTIONS **********************************************************/
522 
523 /*
524  * @implemented
525  */
526 PVOID
527 NTAPI
528 IoAllocateErrorLogEntry(IN PVOID IoObject,
529                         IN UCHAR EntrySize)
530 {
531     PERROR_LOG_ENTRY LogEntry;
532     ULONG LogEntrySize;
533     PDEVICE_OBJECT DeviceObject;
534     PDRIVER_OBJECT DriverObject;
535 
536     /* Make sure we have an object */
537     if (!IoObject) return NULL;
538 
539     /* Check if this is a device object or driver object */
540     DeviceObject = (PDEVICE_OBJECT)IoObject;
541     if (DeviceObject->Type == IO_TYPE_DEVICE)
542     {
543         /* It's a device, get the driver */
544         // DeviceObject = (PDEVICE_OBJECT)IoObject;
545         DriverObject = DeviceObject->DriverObject;
546     }
547     else if (DeviceObject->Type == IO_TYPE_DRIVER)
548     {
549         /* It's a driver, so we don't have a device */
550         DeviceObject = NULL;
551         DriverObject = (PDRIVER_OBJECT)IoObject;
552     }
553     else
554     {
555         /* Fail */
556         return NULL;
557     }
558 
559     /* Check whether the size is too small or too large */
560     if ((EntrySize < sizeof(IO_ERROR_LOG_PACKET)) ||
561         (EntrySize > ERROR_LOG_MAXIMUM_SIZE))
562     {
563         /* Fail */
564         return NULL;
565     }
566 
567     /* Round up the size and calculate the total size */
568     EntrySize = ROUND_UP(EntrySize, sizeof(PVOID));
569     LogEntrySize = sizeof(ERROR_LOG_ENTRY) + EntrySize;
570 
571     /* Check if we're past our buffer */
572     // TODO: Improve (what happens in case of concurrent calls?)
573     if (IopTotalLogSize + LogEntrySize > IOP_MAXIMUM_LOG_SIZE) return NULL;
574 
575     /* Allocate the entry */
576     LogEntry = ExAllocatePoolWithTag(NonPagedPool,
577                                      LogEntrySize,
578                                      TAG_ERROR_LOG);
579     if (!LogEntry) return NULL;
580 
581     /* Reference the Objects */
582     if (DeviceObject) ObReferenceObject(DeviceObject);
583     if (DriverObject) ObReferenceObject(DriverObject);
584 
585     /* Update log size */
586     InterlockedExchangeAdd(&IopTotalLogSize, LogEntrySize);
587 
588     /* Clear the entry and set it up */
589     RtlZeroMemory(LogEntry, LogEntrySize);
590     LogEntry->Type = IO_TYPE_ERROR_LOG;
591     LogEntry->Size = LogEntrySize;
592     LogEntry->DeviceObject = DeviceObject;
593     LogEntry->DriverObject = DriverObject;
594 
595     /* Return the entry data */
596     return (PVOID)((ULONG_PTR)LogEntry + sizeof(ERROR_LOG_ENTRY));
597 }
598 
599 /*
600  * @implemented
601  */
602 VOID
603 NTAPI
604 IoFreeErrorLogEntry(IN PVOID ElEntry)
605 {
606     PERROR_LOG_ENTRY LogEntry;
607 
608     /* Make sure there is an entry */
609     if (!ElEntry) return;
610 
611     /* Get the actual header */
612     LogEntry = (PERROR_LOG_ENTRY)((ULONG_PTR)ElEntry - sizeof(ERROR_LOG_ENTRY));
613 
614     /* Dereference both objects */
615     if (LogEntry->DeviceObject) ObDereferenceObject(LogEntry->DeviceObject);
616     if (LogEntry->DriverObject) ObDereferenceObject(LogEntry->DriverObject);
617 
618     /* Decrease the total allocation size and free the entry */
619     InterlockedExchangeAdd(&IopTotalLogSize, -(LONG)LogEntry->Size);
620     ExFreePoolWithTag(LogEntry, TAG_ERROR_LOG);
621 }
622 
623 /*
624  * @implemented
625  */
626 VOID
627 NTAPI
628 IoWriteErrorLogEntry(IN PVOID ElEntry)
629 {
630     PERROR_LOG_ENTRY LogEntry;
631     KIRQL Irql;
632 
633     /* Make sure there is an entry */
634     if (!ElEntry) return;
635 
636     /* Get the actual header */
637     LogEntry = (PERROR_LOG_ENTRY)((ULONG_PTR)ElEntry - sizeof(ERROR_LOG_ENTRY));
638 
639     /* Get time stamp */
640     KeQuerySystemTime(&LogEntry->TimeStamp);
641 
642     /* Acquire the lock and insert this write in the list */
643     KeAcquireSpinLock(&IopLogListLock, &Irql);
644     InsertHeadList(&IopErrorLogListHead, &LogEntry->ListEntry);
645 
646     /* Check if the worker is running */
647     if (!IopLogWorkerRunning)
648     {
649         /* It's not, initialize it and queue it */
650         IopLogWorkerRunning = TRUE;
651         ExInitializeWorkItem(&IopErrorLogWorkItem, IopLogWorker, NULL);
652         ExQueueWorkItem(&IopErrorLogWorkItem, DelayedWorkQueue);
653     }
654 
655     /* Release the lock and return */
656     KeReleaseSpinLock(&IopLogListLock, Irql);
657 }
658 
659 /*
660  * @implemented
661  */
662 VOID
663 NTAPI
664 IoRaiseHardError(IN PIRP Irp,
665                  IN PVPB Vpb,
666                  IN PDEVICE_OBJECT RealDeviceObject)
667 {
668     PETHREAD Thread = Irp->Tail.Overlay.Thread;
669     PKAPC ErrorApc;
670 
671     /* Don't do anything if hard errors are disabled on the thread */
672     if (Thread->HardErrorsAreDisabled)
673     {
674         /* Complete the request */
675         Irp->IoStatus.Information = 0;
676         IoCompleteRequest(Irp, IO_DISK_INCREMENT);
677         return;
678     }
679 
680     // TODO: In case we were called in the context of a paging I/O or for
681     // a synchronous operation, that happens with APCs disabled, queue the
682     // hard-error call for later processing (see also IofCompleteRequest).
683 
684     /* Setup an APC and queue it to deal with the error (see OSR documentation) */
685     ErrorApc = ExAllocatePoolWithTag(NonPagedPool, sizeof(*ErrorApc), TAG_APC);
686     if (!ErrorApc)
687     {
688         /* Fail */
689         IoCompleteRequest(Irp, IO_DISK_INCREMENT);
690         return;
691     }
692 
693     KeInitializeApc(ErrorApc,
694                     &Thread->Tcb,
695                     Irp->ApcEnvironment,
696                     IopFreeApc,
697                     NULL,
698                     IopRaiseHardError,
699                     KernelMode,
700                     Irp);
701 
702     KeInsertQueueApc(ErrorApc, Vpb, RealDeviceObject, IO_NO_INCREMENT);
703 }
704 
705 /*
706  * @unimplemented
707  */
708 BOOLEAN
709 NTAPI
710 IoRaiseInformationalHardError(IN NTSTATUS ErrorStatus,
711                               IN PUNICODE_STRING String,
712                               IN PKTHREAD Thread)
713 {
714     DPRINT1("IoRaiseInformationalHardError: %lx, '%wZ'\n", ErrorStatus, String);
715     return FALSE;
716 }
717 
718 /*
719  * @implemented
720  */
721 BOOLEAN
722 NTAPI
723 IoSetThreadHardErrorMode(IN BOOLEAN HardErrorEnabled)
724 {
725     PETHREAD Thread = PsGetCurrentThread();
726     BOOLEAN OldMode;
727 
728     /* Get the current value */
729     OldMode = !Thread->HardErrorsAreDisabled;
730 
731     /* Set the new one and return the old */
732     Thread->HardErrorsAreDisabled = !HardErrorEnabled;
733     return OldMode;
734 }
735