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