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