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