Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

875 lines
22 KiB

  1. /*++
  2. Copyright (c) 1989 Microsoft Corporation
  3. Module Name:
  4. errorlog.c
  5. Abstract:
  6. This module contains the code for the I/O error log thread.
  7. Author:
  8. Darryl E. Havens (darrylh) May 3, 1989
  9. Environment:
  10. Kernel mode, system process thread
  11. Revision History:
  12. --*/
  13. #include "iomgr.h"
  14. #include "elfkrnl.h"
  15. typedef struct _IOP_ERROR_LOG_CONTEXT {
  16. KDPC ErrorLogDpc;
  17. KTIMER ErrorLogTimer;
  18. }IOP_ERROR_LOG_CONTEXT, *PIOP_ERROR_LOG_CONTEXT;
  19. //
  20. // Declare routines local to this module.
  21. //
  22. BOOLEAN
  23. IopErrorLogConnectPort(
  24. VOID
  25. );
  26. VOID
  27. IopErrorLogDpc(
  28. IN struct _KDPC *Dpc,
  29. IN PVOID DeferredContext,
  30. IN PVOID SystemArgument1,
  31. IN PVOID SystemArgument2
  32. );
  33. PLIST_ENTRY
  34. IopErrorLogGetEntry(
  35. );
  36. VOID
  37. IopErrorLogQueueRequest(
  38. VOID
  39. );
  40. VOID
  41. IopErrorLogRequeueEntry(
  42. IN PLIST_ENTRY ListEntry
  43. );
  44. #ifdef ALLOC_PRAGMA
  45. #pragma alloc_text(PAGE, IopErrorLogThread)
  46. #pragma alloc_text(PAGE, IopErrorLogConnectPort)
  47. #pragma alloc_text(PAGE, IopErrorLogQueueRequest)
  48. #endif
  49. //
  50. // Define a global varibles used by the error logging code.
  51. //
  52. WORK_QUEUE_ITEM IopErrorLogWorkItem;
  53. #ifdef ALLOC_DATA_PRAGMA
  54. #pragma data_seg("PAGEDATA")
  55. #endif
  56. HANDLE ErrorLogPort;
  57. #ifdef ALLOC_DATA_PRAGMA
  58. #pragma data_seg()
  59. #endif
  60. BOOLEAN ErrorLogPortConnected;
  61. BOOLEAN IopErrorLogPortPending;
  62. BOOLEAN IopErrorLogDisabledThisBoot;
  63. //
  64. // Define the amount of space required for the device and driver names.
  65. //
  66. #define IO_ERROR_NAME_LENGTH 100
  67. VOID
  68. IopErrorLogThread(
  69. IN PVOID StartContext
  70. )
  71. /*++
  72. Routine Description:
  73. This is the main loop for the I/O error log thread which executes in the
  74. system process context. This routine is started when the system is
  75. initialized.
  76. Arguments:
  77. StartContext - Startup context; not used.
  78. Return Value:
  79. None.
  80. --*/
  81. {
  82. PERROR_LOG_ENTRY errorLogEntry;
  83. UNICODE_STRING nameString;
  84. PLIST_ENTRY listEntry;
  85. PIO_ERROR_LOG_MESSAGE errorMessage;
  86. NTSTATUS status;
  87. PELF_PORT_MSG portMessage;
  88. PCHAR objectName;
  89. ULONG messageLength;
  90. ULONG driverNameLength;
  91. ULONG deviceNameLength;
  92. ULONG objectNameLength;
  93. ULONG remainingLength;
  94. ULONG stringLength;
  95. CHAR nameBuffer[IO_ERROR_NAME_LENGTH+sizeof( OBJECT_NAME_INFORMATION )];
  96. PDRIVER_OBJECT driverObject;
  97. POBJECT_NAME_INFORMATION nameInformation;
  98. PIO_ERROR_LOG_PACKET errorData;
  99. PWSTR string;
  100. PAGED_CODE();
  101. UNREFERENCED_PARAMETER( StartContext );
  102. //
  103. // Check to see whether a connection has been made to the error log
  104. // port. If the port is not connected return.
  105. //
  106. if (!IopErrorLogConnectPort()) {
  107. //
  108. // The port could not be connected. A timer was started that will
  109. // try again later.
  110. //
  111. return;
  112. }
  113. //
  114. // Allocate and zero the port message structure, include space for the
  115. // name of the device and driver.
  116. //
  117. messageLength = IO_ERROR_LOG_MESSAGE_LENGTH;
  118. portMessage = ExAllocatePool(PagedPool, messageLength);
  119. if (portMessage == NULL) {
  120. //
  121. // The message buffer could not be allocated. Request that
  122. // the error log thread routine be called again later.
  123. //
  124. IopErrorLogQueueRequest();
  125. return;
  126. }
  127. RtlZeroMemory( portMessage, sizeof( *portMessage ) );
  128. portMessage->MessageType = IO_ERROR_LOG;
  129. errorMessage = &portMessage->u.IoErrorLogMessage;
  130. nameInformation = (PVOID) &nameBuffer;
  131. //
  132. // Now enter the main loop for this thread. This thread performs the
  133. // following operations:
  134. //
  135. // 1) If a connection has been made to the error log port, dequeue a
  136. // packet from the queue head and attempt to send it to the port.
  137. //
  138. // 2) If the send works, loop sending packets until there are no more
  139. // packets; otherwise, indicate that the connection has been broken,
  140. // cleanup, place the packet back onto the head of the queue and
  141. // return.
  142. //
  143. // 3) After all the packets are sent clear the pending variable and
  144. // return.
  145. //
  146. for (;;) {
  147. //
  148. // Loop dequeueing packets from the queue head and attempt to send
  149. // each to the port.
  150. //
  151. // If the send works, continue looping until there are no more packets.
  152. // Otherwise, indicate that the connection has been broken, cleanup,
  153. // place the packet back onto the head of the queue, and start from the
  154. // top of the loop again.
  155. //
  156. if (!(listEntry = IopErrorLogGetEntry())) {
  157. break;
  158. }
  159. errorLogEntry = CONTAINING_RECORD( listEntry,
  160. ERROR_LOG_ENTRY,
  161. ListEntry );
  162. //
  163. // The size of errorLogEntry is ERROR_LOG_ENTRY +
  164. // IO_ERROR_LOG_PACKET + (Extra Dump data). The size of the
  165. // initial message length should be IO_ERROR_LOG_MESSAGE +
  166. // (Extra Dump data), since IO_ERROR_LOG_MESSAGE contains an
  167. // IO_ERROR_LOG_PACKET. Using the above calculations set the
  168. // message length.
  169. //
  170. messageLength = sizeof( IO_ERROR_LOG_MESSAGE ) -
  171. sizeof( ERROR_LOG_ENTRY ) - sizeof( IO_ERROR_LOG_PACKET ) +
  172. errorLogEntry->Size;
  173. errorData = (PIO_ERROR_LOG_PACKET) (errorLogEntry + 1);
  174. //
  175. // Copy the error log packet and the extra data to the message.
  176. //
  177. RtlCopyMemory( &errorMessage->EntryData,
  178. errorData,
  179. errorLogEntry->Size - sizeof( ERROR_LOG_ENTRY ) );
  180. errorMessage->TimeStamp = errorLogEntry->TimeStamp;
  181. errorMessage->Type = IO_TYPE_ERROR_MESSAGE;
  182. //
  183. // Add the driver and device name string. These strings go
  184. // before the error log strings. Just write over the current
  185. // strings and they will be recopied later.
  186. //
  187. if (errorData->NumberOfStrings != 0) {
  188. //
  189. // Start the driver and device strings where the current
  190. // strings start.
  191. //
  192. objectName = (PCHAR) (&errorMessage->EntryData) +
  193. errorData->StringOffset;
  194. } else {
  195. //
  196. // Put the driver and device strings at the end of the
  197. // data.
  198. //
  199. objectName = (PCHAR) errorMessage + messageLength;
  200. }
  201. //
  202. // Make sure the driver offset starts on an even bountry.
  203. //
  204. objectName = (PCHAR) ((ULONG_PTR) (objectName + sizeof(WCHAR) - 1) &
  205. ~(ULONG_PTR)(sizeof(WCHAR) - 1));
  206. errorMessage->DriverNameOffset = (ULONG)(objectName - (PCHAR) errorMessage);
  207. remainingLength = (ULONG)((PCHAR) portMessage + IO_ERROR_LOG_MESSAGE_LENGTH
  208. - objectName);
  209. //
  210. // Calculate the length of the driver name and
  211. // the device name. If the driver object has a name then get
  212. // it from there; otherwise try to query the device object.
  213. //
  214. driverObject = errorLogEntry->DriverObject;
  215. driverNameLength = 0;
  216. if (driverObject != NULL) {
  217. if (driverObject->DriverName.Buffer != NULL) {
  218. nameString.Buffer = driverObject->DriverName.Buffer;
  219. driverNameLength = driverObject->DriverName.Length;
  220. }
  221. if (driverNameLength == 0) {
  222. //
  223. // Try to query the driver object for a name.
  224. //
  225. status = ObQueryNameString( driverObject,
  226. nameInformation,
  227. IO_ERROR_NAME_LENGTH + sizeof( OBJECT_NAME_INFORMATION ),
  228. &objectNameLength );
  229. if (!NT_SUCCESS( status ) || !nameInformation->Name.Length) {
  230. //
  231. // No driver name was available.
  232. //
  233. driverNameLength = 0;
  234. } else {
  235. nameString = nameInformation->Name;
  236. }
  237. }
  238. } else {
  239. //
  240. // If no driver object, this message must be from the
  241. // kernel. We need to point the eventlog service to
  242. // an event message file containing ntstatus messages,
  243. // ie, ntdll, we do this by claiming this event is an
  244. // application popup.
  245. //
  246. nameString.Buffer = L"Application Popup";
  247. driverNameLength = wcslen(nameString.Buffer) * sizeof(WCHAR);
  248. }
  249. if (driverNameLength != 0 ) {
  250. //
  251. // Pick out the module name.
  252. //
  253. string = nameString.Buffer +
  254. (driverNameLength / sizeof(WCHAR));
  255. driverNameLength = sizeof(WCHAR);
  256. string--;
  257. while (*string != L'\\' && string != nameString.Buffer) {
  258. string--;
  259. driverNameLength += sizeof(WCHAR);
  260. }
  261. if (*string == L'\\') {
  262. string++;
  263. driverNameLength -= sizeof(WCHAR);
  264. }
  265. //
  266. // Ensure there is enough room for the driver name.
  267. // Save space for 3 NULLs one for the driver name,
  268. // one for the device name and one for strings.
  269. //
  270. if (driverNameLength > remainingLength - (3 * sizeof(WCHAR))) {
  271. driverNameLength = remainingLength - (3 * sizeof(WCHAR));
  272. }
  273. RtlCopyMemory(
  274. objectName,
  275. string,
  276. driverNameLength
  277. );
  278. }
  279. //
  280. // Add a null after the driver name even if there is no
  281. // driver name.
  282. //
  283. *((PWSTR) (objectName + driverNameLength)) = L'\0';
  284. driverNameLength += sizeof(WCHAR);
  285. //
  286. // Determine where the next string goes.
  287. //
  288. objectName += driverNameLength;
  289. remainingLength -= driverNameLength;
  290. errorMessage->EntryData.StringOffset = (USHORT)(objectName - (PCHAR) errorMessage);
  291. if (errorLogEntry->DeviceObject != NULL) {
  292. status = ObQueryNameString( errorLogEntry->DeviceObject,
  293. nameInformation,
  294. IO_ERROR_NAME_LENGTH + sizeof( OBJECT_NAME_INFORMATION ) - driverNameLength,
  295. &objectNameLength );
  296. if (!NT_SUCCESS( status ) || !nameInformation->Name.Length) {
  297. //
  298. // No device name was available. Add a Null string.
  299. //
  300. nameInformation->Name.Length = 0;
  301. nameInformation->Name.Buffer = L"\0";
  302. }
  303. //
  304. // No device name was available. Add a Null string.
  305. // Always add a device name string so that the
  306. // insertion string counts are correct.
  307. //
  308. } else {
  309. //
  310. // No device name was available. Add a Null string.
  311. // Always add a device name string so that the
  312. // insertion string counts are correct.
  313. //
  314. nameInformation->Name.Length = 0;
  315. nameInformation->Name.Buffer = L"\0";
  316. }
  317. deviceNameLength = nameInformation->Name.Length;
  318. //
  319. // Ensure there is enough room for the device name.
  320. // Save space for a NULL.
  321. //
  322. if (deviceNameLength > remainingLength - (2 * sizeof(WCHAR))) {
  323. deviceNameLength = remainingLength - (2 * sizeof(WCHAR));
  324. }
  325. RtlCopyMemory( objectName,
  326. nameInformation->Name.Buffer,
  327. deviceNameLength );
  328. //
  329. // Add a null after the device name even if there is no
  330. // device name.
  331. //
  332. *((PWSTR) (objectName + deviceNameLength)) = L'\0';
  333. deviceNameLength += sizeof(WCHAR);
  334. //
  335. // Update the string count for the device object.
  336. //
  337. errorMessage->EntryData.NumberOfStrings++;
  338. objectName += deviceNameLength;
  339. remainingLength -= deviceNameLength;
  340. if (errorData->NumberOfStrings) {
  341. stringLength = errorLogEntry->Size - sizeof( ERROR_LOG_ENTRY ) -
  342. errorData->StringOffset;
  343. //
  344. // Align the length to an even byte boundary.
  345. //
  346. stringLength = ((stringLength + sizeof(WCHAR) - 1) & ~(sizeof(WCHAR) - 1));
  347. //
  348. // Ensure there is enough room for the strings.
  349. // Save space for a NULL.
  350. //
  351. if (stringLength > remainingLength - sizeof(WCHAR)) {
  352. messageLength -= stringLength - remainingLength;
  353. stringLength = remainingLength - sizeof(WCHAR);
  354. }
  355. //
  356. // Copy the strings to the end of the message.
  357. //
  358. RtlCopyMemory( objectName,
  359. (PCHAR) errorData + errorData->StringOffset,
  360. stringLength );
  361. //
  362. // Add a null after the strings
  363. //
  364. //
  365. *((PWSTR) (objectName + stringLength)) = L'\0';
  366. }
  367. //
  368. // Update the message length.
  369. //
  370. errorMessage->DriverNameLength = (USHORT) driverNameLength;
  371. messageLength += deviceNameLength + driverNameLength;
  372. errorMessage->Size = (USHORT) messageLength;
  373. messageLength += FIELD_OFFSET ( ELF_PORT_MSG, u ) -
  374. FIELD_OFFSET (ELF_PORT_MSG, MessageType);
  375. portMessage->PortMessage.u1.s1.TotalLength = (USHORT)
  376. (sizeof( PORT_MESSAGE ) + messageLength);
  377. portMessage->PortMessage.u1.s1.DataLength = (USHORT) (messageLength);
  378. status = NtRequestPort( ErrorLogPort, (PPORT_MESSAGE) portMessage );
  379. if (!NT_SUCCESS( status )) {
  380. //
  381. // The send failed. Place the packet back onto the head of
  382. // the error log queue, forget the current connection since
  383. // it no longer works, and close the handle to the port.
  384. // Set a timer up for another attempt later.
  385. // Finally, exit the loop since there is no connection
  386. // to do any work on.
  387. //
  388. NtClose( ErrorLogPort );
  389. IopErrorLogRequeueEntry( &errorLogEntry->ListEntry );
  390. IopErrorLogQueueRequest();
  391. break;
  392. } else {
  393. //
  394. // The send worked fine. Free the packet and the update
  395. // the allocation count.
  396. //
  397. InterlockedExchangeAdd( &IopErrorLogAllocation,
  398. -((LONG) (errorLogEntry->Size )));
  399. //
  400. // Dereference the object pointers now that the name has been
  401. // captured.
  402. //
  403. if (errorLogEntry->DeviceObject != NULL) {
  404. ObDereferenceObject( errorLogEntry->DeviceObject );
  405. }
  406. if (driverObject != NULL) {
  407. ObDereferenceObject( errorLogEntry->DriverObject );
  408. }
  409. ExFreePool( errorLogEntry );
  410. } // if
  411. } // for
  412. //
  413. // Finally, free the message buffer and return.
  414. //
  415. ExFreePool(portMessage);
  416. }
  417. BOOLEAN
  418. IopErrorLogConnectPort(
  419. VOID
  420. )
  421. /*++
  422. Routine Description:
  423. This routine attempts to connect to the error log port. If the connection
  424. was made successfully and the port allows suficiently large messages, then
  425. the ErrorLogPort to the port handle, ErrorLogPortConnected is set to
  426. TRUE and TRUE is retuned. Otherwise a timer is started to queue a
  427. worker thread at a later time, unless there is a pending connection.
  428. Arguments:
  429. None.
  430. Return Value:
  431. Returns TRUE if the port was connected.
  432. --*/
  433. {
  434. UNICODE_STRING errorPortName;
  435. NTSTATUS status;
  436. ULONG maxMessageLength;
  437. SECURITY_QUALITY_OF_SERVICE dynamicQos;
  438. PAGED_CODE();
  439. //
  440. // If the ErrorLogPort is connected then return true.
  441. //
  442. if (ErrorLogPortConnected) {
  443. //
  444. // The port is connect return.
  445. //
  446. return(TRUE);
  447. }
  448. //
  449. // Set up the security quality of service parameters to use over the
  450. // port. Use the most efficient (least overhead) - which is dynamic
  451. // rather than static tracking.
  452. //
  453. dynamicQos.ImpersonationLevel = SecurityImpersonation;
  454. dynamicQos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
  455. dynamicQos.EffectiveOnly = TRUE;
  456. //
  457. // Generate the string structure for describing the error logger's port.
  458. //
  459. RtlInitUnicodeString( &errorPortName, ELF_PORT_NAME_U );
  460. status = NtConnectPort( &ErrorLogPort,
  461. &errorPortName,
  462. &dynamicQos,
  463. (PPORT_VIEW) NULL,
  464. (PREMOTE_PORT_VIEW) NULL,
  465. &maxMessageLength,
  466. (PVOID) NULL,
  467. (PULONG) NULL );
  468. if (NT_SUCCESS( status )) {
  469. if (maxMessageLength >= IO_ERROR_LOG_MESSAGE_LENGTH) {
  470. ErrorLogPortConnected = TRUE;
  471. return(TRUE);
  472. } else {
  473. NtClose(ErrorLogPort);
  474. }
  475. }
  476. //
  477. // The port was not successfully opened, or its message size was unsuitable
  478. // for use here. Queue a later request to run the error log thread.
  479. //
  480. IopErrorLogQueueRequest();
  481. //
  482. // The port could not be connected at this time return false.
  483. //
  484. return(FALSE);
  485. }
  486. VOID
  487. IopErrorLogDpc(
  488. IN struct _KDPC *Dpc,
  489. IN PVOID DeferredContext,
  490. IN PVOID SystemArgument1,
  491. IN PVOID SystemArgument2
  492. )
  493. /*++
  494. Routine Description:
  495. This routine queues a work request to the worker thread to process logged
  496. errors. It is called by a timer DPC when the error log port cannot be
  497. connected. The DPC structure itself is freed by this routine.
  498. Arguments:
  499. Dpc - Supplies a pointer to the DPC structure. This structure is freed by
  500. this routine.
  501. DeferredContext - Unused.
  502. SystemArgument1 - Unused.
  503. SystemArgument2 - Unused.
  504. Return Value:
  505. None
  506. --*/
  507. {
  508. //
  509. // Free the DPC structure if there is one.
  510. //
  511. if (Dpc != NULL) {
  512. ExFreePool(Dpc);
  513. }
  514. ExInitializeWorkItem( &IopErrorLogWorkItem, IopErrorLogThread, NULL );
  515. ExQueueWorkItem( &IopErrorLogWorkItem, DelayedWorkQueue );
  516. }
  517. PLIST_ENTRY
  518. IopErrorLogGetEntry(
  519. )
  520. /*++
  521. Routine Description:
  522. This routine gets the next entry from the head of the error log queue
  523. and returns it to the caller.
  524. Arguments:
  525. None.
  526. Return Value:
  527. The return value is a pointer to the packet removed, or NULL if there were
  528. no packets on the queue.
  529. --*/
  530. {
  531. KIRQL irql;
  532. PLIST_ENTRY listEntry;
  533. //
  534. // Remove the next packet from the queue, if there is one.
  535. //
  536. ExAcquireSpinLock( &IopErrorLogLock, &irql );
  537. if (IsListEmpty( &IopErrorLogListHead )) {
  538. //
  539. // Indicate no more work will be done in the context of this worker
  540. // thread and indicate to the caller that no packets were located.
  541. //
  542. IopErrorLogPortPending = FALSE;
  543. listEntry = (PLIST_ENTRY) NULL;
  544. } else {
  545. //
  546. // Remove the next packet from the head of the list.
  547. //
  548. listEntry = RemoveHeadList( &IopErrorLogListHead );
  549. }
  550. ExReleaseSpinLock( &IopErrorLogLock, irql );
  551. return listEntry;
  552. }
  553. VOID
  554. IopErrorLogQueueRequest(
  555. VOID
  556. )
  557. /*++
  558. Routine Description:
  559. This routine sets a timer to fire after 30 seconds. The timer queues a
  560. DPC which then queues a worker thread request to run the error log thread
  561. routine.
  562. Arguments:
  563. None.
  564. Return Value:
  565. None.
  566. --*/
  567. {
  568. LARGE_INTEGER interval;
  569. PIOP_ERROR_LOG_CONTEXT context;
  570. PAGED_CODE();
  571. //
  572. // Allocate a context block which will contain the timer and the DPC.
  573. //
  574. context = ExAllocatePool( NonPagedPool, sizeof( IOP_ERROR_LOG_CONTEXT ) );
  575. if (context == NULL) {
  576. //
  577. // The context block could not be allocated. Clear the error log
  578. // pending bit. If there is another error then a new attempt will
  579. // be made. Note the spinlock does not need to be held here since
  580. // new attempt should be made later not right now, so if another
  581. // error log packet is currently being queue, it waits with the
  582. // others.
  583. //
  584. IopErrorLogPortPending = FALSE;
  585. return;
  586. }
  587. KeInitializeDpc( &context->ErrorLogDpc,
  588. IopErrorLogDpc,
  589. NULL );
  590. KeInitializeTimer( &context->ErrorLogTimer );
  591. //
  592. // Delay for 30 seconds and try for the port again.
  593. //
  594. interval.QuadPart = - 10 * 1000 * 1000 * 30;
  595. //
  596. // Set the timer to fire a DPC in 30 seconds.
  597. //
  598. KeSetTimer( &context->ErrorLogTimer, interval, &context->ErrorLogDpc );
  599. }
  600. VOID
  601. IopErrorLogRequeueEntry(
  602. IN PLIST_ENTRY ListEntry
  603. )
  604. /*++
  605. Routine Description:
  606. This routine puts an error packet back at the head of the error log queue
  607. since it cannot be processed at the moment.
  608. Arguments:
  609. ListEntry - Supplies a pointer to the packet to be placed back onto the
  610. error log queue.
  611. Return Value:
  612. None.
  613. --*/
  614. {
  615. KIRQL irql;
  616. //
  617. // Simply insert the packet back onto the head of the queue, indicate that
  618. // the error log port is not connected, queue a request to check again
  619. // soon, and return.
  620. //
  621. ExAcquireSpinLock( &IopErrorLogLock, &irql );
  622. InsertHeadList( &IopErrorLogListHead, ListEntry );
  623. ErrorLogPortConnected = FALSE;
  624. ExReleaseSpinLock( &IopErrorLogLock, irql );
  625. }