27f3dcca85f5944aae330fcc5cda94f72d599971
[reactos.git] / reactos / subsys / system / cmd / cmd.c
1 /*
2 * CMD.C - command-line interface.
3 *
4 *
5 * History:
6 *
7 * 17 Jun 1994 (Tim Norman)
8 * started.
9 *
10 * 08 Aug 1995 (Matt Rains)
11 * I have cleaned up the source code. changes now bring this source
12 * into guidelines for recommended programming practice.
13 *
14 * A added the the standard FreeDOS GNU licence test to the
15 * initialize() function.
16 *
17 * Started to replace puts() with printf(). this will help
18 * standardize output. please follow my lead.
19 *
20 * I have added some constants to help making changes easier.
21 *
22 * 15 Dec 1995 (Tim Norman)
23 * major rewrite of the code to make it more efficient and add
24 * redirection support (finally!)
25 *
26 * 06 Jan 1996 (Tim Norman)
27 * finished adding redirection support! Changed to use our own
28 * exec code (MUCH thanks to Svante Frey!!)
29 *
30 * 29 Jan 1996 (Tim Norman)
31 * added support for CHDIR, RMDIR, MKDIR, and ERASE, as per
32 * suggestion of Steffan Kaiser
33 *
34 * changed "file not found" error message to "bad command or
35 * filename" thanks to Dustin Norman for noticing that confusing
36 * message!
37 *
38 * changed the format to call internal commands (again) so that if
39 * they want to split their commands, they can do it themselves
40 * (none of the internal functions so far need that much power, anyway)
41 *
42 * 27 Aug 1996 (Tim Norman)
43 * added in support for Oliver Mueller's ALIAS command
44 *
45 * 14 Jun 1997 (Steffan Kaiser)
46 * added ctrl-break handling and error level
47 *
48 * 16 Jun 1998 (Rob Lake)
49 * Runs command.com if /P is specified in command line. Command.com
50 * also stays permanent. If /C is in the command line, starts the
51 * program next in the line.
52 *
53 * 21 Jun 1998 (Rob Lake)
54 * Fixed up /C so that arguments for the program
55 *
56 * 08-Jul-1998 (John P. Price)
57 * Now sets COMSPEC environment variable
58 * misc clean up and optimization
59 * added date and time commands
60 * changed to using spawnl instead of exec. exec does not copy the
61 * environment to the child process!
62 *
63 * 14 Jul 1998 (Hans B Pufal)
64 * Reorganised source to be more efficient and to more closely
65 * follow MS-DOS conventions. (eg %..% environment variable
66 * replacement works form command line as well as batch file.
67 *
68 * New organisation also properly support nested batch files.
69 *
70 * New command table structure is half way towards providing a
71 * system in which COMMAND will find out what internal commands
72 * are loaded
73 *
74 * 24 Jul 1998 (Hans B Pufal) [HBP_003]
75 * Fixed return value when called with /C option
76 *
77 * 27 Jul 1998 John P. Price
78 * added config.h include
79 *
80 * 28 Jul 1998 John P. Price
81 * added showcmds function to show commands and options available
82 *
83 * 07-Aug-1998 (John P Price <linux-guru@gcfl.net>)
84 * Fixed carrage return output to better match MSDOS with echo
85 * on or off. (marked with "JPP 19980708")
86 *
87 * 07-Dec-1998 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
88 * First ReactOS release.
89 * Extended length of commandline buffers to 512.
90 *
91 * 13-Dec-1998 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
92 * Added COMSPEC environment variable.
93 * Added "/t" support (color) on cmd command line.
94 *
95 * 07-Jan-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
96 * Added help text ("cmd /?").
97 *
98 * 25-Jan-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
99 * Unicode and redirection safe!
100 * Fixed redirections and piping.
101 * Piping is based on temporary files, but basic support
102 * for anonymous pipes already exists.
103 *
104 * 27-Jan-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
105 * Replaced spawnl() by CreateProcess().
106 *
107 * 22-Oct-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
108 * Added break handler.
109 *
110 * 15-Dec-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
111 * Fixed current directory
112 *
113 * 28-Dec-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
114 * Restore window title after program/batch execution
115 *
116 * 03-Feb-2001 (Eric Kohl <ekohl@rz-online.de>)
117 * Workaround because argc[0] is NULL under ReactOS
118 *
119 * 23-Feb-2001 (Carl Nettelblad <cnettel@hem.passagen.se>)
120 * %envvar% replacement conflicted with for.
121 *
122 * 30-Apr-2004 (Filip Navara <xnavara@volny.cz>)
123 * Make MakeSureDirectoryPathExistsEx unicode safe.
124 *
125 * 28-Mai-2004 (Hartmut Birr)
126 * Removed MakeSureDirectoryPathExistsEx.
127 * Use the current directory if GetTempPath fails.
128 *
129 * 12-Jul-2004 (Jens Collin <jens.collin@lakhei.com>)
130 * Added ShellExecute call when all else fails to be able to "launch" any file.
131 *
132 * 02-Apr-2005 (Magnus Olsen) <magnus@greatlord.com>)
133 * Remove all hardcode string to En.rc
134 */
135
136 #include "precomp.h"
137 #include "resource.h"
138
139 #ifndef NT_SUCCESS
140 #define NT_SUCCESS(StatCode) ((NTSTATUS)(StatCode) >= 0)
141 #endif
142
143 typedef NTSTATUS (STDCALL *NtQueryInformationProcessProc)(HANDLE, PROCESSINFOCLASS,
144 PVOID, ULONG, PULONG);
145 typedef NTSTATUS (STDCALL *NtReadVirtualMemoryProc)(HANDLE, PVOID, PVOID, ULONG, PULONG);
146
147 BOOL bExit = FALSE; /* indicates EXIT was typed */
148 BOOL bCanExit = TRUE; /* indicates if this shell is exitable */
149 BOOL bCtrlBreak = FALSE; /* Ctrl-Break or Ctrl-C hit */
150 BOOL bIgnoreEcho = FALSE; /* Ignore 'newline' before 'cls' */
151 INT nErrorLevel = 0; /* Errorlevel of last launched external program */
152 BOOL bChildProcessRunning = FALSE;
153 DWORD dwChildProcessId = 0;
154 OSVERSIONINFO osvi;
155 HANDLE hIn;
156 HANDLE hOut;
157 HANDLE hConsole;
158 HANDLE CMD_ModuleHandle;
159
160 static NtQueryInformationProcessProc NtQueryInformationProcessPtr;
161 static NtReadVirtualMemoryProc NtReadVirtualMemoryPtr;
162 static BOOL NtDllChecked = FALSE;
163
164 #ifdef INCLUDE_CMD_COLOR
165 WORD wColor; /* current color */
166 WORD wDefColor; /* default color */
167 #endif
168
169 /*
170 * is character a delimeter when used on first word?
171 *
172 */
173
174 static BOOL IsDelimiter (TCHAR c)
175 {
176 return (c == _T('/') || c == _T('=') || c == _T('\0') || _istspace (c));
177 }
178
179 /*
180 * Is a process a console process?
181 */
182 static BOOL IsConsoleProcess(HANDLE Process)
183 {
184 NTSTATUS Status;
185 PROCESS_BASIC_INFORMATION Info;
186 PEB ProcessPeb;
187 ULONG BytesRead;
188 HMODULE NtDllModule;
189
190 /* Some people like to run ReactOS cmd.exe on Win98, it helps in the
191 build process. So don't link implicitly against ntdll.dll, load it
192 dynamically instead */
193 if (! NtDllChecked)
194 {
195 NtDllChecked = TRUE;
196 NtDllModule = LoadLibrary(_T("ntdll.dll"));
197 if (NULL == NtDllModule)
198 {
199 /* Probably non-WinNT system. Just wait for the commands
200 to finish. */
201 NtQueryInformationProcessPtr = NULL;
202 NtReadVirtualMemoryPtr = NULL;
203 return TRUE;
204 }
205 NtQueryInformationProcessPtr = (NtQueryInformationProcessProc)
206 GetProcAddress(NtDllModule, "NtQueryInformationProcess");
207 NtReadVirtualMemoryPtr = (NtReadVirtualMemoryProc)
208 GetProcAddress(NtDllModule, "NtReadVirtualMemory");
209 }
210
211 if (NULL == NtQueryInformationProcessPtr || NULL == NtReadVirtualMemoryPtr)
212 {
213 return TRUE;
214 }
215
216 Status = NtQueryInformationProcessPtr(Process, ProcessBasicInformation,
217 &Info, sizeof(PROCESS_BASIC_INFORMATION), NULL);
218 if (! NT_SUCCESS(Status))
219 {
220 #ifdef _DEBUG
221 DebugPrintf (_T("NtQueryInformationProcess failed with status %08x\n"), Status);
222 #endif
223 return TRUE;
224 }
225 Status = NtReadVirtualMemoryPtr(Process, Info.PebBaseAddress, &ProcessPeb,
226 sizeof(PEB), &BytesRead);
227 if (! NT_SUCCESS(Status) || sizeof(PEB) != BytesRead)
228 {
229 #ifdef _DEBUG
230 DebugPrintf (_T("Couldn't read virt mem status %08x bytes read %lu\n"), Status, BytesRead);
231 #endif
232 return TRUE;
233 }
234
235 return IMAGE_SUBSYSTEM_WINDOWS_CUI == ProcessPeb.ImageSubSystem;
236 }
237
238
239
240 #ifdef _UNICODE
241 #define SHELLEXECUTETEXT "ShellExecuteW"
242 #else
243 #define SHELLEXECUTETEXT "ShellExecuteA"
244 #endif
245
246 typedef HINSTANCE (WINAPI *MYEX)(
247 HWND hwnd,
248 LPCTSTR lpOperation,
249 LPCTSTR lpFile,
250 LPCTSTR lpParameters,
251 LPCTSTR lpDirectory,
252 INT nShowCmd
253 );
254
255
256
257 static BOOL RunFile(LPTSTR filename)
258 {
259 HMODULE hShell32;
260 MYEX hShExt;
261 HINSTANCE ret;
262
263 #ifdef _DEBUG
264 DebugPrintf (_T("RunFile(%s)\n"), filename);
265 #endif
266 hShell32 = LoadLibrary(_T("SHELL32.DLL"));
267 if (!hShell32)
268 {
269 #ifdef _DEBUG
270 DebugPrintf (_T("RunFile: couldn't load SHELL32.DLL!\n"));
271 #endif
272 return FALSE;
273 }
274
275 hShExt = (MYEX)(FARPROC)GetProcAddress(hShell32, SHELLEXECUTETEXT);
276 if (!hShExt)
277 {
278 #ifdef _DEBUG
279 DebugPrintf (_T("RunFile: couldn't find ShellExecuteA/W in SHELL32.DLL!\n"));
280 #endif
281 FreeLibrary(hShell32);
282 return FALSE;
283 }
284
285 #ifdef _DEBUG
286 DebugPrintf (_T("RunFile: ShellExecuteA/W is at %x\n"), hShExt);
287 #endif
288
289 ret = (hShExt)(NULL, _T("open"), filename, NULL, NULL, SW_SHOWNORMAL);
290
291 #ifdef _DEBUG
292 DebugPrintf (_T("RunFile: ShellExecuteA/W returned %d\n"), (DWORD)ret);
293 #endif
294
295 FreeLibrary(hShell32);
296 return (((DWORD)ret) > 32);
297 }
298
299
300
301 /*
302 * This command (in first) was not found in the command table
303 *
304 * first - first word on command line
305 * rest - rest of command line
306 */
307
308 static VOID
309 Execute (LPTSTR full, LPTSTR first, LPTSTR rest)
310 {
311 TCHAR szFullName[MAX_PATH];
312 #ifndef __REACTOS__
313 TCHAR szWindowTitle[MAX_PATH];
314 #endif
315 DWORD dwExitCode = 0;
316
317 #ifdef _DEBUG
318 DebugPrintf (_T("Execute: \'%s\' \'%s\'\n"), first, rest);
319 #endif
320
321 /* check for a drive change */
322 if ((_istalpha (first[0])) && (!_tcscmp (first + 1, _T(":"))))
323 {
324 BOOL working = TRUE;
325 if (!SetCurrentDirectory(first))
326 /* Guess they changed disc or something, handle that gracefully and get to root */
327 {
328 TCHAR str[4];
329 str[0]=first[0];
330 str[1]=_T(':');
331 str[2]=_T('\\');
332 str[3]=0;
333 working = SetCurrentDirectory(str);
334 }
335
336 if (!working) ConErrResPuts (STRING_FREE_ERROR1);
337
338 return;
339 }
340
341 /* get the PATH environment variable and parse it */
342 /* search the PATH environment variable for the binary */
343 if (!SearchForExecutable (first, szFullName))
344 {
345 error_bad_command ();
346 return;
347 }
348
349 #ifndef __REACTOS__
350 GetConsoleTitle (szWindowTitle, MAX_PATH);
351 #endif
352
353 /* check if this is a .BAT or .CMD file */
354 if (!_tcsicmp (_tcsrchr (szFullName, _T('.')), _T(".bat")) ||
355 !_tcsicmp (_tcsrchr (szFullName, _T('.')), _T(".cmd")))
356 {
357 #ifdef _DEBUG
358 DebugPrintf (_T("[BATCH: %s %s]\n"), szFullName, rest);
359 #endif
360 Batch (szFullName, first, rest);
361 }
362 else
363 {
364 /* exec the program */
365 PROCESS_INFORMATION prci;
366 STARTUPINFO stui;
367
368 #ifdef _DEBUG
369 DebugPrintf (_T("[EXEC: %s %s]\n"), full, rest);
370 #endif
371 /* build command line for CreateProcess() */
372
373 /* fill startup info */
374 memset (&stui, 0, sizeof (STARTUPINFO));
375 stui.cb = sizeof (STARTUPINFO);
376 stui.dwFlags = STARTF_USESHOWWINDOW;
377 stui.wShowWindow = SW_SHOWDEFAULT;
378
379 // return console to standard mode
380 SetConsoleMode (GetStdHandle(STD_INPUT_HANDLE),
381 ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT );
382
383 if (CreateProcess (szFullName,
384 full,
385 NULL,
386 NULL,
387 TRUE,
388 CREATE_NEW_PROCESS_GROUP,
389 NULL,
390 NULL,
391 &stui,
392 &prci))
393 {
394 if (IsConsoleProcess(prci.hProcess))
395 {
396 /* FIXME: Protect this with critical section */
397 bChildProcessRunning = TRUE;
398 dwChildProcessId = prci.dwProcessId;
399
400 WaitForSingleObject (prci.hProcess, INFINITE);
401
402 /* FIXME: Protect this with critical section */
403 bChildProcessRunning = FALSE;
404
405 GetExitCodeProcess (prci.hProcess, &dwExitCode);
406 nErrorLevel = (INT)dwExitCode;
407 }
408 CloseHandle (prci.hThread);
409 CloseHandle (prci.hProcess);
410 }
411 else
412 {
413 #ifdef _DEBUG
414 DebugPrintf (_T("[ShellExecute: %s]\n"), full);
415 #endif
416 // See if we can run this with ShellExecute() ie myfile.xls
417 if (!RunFile(full))
418 {
419 #ifdef _DEBUG
420 DebugPrintf (_T("[ShellExecute failed!: %s]\n"), full);
421 #endif
422 error_bad_command ();
423 }
424 }
425 // restore console mode
426 SetConsoleMode( GetStdHandle( STD_INPUT_HANDLE ),
427 ENABLE_PROCESSED_INPUT );
428 }
429
430 /* Get code page if it has been change */
431 InputCodePage= GetConsoleCP();
432 OutputCodePage = GetConsoleOutputCP();
433 #ifndef __REACTOS__
434 SetConsoleTitle (szWindowTitle);
435 #endif
436 }
437
438
439 /*
440 * look through the internal commands and determine whether or not this
441 * command is one of them. If it is, call the command. If not, call
442 * execute to run it as an external program.
443 *
444 * line - the command line of the program to run
445 *
446 */
447
448 static VOID
449 DoCommand (LPTSTR line)
450 {
451 TCHAR com[CMDLINE_LENGTH]; /* the first word in the command */
452 LPTSTR cp = com;
453 LPTSTR cstart;
454 LPTSTR rest; /* pointer to the rest of the command line */
455 INT cl;
456 LPCOMMAND cmdptr;
457
458 #ifdef _DEBUG
459 DebugPrintf (_T("DoCommand: (\'%s\')\n"), line);
460 #endif /* DEBUG */
461
462 /* Skip over initial white space */
463 while (_istspace (*line))
464 line++;
465 rest = line;
466
467 cstart = rest;
468
469 /* Anything to do ? */
470 if (*rest)
471 {
472 if (*rest == _T('"'))
473 {
474 /* treat quoted words specially */
475
476 rest++;
477
478 while(*rest != _T('\0') && *rest != _T('"'))
479 *cp++ = _totlower (*rest++);
480 if (*rest == _T('"'))
481 rest++;
482 }
483 else
484 {
485 while (!IsDelimiter (*rest))
486 *cp++ = _totlower (*rest++);
487 }
488
489
490 /* Terminate first word */
491 *cp = _T('\0');
492
493 /* commands are limited to MAX_PATH */
494 if(_tcslen(com) > MAX_PATH)
495 {
496 error_bad_command();
497 return;
498 }
499
500 /* Skip over whitespace to rest of line */
501 while (_istspace (*rest))
502 rest++;
503
504 /* Scan internal command table */
505 for (cmdptr = cmds;; cmdptr++)
506 {
507 /* If end of table execute ext cmd */
508 if (cmdptr->name == NULL)
509 {
510 Execute (line, com, rest);
511 break;
512 }
513
514 if (!_tcscmp (com, cmdptr->name))
515 {
516 cmdptr->func (com, rest);
517 break;
518 }
519
520 /* The following code handles the case of commands like CD which
521 * are recognised even when the command name and parameter are
522 * not space separated.
523 *
524 * e.g dir..
525 * cd\freda
526 */
527
528 /* Get length of command name */
529 cl = _tcslen (cmdptr->name);
530
531 if ((cmdptr->flags & CMD_SPECIAL) &&
532 (!_tcsncmp (cmdptr->name, com, cl)) &&
533 (_tcschr (_T("\\.-"), *(com + cl))))
534 {
535 /* OK its one of the specials...*/
536
537 /* Terminate first word properly */
538 com[cl] = _T('\0');
539
540 /* Call with new rest */
541 cmdptr->func (com, cstart + cl);
542 break;
543 }
544 }
545 }
546 }
547
548
549 /*
550 * process the command line and execute the appropriate functions
551 * full input/output redirection and piping are supported
552 */
553
554 VOID ParseCommandLine (LPTSTR cmd)
555 {
556 TCHAR szMsg[RC_STRING_MAX_SIZE];
557 TCHAR cmdline[CMDLINE_LENGTH];
558 LPTSTR s;
559 #ifdef FEATURE_REDIRECTION
560 TCHAR in[CMDLINE_LENGTH] = _T("");
561 TCHAR out[CMDLINE_LENGTH] = _T("");
562 TCHAR err[CMDLINE_LENGTH] = _T("");
563 TCHAR szTempPath[MAX_PATH] = _T(".\\");
564 TCHAR szFileName[2][MAX_PATH] = {_T(""), _T("")};
565 HANDLE hFile[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
566 LPTSTR t = NULL;
567 INT num = 0;
568 INT nRedirFlags = 0;
569 INT Length;
570 UINT Attributes;
571
572 HANDLE hOldConIn;
573 HANDLE hOldConOut;
574 HANDLE hOldConErr;
575 #endif /* FEATURE_REDIRECTION */
576
577 _tcscpy (cmdline, cmd);
578 s = &cmdline[0];
579
580 #ifdef _DEBUG
581 DebugPrintf (_T("ParseCommandLine: (\'%s\')\n"), s);
582 #endif /* DEBUG */
583
584 #ifdef FEATURE_ALIASES
585 /* expand all aliases */
586 ExpandAlias (s, CMDLINE_LENGTH);
587 #endif /* FEATURE_ALIAS */
588
589 #ifdef FEATURE_REDIRECTION
590 /* find the temp path to store temporary files */
591 Length = GetTempPath (MAX_PATH, szTempPath);
592 if (Length > 0 && Length < MAX_PATH)
593 {
594 Attributes = GetFileAttributes(szTempPath);
595 if (Attributes == 0xffffffff ||
596 !(Attributes & FILE_ATTRIBUTE_DIRECTORY))
597 {
598 Length = 0;
599 }
600 }
601 if (Length == 0 || Length >= MAX_PATH)
602 {
603 _tcscpy(szTempPath, _T(".\\"));
604 }
605 if (szTempPath[_tcslen (szTempPath) - 1] != _T('\\'))
606 _tcscat (szTempPath, _T("\\"));
607
608 /* get the redirections from the command line */
609 num = GetRedirection (s, in, out, err, &nRedirFlags);
610
611 /* more efficient, but do we really need to do this? */
612 for (t = in; _istspace (*t); t++)
613 ;
614 _tcscpy (in, t);
615
616 for (t = out; _istspace (*t); t++)
617 ;
618 _tcscpy (out, t);
619
620 for (t = err; _istspace (*t); t++)
621 ;
622 _tcscpy (err, t);
623
624 /* Set up the initial conditions ... */
625 /* preserve STDIN, STDOUT and STDERR handles */
626 hOldConIn = GetStdHandle (STD_INPUT_HANDLE);
627 hOldConOut = GetStdHandle (STD_OUTPUT_HANDLE);
628 hOldConErr = GetStdHandle (STD_ERROR_HANDLE);
629
630 /* redirect STDIN */
631 if (in[0])
632 {
633 HANDLE hFile;
634 SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};
635
636 hFile = CreateFile (in, GENERIC_READ, FILE_SHARE_READ, &sa, OPEN_EXISTING,
637 FILE_ATTRIBUTE_NORMAL, NULL);
638 if (hFile == INVALID_HANDLE_VALUE)
639 {
640 LoadString(CMD_ModuleHandle, STRING_CMD_ERROR1, szMsg, RC_STRING_MAX_SIZE);
641 ConErrPrintf(szMsg, in);
642 return;
643 }
644
645 if (!SetStdHandle (STD_INPUT_HANDLE, hFile))
646 {
647 LoadString(CMD_ModuleHandle, STRING_CMD_ERROR1, szMsg, RC_STRING_MAX_SIZE);
648 ConErrPrintf(szMsg, in);
649 return;
650 }
651 #ifdef _DEBUG
652 DebugPrintf (_T("Input redirected from: %s\n"), in);
653 #endif
654 }
655
656 /* Now do all but the last pipe command */
657 *szFileName[0] = _T('\0');
658 hFile[0] = INVALID_HANDLE_VALUE;
659
660 while (num-- > 1)
661 {
662 SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};
663
664 /* Create unique temporary file name */
665 GetTempFileName (szTempPath, _T("CMD"), 0, szFileName[1]);
666
667 /* Set current stdout to temporary file */
668 hFile[1] = CreateFile (szFileName[1], GENERIC_WRITE, 0, &sa,
669 TRUNCATE_EXISTING, FILE_ATTRIBUTE_TEMPORARY, NULL);
670 if (hFile[1] == INVALID_HANDLE_VALUE)
671 {
672 LoadString(CMD_ModuleHandle, STRING_CMD_ERROR2, szMsg, RC_STRING_MAX_SIZE);
673 ConErrPrintf(szMsg);
674 return;
675 }
676
677 SetStdHandle (STD_OUTPUT_HANDLE, hFile[1]);
678
679 DoCommand (s);
680
681 /* close stdout file */
682 SetStdHandle (STD_OUTPUT_HANDLE, hOldConOut);
683 if ((hFile[1] != INVALID_HANDLE_VALUE) && (hFile[1] != hOldConOut))
684 {
685 CloseHandle (hFile[1]);
686 hFile[1] = INVALID_HANDLE_VALUE;
687 }
688
689 /* close old stdin file */
690 SetStdHandle (STD_INPUT_HANDLE, hOldConIn);
691 if ((hFile[0] != INVALID_HANDLE_VALUE) && (hFile[0] != hOldConIn))
692 {
693 /* delete old stdin file, if it is a real file */
694 CloseHandle (hFile[0]);
695 hFile[0] = INVALID_HANDLE_VALUE;
696 DeleteFile (szFileName[0]);
697 *szFileName[0] = _T('\0');
698 }
699
700 /* copy stdout file name to stdin file name */
701 _tcscpy (szFileName[0], szFileName[1]);
702 *szFileName[1] = _T('\0');
703
704 /* open new stdin file */
705 hFile[0] = CreateFile (szFileName[0], GENERIC_READ, 0, &sa,
706 OPEN_EXISTING, FILE_ATTRIBUTE_TEMPORARY, NULL);
707 SetStdHandle (STD_INPUT_HANDLE, hFile[0]);
708
709 s = s + _tcslen (s) + 1;
710 }
711
712 /* Now set up the end conditions... */
713 /* redirect STDOUT */
714 if (out[0])
715 {
716 /* Final output to here */
717 HANDLE hFile;
718 SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};
719
720 hFile = CreateFile (out, GENERIC_WRITE, FILE_SHARE_READ, &sa,
721 (nRedirFlags & OUTPUT_APPEND) ? OPEN_ALWAYS : CREATE_ALWAYS,
722 FILE_ATTRIBUTE_NORMAL, NULL);
723 if (hFile == INVALID_HANDLE_VALUE)
724 {
725 LoadString(CMD_ModuleHandle, STRING_CMD_ERROR3, szMsg, RC_STRING_MAX_SIZE);
726 ConErrPrintf(szMsg, out);
727 return;
728 }
729
730 if (!SetStdHandle (STD_OUTPUT_HANDLE, hFile))
731 {
732 LoadString(CMD_ModuleHandle, STRING_CMD_ERROR3, szMsg, RC_STRING_MAX_SIZE);
733 ConErrPrintf(szMsg, out);
734 return;
735 }
736
737 if (nRedirFlags & OUTPUT_APPEND)
738 {
739 LONG lHighPos = 0;
740
741 if (GetFileType (hFile) == FILE_TYPE_DISK)
742 SetFilePointer (hFile, 0, &lHighPos, FILE_END);
743 }
744 #ifdef _DEBUG
745 DebugPrintf (_T("Output redirected to: %s\n"), out);
746 #endif
747 }
748 else if (hOldConOut != INVALID_HANDLE_VALUE)
749 {
750 /* Restore original stdout */
751 HANDLE hOut = GetStdHandle (STD_OUTPUT_HANDLE);
752 SetStdHandle (STD_OUTPUT_HANDLE, hOldConOut);
753 if (hOldConOut != hOut)
754 CloseHandle (hOut);
755 hOldConOut = INVALID_HANDLE_VALUE;
756 }
757
758 /* redirect STDERR */
759 if (err[0])
760 {
761 /* Final output to here */
762 HANDLE hFile;
763 SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};
764
765 if (!_tcscmp (err, out))
766 {
767 #ifdef _DEBUG
768 DebugPrintf (_T("Stdout and stderr will use the same file!!\n"));
769 #endif
770 DuplicateHandle (GetCurrentProcess (),
771 GetStdHandle (STD_OUTPUT_HANDLE),
772 GetCurrentProcess (),
773 &hFile, 0, TRUE, DUPLICATE_SAME_ACCESS);
774 }
775 else
776 {
777 hFile = CreateFile (err,
778 GENERIC_WRITE,
779 0,
780 &sa,
781 (nRedirFlags & ERROR_APPEND) ? OPEN_ALWAYS : CREATE_ALWAYS,
782 FILE_ATTRIBUTE_NORMAL,
783 NULL);
784 if (hFile == INVALID_HANDLE_VALUE)
785 {
786 LoadString(CMD_ModuleHandle, STRING_CMD_ERROR3, szMsg, RC_STRING_MAX_SIZE);
787 ConErrPrintf(szMsg, err);
788 return;
789 }
790 }
791
792 if (!SetStdHandle (STD_ERROR_HANDLE, hFile))
793 {
794 LoadString(CMD_ModuleHandle, STRING_CMD_ERROR3, szMsg, RC_STRING_MAX_SIZE);
795 ConErrPrintf(szMsg, err);
796 return;
797 }
798
799 if (nRedirFlags & ERROR_APPEND)
800 {
801 LONG lHighPos = 0;
802
803 if (GetFileType (hFile) == FILE_TYPE_DISK)
804 SetFilePointer (hFile, 0, &lHighPos, FILE_END);
805 }
806 #ifdef _DEBUG
807 DebugPrintf (_T("Error redirected to: %s\n"), err);
808 #endif
809 }
810 else if (hOldConErr != INVALID_HANDLE_VALUE)
811 {
812 /* Restore original stderr */
813 HANDLE hErr = GetStdHandle (STD_ERROR_HANDLE);
814 SetStdHandle (STD_ERROR_HANDLE, hOldConErr);
815 if (hOldConErr != hErr)
816 CloseHandle (hErr);
817 hOldConErr = INVALID_HANDLE_VALUE;
818 }
819 #endif
820
821 /* process final command */
822 DoCommand (s);
823
824 #ifdef FEATURE_REDIRECTION
825 /* close old stdin file */
826 #if 0 /* buggy implementation */
827 SetStdHandle (STD_INPUT_HANDLE, hOldConIn);
828 if ((hFile[0] != INVALID_HANDLE_VALUE) &&
829 (hFile[0] != hOldConIn))
830 {
831 /* delete old stdin file, if it is a real file */
832 CloseHandle (hFile[0]);
833 hFile[0] = INVALID_HANDLE_VALUE;
834 DeleteFile (szFileName[0]);
835 *szFileName[0] = _T('\0');
836 }
837
838 /* Restore original STDIN */
839 if (hOldConIn != INVALID_HANDLE_VALUE)
840 {
841 HANDLE hIn = GetStdHandle (STD_INPUT_HANDLE);
842 SetStdHandle (STD_INPUT_HANDLE, hOldConIn);
843 if (hOldConIn != hIn)
844 CloseHandle (hIn);
845 hOldConIn = INVALID_HANDLE_VALUE;
846 }
847 else
848 {
849 #ifdef _DEBUG
850 DebugPrintf (_T("Can't restore STDIN! Is invalid!!\n"), out);
851 #endif
852 }
853 #endif /* buggy implementation */
854
855
856 if (hOldConIn != INVALID_HANDLE_VALUE)
857 {
858 HANDLE hIn = GetStdHandle (STD_INPUT_HANDLE);
859 SetStdHandle (STD_INPUT_HANDLE, hOldConIn);
860 if (hIn == INVALID_HANDLE_VALUE)
861 {
862 #ifdef _DEBUG
863 DebugPrintf (_T("Previous STDIN is invalid!!\n"));
864 #endif
865 }
866 else
867 {
868 if (GetFileType (hIn) == FILE_TYPE_DISK)
869 {
870 if (hFile[0] == hIn)
871 {
872 CloseHandle (hFile[0]);
873 hFile[0] = INVALID_HANDLE_VALUE;
874 DeleteFile (szFileName[0]);
875 *szFileName[0] = _T('\0');
876 }
877 else
878 {
879 #ifdef _DEBUG
880 DebugPrintf (_T("hFile[0] and hIn dont match!!!\n"));
881 #endif
882 }
883 }
884 }
885 }
886
887
888 /* Restore original STDOUT */
889 if (hOldConOut != INVALID_HANDLE_VALUE)
890 {
891 HANDLE hOut = GetStdHandle (STD_OUTPUT_HANDLE);
892 SetStdHandle (STD_OUTPUT_HANDLE, hOldConOut);
893 if (hOldConOut != hOut)
894 CloseHandle (hOut);
895 hOldConOut = INVALID_HANDLE_VALUE;
896 }
897
898 /* Restore original STDERR */
899 if (hOldConErr != INVALID_HANDLE_VALUE)
900 {
901 HANDLE hErr = GetStdHandle (STD_ERROR_HANDLE);
902 SetStdHandle (STD_ERROR_HANDLE, hOldConErr);
903 if (hOldConErr != hErr)
904 CloseHandle (hErr);
905 hOldConErr = INVALID_HANDLE_VALUE;
906 }
907 #endif /* FEATURE_REDIRECTION */
908 }
909
910
911 /*
912 * do the prompt/input/process loop
913 *
914 */
915
916 static INT
917 ProcessInput (BOOL bFlag)
918 {
919 TCHAR commandline[CMDLINE_LENGTH];
920 TCHAR readline[CMDLINE_LENGTH];
921 LPTSTR tp = NULL;
922 LPTSTR ip;
923 LPTSTR cp;
924 BOOL bEchoThisLine;
925
926
927 do
928 {
929 /* if no batch input then... */
930 if (!(ip = ReadBatchLine (&bEchoThisLine)))
931 {
932 if (bFlag)
933 return 0;
934
935 ReadCommand (readline, CMDLINE_LENGTH);
936 ip = readline;
937 bEchoThisLine = FALSE;
938 }
939
940 cp = commandline;
941 while (*ip)
942 {
943 if (*ip == _T('%'))
944 {
945 switch (*++ip)
946 {
947 case _T('%'):
948 *cp++ = *ip++;
949 break;
950
951 case _T('0'):
952 case _T('1'):
953 case _T('2'):
954 case _T('3'):
955 case _T('4'):
956 case _T('5'):
957 case _T('6'):
958 case _T('7'):
959 case _T('8'):
960 case _T('9'):
961 if ((tp = FindArg (*ip - _T('0'))))
962 {
963 cp = _stpcpy (cp, tp);
964 ip++;
965 }
966 else
967 *cp++ = _T('%');
968 break;
969
970 case _T('?'):
971 cp += _stprintf (cp, _T("%u"), nErrorLevel);
972 ip++;
973 break;
974
975 default:
976 tp = _tcschr(ip, _T('%'));
977 if ((tp != NULL) &&
978 (tp <= _tcschr(ip, _T(' ')) - 1))
979 {
980 TCHAR evar[512];
981 *tp = _T('\0');
982
983 /* FIXME: This is just a quick hack!! */
984 /* Do a proper memory allocation!! */
985 if (GetEnvironmentVariable (ip, evar, 512))
986 cp = _stpcpy (cp, evar);
987
988 ip = tp + 1;
989 }
990 else
991 {
992 *cp++ = _T('%');
993 }
994 break;
995 }
996 continue;
997 }
998
999 if (_istcntrl (*ip))
1000 *ip = _T(' ');
1001 *cp++ = *ip++;
1002 }
1003
1004 *cp = _T('\0');
1005
1006 /* strip trailing spaces */
1007 while ((--cp >= commandline) && _istspace (*cp));
1008
1009 *(cp + 1) = _T('\0');
1010
1011 /* JPP 19980807 */
1012 /* Echo batch file line */
1013 if (bEchoThisLine)
1014 {
1015 PrintPrompt ();
1016 ConOutPuts (commandline);
1017 }
1018
1019 if (*commandline)
1020 {
1021 ParseCommandLine (commandline);
1022 if (bEcho && !bIgnoreEcho)
1023 ConOutChar ('\n');
1024 bIgnoreEcho = FALSE;
1025 }
1026 }
1027 while (!bCanExit || !bExit);
1028
1029 return 0;
1030 }
1031
1032
1033 /*
1034 * control-break handler.
1035 */
1036 BOOL WINAPI BreakHandler (DWORD dwCtrlType)
1037 {
1038
1039 if ((dwCtrlType != CTRL_C_EVENT) &&
1040 (dwCtrlType != CTRL_BREAK_EVENT))
1041 return FALSE;
1042
1043 if (bChildProcessRunning == TRUE)
1044 {
1045 GenerateConsoleCtrlEvent (CTRL_C_EVENT,
1046 dwChildProcessId);
1047 return TRUE;
1048 }
1049
1050 /* FIXME: Handle batch files */
1051
1052 /* FIXME: Print "^C" */
1053
1054
1055 return TRUE;
1056 }
1057
1058
1059 VOID AddBreakHandler (VOID)
1060 {
1061 SetConsoleCtrlHandler ((PHANDLER_ROUTINE)BreakHandler, TRUE);
1062 }
1063
1064
1065 VOID RemoveBreakHandler (VOID)
1066 {
1067 SetConsoleCtrlHandler ((PHANDLER_ROUTINE)BreakHandler, FALSE);
1068 }
1069
1070
1071 /*
1072 * show commands and options that are available.
1073 *
1074 */
1075 #if 0
1076 static VOID
1077 ShowCommands (VOID)
1078 {
1079 /* print command list */
1080 ConOutResPuts(STRING_CMD_HELP1);
1081 PrintCommandList();
1082
1083 /* print feature list */
1084 ConOutResPuts(STRING_CMD_HELP2);
1085
1086 #ifdef FEATURE_ALIASES
1087 ConOutResPuts(STRING_CMD_HELP3);
1088 #endif
1089 #ifdef FEATURE_HISTORY
1090 ConOutResPuts(STRING_CMD_HELP4);
1091 #endif
1092 #ifdef FEATURE_UNIX_FILENAME_COMPLETION
1093 ConOutResPuts(STRING_CMD_HELP5);
1094 #endif
1095 #ifdef FEATURE_DIRECTORY_STACK
1096 ConOutResPuts(STRING_CMD_HELP6);
1097 #endif
1098 #ifdef FEATURE_REDIRECTION
1099 ConOutResPuts(STRING_CMD_HELP7);
1100 #endif
1101 ConOutChar(_T('\n'));
1102 }
1103 #endif
1104
1105 /*
1106 * set up global initializations and process parameters
1107 *
1108 * argc - number of parameters to command.com
1109 * argv - command-line parameters
1110 *
1111 */
1112 static VOID
1113 Initialize (int argc, TCHAR* argv[])
1114 {
1115 TCHAR commandline[CMDLINE_LENGTH];
1116 TCHAR ModuleName[_MAX_PATH + 1];
1117 INT i;
1118
1119 //INT len;
1120 //TCHAR *ptr, *cmdLine;
1121
1122
1123 #ifdef _DEBUG
1124 INT x;
1125
1126 DebugPrintf (_T("[command args:\n"));
1127 for (x = 0; x < argc; x++)
1128 {
1129 DebugPrintf (_T("%d. %s\n"), x, argv[x]);
1130 }
1131 DebugPrintf (_T("]\n"));
1132 #endif
1133
1134 /* get version information */
1135 osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
1136 GetVersionEx (&osvi);
1137
1138 InitLocale ();
1139
1140 /* get default input and output console handles */
1141 hOut = GetStdHandle (STD_OUTPUT_HANDLE);
1142 hIn = GetStdHandle (STD_INPUT_HANDLE);
1143
1144
1145 if (argc >= 2 && !_tcsncmp (argv[1], _T("/?"), 2))
1146 {
1147 ConOutResPuts(STRING_CMD_HELP8);
1148 ExitProcess(0);
1149 }
1150 SetConsoleMode (hIn, ENABLE_PROCESSED_INPUT);
1151
1152 #ifdef INCLUDE_CMD_CHDIR
1153 InitLastPath ();
1154 #endif
1155
1156 #ifdef FATURE_ALIASES
1157 InitializeAlias ();
1158 #endif
1159
1160 if (argc >= 2)
1161 {
1162 for (i = 1; i < argc; i++)
1163 {
1164 if (!_tcsicmp (argv[i], _T("/p")))
1165 {
1166 if (!IsExistingFile (_T("\\autoexec.bat")))
1167 {
1168 #ifdef INCLUDE_CMD_DATE
1169 cmd_date (_T(""), _T(""));
1170 #endif
1171 #ifdef INCLUDE_CMD_TIME
1172 cmd_time (_T(""), _T(""));
1173 #endif
1174 }
1175 else
1176 {
1177 ParseCommandLine (_T("\\autoexec.bat"));
1178 }
1179 bCanExit = FALSE;
1180 }
1181 else if (!_tcsicmp (argv[i], _T("/c")))
1182 {
1183 /* This just runs a program and exits */
1184 ++i;
1185 if (i < argc)
1186 {
1187 _tcscpy (commandline, argv[i]);
1188 while (++i < argc)
1189 {
1190 _tcscat (commandline, _T(" "));
1191 _tcscat (commandline, argv[i]);
1192 }
1193
1194 ParseCommandLine(commandline);
1195 ExitProcess (ProcessInput (TRUE));
1196 }
1197 else
1198 {
1199 ExitProcess (0);
1200 }
1201 }
1202 else if (!_tcsicmp (argv[i], _T("/k")))
1203 {
1204 /* This just runs a program and remains */
1205 ++i;
1206 if (i < argc)
1207 {
1208 _tcscpy (commandline, argv[i]);
1209 while (++i < argc)
1210 {
1211 _tcscat (commandline, _T(" "));
1212 _tcscat (commandline, argv[i]);
1213 }
1214
1215 ParseCommandLine(commandline);
1216 }
1217 }
1218 #ifdef INCLUDE_CMD_COLOR
1219 else if (!_tcsnicmp (argv[i], _T("/t:"), 3))
1220 {
1221 /* process /t (color) argument */
1222 wDefColor = (WORD)_tcstoul (&argv[i][3], NULL, 16);
1223 wColor = wDefColor;
1224 SetScreenColor (wColor, TRUE);
1225 }
1226 #endif
1227 }
1228 }
1229
1230 /* run cmdstart.bat */
1231 if (IsExistingFile (_T("cmdstart.bat")))
1232 {
1233 ParseCommandLine (_T("cmdstart.bat"));
1234 }
1235 else if (IsExistingFile (_T("\\cmdstart.bat")))
1236 {
1237 ParseCommandLine (_T("\\cmdstart.bat"));
1238 }
1239 #ifndef __REACTOS__
1240 else
1241 {
1242 /* try to run cmdstart.bat from install dir */
1243 LPTSTR p;
1244
1245 _tcscpy (commandline, argv[0]);
1246 p = _tcsrchr (commandline, _T('\\')) + 1;
1247 _tcscpy (p, _T("cmdstart.bat"));
1248
1249 if (IsExistingFile (_T("commandline")))
1250 {
1251 LoadString(CMD_ModuleHandle, STRING_CMD_ERROR4, szMsg, RC_STRING_MAX_SIZE);
1252 ConErrPrintf(szMsg, commandline);
1253 ParseCommandLine (commandline);
1254 }
1255 }
1256 #endif
1257
1258 #ifdef FEATURE_DIR_STACK
1259 /* initialize directory stack */
1260 InitDirectoryStack ();
1261 #endif
1262
1263
1264 #ifdef FEATURE_HISTORY
1265 /*initialize history*/
1266 InitHistory();
1267 #endif
1268
1269 /* Set COMSPEC environment variable */
1270 if (0 != GetModuleFileName (NULL, ModuleName, _MAX_PATH + 1))
1271 {
1272 ModuleName[_MAX_PATH] = _T('\0');
1273 SetEnvironmentVariable (_T("COMSPEC"), ModuleName);
1274 }
1275
1276 /* add ctrl break handler */
1277 AddBreakHandler ();
1278 }
1279
1280
1281 static VOID Cleanup (int argc, TCHAR *argv[])
1282 {
1283 #ifndef __REACTOS__
1284 TCHAR szMsg[RC_STRING_MAX_SIZE];
1285 #endif
1286
1287 /* run cmdexit.bat */
1288 if (IsExistingFile (_T("cmdexit.bat")))
1289 {
1290 ConErrResPuts(STRING_CMD_ERROR5);
1291
1292 ParseCommandLine (_T("cmdexit.bat"));
1293 }
1294 else if (IsExistingFile (_T("\\cmdexit.bat")))
1295 {
1296 ConErrResPuts (STRING_CMD_ERROR5);
1297 ParseCommandLine (_T("\\cmdexit.bat"));
1298 }
1299 #ifndef __REACTOS__
1300 else
1301 {
1302 /* try to run cmdexit.bat from install dir */
1303 TCHAR commandline[CMDLINE_LENGTH];
1304 LPTSTR p;
1305
1306 _tcscpy (commandline, argv[0]);
1307 p = _tcsrchr (commandline, _T('\\')) + 1;
1308 _tcscpy (p, _T("cmdexit.bat"));
1309
1310 if (IsExistingFile (_T("commandline")))
1311 {
1312 LoadString(CMD_ModuleHandle, STRING_CMD_ERROR4, szMsg, RC_STRING_MAX_SIZE);
1313 ConErrPrintf(szMsg, commandline);
1314 ParseCommandLine (commandline);
1315 }
1316 }
1317 #endif
1318
1319 #ifdef FEATURE_ALIASES
1320 DestroyAlias ();
1321 #endif
1322
1323 #ifdef FEATURE_DIECTORY_STACK
1324 /* destroy directory stack */
1325 DestroyDirectoryStack ();
1326 #endif
1327
1328 #ifdef INCLUDE_CMD_CHDIR
1329 FreeLastPath ();
1330 #endif
1331
1332 #ifdef FEATURE_HISTORY
1333 CleanHistory();
1334 #endif
1335
1336
1337 /* remove ctrl break handler */
1338 RemoveBreakHandler ();
1339 SetConsoleMode( GetStdHandle( STD_INPUT_HANDLE ),
1340 ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT );
1341 }
1342
1343 #ifdef __REACTOS__
1344 #ifdef _UNICODE
1345 PWCHAR * _CommandLineToArgvW(PWCHAR lpCmdLine, int *pNumArgs)
1346 {
1347 PWCHAR * argvw = NULL;
1348 PWCHAR ptr = lpCmdLine;
1349 PWCHAR str;
1350 int len;
1351 int NumArgs;
1352
1353 NumArgs = 0;
1354
1355 while(lpCmdLine && *lpCmdLine)
1356 {
1357 while (iswspace(*lpCmdLine)) lpCmdLine++;
1358 if (*lpCmdLine)
1359 {
1360 if ((NumArgs % 10)==0)
1361 {
1362 PWCHAR * old_argvw = argvw;
1363 argvw = malloc((NumArgs + 10) * sizeof(PWCHAR));
1364 memcpy(argvw, old_argvw, NumArgs * sizeof(PWCHAR));
1365 free(old_argvw);
1366 }
1367 ptr = wcschr(lpCmdLine, L' ');
1368 if (ptr)
1369 {
1370 len = ptr - lpCmdLine;
1371 }
1372 else
1373 {
1374 len = wcslen(lpCmdLine);
1375 }
1376 str = malloc((len + 1) * sizeof(WCHAR));
1377 memcpy(str, lpCmdLine, len * sizeof(WCHAR));
1378 str[len] = 0;
1379 argvw[NumArgs]=str;
1380 NumArgs++;
1381 lpCmdLine = ptr;
1382 }
1383 }
1384 *pNumArgs = NumArgs;
1385 return argvw;
1386 }
1387 #endif
1388 #endif
1389
1390 /*
1391 * main function
1392 */
1393 #ifdef _UNICODE
1394 int main(void)
1395 #else
1396 int main (int argc, char *argv[])
1397 #endif
1398 {
1399 CONSOLE_SCREEN_BUFFER_INFO Info;
1400 INT nExitCode;
1401 #ifdef _UNICODE
1402 PWCHAR * argv;
1403 int argc=0;
1404 #ifdef __REACTOS__
1405 argv = _CommandLineToArgvW(GetCommandLineW(), &argc);
1406 #else
1407 argv = CommandLineToArgvW(GetCommandLineW(), &argc);
1408 #endif
1409 #endif
1410
1411 SetFileApisToOEM();
1412 InputCodePage= 0;
1413 OutputCodePage = 0;
1414
1415 hConsole = CreateFile(_T("CONOUT$"), GENERIC_READ|GENERIC_WRITE,
1416 FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
1417 OPEN_EXISTING, 0, NULL);
1418 if (GetConsoleScreenBufferInfo(hConsole, &Info) == FALSE)
1419 {
1420 ConOutFormatMessage(GetLastError());
1421 return(1);
1422 }
1423 wColor = Info.wAttributes;
1424 wDefColor = wColor;
1425
1426 InputCodePage= GetConsoleCP();
1427 OutputCodePage = GetConsoleOutputCP();
1428 CMD_ModuleHandle = GetModuleHandle(NULL);
1429
1430 /* check switches on command-line */
1431 Initialize(argc, argv);
1432
1433 /* call prompt routine */
1434 nExitCode = ProcessInput(FALSE);
1435
1436 /* do the cleanup */
1437 Cleanup(argc, argv);
1438
1439 return(nExitCode);
1440 }
1441
1442 /* EOF */