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