Added a few more Unicode digits from Unicode version 4.1.
[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 "ntstatus.h"
28 #include "wcmd.h"
29
30 const char * const inbuilt[] = {"ATTRIB", "CALL", "CD", "CHDIR", "CLS", "COPY", "CTTY",
31                 "DATE", "DEL", "DIR", "ECHO", "ERASE", "FOR", "GOTO",
32                 "HELP", "IF", "LABEL", "MD", "MKDIR", "MOVE", "PATH", "PAUSE",
33                 "PROMPT", "REM", "REN", "RENAME", "RD", "RMDIR", "SET", "SHIFT",
34                 "TIME", "TITLE", "TYPE", "VERIFY", "VER", "VOL", 
35                 "ENDLOCAL", "SETLOCAL", "EXIT" };
36
37 HINSTANCE hinst;
38 DWORD errorlevel;
39 int echo_mode = 1, verify_mode = 0;
40 const char nyi[] = "Not Yet Implemented\n\n";
41 const char newline[] = "\n";
42 const char version_string[] = "WCMD Version 0.17\n\n";
43 const char anykey[] = "Press Return key to continue: ";
44 char quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
45 BATCH_CONTEXT *context = NULL;
46 static HANDLE old_stdin = INVALID_HANDLE_VALUE, old_stdout = INVALID_HANDLE_VALUE;
47
48 /*****************************************************************************
49  * Main entry point. This is a console application so we have a main() not a
50  * winmain().
51  */
52
53 int main (int argc, char *argv[])
54 {
55   char string[1024];
56   char* cmd=NULL;
57   DWORD count;
58   HANDLE h;
59   int opt_c, opt_k, opt_q;
60
61   argv++;
62   opt_c=opt_k=opt_q=0;
63   while (*argv!=NULL)
64   {
65       if (lstrcmpi(*argv,"/c")==0) {
66           opt_c=1;
67           argv++;
68           break;
69       } else if (lstrcmpi(*argv,"/q")==0) {
70           opt_q=1;
71       } else if (lstrcmpi(*argv,"/k")==0) {
72           opt_k=1;
73           argv++;
74           break;
75       } else if (lstrcmpi(*argv,"/t")==0 || lstrcmpi(*argv,"/x")==0 ||
76                  lstrcmpi(*argv,"/y")==0) {
77           /* Ignored for compatibility with Windows */
78       } else {
79           break;
80       }
81       argv++;
82   }
83
84   if (opt_q) {
85     WCMD_echo("OFF");
86   }
87
88   if (opt_c || opt_k) {
89       int len;
90       char** arg;
91       char* p;
92
93       /* Build the command to execute */
94       len = 0;
95       for (arg = argv; *arg; arg++)
96       {
97           int has_space,bcount;
98           char* a;
99
100           has_space=0;
101           bcount=0;
102           a=*arg;
103           if( !*a ) has_space=1;
104           while (*a!='\0') {
105               if (*a=='\\') {
106                   bcount++;
107               } else {
108                   if (*a==' ' || *a=='\t') {
109                       has_space=1;
110                   } else if (*a=='"') {
111                       /* doubling of '\' preceding a '"',
112                        * plus escaping of said '"'
113                        */
114                       len+=2*bcount+1;
115                   }
116                   bcount=0;
117               }
118               a++;
119           }
120           len+=(a-*arg)+1 /* for the separating space */;
121           if (has_space)
122               len+=2; /* for the quotes */
123       }
124
125       cmd = HeapAlloc(GetProcessHeap(), 0, len);
126       if (!cmd)
127           exit(1);
128
129       p = cmd;
130       for (arg = argv; *arg; arg++)
131       {
132           int has_space,has_quote;
133           char* a;
134
135           /* Check for quotes and spaces in this argument */
136           has_space=has_quote=0;
137           a=*arg;
138           if( !*a ) has_space=1;
139           while (*a!='\0') {
140               if (*a==' ' || *a=='\t') {
141                   has_space=1;
142                   if (has_quote)
143                       break;
144               } else if (*a=='"') {
145                   has_quote=1;
146                   if (has_space)
147                       break;
148               }
149               a++;
150           }
151
152           /* Now transfer it to the command line */
153           if (has_space)
154               *p++='"';
155           if (has_quote) {
156               int bcount;
157               char* a;
158
159               bcount=0;
160               a=*arg;
161               while (*a!='\0') {
162                   if (*a=='\\') {
163                       *p++=*a;
164                       bcount++;
165                   } else {
166                       if (*a=='"') {
167                           int i;
168
169                           /* Double all the '\\' preceding this '"', plus one */
170                           for (i=0;i<=bcount;i++)
171                               *p++='\\';
172                           *p++='"';
173                       } else {
174                           *p++=*a;
175                       }
176                       bcount=0;
177                   }
178                   a++;
179               }
180           } else {
181               strcpy(p,*arg);
182               p+=strlen(*arg);
183           }
184           if (has_space)
185               *p++='"';
186           *p++=' ';
187       }
188       if (p > cmd)
189           p--;  /* remove last space */
190       *p = '\0';
191   }
192
193   if (opt_c) {
194       /* If we do a "wcmd /c command", we don't want to allocate a new
195        * console since the command returns immediately. Rather, we use
196        * the currently allocated input and output handles. This allows
197        * us to pipe to and read from the command interpreter.
198        */
199       if (strchr(cmd,'|') != NULL)
200           WCMD_pipe(cmd);
201       else
202           WCMD_process_command(cmd);
203       HeapFree(GetProcessHeap(), 0, cmd);
204       return 0;
205   }
206
207   SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
208                  ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
209   SetConsoleTitle("Wine Command Prompt");
210
211   if (opt_k) {
212       WCMD_process_command(cmd);
213       HeapFree(GetProcessHeap(), 0, cmd);
214   }
215
216 /*
217  *      If there is an AUTOEXEC.BAT file, try to execute it.
218  */
219
220   GetFullPathName ("\\autoexec.bat", sizeof(string), string, NULL);
221   h = CreateFile (string, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
222   if (h != INVALID_HANDLE_VALUE) {
223     CloseHandle (h);
224 #if 0
225     WCMD_batch_command (string);
226 #endif
227   }
228
229 /*
230  *      Loop forever getting commands and executing them.
231  */
232
233   WCMD_version ();
234   while (TRUE) {
235     WCMD_show_prompt ();
236     ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string), &count, NULL);
237     if (count > 1) {
238       string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
239       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
240       if (lstrlen (string) != 0) {
241         if (strchr(string,'|') != NULL) {
242           WCMD_pipe (string);
243         }
244         else {
245           WCMD_process_command (string);
246         }
247       }
248     }
249   }
250 }
251
252
253 /*****************************************************************************
254  * Process one command. If the command is EXIT this routine does not return.
255  * We will recurse through here executing batch files.
256  */
257
258
259 void WCMD_process_command (char *command)
260 {
261     char *cmd, *p;
262     int status, i, len;
263     DWORD count, creationDisposition;
264     HANDLE h;
265     char *whichcmd;
266     SECURITY_ATTRIBUTES sa;
267
268 /*
269  *      Expand up environment variables.
270  */
271     len = ExpandEnvironmentStrings (command, NULL, 0);
272     cmd = HeapAlloc( GetProcessHeap(), 0, len );
273     status = ExpandEnvironmentStrings (command, cmd, len);
274     if (!status) {
275       WCMD_print_error ();
276       HeapFree( GetProcessHeap(), 0, cmd );
277       return;
278     }
279
280 /*
281  *      Changing default drive has to be handled as a special case.
282  */
283
284     if ((cmd[1] == ':') && IsCharAlpha (cmd[0]) && (strlen(cmd) == 2)) {
285       status = SetCurrentDirectory (cmd);
286       if (!status) WCMD_print_error ();
287       HeapFree( GetProcessHeap(), 0, cmd );
288       return;
289     }
290
291     /* Don't issue newline WCMD_output (newline);           @JED*/
292
293     sa.nLength = sizeof(sa);
294     sa.lpSecurityDescriptor = NULL;
295     sa.bInheritHandle = TRUE;
296 /*
297  *      Redirect stdin and/or stdout if required.
298  */
299
300     if ((p = strchr(cmd,'<')) != NULL) {
301       h = CreateFile (WCMD_parameter (++p, 0, NULL), GENERIC_READ, FILE_SHARE_READ, &sa, OPEN_EXISTING,
302                 FILE_ATTRIBUTE_NORMAL, NULL);
303       if (h == INVALID_HANDLE_VALUE) {
304         WCMD_print_error ();
305         HeapFree( GetProcessHeap(), 0, cmd );
306         return;
307       }
308       old_stdin = GetStdHandle (STD_INPUT_HANDLE);
309       SetStdHandle (STD_INPUT_HANDLE, h);
310     }
311     if ((p = strchr(cmd,'>')) != NULL) {
312       *p++ = '\0';
313       if ('>' == *p) {
314         creationDisposition = OPEN_ALWAYS;
315         p++;
316       }
317       else {
318         creationDisposition = CREATE_ALWAYS;
319       }
320       h = CreateFile (WCMD_parameter (p, 0, NULL), GENERIC_WRITE, 0, &sa, creationDisposition,
321                 FILE_ATTRIBUTE_NORMAL, NULL);
322       if (h == INVALID_HANDLE_VALUE) {
323         WCMD_print_error ();
324         HeapFree( GetProcessHeap(), 0, cmd );
325         return;
326       }
327       if (SetFilePointer (h, 0, NULL, FILE_END) ==
328           INVALID_SET_FILE_POINTER) {
329         WCMD_print_error ();
330       }
331       old_stdout = GetStdHandle (STD_OUTPUT_HANDLE);
332       SetStdHandle (STD_OUTPUT_HANDLE, h);
333     }
334     if ((p = strchr(cmd,'<')) != NULL) *p = '\0';
335
336 /*
337  * Strip leading whitespaces, and a '@' if supplied
338  */
339     whichcmd = WCMD_strtrim_leading_spaces(cmd);
340     if (whichcmd[0] == '@') whichcmd++;
341
342 /*
343  *      Check if the command entered is internal. If it is, pass the rest of the
344  *      line down to the command. If not try to run a program.
345  */
346
347     count = 0;
348     while (IsCharAlphaNumeric(whichcmd[count])) {
349       count++;
350     }
351     for (i=0; i<=WCMD_EXIT; i++) {
352       if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
353           whichcmd, count, inbuilt[i], -1) == 2) break;
354     }
355     p = WCMD_strtrim_leading_spaces (&whichcmd[count]);
356     WCMD_parse (p, quals, param1, param2);
357     switch (i) {
358
359       case WCMD_ATTRIB:
360         WCMD_setshow_attrib ();
361         break;
362       case WCMD_CALL:
363         WCMD_batch (param1, p, 1);
364         break;
365       case WCMD_CD:
366       case WCMD_CHDIR:
367         WCMD_setshow_default ();
368         break;
369       case WCMD_CLS:
370         WCMD_clear_screen ();
371         break;
372       case WCMD_COPY:
373         WCMD_copy ();
374         break;
375       case WCMD_CTTY:
376         WCMD_change_tty ();
377         break;
378       case WCMD_DATE:
379         WCMD_setshow_date ();
380         break;
381       case WCMD_DEL:
382       case WCMD_ERASE:
383         WCMD_delete (0);
384         break;
385       case WCMD_DIR:
386         WCMD_directory ();
387         break;
388       case WCMD_ECHO:
389         WCMD_echo(&whichcmd[count]);
390         break;
391       case WCMD_FOR:
392         WCMD_for (p);
393         break;
394       case WCMD_GOTO:
395         WCMD_goto ();
396         break;
397       case WCMD_HELP:
398         WCMD_give_help (p);
399         break;
400       case WCMD_IF:
401         WCMD_if (p);
402         break;
403       case WCMD_LABEL:
404         WCMD_volume (1, p);
405         break;
406       case WCMD_MD:
407       case WCMD_MKDIR:
408         WCMD_create_dir ();
409         break;
410       case WCMD_MOVE:
411         WCMD_move ();
412         break;
413       case WCMD_PATH:
414         WCMD_setshow_path (p);
415         break;
416       case WCMD_PAUSE:
417         WCMD_pause ();
418         break;
419       case WCMD_PROMPT:
420         WCMD_setshow_prompt ();
421         break;
422       case WCMD_REM:
423         break;
424       case WCMD_REN:
425       case WCMD_RENAME:
426         WCMD_rename ();
427         break;
428       case WCMD_RD:
429       case WCMD_RMDIR:
430         WCMD_remove_dir ();
431         break;
432       case WCMD_SETLOCAL:
433         WCMD_setlocal(p);
434         break;
435       case WCMD_ENDLOCAL:
436         WCMD_endlocal();
437         break;
438       case WCMD_SET:
439         WCMD_setshow_env (p);
440         break;
441       case WCMD_SHIFT:
442         WCMD_shift ();
443         break;
444       case WCMD_TIME:
445         WCMD_setshow_time ();
446         break;
447       case WCMD_TITLE:
448         if (strlen(&whichcmd[count]) > 0)
449           WCMD_title(&whichcmd[count+1]);
450         break;
451       case WCMD_TYPE:
452         WCMD_type ();
453         break;
454       case WCMD_VER:
455         WCMD_version ();
456         break;
457       case WCMD_VERIFY:
458         WCMD_verify (p);
459         break;
460       case WCMD_VOL:
461         WCMD_volume (0, p);
462         break;
463       case WCMD_EXIT:
464         ExitProcess (0);
465       default:
466         WCMD_run_program (whichcmd);
467     }
468     HeapFree( GetProcessHeap(), 0, cmd );
469     if (old_stdin != INVALID_HANDLE_VALUE) {
470       CloseHandle (GetStdHandle (STD_INPUT_HANDLE));
471       SetStdHandle (STD_INPUT_HANDLE, old_stdin);
472       old_stdin = INVALID_HANDLE_VALUE;
473     }
474     if (old_stdout != INVALID_HANDLE_VALUE) {
475       CloseHandle (GetStdHandle (STD_OUTPUT_HANDLE));
476       SetStdHandle (STD_OUTPUT_HANDLE, old_stdout);
477       old_stdout = INVALID_HANDLE_VALUE;
478     }
479 }
480
481 static void init_msvcrt_io_block(STARTUPINFO* st)
482 {
483     STARTUPINFO st_p;
484     /* fetch the parent MSVCRT info block if any, so that the child can use the
485      * same handles as its grand-father
486      */
487     st_p.cb = sizeof(STARTUPINFO);
488     GetStartupInfo(&st_p);
489     st->cbReserved2 = st_p.cbReserved2;
490     st->lpReserved2 = st_p.lpReserved2;
491     if (st_p.cbReserved2 && st_p.lpReserved2 &&
492         (old_stdin != INVALID_HANDLE_VALUE || old_stdout != INVALID_HANDLE_VALUE))
493     {
494         /* Override the entries for fd 0,1,2 if we happened
495          * to change those std handles (this depends on the way wcmd sets
496          * it's new input & output handles)
497          */
498         size_t sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
499         BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
500         if (ptr)
501         {
502             unsigned num = *(unsigned*)st_p.lpReserved2;
503             char* flags = (char*)(ptr + sizeof(unsigned));
504             HANDLE* handles = (HANDLE*)(flags + num * sizeof(char));
505
506             memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
507             st->cbReserved2 = sz;
508             st->lpReserved2 = ptr;
509
510 #define WX_OPEN 0x01    /* see dlls/msvcrt/file.c */
511             if (num <= 0 || (flags[0] & WX_OPEN))
512             {
513                 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
514                 flags[0] |= WX_OPEN;
515             }
516             if (num <= 1 || (flags[1] & WX_OPEN))
517             {
518                 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
519                 flags[1] |= WX_OPEN;
520             }
521             if (num <= 2 || (flags[2] & WX_OPEN))
522             {
523                 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
524                 flags[2] |= WX_OPEN;
525             }
526 #undef WX_OPEN
527         }
528     }
529 }
530
531 /******************************************************************************
532  * WCMD_run_program
533  *
534  *      Execute a command line as an external program. If no extension given then
535  *      precedence is given to .BAT files. Must allow recursion.
536  *
537  *      FIXME: Case sensitivity in suffixes!
538  */
539
540 void WCMD_run_program (char *command) {
541
542 STARTUPINFO st;
543 PROCESS_INFORMATION pe;
544 SHFILEINFO psfi;
545 DWORD console;
546 BOOL status;
547 HANDLE h;
548 HINSTANCE hinst;
549 char filetorun[MAX_PATH];
550
551   WCMD_parse (command, quals, param1, param2);  /* Quick way to get the filename */
552   if (!(*param1) && !(*param2))
553     return;
554   if (strpbrk (param1, "/\\:") == NULL) {  /* No explicit path given */
555     char *ext = strrchr( param1, '.' );
556     if (!ext || !strcasecmp( ext, ".bat"))
557     {
558       if (SearchPath (NULL, param1, ".bat", sizeof(filetorun), filetorun, NULL)) {
559         WCMD_batch (filetorun, command, 0);
560         return;
561       }
562     }
563     if (!ext || !strcasecmp( ext, ".cmd"))
564     {
565       if (SearchPath (NULL, param1, ".cmd", sizeof(filetorun), filetorun, NULL)) {
566         WCMD_batch (filetorun, command, 0);
567         return;
568       }
569     }
570   }
571   else {                                        /* Explicit path given */
572     char *ext = strrchr( param1, '.' );
573     if (ext && (!strcasecmp( ext, ".bat" ) || !strcasecmp( ext, ".cmd" )))
574     {
575       WCMD_batch (param1, command, 0);
576       return;
577     }
578
579     if (ext && strpbrk( ext, "/\\:" )) ext = NULL;
580     if (!ext)
581     {
582       strcpy (filetorun, param1);
583       strcat (filetorun, ".bat");
584       h = CreateFile (filetorun, GENERIC_READ, FILE_SHARE_READ,
585                       NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
586       if (h != INVALID_HANDLE_VALUE) {
587         CloseHandle (h);
588         WCMD_batch (param1, command, 0);
589         return;
590       }
591     }
592   }
593
594         /* No batch file found, assume executable */
595
596   hinst = FindExecutable (param1, NULL, filetorun);
597   if ((int)hinst < 32) {
598     WCMD_print_error ();
599     return;
600   }
601   console = SHGetFileInfo (filetorun, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
602   ZeroMemory (&st, sizeof(STARTUPINFO));
603   st.cb = sizeof(STARTUPINFO);
604   init_msvcrt_io_block(&st);
605
606   status = CreateProcess (NULL, command, NULL, NULL, TRUE, 
607                           0, NULL, NULL, &st, &pe);
608   if (!status) {
609     WCMD_print_error ();
610     return;
611   }
612   if (!console) errorlevel = 0;
613   else
614   {
615       if (!HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
616       GetExitCodeProcess (pe.hProcess, &errorlevel);
617       if (errorlevel == STILL_ACTIVE) errorlevel = 0;
618   }
619   CloseHandle(pe.hProcess);
620   CloseHandle(pe.hThread);
621 }
622
623 /******************************************************************************
624  * WCMD_show_prompt
625  *
626  *      Display the prompt on STDout
627  *
628  */
629
630 void WCMD_show_prompt (void) {
631
632 int status;
633 char out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
634 char *p, *q;
635
636   status = GetEnvironmentVariable ("PROMPT", prompt_string, sizeof(prompt_string));
637   if ((status == 0) || (status > sizeof(prompt_string))) {
638     lstrcpy (prompt_string, "$P$G");
639   }
640   p = prompt_string;
641   q = out_string;
642   *q = '\0';
643   while (*p != '\0') {
644     if (*p != '$') {
645       *q++ = *p++;
646       *q = '\0';
647     }
648     else {
649       p++;
650       switch (toupper(*p)) {
651         case '$':
652           *q++ = '$';
653           break;
654         case 'B':
655           *q++ = '|';
656           break;
657         case 'D':
658           GetDateFormat (LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
659           while (*q) q++;
660           break;
661         case 'E':
662           *q++ = '\E';
663           break;
664         case 'G':
665           *q++ = '>';
666           break;
667         case 'L':
668           *q++ = '<';
669           break;
670         case 'N':
671           status = GetCurrentDirectory (sizeof(curdir), curdir);
672           if (status) {
673             *q++ = curdir[0];
674           }
675           break;
676         case 'P':
677           status = GetCurrentDirectory (sizeof(curdir), curdir);
678           if (status) {
679             lstrcat (q, curdir);
680             while (*q) q++;
681           }
682           break;
683         case 'Q':
684           *q++ = '=';
685           break;
686         case 'T':
687           GetTimeFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
688           while (*q) q++;
689           break;
690        case 'V':
691            lstrcat (q, version_string);
692            while (*q) q++;
693          break;
694         case '_':
695           *q++ = '\n';
696           break;
697       }
698       p++;
699       *q = '\0';
700     }
701   }
702   WCMD_output (out_string);
703 }
704
705 /****************************************************************************
706  * WCMD_print_error
707  *
708  * Print the message for GetLastError
709  */
710
711 void WCMD_print_error (void) {
712 LPVOID lpMsgBuf;
713 DWORD error_code;
714 int status;
715
716   error_code = GetLastError ();
717   status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
718                         NULL, error_code, 0, (LPTSTR) &lpMsgBuf, 0, NULL);
719   if (!status) {
720     WCMD_output ("FIXME: Cannot display message for error %d, status %d\n",
721                         error_code, GetLastError());
722     return;
723   }
724   WCMD_output (lpMsgBuf);
725   LocalFree ((HLOCAL)lpMsgBuf);
726   WCMD_output (newline);
727   return;
728 }
729
730 /*******************************************************************
731  * WCMD_parse - parse a command into parameters and qualifiers.
732  *
733  *      On exit, all qualifiers are concatenated into q, the first string
734  *      not beginning with "/" is in p1 and the
735  *      second in p2. Any subsequent non-qualifier strings are lost.
736  *      Parameters in quotes are handled.
737  */
738
739 void WCMD_parse (char *s, char *q, char *p1, char *p2) {
740
741 int p = 0;
742
743   *q = *p1 = *p2 = '\0';
744   while (TRUE) {
745     switch (*s) {
746       case '/':
747         *q++ = *s++;
748         while ((*s != '\0') && (*s != ' ') && *s != '/') {
749           *q++ = toupper (*s++);
750         }
751         *q = '\0';
752         break;
753       case ' ':
754       case '\t':
755         s++;
756         break;
757       case '"':
758         s++;
759         while ((*s != '\0') && (*s != '"')) {
760           if (p == 0) *p1++ = *s++;
761           else if (p == 1) *p2++ = *s++;
762           else s++;
763         }
764         if (p == 0) *p1 = '\0';
765         if (p == 1) *p2 = '\0';
766         p++;
767         if (*s == '"') s++;
768         break;
769       case '\0':
770         return;
771       default:
772         while ((*s != '\0') && (*s != ' ') && (*s != '\t')) {
773           if (p == 0) *p1++ = *s++;
774           else if (p == 1) *p2++ = *s++;
775           else s++;
776         }
777         if (p == 0) *p1 = '\0';
778         if (p == 1) *p2 = '\0';
779         p++;
780     }
781   }
782 }
783
784 /*******************************************************************
785  * WCMD_output - send output to current standard output device.
786  *
787  */
788
789 void WCMD_output (const char *format, ...) {
790
791 va_list ap;
792 char string[1024];
793
794   va_start(ap,format);
795   vsprintf (string, format, ap);
796   va_end(ap);
797   WCMD_output_asis(string);
798 }
799
800
801 static int line_count;
802 static int max_height;
803 static BOOL paged_mode;
804
805 void WCMD_enter_paged_mode(void)
806 {
807 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
808
809   if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo))
810     max_height = consoleInfo.dwSize.Y;
811   else
812     max_height = 25;
813   paged_mode = TRUE;
814   line_count = 5; /* keep 5 lines from previous output */
815 }
816
817 void WCMD_leave_paged_mode(void)
818 {
819   paged_mode = FALSE;
820 }
821
822 /*******************************************************************
823  * WCMD_output_asis - send output to current standard output device.
824  *        without formatting eg. when message contains '%'
825  */
826
827 void WCMD_output_asis (const char *message) {
828   DWORD count;
829   char* ptr;
830   char string[1024];
831
832   if (paged_mode) {
833     do {
834       if ((ptr = strchr(message, '\n')) != NULL) ptr++;
835       WriteFile (GetStdHandle(STD_OUTPUT_HANDLE), message, 
836                  (ptr) ? ptr - message : lstrlen(message), &count, NULL);
837       if (ptr) {
838         if (++line_count >= max_height - 1) {
839           line_count = 0;
840           WCMD_output_asis (anykey);
841           ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string), &count, NULL);
842         }
843       }
844     } while ((message = ptr) != NULL);
845   } else {
846       WriteFile (GetStdHandle(STD_OUTPUT_HANDLE), message, lstrlen(message), &count, NULL);
847   }
848 }
849
850
851 /***************************************************************************
852  * WCMD_strtrim_leading_spaces
853  *
854  *      Remove leading spaces from a string. Return a pointer to the first
855  *      non-space character. Does not modify the input string
856  */
857
858 char *WCMD_strtrim_leading_spaces (char *string) {
859
860 char *ptr;
861
862   ptr = string;
863   while (*ptr == ' ') ptr++;
864   return ptr;
865 }
866
867 /*************************************************************************
868  * WCMD_strtrim_trailing_spaces
869  *
870  *      Remove trailing spaces from a string. This routine modifies the input
871  *      string by placing a null after the last non-space character
872  */
873
874 void WCMD_strtrim_trailing_spaces (char *string) {
875
876 char *ptr;
877
878   ptr = string + lstrlen (string) - 1;
879   while ((*ptr == ' ') && (ptr >= string)) {
880     *ptr = '\0';
881     ptr--;
882   }
883 }
884
885 /*************************************************************************
886  * WCMD_pipe
887  *
888  *      Handle pipes within a command - the DOS way using temporary files.
889  */
890
891 void WCMD_pipe (char *command) {
892
893 char *p;
894 char temp_path[MAX_PATH], temp_file[MAX_PATH], temp_file2[MAX_PATH], temp_cmd[1024];
895
896   GetTempPath (sizeof(temp_path), temp_path);
897   GetTempFileName (temp_path, "WCMD", 0, temp_file);
898   p = strchr(command, '|');
899   *p++ = '\0';
900   wsprintf (temp_cmd, "%s > %s", command, temp_file);
901   WCMD_process_command (temp_cmd);
902   command = p;
903   while ((p = strchr(command, '|'))) {
904     *p++ = '\0';
905     GetTempFileName (temp_path, "WCMD", 0, temp_file2);
906     wsprintf (temp_cmd, "%s < %s > %s", command, temp_file, temp_file2);
907     WCMD_process_command (temp_cmd);
908     DeleteFile (temp_file);
909     lstrcpy (temp_file, temp_file2);
910     command = p;
911   }
912   wsprintf (temp_cmd, "%s < %s", command, temp_file);
913   WCMD_process_command (temp_cmd);
914   DeleteFile (temp_file);
915 }