Fixed color definition (bg and fg were swapped).
[wine] / programs / wcmd / wcmdmain.c
1 /*
2  * WCMD - Wine-compatible command line interface. 
3  *
4  * (C) 1999 - 2001 D A Pickles
5  */
6
7 /*
8  * FIXME:
9  * - Cannot handle parameters in quotes
10  * - Lots of functionality missing from builtins
11  */
12
13 #include "wcmd.h"
14
15 char *inbuilt[] = {"ATTRIB", "CALL", "CD", "CHDIR", "CLS", "COPY", "CTTY",
16                 "DATE", "DEL", "DIR", "ECHO", "ERASE", "FOR", "GOTO",
17                 "HELP", "IF", "LABEL", "MD", "MKDIR", "MOVE", "PATH", "PAUSE",
18                 "PROMPT", "REM", "REN", "RENAME", "RD", "RMDIR", "SET", "SHIFT",
19                 "TIME", "TYPE", "VERIFY", "VER", "VOL", "EXIT"};
20
21 HINSTANCE hinst;
22 DWORD errorlevel;
23 int echo_mode = 1, verify_mode = 0;
24 char nyi[] = "Not Yet Implemented\n\n";
25 char newline[] = "\n";
26 char version_string[] = "WCMD Version 0.17\n\n";
27 char anykey[] = "Press any key to continue: ";
28 char quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
29 BATCH_CONTEXT *context = NULL;
30
31 /*****************************************************************************
32  * Main entry point. This is a console application so we have a main() not a
33  * winmain().
34  */
35
36
37 int wine_main (int argc, char *argv[]) {
38
39 char string[1024], args[MAX_PATH], param[MAX_PATH];
40 int status, i;
41 DWORD count;
42 HANDLE h;
43
44   args[0] = param[0] = '\0';
45   if (argc > 1) {
46     for (i=1; i<argc; i++) {
47       if (argv[i][0] == '/') {
48         strcat (args, argv[i]);
49       }
50       else {
51         strcat (param, argv[i]);
52         strcat (param, " ");
53       }
54     }
55   }
56
57 /*
58  *      Allocate a console and set it up.
59  */
60
61   status = FreeConsole ();
62   if (!status) WCMD_print_error();
63   status = AllocConsole();
64   if (!status) WCMD_print_error();
65   SetConsoleMode (GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT |
66         ENABLE_PROCESSED_INPUT);
67
68 /*
69  *      Execute any command-line options.
70  */
71
72   if (strstr(args, "/q") != NULL) {
73     WCMD_echo ("OFF");
74   }
75
76   if (strstr(args, "/c") != NULL) {
77     WCMD_process_command (param);
78     return 0;
79   }
80
81   if (strstr(args, "/k") != NULL) {
82     WCMD_process_command (param);
83   }
84
85 /*
86  *      If there is an AUTOEXEC.BAT file, try to execute it.
87  */
88
89   GetFullPathName ("\\autoexec.bat", sizeof(string), string, NULL);
90   h = CreateFile (string, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
91   if (h != INVALID_HANDLE_VALUE) {
92     CloseHandle (h);
93 #if 0
94     WCMD_batch (string, " ");
95 #endif
96   }
97
98 /*
99  *      Loop forever getting commands and executing them.
100  */
101
102   WCMD_version ();
103   while (TRUE) {
104     WCMD_show_prompt ();
105     ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string), &count, NULL);
106     if (count > 1) {
107       string[count-1] = '\0';           /* ReadFile output is not null-terminated! */
108       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
109       if (lstrlen (string) != 0) {
110         if (strchr(string,'|') != NULL) {
111           WCMD_pipe (string);
112         }
113         else {
114           WCMD_process_command (string);
115         }
116       }
117     }
118   }
119 }
120
121
122 /*****************************************************************************
123  * Process one command. If the command is EXIT this routine does not return.
124  * We will recurse through here executing batch files.
125  */
126
127
128 void WCMD_process_command (char *command) {
129
130 char cmd[1024];
131 char *p;
132 int status, i;
133 DWORD count;
134 HANDLE old_stdin = 0, old_stdout = 0, h;
135 char *whichcmd;
136
137
138 /*
139  *      Expand up environment variables.
140  */
141
142     status = ExpandEnvironmentStrings (command, cmd, sizeof(cmd));
143     if (!status) {
144       WCMD_print_error ();
145       return;
146     }
147
148 /*
149  *      Changing default drive has to be handled as a special case.
150  */
151
152     if ((cmd[1] == ':') && IsCharAlpha (cmd[0]) && (strlen(cmd) == 2)) {
153       status = SetCurrentDirectory (cmd);
154       if (!status) WCMD_print_error ();
155       return;
156     }
157
158     /* Dont issue newline WCMD_output (newline);           @JED*/
159
160 /*
161  *      Redirect stdin and/or stdout if required.
162  */
163
164     if ((p = strchr(cmd,'<')) != NULL) {
165       h = CreateFile (WCMD_parameter (++p, 0, NULL), GENERIC_READ, 0, NULL, OPEN_EXISTING,
166                 FILE_ATTRIBUTE_NORMAL, NULL);
167       if (h == INVALID_HANDLE_VALUE) {
168         WCMD_print_error ();
169         return;
170       }
171       old_stdin = GetStdHandle (STD_INPUT_HANDLE);
172       SetStdHandle (STD_INPUT_HANDLE, h);
173     }
174     if ((p = strchr(cmd,'>')) != NULL) {
175       h = CreateFile (WCMD_parameter (++p, 0, NULL), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
176                 FILE_ATTRIBUTE_NORMAL, NULL);
177       if (h == INVALID_HANDLE_VALUE) {
178         WCMD_print_error ();
179         return;
180       }
181       old_stdout = GetStdHandle (STD_OUTPUT_HANDLE);
182       SetStdHandle (STD_OUTPUT_HANDLE, h);
183       *--p = '\0';
184     }
185     if ((p = strchr(cmd,'<')) != NULL) *p = '\0';
186
187 /*                                                               
188  * Strip leading whitespaces, and a '@' if supplied              
189  */                                                            
190     whichcmd = WCMD_strtrim_leading_spaces(cmd);               
191     if (whichcmd[0] == '@') whichcmd++;                        
192
193 /*
194  *      Check if the command entered is internal. If it is, pass the rest of the
195  *      line down to the command. If not try to run a program.
196  */
197
198     count = 0;
199     while (IsCharAlphaNumeric(whichcmd[count])) {              
200       count++;
201     }
202     for (i=0; i<=WCMD_EXIT; i++) {
203       if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
204           whichcmd, count, inbuilt[i], -1) == 2) break;        
205     }
206     p = WCMD_strtrim_leading_spaces (&whichcmd[count]);       
207     WCMD_parse (p, quals, param1, param2);
208     switch (i) {
209
210       case WCMD_ATTRIB:
211         WCMD_setshow_attrib ();
212         break;
213       case WCMD_CALL:
214         WCMD_batch (param1, p, 1);
215         break;
216       case WCMD_CD:
217       case WCMD_CHDIR:
218         WCMD_setshow_default ();
219         break;
220       case WCMD_CLS:
221         WCMD_clear_screen ();
222         break;
223       case WCMD_COPY:
224         WCMD_copy ();
225         break;
226       case WCMD_CTTY:
227         WCMD_change_tty ();
228         break;
229       case WCMD_DATE:
230         WCMD_setshow_date ();
231         break;
232       case WCMD_DEL:
233       case WCMD_ERASE:
234         WCMD_delete (0);
235         break;
236       case WCMD_DIR:
237         WCMD_directory ();
238         break;
239       case WCMD_ECHO:
240         /* Use the unstripped version of the following data - step over the space */
241         /* but only if a parameter follows                                        */
242         if (strlen(&whichcmd[count]) > 0)                                       
243           WCMD_echo(&whichcmd[count+1]);                                        
244         else                                                                    
245           WCMD_echo(&whichcmd[count]);                                          
246         break;                         
247       case WCMD_FOR:
248         WCMD_for (p);
249         break;
250       case WCMD_GOTO:
251         WCMD_goto ();
252         break;
253       case WCMD_HELP:
254         WCMD_give_help (p);
255         break;
256       case WCMD_IF:
257         WCMD_if (p);
258         break;
259       case WCMD_LABEL:
260         WCMD_volume (1, p);
261         break;
262       case WCMD_MD:
263       case WCMD_MKDIR:
264         WCMD_create_dir ();
265         break;
266       case WCMD_MOVE:
267         WCMD_move ();
268         break;
269       case WCMD_PATH:
270         WCMD_setshow_path ();
271         break;
272       case WCMD_PAUSE:
273         WCMD_pause ();
274         break;
275       case WCMD_PROMPT:
276         WCMD_setshow_prompt ();
277         break;
278       case WCMD_REM:
279         break;
280       case WCMD_REN:
281       case WCMD_RENAME:
282         WCMD_rename ();
283         break;
284       case WCMD_RD:
285       case WCMD_RMDIR:
286         WCMD_remove_dir ();
287         break;
288       case WCMD_SET:
289         WCMD_setshow_env (p);
290         break;
291       case WCMD_SHIFT:
292         WCMD_shift ();
293         break;
294       case WCMD_TIME:
295         WCMD_setshow_time ();
296         break;
297       case WCMD_TYPE:
298         WCMD_type ();
299         break;
300       case WCMD_VER:
301         WCMD_version ();
302         break;
303       case WCMD_VERIFY:
304         WCMD_verify (p);
305         break;
306       case WCMD_VOL:
307         WCMD_volume (0, p);
308         break;
309       case WCMD_EXIT:
310         ExitProcess (0);
311       default:
312         WCMD_run_program (whichcmd);                   
313     };
314     if (old_stdin) {
315       CloseHandle (GetStdHandle (STD_INPUT_HANDLE));
316       SetStdHandle (STD_INPUT_HANDLE, old_stdin);
317     }
318     if (old_stdout) {
319       CloseHandle (GetStdHandle (STD_OUTPUT_HANDLE));
320       SetStdHandle (STD_OUTPUT_HANDLE, old_stdout);
321     }
322   }
323
324 /******************************************************************************
325  * WCMD_run_program
326  *
327  *      Execute a command line as an external program. If no extension given then
328  *      precedence is given to .BAT files. Must allow recursion.
329  *
330  *      FIXME: Case sensitivity in suffixes!
331  */
332
333 void WCMD_run_program (char *command) {
334
335 STARTUPINFO st;
336 PROCESS_INFORMATION pe;
337 SHFILEINFO psfi;
338 DWORD console;
339 BOOL status;
340 HANDLE h;
341 HINSTANCE hinst;
342 char filetorun[MAX_PATH];
343
344   WCMD_parse (command, quals, param1, param2);  /* Quick way to get the filename */
345   if (strpbrk (param1, "\\:") == NULL) {        /* No explicit path given */
346     if ((strchr (param1, '.') == NULL) || (strstr (param1, ".bat") != NULL)) {
347       if (SearchPath (NULL, param1, ".bat", sizeof(filetorun), filetorun, NULL)) {
348         WCMD_batch (filetorun, command, 0);
349         return;
350       }
351     }
352   }
353   else {                                        /* Explicit path given */
354     if (strstr (param1, ".bat") != NULL) {
355       WCMD_batch (param1, command, 0);
356       return;
357     }
358     if (strchr (param1, '.') == NULL) {
359       strcpy (filetorun, param1);
360       strcat (filetorun, ".bat");
361       h = CreateFile (filetorun, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
362       if (h != INVALID_HANDLE_VALUE) {
363         CloseHandle (h);
364         WCMD_batch (param1, command, 0);
365         return;
366       }
367     }
368   }
369
370         /* No batch file found, assume executable */
371
372   hinst = FindExecutable (param1, NULL, filetorun);
373   if ((int)hinst < 32) {
374     WCMD_print_error ();
375     return;
376   }
377   console = SHGetFileInfo (filetorun, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
378   if (!console) {
379     WCMD_print_error ();
380     return;
381   }
382   ZeroMemory (&st, sizeof(STARTUPINFO));
383   st.cb = sizeof(STARTUPINFO);
384   status = CreateProcess (NULL, command, NULL, NULL, FALSE,
385                  0, NULL, NULL, &st, &pe);
386   if (!status) {
387     WCMD_print_error ();
388   }
389   if (!HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
390   GetExitCodeProcess (pe.hProcess, &errorlevel);
391   if (errorlevel == STILL_ACTIVE) errorlevel = 0;
392 }
393
394 /******************************************************************************
395  * WCMD_show_prompt
396  *
397  *      Display the prompt on STDout
398  *
399  */
400
401 void WCMD_show_prompt () {
402
403 int status;
404 char out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
405 char *p, *q;
406
407   status = GetEnvironmentVariable ("PROMPT", prompt_string, sizeof(prompt_string));
408   if ((status == 0) || (status > sizeof(prompt_string))) {
409     lstrcpy (prompt_string, "$N$G");
410   }
411   p = prompt_string;
412   q = out_string;
413   *q = '\0';
414   while (*p != '\0') {
415     if (*p != '$') {
416       *q++ = *p++;
417       *q = '\0';
418     }
419     else {
420       p++;
421       switch (toupper(*p)) {
422         case '$':
423           *q++ = '$';
424           break;
425         case 'B':
426           *q++ = '|';
427           break;
428         case 'D':
429           GetDateFormat (LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
430           while (*q) q++;
431           break;
432         case 'E':
433           *q++ = '\E';
434           break;
435         case 'G':
436           *q++ = '>';
437           break;
438         case 'L':
439           *q++ = '<';
440           break;
441         case 'N':
442           status = GetCurrentDirectory (sizeof(curdir), curdir);
443           if (status) {
444             *q++ = curdir[0];
445           }
446           break;
447         case 'P':
448           status = GetCurrentDirectory (sizeof(curdir), curdir);
449           if (status) {
450             lstrcat (q, curdir);
451             while (*q) q++;
452           }
453           break;
454         case 'Q':
455           *q++ = '=';
456           break;
457         case 'T':
458           GetTimeFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
459           while (*q) q++;
460           break;
461         case '_':
462           *q++ = '\n';
463           break;
464       }
465       p++;
466       *q = '\0';
467     }
468   }
469   WCMD_output (out_string);
470 }
471
472 /****************************************************************************
473  * WCMD_print_error
474  *
475  * Print the message for GetLastError
476  */
477
478 void WCMD_print_error () {
479 LPVOID lpMsgBuf;
480 DWORD error_code;
481 int status;
482
483   error_code = GetLastError ();
484   status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
485                         NULL, error_code, 0, (LPTSTR) &lpMsgBuf, 0, NULL);
486   if (!status) {
487     WCMD_output ("FIXME: Cannot display message for error %d, status %d\n",
488                         error_code, GetLastError());
489     return;
490   }
491   WCMD_output (lpMsgBuf);
492   LocalFree ((HLOCAL)lpMsgBuf);
493   WCMD_output (newline);
494   return;
495 }
496
497 /*******************************************************************
498  * WCMD_parse - parse a command into parameters and qualifiers.
499  *
500  *      On exit, all qualifiers are concatenated into q, the first string
501  *      not beginning with "/" is in p1 and the
502  *      second in p2. Any subsequent non-qualifier strings are lost.
503  *      Parameters in quotes are handled.
504  */
505
506 void WCMD_parse (char *s, char *q, char *p1, char *p2) {
507
508 int p = 0;
509
510   *q = *p1 = *p2 = '\0';
511   while (TRUE) {
512     switch (*s) {
513       case '/':
514         *q++ = *s++;
515         while ((*s != '\0') && (*s != ' ') && *s != '/') {
516           *q++ = toupper (*s++);
517         }
518         *q = '\0';
519         break;
520       case ' ':
521         s++;
522         break;
523       case '"':
524         s++;
525         while ((*s != '\0') && (*s != '"')) {
526           if (p == 0) *p1++ = *s++;
527           else if (p == 1) *p2++ = *s++;
528           else s++;
529         }
530         if (p == 0) *p1 = '\0';
531         if (p == 1) *p2 = '\0';
532         p++;
533         if (*s == '"') s++;
534         break;
535       case '\0':
536         return;
537       default:
538         while ((*s != '\0') && (*s != ' ') && (*s != '/')) {
539           if (p == 0) *p1++ = *s++;
540           else if (p == 1) *p2++ = *s++;
541           else s++;
542         }
543         if (p == 0) *p1 = '\0';
544         if (p == 1) *p2 = '\0';
545         p++;
546     }
547   }
548 }
549
550 /*******************************************************************
551  * WCMD_output - send output to current standard output device.
552  *
553  */
554
555 void WCMD_output (char *format, ...) {
556
557 va_list ap;
558 char string[1024];
559 DWORD count;
560
561   va_start(ap,format);
562   vsprintf (string, format, ap);
563   WriteFile (GetStdHandle(STD_OUTPUT_HANDLE), string, lstrlen(string), &count, NULL);
564   va_end(ap);
565 }
566
567 /******************************************************************* 
568  * WCMD_output_asis - send output to current standard output device.
569  *        without formatting eg. when message contains '%'
570  */
571
572 void WCMD_output_asis (char *message) {
573   DWORD count;
574   WriteFile (GetStdHandle(STD_OUTPUT_HANDLE), message, lstrlen(message), &count, NULL);
575 }
576
577
578
579 /***************************************************************************
580  * WCMD_strtrim_leading_spaces
581  *
582  *      Remove leading spaces from a string. Return a pointer to the first
583  *      non-space character. Does not modify the input string
584  */
585
586 char *WCMD_strtrim_leading_spaces (char *string) {
587
588 char *ptr;
589
590   ptr = string;
591   while (*ptr == ' ') ptr++;
592   return ptr;
593 }
594
595 /*************************************************************************
596  * WCMD_strtrim_trailing_spaces
597  *
598  *      Remove trailing spaces from a string. This routine modifies the input
599  *      string by placing a null after the last non-space character
600  */
601
602 void WCMD_strtrim_trailing_spaces (char *string) {
603
604 char *ptr;
605
606   ptr = string + lstrlen (string) - 1;
607   while ((*ptr == ' ') && (ptr >= string)) {
608     *ptr = '\0';
609     ptr--;
610   }
611 }
612
613 /*************************************************************************
614  * WCMD_pipe
615  *
616  *      Handle pipes within a command - the DOS way using temporary files.
617  */
618
619 void WCMD_pipe (char *command) {
620
621 char *p;
622 char temp_path[MAX_PATH], temp_file[MAX_PATH], temp_file2[MAX_PATH], temp_cmd[1024];
623
624   GetTempPath (sizeof(temp_path), temp_path);
625   GetTempFileName (temp_path, "WCMD", 0, temp_file);
626   p = strchr(command, '|');
627   *p++ = '\0';
628   wsprintf (temp_cmd, "%s > %s", command, temp_file);
629   WCMD_process_command (temp_cmd);
630   command = p;
631   while ((p = strchr(command, '|'))) {
632     *p++ = '\0';
633     GetTempFileName (temp_path, "WCMD", 0, temp_file2);
634     wsprintf (temp_cmd, "%s < %s > %s", command, temp_file, temp_file2);
635     WCMD_process_command (temp_cmd);
636     DeleteFile (temp_file);
637     lstrcpy (temp_file, temp_file2);
638     command = p;
639   }
640   wsprintf (temp_cmd, "%s < %s", command, temp_file);
641   WCMD_process_command (temp_cmd);
642   DeleteFile (temp_file);
643 }
644