* Sync up to trunk head (r64921).
[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(Buffer),
314 &ReturnedLength);
315 if (!NT_SUCCESS(Status) || (ObjectNameInfo->Name.Length == 0))
316 {
317 /* Setup an empty name */
318 ObjectNameInfo->Name.Length = 0;
319 ObjectNameInfo->Name.Buffer = L"";
320
321 /* Check if we failed because our buffer wasn't large enough */
322 if (Status == STATUS_INFO_LENGTH_MISMATCH)
323 {
324 /* Then we'll allocate one... we really want this name! */
325 PoolObjectNameInfo = ExAllocatePoolWithTag(PagedPool,
326 ReturnedLength,
327 TAG_IO);
328 if (PoolObjectNameInfo)
329 {
330 /* Query it again */
331 ObjectNameInfo = PoolObjectNameInfo;
332 Status = ObQueryNameString(LogEntry->DeviceObject,
333 ObjectNameInfo,
334 ReturnedLength,
335 &ReturnedLength);
336 if (NT_SUCCESS(Status))
337 {
338 /* Success, update the information */
339 ObjectNameInfo->Name.Length =
340 100 - (USHORT)DriverNameLength;
341 }
342 }
343 }
344 }
345 }
346 else
347 {
348 /* No device object, setup an empty name */
349 ObjectNameInfo->Name.Length = 0;
350 ObjectNameInfo->Name.Buffer = L"";
351 }
352
353 /*
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
356 */
357 DeviceNameLength = min(ObjectNameInfo->Name.Length,
358 RemainingLength - 2 * sizeof(UNICODE_NULL));
359 RtlCopyMemory(StringBuffer,
360 ObjectNameInfo->Name.Buffer,
361 DeviceNameLength);
362
363 /* Null-terminate the device name */
364 *((PWSTR)(StringBuffer + DeviceNameLength)) = L'\0';
365 DeviceNameLength += sizeof(WCHAR);
366
367 /* Free the buffer if we had one */
368 if (PoolObjectNameInfo)
369 {
370 ExFreePool(PoolObjectNameInfo);
371 PoolObjectNameInfo = NULL;
372 ObjectNameInfo = (POBJECT_NAME_INFORMATION)&Buffer;
373 }
374
375 /* Go to the next string buffer position */
376 ErrorMessage->EntryData.NumberOfStrings++;
377 StringBuffer += DeviceNameLength;
378 RemainingLength -= DeviceNameLength;
379
380 /* Check if we have any extra strings */
381 if (Packet->NumberOfStrings)
382 {
383 /* Find out the size of the extra strings */
384 ExtraStringLength = LogEntry->Size -
385 sizeof(ERROR_LOG_ENTRY) -
386 Packet->StringOffset;
387
388 /* Make sure that the extra strings fit in our buffer */
389 if (ExtraStringLength > (RemainingLength - sizeof(UNICODE_NULL)))
390 {
391 /* They wouldn't, so set normalize the length */
392 MessageLength -= ExtraStringLength - RemainingLength;
393 ExtraStringLength = RemainingLength - sizeof(UNICODE_NULL);
394 }
395
396 /* Now copy the extra strings */
397 RtlCopyMemory(StringBuffer,
398 (PCHAR)Packet + Packet->StringOffset,
399 ExtraStringLength);
400
401 /* Null-terminate them */
402 *((PWSTR)(StringBuffer + ExtraStringLength)) = L'\0';
403 }
404
405 /* Set the driver name length */
406 ErrorMessage->DriverNameLength = (USHORT)DriverNameLength;
407
408 /* Update the message length to include the device and driver names */
409 MessageLength += DeviceNameLength + DriverNameLength;
410 ErrorMessage->Size = (USHORT)MessageLength;
411
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]));
415
416 /* Set the total and data lengths */
417 Message->h.u1.s1.TotalLength = (USHORT)(sizeof(PORT_MESSAGE) +
418 MessageLength);
419 Message->h.u1.s1.DataLength = (USHORT)(MessageLength);
420
421 /* Send the message */
422 Status = NtRequestPort(IopLogPort, (PPORT_MESSAGE)Message);
423 if (!NT_SUCCESS(Status))
424 {
425 /* Requeue log message and restart the worker */
426 ExInterlockedInsertTailList(&IopErrorLogListHead,
427 &LogEntry->ListEntry,
428 &IopLogListLock);
429 IopLogWorkerRunning = FALSE;
430 IopRestartLogWorker();
431 break;
432 }
433
434 /* Dereference the device object */
435 if (LogEntry->DeviceObject) ObDereferenceObject(LogEntry->DeviceObject);
436 if (DriverObject) ObDereferenceObject(LogEntry->DriverObject);
437
438 /* Update size */
439 InterlockedExchangeAdd(&IopTotalLogSize,
440 -(LONG)(LogEntry->Size -
441 sizeof(ERROR_LOG_ENTRY)));
442 }
443
444 /* Free the LPC Message */
445 ExFreePool(Message);
446 }
447
448 VOID
449 NTAPI
450 IopFreeApc(IN PKAPC Apc,
451 IN PKNORMAL_ROUTINE *NormalRoutine,
452 IN PVOID *NormalContext,
453 IN PVOID *SystemArgument1,
454 IN PVOID *SystemArgument2)
455 {
456 /* Free the APC */
457 ExFreePool(Apc);
458 }
459
460 VOID
461 NTAPI
462 IopRaiseHardError(IN PKAPC Apc,
463 IN PKNORMAL_ROUTINE *NormalRoutine,
464 IN PVOID *NormalContext,
465 IN PVOID *SystemArgument1,
466 IN PVOID *SystemArgument2)
467 {
468 PIRP Irp = (PIRP)NormalContext;
469 //PVPB Vpb = (PVPB)SystemArgument1;
470 //PDEVICE_OBJECT DeviceObject = (PDEVICE_OBJECT)SystemArgument2;
471
472 UNIMPLEMENTED;
473
474 /* FIXME: UNIMPLEMENTED */
475 Irp->IoStatus.Status = STATUS_NOT_IMPLEMENTED;
476 Irp->IoStatus.Information = 0;
477 IoCompleteRequest(Irp, IO_DISK_INCREMENT);
478 }
479
480 /* PUBLIC FUNCTIONS **********************************************************/
481
482 /*
483 * @implemented
484 */
485 PVOID
486 NTAPI
487 IoAllocateErrorLogEntry(IN PVOID IoObject,
488 IN UCHAR EntrySize)
489 {
490 PERROR_LOG_ENTRY LogEntry;
491 ULONG LogEntrySize;
492 PDEVICE_OBJECT DeviceObject;
493 PDRIVER_OBJECT DriverObject;
494
495 /* Make sure we have an object */
496 if (!IoObject) return NULL;
497
498 /* Check if we're past our buffer */
499 if (IopTotalLogSize > PAGE_SIZE) return NULL;
500
501 /* Check if this is a device object or driver object */
502 if (((PDEVICE_OBJECT)IoObject)->Type == IO_TYPE_DEVICE)
503 {
504 /* It's a device, get the driver */
505 DeviceObject = (PDEVICE_OBJECT)IoObject;
506 DriverObject = DeviceObject->DriverObject;
507 }
508 else if (((PDEVICE_OBJECT)IoObject)->Type == IO_TYPE_DRIVER)
509 {
510 /* It's a driver, so we don't have a device */
511 DeviceObject = NULL;
512 DriverObject = (PDRIVER_OBJECT)IoObject;
513 }
514 else
515 {
516 /* Fail */
517 return NULL;
518 }
519
520 /* Calculate the total size and allocate it */
521 LogEntrySize = sizeof(ERROR_LOG_ENTRY) + EntrySize;
522 LogEntry = ExAllocatePoolWithTag(NonPagedPool,
523 LogEntrySize,
524 TAG_ERROR_LOG);
525 if (!LogEntry) return NULL;
526
527 /* Reference the Objects */
528 if (DeviceObject) ObReferenceObject(DeviceObject);
529 if (DriverObject) ObReferenceObject(DriverObject);
530
531 /* Update log size */
532 InterlockedExchangeAdd(&IopTotalLogSize, EntrySize);
533
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;
540
541 /* Return the entry data */
542 return (PVOID)((ULONG_PTR)LogEntry + sizeof(ERROR_LOG_ENTRY));
543 }
544
545 /*
546 * @implemented
547 */
548 VOID
549 NTAPI
550 IoFreeErrorLogEntry(IN PVOID ElEntry)
551 {
552 PERROR_LOG_ENTRY LogEntry;
553
554 /* Make sure there's an entry */
555 if (!ElEntry) return;
556
557 /* Get the actual header */
558 LogEntry = (PERROR_LOG_ENTRY)((ULONG_PTR)ElEntry - sizeof(ERROR_LOG_ENTRY));
559
560 /* Dereference both objects */
561 if (LogEntry->DeviceObject) ObDereferenceObject(LogEntry->DeviceObject);
562 if (LogEntry->DriverObject) ObDereferenceObject(LogEntry->DriverObject);
563
564 /* Decrease total allocation size and free the entry */
565 InterlockedExchangeAdd(&IopTotalLogSize,
566 -(LONG)(LogEntry->Size - sizeof(ERROR_LOG_ENTRY)));
567 ExFreePool(LogEntry);
568 }
569
570 /*
571 * @implemented
572 */
573 VOID
574 NTAPI
575 IoWriteErrorLogEntry(IN PVOID ElEntry)
576 {
577 PERROR_LOG_ENTRY LogEntry;
578 KIRQL Irql;
579
580 /* Get the main header */
581 LogEntry = (PERROR_LOG_ENTRY)((ULONG_PTR)ElEntry -
582 sizeof(ERROR_LOG_ENTRY));
583
584 /* Get time stamp */
585 KeQuerySystemTime(&LogEntry->TimeStamp);
586
587 /* Acquire the lock and insert this write in the list */
588 KeAcquireSpinLock(&IopLogListLock, &Irql);
589 InsertHeadList(&IopErrorLogListHead, &LogEntry->ListEntry);
590
591 /* Check if the worker is running */
592 if (!IopLogWorkerRunning)
593 {
594 #if 0
595 /* It's not, initialize it and queue it */
596 ExInitializeWorkItem(&IopErrorLogWorkItem,
597 IopLogWorker,
598 &IopErrorLogWorkItem);
599 ExQueueWorkItem(&IopErrorLogWorkItem, DelayedWorkQueue);
600 IopLogWorkerRunning = TRUE;
601 #endif
602 }
603
604 /* Release the lock and return */
605 KeReleaseSpinLock(&IopLogListLock, Irql);
606 }
607
608 /*
609 * @implemented
610 */
611 VOID
612 NTAPI
613 IoRaiseHardError(IN PIRP Irp,
614 IN PVPB Vpb,
615 IN PDEVICE_OBJECT RealDeviceObject)
616 {
617 PETHREAD Thread = (PETHREAD)&Irp->Tail.Overlay.Thread;
618 PKAPC ErrorApc;
619
620 /* Don't do anything if hard errors are disabled on the thread */
621 if (Thread->HardErrorsAreDisabled)
622 {
623 /* Complete the request */
624 Irp->IoStatus.Information = 0;
625 IoCompleteRequest(Irp, IO_DISK_INCREMENT);
626 return;
627 }
628
629 /* Setup an APC */
630 ErrorApc = ExAllocatePoolWithTag(NonPagedPool,
631 sizeof(KAPC),
632 TAG_APC);
633 KeInitializeApc(ErrorApc,
634 &Thread->Tcb,
635 Irp->ApcEnvironment,
636 NULL,
637 (PKRUNDOWN_ROUTINE)IopFreeApc,
638 (PKNORMAL_ROUTINE)IopRaiseHardError,
639 KernelMode,
640 Irp);
641
642 /* Queue an APC to deal with the error (see osr documentation) */
643 KeInsertQueueApc(ErrorApc, Vpb, RealDeviceObject, 0);
644 }
645
646 /*
647 * @unimplemented
648 */
649 BOOLEAN
650 NTAPI
651 IoRaiseInformationalHardError(IN NTSTATUS ErrorStatus,
652 IN PUNICODE_STRING String,
653 IN PKTHREAD Thread)
654 {
655 UNIMPLEMENTED;
656 return FALSE;
657 }
658
659 /*
660 * @implemented
661 */
662 BOOLEAN
663 NTAPI
664 IoSetThreadHardErrorMode(IN BOOLEAN HardErrorEnabled)
665 {
666 PETHREAD Thread = PsGetCurrentThread();
667 BOOLEAN OldMode;
668
669 /* Get the current value */
670 OldMode = !Thread->HardErrorsAreDisabled;
671
672 /* Set the new one and return the old */
673 Thread->HardErrorsAreDisabled = !HardErrorEnabled;
674 return OldMode;
675 }