1 /* 2 * PROJECT: ReactOS More Command 3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) 4 * PURPOSE: Displays text stream from STDIN or from an arbitrary number 5 * of files to STDOUT, with screen capabilities (more than CAT, 6 * but less than LESS ^^). 7 * COPYRIGHT: Copyright 1999 Paolo Pantaleo 8 * Copyright 2003 Timothy Schepens 9 * Copyright 2016-2021 Hermes Belusca-Maito 10 * Copyright 2021 Katayama Hirofumi MZ 11 */ 12 /* 13 * MORE.C - external command. 14 * 15 * clone from 4nt more command 16 * 17 * 26 Sep 1999 - Paolo Pantaleo <paolopan@freemail.it> 18 * started 19 * 20 * Oct 2003 - Timothy Schepens <tischepe at fastmail dot fm> 21 * use window size instead of buffer size. 22 */ 23 24 #include <stdio.h> 25 #include <stdlib.h> 26 27 #include <windef.h> 28 #include <winbase.h> 29 #include <winnt.h> 30 #include <winnls.h> 31 #include <winreg.h> 32 #include <winuser.h> 33 34 #include <conutils.h> 35 #include <strsafe.h> 36 37 #include "resource.h" 38 39 /* PagePrompt statistics for the current file */ 40 DWORD dwFileSize; // In bytes 41 DWORD dwSumReadBytes, dwSumReadChars; 42 // The average number of bytes per character is equal to 43 // dwSumReadBytes / dwSumReadChars. Note that dwSumReadChars 44 // will never be == 0 when ConWritePaging (and possibly PagePrompt) 45 // is called. 46 47 /* Handles for file and console */ 48 HANDLE hFile = INVALID_HANDLE_VALUE; 49 HANDLE hStdIn, hStdOut; 50 HANDLE hKeyboard; 51 52 /* Enable/Disable extensions */ 53 BOOL bEnableExtensions = TRUE; // FIXME: By default, it should be FALSE. 54 55 /* Parser flags */ 56 #define FLAG_HELP (1 << 0) 57 #define FLAG_E (1 << 1) 58 #define FLAG_C (1 << 2) 59 #define FLAG_P (1 << 3) 60 #define FLAG_S (1 << 4) 61 #define FLAG_Tn (1 << 5) 62 #define FLAG_PLUSn (1 << 6) 63 64 /* Prompt flags */ 65 #define PROMPT_PERCENT (1 << 0) 66 #define PROMPT_LINE_AT (1 << 1) 67 #define PROMPT_OPTIONS (1 << 2) 68 #define PROMPT_LINES (1 << 3) 69 70 static DWORD s_dwFlags = 0; 71 static LONG s_nTabWidth = 8; 72 static DWORD s_nNextLineNo = 0; 73 static BOOL s_bPrevLineIsBlank = FALSE; 74 static WORD s_fPrompt = 0; 75 static BOOL s_bDoNextFile = FALSE; 76 77 static BOOL IsBlankLine(IN PCWCH line, IN DWORD cch) 78 { 79 DWORD ich; 80 WORD wType; 81 for (ich = 0; ich < cch; ++ich) 82 { 83 /* 84 * Explicitly exclude FORM-FEED from the check, 85 * so that the pager can handle it. 86 */ 87 if (line[ich] == L'\f') 88 return FALSE; 89 90 /* 91 * Otherwise do the extended blanks check. 92 * Note that MS MORE.COM only checks for spaces (\x20) and TABs (\x09). 93 * See http://archives.miloush.net/michkap/archive/2007/06/11/3230072.html 94 * for more information. 95 */ 96 wType = 0; 97 GetStringTypeW(CT_CTYPE1, &line[ich], 1, &wType); 98 if (!(wType & (C1_BLANK | C1_SPACE))) 99 return FALSE; 100 } 101 return TRUE; 102 } 103 104 static BOOL 105 __stdcall 106 MorePagerLine( 107 IN OUT PCON_PAGER Pager, 108 IN PCWCH line, 109 IN DWORD cch) 110 { 111 if (s_dwFlags & FLAG_PLUSn) /* Skip lines */ 112 { 113 if (Pager->lineno < s_nNextLineNo) 114 { 115 s_bPrevLineIsBlank = FALSE; 116 return TRUE; /* Handled */ 117 } 118 s_dwFlags &= ~FLAG_PLUSn; 119 } 120 121 if (s_dwFlags & FLAG_S) /* Shrink blank lines */ 122 { 123 if (IsBlankLine(line, cch)) 124 { 125 if (s_bPrevLineIsBlank) 126 return TRUE; /* Handled */ 127 128 /* 129 * Display a single blank line, independently of the actual size 130 * of the current line, by displaying just one space: this is 131 * especially needed in order to force line wrapping when the 132 * ENABLE_VIRTUAL_TERMINAL_PROCESSING or DISABLE_NEWLINE_AUTO_RETURN 133 * console modes are enabled. 134 * Then, reposition the cursor to the next line, first column. 135 */ 136 if (Pager->PageColumns > 0) 137 ConStreamWrite(Pager->Screen->Stream, TEXT(" "), 1); 138 ConStreamWrite(Pager->Screen->Stream, TEXT("\n"), 1); 139 Pager->iLine++; 140 Pager->iColumn = 0; 141 142 s_bPrevLineIsBlank = TRUE; 143 s_nNextLineNo = 0; 144 145 return TRUE; /* Handled */ 146 } 147 else 148 { 149 s_bPrevLineIsBlank = FALSE; 150 } 151 } 152 153 s_nNextLineNo = 0; 154 /* Not handled, let the pager do the default action */ 155 return FALSE; 156 } 157 158 static BOOL 159 __stdcall 160 PagePrompt(PCON_PAGER Pager, DWORD Done, DWORD Total) 161 { 162 HANDLE hInput = ConStreamGetOSHandle(StdIn); 163 HANDLE hOutput = ConStreamGetOSHandle(Pager->Screen->Stream); 164 CONSOLE_SCREEN_BUFFER_INFO csbi; 165 COORD orgCursorPosition; 166 DWORD dwMode; 167 168 KEY_EVENT_RECORD KeyEvent; 169 BOOL fCtrl; 170 DWORD nLines; 171 WCHAR chSubCommand = 0; 172 173 /* Prompt strings (small size since the prompt should 174 * hold ideally on one <= 80-character line) */ 175 static WCHAR StrPercent[80] = L""; 176 static WCHAR StrLineAt[80] = L""; 177 static WCHAR StrOptions[80] = L""; 178 static WCHAR StrLines[80] = L""; 179 static BOOL AreStrLoaded = FALSE; 180 181 WCHAR szPercent[80] = L""; 182 WCHAR szLineAt[80] = L""; 183 184 /* Load the prompt strings */ 185 if (!AreStrLoaded) 186 { 187 K32LoadStringW(NULL, IDS_CONTINUE_PERCENT, StrPercent, ARRAYSIZE(StrPercent)); 188 K32LoadStringW(NULL, IDS_CONTINUE_LINE_AT, StrLineAt, ARRAYSIZE(StrLineAt)); 189 K32LoadStringW(NULL, IDS_CONTINUE_OPTIONS, StrOptions, ARRAYSIZE(StrOptions)); 190 K32LoadStringW(NULL, IDS_CONTINUE_LINES, StrLines, ARRAYSIZE(StrLines)); 191 AreStrLoaded = TRUE; 192 } 193 194 /* 195 * Check whether the pager is prompting, but we have actually finished 196 * to display a given file, or no data is present in STDIN anymore. 197 * In this case, skip the prompt altogether. The only exception is when 198 * we are displaying other files. 199 */ 200 // TODO: Implement! 201 202 Restart: 203 nLines = 0; 204 205 /* Do not show the progress percentage when STDIN is being displayed */ 206 if (s_fPrompt & PROMPT_PERCENT) // && (hFile != hStdIn) 207 { 208 /* 209 * The progress percentage is evaluated as follows. 210 * So far we have read a total of 'dwSumReadBytes' bytes from the file. 211 * Amongst those is the latest read chunk of 'dwReadBytes' bytes, to which 212 * correspond a number of 'dwReadChars' characters with which we have called 213 * ConWritePaging who called PagePrompt. We then have: Total == dwReadChars. 214 * During this ConWritePaging call the PagePrompt was called after 'Done' 215 * number of characters over 'Total'. 216 * It should be noted that for 'dwSumReadBytes' number of bytes read it 217 * *roughly* corresponds 'dwSumReadChars' number of characters. This is 218 * because there may be some failures happening during the conversion of 219 * the bytes read to the character string for a given encoding. 220 * Therefore the number of characters displayed on screen is equal to: 221 * dwSumReadChars - Total + Done , 222 * but the best corresponding approximed number of bytes would be: 223 * dwSumReadBytes - (Total - Done) * (dwSumReadBytes / dwSumReadChars) , 224 * where the ratio is the average number of bytes per character. 225 * The percentage is then computed relative to the total file size. 226 */ 227 DWORD dwPercent = (dwSumReadBytes - (Total - Done) * 228 (dwSumReadBytes / dwSumReadChars)) * 100 / dwFileSize; 229 StringCchPrintfW(szPercent, ARRAYSIZE(szPercent), StrPercent, dwPercent); 230 } 231 if (s_fPrompt & PROMPT_LINE_AT) 232 { 233 StringCchPrintfW(szLineAt, ARRAYSIZE(szLineAt), StrLineAt, Pager->lineno); 234 } 235 236 /* Suitably format and display the prompt */ 237 ConResMsgPrintf(Pager->Screen->Stream, 0, IDS_CONTINUE_PROMPT, 238 (s_fPrompt & PROMPT_PERCENT ? szPercent : L""), 239 (s_fPrompt & PROMPT_LINE_AT ? szLineAt : L""), 240 (s_fPrompt & PROMPT_OPTIONS ? StrOptions : L""), 241 (s_fPrompt & PROMPT_LINES ? StrLines : L"")); 242 243 /* Reset the prompt to a default state */ 244 s_fPrompt &= ~(PROMPT_LINE_AT | PROMPT_OPTIONS | PROMPT_LINES); 245 246 /* RemoveBreakHandler */ 247 SetConsoleCtrlHandler(NULL, TRUE); 248 /* ConInDisable */ 249 GetConsoleMode(hInput, &dwMode); 250 dwMode &= ~ENABLE_PROCESSED_INPUT; 251 SetConsoleMode(hInput, dwMode); 252 253 // FIXME: Does not support TTY yet! 254 ConGetScreenInfo(Pager->Screen, &csbi); 255 orgCursorPosition = csbi.dwCursorPosition; 256 for (;;) 257 { 258 INPUT_RECORD ir = {0}; 259 DWORD dwRead; 260 WCHAR ch; 261 262 do 263 { 264 ReadConsoleInput(hInput, &ir, 1, &dwRead); 265 } 266 while ((ir.EventType != KEY_EVENT) || (!ir.Event.KeyEvent.bKeyDown)); 267 268 /* Got our key */ 269 KeyEvent = ir.Event.KeyEvent; 270 271 /* Ignore any unsupported keyboard press */ 272 if ((KeyEvent.wVirtualKeyCode == VK_SHIFT) || 273 (KeyEvent.wVirtualKeyCode == VK_MENU) || 274 (KeyEvent.wVirtualKeyCode == VK_CONTROL)) 275 { 276 continue; 277 } 278 279 /* Ctrl key is pressed? */ 280 fCtrl = !!(KeyEvent.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)); 281 282 /* Ctrl+C or Ctrl+Esc? */ 283 if (fCtrl && ((KeyEvent.wVirtualKeyCode == VK_ESCAPE) || 284 (KeyEvent.wVirtualKeyCode == L'C'))) 285 { 286 chSubCommand = 0; 287 break; 288 } 289 290 /* If extended features are unavailable, or no 291 * pending commands, don't do more processing. */ 292 if (!(s_dwFlags & FLAG_E) || (chSubCommand == 0)) 293 break; 294 295 ch = KeyEvent.uChar.UnicodeChar; 296 if (L'0' <= ch && ch <= L'9') 297 { 298 nLines *= 10; 299 nLines += ch - L'0'; 300 ConStreamWrite(Pager->Screen->Stream, &ch, 1); 301 continue; 302 } 303 else if (KeyEvent.wVirtualKeyCode == VK_RETURN) 304 { 305 /* Validate the line number */ 306 break; 307 } 308 else if (KeyEvent.wVirtualKeyCode == VK_ESCAPE) 309 { 310 /* Cancel the current command */ 311 chSubCommand = 0; 312 break; 313 } 314 else if (KeyEvent.wVirtualKeyCode == VK_BACK) 315 { 316 if (nLines != 0) 317 nLines /= 10; 318 319 /* Erase the current character */ 320 ConGetScreenInfo(Pager->Screen, &csbi); 321 if ( (csbi.dwCursorPosition.Y > orgCursorPosition.Y) || 322 ((csbi.dwCursorPosition.Y == orgCursorPosition.Y) && 323 (csbi.dwCursorPosition.X > orgCursorPosition.X)) ) 324 { 325 if (csbi.dwCursorPosition.X > 0) 326 { 327 csbi.dwCursorPosition.X = csbi.dwCursorPosition.X - 1; 328 } 329 else if (csbi.dwCursorPosition.Y > 0) 330 { 331 csbi.dwCursorPosition.Y = csbi.dwCursorPosition.Y - 1; 332 csbi.dwCursorPosition.X = (csbi.dwSize.X ? csbi.dwSize.X - 1 : 0); 333 } 334 335 SetConsoleCursorPosition(hOutput, csbi.dwCursorPosition); 336 337 ch = L' '; 338 ConStreamWrite(Pager->Screen->Stream, &ch, 1); 339 SetConsoleCursorPosition(hOutput, csbi.dwCursorPosition); 340 } 341 342 continue; 343 } 344 } 345 346 /* AddBreakHandler */ 347 SetConsoleCtrlHandler(NULL, FALSE); 348 /* ConInEnable */ 349 GetConsoleMode(hInput, &dwMode); 350 dwMode |= ENABLE_PROCESSED_INPUT; 351 SetConsoleMode(hInput, dwMode); 352 353 /* Refresh the screen information, as the console may have been 354 * redimensioned. Update also the default number of lines to scroll. */ 355 ConGetScreenInfo(Pager->Screen, &csbi); 356 Pager->ScrollRows = csbi.srWindow.Bottom - csbi.srWindow.Top; 357 358 /* 359 * Erase the full line where the cursor is, and move 360 * the cursor back to the beginning of the line. 361 */ 362 ConClearLine(Pager->Screen->Stream); 363 364 /* Ctrl+C or Ctrl+Esc: Control Break */ 365 if (fCtrl && ((KeyEvent.wVirtualKeyCode == VK_ESCAPE) || 366 (KeyEvent.wVirtualKeyCode == L'C'))) 367 { 368 /* We break, output a newline */ 369 WCHAR ch = L'\n'; 370 ConStreamWrite(Pager->Screen->Stream, &ch, 1); 371 return FALSE; 372 } 373 374 switch (chSubCommand) 375 { 376 case L'P': 377 { 378 /* If we don't display other lines, just restart the prompt */ 379 if (nLines == 0) 380 { 381 chSubCommand = 0; 382 goto Restart; 383 } 384 /* Otherwise tell the pager to display them */ 385 Pager->ScrollRows = nLines; 386 return TRUE; 387 } 388 case L'S': 389 { 390 s_dwFlags |= FLAG_PLUSn; 391 s_nNextLineNo = Pager->lineno + nLines; 392 /* Use the default Pager->ScrollRows value */ 393 return TRUE; 394 } 395 default: 396 chSubCommand = 0; 397 break; 398 } 399 400 /* If extended features are available */ 401 if (s_dwFlags & FLAG_E) 402 { 403 /* Ignore any key presses if Ctrl is pressed */ 404 if (fCtrl) 405 { 406 chSubCommand = 0; 407 goto Restart; 408 } 409 410 /* 'Q': Quit */ 411 if (KeyEvent.wVirtualKeyCode == L'Q') 412 { 413 /* We break, output a newline */ 414 WCHAR ch = L'\n'; 415 ConStreamWrite(Pager->Screen->Stream, &ch, 1); 416 return FALSE; 417 } 418 419 /* 'F': Next file */ 420 if (KeyEvent.wVirtualKeyCode == L'F') 421 { 422 s_bDoNextFile = TRUE; 423 return FALSE; 424 } 425 426 /* '?': Show Options */ 427 if (KeyEvent.uChar.UnicodeChar == L'?') 428 { 429 s_fPrompt |= PROMPT_OPTIONS; 430 goto Restart; 431 } 432 433 /* [Enter] key: Display one line */ 434 if (KeyEvent.wVirtualKeyCode == VK_RETURN) 435 { 436 Pager->ScrollRows = 1; 437 return TRUE; 438 } 439 440 /* [Space] key: Display one page */ 441 if (KeyEvent.wVirtualKeyCode == VK_SPACE) 442 { 443 if (s_dwFlags & FLAG_C) 444 { 445 /* Clear the screen */ 446 ConClearScreen(Pager->Screen); 447 } 448 /* Use the default Pager->ScrollRows value */ 449 return TRUE; 450 } 451 452 /* 'P': Display n lines */ 453 if (KeyEvent.wVirtualKeyCode == L'P') 454 { 455 s_fPrompt |= PROMPT_LINES; 456 chSubCommand = L'P'; 457 goto Restart; 458 } 459 460 /* 'S': Skip n lines */ 461 if (KeyEvent.wVirtualKeyCode == L'S') 462 { 463 s_fPrompt |= PROMPT_LINES; 464 chSubCommand = L'S'; 465 goto Restart; 466 } 467 468 /* '=': Show current line number */ 469 if (KeyEvent.uChar.UnicodeChar == L'=') 470 { 471 s_fPrompt |= PROMPT_LINE_AT; 472 goto Restart; 473 } 474 475 chSubCommand = 0; 476 goto Restart; 477 } 478 else 479 { 480 /* Extended features are unavailable: display one page */ 481 /* Use the default Pager->ScrollRows value */ 482 return TRUE; 483 } 484 } 485 486 /* 487 * See base/applications/cmdutils/clip/clip.c!IsDataUnicode() 488 * and base/applications/notepad/text.c!ReadText() for more details. 489 * Also some good code example can be found at: 490 * https://github.com/AutoIt/text-encoding-detect 491 */ 492 typedef enum 493 { 494 ENCODING_ANSI = 0, 495 ENCODING_UTF16LE = 1, 496 ENCODING_UTF16BE = 2, 497 ENCODING_UTF8 = 3 498 } ENCODING; 499 500 static BOOL 501 IsDataUnicode( 502 IN PVOID Buffer, 503 IN DWORD BufferSize, 504 OUT ENCODING* Encoding OPTIONAL, 505 OUT PDWORD SkipBytes OPTIONAL) 506 { 507 PBYTE pBytes = Buffer; 508 ENCODING encFile = ENCODING_ANSI; 509 DWORD dwPos = 0; 510 511 /* 512 * See http://archives.miloush.net/michkap/archive/2007/04/22/2239345.html 513 * for more details about the algorithm and the pitfalls behind it. 514 * Of course it would be actually great to make a nice function that 515 * would work, once and for all, and put it into a library. 516 */ 517 518 /* Look for Byte Order Marks */ 519 if ((BufferSize >= 2) && (pBytes[0] == 0xFF) && (pBytes[1] == 0xFE)) 520 { 521 encFile = ENCODING_UTF16LE; 522 dwPos = 2; 523 } 524 else if ((BufferSize >= 2) && (pBytes[0] == 0xFE) && (pBytes[1] == 0xFF)) 525 { 526 encFile = ENCODING_UTF16BE; 527 dwPos = 2; 528 } 529 else if ((BufferSize >= 3) && (pBytes[0] == 0xEF) && (pBytes[1] == 0xBB) && (pBytes[2] == 0xBF)) 530 { 531 encFile = ENCODING_UTF8; 532 dwPos = 3; 533 } 534 else 535 { 536 /* 537 * Try using statistical analysis. Do not rely on the return value of 538 * IsTextUnicode as we can get FALSE even if the text is in UTF-16 BE 539 * (i.e. we have some of the IS_TEXT_UNICODE_REVERSE_MASK bits set). 540 * Instead, set all the tests we want to perform, then just check 541 * the passed tests and try to deduce the string properties. 542 */ 543 544 /* 545 * This mask contains the 3 highest bits from IS_TEXT_UNICODE_NOT_ASCII_MASK 546 * and the 1st highest bit from IS_TEXT_UNICODE_NOT_UNICODE_MASK. 547 */ 548 #define IS_TEXT_UNKNOWN_FLAGS_MASK ((7 << 13) | (1 << 11)) 549 550 /* Flag out the unknown flags here, the passed tests will not have them either */ 551 INT Tests = (IS_TEXT_UNICODE_NOT_ASCII_MASK | 552 IS_TEXT_UNICODE_NOT_UNICODE_MASK | 553 IS_TEXT_UNICODE_REVERSE_MASK | IS_TEXT_UNICODE_UNICODE_MASK) 554 & ~IS_TEXT_UNKNOWN_FLAGS_MASK; 555 INT Results; 556 557 IsTextUnicode(Buffer, BufferSize, &Tests); 558 Results = Tests; 559 560 /* 561 * As the IS_TEXT_UNICODE_NULL_BYTES or IS_TEXT_UNICODE_ILLEGAL_CHARS 562 * flags are expected to be potentially present in the result without 563 * modifying our expectations, filter them out now. 564 */ 565 Results &= ~(IS_TEXT_UNICODE_NULL_BYTES | IS_TEXT_UNICODE_ILLEGAL_CHARS); 566 567 /* 568 * NOTE: The flags IS_TEXT_UNICODE_ASCII16 and 569 * IS_TEXT_UNICODE_REVERSE_ASCII16 are not reliable. 570 * 571 * NOTE2: Check for potential "bush hid the facts" effect by also 572 * checking the original results (in 'Tests') for the absence of 573 * the IS_TEXT_UNICODE_NULL_BYTES flag, as we may presumably expect 574 * that in UTF-16 text there will be at some point some NULL bytes. 575 * If not, fall back to ANSI. This shows the limitations of using the 576 * IsTextUnicode API to perform such tests, and the usage of a more 577 * improved encoding detection algorithm would be really welcome. 578 */ 579 if (!(Results & IS_TEXT_UNICODE_NOT_UNICODE_MASK) && 580 !(Results & IS_TEXT_UNICODE_REVERSE_MASK) && 581 (Results & IS_TEXT_UNICODE_UNICODE_MASK) && 582 (Tests & IS_TEXT_UNICODE_NULL_BYTES)) 583 { 584 encFile = ENCODING_UTF16LE; 585 dwPos = (Results & IS_TEXT_UNICODE_SIGNATURE) ? 2 : 0; 586 } 587 else 588 if (!(Results & IS_TEXT_UNICODE_NOT_UNICODE_MASK) && 589 !(Results & IS_TEXT_UNICODE_UNICODE_MASK) && 590 (Results & IS_TEXT_UNICODE_REVERSE_MASK) && 591 (Tests & IS_TEXT_UNICODE_NULL_BYTES)) 592 { 593 encFile = ENCODING_UTF16BE; 594 dwPos = (Results & IS_TEXT_UNICODE_REVERSE_SIGNATURE) ? 2 : 0; 595 } 596 else 597 { 598 /* 599 * Either 'Results' has neither of those masks set, as it can be 600 * the case for UTF-8 text (or ANSI), or it has both as can be the 601 * case when analysing pure binary data chunk. This is therefore 602 * invalid and we fall back to ANSI encoding. 603 * FIXME: In case of failure, assume ANSI (as long as we do not have 604 * correct tests for UTF8, otherwise we should do them, and at the 605 * very end, assume ANSI). 606 */ 607 encFile = ENCODING_ANSI; // ENCODING_UTF8; 608 dwPos = 0; 609 } 610 } 611 612 if (Encoding) 613 *Encoding = encFile; 614 if (SkipBytes) 615 *SkipBytes = dwPos; 616 617 return (encFile != ENCODING_ANSI); 618 } 619 620 /* 621 * Adapted from base/shell/cmd/misc.c!FileGetString(), but with correct 622 * text encoding support. Also please note that similar code should be 623 * also used in the CMD.EXE 'TYPE' command. 624 * Contrary to CMD's FileGetString() we do not stop at new-lines. 625 * 626 * Read text data from a file and convert it from a given encoding to UTF-16. 627 * 628 * IN OUT PVOID pCacheBuffer and IN DWORD CacheBufferLength : 629 * Implementation detail so that the function uses an external user-provided 630 * buffer to store the data temporarily read from the file. The function 631 * could have used an internal buffer instead. The length is in number of bytes. 632 * 633 * IN OUT PWSTR* pBuffer and IN OUT PDWORD pnBufferLength : 634 * Reallocated buffer containing the string data converted to UTF-16. 635 * In input, contains a pointer to the original buffer and its length. 636 * In output, contains a pointer to the reallocated buffer and its length. 637 * The length is in number of characters. 638 * 639 * At first call to this function, pBuffer can be set to NULL, in which case 640 * when the function returns the pointer will point to a valid buffer. 641 * After the last call to this function, free the pBuffer pointer with: 642 * HeapFree(GetProcessHeap(), 0, *pBuffer); 643 * 644 * If Encoding is set to ENCODING_UTF16LE or ENCODING_UTF16BE, since we are 645 * compiled in UNICODE, no extra conversion is performed and therefore 646 * pBuffer is unused (remains unallocated) and one can directly use the 647 * contents of pCacheBuffer as it is expected to contain valid UTF-16 text. 648 * 649 * OUT PDWORD pdwReadBytes : Number of bytes read from the file (optional). 650 * OUT PDWORD pdwReadChars : Corresponding number of characters read (optional). 651 */ 652 static BOOL 653 FileGetString( 654 IN HANDLE hFile, 655 IN ENCODING Encoding, 656 IN OUT PVOID pCacheBuffer, 657 IN DWORD CacheBufferLength, 658 IN OUT PWCHAR* pBuffer, 659 IN OUT PDWORD pnBufferLength, 660 OUT PDWORD pdwReadBytes OPTIONAL, 661 OUT PDWORD pdwReadChars OPTIONAL) 662 { 663 BOOL Success; 664 UINT CodePage = (UINT)-1; 665 DWORD dwReadBytes; 666 INT len; 667 668 // ASSERT(pCacheBuffer && (CacheBufferLength > 0)); 669 // ASSERT(CacheBufferLength % 2 == 0); // Cache buffer length MUST BE even! 670 // ASSERT(pBuffer && pnBufferLength); 671 672 /* Always reset the retrieved number of bytes/characters */ 673 if (pdwReadBytes) *pdwReadBytes = 0; 674 if (pdwReadChars) *pdwReadChars = 0; 675 676 Success = ReadFile(hFile, pCacheBuffer, CacheBufferLength, &dwReadBytes, NULL); 677 if (!Success || dwReadBytes == 0) 678 return FALSE; 679 680 if (pdwReadBytes) *pdwReadBytes = dwReadBytes; 681 682 if ((Encoding == ENCODING_ANSI) || (Encoding == ENCODING_UTF8)) 683 { 684 /* Conversion is needed */ 685 686 if (Encoding == ENCODING_ANSI) 687 CodePage = GetConsoleCP(); // CP_ACP; // FIXME: Cache GetConsoleCP() value. 688 else // if (Encoding == ENCODING_UTF8) 689 CodePage = CP_UTF8; 690 691 /* Retrieve the needed buffer size */ 692 len = MultiByteToWideChar(CodePage, 0, pCacheBuffer, dwReadBytes, 693 NULL, 0); 694 if (len == 0) 695 { 696 /* Failure, bail out */ 697 return FALSE; 698 } 699 700 /* Initialize the conversion buffer if needed... */ 701 if (*pBuffer == NULL) 702 { 703 *pnBufferLength = len; 704 *pBuffer = HeapAlloc(GetProcessHeap(), 0, *pnBufferLength * sizeof(WCHAR)); 705 if (*pBuffer == NULL) 706 { 707 // *pBuffer = NULL; 708 *pnBufferLength = 0; 709 // WARN("DEBUG: Cannot allocate memory for *pBuffer!\n"); 710 // ConErrFormatMessage(GetLastError()); 711 return FALSE; 712 } 713 } 714 /* ... or reallocate only if the new length is greater than the old one */ 715 else if (len > *pnBufferLength) 716 { 717 PWSTR OldBuffer = *pBuffer; 718 719 *pnBufferLength = len; 720 *pBuffer = HeapReAlloc(GetProcessHeap(), 0, *pBuffer, *pnBufferLength * sizeof(WCHAR)); 721 if (*pBuffer == NULL) 722 { 723 /* Do not leak old buffer */ 724 HeapFree(GetProcessHeap(), 0, OldBuffer); 725 // *pBuffer = NULL; 726 *pnBufferLength = 0; 727 // WARN("DEBUG: Cannot reallocate memory for *pBuffer!\n"); 728 // ConErrFormatMessage(GetLastError()); 729 return FALSE; 730 } 731 } 732 733 /* Now perform the conversion proper */ 734 len = MultiByteToWideChar(CodePage, 0, pCacheBuffer, dwReadBytes, 735 *pBuffer, len); 736 dwReadBytes = len; 737 } 738 else 739 { 740 /* 741 * No conversion needed, just convert from big to little endian if needed. 742 * pBuffer and pnBufferLength are left untouched and pCacheBuffer can be 743 * directly used. 744 */ 745 PWCHAR pWChars = pCacheBuffer; 746 DWORD i; 747 748 dwReadBytes /= sizeof(WCHAR); 749 750 if (Encoding == ENCODING_UTF16BE) 751 { 752 for (i = 0; i < dwReadBytes; i++) 753 { 754 /* Equivalent to RtlUshortByteSwap: reverse high/low bytes */ 755 pWChars[i] = MAKEWORD(HIBYTE(pWChars[i]), LOBYTE(pWChars[i])); 756 } 757 } 758 // else if (Encoding == ENCODING_UTF16LE), we are good, nothing to do. 759 } 760 761 /* Return the number of characters (dwReadBytes is converted) */ 762 if (pdwReadChars) *pdwReadChars = dwReadBytes; 763 764 return TRUE; 765 } 766 767 static VOID 768 LoadRegistrySettings(HKEY hKeyRoot) 769 { 770 LONG lRet; 771 HKEY hKey; 772 DWORD dwType, len; 773 /* 774 * Buffer big enough to hold the string L"4294967295", 775 * corresponding to the literal 0xFFFFFFFF (MAXULONG) in decimal. 776 */ 777 WCHAR Buffer[sizeof("4294967295")]; 778 C_ASSERT(sizeof(Buffer) >= sizeof(DWORD)); 779 780 lRet = RegOpenKeyExW(hKeyRoot, 781 L"Software\\Microsoft\\Command Processor", 782 0, 783 KEY_QUERY_VALUE, 784 &hKey); 785 if (lRet != ERROR_SUCCESS) 786 return; 787 788 len = sizeof(Buffer); 789 lRet = RegQueryValueExW(hKey, 790 L"EnableExtensions", 791 NULL, 792 &dwType, 793 (PBYTE)&Buffer, 794 &len); 795 if (lRet == ERROR_SUCCESS) 796 { 797 /* Overwrite the default setting */ 798 if (dwType == REG_DWORD) 799 bEnableExtensions = !!*(PDWORD)Buffer; 800 else if (dwType == REG_SZ) 801 bEnableExtensions = (_wtol((PWSTR)Buffer) == 1); 802 } 803 // else, use the default setting set globally. 804 805 RegCloseKey(hKey); 806 } 807 808 static BOOL IsFlag(PCWSTR param) 809 { 810 PCWSTR pch; 811 PWCHAR endptr; 812 813 if (param[0] == L'/') 814 return TRUE; 815 816 if (param[0] == L'+') 817 { 818 pch = param + 1; 819 if (*pch) 820 { 821 (void)wcstol(pch, &endptr, 10); 822 return (*endptr == 0); 823 } 824 } 825 return FALSE; 826 } 827 828 static BOOL ParseArgument(PCWSTR arg, BOOL* pbHasFiles) 829 { 830 PWCHAR endptr; 831 832 if (arg[0] == L'/') 833 { 834 switch (towupper(arg[1])) 835 { 836 case L'?': 837 if (arg[2] == 0) 838 { 839 s_dwFlags |= FLAG_HELP; 840 return TRUE; 841 } 842 break; 843 case L'E': 844 if (arg[2] == 0) 845 { 846 s_dwFlags |= FLAG_E; 847 return TRUE; 848 } 849 break; 850 case L'C': 851 if (arg[2] == 0) 852 { 853 s_dwFlags |= FLAG_C; 854 return TRUE; 855 } 856 break; 857 case L'P': 858 if (arg[2] == 0) 859 { 860 s_dwFlags |= FLAG_P; 861 return TRUE; 862 } 863 break; 864 case L'S': 865 if (arg[2] == 0) 866 { 867 s_dwFlags |= FLAG_S; 868 return TRUE; 869 } 870 break; 871 case L'T': 872 if (arg[2] != 0) 873 { 874 s_dwFlags |= FLAG_Tn; 875 s_nTabWidth = wcstol(&arg[2], &endptr, 10); 876 if (*endptr == 0) 877 return TRUE; 878 } 879 break; 880 default: 881 break; 882 } 883 } 884 else if (arg[0] == L'+') 885 { 886 if (arg[1] != 0) 887 { 888 s_dwFlags |= FLAG_PLUSn; 889 s_nNextLineNo = wcstol(&arg[1], &endptr, 10) + 1; 890 if (*endptr == 0) 891 return TRUE; 892 } 893 } 894 895 if (IsFlag(arg)) 896 { 897 ConResPrintf(StdErr, IDS_BAD_FLAG, arg); 898 return FALSE; 899 } 900 else 901 { 902 *pbHasFiles = TRUE; 903 } 904 905 return TRUE; 906 } 907 908 static BOOL ParseMoreVariable(BOOL* pbHasFiles) 909 { 910 BOOL ret = TRUE; 911 PWSTR psz; 912 PWCHAR pch; 913 DWORD cch; 914 915 cch = GetEnvironmentVariableW(L"MORE", NULL, 0); 916 if (cch == 0) 917 return TRUE; 918 919 psz = (PWSTR)malloc((cch + 1) * sizeof(WCHAR)); 920 if (!psz) 921 return TRUE; 922 923 if (!GetEnvironmentVariableW(L"MORE", psz, cch + 1)) 924 { 925 free(psz); 926 return TRUE; 927 } 928 929 for (pch = wcstok(psz, L" "); pch; pch = wcstok(NULL, L" ")) 930 { 931 ret = ParseArgument(pch, pbHasFiles); 932 if (!ret) 933 break; 934 } 935 936 free(psz); 937 return ret; 938 } 939 940 // INT CommandMore(LPTSTR cmd, LPTSTR param) 941 int wmain(int argc, WCHAR* argv[]) 942 { 943 // FIXME this stuff! 944 CON_SCREEN Screen = {StdOut}; 945 CON_PAGER Pager = {&Screen, 0}; 946 947 int i; 948 949 BOOL bRet, bContinue; 950 951 ENCODING Encoding; 952 DWORD SkipBytes = 0; 953 BOOL HasFiles; 954 955 #define FileCacheBufferSize 4096 956 PVOID FileCacheBuffer = NULL; 957 PWCHAR StringBuffer = NULL; 958 DWORD StringBufferLength = 0; 959 DWORD dwReadBytes = 0, dwReadChars = 0; 960 961 TCHAR szFullPath[MAX_PATH]; 962 963 hStdIn = GetStdHandle(STD_INPUT_HANDLE); 964 hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); 965 966 /* Initialize the Console Standard Streams */ 967 ConStreamInit(StdIn , GetStdHandle(STD_INPUT_HANDLE) , UTF8Text, INVALID_CP); 968 ConStreamInit(StdOut, GetStdHandle(STD_OUTPUT_HANDLE), UTF8Text, INVALID_CP); 969 ConStreamInit(StdErr, GetStdHandle(STD_ERROR_HANDLE) , UTF8Text, INVALID_CP); 970 971 /* 972 * Bad usage (too much options) or we use the /? switch. 973 * Display help for the MORE command. 974 */ 975 if (argc > 1 && wcscmp(argv[1], L"/?") == 0) 976 { 977 ConResPuts(StdOut, IDS_USAGE); 978 return 0; 979 } 980 981 /* Load the registry settings */ 982 LoadRegistrySettings(HKEY_LOCAL_MACHINE); 983 LoadRegistrySettings(HKEY_CURRENT_USER); 984 if (bEnableExtensions) 985 s_dwFlags |= FLAG_E; 986 987 // NOTE: We might try to duplicate the ConOut for read access... ? 988 hKeyboard = CreateFileW(L"CONIN$", GENERIC_READ|GENERIC_WRITE, 989 FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, 990 OPEN_EXISTING, 0, NULL); 991 FlushConsoleInputBuffer(hKeyboard); 992 ConStreamSetOSHandle(StdIn, hKeyboard); 993 994 FileCacheBuffer = HeapAlloc(GetProcessHeap(), 0, FileCacheBufferSize); 995 if (!FileCacheBuffer) 996 { 997 ConPuts(StdErr, L"Error: no memory\n"); 998 CloseHandle(hKeyboard); 999 return 1; 1000 } 1001 1002 /* First, load the "MORE" environment variable and parse it, 1003 * then parse the command-line parameters. */ 1004 HasFiles = FALSE; 1005 if (!ParseMoreVariable(&HasFiles)) 1006 return 1; 1007 for (i = 1; i < argc; i++) 1008 { 1009 if (!ParseArgument(argv[i], &HasFiles)) 1010 return 1; 1011 } 1012 1013 if (s_dwFlags & FLAG_HELP) 1014 { 1015 ConResPuts(StdOut, IDS_USAGE); 1016 return 0; 1017 } 1018 1019 Pager.PagerLine = MorePagerLine; 1020 Pager.dwFlags |= CON_PAGER_EXPAND_TABS | CON_PAGER_CACHE_INCOMPLETE_LINE; 1021 if (s_dwFlags & FLAG_P) 1022 Pager.dwFlags |= CON_PAGER_EXPAND_FF; 1023 Pager.nTabWidth = s_nTabWidth; 1024 1025 /* Special case where we run 'MORE' without any argument: we use STDIN */ 1026 if (!HasFiles) 1027 { 1028 /* 1029 * Assign STDIN handle to hFile so that the page prompt function will 1030 * know the data comes from STDIN, and will take different actions. 1031 */ 1032 hFile = hStdIn; 1033 1034 /* Update the statistics for PagePrompt */ 1035 dwFileSize = 0; 1036 dwSumReadBytes = dwSumReadChars = 0; 1037 1038 /* We suppose we read text from the file */ 1039 1040 /* For STDIN we always suppose we are in ANSI mode */ 1041 // SetFilePointer(hFile, 0, NULL, FILE_BEGIN); 1042 Encoding = ENCODING_ANSI; // ENCODING_UTF8; 1043 1044 /* Start paging */ 1045 bContinue = ConWritePaging(&Pager, PagePrompt, TRUE, NULL, 0); 1046 if (!bContinue) 1047 goto Quit; 1048 1049 do 1050 { 1051 bRet = FileGetString(hFile, Encoding, 1052 FileCacheBuffer, FileCacheBufferSize, 1053 &StringBuffer, &StringBufferLength, 1054 &dwReadBytes, &dwReadChars); 1055 if (!bRet || dwReadBytes == 0 || dwReadChars == 0) 1056 { 1057 /* We failed at reading the file, bail out */ 1058 break; 1059 } 1060 1061 /* Update the statistics for PagePrompt */ 1062 dwSumReadBytes += dwReadBytes; 1063 dwSumReadChars += dwReadChars; 1064 1065 bContinue = ConWritePaging(&Pager, PagePrompt, FALSE, 1066 StringBuffer, dwReadChars); 1067 /* If we Ctrl-C/Ctrl-Break, stop everything */ 1068 if (!bContinue) 1069 break; 1070 } 1071 while (bRet && dwReadBytes > 0); 1072 1073 /* Flush any cached pager buffers */ 1074 if (bContinue) 1075 bContinue = ConWritePaging(&Pager, PagePrompt, FALSE, NULL, 0); 1076 1077 goto Quit; 1078 } 1079 1080 /* We have files: read them and output them to STDOUT */ 1081 for (i = 1; i < argc; i++) 1082 { 1083 if (IsFlag(argv[i])) 1084 continue; 1085 1086 GetFullPathNameW(argv[i], ARRAYSIZE(szFullPath), szFullPath, NULL); 1087 hFile = CreateFileW(szFullPath, 1088 GENERIC_READ, 1089 FILE_SHARE_READ, 1090 NULL, 1091 OPEN_EXISTING, 1092 0, // FILE_ATTRIBUTE_NORMAL, 1093 NULL); 1094 if (hFile == INVALID_HANDLE_VALUE) 1095 { 1096 ConResPrintf(StdErr, IDS_FILE_ACCESS, szFullPath); 1097 goto Quit; 1098 } 1099 1100 /* We currently do not support files too big */ 1101 dwFileSize = GetFileSize(hFile, NULL); 1102 if (dwFileSize == INVALID_FILE_SIZE) 1103 { 1104 ConPuts(StdErr, L"ERROR: Invalid file size!\n"); 1105 CloseHandle(hFile); 1106 continue; 1107 } 1108 1109 /* We suppose we read text from the file */ 1110 1111 /* Check whether the file is UNICODE and retrieve its encoding */ 1112 SetFilePointer(hFile, 0, NULL, FILE_BEGIN); 1113 bRet = ReadFile(hFile, FileCacheBuffer, FileCacheBufferSize, &dwReadBytes, NULL); 1114 IsDataUnicode(FileCacheBuffer, dwReadBytes, &Encoding, &SkipBytes); 1115 SetFilePointer(hFile, SkipBytes, NULL, FILE_BEGIN); 1116 1117 /* Reset state for paging */ 1118 s_nNextLineNo = 0; 1119 s_bPrevLineIsBlank = FALSE; 1120 s_fPrompt = PROMPT_PERCENT; 1121 s_bDoNextFile = FALSE; 1122 1123 /* Update the statistics for PagePrompt */ 1124 dwSumReadBytes = dwSumReadChars = 0; 1125 1126 /* Start paging */ 1127 bContinue = ConWritePaging(&Pager, PagePrompt, TRUE, NULL, 0); 1128 if (!bContinue) 1129 { 1130 /* We stop displaying this file */ 1131 CloseHandle(hFile); 1132 if (s_bDoNextFile) 1133 { 1134 /* Bail out and continue with the other files */ 1135 continue; 1136 } 1137 1138 /* We Ctrl-C/Ctrl-Break, stop everything */ 1139 goto Quit; 1140 } 1141 1142 do 1143 { 1144 bRet = FileGetString(hFile, Encoding, 1145 FileCacheBuffer, FileCacheBufferSize, 1146 &StringBuffer, &StringBufferLength, 1147 &dwReadBytes, &dwReadChars); 1148 if (!bRet || dwReadBytes == 0 || dwReadChars == 0) 1149 { 1150 /* 1151 * We failed at reading the file, bail out 1152 * and continue with the other files. 1153 */ 1154 break; 1155 } 1156 1157 /* Update the statistics for PagePrompt */ 1158 dwSumReadBytes += dwReadBytes; 1159 dwSumReadChars += dwReadChars; 1160 1161 if ((Encoding == ENCODING_UTF16LE) || (Encoding == ENCODING_UTF16BE)) 1162 { 1163 bContinue = ConWritePaging(&Pager, PagePrompt, FALSE, 1164 FileCacheBuffer, dwReadChars); 1165 } 1166 else 1167 { 1168 bContinue = ConWritePaging(&Pager, PagePrompt, FALSE, 1169 StringBuffer, dwReadChars); 1170 } 1171 if (!bContinue) 1172 { 1173 /* We stop displaying this file */ 1174 break; 1175 } 1176 } 1177 while (bRet && dwReadBytes > 0); 1178 1179 /* Flush any cached pager buffers */ 1180 if (bContinue) 1181 bContinue = ConWritePaging(&Pager, PagePrompt, FALSE, NULL, 0); 1182 1183 CloseHandle(hFile); 1184 1185 /* Check whether we should stop displaying this file */ 1186 if (!bContinue) 1187 { 1188 if (s_bDoNextFile) 1189 { 1190 /* Bail out and continue with the other files */ 1191 continue; 1192 } 1193 1194 /* We Ctrl-C/Ctrl-Break, stop everything */ 1195 goto Quit; 1196 } 1197 } 1198 1199 Quit: 1200 if (StringBuffer) HeapFree(GetProcessHeap(), 0, StringBuffer); 1201 HeapFree(GetProcessHeap(), 0, FileCacheBuffer); 1202 CloseHandle(hKeyboard); 1203 return 0; 1204 } 1205 1206 /* EOF */ 1207