xref: /reactos/base/shell/cmd/internal.c (revision 82bcb3f9)
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 /* helper functions for getting current path from drive
145    without changing drive. Return code 0 = ok, 1 = fail.
146    INT GetRootPath("C:",outbuffer,chater size of outbuffer);
147    the first param can have any size, if the the two frist
148    letter are not a drive with : it will get Currentpath on
149    current drive exactly as GetCurrentDirectory does.
150    */
151 
152 INT GetRootPath(TCHAR *InPath,TCHAR *OutPath,INT size)
153 {
154     if (InPath[0] && InPath[1] == _T(':'))
155     {
156         INT t=0;
157 
158         if ((InPath[0] >= _T('0')) && (InPath[0] <= _T('9')))
159         {
160             t = (InPath[0] - _T('0')) +28;
161         }
162 
163         if ((InPath[0] >= _T('a')) && (InPath[0] <= _T('z')))
164         {
165             t = (InPath[0] - _T('a')) +1;
166             InPath[0] = t + _T('A') - 1;
167         }
168 
169         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     TCHAR OutPath[MAX_PATH];
185     TCHAR OutPathTemp[MAX_PATH];
186 
187     StripQuotes(InPath);
188 
189     /* Retrieve the full path name from the (possibly relative) InPath */
190     if (GetFullPathName(InPath, MAX_PATH, OutPathTemp, NULL) == 0)
191         goto Fail;
192 
193     /* Convert the full path to its correct case.
194      * Example: c:\windows\SYSTEM32 => C:\WINDOWS\System32 */
195     GetPathCase(OutPathTemp, OutPath);
196 
197     /* Use _tchdir, since unlike SetCurrentDirectory it updates
198      * the current-directory-on-drive environment variables. */
199     if (_tchdir(OutPath) != 0)
200         goto Fail;
201 
202     /* Keep original drive in ordinary CD/CHDIR (without /D switch). */
203     if (oldpath != NULL && _tcsncicmp(OutPath, oldpath, 2) != 0)
204         SetCurrentDirectory(oldpath);
205 
206     return TRUE;
207 
208 Fail:
209     ConErrFormatMessage(GetLastError());
210     nErrorLevel = 1;
211     return FALSE;
212 }
213 
214 
215 /*
216  * CD / CHDIR
217  *
218  */
219 INT cmd_chdir (LPTSTR param)
220 {
221     BOOL bChangeDrive = FALSE;
222     LPTSTR tmp;
223     TCHAR szCurrent[MAX_PATH];
224 
225     /* Filter out special cases first */
226 
227     /* Print Help */
228     if (!_tcsncmp(param, _T("/?"), 2))
229     {
230         ConOutResPaging(TRUE,STRING_CD_HELP);
231         return 0;
232     }
233 
234     /* Remove extra quotes and strip trailing whitespace */
235     StripQuotes(param);
236     tmp = param + _tcslen(param) - 1;
237     while (tmp > param && _istspace(*tmp))
238         tmp--;
239     *(tmp + 1) = _T('\0');
240 
241     /* Set Error Level to Success */
242     nErrorLevel = 0;
243 
244     /* Print Current Directory on a disk */
245     if (_tcslen(param) == 2 && param[1] == _T(':'))
246     {
247         if (GetRootPath(param, szCurrent, MAX_PATH))
248         {
249             error_invalid_drive();
250             return 1;
251         }
252         ConOutPrintf(_T("%s\n"), szCurrent);
253         return 0;
254     }
255 
256     /* Get Current Directory */
257     GetCurrentDirectory(MAX_PATH, szCurrent);
258     if (param[0] == _T('\0'))
259     {
260         ConOutPrintf(_T("%s\n"), szCurrent);
261         return 0;
262     }
263 
264     /* Input String Contains /D Switch */
265     if (!_tcsncicmp(param, _T("/D"), 2))
266     {
267         bChangeDrive = TRUE;
268         param += 2;
269         while (_istspace(*param))
270             param++;
271     }
272 
273     if (!SetRootPath(bChangeDrive ? NULL : szCurrent, param))
274     {
275         nErrorLevel = 1;
276         return 1;
277     }
278 
279     return 0;
280 }
281 
282 #endif
283 
284 #ifdef INCLUDE_CMD_MKDIR
285 
286 /* Helper function for mkdir to make directories in a path.
287 Don't use the api to decrease dependence on libs */
288 BOOL
289 MakeFullPath(TCHAR * DirPath)
290 {
291     TCHAR path[MAX_PATH];
292     TCHAR *p = DirPath;
293     INT_PTR  n;
294 
295     if (CreateDirectory(DirPath, NULL))
296         return TRUE;
297     else if (GetLastError() != ERROR_PATH_NOT_FOUND)
298         return FALSE;
299 
300     /* got ERROR_PATH_NOT_FOUND, so try building it up one component at a time */
301     if (p[0] && p[1] == _T(':'))
302         p += 2;
303     while (*p == _T('\\'))
304         p++; /* skip drive root */
305     do
306     {
307         p = _tcschr(p, _T('\\'));
308         n = p ? p++ - DirPath : _tcslen(DirPath);
309         _tcsncpy(path, DirPath, n);
310         path[n] = _T('\0');
311         if ( !CreateDirectory(path, NULL) &&
312             (GetLastError() != ERROR_ALREADY_EXISTS))
313         {
314             return FALSE;
315         }
316     } while (p != NULL);
317 
318     return TRUE;
319 }
320 
321 /*
322  * MD / MKDIR
323  */
324 INT cmd_mkdir (LPTSTR param)
325 {
326     LPTSTR *p;
327     INT argc, i;
328     if (!_tcsncmp (param, _T("/?"), 2))
329     {
330         ConOutResPaging(TRUE,STRING_MKDIR_HELP);
331         return 0;
332     }
333 
334     p = split (param, &argc, FALSE, FALSE);
335     if (argc == 0)
336     {
337         ConErrResPuts(STRING_ERROR_REQ_PARAM_MISSING);
338         freep(p);
339         nErrorLevel = 1;
340         return 1;
341     }
342 
343     nErrorLevel = 0;
344     for (i = 0; i < argc; i++)
345     {
346         if (!MakeFullPath(p[i]))
347         {
348             if (GetLastError() == ERROR_PATH_NOT_FOUND)
349             {
350                 ConErrResPuts(STRING_MD_ERROR2);
351             }
352             else
353             {
354                 ErrorMessage (GetLastError(), _T("MD"));
355             }
356             nErrorLevel = 1;
357         }
358     }
359 
360     freep (p);
361     return nErrorLevel;
362 }
363 #endif
364 
365 
366 #ifdef INCLUDE_CMD_RMDIR
367 /*
368  * RD / RMDIR
369  */
370 BOOL DeleteFolder(LPTSTR FileName)
371 {
372     TCHAR Base[MAX_PATH];
373     TCHAR TempFileName[MAX_PATH];
374     HANDLE hFile;
375     WIN32_FIND_DATA f;
376     _tcscpy(Base,FileName);
377     _tcscat(Base,_T("\\*"));
378     hFile = FindFirstFile(Base, &f);
379     Base[_tcslen(Base) - 1] = _T('\0');
380     if (hFile != INVALID_HANDLE_VALUE)
381     {
382         do
383         {
384             if (!_tcscmp(f.cFileName, _T(".")) ||
385                 !_tcscmp(f.cFileName, _T("..")))
386                 continue;
387             _tcscpy(TempFileName,Base);
388             _tcscat(TempFileName,f.cFileName);
389 
390             if (f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
391                 DeleteFolder(TempFileName);
392             else
393             {
394                 SetFileAttributes(TempFileName,FILE_ATTRIBUTE_NORMAL);
395                 if (!DeleteFile(TempFileName))
396                 {
397                     FindClose (hFile);
398                     return 0;
399                 }
400             }
401 
402         }while (FindNextFile (hFile, &f));
403         FindClose (hFile);
404     }
405     return RemoveDirectory(FileName);
406 }
407 
408 INT cmd_rmdir (LPTSTR param)
409 {
410     TCHAR ch;
411     INT args;
412     INT dirCount;
413     LPTSTR *arg;
414     INT i;
415     BOOL RD_SUB = FALSE;
416     BOOL RD_QUIET = FALSE;
417     INT res;
418     INT nError = 0;
419     TCHAR szFullPath[MAX_PATH];
420 
421     if (!_tcsncmp (param, _T("/?"), 2))
422     {
423         ConOutResPaging(TRUE,STRING_RMDIR_HELP);
424         return 0;
425     }
426 
427     arg = split (param, &args, FALSE, FALSE);
428     dirCount = 0;
429 
430     /* check for options anywhere in command line */
431     for (i = 0; i < args; i++)
432     {
433         if (*arg[i] == _T('/'))
434         {
435             /*found a command, but check to make sure it has something after it*/
436             if (_tcslen (arg[i]) == 2)
437             {
438                 ch = _totupper (arg[i][1]);
439 
440                 if (ch == _T('S'))
441                 {
442                     RD_SUB = TRUE;
443                 }
444                 else if (ch == _T('Q'))
445                 {
446                     RD_QUIET = TRUE;
447                 }
448             }
449         }
450         else
451         {
452             dirCount++;
453         }
454     }
455 
456     if (dirCount == 0)
457     {
458         /* No folder to remove */
459         error_req_param_missing();
460         freep(arg);
461         return 1;
462     }
463 
464     for (i = 0; i < args; i++)
465     {
466         if (*arg[i] == _T('/'))
467             continue;
468 
469         if (RD_SUB)
470         {
471             /* ask if they want to delete everything in the folder */
472             if (!RD_QUIET)
473             {
474                 res = FilePromptYNA (STRING_DEL_HELP2);
475                 if (res == PROMPT_NO || res == PROMPT_BREAK)
476                 {
477                     nError = 1;
478                     continue;
479                 }
480                 if (res == PROMPT_ALL)
481                     RD_QUIET = TRUE;
482             }
483             /* get the folder name */
484             GetFullPathName(arg[i],MAX_PATH,szFullPath,NULL);
485 
486             /* remove trailing \ if any, but ONLY if dir is not the root dir */
487             if (_tcslen (szFullPath) >= 2 && szFullPath[_tcslen (szFullPath) - 1] == _T('\\'))
488                 szFullPath[_tcslen(szFullPath) - 1] = _T('\0');
489 
490             res = DeleteFolder(szFullPath);
491         }
492         else
493         {
494             res = RemoveDirectory(arg[i]);
495         }
496 
497         if (!res)
498         {
499             /* Couldn't delete the folder, print out the error */
500             nError = GetLastError();
501             ErrorMessage(nError, _T("RD"));
502         }
503     }
504 
505     freep (arg);
506     return nError;
507 }
508 #endif
509 
510 
511 /*
512  * Either exits the command interpreter, or quits the current batch context.
513  */
514 
515 /* Enable this define for supporting EXIT /B even when extensions are disabled */
516 // #define SUPPORT_EXIT_B_NO_EXTENSIONS
517 
518 INT CommandExit(LPTSTR param)
519 {
520     if (!_tcsncmp(param, _T("/?"), 2))
521     {
522         ConOutResPaging(TRUE, STRING_EXIT_HELP);
523 
524         /* Just make sure we don't exit */
525         bExit = FALSE;
526         return 0;
527     }
528 
529     if (_tcsnicmp(param, _T("/B"), 2) == 0)
530     {
531         param += 2;
532 
533         /*
534          * If a current batch file is running, exit it,
535          * otherwise exit this command interpreter instance.
536          */
537         if (bc)
538         {
539             /* Windows' CMD compatibility: Use GOTO :EOF */
540             TCHAR EofLabel[] = _T(":EOF");
541 
542 #ifdef SUPPORT_EXIT_B_NO_EXTENSIONS
543             /*
544              * Temporarily enable extensions so as to support :EOF.
545              *
546              * Our GOTO implementation ensures that, when extensions are
547              * enabled and the label is ':EOF', no immediate change of batch
548              * context (done e.g. via ExitBatch() calls) is performed.
549              * This will therefore ensure that we do not spoil the extensions
550              * state when we restore it below.
551              */
552             BOOL bOldEnableExtensions = bEnableExtensions;
553             bEnableExtensions = TRUE;
554 #endif
555 
556             cmd_goto(EofLabel);
557 
558 #ifdef SUPPORT_EXIT_B_NO_EXTENSIONS
559             /* Restore the original state of the extensions */
560             bEnableExtensions = bOldEnableExtensions;
561 #endif
562         }
563         else
564         {
565             bExit = TRUE;
566         }
567     }
568     else
569     {
570         /* Exit this command interpreter instance */
571         bExit = TRUE;
572     }
573 
574     /* Search for an optional exit code */
575     while (_istspace(*param))
576         ++param;
577 
578     /* Set the errorlevel to the exit code */
579     if (_istdigit(*param))
580     {
581         nErrorLevel = _ttoi(param);
582         // if (fSingleCommand == 1) return nErrorLevel;
583     }
584 
585     return (bExit ? nErrorLevel : 0);
586 }
587 
588 #ifdef INCLUDE_CMD_REM
589 /*
590  * does nothing
591  */
592 INT CommandRem (LPTSTR param)
593 {
594     if (!_tcsncmp (param, _T("/?"), 2))
595     {
596         ConOutResPaging(TRUE,STRING_REM_HELP);
597     }
598 
599     return 0;
600 }
601 #endif /* INCLUDE_CMD_REM */
602 
603 
604 INT CommandShowCommands (LPTSTR param)
605 {
606     PrintCommandList();
607     return 0;
608 }
609 
610 /* EOF */
611