user: Use computed icon rectangle if SS_CENTERIMAGE style is set.
[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., 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", "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[] = "WCMD 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 0;
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;
305     int status, i, len;
306     DWORD count, creationDisposition;
307     HANDLE h;
308     char *whichcmd;
309     SECURITY_ATTRIBUTES sa;
310
311 /*
312  *      Expand up environment variables.
313  */
314     len = ExpandEnvironmentStrings (command, NULL, 0);
315     cmd = HeapAlloc( GetProcessHeap(), 0, len );
316     status = ExpandEnvironmentStrings (command, cmd, len);
317     if (!status) {
318       WCMD_print_error ();
319       HeapFree( GetProcessHeap(), 0, cmd );
320       return;
321     }
322
323 /*
324  *      Changing default drive has to be handled as a special case.
325  */
326
327     if ((cmd[1] == ':') && IsCharAlpha (cmd[0]) && (strlen(cmd) == 2)) {
328       status = SetCurrentDirectory (cmd);
329       if (!status) WCMD_print_error ();
330       HeapFree( GetProcessHeap(), 0, cmd );
331       return;
332     }
333
334     /* Don't issue newline WCMD_output (newline);           @JED*/
335
336     sa.nLength = sizeof(sa);
337     sa.lpSecurityDescriptor = NULL;
338     sa.bInheritHandle = TRUE;
339 /*
340  *      Redirect stdin and/or stdout if required.
341  */
342
343     if ((p = strchr(cmd,'<')) != NULL) {
344       h = CreateFile (WCMD_parameter (++p, 0, NULL), GENERIC_READ, FILE_SHARE_READ, &sa, OPEN_EXISTING,
345                 FILE_ATTRIBUTE_NORMAL, NULL);
346       if (h == INVALID_HANDLE_VALUE) {
347         WCMD_print_error ();
348         HeapFree( GetProcessHeap(), 0, cmd );
349         return;
350       }
351       old_stdin = GetStdHandle (STD_INPUT_HANDLE);
352       SetStdHandle (STD_INPUT_HANDLE, h);
353     }
354     if ((p = strchr(cmd,'>')) != NULL) {
355       *p++ = '\0';
356       if ('>' == *p) {
357         creationDisposition = OPEN_ALWAYS;
358         p++;
359       }
360       else {
361         creationDisposition = CREATE_ALWAYS;
362       }
363       h = CreateFile (WCMD_parameter (p, 0, NULL), GENERIC_WRITE, 0, &sa, creationDisposition,
364                 FILE_ATTRIBUTE_NORMAL, NULL);
365       if (h == INVALID_HANDLE_VALUE) {
366         WCMD_print_error ();
367         HeapFree( GetProcessHeap(), 0, cmd );
368         return;
369       }
370       if (SetFilePointer (h, 0, NULL, FILE_END) ==
371           INVALID_SET_FILE_POINTER) {
372         WCMD_print_error ();
373       }
374       old_stdout = GetStdHandle (STD_OUTPUT_HANDLE);
375       SetStdHandle (STD_OUTPUT_HANDLE, h);
376     }
377     if ((p = strchr(cmd,'<')) != NULL) *p = '\0';
378
379 /*
380  * Strip leading whitespaces, and a '@' if supplied
381  */
382     whichcmd = WCMD_strtrim_leading_spaces(cmd);
383     if (whichcmd[0] == '@') whichcmd++;
384
385 /*
386  *      Check if the command entered is internal. If it is, pass the rest of the
387  *      line down to the command. If not try to run a program.
388  */
389
390     count = 0;
391     while (IsCharAlphaNumeric(whichcmd[count])) {
392       count++;
393     }
394     for (i=0; i<=WCMD_EXIT; i++) {
395       if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
396           whichcmd, count, inbuilt[i], -1) == 2) break;
397     }
398     p = WCMD_strtrim_leading_spaces (&whichcmd[count]);
399     WCMD_parse (p, quals, param1, param2);
400     switch (i) {
401
402       case WCMD_ATTRIB:
403         WCMD_setshow_attrib ();
404         break;
405       case WCMD_CALL:
406         WCMD_run_program (p, 1);
407         break;
408       case WCMD_CD:
409       case WCMD_CHDIR:
410         WCMD_setshow_default ();
411         break;
412       case WCMD_CLS:
413         WCMD_clear_screen ();
414         break;
415       case WCMD_COPY:
416         WCMD_copy ();
417         break;
418       case WCMD_CTTY:
419         WCMD_change_tty ();
420         break;
421       case WCMD_DATE:
422         WCMD_setshow_date ();
423         break;
424       case WCMD_DEL:
425       case WCMD_ERASE:
426         WCMD_delete (0);
427         break;
428       case WCMD_DIR:
429         WCMD_directory ();
430         break;
431       case WCMD_ECHO:
432         WCMD_echo(&whichcmd[count]);
433         break;
434       case WCMD_FOR:
435         WCMD_for (p);
436         break;
437       case WCMD_GOTO:
438         WCMD_goto ();
439         break;
440       case WCMD_HELP:
441         WCMD_give_help (p);
442         break;
443       case WCMD_IF:
444         WCMD_if (p);
445         break;
446       case WCMD_LABEL:
447         WCMD_volume (1, p);
448         break;
449       case WCMD_MD:
450       case WCMD_MKDIR:
451         WCMD_create_dir ();
452         break;
453       case WCMD_MOVE:
454         WCMD_move ();
455         break;
456       case WCMD_PATH:
457         WCMD_setshow_path (p);
458         break;
459       case WCMD_PAUSE:
460         WCMD_pause ();
461         break;
462       case WCMD_PROMPT:
463         WCMD_setshow_prompt ();
464         break;
465       case WCMD_REM:
466         break;
467       case WCMD_REN:
468       case WCMD_RENAME:
469         WCMD_rename ();
470         break;
471       case WCMD_RD:
472       case WCMD_RMDIR:
473         WCMD_remove_dir ();
474         break;
475       case WCMD_SETLOCAL:
476         WCMD_setlocal(p);
477         break;
478       case WCMD_ENDLOCAL:
479         WCMD_endlocal();
480         break;
481       case WCMD_SET:
482         WCMD_setshow_env (p);
483         break;
484       case WCMD_SHIFT:
485         WCMD_shift ();
486         break;
487       case WCMD_TIME:
488         WCMD_setshow_time ();
489         break;
490       case WCMD_TITLE:
491         if (strlen(&whichcmd[count]) > 0)
492           WCMD_title(&whichcmd[count+1]);
493         break;
494       case WCMD_TYPE:
495         WCMD_type ();
496         break;
497       case WCMD_VER:
498         WCMD_version ();
499         break;
500       case WCMD_VERIFY:
501         WCMD_verify (p);
502         break;
503       case WCMD_VOL:
504         WCMD_volume (0, p);
505         break;
506       case WCMD_EXIT:
507         ExitProcess (0);
508       default:
509         WCMD_run_program (whichcmd, 0);
510     }
511     HeapFree( GetProcessHeap(), 0, cmd );
512     if (old_stdin != INVALID_HANDLE_VALUE) {
513       CloseHandle (GetStdHandle (STD_INPUT_HANDLE));
514       SetStdHandle (STD_INPUT_HANDLE, old_stdin);
515       old_stdin = INVALID_HANDLE_VALUE;
516     }
517     if (old_stdout != INVALID_HANDLE_VALUE) {
518       CloseHandle (GetStdHandle (STD_OUTPUT_HANDLE));
519       SetStdHandle (STD_OUTPUT_HANDLE, old_stdout);
520       old_stdout = INVALID_HANDLE_VALUE;
521     }
522 }
523
524 static void init_msvcrt_io_block(STARTUPINFO* st)
525 {
526     STARTUPINFO st_p;
527     /* fetch the parent MSVCRT info block if any, so that the child can use the
528      * same handles as its grand-father
529      */
530     st_p.cb = sizeof(STARTUPINFO);
531     GetStartupInfo(&st_p);
532     st->cbReserved2 = st_p.cbReserved2;
533     st->lpReserved2 = st_p.lpReserved2;
534     if (st_p.cbReserved2 && st_p.lpReserved2 &&
535         (old_stdin != INVALID_HANDLE_VALUE || old_stdout != INVALID_HANDLE_VALUE))
536     {
537         /* Override the entries for fd 0,1,2 if we happened
538          * to change those std handles (this depends on the way wcmd sets
539          * it's new input & output handles)
540          */
541         size_t sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
542         BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
543         if (ptr)
544         {
545             unsigned num = *(unsigned*)st_p.lpReserved2;
546             char* flags = (char*)(ptr + sizeof(unsigned));
547             HANDLE* handles = (HANDLE*)(flags + num * sizeof(char));
548
549             memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
550             st->cbReserved2 = sz;
551             st->lpReserved2 = ptr;
552
553 #define WX_OPEN 0x01    /* see dlls/msvcrt/file.c */
554             if (num <= 0 || (flags[0] & WX_OPEN))
555             {
556                 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
557                 flags[0] |= WX_OPEN;
558             }
559             if (num <= 1 || (flags[1] & WX_OPEN))
560             {
561                 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
562                 flags[1] |= WX_OPEN;
563             }
564             if (num <= 2 || (flags[2] & WX_OPEN))
565             {
566                 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
567                 flags[2] |= WX_OPEN;
568             }
569 #undef WX_OPEN
570         }
571     }
572 }
573
574 /******************************************************************************
575  * WCMD_run_program
576  *
577  *      Execute a command line as an external program. If no extension given then
578  *      precedence is given to .BAT files. Must allow recursion.
579  *      
580  *      called is 1 if the program was invoked with a CALL command - removed
581  *      from command -. It is only used for batch programs.
582  *
583  *      FIXME: Case sensitivity in suffixes!
584  */
585
586 void WCMD_run_program (char *command, int called) {
587
588 STARTUPINFO st;
589 PROCESS_INFORMATION pe;
590 SHFILEINFO psfi;
591 DWORD console;
592 BOOL status;
593 HANDLE h;
594 HINSTANCE hinst;
595 char filetorun[MAX_PATH];
596
597   WCMD_parse (command, quals, param1, param2);  /* Quick way to get the filename */
598   if (!(*param1) && !(*param2))
599     return;
600   if (strpbrk (param1, "/\\:") == NULL) {  /* No explicit path given */
601     char *ext = strrchr( param1, '.' );
602     if (!ext || !strcasecmp( ext, ".bat"))
603     {
604       if (SearchPath (NULL, param1, ".bat", sizeof(filetorun), filetorun, NULL)) {
605         WCMD_batch (filetorun, command, called);
606         return;
607       }
608     }
609     if (!ext || !strcasecmp( ext, ".cmd"))
610     {
611       if (SearchPath (NULL, param1, ".cmd", sizeof(filetorun), filetorun, NULL)) {
612         WCMD_batch (filetorun, command, called);
613         return;
614       }
615     }
616   }
617   else {                                        /* Explicit path given */
618     char *ext = strrchr( param1, '.' );
619     if (ext && (!strcasecmp( ext, ".bat" ) || !strcasecmp( ext, ".cmd" )))
620     {
621       WCMD_batch (param1, command, called);
622       return;
623     }
624
625     if (ext && strpbrk( ext, "/\\:" )) ext = NULL;
626     if (!ext)
627     {
628       strcpy (filetorun, param1);
629       strcat (filetorun, ".bat");
630       h = CreateFile (filetorun, GENERIC_READ, FILE_SHARE_READ,
631                       NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
632       if (h != INVALID_HANDLE_VALUE) {
633         CloseHandle (h);
634         WCMD_batch (param1, command, called);
635         return;
636       }
637     }
638   }
639
640         /* No batch file found, assume executable */
641
642   hinst = FindExecutable (param1, NULL, filetorun);
643   if ((INT_PTR)hinst < 32)
644     console = 0;
645   else
646     console = SHGetFileInfo (filetorun, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
647
648   ZeroMemory (&st, sizeof(STARTUPINFO));
649   st.cb = sizeof(STARTUPINFO);
650   init_msvcrt_io_block(&st);
651
652   status = CreateProcess (NULL, command, NULL, NULL, TRUE, 
653                           0, NULL, NULL, &st, &pe);
654   if ((opt_c || opt_k) && !opt_s && !status
655       && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
656     /* strip first and last quote characters and try again */
657     WCMD_opt_s_strip_quotes(command);
658     opt_s=1;
659     WCMD_run_program(command, called);
660     return;
661   }
662   if (!status) {
663     WCMD_print_error ();
664     return;
665   }
666   if (!console) errorlevel = 0;
667   else
668   {
669       if (!HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
670       GetExitCodeProcess (pe.hProcess, &errorlevel);
671       if (errorlevel == STILL_ACTIVE) errorlevel = 0;
672   }
673   CloseHandle(pe.hProcess);
674   CloseHandle(pe.hThread);
675 }
676
677 /******************************************************************************
678  * WCMD_show_prompt
679  *
680  *      Display the prompt on STDout
681  *
682  */
683
684 void WCMD_show_prompt (void) {
685
686 int status;
687 char out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
688 char *p, *q;
689
690   status = GetEnvironmentVariable ("PROMPT", prompt_string, sizeof(prompt_string));
691   if ((status == 0) || (status > sizeof(prompt_string))) {
692     lstrcpy (prompt_string, "$P$G");
693   }
694   p = prompt_string;
695   q = out_string;
696   *q = '\0';
697   while (*p != '\0') {
698     if (*p != '$') {
699       *q++ = *p++;
700       *q = '\0';
701     }
702     else {
703       p++;
704       switch (toupper(*p)) {
705         case '$':
706           *q++ = '$';
707           break;
708         case 'B':
709           *q++ = '|';
710           break;
711         case 'D':
712           GetDateFormat (LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
713           while (*q) q++;
714           break;
715         case 'E':
716           *q++ = '\E';
717           break;
718         case 'G':
719           *q++ = '>';
720           break;
721         case 'L':
722           *q++ = '<';
723           break;
724         case 'N':
725           status = GetCurrentDirectory (sizeof(curdir), curdir);
726           if (status) {
727             *q++ = curdir[0];
728           }
729           break;
730         case 'P':
731           status = GetCurrentDirectory (sizeof(curdir), curdir);
732           if (status) {
733             lstrcat (q, curdir);
734             while (*q) q++;
735           }
736           break;
737         case 'Q':
738           *q++ = '=';
739           break;
740         case 'T':
741           GetTimeFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
742           while (*q) q++;
743           break;
744        case 'V':
745            lstrcat (q, version_string);
746            while (*q) q++;
747          break;
748         case '_':
749           *q++ = '\n';
750           break;
751       }
752       p++;
753       *q = '\0';
754     }
755   }
756   WCMD_output_asis (out_string);
757 }
758
759 /****************************************************************************
760  * WCMD_print_error
761  *
762  * Print the message for GetLastError
763  */
764
765 void WCMD_print_error (void) {
766 LPVOID lpMsgBuf;
767 DWORD error_code;
768 int status;
769
770   error_code = GetLastError ();
771   status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
772                         NULL, error_code, 0, (LPTSTR) &lpMsgBuf, 0, NULL);
773   if (!status) {
774     WCMD_output ("FIXME: Cannot display message for error %d, status %d\n",
775                         error_code, GetLastError());
776     return;
777   }
778   WCMD_output_asis (lpMsgBuf);
779   LocalFree ((HLOCAL)lpMsgBuf);
780   WCMD_output_asis (newline);
781   return;
782 }
783
784 /*******************************************************************
785  * WCMD_parse - parse a command into parameters and qualifiers.
786  *
787  *      On exit, all qualifiers are concatenated into q, the first string
788  *      not beginning with "/" is in p1 and the
789  *      second in p2. Any subsequent non-qualifier strings are lost.
790  *      Parameters in quotes are handled.
791  */
792
793 void WCMD_parse (char *s, char *q, char *p1, char *p2) {
794
795 int p = 0;
796
797   *q = *p1 = *p2 = '\0';
798   while (TRUE) {
799     switch (*s) {
800       case '/':
801         *q++ = *s++;
802         while ((*s != '\0') && (*s != ' ') && *s != '/') {
803           *q++ = toupper (*s++);
804         }
805         *q = '\0';
806         break;
807       case ' ':
808       case '\t':
809         s++;
810         break;
811       case '"':
812         s++;
813         while ((*s != '\0') && (*s != '"')) {
814           if (p == 0) *p1++ = *s++;
815           else if (p == 1) *p2++ = *s++;
816           else s++;
817         }
818         if (p == 0) *p1 = '\0';
819         if (p == 1) *p2 = '\0';
820         p++;
821         if (*s == '"') s++;
822         break;
823       case '\0':
824         return;
825       default:
826         while ((*s != '\0') && (*s != ' ') && (*s != '\t')) {
827           if (p == 0) *p1++ = *s++;
828           else if (p == 1) *p2++ = *s++;
829           else s++;
830         }
831         if (p == 0) *p1 = '\0';
832         if (p == 1) *p2 = '\0';
833         p++;
834     }
835   }
836 }
837
838 /*******************************************************************
839  * WCMD_output - send output to current standard output device.
840  *
841  */
842
843 void WCMD_output (const char *format, ...) {
844
845 va_list ap;
846 char string[1024];
847 int ret;
848
849   va_start(ap,format);
850   ret = vsnprintf (string, sizeof( string), format, ap);
851   va_end(ap);
852   if( ret >= sizeof( string)) {
853        WCMD_output_asis("ERR: output truncated in WCMD_output\n" );
854        string[sizeof( string) -1] = '\0';
855   }
856   WCMD_output_asis(string);
857 }
858
859
860 static int line_count;
861 static int max_height;
862 static BOOL paged_mode;
863
864 void WCMD_enter_paged_mode(void)
865 {
866 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
867
868   if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo))
869     max_height = consoleInfo.dwSize.Y;
870   else
871     max_height = 25;
872   paged_mode = TRUE;
873   line_count = 5; /* keep 5 lines from previous output */
874 }
875
876 void WCMD_leave_paged_mode(void)
877 {
878   paged_mode = FALSE;
879 }
880
881 /*******************************************************************
882  * WCMD_output_asis - send output to current standard output device.
883  *        without formatting eg. when message contains '%'
884  */
885
886 void WCMD_output_asis (const char *message) {
887   DWORD count;
888   char* ptr;
889   char string[1024];
890
891   if (paged_mode) {
892     do {
893       if ((ptr = strchr(message, '\n')) != NULL) ptr++;
894       WriteFile (GetStdHandle(STD_OUTPUT_HANDLE), message, 
895                  (ptr) ? ptr - message : lstrlen(message), &count, NULL);
896       if (ptr) {
897         if (++line_count >= max_height - 1) {
898           line_count = 0;
899           WCMD_output_asis (anykey);
900           ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string), &count, NULL);
901         }
902       }
903     } while ((message = ptr) != NULL);
904   } else {
905       WriteFile (GetStdHandle(STD_OUTPUT_HANDLE), message, lstrlen(message), &count, NULL);
906   }
907 }
908
909
910 /***************************************************************************
911  * WCMD_strtrim_leading_spaces
912  *
913  *      Remove leading spaces from a string. Return a pointer to the first
914  *      non-space character. Does not modify the input string
915  */
916
917 char *WCMD_strtrim_leading_spaces (char *string) {
918
919 char *ptr;
920
921   ptr = string;
922   while (*ptr == ' ') ptr++;
923   return ptr;
924 }
925
926 /*************************************************************************
927  * WCMD_strtrim_trailing_spaces
928  *
929  *      Remove trailing spaces from a string. This routine modifies the input
930  *      string by placing a null after the last non-space character
931  */
932
933 void WCMD_strtrim_trailing_spaces (char *string) {
934
935 char *ptr;
936
937   ptr = string + lstrlen (string) - 1;
938   while ((*ptr == ' ') && (ptr >= string)) {
939     *ptr = '\0';
940     ptr--;
941   }
942 }
943
944 /*************************************************************************
945  * WCMD_opt_s_strip_quotes
946  *
947  *      Remove first and last quote characters, preserving all other text
948  */
949
950 void WCMD_opt_s_strip_quotes(char *cmd) {
951   char *src = cmd + 1, *dest = cmd, *lastq = NULL;
952   while((*dest=*src) != '\0') {
953       if (*src=='\"')
954           lastq=dest;
955       dest++, src++;
956   }
957   if (lastq) {
958       dest=lastq++;
959       while ((*dest++=*lastq++) != 0)
960           ;
961   }
962 }
963
964 /*************************************************************************
965  * WCMD_pipe
966  *
967  *      Handle pipes within a command - the DOS way using temporary files.
968  */
969
970 void WCMD_pipe (char *command) {
971
972 char *p;
973 char temp_path[MAX_PATH], temp_file[MAX_PATH], temp_file2[MAX_PATH], temp_cmd[1024];
974
975   GetTempPath (sizeof(temp_path), temp_path);
976   GetTempFileName (temp_path, "WCMD", 0, temp_file);
977   p = strchr(command, '|');
978   *p++ = '\0';
979   wsprintf (temp_cmd, "%s > %s", command, temp_file);
980   WCMD_process_command (temp_cmd);
981   command = p;
982   while ((p = strchr(command, '|'))) {
983     *p++ = '\0';
984     GetTempFileName (temp_path, "WCMD", 0, temp_file2);
985     wsprintf (temp_cmd, "%s < %s > %s", command, temp_file, temp_file2);
986     WCMD_process_command (temp_cmd);
987     DeleteFile (temp_file);
988     lstrcpy (temp_file, temp_file2);
989     command = p;
990   }
991   wsprintf (temp_cmd, "%s < %s", command, temp_file);
992   WCMD_process_command (temp_cmd);
993   DeleteFile (temp_file);
994 }