Merged msacm and msacm32 dlls.
[wine] / programs / wcmd / wcmdmain.c
1 /*
2  * WCMD - Wine-compatible command line interface. 
3  *
4  * (C) 1999 D A Pickles
5  */
6
7 /*
8  * FIXME:
9  * - No support for pipes
10  * - 32-bit limit on file sizes in DIR command
11  * - Cannot handle parameters in quotes
12  * - Lots of functionality missing from builtins
13  */
14
15 #include "wcmd.h"
16
17 char *inbuilt[] = {"ATTRIB", "CALL", "CD", "CHDIR", "CLS", "COPY", "CTTY",
18                 "DATE", "DEL", "DIR", "ECHO", "ERASE", "FOR", "GOTO",
19                 "HELP", "IF", "LABEL", "MD", "MKDIR", "MOVE", "PATH", "PAUSE",
20                 "PROMPT", "REM", "REN", "RENAME", "RD", "RMDIR", "SET", "SHIFT",
21                 "TIME", "TYPE", "VERIFY", "VER", "VOL", "EXIT"};
22
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.12\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, 0);
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         WCMD_process_command (string);
111       }
112     }
113   }
114 }
115
116
117 /*****************************************************************************
118  * Process one command. If the command is EXIT this routine does not return.
119  * We will recurse through here executing batch files.
120  */
121
122
123 void WCMD_process_command (char *command) {
124
125 char cmd[1024];
126 char *p;
127 int status, i;
128 DWORD count;
129 HANDLE old_stdin = 0, old_stdout = 0, h;
130
131 /*
132  *      Throw away constructs we don't support yet
133  */
134
135     if (strchr(command,'|') != NULL) {
136       WCMD_output ("Pipes not yet implemented\n");
137       return;
138     }
139
140 /*
141  *      Expand up environment variables.
142  */
143
144     status = ExpandEnvironmentStrings (command, cmd, sizeof(cmd));
145     if (!status) {
146       WCMD_print_error ();
147       return;
148     }
149
150 /*
151  *      Changing default drive has to be handled as a special case.
152  */
153
154     if ((cmd[1] == ':') && IsCharAlpha (cmd[0]) && (strlen(cmd) == 2)) {
155       status = SetCurrentDirectory (cmd);
156       if (!status) WCMD_print_error ();
157       return;
158     }
159     WCMD_output (newline);
160
161 /*
162  *      Redirect stdin and/or stdout if required.
163  */
164
165     if ((p = strchr(cmd,'<')) != NULL) {
166       h = CreateFile (WCMD_parameter (++p, 0, NULL), GENERIC_READ, 0, NULL, OPEN_EXISTING,
167                 FILE_ATTRIBUTE_NORMAL, 0);
168       if (h == INVALID_HANDLE_VALUE) {
169         WCMD_print_error ();
170         return;
171       }
172       old_stdin = GetStdHandle (STD_INPUT_HANDLE);
173       SetStdHandle (STD_INPUT_HANDLE, h);
174     }
175     if ((p = strchr(cmd,'>')) != NULL) {
176       h = CreateFile (WCMD_parameter (++p, 0, NULL), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
177                 FILE_ATTRIBUTE_NORMAL, 0);
178       if (h == INVALID_HANDLE_VALUE) {
179         WCMD_print_error ();
180         return;
181       }
182       old_stdout = GetStdHandle (STD_OUTPUT_HANDLE);
183       SetStdHandle (STD_OUTPUT_HANDLE, h);
184       *--p = '\0';
185     }
186     if ((p = strchr(cmd,'<')) != NULL) *p = '\0';
187
188 /*
189  *      Check if the command entered is internal. If it is, pass the rest of the
190  *      line down to the command. If not try to run a program.
191  */
192
193     count = 0;
194     while (IsCharAlphaNumeric(cmd[count])) {
195       count++;
196     }
197     for (i=0; i<=WCMD_EXIT; i++) {
198       if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
199           cmd, count, inbuilt[i], -1) == 2) break;
200     }
201     p = WCMD_strtrim_leading_spaces (&cmd[count]);
202     WCMD_parse (p, quals, param1, param2);
203     switch (i) {
204
205       case WCMD_ATTRIB:
206         WCMD_setshow_attrib ();
207         break;
208       case WCMD_CALL:
209         WCMD_batch (param1, p, 1);
210         break;
211       case WCMD_CD:
212       case WCMD_CHDIR:
213         WCMD_setshow_default ();
214         break;
215       case WCMD_CLS:
216         WCMD_clear_screen ();
217         break;
218       case WCMD_COPY:
219         WCMD_copy ();
220         break;
221       case WCMD_CTTY:
222         WCMD_change_tty ();
223         break;
224       case WCMD_DATE:
225         WCMD_setshow_date ();
226         break;
227       case WCMD_DEL:
228       case WCMD_ERASE:
229         WCMD_delete (0);
230         break;
231       case WCMD_DIR:
232         WCMD_directory ();
233         break;
234       case WCMD_ECHO:
235         WCMD_echo (p);
236         break;
237       case WCMD_FOR:
238         WCMD_for (p);
239         break;
240       case WCMD_GOTO:
241         WCMD_goto ();
242         break;
243       case WCMD_HELP:
244         WCMD_give_help (p);
245         break;
246       case WCMD_IF:
247         WCMD_if (p);
248         break;
249       case WCMD_LABEL:
250         WCMD_volume (1, p);
251         break;
252       case WCMD_MD:
253       case WCMD_MKDIR:
254         WCMD_create_dir ();
255         break;
256       case WCMD_MOVE:
257         WCMD_move ();
258         break;
259       case WCMD_PATH:
260         WCMD_setshow_path ();
261         break;
262       case WCMD_PAUSE:
263         WCMD_pause ();
264         break;
265       case WCMD_PROMPT:
266         WCMD_setshow_prompt ();
267         break;
268       case WCMD_REM:
269         break;
270       case WCMD_REN:
271       case WCMD_RENAME:
272         WCMD_rename ();
273         break;
274       case WCMD_RD:
275       case WCMD_RMDIR:
276         WCMD_remove_dir ();
277         break;
278       case WCMD_SET:
279         WCMD_setshow_env (p);
280         break;
281       case WCMD_SHIFT:
282         WCMD_shift ();
283         break;
284       case WCMD_TIME:
285         WCMD_setshow_time ();
286         break;
287       case WCMD_TYPE:
288         WCMD_type ();
289         break;
290       case WCMD_VER:
291         WCMD_version ();
292         break;
293       case WCMD_VERIFY:
294         WCMD_verify (p);
295         break;
296       case WCMD_VOL:
297         WCMD_volume (0, p);
298         break;
299       case WCMD_EXIT:
300         ExitProcess (0);
301       default:
302         WCMD_run_program (cmd);
303     };
304     if (old_stdin) {
305       CloseHandle (GetStdHandle (STD_INPUT_HANDLE));
306       SetStdHandle (STD_INPUT_HANDLE, old_stdin);
307     }
308     if (old_stdout) {
309       CloseHandle (GetStdHandle (STD_OUTPUT_HANDLE));
310       SetStdHandle (STD_OUTPUT_HANDLE, old_stdout);
311     }
312   }
313
314 /******************************************************************************
315  * WCMD_run_program
316  *
317  *      Execute a command line as an external program. If no extension given then
318  *      precedence is given to .BAT files. Must allow recursion.
319  *
320  *      FIXME: Case sensitivity in suffixes!
321  */
322
323 void WCMD_run_program (char *command) {
324
325 STARTUPINFO st;
326 PROCESS_INFORMATION pe;
327 BOOL status;
328 HANDLE h;
329 char filetorun[MAX_PATH];
330
331   WCMD_parse (command, quals, param1, param2);  /* Quick way to get the filename */
332   if (strpbrk (param1, "\\:") == NULL) {        /* No explicit path given */
333     if ((strchr (param1, '.') == NULL) || (strstr (param1, ".bat") != NULL)) {
334       if (SearchPath (NULL, param1, ".bat", sizeof(filetorun), filetorun, NULL)) {
335         WCMD_batch (filetorun, command, 0);
336         return;
337       }
338     }
339   }
340   else {                                        /* Explicit path given */
341     if (strstr (param1, ".bat") != NULL) {
342       WCMD_batch (param1, command, 0);
343       return;
344     }
345     if (strchr (param1, '.') == NULL) {
346       strcpy (filetorun, param1);
347       strcat (filetorun, ".bat");
348       h = CreateFile (filetorun, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
349       if (h != INVALID_HANDLE_VALUE) {
350         CloseHandle (h);
351         WCMD_batch (param1, command, 0);
352         return;
353       }
354     }
355   }
356
357         /* No batch file found, assume executable */
358
359   ZeroMemory (&st, sizeof(STARTUPINFO));
360   st.cb = sizeof(STARTUPINFO);
361   status = CreateProcess (NULL, command, NULL, NULL, FALSE,
362                  0, NULL, NULL, &st, &pe);
363   if (!status) {
364     WCMD_print_error ();
365   }
366 }
367
368 /******************************************************************************
369  * WCMD_show_prompt
370  *
371  *      Display the prompt on STDout
372  *
373  */
374
375 void WCMD_show_prompt () {
376
377 int status;
378 char out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
379 char *p, *q;
380
381   status = GetEnvironmentVariable ("PROMPT", prompt_string, sizeof(prompt_string));
382   if ((status == 0) || (status > sizeof(prompt_string))) {
383     lstrcpy (prompt_string, "$N$G");
384   }
385   p = prompt_string;
386   q = out_string;
387   *q = '\0';
388   while (*p != '\0') {
389     if (*p != '$') {
390       *q++ = *p++;
391       *q = '\0';
392     }
393     else {
394       p++;
395       switch (toupper(*p)) {
396         case '$':
397           *q++ = '$';
398           break;
399         case 'B':
400           *q++ = '|';
401           break;
402         case 'D':
403           GetDateFormat (LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
404           while (*q) q++;
405           break;
406         case 'E':
407           *q++ = '\E';
408           break;
409         case 'G':
410           *q++ = '>';
411           break;
412         case 'L':
413           *q++ = '<';
414           break;
415         case 'N':
416           status = GetCurrentDirectory (sizeof(curdir), curdir);
417           if (status) {
418             *q++ = curdir[0];
419           }
420           break;
421         case 'P':
422           status = GetCurrentDirectory (sizeof(curdir), curdir);
423           if (status) {
424             lstrcat (q, curdir);
425             while (*q) q++;
426           }
427           break;
428         case 'Q':
429           *q++ = '=';
430           break;
431         case 'T':
432           GetTimeFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
433           while (*q) q++;
434           break;
435         case '_':
436           *q++ = '\n';
437           break;
438       }
439       p++;
440       *q = '\0';
441     }
442   }
443   WCMD_output (out_string);
444 }
445
446 /****************************************************************************
447  * WCMD_print_error
448  *
449  * Print the message for GetLastError - not much use yet as Wine doesn't have
450  * the messages available, so we show meaningful messages for the most likely.
451  */
452
453 void WCMD_print_error () {
454 LPVOID lpMsgBuf;
455 DWORD error_code;
456
457   error_code = GetLastError ();
458   switch (error_code) {
459     case ERROR_FILE_NOT_FOUND:
460       WCMD_output ("File Not Found\n");
461       break;
462     case ERROR_PATH_NOT_FOUND:
463       WCMD_output ("Path Not Found\n");
464       break;
465     default:
466       FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
467                         NULL, error_code,
468                         MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),
469                         (LPTSTR) &lpMsgBuf, 0, NULL);
470       WCMD_output (lpMsgBuf);
471       LocalFree ((HLOCAL)lpMsgBuf);
472   }
473   return;
474 }
475
476 /*******************************************************************
477  * WCMD_parse - parse a command into parameters and qualifiers.
478  *
479  *      On exit, all qualifiers are concatenated into q, the first string
480  *      not beginning with "/" is in p1 and the
481  *      second in p2. Any subsequent non-qualifier strings are lost.
482  *      Parameters in quotes are handled.
483  */
484
485 void WCMD_parse (char *s, char *q, char *p1, char *p2) {
486
487 int p = 0;
488
489   *q = *p1 = *p2 = '\0';
490   while (TRUE) {
491     switch (*s) {
492       case '/':
493         *q++ = *s++;
494         while ((*s != '\0') && (*s != ' ') && *s != '/') {
495           *q++ = toupper (*s++);
496         }
497         *q = '\0';
498         break;
499       case ' ':
500         s++;
501         break;
502       case '"':
503         s++;
504         while ((*s != '\0') && (*s != '"')) {
505           if (p == 0) *p1++ = *s++;
506           else if (p == 1) *p2++ = *s++;
507           else s++;
508         }
509         if (p == 0) *p1 = '\0';
510         if (p == 1) *p2 = '\0';
511         p++;
512         if (*s == '"') s++;
513         break;
514       case '\0':
515         return;
516       default:
517         while ((*s != '\0') && (*s != ' ') && (*s != '/')) {
518           if (p == 0) *p1++ = *s++;
519           else if (p == 1) *p2++ = *s++;
520           else s++;
521         }
522         if (p == 0) *p1 = '\0';
523         if (p == 1) *p2 = '\0';
524         p++;
525     }
526   }
527 }
528
529 /*******************************************************************
530  * WCMD_output - send output to current standard output device.
531  *
532  */
533
534 void WCMD_output (char *format, ...) {
535
536 va_list ap;
537 char string[1024];
538 DWORD count;
539
540   va_start(ap,format);
541   vsprintf (string, format, ap);
542   WriteFile (GetStdHandle(STD_OUTPUT_HANDLE), string, lstrlen(string), &count, NULL);
543   va_end(ap);
544 }
545
546
547 /*      Remove leading spaces from a string. Return a pointer to the first
548  *      non-space character. Does not modify the input string
549  */
550
551 char *WCMD_strtrim_leading_spaces (char *string) {
552
553 char *ptr;
554
555   ptr = string;
556   while (*ptr == ' ') ptr++;
557   return ptr;
558 }
559
560 /*      Remove trailing spaces from a string. This routine modifies the input
561  *      string by placing a null after the last non-space character
562  */
563
564 void WCMD_strtrim_trailing_spaces (char *string) {
565
566 char *ptr;
567
568   ptr = string + lstrlen (string) - 1;
569   while ((*ptr == ' ') && (ptr >= string)) {
570     *ptr = '\0';
571     ptr--;
572   }
573 }