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