gdi32/tests: Use skip where appropriate.
[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 #include "wine/debug.h"
30
31 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
32
33 const char * const inbuilt[] = {"ATTRIB", "CALL", "CD", "CHDIR", "CLS", "COPY", "CTTY",
34                 "DATE", "DEL", "DIR", "ECHO", "ERASE", "FOR", "GOTO",
35                 "HELP", "IF", "LABEL", "MD", "MKDIR", "MOVE", "PATH", "PAUSE",
36                 "PROMPT", "REM", "REN", "RENAME", "RD", "RMDIR", "SET", "SHIFT",
37                 "TIME", "TITLE", "TYPE", "VERIFY", "VER", "VOL", 
38                 "ENDLOCAL", "SETLOCAL", "PUSHD", "POPD", "ASSOC", "EXIT" };
39
40 HINSTANCE hinst;
41 DWORD errorlevel;
42 int echo_mode = 1, verify_mode = 0;
43 static int opt_c, opt_k, opt_s;
44 const char nyi[] = "Not Yet Implemented\n\n";
45 const char newline[] = "\n";
46 const char version_string[] = "CMD Version " PACKAGE_VERSION "\n\n";
47 const char anykey[] = "Press Return key to continue: ";
48 char quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
49 BATCH_CONTEXT *context = NULL;
50 extern struct env_stack *pushd_directories;
51
52 static char *WCMD_expand_envvar(char *start);
53
54 /*****************************************************************************
55  * Main entry point. This is a console application so we have a main() not a
56  * winmain().
57  */
58
59 int main (int argc, char *argv[])
60 {
61   char string[1024];
62   char* cmd=NULL;
63   DWORD count;
64   HANDLE h;
65   int opt_q;
66
67   opt_c=opt_k=opt_q=opt_s=0;
68   while (*argv!=NULL)
69   {
70       char c;
71       if ((*argv)[0]!='/' || (*argv)[1]=='\0') {
72           argv++;
73           continue;
74       }
75
76       c=(*argv)[1];
77       if (tolower(c)=='c') {
78           opt_c=1;
79       } else if (tolower(c)=='q') {
80           opt_q=1;
81       } else if (tolower(c)=='k') {
82           opt_k=1;
83       } else if (tolower(c)=='s') {
84           opt_s=1;
85       } else if (tolower(c)=='t' || tolower(c)=='x' || tolower(c)=='y') {
86           /* Ignored for compatibility with Windows */
87       }
88
89       if ((*argv)[2]==0)
90           argv++;
91       else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
92           *argv+=2;
93
94       if (opt_c || opt_k) /* break out of parsing immediately after c or k */
95           break;
96   }
97
98   if (opt_q) {
99     WCMD_echo("OFF");
100   }
101
102   if (opt_c || opt_k) {
103       int len,qcount;
104       char** arg;
105       char* p;
106
107       /* opt_s left unflagged if the command starts with and contains exactly
108        * one quoted string (exactly two quote characters). The quoted string
109        * must be an executable name that has whitespace and must not have the
110        * following characters: &<>()@^| */
111
112       /* Build the command to execute */
113       len = 0;
114       qcount = 0;
115       for (arg = argv; *arg; arg++)
116       {
117           int has_space,bcount;
118           char* a;
119
120           has_space=0;
121           bcount=0;
122           a=*arg;
123           if( !*a ) has_space=1;
124           while (*a!='\0') {
125               if (*a=='\\') {
126                   bcount++;
127               } else {
128                   if (*a==' ' || *a=='\t') {
129                       has_space=1;
130                   } else if (*a=='"') {
131                       /* doubling of '\' preceding a '"',
132                        * plus escaping of said '"'
133                        */
134                       len+=2*bcount+1;
135                       qcount++;
136                   }
137                   bcount=0;
138               }
139               a++;
140           }
141           len+=(a-*arg)+1 /* for the separating space */;
142           if (has_space)
143           {
144               len+=2; /* for the quotes */
145               qcount+=2;
146           }
147       }
148
149       if (qcount!=2)
150           opt_s=1;
151
152       /* check argv[0] for a space and invalid characters */
153       if (!opt_s) {
154           opt_s=1;
155           p=*argv;
156           while (*p!='\0') {
157               if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
158                   || *p=='@' || *p=='^' || *p=='|') {
159                   opt_s=1;
160                   break;
161               }
162               if (*p==' ')
163                   opt_s=0;
164               p++;
165           }
166       }
167
168       cmd = HeapAlloc(GetProcessHeap(), 0, len);
169       if (!cmd)
170           exit(1);
171
172       p = cmd;
173       for (arg = argv; *arg; arg++)
174       {
175           int has_space,has_quote;
176           char* a;
177
178           /* Check for quotes and spaces in this argument */
179           has_space=has_quote=0;
180           a=*arg;
181           if( !*a ) has_space=1;
182           while (*a!='\0') {
183               if (*a==' ' || *a=='\t') {
184                   has_space=1;
185                   if (has_quote)
186                       break;
187               } else if (*a=='"') {
188                   has_quote=1;
189                   if (has_space)
190                       break;
191               }
192               a++;
193           }
194
195           /* Now transfer it to the command line */
196           if (has_space)
197               *p++='"';
198           if (has_quote) {
199               int bcount;
200               char* a;
201
202               bcount=0;
203               a=*arg;
204               while (*a!='\0') {
205                   if (*a=='\\') {
206                       *p++=*a;
207                       bcount++;
208                   } else {
209                       if (*a=='"') {
210                           int i;
211
212                           /* Double all the '\\' preceding this '"', plus one */
213                           for (i=0;i<=bcount;i++)
214                               *p++='\\';
215                           *p++='"';
216                       } else {
217                           *p++=*a;
218                       }
219                       bcount=0;
220                   }
221                   a++;
222               }
223           } else {
224               strcpy(p,*arg);
225               p+=strlen(*arg);
226           }
227           if (has_space)
228               *p++='"';
229           *p++=' ';
230       }
231       if (p > cmd)
232           p--;  /* remove last space */
233       *p = '\0';
234
235       /* strip first and last quote characters if opt_s; check for invalid
236        * executable is done later */
237       if (opt_s && *cmd=='\"')
238           WCMD_opt_s_strip_quotes(cmd);
239   }
240
241   if (opt_c) {
242       /* If we do a "wcmd /c command", we don't want to allocate a new
243        * console since the command returns immediately. Rather, we use
244        * the currently allocated input and output handles. This allows
245        * us to pipe to and read from the command interpreter.
246        */
247       if (strchr(cmd,'|') != NULL)
248           WCMD_pipe(cmd);
249       else
250           WCMD_process_command(cmd);
251       HeapFree(GetProcessHeap(), 0, cmd);
252       return errorlevel;
253   }
254
255   SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
256                  ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
257   SetConsoleTitle("Wine Command Prompt");
258
259   if (opt_k) {
260       WCMD_process_command(cmd);
261       HeapFree(GetProcessHeap(), 0, cmd);
262   }
263
264 /*
265  *      If there is an AUTOEXEC.BAT file, try to execute it.
266  */
267
268   GetFullPathName ("\\autoexec.bat", sizeof(string), string, NULL);
269   h = CreateFile (string, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
270   if (h != INVALID_HANDLE_VALUE) {
271     CloseHandle (h);
272 #if 0
273     WCMD_batch ((char *)"\\autoexec.bat", (char *)"\\autoexec.bat", 0, NULL, INVALID_HANDLE_VALUE);
274 #endif
275   }
276
277 /*
278  *      Loop forever getting commands and executing them.
279  */
280
281   WCMD_version ();
282   while (TRUE) {
283     WCMD_show_prompt ();
284     ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string), &count, NULL);
285     if (count > 1) {
286       string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
287       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
288       if (lstrlen (string) != 0) {
289         if (strchr(string,'|') != NULL) {
290           WCMD_pipe (string);
291         }
292         else {
293           WCMD_process_command (string);
294         }
295       }
296     }
297   }
298 }
299
300
301 /*****************************************************************************
302  * Process one command. If the command is EXIT this routine does not return.
303  * We will recurse through here executing batch files.
304  */
305
306
307 void WCMD_process_command (char *command)
308 {
309     char *cmd, *p, *s, *t;
310     int status, i;
311     DWORD count, creationDisposition;
312     HANDLE h;
313     char *whichcmd;
314     SECURITY_ATTRIBUTES sa;
315     char *new_cmd;
316     HANDLE old_stdin = INVALID_HANDLE_VALUE;
317     HANDLE old_stdout = INVALID_HANDLE_VALUE;
318
319     /* Move copy of the command onto the heap so it can be expanded */
320     new_cmd = HeapAlloc( GetProcessHeap(), 0, MAXSTRING );
321     strcpy(new_cmd, command);
322
323     /* For commands in a context (batch program):                  */
324     /*   Expand environment variables in a batch file %{0-9} first */
325     /*     including support for any ~ modifiers                   */
326     /* Additionally:                                               */
327     /*   Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special  */
328     /*     names allowing environment variable overrides           */
329     /* NOTE: To support the %PATH:xxx% syntax, also perform        */
330     /*   manual expansion of environment variables here            */
331
332     p = new_cmd;
333     while ((p = strchr(p, '%'))) {
334       i = *(p+1) - '0';
335
336       /* Replace %~ modifications if in batch program */
337       if (context && *(p+1) == '~') {
338         WCMD_HandleTildaModifiers(&p, NULL);
339         p++;
340
341       /* Replace use of %0...%9 if in batch program*/
342       } else if (context && (i >= 0) && (i <= 9)) {
343         s = strdup (p+2);
344         t = WCMD_parameter (context -> command, i + context -> shift_count, NULL);
345         strcpy (p, t);
346         strcat (p, s);
347         free (s);
348
349       /* Replace use of %* if in batch program*/
350       } else if (context && *(p+1)=='*') {
351         char *startOfParms = NULL;
352         s = strdup (p+2);
353         t = WCMD_parameter (context -> command, 1, &startOfParms);
354         if (startOfParms != NULL) strcpy (p, startOfParms);
355         else *p = 0x00;
356         strcat (p, s);
357         free (s);
358
359       } else {
360         p = WCMD_expand_envvar(p);
361       }
362     }
363     cmd = new_cmd;
364
365     /* In a batch program, unknown variables are replace by nothing */
366     /* so remove any remaining %var%                                */
367     if (context) {
368       p = cmd;
369       while ((p = strchr(p, '%'))) {
370         s = strchr(p+1, '%');
371         if (!s) {
372           *p=0x00;
373         } else {
374           t = strdup(s+1);
375           strcpy(p, t);
376           free(t);
377         }
378       }
379
380       /* Show prompt before batch line IF echo is on and in batch program */
381       if (echo_mode && (cmd[0] != '@')) {
382         WCMD_show_prompt();
383         WCMD_output_asis ( cmd);
384         WCMD_output_asis ( "\n");
385       }
386     }
387
388 /*
389  *      Changing default drive has to be handled as a special case.
390  */
391
392     if ((cmd[1] == ':') && IsCharAlpha (cmd[0]) && (strlen(cmd) == 2)) {
393       status = SetCurrentDirectory (cmd);
394       if (!status) WCMD_print_error ();
395       HeapFree( GetProcessHeap(), 0, cmd );
396       return;
397     }
398
399     /* Don't issue newline WCMD_output (newline);           @JED*/
400
401     sa.nLength = sizeof(sa);
402     sa.lpSecurityDescriptor = NULL;
403     sa.bInheritHandle = TRUE;
404 /*
405  *      Redirect stdin and/or stdout if required.
406  */
407
408     if ((p = strchr(cmd,'<')) != NULL) {
409       h = CreateFile (WCMD_parameter (++p, 0, NULL), GENERIC_READ, FILE_SHARE_READ, &sa, OPEN_EXISTING,
410                 FILE_ATTRIBUTE_NORMAL, NULL);
411       if (h == INVALID_HANDLE_VALUE) {
412         WCMD_print_error ();
413         HeapFree( GetProcessHeap(), 0, cmd );
414         return;
415       }
416       old_stdin = GetStdHandle (STD_INPUT_HANDLE);
417       SetStdHandle (STD_INPUT_HANDLE, h);
418     }
419     if ((p = strchr(cmd,'>')) != NULL) {
420       *p++ = '\0';
421       if ('>' == *p) {
422         creationDisposition = OPEN_ALWAYS;
423         p++;
424       }
425       else {
426         creationDisposition = CREATE_ALWAYS;
427       }
428       h = CreateFile (WCMD_parameter (p, 0, NULL), GENERIC_WRITE, 0, &sa, creationDisposition,
429                 FILE_ATTRIBUTE_NORMAL, NULL);
430       if (h == INVALID_HANDLE_VALUE) {
431         WCMD_print_error ();
432         HeapFree( GetProcessHeap(), 0, cmd );
433         return;
434       }
435       if (SetFilePointer (h, 0, NULL, FILE_END) ==
436           INVALID_SET_FILE_POINTER) {
437         WCMD_print_error ();
438       }
439       old_stdout = GetStdHandle (STD_OUTPUT_HANDLE);
440       SetStdHandle (STD_OUTPUT_HANDLE, h);
441     }
442     if ((p = strchr(cmd,'<')) != NULL) *p = '\0';
443
444 /*
445  * Strip leading whitespaces, and a '@' if supplied
446  */
447     whichcmd = WCMD_strtrim_leading_spaces(cmd);
448     WINE_TRACE("Command: '%s'\n", cmd);
449     if (whichcmd[0] == '@') whichcmd++;
450
451 /*
452  *      Check if the command entered is internal. If it is, pass the rest of the
453  *      line down to the command. If not try to run a program.
454  */
455
456     count = 0;
457     while (IsCharAlphaNumeric(whichcmd[count])) {
458       count++;
459     }
460     for (i=0; i<=WCMD_EXIT; i++) {
461       if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
462           whichcmd, count, inbuilt[i], -1) == 2) break;
463     }
464     p = WCMD_strtrim_leading_spaces (&whichcmd[count]);
465     WCMD_parse (p, quals, param1, param2);
466     switch (i) {
467
468       case WCMD_ATTRIB:
469         WCMD_setshow_attrib ();
470         break;
471       case WCMD_CALL:
472         WCMD_call (p);
473         break;
474       case WCMD_CD:
475       case WCMD_CHDIR:
476         WCMD_setshow_default ();
477         break;
478       case WCMD_CLS:
479         WCMD_clear_screen ();
480         break;
481       case WCMD_COPY:
482         WCMD_copy ();
483         break;
484       case WCMD_CTTY:
485         WCMD_change_tty ();
486         break;
487       case WCMD_DATE:
488         WCMD_setshow_date ();
489         break;
490       case WCMD_DEL:
491       case WCMD_ERASE:
492         WCMD_delete (p);
493         break;
494       case WCMD_DIR:
495         WCMD_directory ();
496         break;
497       case WCMD_ECHO:
498         WCMD_echo(&whichcmd[count]);
499         break;
500       case WCMD_FOR:
501         WCMD_for (p);
502         break;
503       case WCMD_GOTO:
504         WCMD_goto ();
505         break;
506       case WCMD_HELP:
507         WCMD_give_help (p);
508         break;
509       case WCMD_IF:
510         WCMD_if (p);
511         break;
512       case WCMD_LABEL:
513         WCMD_volume (1, p);
514         break;
515       case WCMD_MD:
516       case WCMD_MKDIR:
517         WCMD_create_dir ();
518         break;
519       case WCMD_MOVE:
520         WCMD_move ();
521         break;
522       case WCMD_PATH:
523         WCMD_setshow_path (p);
524         break;
525       case WCMD_PAUSE:
526         WCMD_pause ();
527         break;
528       case WCMD_PROMPT:
529         WCMD_setshow_prompt ();
530         break;
531       case WCMD_REM:
532         break;
533       case WCMD_REN:
534       case WCMD_RENAME:
535         WCMD_rename ();
536         break;
537       case WCMD_RD:
538       case WCMD_RMDIR:
539         WCMD_remove_dir (p);
540         break;
541       case WCMD_SETLOCAL:
542         WCMD_setlocal(p);
543         break;
544       case WCMD_ENDLOCAL:
545         WCMD_endlocal();
546         break;
547       case WCMD_SET:
548         WCMD_setshow_env (p);
549         break;
550       case WCMD_SHIFT:
551         WCMD_shift ();
552         break;
553       case WCMD_TIME:
554         WCMD_setshow_time ();
555         break;
556       case WCMD_TITLE:
557         if (strlen(&whichcmd[count]) > 0)
558           WCMD_title(&whichcmd[count+1]);
559         break;
560       case WCMD_TYPE:
561         WCMD_type ();
562         break;
563       case WCMD_VER:
564         WCMD_version ();
565         break;
566       case WCMD_VERIFY:
567         WCMD_verify (p);
568         break;
569       case WCMD_VOL:
570         WCMD_volume (0, p);
571         break;
572       case WCMD_PUSHD:
573         WCMD_pushd();
574         break;
575       case WCMD_POPD:
576         WCMD_popd();
577         break;
578       case WCMD_ASSOC:
579         WCMD_assoc(p);
580         break;
581       case WCMD_EXIT:
582         WCMD_exit ();
583         break;
584       default:
585         WCMD_run_program (whichcmd, 0);
586     }
587     HeapFree( GetProcessHeap(), 0, cmd );
588     if (old_stdin != INVALID_HANDLE_VALUE) {
589       CloseHandle (GetStdHandle (STD_INPUT_HANDLE));
590       SetStdHandle (STD_INPUT_HANDLE, old_stdin);
591     }
592     if (old_stdout != INVALID_HANDLE_VALUE) {
593       CloseHandle (GetStdHandle (STD_OUTPUT_HANDLE));
594       SetStdHandle (STD_OUTPUT_HANDLE, old_stdout);
595     }
596 }
597
598 static void init_msvcrt_io_block(STARTUPINFO* st)
599 {
600     STARTUPINFO st_p;
601     /* fetch the parent MSVCRT info block if any, so that the child can use the
602      * same handles as its grand-father
603      */
604     st_p.cb = sizeof(STARTUPINFO);
605     GetStartupInfo(&st_p);
606     st->cbReserved2 = st_p.cbReserved2;
607     st->lpReserved2 = st_p.lpReserved2;
608     if (st_p.cbReserved2 && st_p.lpReserved2)
609     {
610         /* Override the entries for fd 0,1,2 if we happened
611          * to change those std handles (this depends on the way wcmd sets
612          * it's new input & output handles)
613          */
614         size_t sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
615         BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
616         if (ptr)
617         {
618             unsigned num = *(unsigned*)st_p.lpReserved2;
619             char* flags = (char*)(ptr + sizeof(unsigned));
620             HANDLE* handles = (HANDLE*)(flags + num * sizeof(char));
621
622             memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
623             st->cbReserved2 = sz;
624             st->lpReserved2 = ptr;
625
626 #define WX_OPEN 0x01    /* see dlls/msvcrt/file.c */
627             if (num <= 0 || (flags[0] & WX_OPEN))
628             {
629                 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
630                 flags[0] |= WX_OPEN;
631             }
632             if (num <= 1 || (flags[1] & WX_OPEN))
633             {
634                 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
635                 flags[1] |= WX_OPEN;
636             }
637             if (num <= 2 || (flags[2] & WX_OPEN))
638             {
639                 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
640                 flags[2] |= WX_OPEN;
641             }
642 #undef WX_OPEN
643         }
644     }
645 }
646
647 /******************************************************************************
648  * WCMD_run_program
649  *
650  *      Execute a command line as an external program. Must allow recursion.
651  *
652  *      Precedence:
653  *        Manual testing under windows shows PATHEXT plays a key part in this,
654  *      and the search algorithm and precedence appears to be as follows.
655  *
656  *      Search locations:
657  *        If directory supplied on command, just use that directory
658  *        If extension supplied on command, look for that explicit name first
659  *        Otherwise, search in each directory on the path
660  *      Precedence:
661  *        If extension supplied on command, look for that explicit name first
662  *        Then look for supplied name .* (even if extension supplied, so
663  *          'garbage.exe' will match 'garbage.exe.cmd')
664  *        If any found, cycle through PATHEXT looking for name.exe one by one
665  *      Launching
666  *        Once a match has been found, it is launched - Code currently uses
667  *          findexecutable to acheive this which is left untouched.
668  */
669
670 void WCMD_run_program (char *command, int called) {
671
672   char  temp[MAX_PATH];
673   char  pathtosearch[MAX_PATH];
674   char *pathposn;
675   char  stemofsearch[MAX_PATH];
676   char *lastSlash;
677   char  pathext[MAXSTRING];
678   BOOL  extensionsupplied = FALSE;
679   BOOL  launched = FALSE;
680   BOOL  status;
681   DWORD len;
682
683
684   WCMD_parse (command, quals, param1, param2);  /* Quick way to get the filename */
685   if (!(*param1) && !(*param2))
686     return;
687
688   /* Calculate the search path and stem to search for */
689   if (strpbrk (param1, "/\\:") == NULL) {  /* No explicit path given, search path */
690     strcpy(pathtosearch,".;");
691     len = GetEnvironmentVariable ("PATH", &pathtosearch[2], sizeof(pathtosearch)-2);
692     if ((len == 0) || (len >= sizeof(pathtosearch) - 2)) {
693       lstrcpy (pathtosearch, ".");
694     }
695     if (strchr(param1, '.') != NULL) extensionsupplied = TRUE;
696     strcpy(stemofsearch, param1);
697
698   } else {
699
700     /* Convert eg. ..\fred to include a directory by removing file part */
701     GetFullPathName(param1, MAX_PATH, pathtosearch, NULL);
702     lastSlash = strrchr(pathtosearch, '\\');
703     if (lastSlash) *lastSlash = 0x00;
704     if (strchr(lastSlash, '.') != NULL) extensionsupplied = TRUE;
705     strcpy(stemofsearch, lastSlash+1);
706   }
707
708   /* Now extract PATHEXT */
709   len = GetEnvironmentVariable ("PATHEXT", pathext, sizeof(pathext));
710   if ((len == 0) || (len >= sizeof(pathext))) {
711     lstrcpy (pathext, ".bat;.com;.cmd;.exe");
712   }
713
714   /* Loop through the search path, dir by dir */
715   pathposn = pathtosearch;
716   while (!launched && pathposn) {
717
718     char  thisDir[MAX_PATH] = "";
719     char *pos               = NULL;
720     BOOL  found             = FALSE;
721
722     /* Work on the first directory on the search path */
723     pos = strchr(pathposn, ';');
724     if (pos) {
725       strncpy(thisDir, pathposn, (pos-pathposn));
726       thisDir[(pos-pathposn)] = 0x00;
727       pathposn = pos+1;
728
729     } else {
730       strcpy(thisDir, pathposn);
731       pathposn = NULL;
732     }
733
734     /* Since you can have eg. ..\.. on the path, need to expand
735        to full information                                      */
736     strcpy(temp, thisDir);
737     GetFullPathName(temp, MAX_PATH, thisDir, NULL);
738
739     /* 1. If extension supplied, see if that file exists */
740     strcat(thisDir, "\\");
741     strcat(thisDir, stemofsearch);
742     pos = &thisDir[strlen(thisDir)]; /* Pos = end of name */
743
744     if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
745       found = TRUE;
746     }
747
748     /* 2. Any .* matches? */
749     if (!found) {
750       HANDLE          h;
751       WIN32_FIND_DATA finddata;
752
753       strcat(thisDir,".*");
754       h = FindFirstFile(thisDir, &finddata);
755       FindClose(h);
756       if (h != INVALID_HANDLE_VALUE) {
757
758         char *thisExt = pathext;
759
760         /* 3. Yes - Try each path ext */
761         while (thisExt) {
762           char *nextExt = strchr(thisExt, ';');
763
764           if (nextExt) {
765             strncpy(pos, thisExt, (nextExt-thisExt));
766             pos[(nextExt-thisExt)] = 0x00;
767             thisExt = nextExt+1;
768           } else {
769             strcpy(pos, thisExt);
770             thisExt = NULL;
771           }
772
773           if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
774             found = TRUE;
775             thisExt = NULL;
776           }
777         }
778       }
779     }
780
781     /* Once found, launch it */
782     if (found) {
783       STARTUPINFO st;
784       PROCESS_INFORMATION pe;
785       SHFILEINFO psfi;
786       DWORD console;
787       HINSTANCE hinst;
788       char *ext = strrchr( thisDir, '.' );
789       launched = TRUE;
790
791       /* Special case BAT and CMD */
792       if (ext && !strcasecmp(ext, ".bat")) {
793         WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
794         return;
795       } else if (ext && !strcasecmp(ext, ".cmd")) {
796         WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
797         return;
798       } else {
799
800         /* thisDir contains the file to be launched, but with what?
801            eg. a.exe will require a.exe to be launched, a.html may be iexplore */
802         hinst = FindExecutable (param1, NULL, temp);
803         if ((INT_PTR)hinst < 32)
804           console = 0;
805         else
806           console = SHGetFileInfo (temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
807
808         ZeroMemory (&st, sizeof(STARTUPINFO));
809         st.cb = sizeof(STARTUPINFO);
810         init_msvcrt_io_block(&st);
811
812         /* Launch the process and if a CUI wait on it to complete */
813         status = CreateProcess (thisDir, command, NULL, NULL, TRUE,
814                                 0, NULL, NULL, &st, &pe);
815         if ((opt_c || opt_k) && !opt_s && !status
816             && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
817           /* strip first and last quote characters and try again */
818           WCMD_opt_s_strip_quotes(command);
819           opt_s=1;
820           WCMD_run_program(command, called);
821           return;
822         }
823         if (!status) {
824           WCMD_print_error ();
825           /* If a command fails to launch, it sets errorlevel 9009 - which
826              does not seem to have any associated constant definition     */
827           errorlevel = 9009;
828           return;
829         }
830         if (!console) errorlevel = 0;
831         else
832         {
833             if (!HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
834             GetExitCodeProcess (pe.hProcess, &errorlevel);
835             if (errorlevel == STILL_ACTIVE) errorlevel = 0;
836         }
837         CloseHandle(pe.hProcess);
838         CloseHandle(pe.hThread);
839         return;
840       }
841     }
842   }
843
844   /* Not found anywhere - give up */
845   SetLastError(ERROR_FILE_NOT_FOUND);
846   WCMD_print_error ();
847
848   /* If a command fails to launch, it sets errorlevel 9009 - which
849      does not seem to have any associated constant definition     */
850   errorlevel = 9009;
851   return;
852
853 }
854
855 /******************************************************************************
856  * WCMD_show_prompt
857  *
858  *      Display the prompt on STDout
859  *
860  */
861
862 void WCMD_show_prompt (void) {
863
864   int status;
865   char out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
866   char *p, *q;
867   DWORD len;
868
869   len = GetEnvironmentVariable ("PROMPT", prompt_string, sizeof(prompt_string));
870   if ((len == 0) || (len >= sizeof(prompt_string))) {
871     lstrcpy (prompt_string, "$P$G");
872   }
873   p = prompt_string;
874   q = out_string;
875   *q = '\0';
876   while (*p != '\0') {
877     if (*p != '$') {
878       *q++ = *p++;
879       *q = '\0';
880     }
881     else {
882       p++;
883       switch (toupper(*p)) {
884         case '$':
885           *q++ = '$';
886           break;
887         case 'A':
888           *q++ = '&';
889           break;
890         case 'B':
891           *q++ = '|';
892           break;
893         case 'C':
894           *q++ = '(';
895           break;
896         case 'D':
897           GetDateFormat (LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
898           while (*q) q++;
899           break;
900         case 'E':
901           *q++ = '\E';
902           break;
903         case 'F':
904           *q++ = ')';
905           break;
906         case 'G':
907           *q++ = '>';
908           break;
909         case 'H':
910           *q++ = '\b';
911           break;
912         case 'L':
913           *q++ = '<';
914           break;
915         case 'N':
916           status = GetCurrentDirectory (sizeof(curdir), curdir);
917           if (status) {
918             *q++ = curdir[0];
919           }
920           break;
921         case 'P':
922           status = GetCurrentDirectory (sizeof(curdir), curdir);
923           if (status) {
924             lstrcat (q, curdir);
925             while (*q) q++;
926           }
927           break;
928         case 'Q':
929           *q++ = '=';
930           break;
931         case 'S':
932           *q++ = ' ';
933           break;
934         case 'T':
935           GetTimeFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
936           while (*q) q++;
937           break;
938         case 'V':
939           lstrcat (q, version_string);
940           while (*q) q++;
941           break;
942         case '_':
943           *q++ = '\n';
944           break;
945         case '+':
946           if (pushd_directories) {
947             memset(q, '+', pushd_directories->stackdepth);
948             q = q + pushd_directories->stackdepth;
949           }
950           break;
951       }
952       p++;
953       *q = '\0';
954     }
955   }
956   WCMD_output_asis (out_string);
957 }
958
959 /****************************************************************************
960  * WCMD_print_error
961  *
962  * Print the message for GetLastError
963  */
964
965 void WCMD_print_error (void) {
966   LPVOID lpMsgBuf;
967   DWORD error_code;
968   int status;
969
970   error_code = GetLastError ();
971   status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
972                         NULL, error_code, 0, (LPTSTR) &lpMsgBuf, 0, NULL);
973   if (!status) {
974     WCMD_output ("FIXME: Cannot display message for error %d, status %d\n",
975                         error_code, GetLastError());
976     return;
977   }
978   WCMD_output_asis (lpMsgBuf);
979   LocalFree ((HLOCAL)lpMsgBuf);
980   WCMD_output_asis (newline);
981   return;
982 }
983
984 /*******************************************************************
985  * WCMD_parse - parse a command into parameters and qualifiers.
986  *
987  *      On exit, all qualifiers are concatenated into q, the first string
988  *      not beginning with "/" is in p1 and the
989  *      second in p2. Any subsequent non-qualifier strings are lost.
990  *      Parameters in quotes are handled.
991  */
992
993 void WCMD_parse (char *s, char *q, char *p1, char *p2) {
994
995 int p = 0;
996
997   *q = *p1 = *p2 = '\0';
998   while (TRUE) {
999     switch (*s) {
1000       case '/':
1001         *q++ = *s++;
1002         while ((*s != '\0') && (*s != ' ') && *s != '/') {
1003           *q++ = toupper (*s++);
1004         }
1005         *q = '\0';
1006         break;
1007       case ' ':
1008       case '\t':
1009         s++;
1010         break;
1011       case '"':
1012         s++;
1013         while ((*s != '\0') && (*s != '"')) {
1014           if (p == 0) *p1++ = *s++;
1015           else if (p == 1) *p2++ = *s++;
1016           else s++;
1017         }
1018         if (p == 0) *p1 = '\0';
1019         if (p == 1) *p2 = '\0';
1020         p++;
1021         if (*s == '"') s++;
1022         break;
1023       case '\0':
1024         return;
1025       default:
1026         while ((*s != '\0') && (*s != ' ') && (*s != '\t')) {
1027           if (p == 0) *p1++ = *s++;
1028           else if (p == 1) *p2++ = *s++;
1029           else s++;
1030         }
1031         if (p == 0) *p1 = '\0';
1032         if (p == 1) *p2 = '\0';
1033         p++;
1034     }
1035   }
1036 }
1037
1038 /*******************************************************************
1039  * WCMD_output - send output to current standard output device.
1040  *
1041  */
1042
1043 void WCMD_output (const char *format, ...) {
1044
1045   va_list ap;
1046   char string[1024];
1047   int ret;
1048
1049   va_start(ap,format);
1050   ret = vsnprintf (string, sizeof( string), format, ap);
1051   va_end(ap);
1052   if( ret >= sizeof( string)) {
1053        WCMD_output_asis("ERR: output truncated in WCMD_output\n" );
1054        string[sizeof( string) -1] = '\0';
1055   }
1056   WCMD_output_asis(string);
1057 }
1058
1059
1060 static int line_count;
1061 static int max_height;
1062 static BOOL paged_mode;
1063
1064 void WCMD_enter_paged_mode(void)
1065 {
1066   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
1067
1068   if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo))
1069     max_height = consoleInfo.dwSize.Y;
1070   else
1071     max_height = 25;
1072   paged_mode = TRUE;
1073   line_count = 5; /* keep 5 lines from previous output */
1074 }
1075
1076 void WCMD_leave_paged_mode(void)
1077 {
1078   paged_mode = FALSE;
1079 }
1080
1081 /*******************************************************************
1082  * WCMD_output_asis - send output to current standard output device.
1083  *        without formatting eg. when message contains '%'
1084  */
1085
1086 void WCMD_output_asis (const char *message) {
1087   DWORD count;
1088   char* ptr;
1089   char string[1024];
1090
1091   if (paged_mode) {
1092     do {
1093       if ((ptr = strchr(message, '\n')) != NULL) ptr++;
1094       WriteFile (GetStdHandle(STD_OUTPUT_HANDLE), message, 
1095                  (ptr) ? ptr - message : lstrlen(message), &count, NULL);
1096       if (ptr) {
1097         if (++line_count >= max_height - 1) {
1098           line_count = 0;
1099           WCMD_output_asis (anykey);
1100           ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string), &count, NULL);
1101         }
1102       }
1103     } while ((message = ptr) != NULL);
1104   } else {
1105       WriteFile (GetStdHandle(STD_OUTPUT_HANDLE), message, lstrlen(message), &count, NULL);
1106   }
1107 }
1108
1109
1110 /***************************************************************************
1111  * WCMD_strtrim_leading_spaces
1112  *
1113  *      Remove leading spaces from a string. Return a pointer to the first
1114  *      non-space character. Does not modify the input string
1115  */
1116
1117 char *WCMD_strtrim_leading_spaces (char *string) {
1118
1119   char *ptr;
1120
1121   ptr = string;
1122   while (*ptr == ' ') ptr++;
1123   return ptr;
1124 }
1125
1126 /*************************************************************************
1127  * WCMD_strtrim_trailing_spaces
1128  *
1129  *      Remove trailing spaces from a string. This routine modifies the input
1130  *      string by placing a null after the last non-space character
1131  */
1132
1133 void WCMD_strtrim_trailing_spaces (char *string) {
1134
1135   char *ptr;
1136
1137   ptr = string + lstrlen (string) - 1;
1138   while ((*ptr == ' ') && (ptr >= string)) {
1139     *ptr = '\0';
1140     ptr--;
1141   }
1142 }
1143
1144 /*************************************************************************
1145  * WCMD_opt_s_strip_quotes
1146  *
1147  *      Remove first and last quote characters, preserving all other text
1148  */
1149
1150 void WCMD_opt_s_strip_quotes(char *cmd) {
1151   char *src = cmd + 1, *dest = cmd, *lastq = NULL;
1152   while((*dest=*src) != '\0') {
1153       if (*src=='\"')
1154           lastq=dest;
1155       dest++, src++;
1156   }
1157   if (lastq) {
1158       dest=lastq++;
1159       while ((*dest++=*lastq++) != 0)
1160           ;
1161   }
1162 }
1163
1164 /*************************************************************************
1165  * WCMD_pipe
1166  *
1167  *      Handle pipes within a command - the DOS way using temporary files.
1168  */
1169
1170 void WCMD_pipe (char *command) {
1171
1172   char *p;
1173   char temp_path[MAX_PATH], temp_file[MAX_PATH], temp_file2[MAX_PATH], temp_cmd[1024];
1174
1175   GetTempPath (sizeof(temp_path), temp_path);
1176   GetTempFileName (temp_path, "CMD", 0, temp_file);
1177   p = strchr(command, '|');
1178   *p++ = '\0';
1179   wsprintf (temp_cmd, "%s > %s", command, temp_file);
1180   WCMD_process_command (temp_cmd);
1181   command = p;
1182   while ((p = strchr(command, '|'))) {
1183     *p++ = '\0';
1184     GetTempFileName (temp_path, "CMD", 0, temp_file2);
1185     wsprintf (temp_cmd, "%s < %s > %s", command, temp_file, temp_file2);
1186     WCMD_process_command (temp_cmd);
1187     DeleteFile (temp_file);
1188     lstrcpy (temp_file, temp_file2);
1189     command = p;
1190   }
1191   wsprintf (temp_cmd, "%s < %s", command, temp_file);
1192   WCMD_process_command (temp_cmd);
1193   DeleteFile (temp_file);
1194 }
1195
1196 /*************************************************************************
1197  * WCMD_expand_envvar
1198  *
1199  *      Expands environment variables, allowing for character substitution
1200  */
1201 static char *WCMD_expand_envvar(char *start) {
1202     char *endOfVar = NULL, *s;
1203     char *colonpos = NULL;
1204     char thisVar[MAXSTRING];
1205     char thisVarContents[MAXSTRING];
1206     char savedchar = 0x00;
1207     int len;
1208
1209     /* Find the end of the environment variable, and extract name */
1210     endOfVar = strchr(start+1, '%');
1211     if (endOfVar == NULL) {
1212       /* FIXME: Some special conditions here depending opn whether
1213          in batch, complex or not, and whether env var exists or not! */
1214       return start+1;
1215     }
1216     strncpy(thisVar, start, (endOfVar - start)+1);
1217     thisVar[(endOfVar - start)+1] = 0x00;
1218     colonpos = strchr(thisVar+1, ':');
1219
1220     /* If there's complex substitution, just need %var% for now
1221        to get the expanded data to play with                    */
1222     if (colonpos) {
1223         *colonpos = '%';
1224         savedchar = *(colonpos+1);
1225         *(colonpos+1) = 0x00;
1226     }
1227
1228     /* Expand to contents, if unchanged, return */
1229     /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
1230     /* override if existing env var called that name              */
1231     if ((CompareString (LOCALE_USER_DEFAULT,
1232                         NORM_IGNORECASE | SORT_STRINGSORT,
1233                         thisVar, 12, "%ERRORLEVEL%", -1) == 2) &&
1234                 (GetEnvironmentVariable("ERRORLEVEL", thisVarContents, 1) == 0) &&
1235                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1236       sprintf(thisVarContents, "%d", errorlevel);
1237       len = strlen(thisVarContents);
1238
1239     } else if ((CompareString (LOCALE_USER_DEFAULT,
1240                                NORM_IGNORECASE | SORT_STRINGSORT,
1241                                thisVar, 6, "%DATE%", -1) == 2) &&
1242                 (GetEnvironmentVariable("DATE", thisVarContents, 1) == 0) &&
1243                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1244
1245       GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
1246                     NULL, thisVarContents, MAXSTRING);
1247       len = strlen(thisVarContents);
1248
1249     } else if ((CompareString (LOCALE_USER_DEFAULT,
1250                                NORM_IGNORECASE | SORT_STRINGSORT,
1251                                thisVar, 6, "%TIME%", -1) == 2) &&
1252                 (GetEnvironmentVariable("TIME", thisVarContents, 1) == 0) &&
1253                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1254       GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
1255                         NULL, thisVarContents, MAXSTRING);
1256       len = strlen(thisVarContents);
1257
1258     } else if ((CompareString (LOCALE_USER_DEFAULT,
1259                                NORM_IGNORECASE | SORT_STRINGSORT,
1260                                thisVar, 4, "%CD%", -1) == 2) &&
1261                 (GetEnvironmentVariable("CD", thisVarContents, 1) == 0) &&
1262                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1263       GetCurrentDirectory (MAXSTRING, thisVarContents);
1264       len = strlen(thisVarContents);
1265
1266     } else if ((CompareString (LOCALE_USER_DEFAULT,
1267                                NORM_IGNORECASE | SORT_STRINGSORT,
1268                                thisVar, 8, "%RANDOM%", -1) == 2) &&
1269                 (GetEnvironmentVariable("RANDOM", thisVarContents, 1) == 0) &&
1270                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1271       sprintf(thisVarContents, "%d", rand() % 32768);
1272       len = strlen(thisVarContents);
1273
1274     } else {
1275
1276       len = ExpandEnvironmentStrings(thisVar, thisVarContents,
1277                                       sizeof(thisVarContents));
1278     }
1279
1280     if (len == 0)
1281       return endOfVar+1;
1282
1283     /* In a batch program, unknown env vars are replaced with nothing,
1284          note syntax %garbage:1,3% results in anything after the ':'
1285          except the %
1286        From the command line, you just get back what you entered      */
1287     if (lstrcmpi(thisVar, thisVarContents) == 0) {
1288
1289       /* Restore the complex part after the compare */
1290       if (colonpos) {
1291         *colonpos = ':';
1292         *(colonpos+1) = savedchar;
1293       }
1294
1295       s = strdup (endOfVar + 1);
1296
1297       /* Command line - just ignore this */
1298       if (context == NULL) return endOfVar+1;
1299
1300       /* Batch - replace unknown env var with nothing */
1301       if (colonpos == NULL) {
1302         strcpy (start, s);
1303
1304       } else {
1305         len = strlen(thisVar);
1306         thisVar[len-1] = 0x00;
1307         /* If %:...% supplied, : is retained */
1308         if (colonpos == thisVar+1) {
1309           strcpy (start, colonpos);
1310         } else {
1311           strcpy (start, colonpos+1);
1312         }
1313         strcat (start, s);
1314       }
1315       free (s);
1316       return start;
1317
1318     }
1319
1320     /* See if we need to do complex substitution (any ':'s), if not
1321        then our work here is done                                  */
1322     if (colonpos == NULL) {
1323       s = strdup (endOfVar + 1);
1324       strcpy (start, thisVarContents);
1325       strcat (start, s);
1326       free(s);
1327       return start;
1328     }
1329
1330     /* Restore complex bit */
1331     *colonpos = ':';
1332     *(colonpos+1) = savedchar;
1333
1334     /*
1335         Handle complex substitutions:
1336            xxx=yyy    (replace xxx with yyy)
1337            *xxx=yyy   (replace up to and including xxx with yyy)
1338            ~x         (from x chars in)
1339            ~-x        (from x chars from the end)
1340            ~x,y       (from x chars in for y characters)
1341            ~x,-y      (from x chars in until y characters from the end)
1342      */
1343
1344     /* ~ is substring manipulation */
1345     if (savedchar == '~') {
1346
1347       int   substrposition, substrlength;
1348       char *commapos = strchr(colonpos+2, ',');
1349       char *startCopy;
1350
1351       substrposition = atol(colonpos+2);
1352       if (commapos) substrlength = atol(commapos+1);
1353
1354       s = strdup (endOfVar + 1);
1355
1356       /* Check bounds */
1357       if (substrposition >= 0) {
1358         startCopy = &thisVarContents[min(substrposition, len)];
1359       } else {
1360         startCopy = &thisVarContents[max(0, len+substrposition-1)];
1361       }
1362
1363       if (commapos == NULL) {
1364         strcpy (start, startCopy); /* Copy the lot */
1365       } else if (substrlength < 0) {
1366
1367         int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
1368         if (copybytes > len) copybytes = len;
1369         else if (copybytes < 0) copybytes = 0;
1370         strncpy (start, startCopy, copybytes); /* Copy the lot */
1371         start[copybytes] = 0x00;
1372       } else {
1373         strncpy (start, startCopy, substrlength); /* Copy the lot */
1374         start[substrlength] = 0x00;
1375       }
1376
1377       strcat (start, s);
1378       free(s);
1379       return start;
1380
1381     /* search and replace manipulation */
1382     } else {
1383       char *equalspos = strstr(colonpos, "=");
1384       char *replacewith = equalspos+1;
1385       char *found       = NULL;
1386       char *searchIn;
1387       char *searchFor;
1388
1389       s = strdup (endOfVar + 1);
1390       if (equalspos == NULL) return start+1;
1391
1392       /* Null terminate both strings */
1393       thisVar[strlen(thisVar)-1] = 0x00;
1394       *equalspos = 0x00;
1395
1396       /* Since we need to be case insensitive, copy the 2 buffers */
1397       searchIn  = strdup(thisVarContents);
1398       CharUpperBuff(searchIn, strlen(thisVarContents));
1399       searchFor = strdup(colonpos+1);
1400       CharUpperBuff(searchFor, strlen(colonpos+1));
1401
1402
1403       /* Handle wildcard case */
1404       if (*(colonpos+1) == '*') {
1405         /* Search for string to replace */
1406         found = strstr(searchIn, searchFor+1);
1407
1408         if (found) {
1409           /* Do replacement */
1410           strcpy(start, replacewith);
1411           strcat(start, thisVarContents + (found-searchIn) + strlen(searchFor+1));
1412           strcat(start, s);
1413           free(s);
1414         } else {
1415           /* Copy as it */
1416           strcpy(start, thisVarContents);
1417           strcat(start, s);
1418         }
1419
1420       } else {
1421         /* Loop replacing all instances */
1422         char *lastFound = searchIn;
1423         char *outputposn = start;
1424
1425         *start = 0x00;
1426         while ((found = strstr(lastFound, searchFor))) {
1427             strncpy(outputposn,
1428                     thisVarContents + (lastFound-searchIn),
1429                     (found - lastFound));
1430             outputposn  = outputposn + (found - lastFound);
1431             *outputposn = 0x00;
1432             strcat(outputposn, replacewith);
1433             outputposn = outputposn + strlen(replacewith);
1434             lastFound = found + strlen(searchFor);
1435         }
1436         strcat(outputposn,
1437                 thisVarContents + (lastFound-searchIn));
1438         strcat(outputposn, s);
1439       }
1440       free(searchIn);
1441       free(searchFor);
1442       return start;
1443     }
1444     return start+1;
1445 }