batch.c 23 KB

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