cmd: Add support for calling a built in command.
[wine] / programs / cmd / batch.c
1 /*
2  * CMD - Wine-compatible command line interface - batch interface.
3  *
4  * Copyright (C) 1999 D A Pickles
5  * Copyright (C) 2007 J Edmeades
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21
22 #include "wcmd.h"
23 #include "wine/debug.h"
24
25 extern struct env_stack *saved_environment;
26
27 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
28
29 /****************************************************************************
30  * WCMD_batch
31  *
32  * Open and execute a batch file.
33  * On entry *command includes the complete command line beginning with the name
34  * of the batch file (if a CALL command was entered the CALL has been removed).
35  * *file is the name of the file, which might not exist and may not have the
36  * .BAT suffix on. Called is 1 for a CALL, 0 otherwise.
37  *
38  * We need to handle recursion correctly, since one batch program might call another.
39  * So parameters for this batch file are held in a BATCH_CONTEXT structure.
40  *
41  * To support call within the same batch program, another input parameter is
42  * a label to goto once opened.
43  */
44
45 void WCMD_batch (WCHAR *file, WCHAR *command, BOOL called, WCHAR *startLabel, HANDLE pgmHandle)
46 {
47   HANDLE h = INVALID_HANDLE_VALUE;
48   BATCH_CONTEXT *prev_context;
49
50   if (startLabel == NULL) {
51     h = CreateFileW (file, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
52                      NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
53     if (h == INVALID_HANDLE_VALUE) {
54       SetLastError (ERROR_FILE_NOT_FOUND);
55       WCMD_print_error ();
56       return;
57     }
58   } else {
59     DuplicateHandle(GetCurrentProcess(), pgmHandle,
60                     GetCurrentProcess(), &h,
61                     0, FALSE, DUPLICATE_SAME_ACCESS);
62   }
63
64 /*
65  *      Create a context structure for this batch file.
66  */
67
68   prev_context = context;
69   context = LocalAlloc (LMEM_FIXED, sizeof (BATCH_CONTEXT));
70   context -> h = h;
71   context->batchfileW = WCMD_strdupW(file);
72   context -> command = command;
73   memset(context -> shift_count, 0x00, sizeof(context -> shift_count));
74   context -> prev_context = prev_context;
75   context -> skip_rest = FALSE;
76
77   /* If processing a call :label, 'goto' the label in question */
78   if (startLabel) {
79     strcpyW(param1, startLabel);
80     WCMD_goto(NULL);
81   }
82
83 /*
84  *      Work through the file line by line. Specific batch commands are processed here,
85  *      the rest are handled by the main command processor.
86  */
87
88   while (context -> skip_rest == FALSE) {
89       CMD_LIST *toExecute = NULL;         /* Commands left to be executed */
90       if (!WCMD_ReadAndParseLine(NULL, &toExecute, h))
91         break;
92       /* Note: although this batch program itself may be called, we are not retrying
93          the command as a result of a call failing to find a program, hence the
94          retryCall parameter below is FALSE                                           */
95       WCMD_process_commands(toExecute, FALSE, NULL, NULL, FALSE);
96       WCMD_free_commands(toExecute);
97       toExecute = NULL;
98   }
99   CloseHandle (h);
100
101 /*
102  *  If there are outstanding setlocal's to the current context, unwind them.
103  */
104   while (saved_environment && saved_environment->batchhandle == context->h) {
105       WCMD_endlocal();
106   }
107
108 /*
109  *      If invoked by a CALL, we return to the context of our caller. Otherwise return
110  *      to the caller's caller.
111  */
112
113   HeapFree(GetProcessHeap(), 0, context->batchfileW);
114   LocalFree (context);
115   if ((prev_context != NULL) && (!called)) {
116     prev_context -> skip_rest = TRUE;
117     context = prev_context;
118   }
119   context = prev_context;
120 }
121
122 /*******************************************************************
123  * WCMD_parameter
124  *
125  * Extracts a delimited parameter from an input string
126  *
127  * PARAMS
128  *  s     [I] input string, non NULL
129  *  n     [I] # of the parameter to return, counted from 0
130  *  start [O] Optional. Pointer to the first char of param n in s
131  *  end   [O] Optional. Pointer to the last char of param n in s
132  *  raw   [I] True to return the parameter in raw format (quotes maintained)
133  *            False returns the parameter with quotes stripped
134  *  wholecmdline [I] True to indicate this routine is being used to parse the
135  *                   command line, and special logic for arg0->1 transition
136  *                   needs to be applied.
137  *
138  * RETURNS
139  *  Success: The nth delimited parameter found in s
140  *           if start != NULL, *start points to the start of the param
141  *           if end != NULL, *end points to the end of the param
142  *  Failure: An empty string if the param is not found.
143  *           *start == *end == NULL
144  *
145  * NOTES
146  *  Return value is stored in static storage (i.e. overwritten after each call).
147  *  Specify 'start' and/or 'end' to include delimiting double quotes as well, if any.
148  *  By default, the parameter is returned with quotes removed, ready for use with
149  *  other API calls, e.g. c:\"a b"\c is returned as c:\a b\c. However, some commands
150  *  need to preserve the exact syntax (echo, for, etc) hence the raw option.
151  */
152 WCHAR *WCMD_parameter (WCHAR *s, int n, WCHAR **start, WCHAR **end, BOOL raw,
153                        BOOL wholecmdline)
154 {
155     static const WCHAR defaultDelims[] = { ' ', '\t', ',', '=', ';', '\0' };
156     int curParamNb = 0;
157     static WCHAR param[MAX_PATH];
158     WCHAR *p = s, *begin;
159
160     if (start != NULL) *start = NULL;
161     if (end != NULL) *end = NULL;
162     param[0] = '\0';
163
164     while (TRUE) {
165
166         /* Absorb repeated word delimiters until we get to the next token (or the end!) */
167         while (*p && (strchrW(defaultDelims, *p) != NULL))
168             p++;
169         if (*p == '\0') return param;
170
171         /* If we have reached the token number we want, remember the beginning of it */
172         if (start != NULL && curParamNb == n) *start = p;
173
174         /* Return the whole word up to the next delimiter, handling quotes in the middle
175            of it, e.g. a"\b c\"d is a single parameter.                                  */
176         begin = p;
177
178         /* Loop character by character, but just need to special case quotes */
179         while (*p) {
180             /* Once we have found a delimiter, break */
181             if (strchrW(defaultDelims, *p) != NULL) break;
182
183             /* Very odd special case - Seems as if a ( acts as a delimiter which is
184                not swallowed but is effective only when it comes between the program
185                name and the parameters. Need to avoid this triggering when used
186                to walk parameters generally.                                         */
187             if (wholecmdline && curParamNb == 0 && *p=='(') break;
188
189             /* If we find a quote, copy until we get the end quote */
190             if (*p == '"') {
191                 p++;
192                 while (*p && *p != '"') p++;
193             }
194
195             /* Now skip the character / quote */
196             if (*p) p++;
197         }
198
199         if (curParamNb == n) {
200             /* Return the parameter in static storage either as-is (raw) or
201                suitable for use with other win32 api calls (quotes stripped) */
202             if (raw) {
203                 memcpy(param, begin, (p - begin) * sizeof(WCHAR));
204                 param[p-begin] = '\0';
205             } else {
206                 int i=0;
207                 while (begin < p) {
208                   if (*begin != '"') param[i++] = *begin;
209                   begin++;
210                 }
211                 param[i] = '\0';
212             }
213             if (end) *end = p - 1;
214             return param;
215         }
216         curParamNb++;
217     }
218 }
219
220 /****************************************************************************
221  * WCMD_fgets
222  *
223  * Gets one line from a file/console and puts it into buffer buf
224  * Pre:  buf has size noChars
225  *       1 <= noChars <= MAXSTRING
226  * Post: buf is filled with at most noChars-1 characters, and gets nul-terminated
227          buf does not include EOL terminator
228  * Returns:
229  *       buf on success
230  *       NULL on error or EOF
231  */
232
233 WCHAR *WCMD_fgets(WCHAR *buf, DWORD noChars, HANDLE h)
234 {
235   DWORD charsRead;
236   BOOL status;
237   LARGE_INTEGER filepos;
238   DWORD i;
239
240   /* We can't use the native f* functions because of the filename syntax differences
241      between DOS and Unix. Also need to lose the LF (or CRLF) from the line. */
242
243   if (!WCMD_is_console_handle(h)) {
244     /* Save current file position */
245     filepos.QuadPart = 0;
246     SetFilePointerEx(h, filepos, &filepos, FILE_CURRENT);
247   }
248
249   status = WCMD_ReadFile(h, buf, noChars, &charsRead);
250   if (!status || charsRead == 0) return NULL;
251
252   /* Find first EOL */
253   for (i = 0; i < charsRead; i++) {
254     if (buf[i] == '\n' || buf[i] == '\r')
255       break;
256   }
257
258   if (!WCMD_is_console_handle(h) && i != charsRead) {
259     /* Sets file pointer to the start of the next line, if any */
260     filepos.QuadPart += i + 1 + (buf[i] == '\r' ? 1 : 0);
261     SetFilePointerEx(h, filepos, NULL, FILE_BEGIN);
262   }
263
264   /* Truncate at EOL (or end of buffer) */
265   if (i == noChars)
266     i--;
267
268   buf[i] = '\0';
269
270   return buf;
271 }
272
273 /* WCMD_splitpath - copied from winefile as no obvious way to use it otherwise */
274 void WCMD_splitpath(const WCHAR* path, WCHAR* drv, WCHAR* dir, WCHAR* name, WCHAR* ext)
275 {
276         const WCHAR* end; /* end of processed string */
277         const WCHAR* p;  /* search pointer */
278         const WCHAR* s;  /* copy pointer */
279
280         /* extract drive name */
281         if (path[0] && path[1]==':') {
282                 if (drv) {
283                         *drv++ = *path++;
284                         *drv++ = *path++;
285                         *drv = '\0';
286                 }
287         } else if (drv)
288                 *drv = '\0';
289
290         end = path + strlenW(path);
291
292         /* search for begin of file extension */
293         for(p=end; p>path && *--p!='\\' && *p!='/'; )
294                 if (*p == '.') {
295                         end = p;
296                         break;
297                 }
298
299         if (ext)
300                 for(s=end; (*ext=*s++); )
301                         ext++;
302
303         /* search for end of directory name */
304         for(p=end; p>path; )
305                 if (*--p=='\\' || *p=='/') {
306                         p++;
307                         break;
308                 }
309
310         if (name) {
311                 for(s=p; s<end; )
312                         *name++ = *s++;
313
314                 *name = '\0';
315         }
316
317         if (dir) {
318                 for(s=path; s<p; )
319                         *dir++ = *s++;
320
321                 *dir = '\0';
322         }
323 }
324
325 /****************************************************************************
326  * WCMD_HandleTildaModifiers
327  *
328  * Handle the ~ modifiers when expanding %0-9 or (%a-z in for command)
329  *    %~xxxxxV  (V=0-9 or A-Z)
330  * Where xxxx is any combination of:
331  *    ~ - Removes quotes
332  *    f - Fully qualified path (assumes current dir if not drive\dir)
333  *    d - drive letter
334  *    p - path
335  *    n - filename
336  *    x - file extension
337  *    s - path with shortnames
338  *    a - attributes
339  *    t - date/time
340  *    z - size
341  *    $ENVVAR: - Searches ENVVAR for (contents of V) and expands to fully
342  *                   qualified path
343  *
344  *  To work out the length of the modifier:
345  *
346  *  Note: In the case of %0-9 knowing the end of the modifier is easy,
347  *    but in a for loop, the for end WCHARacter may also be a modifier
348  *    eg. for %a in (c:\a.a) do echo XXX
349  *             where XXX = %~a    (just ~)
350  *                         %~aa   (~ and attributes)
351  *                         %~aaxa (~, attributes and extension)
352  *                   BUT   %~aax  (~ and attributes followed by 'x')
353  *
354  *  Hence search forwards until find an invalid modifier, and then
355  *  backwards until find for variable or 0-9
356  */
357 void WCMD_HandleTildaModifiers(WCHAR **start, const WCHAR *forVariable,
358                                const WCHAR *forValue, BOOL justFors) {
359
360 #define NUMMODIFIERS 11
361   static const WCHAR validmodifiers[NUMMODIFIERS] = {
362         '~', 'f', 'd', 'p', 'n', 'x', 's', 'a', 't', 'z', '$'
363   };
364
365   WIN32_FILE_ATTRIBUTE_DATA fileInfo;
366   WCHAR  outputparam[MAX_PATH];
367   WCHAR  finaloutput[MAX_PATH];
368   WCHAR  fullfilename[MAX_PATH];
369   WCHAR  thisoutput[MAX_PATH];
370   WCHAR  *pos            = *start+1;
371   WCHAR  *firstModifier  = pos;
372   WCHAR  *lastModifier   = NULL;
373   int   modifierLen     = 0;
374   BOOL  finished        = FALSE;
375   int   i               = 0;
376   BOOL  exists          = TRUE;
377   BOOL  skipFileParsing = FALSE;
378   BOOL  doneModifier    = FALSE;
379
380   /* Search forwards until find invalid character modifier */
381   while (!finished) {
382
383     /* Work on the previous character */
384     if (lastModifier != NULL) {
385
386       for (i=0; i<NUMMODIFIERS; i++) {
387         if (validmodifiers[i] == *lastModifier) {
388
389           /* Special case '$' to skip until : found */
390           if (*lastModifier == '$') {
391             while (*pos != ':' && *pos) pos++;
392             if (*pos == 0x00) return; /* Invalid syntax */
393             pos++;                    /* Skip ':'       */
394           }
395           break;
396         }
397       }
398
399       if (i==NUMMODIFIERS) {
400         finished = TRUE;
401       }
402     }
403
404     /* Save this one away */
405     if (!finished) {
406       lastModifier = pos;
407       pos++;
408     }
409   }
410
411   while (lastModifier > firstModifier) {
412     WINE_TRACE("Looking backwards for parameter id: %s / %s\n",
413                wine_dbgstr_w(lastModifier), wine_dbgstr_w(forVariable));
414
415     if (!justFors && context && (*lastModifier >= '0' && *lastModifier <= '9')) {
416       /* Its a valid parameter identifier - OK */
417       break;
418
419     } else if (forVariable && *lastModifier == *(forVariable+1)) {
420       /* Its a valid parameter identifier - OK */
421       break;
422
423     } else {
424       lastModifier--;
425     }
426   }
427   if (lastModifier == firstModifier) return; /* Invalid syntax */
428
429   /* Extract the parameter to play with */
430   if (*lastModifier == '0') {
431     strcpyW(outputparam, context->batchfileW);
432   } else if ((*lastModifier >= '1' && *lastModifier <= '9')) {
433     strcpyW(outputparam,
434             WCMD_parameter (context -> command,
435                             *lastModifier-'0' + context -> shift_count[*lastModifier-'0'],
436                             NULL, NULL, FALSE, TRUE));
437   } else {
438     strcpyW(outputparam, forValue);
439   }
440
441   /* So now, firstModifier points to beginning of modifiers, lastModifier
442      points to the variable just after the modifiers. Process modifiers
443      in a specific order, remembering there could be duplicates           */
444   modifierLen = lastModifier - firstModifier;
445   finaloutput[0] = 0x00;
446
447   /* 1. Handle '~' : Strip surrounding quotes */
448   if (outputparam[0]=='"' &&
449       memchrW(firstModifier, '~', modifierLen) != NULL) {
450     int len = strlenW(outputparam);
451     if (outputparam[len-1] == '"') {
452         outputparam[len-1]=0x00;
453         len = len - 1;
454     }
455     memmove(outputparam, &outputparam[1], (len * sizeof(WCHAR))-1);
456   }
457
458   /* 2. Handle the special case of a $ */
459   if (memchrW(firstModifier, '$', modifierLen) != NULL) {
460     /* Special Case: Search envar specified in $[envvar] for outputparam
461        Note both $ and : are guaranteed otherwise check above would fail */
462     WCHAR *begin = strchrW(firstModifier, '$') + 1;
463     WCHAR *end   = strchrW(firstModifier, ':');
464     WCHAR env[MAX_PATH];
465     WCHAR fullpath[MAX_PATH];
466
467     /* Extract the env var */
468     memcpy(env, begin, (end-begin) * sizeof(WCHAR));
469     env[(end-begin)] = 0x00;
470
471     /* If env var not found, return empty string */
472     if ((GetEnvironmentVariableW(env, fullpath, MAX_PATH) == 0) ||
473         (SearchPathW(fullpath, outputparam, NULL, MAX_PATH, outputparam, NULL) == 0)) {
474       finaloutput[0] = 0x00;
475       outputparam[0] = 0x00;
476       skipFileParsing = TRUE;
477     }
478   }
479
480   /* After this, we need full information on the file,
481     which is valid not to exist.  */
482   if (!skipFileParsing) {
483     if (GetFullPathNameW(outputparam, MAX_PATH, fullfilename, NULL) == 0)
484       return;
485
486     exists = GetFileAttributesExW(fullfilename, GetFileExInfoStandard,
487                                   &fileInfo);
488
489     /* 2. Handle 'a' : Output attributes (File doesn't have to exist) */
490     if (memchrW(firstModifier, 'a', modifierLen) != NULL) {
491
492       WCHAR defaults[] = {'-','-','-','-','-','-','-','-','-','\0'};
493       doneModifier = TRUE;
494
495       if (exists) {
496         strcpyW(thisoutput, defaults);
497         if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
498           thisoutput[0]='d';
499         if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
500           thisoutput[1]='r';
501         if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
502           thisoutput[2]='a';
503         if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
504           thisoutput[3]='h';
505         if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
506           thisoutput[4]='s';
507         if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED)
508           thisoutput[5]='c';
509         /* FIXME: What are 6 and 7? */
510         if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
511           thisoutput[8]='l';
512         strcatW(finaloutput, thisoutput);
513       }
514     }
515
516     /* 3. Handle 't' : Date+time (File doesn't have to exist) */
517     if (memchrW(firstModifier, 't', modifierLen) != NULL) {
518
519       SYSTEMTIME systime;
520       int datelen;
521
522       doneModifier = TRUE;
523
524       if (exists) {
525         if (finaloutput[0] != 0x00) strcatW(finaloutput, spaceW);
526
527         /* Format the time */
528         FileTimeToSystemTime(&fileInfo.ftLastWriteTime, &systime);
529         GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &systime,
530                           NULL, thisoutput, MAX_PATH);
531         strcatW(thisoutput, spaceW);
532         datelen = strlenW(thisoutput);
533         GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &systime,
534                           NULL, (thisoutput+datelen), MAX_PATH-datelen);
535         strcatW(finaloutput, thisoutput);
536       }
537     }
538
539     /* 4. Handle 'z' : File length (File doesn't have to exist) */
540     if (memchrW(firstModifier, 'z', modifierLen) != NULL) {
541       /* FIXME: Output full 64 bit size (sprintf does not support I64 here) */
542       ULONG/*64*/ fullsize = /*(fileInfo.nFileSizeHigh << 32) +*/
543                                   fileInfo.nFileSizeLow;
544       static const WCHAR fmt[] = {'%','u','\0'};
545
546       doneModifier = TRUE;
547       if (exists) {
548         if (finaloutput[0] != 0x00) strcatW(finaloutput, spaceW);
549         wsprintfW(thisoutput, fmt, fullsize);
550         strcatW(finaloutput, thisoutput);
551       }
552     }
553
554     /* 4. Handle 's' : Use short paths (File doesn't have to exist) */
555     if (memchrW(firstModifier, 's', modifierLen) != NULL) {
556       if (finaloutput[0] != 0x00) strcatW(finaloutput, spaceW);
557       /* Don't flag as doneModifier - %~s on its own is processed later */
558       GetShortPathNameW(outputparam, outputparam, sizeof(outputparam)/sizeof(outputparam[0]));
559     }
560
561     /* 5. Handle 'f' : Fully qualified path (File doesn't have to exist) */
562     /*      Note this overrides d,p,n,x                                 */
563     if (memchrW(firstModifier, 'f', modifierLen) != NULL) {
564       doneModifier = TRUE;
565       if (finaloutput[0] != 0x00) strcatW(finaloutput, spaceW);
566       strcatW(finaloutput, fullfilename);
567     } else {
568
569       WCHAR drive[10];
570       WCHAR dir[MAX_PATH];
571       WCHAR fname[MAX_PATH];
572       WCHAR ext[MAX_PATH];
573       BOOL doneFileModifier = FALSE;
574       BOOL addSpace = (finaloutput[0] != 0x00);
575
576       /* Split into components */
577       WCMD_splitpath(fullfilename, drive, dir, fname, ext);
578
579       /* 5. Handle 'd' : Drive Letter */
580       if (memchrW(firstModifier, 'd', modifierLen) != NULL) {
581         if (addSpace) {
582           strcatW(finaloutput, spaceW);
583           addSpace = FALSE;
584         }
585
586         strcatW(finaloutput, drive);
587         doneModifier = TRUE;
588         doneFileModifier = TRUE;
589       }
590
591       /* 6. Handle 'p' : Path */
592       if (memchrW(firstModifier, 'p', modifierLen) != NULL) {
593         if (addSpace) {
594           strcatW(finaloutput, spaceW);
595           addSpace = FALSE;
596         }
597
598         strcatW(finaloutput, dir);
599         doneModifier = TRUE;
600         doneFileModifier = TRUE;
601       }
602
603       /* 7. Handle 'n' : Name */
604       if (memchrW(firstModifier, 'n', modifierLen) != NULL) {
605         if (addSpace) {
606           strcatW(finaloutput, spaceW);
607           addSpace = FALSE;
608         }
609
610         strcatW(finaloutput, fname);
611         doneModifier = TRUE;
612         doneFileModifier = TRUE;
613       }
614
615       /* 8. Handle 'x' : Ext */
616       if (memchrW(firstModifier, 'x', modifierLen) != NULL) {
617         if (addSpace) {
618           strcatW(finaloutput, spaceW);
619           addSpace = FALSE;
620         }
621
622         strcatW(finaloutput, ext);
623         doneModifier = TRUE;
624         doneFileModifier = TRUE;
625       }
626
627       /* If 's' but no other parameter, dump the whole thing */
628       if (!doneFileModifier &&
629           memchrW(firstModifier, 's', modifierLen) != NULL) {
630         doneModifier = TRUE;
631         if (finaloutput[0] != 0x00) strcatW(finaloutput, spaceW);
632         strcatW(finaloutput, outputparam);
633       }
634     }
635   }
636
637   /* If No other modifier processed,  just add in parameter */
638   if (!doneModifier) strcpyW(finaloutput, outputparam);
639
640   /* Finish by inserting the replacement into the string */
641   WCMD_strsubstW(*start, lastModifier+1, finaloutput, -1);
642 }
643
644 /*******************************************************************
645  * WCMD_call - processes a batch call statement
646  *
647  *      If there is a leading ':', calls within this batch program
648  *      otherwise launches another program.
649  */
650 void WCMD_call (WCHAR *command) {
651
652   /* Run other program if no leading ':' */
653   if (*command != ':') {
654     WCMD_run_program(command, TRUE);
655     /* If the thing we try to run does not exist, call returns 1 */
656     if (errorlevel) errorlevel=1;
657   } else {
658
659     WCHAR gotoLabel[MAX_PATH];
660
661     strcpyW(gotoLabel, param1);
662
663     if (context) {
664
665       LARGE_INTEGER li;
666
667       /* Save the current file position, call the same file,
668          restore position                                    */
669       li.QuadPart = 0;
670       li.u.LowPart = SetFilePointer(context -> h, li.u.LowPart,
671                      &li.u.HighPart, FILE_CURRENT);
672
673       WCMD_batch (param1, command, TRUE, gotoLabel, context->h);
674
675       SetFilePointer(context -> h, li.u.LowPart,
676                      &li.u.HighPart, FILE_BEGIN);
677     } else {
678       WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_CALLINSCRIPT));
679     }
680   }
681 }