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