1 /* 2 * INTERNAL.C - command.com internal commands. 3 * 4 * 5 * History: 6 * 7 * 17/08/94 (Tim Norman) 8 * started. 9 * 10 * 08/08/95 (Matt Rains) 11 * i have cleaned up the source code. changes now bring this source into 12 * guidelines for recommended programming practice. 13 * 14 * cd() 15 * started. 16 * 17 * dir() 18 * i have added support for file attributes to the DIR() function. the 19 * routine adds "d" (directory) and "r" (read only) output. files with the 20 * system attribute have the filename converted to lowercase. files with 21 * the hidden attribute are not displayed. 22 * 23 * i have added support for directorys. now if the directory attribute is 24 * detected the file size if replaced with the string "<dir>". 25 * 26 * ver() 27 * started. 28 * 29 * md() 30 * started. 31 * 32 * rd() 33 * started. 34 * 35 * del() 36 * started. 37 * 38 * does not support wildcard selection. 39 * 40 * todo: add delete directory support. 41 * add recursive directory delete support. 42 * 43 * ren() 44 * started. 45 * 46 * does not support wildcard selection. 47 * 48 * todo: add rename directory support. 49 * 50 * a general structure has been used for the cd, rd and md commands. this 51 * will be better in the long run. it is too hard to maintain such diverse 52 * functions when you are involved in a group project like this. 53 * 54 * 12/14/95 (Tim Norman) 55 * fixed DIR so that it will stick \*.* if a directory is specified and 56 * that it will stick on .* if a file with no extension is specified or 57 * *.* if it ends in a \ 58 * 59 * 1/6/96 (Tim Norman) 60 * added an isatty call to DIR so it won't prompt for keypresses unless 61 * stdin and stdout are the console. 62 * 63 * changed parameters to be mutually consistent to make calling the 64 * functions easier 65 * 66 * rem() 67 * started. 68 * 69 * doskey() 70 * started. 71 * 72 * 01/22/96 (Oliver Mueller) 73 * error messages are now handled by perror. 74 * 75 * 02/05/96 (Tim Norman) 76 * converted all functions to accept first/rest parameters 77 * 78 * 07/26/96 (Tim Norman) 79 * changed return values to int instead of void 80 * 81 * path() started. 82 * 83 * 12/23/96 (Aaron Kaufman) 84 * rewrote dir() to mimic MS-DOS's dir 85 * 86 * 01/28/97 (Tim Norman) 87 * cleaned up Aaron's DIR code 88 * 89 * 06/13/97 (Tim Norman) 90 * moved DIR code to dir.c 91 * re-implemented Aaron's DIR code 92 * 93 * 06/14/97 (Steffan Kaiser) 94 * ctrl-break handling 95 * bug fixes 96 * 97 * 27-Jul-1998 (John P Price <linux-guru@gcfl.net>) 98 * added config.h include 99 * 100 * 03-Dec-1998 (Eric Kohl) 101 * Replaced DOS calls by Win32 calls. 102 * 103 * 08-Dec-1998 (Eric Kohl) 104 * Added help texts ("/?"). 105 * 106 * 18-Dec-1998 (Eric Kohl) 107 * Added support for quoted arguments (cd "program files"). 108 * 109 * 07-Jan-1999 (Eric Kohl) 110 * Clean up. 111 * 112 * 26-Jan-1999 (Eric Kohl) 113 * Replaced remaining CRT io functions by Win32 io functions. 114 * Unicode safe! 115 * 116 * 30-Jan-1999 (Eric Kohl) 117 * Added "cd -" feature. Changes to the previous directory. 118 * 119 * 15-Mar-1999 (Eric Kohl) 120 * Fixed bug in "cd -" feature. If the previous directory was a root 121 * directory, it was ignored. 122 * 123 * 23-Feb-2001 (Carl Nettelblad <cnettel@hem.passagen.se>) 124 * Improved chdir/cd command. 125 * 126 * 02-Apr-2004 (Magnus Olsen <magnus@greatlord.com>) 127 * Remove all hard code string so they can be 128 * translate to other langues. 129 * 130 * 19-Jul-2005 (Brandon Turner <turnerb7@msu.edu>) 131 * Rewrite the CD, it working as Windows 2000 CMD 132 * 133 * 19-Jul-2005 (Magnus Olsen <magnus@greatlord.com>) 134 * Add SetRootPath and GetRootPath 135 * 136 * 14-Jul-2007 (Pierre Schweitzer <heis_spiter@hotmail.com>) 137 * Added commands help display to help command (ex. : "help cmd") 138 */ 139 140 #include "precomp.h" 141 142 #ifdef INCLUDE_CMD_CHDIR 143 144 /* 145 * Helper function for getting the current path from drive 146 * without changing the drive. Return code: 0 = ok, 1 = fail. 147 * 'InPath' can have any size; if the two first letters are 148 * not a drive with ':' it will get the current path on 149 * the current drive exactly as GetCurrentDirectory() does. 150 */ 151 INT 152 GetRootPath( 153 IN LPCTSTR InPath, 154 OUT LPTSTR OutPath, 155 IN INT size) 156 { 157 if (InPath[0] && InPath[1] == _T(':')) 158 { 159 INT t = 0; 160 161 if ((InPath[0] >= _T('0')) && (InPath[0] <= _T('9'))) 162 { 163 t = (InPath[0] - _T('0')) + 28; 164 } 165 else if ((InPath[0] >= _T('a')) && (InPath[0] <= _T('z'))) 166 { 167 t = (InPath[0] - _T('a')) + 1; 168 } 169 else if ((InPath[0] >= _T('A')) && (InPath[0] <= _T('Z'))) 170 { 171 t = (InPath[0] - _T('A')) + 1; 172 } 173 174 return (_tgetdcwd(t, OutPath, size) == NULL); 175 } 176 177 /* Get current directory */ 178 return !GetCurrentDirectory(size, OutPath); 179 } 180 181 182 BOOL SetRootPath(TCHAR *oldpath, TCHAR *InPath) 183 { 184 DWORD dwLastError; 185 TCHAR OutPath[MAX_PATH]; 186 TCHAR OutPathTemp[MAX_PATH]; 187 188 StripQuotes(InPath); 189 190 /* Retrieve the full path name from the (possibly relative) InPath */ 191 if (GetFullPathName(InPath, ARRAYSIZE(OutPathTemp), OutPathTemp, NULL) == 0) 192 { 193 dwLastError = GetLastError(); 194 goto Fail; 195 } 196 197 if (bEnableExtensions) 198 { 199 /* 200 * Convert the full path to its correct case, and 201 * resolve any wilcard present as well in the path 202 * and retrieve the first result. 203 * Example: c:\windows\SYSTEM32 => C:\WINDOWS\System32 204 * Example: C:\WINDOWS\S* => C:\WINDOWS\System, 205 * or C:\WINDOWS\System32, depending on the user's OS. 206 */ 207 GetPathCase(OutPathTemp, OutPath); 208 } 209 else 210 { 211 _tcscpy(OutPath, OutPathTemp); 212 } 213 214 /* Use _tchdir(), since unlike SetCurrentDirectory() it updates 215 * the current-directory-on-drive environment variables. */ 216 if (_tchdir(OutPath) != 0) 217 { 218 dwLastError = GetLastError(); 219 if (dwLastError == ERROR_FILE_NOT_FOUND) 220 dwLastError = ERROR_PATH_NOT_FOUND; 221 goto Fail; 222 } 223 224 /* Keep the original drive in ordinary CD/CHDIR (without /D switch) */ 225 if (oldpath != NULL && _tcsncicmp(OutPath, oldpath, 2) != 0) 226 SetCurrentDirectory(oldpath); 227 228 return TRUE; 229 230 Fail: 231 ConErrFormatMessage(dwLastError); 232 nErrorLevel = 1; 233 return FALSE; 234 } 235 236 237 /* 238 * CD / CHDIR 239 */ 240 INT cmd_chdir(LPTSTR param) 241 { 242 BOOL bChangeDrive = FALSE; 243 LPTSTR tmp; 244 TCHAR szCurrent[MAX_PATH]; 245 246 /* Filter out special cases first */ 247 248 /* Print help */ 249 if (!_tcsncmp(param, _T("/?"), 2)) 250 { 251 ConOutResPaging(TRUE, STRING_CD_HELP); 252 return 0; 253 } 254 255 // 256 // FIXME: Use the split() tokenizer if bEnableExtensions == FALSE, 257 // so as to cut the parameter at the first separator (space, ',', ';'): 258 // - When bEnableExtensions == FALSE, doing 259 // CD system32;winsxs 260 // will go into system32, (but: CD "system32;winsxs" will fail as below), while 261 // - When bEnableExtensions == TRUE, it will fail because the "system32;winsxs" 262 // directory does not exist. 263 // 264 265 /* Remove extra quotes */ 266 StripQuotes(param); 267 268 if (bEnableExtensions) 269 { 270 /* Strip trailing whitespace */ 271 tmp = param + _tcslen(param) - 1; 272 while (tmp > param && _istspace(*tmp)) 273 --tmp; 274 *(tmp + 1) = _T('\0'); 275 } 276 277 /* Reset the error level */ 278 nErrorLevel = 0; 279 280 /* Print the current directory on a disk */ 281 if (_tcslen(param) == 2 && param[1] == _T(':')) 282 { 283 if (GetRootPath(param, szCurrent, ARRAYSIZE(szCurrent))) 284 { 285 error_invalid_drive(); 286 return 1; 287 } 288 ConOutPrintf(_T("%s\n"), szCurrent); 289 return 0; 290 } 291 292 /* Get the current directory */ 293 GetCurrentDirectory(ARRAYSIZE(szCurrent), szCurrent); 294 if (param[0] == _T('\0')) 295 { 296 ConOutPrintf(_T("%s\n"), szCurrent); 297 return 0; 298 } 299 300 /* If the input string is prefixed with the /D switch, change the drive */ 301 if (!_tcsncicmp(param, _T("/D"), 2)) 302 { 303 bChangeDrive = TRUE; 304 param += 2; 305 while (_istspace(*param)) 306 ++param; 307 } 308 309 if (!SetRootPath(bChangeDrive ? NULL : szCurrent, param)) 310 { 311 nErrorLevel = 1; 312 return 1; 313 } 314 315 return 0; 316 } 317 318 #endif 319 320 #ifdef INCLUDE_CMD_MKDIR 321 322 /* Helper function for mkdir to make directories in a path. 323 Don't use the api to decrease dependence on libs */ 324 BOOL 325 MakeFullPath(TCHAR * DirPath) 326 { 327 TCHAR path[MAX_PATH]; 328 TCHAR *p = DirPath; 329 INT_PTR n; 330 331 if (CreateDirectory(DirPath, NULL)) 332 return TRUE; 333 else if (GetLastError() != ERROR_PATH_NOT_FOUND) 334 return FALSE; 335 336 /* got ERROR_PATH_NOT_FOUND, so try building it up one component at a time */ 337 if (p[0] && p[1] == _T(':')) 338 p += 2; 339 while (*p == _T('\\')) 340 p++; /* skip drive root */ 341 do 342 { 343 p = _tcschr(p, _T('\\')); 344 n = p ? p++ - DirPath : _tcslen(DirPath); 345 _tcsncpy(path, DirPath, n); 346 path[n] = _T('\0'); 347 if ( !CreateDirectory(path, NULL) && 348 (GetLastError() != ERROR_ALREADY_EXISTS)) 349 { 350 return FALSE; 351 } 352 } while (p != NULL); 353 354 return TRUE; 355 } 356 357 /* 358 * MD / MKDIR 359 */ 360 INT cmd_mkdir (LPTSTR param) 361 { 362 LPTSTR *p; 363 INT argc, i; 364 DWORD dwLastError; 365 366 if (!_tcsncmp (param, _T("/?"), 2)) 367 { 368 ConOutResPaging(TRUE,STRING_MKDIR_HELP); 369 return 0; 370 } 371 372 p = split (param, &argc, FALSE, FALSE); 373 if (argc == 0) 374 { 375 ConErrResPuts(STRING_ERROR_REQ_PARAM_MISSING); 376 freep(p); 377 nErrorLevel = 1; 378 return 1; 379 } 380 381 nErrorLevel = 0; 382 for (i = 0; i < argc; i++) 383 { 384 if (!MakeFullPath(p[i])) 385 { 386 dwLastError = GetLastError(); 387 switch (dwLastError) 388 { 389 case ERROR_PATH_NOT_FOUND: 390 ConErrResPuts(STRING_MD_ERROR2); 391 break; 392 393 case ERROR_FILE_EXISTS: 394 case ERROR_ALREADY_EXISTS: 395 ConErrResPrintf(STRING_MD_ERROR, p[i]); 396 break; 397 398 default: 399 ErrorMessage(GetLastError(), NULL); 400 } 401 nErrorLevel = 1; 402 } 403 } 404 405 freep (p); 406 return nErrorLevel; 407 } 408 #endif 409 410 #ifdef INCLUDE_CMD_RMDIR 411 /* 412 * RD / RMDIR 413 */ 414 BOOL DeleteFolder(LPTSTR Directory) 415 { 416 LPTSTR pFileName; 417 HANDLE hFile; 418 WIN32_FIND_DATA f; 419 DWORD dwAttribs; 420 TCHAR szFullPath[MAX_PATH]; 421 422 _tcscpy(szFullPath, Directory); 423 pFileName = &szFullPath[_tcslen(szFullPath)]; 424 /* 425 * Append a path separator if we don't have one already, and if this a drive root 426 * path is not specified (paths like "C:" mean the current directory on drive C:). 427 */ 428 if (*szFullPath && *(pFileName - 1) != _T(':') && *(pFileName - 1) != _T('\\')) 429 *pFileName++ = _T('\\'); 430 _tcscpy(pFileName, _T("*")); 431 432 hFile = FindFirstFile(szFullPath, &f); 433 if (hFile != INVALID_HANDLE_VALUE) 434 { 435 do 436 { 437 /* Check Breaker */ 438 if (bCtrlBreak) 439 break; 440 441 if (!_tcscmp(f.cFileName, _T(".")) || 442 !_tcscmp(f.cFileName, _T(".."))) 443 { 444 continue; 445 } 446 447 _tcscpy(pFileName, f.cFileName); 448 449 dwAttribs = f.dwFileAttributes; 450 451 if (dwAttribs & FILE_ATTRIBUTE_DIRECTORY) 452 { 453 if (!DeleteFolder(szFullPath)) 454 { 455 /* Couldn't delete the file, print out the error */ 456 ErrorMessage(GetLastError(), szFullPath); 457 458 /* Continue deleting files/subfolders */ 459 } 460 } 461 else 462 { 463 /* Force file deletion even if it's read-only */ 464 if (dwAttribs & FILE_ATTRIBUTE_READONLY) 465 SetFileAttributes(szFullPath, dwAttribs & ~FILE_ATTRIBUTE_READONLY); 466 467 if (!DeleteFile(szFullPath)) 468 { 469 /* Couldn't delete the file, print out the error */ 470 ErrorMessage(GetLastError(), szFullPath); 471 472 /* Restore file attributes */ 473 SetFileAttributes(szFullPath, dwAttribs); 474 475 /* Continue deleting files/subfolders */ 476 } 477 } 478 479 } while (FindNextFile(hFile, &f)); 480 FindClose(hFile); 481 } 482 483 /* Ignore directory deletion if the user pressed Ctrl-C */ 484 if (bCtrlBreak) 485 return TRUE; 486 487 /* 488 * Detect whether we are trying to delete a pure root drive (e.g. "C:\\", but not "C:"); 489 * if so, just return success. Otherwise the RemoveDirectory() call below would fail 490 * and return ERROR_ACCESS_DENIED. 491 */ 492 if (GetFullPathName(Directory, ARRAYSIZE(szFullPath), szFullPath, NULL) == 3 && 493 szFullPath[1] == _T(':') && szFullPath[2] == _T('\\')) 494 { 495 return TRUE; 496 } 497 498 /* First attempt to delete the directory */ 499 if (RemoveDirectory(Directory)) 500 return TRUE; 501 502 /* 503 * It failed; if it was due to an denied access, check whether it was 504 * due to the directory being read-only. If so, remove its attribute 505 * and retry deletion. 506 */ 507 if (GetLastError() == ERROR_ACCESS_DENIED) 508 { 509 /* Force directory deletion even if it's read-only */ 510 dwAttribs = GetFileAttributes(Directory); 511 if (dwAttribs & FILE_ATTRIBUTE_READONLY) 512 { 513 SetFileAttributes(Directory, dwAttribs & ~FILE_ATTRIBUTE_READONLY); 514 return RemoveDirectory(Directory); 515 } 516 } 517 518 return FALSE; 519 } 520 521 INT cmd_rmdir(LPTSTR param) 522 { 523 INT nError = 0; 524 INT res; 525 LPTSTR *arg; 526 INT args; 527 INT dirCount; 528 INT i; 529 TCHAR ch; 530 BOOL bRecurseDir = FALSE; 531 BOOL bQuiet = FALSE; 532 533 if (!_tcsncmp(param, _T("/?"), 2)) 534 { 535 ConOutResPaging(TRUE,STRING_RMDIR_HELP); 536 return 0; 537 } 538 539 arg = split(param, &args, FALSE, FALSE); 540 dirCount = 0; 541 542 /* Check for options anywhere in command line */ 543 for (i = 0; i < args; i++) 544 { 545 if (*arg[i] == _T('/')) 546 { 547 /* Found an option, but check to make sure it has something after it */ 548 if (_tcslen(arg[i]) == 2) 549 { 550 ch = _totupper(arg[i][1]); 551 552 if (ch == _T('S')) 553 bRecurseDir = TRUE; 554 else if (ch == _T('Q')) 555 bQuiet = TRUE; 556 } 557 } 558 else 559 { 560 dirCount++; 561 } 562 } 563 564 if (dirCount == 0) 565 { 566 /* No folder to remove */ 567 error_req_param_missing(); 568 freep(arg); 569 return 1; 570 } 571 572 for (i = 0; i < args; i++) 573 { 574 if (*arg[i] == _T('/')) 575 continue; 576 577 if (bRecurseDir) 578 { 579 /* Ask the user whether to delete everything in the folder */ 580 if (!bQuiet) 581 { 582 res = FilePromptYNA(STRING_DEL_HELP2); 583 if (res == PROMPT_NO || res == PROMPT_BREAK) 584 { 585 nError = 1; 586 continue; 587 } 588 if (res == PROMPT_ALL) 589 bQuiet = TRUE; 590 } 591 592 res = DeleteFolder(arg[i]); 593 } 594 else 595 { 596 /* Without /S, do not force directory deletion even if it's read-only */ 597 res = RemoveDirectory(arg[i]); 598 } 599 600 if (!res) 601 { 602 /* Couldn't delete the folder, print out the error */ 603 nError = GetLastError(); 604 ErrorMessage(nError, NULL); 605 } 606 } 607 608 freep(arg); 609 return nError; 610 } 611 #endif 612 613 614 /* 615 * Either exits the command interpreter, or quits the current batch context. 616 */ 617 618 /* Enable this define for supporting EXIT /B even when extensions are disabled */ 619 // #define SUPPORT_EXIT_B_NO_EXTENSIONS 620 621 INT CommandExit(LPTSTR param) 622 { 623 if (!_tcsncmp(param, _T("/?"), 2)) 624 { 625 ConOutResPaging(TRUE, STRING_EXIT_HELP); 626 627 /* Just make sure we don't exit */ 628 bExit = FALSE; 629 return 0; 630 } 631 632 if (_tcsnicmp(param, _T("/B"), 2) == 0) 633 { 634 param += 2; 635 636 /* 637 * If a current batch file is running, exit it, 638 * otherwise exit this command interpreter instance. 639 */ 640 if (bc) 641 { 642 /* Windows' CMD compatibility: Use GOTO :EOF */ 643 TCHAR EofLabel[] = _T(":EOF"); 644 645 #ifdef SUPPORT_EXIT_B_NO_EXTENSIONS 646 /* 647 * Temporarily enable extensions so as to support :EOF. 648 * 649 * Our GOTO implementation ensures that, when extensions are 650 * enabled and the label is ':EOF', no immediate change of batch 651 * context (done e.g. via ExitBatch() calls) is performed. 652 * This will therefore ensure that we do not spoil the extensions 653 * state when we restore it below. 654 */ 655 BOOL bOldEnableExtensions = bEnableExtensions; 656 bEnableExtensions = TRUE; 657 #endif 658 659 cmd_goto(EofLabel); 660 661 #ifdef SUPPORT_EXIT_B_NO_EXTENSIONS 662 /* Restore the original state of the extensions */ 663 bEnableExtensions = bOldEnableExtensions; 664 #endif 665 } 666 else 667 { 668 bExit = TRUE; 669 } 670 } 671 else 672 { 673 /* Exit this command interpreter instance */ 674 bExit = TRUE; 675 } 676 677 /* Search for an optional exit code */ 678 while (_istspace(*param)) 679 ++param; 680 681 /* Set the errorlevel to the exit code */ 682 if (_istdigit(*param)) 683 { 684 nErrorLevel = _ttoi(param); 685 // if (fSingleCommand == 1) return nErrorLevel; 686 } 687 688 return (bExit ? nErrorLevel : 0); 689 } 690 691 #ifdef INCLUDE_CMD_REM 692 /* 693 * does nothing 694 */ 695 INT CommandRem (LPTSTR param) 696 { 697 if (_tcsstr(param, _T("/?")) == param) 698 { 699 ConOutResPaging(TRUE,STRING_REM_HELP); 700 } 701 702 return 0; 703 } 704 #endif /* INCLUDE_CMD_REM */ 705 706 707 INT CommandShowCommands(LPTSTR param) 708 { 709 PrintCommandList(); 710 return 0; 711 } 712 713 /* EOF */ 714