Leaked source code of windows server 2003
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.

903 lines
25 KiB

  1. /*++
  2. Copyright (c) 1991 Microsoft Corporation
  3. Module Name:
  4. display.c
  5. Abstract:
  6. This file contains functions that handle the displaying of messages.
  7. Currently a message box is used to display messages. A message queueing
  8. scheme has been setup so that the messenger worker threads can
  9. call a function with a pointer to a message buffer. That message will
  10. get copied into the queue so that the worker thread can go on gathering
  11. more messages. When the display thread will be doing one of the following:
  12. 1) Displaying a message - waiting for the user to press "ok".
  13. 2) Sleeping - waiting for an event that will _tell it to _read the
  14. message queue.
  15. When the display thread completes displaying a message, it will check
  16. the queue for the next message to display. If there are no further
  17. messages, it will go to sleep until a message comes in.
  18. Author:
  19. Dan Lafferty (danl) 24-Feb-1992
  20. Environment:
  21. User Mode -Win32
  22. Notes:
  23. Revision History:
  24. 04-Nov-1992 danl
  25. MsgDisplayThread: Handle Extended Characters. This was done by
  26. translating the Oem-style characters in the message to the unicode
  27. equivalent, and then calling the Unicode version of the MessageBox Api.
  28. It will still call the Ansi version of the MessageBox if the
  29. string cannot be translated for some reason.
  30. 26-Oct-1992 danl
  31. MsgDisplayQueueAdd: Added Beep when message is added to queue.
  32. Fixed bug where "if (status = TRUE)" caused the GlobalMsgDisplayEvent
  33. to always be set.
  34. 24-Feb-1992 danl
  35. created
  36. --*/
  37. //
  38. // INCLUDES
  39. //
  40. #include "msrv.h"
  41. #include <msgdbg.h> // STATIC and MSG_LOG
  42. #include <string.h> // memcpy
  43. #include <winuser.h> // MessageBox
  44. #include "msgdata.h" // GlobalMsgDisplayEvent
  45. //
  46. // DEFINES
  47. //
  48. #define MAX_QUEUE_SIZE 25
  49. #define WAIT_FOREVER 0xffffffff
  50. //
  51. // Queue Entry Structure
  52. //
  53. typedef struct _QUEUE_ENTRY {
  54. struct _QUEUE_ENTRY *Next;
  55. ULONG SessionId;
  56. SYSTEMTIME BigTime;
  57. CHAR Message[1];
  58. }QUEUE_ENTRY, *LPQUEUE_ENTRY;
  59. //
  60. // GLOBALS
  61. //
  62. //
  63. // This critical section serializes access to all the other globals.
  64. //
  65. CRITICAL_SECTION MsgDisplayCriticalSection;
  66. //
  67. // Used to wakeup the display thread if it was put to sleep due to
  68. // not having a user desktop to display the message on.
  69. //
  70. HANDLE hGlobalDisplayEvent;
  71. //
  72. // These are the Display Queue pointers & counts.
  73. //
  74. LPQUEUE_ENTRY GlobalMsgQueueHead;
  75. LPQUEUE_ENTRY GlobalMsgQueueTail;
  76. DWORD GlobalMsgQueueCount;
  77. BOOL fGlobalInitialized;
  78. //
  79. // This indicates whether there is a display thread already available that
  80. // can service requests. If this is false, it means a new thread will
  81. // need to be created.
  82. //
  83. HANDLE GlobalDisplayThread;
  84. //
  85. // Function Prototypes
  86. //
  87. BOOL
  88. MsgDisplayQueueRead(
  89. OUT LPQUEUE_ENTRY *pQueueEntry
  90. );
  91. DWORD
  92. MsgDisplayThread(
  93. LPVOID parm
  94. );
  95. VOID
  96. MsgMakeNewFormattedMsg(
  97. LPWSTR *ppHead,
  98. LPWSTR *ppTime,
  99. LPWSTR *ppBody,
  100. SYSTEMTIME BigTime
  101. );
  102. BOOL
  103. MsgDisplayQueueAdd(
  104. IN LPSTR pMsgBuffer,
  105. IN DWORD MsgSize,
  106. IN ULONG SessionId,
  107. IN SYSTEMTIME BigTime
  108. )
  109. /*++
  110. Routine Description:
  111. This function adds a Message to the display queue. If the queue is
  112. full, the message is rejected.
  113. Arguments:
  114. pMsgBuffer - This is a pointer to the buffer where the message is
  115. stored. The message must be in the form of a pre-formatted
  116. (with message header) NUL-terminated string of ansi characters.
  117. MsgSize - Indicates the size (in bytes) of the message in the
  118. message buffer, including the NUL terminator.
  119. BigTime - This is a SYSTEMTIME that indicates the time the message was
  120. received.
  121. Return Value:
  122. TRUE - The message was successfully stored in the queue.
  123. FALSE - The message was rejected. Either the queue was full, or
  124. there was not enough memory to store the message in the queue.
  125. --*/
  126. {
  127. LPQUEUE_ENTRY pQueueEntry;
  128. BOOL status;
  129. DWORD threadId;
  130. MSG_LOG(TRACE,"Adding a message to the display queue\n",0);
  131. // ***************************
  132. // **** LOCK QUEUE ACCESS ****
  133. // ***************************
  134. EnterCriticalSection(&MsgDisplayCriticalSection);
  135. //
  136. // Is there room for the message in the queue?
  137. //
  138. if (GlobalMsgQueueCount >= MAX_QUEUE_SIZE) {
  139. MSG_LOG(TRACE,"DisplayQueueAdd: Max Queue Size Exceeded\n",0);
  140. status = FALSE;
  141. goto CleanExit;
  142. }
  143. //
  144. // Allocate memory for the message in the queue.
  145. //
  146. pQueueEntry = (LPQUEUE_ENTRY)LocalAlloc(LMEM_FIXED, MsgSize + sizeof(QUEUE_ENTRY));
  147. if (pQueueEntry == NULL) {
  148. MSG_LOG(ERROR,"DisplayQueueAdd: Unable to allocate memory\n",0);
  149. status = FALSE;
  150. goto CleanExit;
  151. }
  152. //
  153. // Copy the message into the queue entry.
  154. //
  155. pQueueEntry->Next = NULL;
  156. memcpy(pQueueEntry->Message, pMsgBuffer, MsgSize);
  157. pQueueEntry->BigTime = BigTime;
  158. pQueueEntry->SessionId = SessionId;
  159. //
  160. // Update the queue management pointer.
  161. //
  162. if (GlobalMsgQueueCount == 0) {
  163. //
  164. // There are no entries in the queue. So make the head
  165. // and the tail equal.
  166. //
  167. GlobalMsgQueueTail = pQueueEntry;
  168. GlobalMsgQueueHead = pQueueEntry;
  169. }
  170. else {
  171. //
  172. // Create the new Queue Tail and have the old tail's next pointer
  173. // point to the new tail.
  174. //
  175. GlobalMsgQueueTail->Next = pQueueEntry;
  176. GlobalMsgQueueTail = pQueueEntry;
  177. }
  178. GlobalMsgQueueCount++;
  179. status = TRUE;
  180. //
  181. // If a display thread doesn't exist, then create one.
  182. //
  183. if (GlobalDisplayThread == NULL) {
  184. //
  185. // No use to create the event in Hydra case, since the thread will never go asleep.
  186. //
  187. if (!g_IsTerminalServer)
  188. {
  189. hGlobalDisplayEvent = CreateEvent( NULL,
  190. FALSE, // auto-reset
  191. FALSE, // init to non-signaled
  192. NULL );
  193. }
  194. GlobalDisplayThread = CreateThread (
  195. NULL, // Thread Attributes
  196. 0, // StackSize -- process default
  197. MsgDisplayThread, // lpStartAddress
  198. (PVOID)NULL, // lpParameter
  199. 0L, // Creation Flags
  200. &threadId); // lpThreadId
  201. if (GlobalDisplayThread == (HANDLE) NULL) {
  202. //
  203. // If we couldn't create the display thread, then we can't do
  204. // much about it. Might as well leave the entry in the queue.
  205. // Perhaps we can display it the next time around.
  206. //
  207. MSG_LOG(ERROR,"MsgDisplayQueueAdd:CreateThread FAILURE %ld\n",
  208. GetLastError());
  209. if (hGlobalDisplayEvent != NULL) {
  210. CloseHandle(hGlobalDisplayEvent);
  211. hGlobalDisplayEvent = NULL;
  212. }
  213. }
  214. }
  215. CleanExit:
  216. // *****************************
  217. // **** UNLOCK QUEUE ACCESS ****
  218. // *****************************
  219. LeaveCriticalSection(&MsgDisplayCriticalSection);
  220. //
  221. // If we actually put something in the queue, then beep.
  222. //
  223. if (status == TRUE) {
  224. if (g_IsTerminalServer)
  225. {
  226. MsgArrivalBeep( SessionId );
  227. }
  228. else
  229. {
  230. MessageBeep(MB_OK);
  231. }
  232. }
  233. return(status);
  234. }
  235. VOID
  236. MsgDisplayThreadWakeup()
  237. /*++
  238. Routine Description:
  239. This function is called at shutdown, or for API requests. It causes
  240. the display thread to wake up and read the queue again.
  241. If the display thread cannot display the message because the MessageBox
  242. call fails, then we assume it is because the user desktop is not avaiable
  243. because the screensaver is on, or because the workstation is locked.
  244. In this case, the display thread hangs around waiting for this
  245. Event to get signalled. Winlogon calls one of the API entry points
  246. in order to stimulate the display thread into action again.
  247. Arguments:
  248. Return Value:
  249. --*/
  250. {
  251. // ***************************
  252. // **** LOCK QUEUE ACCESS ****
  253. // ***************************
  254. EnterCriticalSection(&MsgDisplayCriticalSection);
  255. if ( hGlobalDisplayEvent != (HANDLE)NULL ) {
  256. SetEvent( hGlobalDisplayEvent );
  257. }
  258. // *****************************
  259. // **** UNLOCK QUEUE ACCESS ****
  260. // *****************************
  261. LeaveCriticalSection(&MsgDisplayCriticalSection);
  262. }
  263. DWORD
  264. MsgDisplayInit(
  265. VOID
  266. )
  267. /*++
  268. Routine Description:
  269. This function initializes everything having to do with the displaying
  270. of messages. It does the following:
  271. Initializes the Locks on global data
  272. Creates event for display thread to wait on.
  273. Starts the display thread that will read the msg queue.
  274. Arguments:
  275. NONE
  276. Return Value:
  277. Always TRUE.
  278. --*/
  279. {
  280. DWORD dwError = NO_ERROR;
  281. NTSTATUS status;
  282. MSG_LOG(TRACE,"Initializing the Message Display Code\n",0);
  283. //
  284. // Initialize the Critical Section that protects access to global data.
  285. //
  286. status = MsgInitCriticalSection(&MsgDisplayCriticalSection);
  287. if (NT_SUCCESS(status))
  288. {
  289. fGlobalInitialized = TRUE;
  290. }
  291. else
  292. {
  293. MSG_LOG1(ERROR,
  294. "MsgDisplayInit: MsgInitCriticalSection failed %#x\n",
  295. status);
  296. dwError = ERROR_NOT_ENOUGH_MEMORY;
  297. }
  298. GlobalMsgQueueHead = NULL;
  299. GlobalMsgQueueTail = NULL;
  300. GlobalMsgQueueCount = 0;
  301. GlobalDisplayThread = NULL;
  302. return dwError;
  303. }
  304. VOID
  305. MsgDisplayEnd(
  306. VOID
  307. )
  308. /*++
  309. Routine Description:
  310. This function makes sure the Display Thread has completed its work,
  311. and free's up all of its resources.
  312. *** IMPORTANT ***
  313. NOTE: This function should only be called when it is no longer possible
  314. for the MsgDisplayQueueAdd function to get called.
  315. Arguments:
  316. NONE.
  317. Return Value:
  318. NONE.
  319. --*/
  320. {
  321. LPQUEUE_ENTRY freeEntry;
  322. if (!fGlobalInitialized) {
  323. return;
  324. }
  325. // ***************************
  326. // **** LOCK QUEUE ACCESS ****
  327. // ***************************
  328. EnterCriticalSection(&MsgDisplayCriticalSection);
  329. if (GlobalDisplayThread != NULL) {
  330. TerminateThread(GlobalDisplayThread,0);
  331. CloseHandle( GlobalDisplayThread );
  332. }
  333. //
  334. // To make sure a new thread won't be created...
  335. //
  336. GlobalDisplayThread = INVALID_HANDLE_VALUE;
  337. //
  338. // Free memory in the queue
  339. //
  340. while(GlobalMsgQueueCount > 0) {
  341. freeEntry = GlobalMsgQueueHead;
  342. GlobalMsgQueueHead = GlobalMsgQueueHead->Next;
  343. LocalFree(freeEntry);
  344. GlobalMsgQueueCount--;
  345. }
  346. if (hGlobalDisplayEvent != NULL) {
  347. CloseHandle(hGlobalDisplayEvent);
  348. hGlobalDisplayEvent = NULL;
  349. }
  350. fGlobalInitialized = FALSE;
  351. // *****************************
  352. // **** UNLOCK QUEUE ACCESS ****
  353. // *****************************
  354. LeaveCriticalSection(&MsgDisplayCriticalSection);
  355. DeleteCriticalSection(&MsgDisplayCriticalSection);
  356. MSG_LOG(TRACE,"The Display has free'd resources and is terminating\n",0);
  357. }
  358. BOOL
  359. MsgDisplayQueueRead(
  360. OUT LPQUEUE_ENTRY *pQueueEntry
  361. )
  362. /*++
  363. Routine Description:
  364. Pulls a display entry out of the display queue.
  365. Arguments:
  366. pQueueEntry - This is a pointer to a location where a pointer to the
  367. queue entry structure can be placed.
  368. Return Value:
  369. TRUE - If an entry was found.
  370. FALSE- If an entry wasn't found.
  371. Note on LOCKS:
  372. The caller MUST hold the MsgDisplayCriticalSection Lock prior to calling
  373. this function!!!
  374. --*/
  375. {
  376. BOOL status;
  377. //
  378. // If there is data in the queue, then get the pointer to the queue
  379. // entry from the queue head. Then decrement the queue count and
  380. // set the queue head to the next entry (which could be zero if there
  381. // are no more).
  382. //
  383. if (GlobalMsgQueueCount != 0) {
  384. *pQueueEntry = GlobalMsgQueueHead;
  385. GlobalMsgQueueCount--;
  386. GlobalMsgQueueHead = (*pQueueEntry)->Next;
  387. status = TRUE;
  388. MSG_LOG(TRACE,"A message was read from the display queue\n",0);
  389. }
  390. else{
  391. status = FALSE;
  392. *pQueueEntry = NULL;
  393. }
  394. return(status);
  395. }
  396. DWORD
  397. MsgDisplayThread(
  398. LPVOID parm
  399. )
  400. /*++
  401. Routine Description:
  402. Arguments:
  403. Return Value:
  404. Note:
  405. This worker thread expects that the critical section guarding the
  406. global queue data is already initialized.
  407. --*/
  408. {
  409. LPQUEUE_ENTRY pQueueEntry;
  410. INT displayStatus;
  411. DWORD msgrState;
  412. UNICODE_STRING unicodeString;
  413. OEM_STRING oemString;
  414. NTSTATUS ntStatus;
  415. USHORT unicodeLength;
  416. LPWSTR pHead; // pointer to header portion of message
  417. LPSTR pHeadAnsi; // pointer to header of message pulled from queue
  418. LPWSTR pTime; // pointer to time portion of message
  419. LPWSTR pBody; // pointer to body of message (just after time)
  420. SYSTEMTIME BigTime;
  421. ULONG SessionId; // SessionId of the recipient (found in QUEUE_ENTRY)
  422. BOOL MsgToRead = TRUE; // tells us whether or not to sleep.
  423. UNREFERENCED_PARAMETER(parm);
  424. pHead = NULL;
  425. pQueueEntry = NULL;
  426. do {
  427. //
  428. // If we are not currently working on displaying a message,
  429. // then get a new message from the queue.
  430. //
  431. if (pHead == NULL)
  432. {
  433. // ***************************
  434. // **** LOCK QUEUE ACCESS ****
  435. // ***************************
  436. EnterCriticalSection(&MsgDisplayCriticalSection);
  437. if (!MsgDisplayQueueRead(&pQueueEntry))
  438. {
  439. //
  440. // No display entries in the queue. We can leave.
  441. //
  442. MsgToRead = FALSE;
  443. CloseHandle(GlobalDisplayThread);
  444. GlobalDisplayThread = NULL;
  445. if (hGlobalDisplayEvent != NULL)
  446. {
  447. CloseHandle(hGlobalDisplayEvent);
  448. hGlobalDisplayEvent = NULL;
  449. }
  450. // *****************************
  451. // **** UNLOCK QUEUE ACCESS ****
  452. // *****************************
  453. LeaveCriticalSection(&MsgDisplayCriticalSection);
  454. //
  455. // From this point on, we can't access any global
  456. // variables.
  457. //
  458. }
  459. else
  460. {
  461. // *****************************
  462. // **** UNLOCK QUEUE ACCESS ****
  463. // *****************************
  464. LeaveCriticalSection(&MsgDisplayCriticalSection);
  465. //
  466. // Process the entry.
  467. //
  468. BigTime = pQueueEntry->BigTime;
  469. SessionId = pQueueEntry->SessionId;
  470. //
  471. // Here we trash the pQueueEntry structure by pointing to the
  472. // beginning and copying the message data starting at the
  473. // first address. This is because MsgMakeNewFormattedMsg
  474. // expects the message to begin at a address that can be
  475. // released with LocalFree();
  476. //
  477. pHeadAnsi = (LPSTR) pQueueEntry;
  478. strcpy(pHeadAnsi, pQueueEntry->Message);
  479. //
  480. // Convert the data from the OEM character set to the
  481. // Unicode character set.
  482. //
  483. RtlInitAnsiString(&oemString, pHeadAnsi);
  484. unicodeLength = oemString.Length * sizeof(WCHAR);
  485. unicodeString.Buffer = (LPWSTR) LocalAlloc(LMEM_ZEROINIT,
  486. unicodeLength + sizeof(WCHAR));
  487. if (unicodeString.Buffer == NULL)
  488. {
  489. //
  490. // Couldn't allocate for unicode buffer. Therefore we will
  491. // display the message with the Ansi version of the
  492. // message box API.
  493. //
  494. LocalFree(pHeadAnsi);
  495. pHeadAnsi = NULL;
  496. }
  497. else
  498. {
  499. unicodeString.Length = unicodeLength;
  500. unicodeString.MaximumLength = unicodeLength + sizeof(WCHAR);
  501. ntStatus = RtlOemStringToUnicodeString(
  502. &unicodeString, // Destination
  503. &oemString, // Source
  504. FALSE); // Don't allocate the destination.
  505. LocalFree(pHeadAnsi);
  506. pHeadAnsi = NULL;
  507. if (!NT_SUCCESS(ntStatus))
  508. {
  509. MSG_LOG(ERROR,
  510. "MsgDisplayThread:RtlOemStringToUnicodeString Failed rc=%X\n",
  511. ntStatus);
  512. LocalFree(unicodeString.Buffer);
  513. unicodeString.Buffer = NULL;
  514. }
  515. else
  516. {
  517. pHead = unicodeString.Buffer;
  518. pTime = wcsstr(pHead, GlobalTimePlaceHolderUnicode);
  519. if (pTime != NULL)
  520. {
  521. pBody = pTime + wcslen(GlobalTimePlaceHolderUnicode);
  522. }
  523. else
  524. {
  525. pTime = pBody = pHead;
  526. }
  527. }
  528. }
  529. }
  530. }
  531. if (pHead != NULL)
  532. {
  533. MsgMakeNewFormattedMsg(&pHead, &pTime, &pBody, BigTime);
  534. //
  535. // Display the data in the QueueEntry
  536. //
  537. MSG_LOG(TRACE, "Calling MessageBox\n",0);
  538. if (g_IsTerminalServer)
  539. {
  540. displayStatus = DisplayMessage(pHead,
  541. GlobalMessageBoxTitle,
  542. SessionId);
  543. //
  544. // In Hydra case do not care about the error, since DisplayMessageW returns FALSE
  545. // only if the user cannot be found on any Winstation. No use to try again in that case !
  546. //
  547. // So free up the data in the QueueEntry in any case
  548. //
  549. LocalFree(pHead);
  550. pHead = NULL;
  551. }
  552. else
  553. {
  554. displayStatus = MessageBox(NULL,
  555. pHead,
  556. GlobalMessageBoxTitle,
  557. MB_OK | MB_SYSTEMMODAL | MB_SERVICE_NOTIFICATION |
  558. MB_SETFOREGROUND | MB_DEFAULT_DESKTOP_ONLY);
  559. if (displayStatus == 0)
  560. {
  561. //
  562. // MessageBoxW can fail in case the current desktop is not the application desktop
  563. // So wait and try again later (Winlogon will "tickle" messenger at desktop switching)
  564. //
  565. MSG_LOG1(TRACE,"MessageBox (unicode) Call failed %d\n",GetLastError());
  566. WaitForSingleObject( hGlobalDisplayEvent, INFINITE );
  567. }
  568. else
  569. {
  570. //
  571. // Free up the data in the QueueEntry
  572. //
  573. LocalFree(pHead);
  574. pHead = NULL;
  575. }
  576. }
  577. }
  578. msgrState = GetMsgrState();
  579. }
  580. while(MsgToRead && (msgrState != STOPPING) && (msgrState != STOPPED));
  581. return 0;
  582. }
  583. VOID
  584. MsgMakeNewFormattedMsg(
  585. LPWSTR *ppHead,
  586. LPWSTR *ppTime,
  587. LPWSTR *ppBody,
  588. SYSTEMTIME BigTime
  589. )
  590. /*++
  591. Routine Description:
  592. This function returns a buffer containing an entire message that
  593. consists of a single string of ansi (actually oem) characters.
  594. Pointers to various areas (time and body) within this buffer are
  595. also returned.
  596. MEMORY MANAGEMENT NOTE:
  597. *ppHead is expected to point to the top of the buffer. If the
  598. message is reformatted, then this buffer will have been freed,
  599. and a new buffer will have been allocated. It is expected that
  600. the caller allocates the original buffer passed in, and that
  601. the caller will free it when it is no longer needed.
  602. Arguments:
  603. ppHead - Pointer to location that contains the pointer to the message
  604. buffer.
  605. ppTime - Pointer to location that contains the pointer to the time portion
  606. of the message buffer.
  607. ppBody - Pointer to location that immediately follows the time string.
  608. Return Value:
  609. none. If this fails to allocate memory for the formatted message, then
  610. the unformatted message should be displayed.
  611. --*/
  612. {
  613. WCHAR TimeBuf[TIME_BUF_SIZE + 1];
  614. DWORD BufSize;
  615. LPWSTR pTemp;
  616. DWORD numChars;
  617. LPWSTR pOldHead;
  618. //
  619. // Create a properly formatted time string.
  620. //
  621. BufSize = GetDateFormat(LOCALE_SYSTEM_DEFAULT,
  622. 0, // flags
  623. &BigTime, // date message was received
  624. NULL, // use default format
  625. TimeBuf, // buffer
  626. sizeof(TimeBuf) / sizeof(WCHAR)); // size (in characters)
  627. if (BufSize != 0)
  628. {
  629. //
  630. // Return value includes the trailing NUL
  631. //
  632. TimeBuf[BufSize - 1] = ' ';
  633. BufSize += GetTimeFormat(LOCALE_SYSTEM_DEFAULT,
  634. 0, // flags
  635. &BigTime, // time message was received
  636. NULL, // use default format
  637. TimeBuf + BufSize, // buffer
  638. sizeof(TimeBuf) / sizeof(WCHAR) - BufSize);
  639. ASSERT(wcslen(TimeBuf) == (BufSize - 1));
  640. }
  641. if (BufSize == 0)
  642. {
  643. //
  644. // Something went wrong
  645. //
  646. MSG_LOG1(ERROR,
  647. "MsgMakeNewFormattedMsg: Date/time formatting failed %d\n",
  648. GetLastError());
  649. TimeBuf[0] = L'\0';
  650. }
  651. if (wcsncmp(TimeBuf, *ppTime, BufSize - 1) == 0)
  652. {
  653. //
  654. // If the newly formatted time string is the same as the existing
  655. // time string, there is nothing to do so we just return.
  656. //
  657. MSG_LOG0(TRACE,
  658. "MsgMakeNewFormattedMsg: Time Format has not changed - no update.\n");
  659. return;
  660. }
  661. //
  662. // Allocate a new message buffer
  663. //
  664. BufSize--;
  665. BufSize += wcslen(*ppHead) + 2 - (DWORD) (*ppBody - *ppTime);
  666. pTemp = LocalAlloc(LMEM_ZEROINIT, BufSize * sizeof(WCHAR));
  667. if (pTemp == NULL)
  668. {
  669. MSG_LOG0(ERROR,"MsgMakeNewFormattedMsg: LocalAlloc failed\n");
  670. return;
  671. }
  672. pOldHead = *ppHead;
  673. //
  674. // Copy the header of the message.
  675. //
  676. numChars = (DWORD) (*ppTime - *ppHead);
  677. wcsncpy(pTemp, *ppHead, numChars);
  678. *ppHead = pTemp;
  679. //
  680. // Copy the time string
  681. //
  682. *ppTime = *ppHead + numChars;
  683. wcscpy(*ppTime, TimeBuf);
  684. //
  685. // Copy the Body of the message
  686. //
  687. pTemp = *ppBody;
  688. *ppBody = *ppTime + wcslen(*ppTime);
  689. wcscpy(*ppBody, pTemp);
  690. LocalFree(pOldHead);
  691. return;
  692. }