2 * CMD - Wine-compatible command line interface - batch interface.
4 * Copyright (C) 1999 D A Pickles
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.
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.
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
24 extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
25 extern BATCH_CONTEXT *context;
26 extern DWORD errorlevel;
28 /****************************************************************************
31 * Open and execute a batch file.
32 * On entry *command includes the complete command line beginning with the name
33 * of the batch file (if a CALL command was entered the CALL has been removed).
34 * *file is the name of the file, which might not exist and may not have the
35 * .BAT suffix on. Called is 1 for a CALL, 0 otherwise.
37 * We need to handle recursion correctly, since one batch program might call another.
38 * So parameters for this batch file are held in a BATCH_CONTEXT structure.
40 * To support call within the same batch program, another input parameter is
41 * a label to goto once opened.
44 void WCMD_batch (WCHAR *file, WCHAR *command, int called, WCHAR *startLabel, HANDLE pgmHandle) {
46 #define WCMD_BATCH_EXT_SIZE 5
48 HANDLE h = INVALID_HANDLE_VALUE;
49 WCHAR string[MAXSTRING];
50 static const WCHAR extension_batch[][WCMD_BATCH_EXT_SIZE] = {{'.','b','a','t','\0'},
51 {'.','c','m','d','\0'}};
52 static const WCHAR extension_exe[WCMD_BATCH_EXT_SIZE] = {'.','e','x','e','\0'};
54 BATCH_CONTEXT *prev_context;
56 if (startLabel == NULL) {
57 for(i=0; (i<((sizeof(extension_batch) * sizeof(WCHAR))/WCMD_BATCH_EXT_SIZE)) &&
58 (h == INVALID_HANDLE_VALUE); i++) {
59 strcpyW (string, file);
61 if (strstrW (string, extension_batch[i]) == NULL) strcatW (string, extension_batch[i]);
62 h = CreateFile (string, GENERIC_READ, FILE_SHARE_READ,
63 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
65 if (h == INVALID_HANDLE_VALUE) {
66 strcpyW (string, file);
68 if (strstrW (string, extension_exe) == NULL) strcatW (string, extension_exe);
69 h = CreateFile (string, GENERIC_READ, FILE_SHARE_READ,
70 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
71 if (h != INVALID_HANDLE_VALUE) {
72 WCMD_run_program (command, 0);
74 SetLastError (ERROR_FILE_NOT_FOUND);
80 DuplicateHandle(GetCurrentProcess(), pgmHandle,
81 GetCurrentProcess(), &h,
82 0, FALSE, DUPLICATE_SAME_ACCESS);
86 * Create a context structure for this batch file.
89 prev_context = context;
90 context = (BATCH_CONTEXT *)LocalAlloc (LMEM_FIXED, sizeof (BATCH_CONTEXT));
92 context -> command = command;
93 memset(context -> shift_count, 0x00, sizeof(context -> shift_count));
94 context -> prev_context = prev_context;
95 context -> skip_rest = FALSE;
97 /* If processing a call :label, 'goto' the label in question */
99 strcpyW(param1, startLabel);
104 * Work through the file line by line. Specific batch commands are processed here,
105 * the rest are handled by the main command processor.
108 while (context -> skip_rest == FALSE) {
109 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
110 if (WCMD_ReadAndParseLine(NULL, &toExecute, h) == NULL)
112 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
113 WCMD_free_commands(toExecute);
119 * If invoked by a CALL, we return to the context of our caller. Otherwise return
120 * to the caller's caller.
123 LocalFree ((HANDLE)context);
124 if ((prev_context != NULL) && (!called)) {
125 prev_context -> skip_rest = TRUE;
126 context = prev_context;
128 context = prev_context;
131 /*******************************************************************
132 * WCMD_parameter - extract a parameter from a command line.
134 * Returns the 'n'th space-delimited parameter on the command line (zero-based).
135 * Parameter is in static storage overwritten on the next call.
136 * Parameters in quotes (and brackets) are handled.
137 * Also returns a pointer to the location of the parameter in the command line.
140 WCHAR *WCMD_parameter (WCHAR *s, int n, WCHAR **where) {
143 static WCHAR param[MAX_PATH];
146 if (where != NULL) *where = NULL;
154 if (where != NULL && i==n) *where = s;
156 while ((*s != '\0') && (*s != '"')) {
168 /* The code to handle bracketed parms is removed because it should no longer
169 be necessary after the multiline support has been added and the for loop
170 set of data is now parseable individually. */
174 /* Only return where if it is for the right parameter */
175 if (where != NULL && i==n) *where = s;
176 while ((*s != '\0') && (*s != ' ')) {
190 /****************************************************************************
193 * Get one line from a batch file. We can't use the native f* functions because
194 * of the filename syntax differences between DOS and Unix. Also need to lose
195 * the LF (or CRLF) from the line.
198 WCHAR *WCMD_fgets (WCHAR *s, int noChars, HANDLE h) {
206 status = WCMD_ReadFile (h, s, 1, &bytes, NULL);
207 if ((status == 0) || ((bytes == 0) && (s == p))) return NULL;
208 if (*s == '\n') bytes = 0;
209 else if (*s != '\r') {
214 } while ((bytes == 1) && (noChars > 1));
218 /* WCMD_splitpath - copied from winefile as no obvious way to use it otherwise */
219 void WCMD_splitpath(const WCHAR* path, WCHAR* drv, WCHAR* dir, WCHAR* name, WCHAR* ext)
221 const WCHAR* end; /* end of processed string */
222 const WCHAR* p; /* search pointer */
223 const WCHAR* s; /* copy pointer */
225 /* extract drive name */
226 if (path[0] && path[1]==':') {
235 /* search for end of string or stream separator */
236 for(end=path; *end && *end!=':'; )
239 /* search for begin of file extension */
240 for(p=end; p>path && *--p!='\\' && *p!='/'; )
247 for(s=end; (*ext=*s++); )
250 /* search for end of directory name */
252 if (*--p=='\\' || *p=='/') {
272 /****************************************************************************
273 * WCMD_HandleTildaModifiers
275 * Handle the ~ modifiers when expanding %0-9 or (%a-z in for command)
276 * %~xxxxxV (V=0-9 or A-Z)
277 * Where xxxx is any combination of:
279 * f - Fully qualified path (assumes current dir if not drive\dir)
284 * s - path with shortnames
288 * $ENVVAR: - Searches ENVVAR for (contents of V) and expands to fully
291 * To work out the length of the modifier:
293 * Note: In the case of %0-9 knowing the end of the modifier is easy,
294 * but in a for loop, the for end WCHARacter may also be a modifier
295 * eg. for %a in (c:\a.a) do echo XXX
296 * where XXX = %~a (just ~)
297 * %~aa (~ and attributes)
298 * %~aaxa (~, attributes and extension)
299 * BUT %~aax (~ and attributes followed by 'x')
301 * Hence search forwards until find an invalid modifier, and then
302 * backwards until find for variable or 0-9
304 void WCMD_HandleTildaModifiers(WCHAR **start, WCHAR *forVariable) {
306 #define NUMMODIFIERS 11
307 static const WCHAR validmodifiers[NUMMODIFIERS] = {
308 '~', 'f', 'd', 'p', 'n', 'x', 's', 'a', 't', 'z', '$'
310 static const WCHAR space[] = {' ', '\0'};
312 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
313 WCHAR outputparam[MAX_PATH];
314 WCHAR finaloutput[MAX_PATH];
315 WCHAR fullfilename[MAX_PATH];
316 WCHAR thisoutput[MAX_PATH];
317 WCHAR *pos = *start+1;
318 WCHAR *firstModifier = pos;
319 WCHAR *lastModifier = NULL;
321 BOOL finished = FALSE;
324 BOOL skipFileParsing = FALSE;
325 BOOL doneModifier = FALSE;
327 /* Search forwards until find invalid WCHARacter modifier */
330 /* Work on the previous WCHARacter */
331 if (lastModifier != NULL) {
333 for (i=0; i<NUMMODIFIERS; i++) {
334 if (validmodifiers[i] == *lastModifier) {
336 /* Special case '$' to skip until : found */
337 if (*lastModifier == '$') {
338 while (*pos != ':' && *pos) pos++;
339 if (*pos == 0x00) return; /* Invalid syntax */
340 pos++; /* Skip ':' */
346 if (i==NUMMODIFIERS) {
351 /* Save this one away */
358 /* Now make sure the position we stopped at is a valid parameter */
359 if (!(*lastModifier >= '0' || *lastModifier <= '9') &&
360 (forVariable != NULL) &&
361 (toupperW(*lastModifier) != toupperW(*forVariable))) {
363 /* Its not... Step backwards until it matches or we get to the start */
364 while (toupperW(*lastModifier) != toupperW(*forVariable) &&
365 lastModifier > firstModifier) {
368 if (lastModifier == firstModifier) return; /* Invalid syntax */
371 /* Extract the parameter to play with */
372 if ((*lastModifier >= '0' && *lastModifier <= '9')) {
373 strcpyW(outputparam, WCMD_parameter (context -> command,
374 *lastModifier-'0' + context -> shift_count[*lastModifier-'0'], NULL));
376 /* FIXME: Retrieve 'for' variable %c\n", *lastModifier); */
377 /* Need to get 'for' loop variable into outputparam */
381 /* So now, firstModifier points to beginning of modifiers, lastModifier
382 points to the variable just after the modifiers. Process modifiers
383 in a specific order, remembering there could be duplicates */
384 modifierLen = lastModifier - firstModifier;
385 finaloutput[0] = 0x00;
387 /* Useful for debugging purposes: */
388 /*printf("Modifier string '%*.*s' and variable is %c\n Param starts as '%s'\n",
389 (modifierLen), (modifierLen), firstModifier, *lastModifier,
392 /* 1. Handle '~' : Strip surrounding quotes */
393 if (outputparam[0]=='"' &&
394 memchrW(firstModifier, '~', modifierLen) != NULL) {
395 int len = strlenW(outputparam);
396 if (outputparam[len-1] == '"') {
397 outputparam[len-1]=0x00;
400 memmove(outputparam, &outputparam[1], (len * sizeof(WCHAR))-1);
403 /* 2. Handle the special case of a $ */
404 if (memchrW(firstModifier, '$', modifierLen) != NULL) {
405 /* Special Case: Search envar specified in $[envvar] for outputparam
406 Note both $ and : are guaranteed otherwise check above would fail */
407 WCHAR *start = strchrW(firstModifier, '$') + 1;
408 WCHAR *end = strchrW(firstModifier, ':');
410 WCHAR fullpath[MAX_PATH];
412 /* Extract the env var */
413 memcpy(env, start, (end-start) * sizeof(WCHAR));
414 env[(end-start)] = 0x00;
416 /* If env var not found, return emptry string */
417 if ((GetEnvironmentVariable(env, fullpath, MAX_PATH) == 0) ||
418 (SearchPath(fullpath, outputparam, NULL,
419 MAX_PATH, outputparam, NULL) == 0)) {
420 finaloutput[0] = 0x00;
421 outputparam[0] = 0x00;
422 skipFileParsing = TRUE;
426 /* After this, we need full information on the file,
427 which is valid not to exist. */
428 if (!skipFileParsing) {
429 if (GetFullPathName(outputparam, MAX_PATH, fullfilename, NULL) == 0)
432 exists = GetFileAttributesExW(fullfilename, GetFileExInfoStandard,
435 /* 2. Handle 'a' : Output attributes */
437 memchrW(firstModifier, 'a', modifierLen) != NULL) {
439 WCHAR defaults[] = {'-','-','-','-','-','-','-','-','-','\0'};
441 strcpyW(thisoutput, defaults);
442 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
444 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
446 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
448 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
450 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
452 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED)
454 /* FIXME: What are 6 and 7? */
455 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
457 strcatW(finaloutput, thisoutput);
460 /* 3. Handle 't' : Date+time */
462 memchrW(firstModifier, 't', modifierLen) != NULL) {
468 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
470 /* Format the time */
471 FileTimeToSystemTime(&fileInfo.ftLastWriteTime, &systime);
472 GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &systime,
473 NULL, thisoutput, MAX_PATH);
474 strcatW(thisoutput, space);
475 datelen = strlenW(thisoutput);
476 GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &systime,
477 NULL, (thisoutput+datelen), MAX_PATH-datelen);
478 strcatW(finaloutput, thisoutput);
481 /* 4. Handle 'z' : File length */
483 memchrW(firstModifier, 'z', modifierLen) != NULL) {
484 /* FIXME: Output full 64 bit size (sprintf does not support I64 here) */
485 ULONG/*64*/ fullsize = /*(fileInfo.nFileSizeHigh << 32) +*/
486 fileInfo.nFileSizeLow;
487 static const WCHAR fmt[] = {'%','u','\0'};
490 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
491 wsprintf(thisoutput, fmt, fullsize);
492 strcatW(finaloutput, thisoutput);
495 /* 4. Handle 's' : Use short paths (File doesn't have to exist) */
496 if (memchrW(firstModifier, 's', modifierLen) != NULL) {
497 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
498 /* Don't flag as doneModifier - %~s on its own is processed later */
499 GetShortPathName(outputparam, outputparam, sizeof(outputparam));
502 /* 5. Handle 'f' : Fully qualified path (File doesn't have to exist) */
503 /* Note this overrides d,p,n,x */
504 if (memchrW(firstModifier, 'f', modifierLen) != NULL) {
506 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
507 strcatW(finaloutput, fullfilename);
512 WCHAR fname[MAX_PATH];
514 BOOL doneFileModifier = FALSE;
516 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
518 /* Split into components */
519 WCMD_splitpath(fullfilename, drive, dir, fname, ext);
521 /* 5. Handle 'd' : Drive Letter */
522 if (memchrW(firstModifier, 'd', modifierLen) != NULL) {
523 strcatW(finaloutput, drive);
525 doneFileModifier = TRUE;
528 /* 6. Handle 'p' : Path */
529 if (memchrW(firstModifier, 'p', modifierLen) != NULL) {
530 strcatW(finaloutput, dir);
532 doneFileModifier = TRUE;
535 /* 7. Handle 'n' : Name */
536 if (memchrW(firstModifier, 'n', modifierLen) != NULL) {
537 strcatW(finaloutput, fname);
539 doneFileModifier = TRUE;
542 /* 8. Handle 'x' : Ext */
543 if (memchrW(firstModifier, 'x', modifierLen) != NULL) {
544 strcatW(finaloutput, ext);
546 doneFileModifier = TRUE;
549 /* If 's' but no other parameter, dump the whole thing */
550 if (!doneFileModifier &&
551 memchrW(firstModifier, 's', modifierLen) != NULL) {
553 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
554 strcatW(finaloutput, outputparam);
559 /* If No other modifier processed, just add in parameter */
560 if (!doneModifier) strcpyW(finaloutput, outputparam);
562 /* Finish by inserting the replacement into the string */
563 pos = WCMD_strdupW(lastModifier+1);
564 strcpyW(*start, finaloutput);
565 strcatW(*start, pos);
569 /*******************************************************************
570 * WCMD_call - processes a batch call statement
572 * If there is a leading ':', calls within this batch program
573 * otherwise launches another program.
575 void WCMD_call (WCHAR *command) {
577 /* Run other program if no leading ':' */
578 if (*command != ':') {
579 WCMD_run_program(command, 1);
582 WCHAR gotoLabel[MAX_PATH];
584 strcpyW(gotoLabel, param1);
590 /* Save the current file position, call the same file,
593 li.u.LowPart = SetFilePointer(context -> h, li.u.LowPart,
594 &li.u.HighPart, FILE_CURRENT);
596 WCMD_batch (param1, command, 1, gotoLabel, context->h);
598 SetFilePointer(context -> h, li.u.LowPart,
599 &li.u.HighPart, FILE_BEGIN);
601 WCMD_output_asis( WCMD_LoadMessage(WCMD_CALLINSCRIPT));