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