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