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 char 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 (char *file, char *command, int called, char *startLabel, HANDLE pgmHandle) {
46 #define WCMD_BATCH_EXT_SIZE 5
48 HANDLE h = INVALID_HANDLE_VALUE;
49 char string[MAXSTRING];
50 char extension_batch[][WCMD_BATCH_EXT_SIZE] = {".bat",".cmd"};
51 char extension_exe[WCMD_BATCH_EXT_SIZE] = ".exe";
53 BATCH_CONTEXT *prev_context;
55 if (startLabel == NULL) {
56 for(i=0; (i<(sizeof(extension_batch)/WCMD_BATCH_EXT_SIZE)) &&
57 (h == INVALID_HANDLE_VALUE); i++) {
58 strcpy (string, file);
60 if (strstr (string, extension_batch[i]) == NULL) strcat (string, extension_batch[i]);
61 h = CreateFile (string, GENERIC_READ, FILE_SHARE_READ,
62 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
64 if (h == INVALID_HANDLE_VALUE) {
65 strcpy (string, file);
67 if (strstr (string, extension_exe) == NULL) strcat (string, extension_exe);
68 h = CreateFile (string, GENERIC_READ, FILE_SHARE_READ,
69 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
70 if (h != INVALID_HANDLE_VALUE) {
71 WCMD_run_program (command, 0);
73 SetLastError (ERROR_FILE_NOT_FOUND);
79 DuplicateHandle(GetCurrentProcess(), pgmHandle,
80 GetCurrentProcess(), &h,
81 0, FALSE, DUPLICATE_SAME_ACCESS);
85 * Create a context structure for this batch file.
88 prev_context = context;
89 context = (BATCH_CONTEXT *)LocalAlloc (LMEM_FIXED, sizeof (BATCH_CONTEXT));
91 context -> command = command;
92 memset(context -> shift_count, 0x00, sizeof(context -> shift_count));
93 context -> prev_context = prev_context;
94 context -> skip_rest = FALSE;
96 /* If processing a call :label, 'goto' the label in question */
98 strcpy(param1, startLabel);
103 * Work through the file line by line. Specific batch commands are processed here,
104 * the rest are handled by the main command processor.
107 while (context -> skip_rest == FALSE && WCMD_fgets (string, sizeof(string), h)) {
108 if (strlen(string) == MAXSTRING -1) {
109 WCMD_output_asis( "Line in Batch processing possibly truncated. Using:\n");
110 WCMD_output_asis( string);
111 WCMD_output_asis( "\n");
113 if (string[0] != ':') { /* Skip over labels */
114 WCMD_process_command (string);
120 * If invoked by a CALL, we return to the context of our caller. Otherwise return
121 * to the caller's caller.
124 LocalFree ((HANDLE)context);
125 if ((prev_context != NULL) && (!called)) {
126 CloseHandle (prev_context -> h);
127 context = prev_context -> prev_context;
128 LocalFree ((HANDLE)prev_context);
131 context = prev_context;
135 /*******************************************************************
136 * WCMD_parameter - extract a parameter from a command line.
138 * Returns the 'n'th space-delimited parameter on the command line (zero-based).
139 * Parameter is in static storage overwritten on the next call.
140 * Parameters in quotes (and brackets) are handled.
141 * Also returns a pointer to the location of the parameter in the command line.
144 char *WCMD_parameter (char *s, int n, char **where) {
147 static char param[MAX_PATH];
150 if (where != NULL) *where = NULL;
158 if (where != NULL && i==n) *where = s;
160 while ((*s != '\0') && (*s != '"')) {
173 if (where != NULL && i==n) *where = s;
175 while ((*s != '\0') && (*s != ')')) {
190 /* Only return where if it is for the right parameter */
191 if (where != NULL && i==n) *where = s;
192 while ((*s != '\0') && (*s != ' ')) {
206 /****************************************************************************
209 * Get one line from a batch file. We can't use the native f* functions because
210 * of the filename syntax differences between DOS and Unix. Also need to lose
211 * the LF (or CRLF) from the line.
214 char *WCMD_fgets (char *s, int n, HANDLE h) {
222 status = ReadFile (h, s, 1, &bytes, NULL);
223 if ((status == 0) || ((bytes == 0) && (s == p))) return NULL;
224 if (*s == '\n') bytes = 0;
225 else if (*s != '\r') {
230 } while ((bytes == 1) && (n > 1));
234 /* WCMD_splitpath - copied from winefile as no obvious way to use it otherwise */
235 void WCMD_splitpath(const CHAR* path, CHAR* drv, CHAR* dir, CHAR* name, CHAR* ext)
237 const CHAR* end; /* end of processed string */
238 const CHAR* p; /* search pointer */
239 const CHAR* s; /* copy pointer */
241 /* extract drive name */
242 if (path[0] && path[1]==':') {
251 /* search for end of string or stream separator */
252 for(end=path; *end && *end!=':'; )
255 /* search for begin of file extension */
256 for(p=end; p>path && *--p!='\\' && *p!='/'; )
263 for(s=end; (*ext=*s++); )
266 /* search for end of directory name */
268 if (*--p=='\\' || *p=='/') {
288 /****************************************************************************
289 * WCMD_HandleTildaModifiers
291 * Handle the ~ modifiers when expanding %0-9 or (%a-z in for command)
292 * %~xxxxxV (V=0-9 or A-Z)
293 * Where xxxx is any combination of:
295 * f - Fully qualified path (assumes current dir if not drive\dir)
300 * s - path with shortnames
304 * $ENVVAR: - Searches ENVVAR for (contents of V) and expands to fully
307 * To work out the length of the modifier:
309 * Note: In the case of %0-9 knowing the end of the modifier is easy,
310 * but in a for loop, the for end character may also be a modifier
311 * eg. for %a in (c:\a.a) do echo XXX
312 * where XXX = %~a (just ~)
313 * %~aa (~ and attributes)
314 * %~aaxa (~, attributes and extension)
315 * BUT %~aax (~ and attributes followed by 'x')
317 * Hence search forwards until find an invalid modifier, and then
318 * backwards until find for variable or 0-9
320 void WCMD_HandleTildaModifiers(char **start, char *forVariable) {
322 #define NUMMODIFIERS 11
323 const char validmodifiers[NUMMODIFIERS] = {
324 '~', 'f', 'd', 'p', 'n', 'x', 's', 'a', 't', 'z', '$'
327 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
328 char outputparam[MAX_PATH];
329 char finaloutput[MAX_PATH];
330 char fullfilename[MAX_PATH];
331 char thisoutput[MAX_PATH];
332 char *pos = *start+1;
333 char *firstModifier = pos;
334 char *lastModifier = NULL;
336 BOOL finished = FALSE;
339 BOOL skipFileParsing = FALSE;
340 BOOL doneModifier = FALSE;
342 /* Search forwards until find invalid character modifier */
345 /* Work on the previous character */
346 if (lastModifier != NULL) {
348 for (i=0; i<NUMMODIFIERS; i++) {
349 if (validmodifiers[i] == *lastModifier) {
351 /* Special case '$' to skip until : found */
352 if (*lastModifier == '$') {
353 while (*pos != ':' && *pos) pos++;
354 if (*pos == 0x00) return; /* Invalid syntax */
355 pos++; /* Skip ':' */
361 if (i==NUMMODIFIERS) {
366 /* Save this one away */
373 /* Now make sure the position we stopped at is a valid parameter */
374 if (!(*lastModifier >= '0' || *lastModifier <= '9') &&
375 (forVariable != NULL) &&
376 (toupper(*lastModifier) != toupper(*forVariable))) {
378 /* Its not... Step backwards until it matches or we get to the start */
379 while (toupper(*lastModifier) != toupper(*forVariable) &&
380 lastModifier > firstModifier) {
383 if (lastModifier == firstModifier) return; /* Invalid syntax */
386 /* Extract the parameter to play with */
387 if ((*lastModifier >= '0' && *lastModifier <= '9')) {
388 strcpy(outputparam, WCMD_parameter (context -> command,
389 *lastModifier-'0' + context -> shift_count[*lastModifier-'0'], NULL));
391 /* FIXME: Retrieve 'for' variable %c\n", *lastModifier); */
392 /* Need to get 'for' loop variable into outputparam */
396 /* So now, firstModifier points to beginning of modifiers, lastModifier
397 points to the variable just after the modifiers. Process modifiers
398 in a specific order, remembering there could be duplicates */
399 modifierLen = lastModifier - firstModifier;
400 finaloutput[0] = 0x00;
402 /* Useful for debugging purposes: */
403 /*printf("Modifier string '%*.*s' and variable is %c\n Param starts as '%s'\n",
404 (modifierLen), (modifierLen), firstModifier, *lastModifier,
407 /* 1. Handle '~' : Strip surrounding quotes */
408 if (outputparam[0]=='"' &&
409 memchr(firstModifier, '~', modifierLen) != NULL) {
410 int len = strlen(outputparam);
411 if (outputparam[len-1] == '"') {
412 outputparam[len-1]=0x00;
415 memmove(outputparam, &outputparam[1], len-1);
418 /* 2. Handle the special case of a $ */
419 if (memchr(firstModifier, '$', modifierLen) != NULL) {
420 /* Special Case: Search envar specified in $[envvar] for outputparam
421 Note both $ and : are guaranteed otherwise check above would fail */
422 char *start = strchr(firstModifier, '$') + 1;
423 char *end = strchr(firstModifier, ':');
425 char fullpath[MAX_PATH];
427 /* Extract the env var */
428 strncpy(env, start, (end-start));
429 env[(end-start)] = 0x00;
431 /* If env var not found, return emptry string */
432 if ((GetEnvironmentVariable(env, fullpath, MAX_PATH) == 0) ||
433 (SearchPath(fullpath, outputparam, NULL,
434 MAX_PATH, outputparam, NULL) == 0)) {
435 finaloutput[0] = 0x00;
436 outputparam[0] = 0x00;
437 skipFileParsing = TRUE;
441 /* After this, we need full information on the file,
442 which is valid not to exist. */
443 if (!skipFileParsing) {
444 if (GetFullPathName(outputparam, MAX_PATH, fullfilename, NULL) == 0)
447 exists = GetFileAttributesExA(fullfilename, GetFileExInfoStandard,
450 /* 2. Handle 'a' : Output attributes */
452 memchr(firstModifier, 'a', modifierLen) != NULL) {
455 strcpy(thisoutput, "---------");
456 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
458 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
460 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
462 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
464 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
466 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED)
468 /* FIXME: What are 6 and 7? */
469 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
471 strcat(finaloutput, thisoutput);
474 /* 3. Handle 't' : Date+time */
476 memchr(firstModifier, 't', modifierLen) != NULL) {
482 if (finaloutput[0] != 0x00) strcat(finaloutput, " ");
484 /* Format the time */
485 FileTimeToSystemTime(&fileInfo.ftLastWriteTime, &systime);
486 GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &systime,
487 NULL, thisoutput, MAX_PATH);
488 strcat(thisoutput, " ");
489 datelen = strlen(thisoutput);
490 GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &systime,
491 NULL, (thisoutput+datelen), MAX_PATH-datelen);
492 strcat(finaloutput, thisoutput);
495 /* 4. Handle 'z' : File length */
497 memchr(firstModifier, 'z', modifierLen) != NULL) {
498 /* FIXME: Output full 64 bit size (sprintf does not support I64 here) */
499 ULONG/*64*/ fullsize = /*(fileInfo.nFileSizeHigh << 32) +*/
500 fileInfo.nFileSizeLow;
503 if (finaloutput[0] != 0x00) strcat(finaloutput, " ");
504 sprintf(thisoutput, "%u", fullsize);
505 strcat(finaloutput, thisoutput);
508 /* 4. Handle 's' : Use short paths (File doesn't have to exist) */
509 if (memchr(firstModifier, 's', modifierLen) != NULL) {
510 if (finaloutput[0] != 0x00) strcat(finaloutput, " ");
511 /* Don't flag as doneModifier - %~s on its own is processed later */
512 GetShortPathName(outputparam, outputparam, sizeof(outputparam));
515 /* 5. Handle 'f' : Fully qualified path (File doesn't have to exist) */
516 /* Note this overrides d,p,n,x */
517 if (memchr(firstModifier, 'f', modifierLen) != NULL) {
519 if (finaloutput[0] != 0x00) strcat(finaloutput, " ");
520 strcat(finaloutput, fullfilename);
525 char fname[MAX_PATH];
527 BOOL doneFileModifier = FALSE;
529 if (finaloutput[0] != 0x00) strcat(finaloutput, " ");
531 /* Split into components */
532 WCMD_splitpath(fullfilename, drive, dir, fname, ext);
534 /* 5. Handle 'd' : Drive Letter */
535 if (memchr(firstModifier, 'd', modifierLen) != NULL) {
536 strcat(finaloutput, drive);
538 doneFileModifier = TRUE;
541 /* 6. Handle 'p' : Path */
542 if (memchr(firstModifier, 'p', modifierLen) != NULL) {
543 strcat(finaloutput, dir);
545 doneFileModifier = TRUE;
548 /* 7. Handle 'n' : Name */
549 if (memchr(firstModifier, 'n', modifierLen) != NULL) {
550 strcat(finaloutput, fname);
552 doneFileModifier = TRUE;
555 /* 8. Handle 'x' : Ext */
556 if (memchr(firstModifier, 'x', modifierLen) != NULL) {
557 strcat(finaloutput, ext);
559 doneFileModifier = TRUE;
562 /* If 's' but no other parameter, dump the whole thing */
563 if (!doneFileModifier &&
564 memchr(firstModifier, 's', modifierLen) != NULL) {
566 if (finaloutput[0] != 0x00) strcat(finaloutput, " ");
567 strcat(finaloutput, outputparam);
572 /* If No other modifier processed, just add in parameter */
573 if (!doneModifier) strcpy(finaloutput, outputparam);
575 /* Finish by inserting the replacement into the string */
576 pos = strdup (lastModifier+1);
577 strcpy(*start, finaloutput);
582 /*******************************************************************
583 * WCMD_call - processes a batch call statement
585 * If there is a leading ':', calls within this batch program
586 * otherwise launches another program.
588 void WCMD_call (char *command) {
590 /* Run other program if no leading ':' */
591 if (*command != ':') {
592 WCMD_run_program(command, 1);
595 char gotoLabel[MAX_PATH];
597 strcpy(gotoLabel, param1);
603 /* Save the current file position, call the same file,
606 li.u.LowPart = SetFilePointer(context -> h, li.u.LowPart,
607 &li.u.HighPart, FILE_CURRENT);
609 WCMD_batch (param1, command, 1, gotoLabel, context->h);
611 SetFilePointer(context -> h, li.u.LowPart,
612 &li.u.HighPart, FILE_BEGIN);
614 printf("Cannot call batch label outside of a batch script\n");