Fix merge r65567.
[reactos.git] / win32ss / user / winsrv / consrv / history.c
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 */