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