xref: /reactos/win32ss/user/winsrv/consrv/history.c (revision 5100859e)
1 /*
2  * LICENSE:         GPL - See COPYING in the top level directory
3  * PROJECT:         ReactOS Console Server DLL
4  * FILE:            win32ss/user/winsrv/consrv/history.c
5  * PURPOSE:         Console line input functions
6  * PROGRAMMERS:     Jeffrey Morlan
7  */
8 
9 /* INCLUDES *******************************************************************/
10 
11 #include "consrv.h"
12 #include "popup.h"
13 
14 #define NDEBUG
15 #include <debug.h>
16 
17 typedef struct _HISTORY_BUFFER
18 {
19     LIST_ENTRY ListEntry;
20     ULONG Position;
21     ULONG MaxEntries;
22     ULONG NumEntries;
23     UNICODE_STRING  ExeName;
24     PUNICODE_STRING Entries;
25 } HISTORY_BUFFER, *PHISTORY_BUFFER;
26 
27 
28 BOOLEAN
29 ConvertInputAnsiToUnicode(PCONSRV_CONSOLE Console,
30                           PVOID    Source,
31                           USHORT   SourceLength,
32                           // BOOLEAN  IsUnicode,
33                           PWCHAR*  Target,
34                           PUSHORT  TargetLength);
35 BOOLEAN
36 ConvertInputUnicodeToAnsi(PCONSRV_CONSOLE Console,
37                           PVOID    Source,
38                           USHORT   SourceLength,
39                           // BOOLEAN  IsAnsi,
40                           PCHAR/* * */   Target,
41                           /*P*/USHORT  TargetLength);
42 
43 
44 /* PRIVATE FUNCTIONS **********************************************************/
45 
46 static PHISTORY_BUFFER
47 HistoryCurrentBuffer(PCONSRV_CONSOLE Console,
48                      PUNICODE_STRING ExeName)
49 {
50     PLIST_ENTRY Entry = Console->HistoryBuffers.Flink;
51     PHISTORY_BUFFER Hist;
52 
53     for (; Entry != &Console->HistoryBuffers; Entry = Entry->Flink)
54     {
55         Hist = CONTAINING_RECORD(Entry, HISTORY_BUFFER, ListEntry);
56         if (RtlEqualUnicodeString(ExeName, &Hist->ExeName, FALSE))
57             return Hist;
58     }
59 
60     /* Couldn't find the buffer, create a new one */
61     Hist = ConsoleAllocHeap(0, sizeof(HISTORY_BUFFER) + ExeName->Length);
62     if (!Hist) return NULL;
63     Hist->MaxEntries = Console->HistoryBufferSize;
64     Hist->NumEntries = 0;
65     Hist->Entries = ConsoleAllocHeap(0, Hist->MaxEntries * sizeof(UNICODE_STRING));
66     if (!Hist->Entries)
67     {
68         ConsoleFreeHeap(Hist);
69         return NULL;
70     }
71     Hist->ExeName.Length = Hist->ExeName.MaximumLength = ExeName->Length;
72     Hist->ExeName.Buffer = (PWCHAR)(Hist + 1);
73     memcpy(Hist->ExeName.Buffer, ExeName->Buffer, ExeName->Length);
74     InsertHeadList(&Console->HistoryBuffers, &Hist->ListEntry);
75     return Hist;
76 }
77 
78 static PHISTORY_BUFFER
79 HistoryFindBuffer(PCONSRV_CONSOLE Console,
80                   PVOID    ExeName,
81                   USHORT   ExeLength,
82                   BOOLEAN  UnicodeExe)
83 {
84     UNICODE_STRING ExeNameU;
85 
86     PLIST_ENTRY Entry;
87     PHISTORY_BUFFER Hist = NULL;
88 
89     if (ExeName == NULL) return NULL;
90 
91     if (UnicodeExe)
92     {
93         ExeNameU.Buffer = ExeName;
94         /* Length is in bytes */
95         ExeNameU.MaximumLength = ExeLength;
96     }
97     else
98     {
99         if (!ConvertInputAnsiToUnicode(Console,
100                                        ExeName, ExeLength,
101                                        &ExeNameU.Buffer, &ExeNameU.MaximumLength))
102         {
103             return NULL;
104         }
105     }
106     ExeNameU.Length = ExeNameU.MaximumLength;
107 
108     Entry = Console->HistoryBuffers.Flink;
109     while (Entry != &Console->HistoryBuffers)
110     {
111         Hist = CONTAINING_RECORD(Entry, HISTORY_BUFFER, ListEntry);
112 
113         /* For the history APIs, the caller is allowed to give only part of the name */
114         if (RtlPrefixUnicodeString(&ExeNameU, &Hist->ExeName, TRUE))
115         {
116             if (!UnicodeExe) ConsoleFreeHeap(ExeNameU.Buffer);
117             return Hist;
118         }
119 
120         Entry = Entry->Flink;
121     }
122 
123     if (!UnicodeExe) ConsoleFreeHeap(ExeNameU.Buffer);
124     return NULL;
125 }
126 
127 static VOID
128 HistoryDeleteBuffer(PHISTORY_BUFFER Hist)
129 {
130     if (!Hist) return;
131 
132     while (Hist->NumEntries != 0)
133         RtlFreeUnicodeString(&Hist->Entries[--Hist->NumEntries]);
134 
135     ConsoleFreeHeap(Hist->Entries);
136     RemoveEntryList(&Hist->ListEntry);
137     ConsoleFreeHeap(Hist);
138 }
139 
140 VOID
141 HistoryAddEntry(PCONSRV_CONSOLE Console,
142                 PUNICODE_STRING ExeName,
143                 PUNICODE_STRING Entry)
144 {
145     // UNICODE_STRING NewEntry;
146     PHISTORY_BUFFER Hist = HistoryCurrentBuffer(Console, ExeName);
147 
148     if (!Hist) return;
149 
150     // NewEntry.Length = NewEntry.MaximumLength = Console->LineSize * sizeof(WCHAR);
151     // NewEntry.Buffer = Console->LineBuffer;
152 
153     /* Don't add blank or duplicate entries */
154     if (Entry->Length == 0 || Hist->MaxEntries == 0 ||
155         (Hist->NumEntries > 0 &&
156          RtlEqualUnicodeString(&Hist->Entries[Hist->NumEntries - 1], Entry, FALSE)))
157     {
158         return;
159     }
160 
161     if (Console->HistoryNoDup)
162     {
163         INT i;
164 
165         /* Check if this line has been entered before */
166         for (i = Hist->NumEntries - 1; i >= 0; i--)
167         {
168             if (RtlEqualUnicodeString(&Hist->Entries[i], Entry, FALSE))
169             {
170                 UNICODE_STRING NewEntry;
171 
172                 /* Just rotate the list to bring this entry to the end */
173                 NewEntry = Hist->Entries[i];
174                 memmove(&Hist->Entries[i], &Hist->Entries[i + 1],
175                         (Hist->NumEntries - (i + 1)) * sizeof(UNICODE_STRING));
176                 Hist->Entries[Hist->NumEntries - 1] = NewEntry;
177                 Hist->Position = Hist->NumEntries - 1;
178                 return;
179             }
180         }
181     }
182 
183     if (Hist->NumEntries == Hist->MaxEntries)
184     {
185         /* List is full, remove oldest entry */
186         RtlFreeUnicodeString(&Hist->Entries[0]);
187         memmove(&Hist->Entries[0], &Hist->Entries[1],
188                 --Hist->NumEntries * sizeof(UNICODE_STRING));
189     }
190 
191     if (NT_SUCCESS(RtlDuplicateUnicodeString(0, Entry, &Hist->Entries[Hist->NumEntries])))
192         Hist->NumEntries++;
193     Hist->Position = Hist->NumEntries - 1;
194 }
195 
196 VOID
197 HistoryGetCurrentEntry(PCONSRV_CONSOLE Console,
198                        PUNICODE_STRING ExeName,
199                        PUNICODE_STRING Entry)
200 {
201     PHISTORY_BUFFER Hist = HistoryCurrentBuffer(Console, ExeName);
202 
203     if (!Hist || Hist->NumEntries == 0)
204         Entry->Length = 0;
205     else
206         *Entry = Hist->Entries[Hist->Position];
207 }
208 
209 BOOL
210 HistoryRecallHistory(PCONSRV_CONSOLE Console,
211                      PUNICODE_STRING ExeName,
212                      INT Offset,
213                      PUNICODE_STRING Entry)
214 {
215     PHISTORY_BUFFER Hist = HistoryCurrentBuffer(Console, ExeName);
216     ULONG Position = 0;
217 
218     if (!Hist || Hist->NumEntries == 0) return FALSE;
219 
220     Position = Hist->Position + Offset;
221     Position = min(max(Position, 0), Hist->NumEntries - 1);
222     Hist->Position = Position;
223 
224     *Entry = Hist->Entries[Hist->Position];
225     return TRUE;
226 }
227 
228 BOOL
229 HistoryFindEntryByPrefix(PCONSRV_CONSOLE Console,
230                          PUNICODE_STRING ExeName,
231                          PUNICODE_STRING Prefix,
232                          PUNICODE_STRING Entry)
233 {
234     INT HistPos;
235 
236     /* Search for history entries starting with input. */
237     PHISTORY_BUFFER Hist = HistoryCurrentBuffer(Console, ExeName);
238     if (!Hist || Hist->NumEntries == 0) return FALSE;
239 
240     /*
241      * Like Up/F5, on first time start from current (usually last) entry,
242      * but on subsequent times start at previous entry.
243      */
244     if (Console->LineUpPressed)
245         Hist->Position = (Hist->Position ? Hist->Position : Hist->NumEntries) - 1;
246     Console->LineUpPressed = TRUE;
247 
248     // Entry.Length = Console->LinePos * sizeof(WCHAR); // == Pos * sizeof(WCHAR)
249     // Entry.Buffer = Console->LineBuffer;
250 
251     /*
252      * Keep going backwards, even wrapping around to the end,
253      * until we get back to starting point.
254      */
255     HistPos = Hist->Position;
256     do
257     {
258         if (RtlPrefixUnicodeString(Prefix, &Hist->Entries[HistPos], FALSE))
259         {
260             Hist->Position = HistPos;
261             *Entry = Hist->Entries[HistPos];
262             return TRUE;
263         }
264         if (--HistPos < 0) HistPos += Hist->NumEntries;
265     } while (HistPos != Hist->Position);
266 
267     return FALSE;
268 }
269 
270 PPOPUP_WINDOW
271 HistoryDisplayCurrentHistory(PCONSRV_CONSOLE Console,
272                              PUNICODE_STRING ExeName)
273 {
274     PTEXTMODE_SCREEN_BUFFER ActiveBuffer;
275     PPOPUP_WINDOW Popup;
276 
277     SHORT xLeft, yTop;
278     SHORT Width, Height;
279 
280     PHISTORY_BUFFER Hist = HistoryCurrentBuffer(Console, ExeName);
281 
282     if (!Hist) return NULL;
283     if (Hist->NumEntries == 0) return NULL;
284 
285     if (GetType(Console->ActiveBuffer) != TEXTMODE_BUFFER) return NULL;
286     ActiveBuffer = (PTEXTMODE_SCREEN_BUFFER)Console->ActiveBuffer;
287 
288     Width  = 40;
289     Height = 10;
290 
291     /* Center the popup window on the screen */
292     xLeft = ActiveBuffer->ViewOrigin.X + (ActiveBuffer->ViewSize.X - Width ) / 2;
293     yTop  = ActiveBuffer->ViewOrigin.Y + (ActiveBuffer->ViewSize.Y - Height) / 2;
294 
295     /* Create the popup */
296     Popup = CreatePopupWindow(Console, ActiveBuffer,
297                               xLeft, yTop, Width, Height);
298     if (Popup == NULL) return NULL;
299 
300     Popup->PopupInputRoutine = NULL;
301 
302     return Popup;
303 }
304 
305 VOID
306 HistoryDeleteCurrentBuffer(PCONSRV_CONSOLE Console,
307                            PUNICODE_STRING ExeName)
308 {
309     HistoryDeleteBuffer(HistoryCurrentBuffer(Console, ExeName));
310 }
311 
312 VOID
313 HistoryDeleteBuffers(PCONSRV_CONSOLE Console)
314 {
315     PLIST_ENTRY CurrentEntry;
316     PHISTORY_BUFFER HistoryBuffer;
317 
318     while (!IsListEmpty(&Console->HistoryBuffers))
319     {
320         CurrentEntry = RemoveHeadList(&Console->HistoryBuffers);
321         HistoryBuffer = CONTAINING_RECORD(CurrentEntry, HISTORY_BUFFER, ListEntry);
322         HistoryDeleteBuffer(HistoryBuffer);
323     }
324 }
325 
326 
327 /* PUBLIC SERVER APIS *********************************************************/
328 
329 CSR_API(SrvGetConsoleCommandHistory)
330 {
331     NTSTATUS Status;
332     PCONSOLE_GETCOMMANDHISTORY GetCommandHistoryRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.GetCommandHistoryRequest;
333     PCONSRV_CONSOLE Console;
334     ULONG BytesWritten = 0;
335     PHISTORY_BUFFER Hist;
336 
337     DPRINT1("SrvGetConsoleCommandHistory entered\n");
338 
339     if ( !CsrValidateMessageBuffer(ApiMessage,
340                                    (PVOID*)&GetCommandHistoryRequest->History,
341                                    GetCommandHistoryRequest->HistoryLength,
342                                    sizeof(BYTE))                    ||
343          !CsrValidateMessageBuffer(ApiMessage,
344                                    (PVOID*)&GetCommandHistoryRequest->ExeName,
345                                    GetCommandHistoryRequest->ExeLength,
346                                    sizeof(BYTE)) )
347     {
348         return STATUS_INVALID_PARAMETER;
349     }
350 
351     Status = ConSrvGetConsole(ConsoleGetPerProcessData(CsrGetClientThread()->Process),
352                               &Console, TRUE);
353     if (!NT_SUCCESS(Status)) return Status;
354 
355     Hist = HistoryFindBuffer(Console,
356                              GetCommandHistoryRequest->ExeName,
357                              GetCommandHistoryRequest->ExeLength,
358                              GetCommandHistoryRequest->Unicode2);
359     if (Hist)
360     {
361         ULONG i;
362 
363         LPSTR  TargetBufferA;
364         LPWSTR TargetBufferW;
365         ULONG BufferSize = GetCommandHistoryRequest->HistoryLength;
366 
367         ULONG Offset = 0;
368         ULONG SourceLength;
369 
370         if (GetCommandHistoryRequest->Unicode)
371         {
372             TargetBufferW = GetCommandHistoryRequest->History;
373             BufferSize /= sizeof(WCHAR);
374         }
375         else
376         {
377             TargetBufferA = GetCommandHistoryRequest->History;
378         }
379 
380         for (i = 0; i < Hist->NumEntries; i++)
381         {
382             SourceLength = Hist->Entries[i].Length / sizeof(WCHAR);
383             if (Offset + SourceLength + 1 > BufferSize)
384             {
385                 Status = STATUS_BUFFER_OVERFLOW;
386                 break;
387             }
388 
389             if (GetCommandHistoryRequest->Unicode)
390             {
391                 RtlCopyMemory(&TargetBufferW[Offset], Hist->Entries[i].Buffer, SourceLength * sizeof(WCHAR));
392                 Offset += SourceLength;
393                 TargetBufferW[Offset++] = L'\0';
394             }
395             else
396             {
397                 ConvertInputUnicodeToAnsi(Console,
398                                           Hist->Entries[i].Buffer, SourceLength * sizeof(WCHAR),
399                                           &TargetBufferA[Offset], SourceLength);
400                 Offset += SourceLength;
401                 TargetBufferA[Offset++] = '\0';
402             }
403         }
404 
405         if (GetCommandHistoryRequest->Unicode)
406             BytesWritten = Offset * sizeof(WCHAR);
407         else
408             BytesWritten = Offset;
409     }
410 
411     // GetCommandHistoryRequest->HistoryLength = TargetBuffer - (PBYTE)GetCommandHistoryRequest->History;
412     GetCommandHistoryRequest->HistoryLength = BytesWritten;
413 
414     ConSrvReleaseConsole(Console, TRUE);
415     return Status;
416 }
417 
418 CSR_API(SrvGetConsoleCommandHistoryLength)
419 {
420     NTSTATUS Status;
421     PCONSOLE_GETCOMMANDHISTORYLENGTH GetCommandHistoryLengthRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.GetCommandHistoryLengthRequest;
422     PCONSRV_CONSOLE Console;
423     PHISTORY_BUFFER Hist;
424     ULONG Length = 0;
425 
426     if (!CsrValidateMessageBuffer(ApiMessage,
427                                   (PVOID*)&GetCommandHistoryLengthRequest->ExeName,
428                                   GetCommandHistoryLengthRequest->ExeLength,
429                                   sizeof(BYTE)))
430     {
431         return STATUS_INVALID_PARAMETER;
432     }
433 
434     Status = ConSrvGetConsole(ConsoleGetPerProcessData(CsrGetClientThread()->Process),
435                               &Console, TRUE);
436     if (!NT_SUCCESS(Status)) return Status;
437 
438     Hist = HistoryFindBuffer(Console,
439                              GetCommandHistoryLengthRequest->ExeName,
440                              GetCommandHistoryLengthRequest->ExeLength,
441                              GetCommandHistoryLengthRequest->Unicode2);
442     if (Hist)
443     {
444         ULONG i;
445         for (i = 0; i < Hist->NumEntries; i++)
446             Length += Hist->Entries[i].Length + sizeof(WCHAR); // Each entry is returned NULL-terminated
447     }
448     /*
449      * Quick and dirty way of getting the number of bytes of the
450      * corresponding ANSI string from the one in UNICODE.
451      */
452     if (!GetCommandHistoryLengthRequest->Unicode)
453         Length /= sizeof(WCHAR);
454 
455     GetCommandHistoryLengthRequest->HistoryLength = Length;
456 
457     ConSrvReleaseConsole(Console, TRUE);
458     return Status;
459 }
460 
461 CSR_API(SrvExpungeConsoleCommandHistory)
462 {
463     NTSTATUS Status;
464     PCONSOLE_EXPUNGECOMMANDHISTORY ExpungeCommandHistoryRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.ExpungeCommandHistoryRequest;
465     PCONSRV_CONSOLE Console;
466     PHISTORY_BUFFER Hist;
467 
468     if (!CsrValidateMessageBuffer(ApiMessage,
469                                   (PVOID*)&ExpungeCommandHistoryRequest->ExeName,
470                                   ExpungeCommandHistoryRequest->ExeLength,
471                                   sizeof(BYTE)))
472     {
473         return STATUS_INVALID_PARAMETER;
474     }
475 
476     Status = ConSrvGetConsole(ConsoleGetPerProcessData(CsrGetClientThread()->Process),
477                               &Console, TRUE);
478     if (!NT_SUCCESS(Status)) return Status;
479 
480     Hist = HistoryFindBuffer(Console,
481                              ExpungeCommandHistoryRequest->ExeName,
482                              ExpungeCommandHistoryRequest->ExeLength,
483                              ExpungeCommandHistoryRequest->Unicode2);
484     HistoryDeleteBuffer(Hist);
485 
486     ConSrvReleaseConsole(Console, TRUE);
487     return Status;
488 }
489 
490 CSR_API(SrvSetConsoleNumberOfCommands)
491 {
492     NTSTATUS Status;
493     PCONSOLE_SETHISTORYNUMBERCOMMANDS SetHistoryNumberCommandsRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.SetHistoryNumberCommandsRequest;
494     PCONSRV_CONSOLE Console;
495     PHISTORY_BUFFER Hist;
496 
497     if (!CsrValidateMessageBuffer(ApiMessage,
498                                   (PVOID*)&SetHistoryNumberCommandsRequest->ExeName,
499                                   SetHistoryNumberCommandsRequest->ExeLength,
500                                   sizeof(BYTE)))
501     {
502         return STATUS_INVALID_PARAMETER;
503     }
504 
505     Status = ConSrvGetConsole(ConsoleGetPerProcessData(CsrGetClientThread()->Process),
506                               &Console, TRUE);
507     if (!NT_SUCCESS(Status)) return Status;
508 
509     Hist = HistoryFindBuffer(Console,
510                              SetHistoryNumberCommandsRequest->ExeName,
511                              SetHistoryNumberCommandsRequest->ExeLength,
512                              SetHistoryNumberCommandsRequest->Unicode2);
513     if (Hist)
514     {
515         ULONG MaxEntries = SetHistoryNumberCommandsRequest->NumCommands;
516         PUNICODE_STRING OldEntryList = Hist->Entries;
517         PUNICODE_STRING NewEntryList = ConsoleAllocHeap(0, MaxEntries * sizeof(UNICODE_STRING));
518         if (!NewEntryList)
519         {
520             Status = STATUS_NO_MEMORY;
521         }
522         else
523         {
524             /* If necessary, shrink by removing oldest entries */
525             for (; Hist->NumEntries > MaxEntries; Hist->NumEntries--)
526             {
527                 RtlFreeUnicodeString(Hist->Entries++);
528                 Hist->Position += (Hist->Position == 0);
529             }
530 
531             Hist->MaxEntries = MaxEntries;
532             Hist->Entries = memcpy(NewEntryList, Hist->Entries,
533                                    Hist->NumEntries * sizeof(UNICODE_STRING));
534             ConsoleFreeHeap(OldEntryList);
535         }
536     }
537 
538     ConSrvReleaseConsole(Console, TRUE);
539     return Status;
540 }
541 
542 CSR_API(SrvGetConsoleHistory)
543 {
544 #if 0 // Vista+
545     PCONSOLE_GETSETHISTORYINFO HistoryInfoRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.HistoryInfoRequest;
546     PCONSRV_CONSOLE Console;
547     NTSTATUS Status = ConSrvGetConsole(ConsoleGetPerProcessData(CsrGetClientThread()->Process),
548                                        &Console, TRUE);
549     if (NT_SUCCESS(Status))
550     {
551         HistoryInfoRequest->HistoryBufferSize      = Console->HistoryBufferSize;
552         HistoryInfoRequest->NumberOfHistoryBuffers = Console->NumberOfHistoryBuffers;
553         HistoryInfoRequest->dwFlags                = (Console->HistoryNoDup ? HISTORY_NO_DUP_FLAG : 0);
554         ConSrvReleaseConsole(Console, TRUE);
555     }
556     return Status;
557 #else
558     DPRINT1("%s not yet implemented\n", __FUNCTION__);
559     return STATUS_NOT_IMPLEMENTED;
560 #endif
561 }
562 
563 CSR_API(SrvSetConsoleHistory)
564 {
565 #if 0 // Vista+
566     PCONSOLE_GETSETHISTORYINFO HistoryInfoRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.HistoryInfoRequest;
567     PCONSRV_CONSOLE Console;
568     NTSTATUS Status = ConSrvGetConsole(ConsoleGetPerProcessData(CsrGetClientThread()->Process),
569                                        &Console, TRUE);
570     if (NT_SUCCESS(Status))
571     {
572         Console->HistoryBufferSize      = HistoryInfoRequest->HistoryBufferSize;
573         Console->NumberOfHistoryBuffers = HistoryInfoRequest->NumberOfHistoryBuffers;
574         Console->HistoryNoDup           = !!(HistoryInfoRequest->dwFlags & HISTORY_NO_DUP_FLAG);
575         ConSrvReleaseConsole(Console, TRUE);
576     }
577     return Status;
578 #else
579     DPRINT1("%s not yet implemented\n", __FUNCTION__);
580     return STATUS_NOT_IMPLEMENTED;
581 #endif
582 }
583 
584 CSR_API(SrvSetConsoleCommandHistoryMode)
585 {
586     NTSTATUS Status;
587     PCONSOLE_SETHISTORYMODE SetHistoryModeRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.SetHistoryModeRequest;
588     PCONSRV_CONSOLE Console;
589 
590     DPRINT("SrvSetConsoleCommandHistoryMode(Mode = %d) is not yet implemented\n",
591             SetHistoryModeRequest->Mode);
592 
593     Status = ConSrvGetConsole(ConsoleGetPerProcessData(CsrGetClientThread()->Process),
594                               &Console, TRUE);
595     if (!NT_SUCCESS(Status)) return Status;
596 
597     Console->InsertMode = !!(SetHistoryModeRequest->Mode & CONSOLE_OVERSTRIKE);
598 
599     ConSrvReleaseConsole(Console, TRUE);
600     return STATUS_SUCCESS;
601 }
602 
603 /* EOF */
604