/* * PROJECT: ReactOS More Command * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) * PURPOSE: Displays text stream from STDIN or from an arbitrary number * of files to STDOUT, with screen capabilities (more than CAT, * but less than LESS ^^). * COPYRIGHT: Copyright 1999 Paolo Pantaleo * Copyright 2003 Timothy Schepens * Copyright 2016-2021 Hermes Belusca-Maito * Copyright 2021 Katayama Hirofumi MZ */ /* * MORE.C - external command. * * clone from 4nt more command * * 26 Sep 1999 - Paolo Pantaleo * started * * Oct 2003 - Timothy Schepens * use window size instead of buffer size. */ #include #include #include #include #include #include #include #include #include #include #include "resource.h" /* PagePrompt statistics for the current file */ DWORD dwFileSize; // In bytes DWORD dwSumReadBytes, dwSumReadChars; // The average number of bytes per character is equal to // dwSumReadBytes / dwSumReadChars. Note that dwSumReadChars // will never be == 0 when ConWritePaging (and possibly PagePrompt) // is called. /* Handles for file and console */ HANDLE hFile = INVALID_HANDLE_VALUE; HANDLE hStdIn, hStdOut; HANDLE hKeyboard; /* Enable/Disable extensions */ BOOL bEnableExtensions = TRUE; // FIXME: By default, it should be FALSE. /* Parser flags */ #define FLAG_HELP (1 << 0) #define FLAG_E (1 << 1) #define FLAG_C (1 << 2) #define FLAG_P (1 << 3) #define FLAG_S (1 << 4) #define FLAG_Tn (1 << 5) #define FLAG_PLUSn (1 << 6) /* Prompt flags */ #define PROMPT_PERCENT (1 << 0) #define PROMPT_LINE_AT (1 << 1) #define PROMPT_OPTIONS (1 << 2) #define PROMPT_LINES (1 << 3) static DWORD s_dwFlags = 0; static LONG s_nTabWidth = 8; static DWORD s_nNextLineNo = 0; static BOOL s_bPrevLineIsBlank = FALSE; static WORD s_fPrompt = 0; static BOOL s_bDoNextFile = FALSE; static BOOL IsBlankLine(IN PCWCH line, IN DWORD cch) { DWORD ich; WORD wType; for (ich = 0; ich < cch; ++ich) { /* * Explicitly exclude FORM-FEED from the check, * so that the pager can handle it. */ if (line[ich] == L'\f') return FALSE; /* * Otherwise do the extended blanks check. * Note that MS MORE.COM only checks for spaces (\x20) and TABs (\x09). * See http://archives.miloush.net/michkap/archive/2007/06/11/3230072.html * for more information. */ wType = 0; GetStringTypeW(CT_CTYPE1, &line[ich], 1, &wType); if (!(wType & (C1_BLANK | C1_SPACE))) return FALSE; } return TRUE; } static BOOL __stdcall MorePagerLine( IN OUT PCON_PAGER Pager, IN PCWCH line, IN DWORD cch) { if (s_dwFlags & FLAG_PLUSn) /* Skip lines */ { if (Pager->lineno < s_nNextLineNo) { s_bPrevLineIsBlank = FALSE; return TRUE; /* Handled */ } s_dwFlags &= ~FLAG_PLUSn; } if (s_dwFlags & FLAG_S) /* Shrink blank lines */ { if (IsBlankLine(line, cch)) { if (s_bPrevLineIsBlank) return TRUE; /* Handled */ /* * Display a single blank line, independently of the actual size * of the current line, by displaying just one space: this is * especially needed in order to force line wrapping when the * ENABLE_VIRTUAL_TERMINAL_PROCESSING or DISABLE_NEWLINE_AUTO_RETURN * console modes are enabled. * Then, reposition the cursor to the next line, first column. */ if (Pager->PageColumns > 0) ConStreamWrite(Pager->Screen->Stream, TEXT(" "), 1); ConStreamWrite(Pager->Screen->Stream, TEXT("\n"), 1); Pager->iLine++; Pager->iColumn = 0; s_bPrevLineIsBlank = TRUE; s_nNextLineNo = 0; return TRUE; /* Handled */ } else { s_bPrevLineIsBlank = FALSE; } } s_nNextLineNo = 0; /* Not handled, let the pager do the default action */ return FALSE; } static BOOL __stdcall PagePrompt(PCON_PAGER Pager, DWORD Done, DWORD Total) { HANDLE hInput = ConStreamGetOSHandle(StdIn); HANDLE hOutput = ConStreamGetOSHandle(Pager->Screen->Stream); CONSOLE_SCREEN_BUFFER_INFO csbi; COORD orgCursorPosition; DWORD dwMode; KEY_EVENT_RECORD KeyEvent; BOOL fCtrl; DWORD nLines; WCHAR chSubCommand = 0; /* Prompt strings (small size since the prompt should * hold ideally on one <= 80-character line) */ static WCHAR StrPercent[80] = L""; static WCHAR StrLineAt[80] = L""; static WCHAR StrOptions[80] = L""; static WCHAR StrLines[80] = L""; WCHAR szPercent[80] = L""; WCHAR szLineAt[80] = L""; /* Load the prompt strings */ if (!*StrPercent) K32LoadStringW(NULL, IDS_CONTINUE_PERCENT, StrPercent, ARRAYSIZE(StrPercent)); if (!*StrLineAt) K32LoadStringW(NULL, IDS_CONTINUE_LINE_AT, StrLineAt, ARRAYSIZE(StrLineAt)); if (!*StrOptions) K32LoadStringW(NULL, IDS_CONTINUE_OPTIONS, StrOptions, ARRAYSIZE(StrOptions)); if (!*StrLines) K32LoadStringW(NULL, IDS_CONTINUE_LINES, StrLines, ARRAYSIZE(StrLines)); /* * Check whether the pager is prompting, but we have actually finished * to display a given file, or no data is present in STDIN anymore. * In this case, skip the prompt altogether. The only exception is when * we are displaying other files. */ // TODO: Implement! Restart: nLines = 0; /* Do not show the progress percentage when STDIN is being displayed */ if (s_fPrompt & PROMPT_PERCENT) // && (hFile != hStdIn) { /* * The progress percentage is evaluated as follows. * So far we have read a total of 'dwSumReadBytes' bytes from the file. * Amongst those is the latest read chunk of 'dwReadBytes' bytes, to which * correspond a number of 'dwReadChars' characters with which we have called * ConWritePaging who called PagePrompt. We then have: Total == dwReadChars. * During this ConWritePaging call the PagePrompt was called after 'Done' * number of characters over 'Total'. * It should be noted that for 'dwSumReadBytes' number of bytes read it * *roughly* corresponds 'dwSumReadChars' number of characters. This is * because there may be some failures happening during the conversion of * the bytes read to the character string for a given encoding. * Therefore the number of characters displayed on screen is equal to: * dwSumReadChars - Total + Done , * but the best corresponding approximed number of bytes would be: * dwSumReadBytes - (Total - Done) * (dwSumReadBytes / dwSumReadChars) , * where the ratio is the average number of bytes per character. * The percentage is then computed relative to the total file size. */ DWORD dwPercent = (dwSumReadBytes - (Total - Done) * (dwSumReadBytes / dwSumReadChars)) * 100 / dwFileSize; StringCchPrintfW(szPercent, ARRAYSIZE(szPercent), StrPercent, dwPercent); } if (s_fPrompt & PROMPT_LINE_AT) { StringCchPrintfW(szLineAt, ARRAYSIZE(szLineAt), StrLineAt, Pager->lineno); } /* Suitably format and display the prompt */ ConResMsgPrintf(Pager->Screen->Stream, 0, IDS_CONTINUE_PROMPT, (s_fPrompt & PROMPT_PERCENT ? szPercent : L""), (s_fPrompt & PROMPT_LINE_AT ? szLineAt : L""), (s_fPrompt & PROMPT_OPTIONS ? StrOptions : L""), (s_fPrompt & PROMPT_LINES ? StrLines : L"")); /* Reset the prompt to a default state */ s_fPrompt &= ~(PROMPT_LINE_AT | PROMPT_OPTIONS | PROMPT_LINES); /* RemoveBreakHandler */ SetConsoleCtrlHandler(NULL, TRUE); /* ConInDisable */ GetConsoleMode(hInput, &dwMode); dwMode &= ~ENABLE_PROCESSED_INPUT; SetConsoleMode(hInput, dwMode); // FIXME: Does not support TTY yet! ConGetScreenInfo(Pager->Screen, &csbi); orgCursorPosition = csbi.dwCursorPosition; for (;;) { INPUT_RECORD ir = {0}; DWORD dwRead; WCHAR ch; do { ReadConsoleInput(hInput, &ir, 1, &dwRead); } while ((ir.EventType != KEY_EVENT) || (!ir.Event.KeyEvent.bKeyDown)); /* Got our key */ KeyEvent = ir.Event.KeyEvent; /* Ignore any unsupported keyboard press */ if ((KeyEvent.wVirtualKeyCode == VK_SHIFT) || (KeyEvent.wVirtualKeyCode == VK_MENU) || (KeyEvent.wVirtualKeyCode == VK_CONTROL)) { continue; } /* Ctrl key is pressed? */ fCtrl = !!(KeyEvent.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)); /* Ctrl+C or Ctrl+Esc? */ if (fCtrl && ((KeyEvent.wVirtualKeyCode == VK_ESCAPE) || (KeyEvent.wVirtualKeyCode == L'C'))) { chSubCommand = 0; break; } /* If extended features are unavailable, or no * pending commands, don't do more processing. */ if (!(s_dwFlags & FLAG_E) || (chSubCommand == 0)) break; ch = KeyEvent.uChar.UnicodeChar; if (L'0' <= ch && ch <= L'9') { nLines *= 10; nLines += ch - L'0'; ConStreamWrite(Pager->Screen->Stream, &ch, 1); continue; } else if (KeyEvent.wVirtualKeyCode == VK_RETURN) { /* Validate the line number */ break; } else if (KeyEvent.wVirtualKeyCode == VK_ESCAPE) { /* Cancel the current command */ chSubCommand = 0; break; } else if (KeyEvent.wVirtualKeyCode == VK_BACK) { if (nLines != 0) nLines /= 10; /* Erase the current character */ ConGetScreenInfo(Pager->Screen, &csbi); if ( (csbi.dwCursorPosition.Y > orgCursorPosition.Y) || ((csbi.dwCursorPosition.Y == orgCursorPosition.Y) && (csbi.dwCursorPosition.X > orgCursorPosition.X)) ) { if (csbi.dwCursorPosition.X > 0) { csbi.dwCursorPosition.X = csbi.dwCursorPosition.X - 1; } else if (csbi.dwCursorPosition.Y > 0) { csbi.dwCursorPosition.Y = csbi.dwCursorPosition.Y - 1; csbi.dwCursorPosition.X = (csbi.dwSize.X ? csbi.dwSize.X - 1 : 0); } SetConsoleCursorPosition(hOutput, csbi.dwCursorPosition); ch = L' '; ConStreamWrite(Pager->Screen->Stream, &ch, 1); SetConsoleCursorPosition(hOutput, csbi.dwCursorPosition); } continue; } } /* AddBreakHandler */ SetConsoleCtrlHandler(NULL, FALSE); /* ConInEnable */ GetConsoleMode(hInput, &dwMode); dwMode |= ENABLE_PROCESSED_INPUT; SetConsoleMode(hInput, dwMode); /* Refresh the screen information, as the console may have been * redimensioned. Update also the default number of lines to scroll. */ ConGetScreenInfo(Pager->Screen, &csbi); Pager->ScrollRows = csbi.srWindow.Bottom - csbi.srWindow.Top; /* * Erase the full line where the cursor is, and move * the cursor back to the beginning of the line. */ ConClearLine(Pager->Screen->Stream); /* Ctrl+C or Ctrl+Esc: Control Break */ if (fCtrl && ((KeyEvent.wVirtualKeyCode == VK_ESCAPE) || (KeyEvent.wVirtualKeyCode == L'C'))) { /* We break, output a newline */ WCHAR ch = L'\n'; ConStreamWrite(Pager->Screen->Stream, &ch, 1); return FALSE; } switch (chSubCommand) { case L'P': { /* If we don't display other lines, just restart the prompt */ if (nLines == 0) { chSubCommand = 0; goto Restart; } /* Otherwise tell the pager to display them */ Pager->ScrollRows = nLines; return TRUE; } case L'S': { s_dwFlags |= FLAG_PLUSn; s_nNextLineNo = Pager->lineno + nLines; /* Use the default Pager->ScrollRows value */ return TRUE; } default: chSubCommand = 0; break; } /* If extended features are available */ if (s_dwFlags & FLAG_E) { /* Ignore any key presses if Ctrl is pressed */ if (fCtrl) { chSubCommand = 0; goto Restart; } /* 'Q': Quit */ if (KeyEvent.wVirtualKeyCode == L'Q') { /* We break, output a newline */ WCHAR ch = L'\n'; ConStreamWrite(Pager->Screen->Stream, &ch, 1); return FALSE; } /* 'F': Next file */ if (KeyEvent.wVirtualKeyCode == L'F') { s_bDoNextFile = TRUE; return FALSE; } /* '?': Show Options */ if (KeyEvent.uChar.UnicodeChar == L'?') { s_fPrompt |= PROMPT_OPTIONS; goto Restart; } /* [Enter] key: Display one line */ if (KeyEvent.wVirtualKeyCode == VK_RETURN) { Pager->ScrollRows = 1; return TRUE; } /* [Space] key: Display one page */ if (KeyEvent.wVirtualKeyCode == VK_SPACE) { if (s_dwFlags & FLAG_C) { /* Clear the screen */ ConClearScreen(Pager->Screen); } /* Use the default Pager->ScrollRows value */ return TRUE; } /* 'P': Display n lines */ if (KeyEvent.wVirtualKeyCode == L'P') { s_fPrompt |= PROMPT_LINES; chSubCommand = L'P'; goto Restart; } /* 'S': Skip n lines */ if (KeyEvent.wVirtualKeyCode == L'S') { s_fPrompt |= PROMPT_LINES; chSubCommand = L'S'; goto Restart; } /* '=': Show current line number */ if (KeyEvent.uChar.UnicodeChar == L'=') { s_fPrompt |= PROMPT_LINE_AT; goto Restart; } chSubCommand = 0; goto Restart; } else { /* Extended features are unavailable: display one page */ /* Use the default Pager->ScrollRows value */ return TRUE; } } /* * See base/applications/cmdutils/clip/clip.c!IsDataUnicode() * and base/applications/notepad/text.c!ReadText() for more details. * Also some good code example can be found at: * https://github.com/AutoIt/text-encoding-detect */ typedef enum { ENCODING_ANSI = 0, ENCODING_UTF16LE = 1, ENCODING_UTF16BE = 2, ENCODING_UTF8 = 3 } ENCODING; static BOOL IsDataUnicode( IN PVOID Buffer, IN DWORD BufferSize, OUT ENCODING* Encoding OPTIONAL, OUT PDWORD SkipBytes OPTIONAL) { PBYTE pBytes = Buffer; ENCODING encFile = ENCODING_ANSI; DWORD dwPos = 0; /* * See http://archives.miloush.net/michkap/archive/2007/04/22/2239345.html * for more details about the algorithm and the pitfalls behind it. * Of course it would be actually great to make a nice function that * would work, once and for all, and put it into a library. */ /* Look for Byte Order Marks */ if ((BufferSize >= 2) && (pBytes[0] == 0xFF) && (pBytes[1] == 0xFE)) { encFile = ENCODING_UTF16LE; dwPos = 2; } else if ((BufferSize >= 2) && (pBytes[0] == 0xFE) && (pBytes[1] == 0xFF)) { encFile = ENCODING_UTF16BE; dwPos = 2; } else if ((BufferSize >= 3) && (pBytes[0] == 0xEF) && (pBytes[1] == 0xBB) && (pBytes[2] == 0xBF)) { encFile = ENCODING_UTF8; dwPos = 3; } else { /* * Try using statistical analysis. Do not rely on the return value of * IsTextUnicode as we can get FALSE even if the text is in UTF-16 BE * (i.e. we have some of the IS_TEXT_UNICODE_REVERSE_MASK bits set). * Instead, set all the tests we want to perform, then just check * the passed tests and try to deduce the string properties. */ /* * This mask contains the 3 highest bits from IS_TEXT_UNICODE_NOT_ASCII_MASK * and the 1st highest bit from IS_TEXT_UNICODE_NOT_UNICODE_MASK. */ #define IS_TEXT_UNKNOWN_FLAGS_MASK ((7 << 13) | (1 << 11)) /* Flag out the unknown flags here, the passed tests will not have them either */ INT Tests = (IS_TEXT_UNICODE_NOT_ASCII_MASK | IS_TEXT_UNICODE_NOT_UNICODE_MASK | IS_TEXT_UNICODE_REVERSE_MASK | IS_TEXT_UNICODE_UNICODE_MASK) & ~IS_TEXT_UNKNOWN_FLAGS_MASK; INT Results; IsTextUnicode(Buffer, BufferSize, &Tests); Results = Tests; /* * As the IS_TEXT_UNICODE_NULL_BYTES or IS_TEXT_UNICODE_ILLEGAL_CHARS * flags are expected to be potentially present in the result without * modifying our expectations, filter them out now. */ Results &= ~(IS_TEXT_UNICODE_NULL_BYTES | IS_TEXT_UNICODE_ILLEGAL_CHARS); /* * NOTE: The flags IS_TEXT_UNICODE_ASCII16 and * IS_TEXT_UNICODE_REVERSE_ASCII16 are not reliable. * * NOTE2: Check for potential "bush hid the facts" effect by also * checking the original results (in 'Tests') for the absence of * the IS_TEXT_UNICODE_NULL_BYTES flag, as we may presumably expect * that in UTF-16 text there will be at some point some NULL bytes. * If not, fall back to ANSI. This shows the limitations of using the * IsTextUnicode API to perform such tests, and the usage of a more * improved encoding detection algorithm would be really welcome. */ if (!(Results & IS_TEXT_UNICODE_NOT_UNICODE_MASK) && !(Results & IS_TEXT_UNICODE_REVERSE_MASK) && (Results & IS_TEXT_UNICODE_UNICODE_MASK) && (Tests & IS_TEXT_UNICODE_NULL_BYTES)) { encFile = ENCODING_UTF16LE; dwPos = (Results & IS_TEXT_UNICODE_SIGNATURE) ? 2 : 0; } else if (!(Results & IS_TEXT_UNICODE_NOT_UNICODE_MASK) && !(Results & IS_TEXT_UNICODE_UNICODE_MASK) && (Results & IS_TEXT_UNICODE_REVERSE_MASK) && (Tests & IS_TEXT_UNICODE_NULL_BYTES)) { encFile = ENCODING_UTF16BE; dwPos = (Results & IS_TEXT_UNICODE_REVERSE_SIGNATURE) ? 2 : 0; } else { /* * Either 'Results' has neither of those masks set, as it can be * the case for UTF-8 text (or ANSI), or it has both as can be the * case when analysing pure binary data chunk. This is therefore * invalid and we fall back to ANSI encoding. * FIXME: In case of failure, assume ANSI (as long as we do not have * correct tests for UTF8, otherwise we should do them, and at the * very end, assume ANSI). */ encFile = ENCODING_ANSI; // ENCODING_UTF8; dwPos = 0; } } if (Encoding) *Encoding = encFile; if (SkipBytes) *SkipBytes = dwPos; return (encFile != ENCODING_ANSI); } /* * Adapted from base/shell/cmd/misc.c!FileGetString(), but with correct * text encoding support. Also please note that similar code should be * also used in the CMD.EXE 'TYPE' command. * Contrary to CMD's FileGetString() we do not stop at new-lines. * * Read text data from a file and convert it from a given encoding to UTF-16. * * IN OUT PVOID pCacheBuffer and IN DWORD CacheBufferLength : * Implementation detail so that the function uses an external user-provided * buffer to store the data temporarily read from the file. The function * could have used an internal buffer instead. The length is in number of bytes. * * IN OUT PWSTR* pBuffer and IN OUT PDWORD pnBufferLength : * Reallocated buffer containing the string data converted to UTF-16. * In input, contains a pointer to the original buffer and its length. * In output, contains a pointer to the reallocated buffer and its length. * The length is in number of characters. * * At first call to this function, pBuffer can be set to NULL, in which case * when the function returns the pointer will point to a valid buffer. * After the last call to this function, free the pBuffer pointer with: * HeapFree(GetProcessHeap(), 0, *pBuffer); * * If Encoding is set to ENCODING_UTF16LE or ENCODING_UTF16BE, since we are * compiled in UNICODE, no extra conversion is performed and therefore * pBuffer is unused (remains unallocated) and one can directly use the * contents of pCacheBuffer as it is expected to contain valid UTF-16 text. * * OUT PDWORD pdwReadBytes : Number of bytes read from the file (optional). * OUT PDWORD pdwReadChars : Corresponding number of characters read (optional). */ static BOOL FileGetString( IN HANDLE hFile, IN ENCODING Encoding, IN OUT PVOID pCacheBuffer, IN DWORD CacheBufferLength, IN OUT PWCHAR* pBuffer, IN OUT PDWORD pnBufferLength, OUT PDWORD pdwReadBytes OPTIONAL, OUT PDWORD pdwReadChars OPTIONAL) { BOOL Success; UINT CodePage = (UINT)-1; DWORD dwReadBytes; INT len; // ASSERT(pCacheBuffer && (CacheBufferLength > 0)); // ASSERT(CacheBufferLength % 2 == 0); // Cache buffer length MUST BE even! // ASSERT(pBuffer && pnBufferLength); /* Always reset the retrieved number of bytes/characters */ if (pdwReadBytes) *pdwReadBytes = 0; if (pdwReadChars) *pdwReadChars = 0; Success = ReadFile(hFile, pCacheBuffer, CacheBufferLength, &dwReadBytes, NULL); if (!Success || dwReadBytes == 0) return FALSE; if (pdwReadBytes) *pdwReadBytes = dwReadBytes; if ((Encoding == ENCODING_ANSI) || (Encoding == ENCODING_UTF8)) { /* Conversion is needed */ if (Encoding == ENCODING_ANSI) CodePage = GetConsoleCP(); // CP_ACP; // FIXME: Cache GetConsoleCP() value. else // if (Encoding == ENCODING_UTF8) CodePage = CP_UTF8; /* Retrieve the needed buffer size */ len = MultiByteToWideChar(CodePage, 0, pCacheBuffer, dwReadBytes, NULL, 0); if (len == 0) { /* Failure, bail out */ return FALSE; } /* Initialize the conversion buffer if needed... */ if (*pBuffer == NULL) { *pnBufferLength = len; *pBuffer = HeapAlloc(GetProcessHeap(), 0, *pnBufferLength * sizeof(WCHAR)); if (*pBuffer == NULL) { // *pBuffer = NULL; *pnBufferLength = 0; // WARN("DEBUG: Cannot allocate memory for *pBuffer!\n"); // ConErrFormatMessage(GetLastError()); return FALSE; } } /* ... or reallocate only if the new length is greater than the old one */ else if (len > *pnBufferLength) { PWSTR OldBuffer = *pBuffer; *pnBufferLength = len; *pBuffer = HeapReAlloc(GetProcessHeap(), 0, *pBuffer, *pnBufferLength * sizeof(WCHAR)); if (*pBuffer == NULL) { /* Do not leak old buffer */ HeapFree(GetProcessHeap(), 0, OldBuffer); // *pBuffer = NULL; *pnBufferLength = 0; // WARN("DEBUG: Cannot reallocate memory for *pBuffer!\n"); // ConErrFormatMessage(GetLastError()); return FALSE; } } /* Now perform the conversion proper */ len = MultiByteToWideChar(CodePage, 0, pCacheBuffer, dwReadBytes, *pBuffer, len); dwReadBytes = len; } else { /* * No conversion needed, just convert from big to little endian if needed. * pBuffer and pnBufferLength are left untouched and pCacheBuffer can be * directly used. */ PWCHAR pWChars = pCacheBuffer; DWORD i; dwReadBytes /= sizeof(WCHAR); if (Encoding == ENCODING_UTF16BE) { for (i = 0; i < dwReadBytes; i++) { /* Equivalent to RtlUshortByteSwap: reverse high/low bytes */ pWChars[i] = MAKEWORD(HIBYTE(pWChars[i]), LOBYTE(pWChars[i])); } } // else if (Encoding == ENCODING_UTF16LE), we are good, nothing to do. } /* Return the number of characters (dwReadBytes is converted) */ if (pdwReadChars) *pdwReadChars = dwReadBytes; return TRUE; } static VOID LoadRegistrySettings(HKEY hKeyRoot) { LONG lRet; HKEY hKey; DWORD dwType, len; /* * Buffer big enough to hold the string L"4294967295", * corresponding to the literal 0xFFFFFFFF (MAXULONG) in decimal. */ WCHAR Buffer[sizeof("4294967295")]; C_ASSERT(sizeof(Buffer) >= sizeof(DWORD)); lRet = RegOpenKeyExW(hKeyRoot, L"Software\\Microsoft\\Command Processor", 0, KEY_QUERY_VALUE, &hKey); if (lRet != ERROR_SUCCESS) return; len = sizeof(Buffer); lRet = RegQueryValueExW(hKey, L"EnableExtensions", NULL, &dwType, (PBYTE)&Buffer, &len); if (lRet == ERROR_SUCCESS) { /* Overwrite the default setting */ if (dwType == REG_DWORD) bEnableExtensions = !!*(PDWORD)Buffer; else if (dwType == REG_SZ) bEnableExtensions = (_wtol((PWSTR)Buffer) == 1); } // else, use the default setting set globally. RegCloseKey(hKey); } static BOOL IsFlag(PCWSTR param) { PCWSTR pch; PWCHAR endptr; if (param[0] == L'/') return TRUE; if (param[0] == L'+') { pch = param + 1; if (*pch) { (void)wcstol(pch, &endptr, 10); return (*endptr == 0); } } return FALSE; } static BOOL ParseArgument(PCWSTR arg, BOOL* pbHasFiles) { PWCHAR endptr; if (arg[0] == L'/') { switch (towupper(arg[1])) { case L'?': if (arg[2] == 0) { s_dwFlags |= FLAG_HELP; return TRUE; } break; case L'E': if (arg[2] == 0) { s_dwFlags |= FLAG_E; return TRUE; } break; case L'C': if (arg[2] == 0) { s_dwFlags |= FLAG_C; return TRUE; } break; case L'P': if (arg[2] == 0) { s_dwFlags |= FLAG_P; return TRUE; } break; case L'S': if (arg[2] == 0) { s_dwFlags |= FLAG_S; return TRUE; } break; case L'T': if (arg[2] != 0) { s_dwFlags |= FLAG_Tn; s_nTabWidth = wcstol(&arg[2], &endptr, 10); if (*endptr == 0) return TRUE; } break; default: break; } } else if (arg[0] == L'+') { if (arg[1] != 0) { s_dwFlags |= FLAG_PLUSn; s_nNextLineNo = wcstol(&arg[1], &endptr, 10) + 1; if (*endptr == 0) return TRUE; } } if (IsFlag(arg)) { ConResPrintf(StdErr, IDS_BAD_FLAG, arg); return FALSE; } else { *pbHasFiles = TRUE; } return TRUE; } static BOOL ParseMoreVariable(BOOL* pbHasFiles) { BOOL ret = TRUE; PWSTR psz; PWCHAR pch; DWORD cch; cch = GetEnvironmentVariableW(L"MORE", NULL, 0); if (cch == 0) return TRUE; psz = (PWSTR)malloc((cch + 1) * sizeof(WCHAR)); if (!psz) return TRUE; if (!GetEnvironmentVariableW(L"MORE", psz, cch + 1)) { free(psz); return TRUE; } for (pch = wcstok(psz, L" "); pch; pch = wcstok(NULL, L" ")) { ret = ParseArgument(pch, pbHasFiles); if (!ret) break; } free(psz); return ret; } // INT CommandMore(LPTSTR cmd, LPTSTR param) int wmain(int argc, WCHAR* argv[]) { // FIXME this stuff! CON_SCREEN Screen = {StdOut}; CON_PAGER Pager = {&Screen, 0}; int i; BOOL bRet, bContinue; ENCODING Encoding; DWORD SkipBytes = 0; BOOL HasFiles; #define FileCacheBufferSize 4096 PVOID FileCacheBuffer = NULL; PWCHAR StringBuffer = NULL; DWORD StringBufferLength = 0; DWORD dwReadBytes = 0, dwReadChars = 0; TCHAR szFullPath[MAX_PATH]; hStdIn = GetStdHandle(STD_INPUT_HANDLE); hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); /* Initialize the Console Standard Streams */ ConStreamInit(StdIn , GetStdHandle(STD_INPUT_HANDLE) , UTF8Text, INVALID_CP); ConStreamInit(StdOut, GetStdHandle(STD_OUTPUT_HANDLE), UTF8Text, INVALID_CP); ConStreamInit(StdErr, GetStdHandle(STD_ERROR_HANDLE) , UTF8Text, INVALID_CP); /* * Bad usage (too much options) or we use the /? switch. * Display help for the MORE command. */ if (argc > 1 && wcscmp(argv[1], L"/?") == 0) { ConResPuts(StdOut, IDS_USAGE); return 0; } /* Load the registry settings */ LoadRegistrySettings(HKEY_LOCAL_MACHINE); LoadRegistrySettings(HKEY_CURRENT_USER); if (bEnableExtensions) s_dwFlags |= FLAG_E; // NOTE: We might try to duplicate the ConOut for read access... ? hKeyboard = CreateFileW(L"CONIN$", GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); FlushConsoleInputBuffer(hKeyboard); ConStreamSetOSHandle(StdIn, hKeyboard); FileCacheBuffer = HeapAlloc(GetProcessHeap(), 0, FileCacheBufferSize); if (!FileCacheBuffer) { ConPuts(StdErr, L"Error: no memory\n"); CloseHandle(hKeyboard); return 1; } /* First, load the "MORE" environment variable and parse it, * then parse the command-line parameters. */ HasFiles = FALSE; if (!ParseMoreVariable(&HasFiles)) return 1; for (i = 1; i < argc; i++) { if (!ParseArgument(argv[i], &HasFiles)) return 1; } if (s_dwFlags & FLAG_HELP) { ConResPuts(StdOut, IDS_USAGE); return 0; } Pager.PagerLine = MorePagerLine; Pager.dwFlags |= CON_PAGER_EXPAND_TABS | CON_PAGER_CACHE_INCOMPLETE_LINE; if (s_dwFlags & FLAG_P) Pager.dwFlags |= CON_PAGER_EXPAND_FF; Pager.nTabWidth = s_nTabWidth; /* Special case where we run 'MORE' without any argument: we use STDIN */ if (!HasFiles) { /* * Assign STDIN handle to hFile so that the page prompt function will * know the data comes from STDIN, and will take different actions. */ hFile = hStdIn; /* Update the statistics for PagePrompt */ dwFileSize = 0; dwSumReadBytes = dwSumReadChars = 0; /* We suppose we read text from the file */ /* For STDIN we always suppose we are in ANSI mode */ // SetFilePointer(hFile, 0, NULL, FILE_BEGIN); Encoding = ENCODING_ANSI; // ENCODING_UTF8; /* Start paging */ bContinue = ConWritePaging(&Pager, PagePrompt, TRUE, NULL, 0); if (!bContinue) goto Quit; do { bRet = FileGetString(hFile, Encoding, FileCacheBuffer, FileCacheBufferSize, &StringBuffer, &StringBufferLength, &dwReadBytes, &dwReadChars); if (!bRet || dwReadBytes == 0 || dwReadChars == 0) { /* We failed at reading the file, bail out */ break; } /* Update the statistics for PagePrompt */ dwSumReadBytes += dwReadBytes; dwSumReadChars += dwReadChars; bContinue = ConWritePaging(&Pager, PagePrompt, FALSE, StringBuffer, dwReadChars); /* If we Ctrl-C/Ctrl-Break, stop everything */ if (!bContinue) break; } while (bRet && dwReadBytes > 0); /* Flush any cached pager buffers */ if (bContinue) bContinue = ConWritePaging(&Pager, PagePrompt, FALSE, NULL, 0); goto Quit; } /* We have files: read them and output them to STDOUT */ for (i = 1; i < argc; i++) { if (IsFlag(argv[i])) continue; GetFullPathNameW(argv[i], ARRAYSIZE(szFullPath), szFullPath, NULL); hFile = CreateFileW(szFullPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, // FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { ConResPrintf(StdErr, IDS_FILE_ACCESS, szFullPath); goto Quit; } /* We currently do not support files too big */ dwFileSize = GetFileSize(hFile, NULL); if (dwFileSize == INVALID_FILE_SIZE) { ConPuts(StdErr, L"ERROR: Invalid file size!\n"); CloseHandle(hFile); continue; } /* We suppose we read text from the file */ /* Check whether the file is UNICODE and retrieve its encoding */ SetFilePointer(hFile, 0, NULL, FILE_BEGIN); bRet = ReadFile(hFile, FileCacheBuffer, FileCacheBufferSize, &dwReadBytes, NULL); IsDataUnicode(FileCacheBuffer, dwReadBytes, &Encoding, &SkipBytes); SetFilePointer(hFile, SkipBytes, NULL, FILE_BEGIN); /* Reset state for paging */ s_nNextLineNo = 0; s_bPrevLineIsBlank = FALSE; s_fPrompt = PROMPT_PERCENT; s_bDoNextFile = FALSE; /* Update the statistics for PagePrompt */ dwSumReadBytes = dwSumReadChars = 0; /* Start paging */ bContinue = ConWritePaging(&Pager, PagePrompt, TRUE, NULL, 0); if (!bContinue) { /* We stop displaying this file */ CloseHandle(hFile); if (s_bDoNextFile) { /* Bail out and continue with the other files */ continue; } /* We Ctrl-C/Ctrl-Break, stop everything */ goto Quit; } do { bRet = FileGetString(hFile, Encoding, FileCacheBuffer, FileCacheBufferSize, &StringBuffer, &StringBufferLength, &dwReadBytes, &dwReadChars); if (!bRet || dwReadBytes == 0 || dwReadChars == 0) { /* * We failed at reading the file, bail out * and continue with the other files. */ break; } /* Update the statistics for PagePrompt */ dwSumReadBytes += dwReadBytes; dwSumReadChars += dwReadChars; if ((Encoding == ENCODING_UTF16LE) || (Encoding == ENCODING_UTF16BE)) { bContinue = ConWritePaging(&Pager, PagePrompt, FALSE, FileCacheBuffer, dwReadChars); } else { bContinue = ConWritePaging(&Pager, PagePrompt, FALSE, StringBuffer, dwReadChars); } if (!bContinue) { /* We stop displaying this file */ break; } } while (bRet && dwReadBytes > 0); /* Flush any cached pager buffers */ if (bContinue) bContinue = ConWritePaging(&Pager, PagePrompt, FALSE, NULL, 0); CloseHandle(hFile); /* Check whether we should stop displaying this file */ if (!bContinue) { if (s_bDoNextFile) { /* Bail out and continue with the other files */ continue; } /* We Ctrl-C/Ctrl-Break, stop everything */ goto Quit; } } Quit: if (StringBuffer) HeapFree(GetProcessHeap(), 0, StringBuffer); HeapFree(GetProcessHeap(), 0, FileCacheBuffer); CloseHandle(hKeyboard); return 0; } /* EOF */