1 /* 2 * PROJECT: ReactOS DosKey Command 3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) 4 * PURPOSE: Provides history and command aliases management for 5 * command-line programs. 6 * COPYRIGHT: Copyright 2008 Christoph von Wittich 7 * Copyright 2013-2017 Hermès Bélusca-Maïto 8 */ 9 10 #include <stdio.h> 11 #include <wchar.h> 12 #include <locale.h> 13 14 #include <windef.h> 15 #include <winbase.h> 16 #include <winuser.h> 17 #include <wincon.h> 18 19 /* Console API functions which are absent from wincon.h */ 20 #define EXENAME_LENGTH (255 + 1) 21 22 VOID 23 WINAPI 24 ExpungeConsoleCommandHistoryW(LPCWSTR lpExeName); 25 26 DWORD 27 WINAPI 28 GetConsoleCommandHistoryW(LPWSTR lpHistory, 29 DWORD cbHistory, 30 LPCWSTR lpExeName); 31 32 DWORD 33 WINAPI 34 GetConsoleCommandHistoryLengthW(LPCWSTR lpExeName); 35 36 BOOL 37 WINAPI 38 SetConsoleNumberOfCommandsW(DWORD dwNumCommands, 39 LPCWSTR lpExeName); 40 41 #include "doskey.h" 42 43 #define MAX_STRING 2000 44 WCHAR szStringBuf[MAX_STRING]; 45 LPWSTR pszExeName = L"cmd.exe"; 46 47 static VOID SetInsert(DWORD dwFlag) 48 { 49 /* 50 * NOTE: Enabling the ENABLE_INSERT_MODE mode can also be done by calling 51 * kernel32:SetConsoleCommandHistoryMode(CONSOLE_OVERSTRIKE) (deprecated). 52 */ 53 DWORD dwMode; 54 HANDLE hConsole = GetStdHandle(STD_INPUT_HANDLE); 55 GetConsoleMode(hConsole, &dwMode); 56 dwMode |= ENABLE_EXTENDED_FLAGS; 57 SetConsoleMode(hConsole, (dwMode & ~ENABLE_INSERT_MODE) | dwFlag); 58 } 59 60 static VOID PrintHistory(VOID) 61 { 62 DWORD Length = GetConsoleCommandHistoryLengthW(pszExeName); 63 PBYTE HistBuf; 64 WCHAR *Hist; 65 WCHAR *HistEnd; 66 67 HistBuf = HeapAlloc(GetProcessHeap(), 68 HEAP_ZERO_MEMORY, 69 Length); 70 if (!HistBuf) return; 71 Hist = (WCHAR *)HistBuf; 72 HistEnd = (WCHAR *)&HistBuf[Length]; 73 74 if (GetConsoleCommandHistoryW(Hist, Length, pszExeName)) 75 { 76 for (; Hist < HistEnd; Hist += wcslen(Hist) + 1) 77 { 78 wprintf(L"%s\n", Hist); 79 } 80 } 81 82 HeapFree(GetProcessHeap(), 0, HistBuf); 83 } 84 85 static INT SetMacro(LPWSTR definition) 86 { 87 WCHAR *name, *nameend, *text, temp; 88 89 name = definition; 90 while (*name == L' ') 91 name++; 92 93 /* error if no '=' found */ 94 if ((nameend = wcschr(name, L'=')) != NULL) 95 { 96 text = nameend + 1; 97 while (*text == L' ') 98 text++; 99 100 while (nameend > name && nameend[-1] == L' ') 101 nameend--; 102 103 /* Split rest into name and substitute */ 104 temp = *nameend; 105 *nameend = L'\0'; 106 /* Don't allow spaces in the name, since such a macro would be unusable */ 107 if (!wcschr(name, L' ') && AddConsoleAliasW(name, text, pszExeName)) 108 return 0; 109 *nameend = temp; 110 } 111 112 LoadStringW(GetModuleHandle(NULL), 113 IDS_INVALID_MACRO_DEF, 114 szStringBuf, 115 ARRAYSIZE(szStringBuf)); 116 wprintf(szStringBuf, definition); 117 return 1; 118 } 119 120 static VOID PrintMacros(LPWSTR pszExeName, LPWSTR Indent) 121 { 122 DWORD Length = GetConsoleAliasesLengthW(pszExeName); 123 PBYTE AliasBuf; 124 WCHAR *Alias; 125 WCHAR *AliasEnd; 126 127 AliasBuf = HeapAlloc(GetProcessHeap(), 128 HEAP_ZERO_MEMORY, 129 Length * sizeof(BYTE)); 130 if (!AliasBuf) return; 131 Alias = (WCHAR *)AliasBuf; 132 AliasEnd = (WCHAR *)&AliasBuf[Length]; 133 134 if (GetConsoleAliasesW(Alias, Length * sizeof(BYTE), pszExeName)) 135 { 136 for (; Alias < AliasEnd; Alias += wcslen(Alias) + 1) 137 { 138 wprintf(L"%s%s\n", Indent, Alias); 139 } 140 } 141 142 HeapFree(GetProcessHeap(), 0, AliasBuf); 143 } 144 145 static VOID PrintAllMacros(VOID) 146 { 147 DWORD Length = GetConsoleAliasExesLength(); 148 PBYTE ExeNameBuf; 149 WCHAR *ExeName; 150 WCHAR *ExeNameEnd; 151 152 ExeNameBuf = HeapAlloc(GetProcessHeap(), 153 HEAP_ZERO_MEMORY, 154 Length * sizeof(BYTE)); 155 if (!ExeNameBuf) return; 156 ExeName = (WCHAR *)ExeNameBuf; 157 ExeNameEnd = (WCHAR *)&ExeNameBuf[Length]; 158 159 if (GetConsoleAliasExesW(ExeName, Length * sizeof(BYTE))) 160 { 161 for (; ExeName < ExeNameEnd; ExeName += wcslen(ExeName) + 1) 162 { 163 wprintf(L"[%s]\n", ExeName); 164 PrintMacros(ExeName, L" "); 165 wprintf(L"\n"); 166 } 167 } 168 169 HeapFree(GetProcessHeap(), 0, ExeNameBuf); 170 } 171 172 /* Remove starting and ending quotes from a string, if present */ 173 static LPWSTR RemoveQuotes(LPWSTR str) 174 { 175 WCHAR *end; 176 if (*str == L'"' && *(end = str + wcslen(str) - 1) == L'"') 177 { 178 str++; 179 *end = L'\0'; 180 } 181 return str; 182 } 183 184 static VOID ReadFromFile(LPWSTR FileName) 185 { 186 FILE* fp; 187 WCHAR line[MAX_PATH]; 188 WCHAR ExeNameBuffer[EXENAME_LENGTH]; 189 LPWSTR pszOrgExeName = pszExeName; 190 191 /* Open the file */ 192 fp = _wfopen(FileName, L"rt"); 193 if (!fp) 194 { 195 _wperror(FileName); 196 return; 197 } 198 199 while (fgetws(line, ARRAYSIZE(line), fp) != NULL) 200 { 201 PWCHAR end; 202 203 if (!*line) 204 continue; 205 206 /* Remove trailing newline character */ 207 end = &line[wcslen(line) - 1]; 208 if (*end == L'\n') 209 *end = L'\0'; 210 211 if (!*line) 212 continue; 213 214 /* Check for any section redefining the current executable name */ 215 end = NULL; 216 if (*line == L'[') 217 end = wcschr(line, L']'); 218 219 if (end != NULL) 220 { 221 /* New section: change the current executable name */ 222 223 *end = L'\0'; // NULL-terminate it 224 pszExeName = RemoveQuotes(line + 1); 225 if (*pszExeName) 226 { 227 /* Capture the new executable name and truncate it if needed */ 228 end = &pszExeName[wcslen(pszExeName)]; 229 if (end - pszExeName >= EXENAME_LENGTH) 230 end = &pszExeName[EXENAME_LENGTH - 1]; 231 *end = L'\0'; // Truncate it 232 wcscpy(ExeNameBuffer, pszExeName); 233 pszExeName = ExeNameBuffer; 234 } 235 else 236 { 237 /* Restore the original current executable name */ 238 pszExeName = pszOrgExeName; 239 } 240 } 241 else 242 { 243 /* Set the new macro for the current executable */ 244 SetMacro(line); 245 } 246 } 247 248 /* Restore the original current executable name if it has changed */ 249 pszExeName = pszOrgExeName; 250 251 /* Close the file and return */ 252 fclose(fp); 253 return; 254 } 255 256 /* Get the start and end of the next command-line argument. */ 257 static BOOL GetArg(WCHAR **pStart, WCHAR **pEnd) 258 { 259 BOOL bInQuotes = FALSE; 260 WCHAR *p = *pEnd; 261 p += wcsspn(p, L" \t"); 262 if (!*p) 263 return FALSE; 264 *pStart = p; 265 do 266 { 267 if (!bInQuotes && (*p == L' ' || *p == L'\t')) 268 break; 269 bInQuotes ^= (*p++ == L'"'); 270 } while (*p); 271 *pEnd = p; 272 return TRUE; 273 } 274 275 int 276 wmain(VOID) 277 { 278 LPWSTR pArgStart, pArgEnd; 279 280 setlocale(LC_ALL, ""); 281 282 /* Get the full command line using GetCommandLine(). We can't just use argv, 283 * because then a parameter like "gotoroot=cd \" wouldn't be passed completely. */ 284 pArgEnd = GetCommandLineW(); 285 286 /* Skip the application name */ 287 GetArg(&pArgStart, &pArgEnd); 288 289 while (GetArg(&pArgStart, &pArgEnd)) 290 { 291 /* NULL-terminate this argument to make processing easier */ 292 WCHAR tmp = *pArgEnd; 293 *pArgEnd = L'\0'; 294 295 if (!wcscmp(pArgStart, L"/?")) 296 { 297 LoadStringW(GetModuleHandle(NULL), 298 IDS_HELP, 299 szStringBuf, 300 ARRAYSIZE(szStringBuf)); 301 wprintf(szStringBuf); 302 break; 303 } 304 else if (!_wcsnicmp(pArgStart, L"/EXENAME=", 9)) 305 { 306 pszExeName = RemoveQuotes(pArgStart + 9); 307 } 308 else if (!wcsicmp(pArgStart, L"/H") || 309 !wcsicmp(pArgStart, L"/HISTORY")) 310 { 311 PrintHistory(); 312 } 313 else if (!_wcsnicmp(pArgStart, L"/LISTSIZE=", 10)) 314 { 315 SetConsoleNumberOfCommandsW(_wtoi(pArgStart + 10), pszExeName); 316 } 317 else if (!wcsicmp(pArgStart, L"/REINSTALL")) 318 { 319 ExpungeConsoleCommandHistoryW(pszExeName); 320 } 321 else if (!wcsicmp(pArgStart, L"/INSERT")) 322 { 323 SetInsert(ENABLE_INSERT_MODE); 324 } 325 else if (!wcsicmp(pArgStart, L"/OVERSTRIKE")) 326 { 327 SetInsert(0); 328 } 329 else if (!wcsicmp(pArgStart, L"/M") || 330 !wcsicmp(pArgStart, L"/MACROS")) 331 { 332 PrintMacros(pszExeName, L""); 333 } 334 else if (!_wcsnicmp(pArgStart, L"/M:", 3) || 335 !_wcsnicmp(pArgStart, L"/MACROS:", 8)) 336 { 337 LPWSTR exe = RemoveQuotes(wcschr(pArgStart, L':') + 1); 338 if (!wcsicmp(exe, L"ALL")) 339 PrintAllMacros(); 340 else 341 PrintMacros(exe, L""); 342 } 343 else if (!_wcsnicmp(pArgStart, L"/MACROFILE=", 11)) 344 { 345 ReadFromFile(RemoveQuotes(pArgStart + 11)); 346 } 347 else 348 { 349 /* This is the beginning of a macro definition. It includes 350 * the entire remainder of the line, so first put back the 351 * character that we replaced with NULL. */ 352 *pArgEnd = tmp; 353 return SetMacro(pArgStart); 354 } 355 356 if (!tmp) break; 357 pArgEnd++; 358 } 359 360 return 0; 361 } 362