batch.c 18.9 KB
Newer Older
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 8 9 10 11 12 13 14 15 16 17 18
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * 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 24 25
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(cmd);
26 27

extern int echo_mode;
28
extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
29
extern BATCH_CONTEXT *context;
30
extern DWORD errorlevel;
31 32 33 34 35 36 37 38

/****************************************************************************
 * WCMD_batch
 *
 * Open and execute a batch file.
 * On entry *command includes the complete command line beginning with the name
 * of the batch file (if a CALL command was entered the CALL has been removed).
 * *file is the name of the file, which might not exist and may not have the
39
 * .BAT suffix on. Called is 1 for a CALL, 0 otherwise.
40 41
 *
 * We need to handle recursion correctly, since one batch program might call another.
42
 * So parameters for this batch file are held in a BATCH_CONTEXT structure.
43 44 45
 *
 * To support call within the same batch program, another input parameter is
 * a label to goto once opened.
46 47
 */

48
void WCMD_batch (WCHAR *file, WCHAR *command, int called, WCHAR *startLabel, HANDLE pgmHandle) {
49

50 51
#define WCMD_BATCH_EXT_SIZE 5

52
  HANDLE h = INVALID_HANDLE_VALUE;
53 54 55 56
  WCHAR string[MAXSTRING];
  static const WCHAR extension_batch[][WCMD_BATCH_EXT_SIZE] = {{'.','b','a','t','\0'},
                                                               {'.','c','m','d','\0'}};
  static const WCHAR extension_exe[WCMD_BATCH_EXT_SIZE] = {'.','e','x','e','\0'};
57 58
  unsigned int  i;
  BATCH_CONTEXT *prev_context;
59

60
  if (startLabel == NULL) {
61
    for(i=0; (i<sizeof(extension_batch)/(WCMD_BATCH_EXT_SIZE * sizeof(WCHAR))) &&
62
             (h == INVALID_HANDLE_VALUE); i++) {
63 64 65 66 67
      strcpyW (string, file);
      CharLower (string);
      if (strstrW (string, extension_batch[i]) == NULL) strcatW (string, extension_batch[i]);
      h = CreateFile (string, GENERIC_READ, FILE_SHARE_READ,
                      NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
68
    }
69
    if (h == INVALID_HANDLE_VALUE) {
70
      strcpyW (string, file);
71
      CharLower (string);
72
      if (strstrW (string, extension_exe) == NULL) strcatW (string, extension_exe);
73
      if (GetFileAttributes (string) != INVALID_FILE_ATTRIBUTES) {
74 75 76 77 78 79 80 81 82 83 84
        WCMD_run_program (command, 0);
      } else {
        SetLastError (ERROR_FILE_NOT_FOUND);
        WCMD_print_error ();
      }
      return;
    }
  } else {
    DuplicateHandle(GetCurrentProcess(), pgmHandle,
                    GetCurrentProcess(), &h,
                    0, FALSE, DUPLICATE_SAME_ACCESS);
85 86
  }

87 88 89 90 91 92 93 94
/*
 *	Create a context structure for this batch file.
 */

  prev_context = context;
  context = (BATCH_CONTEXT *)LocalAlloc (LMEM_FIXED, sizeof (BATCH_CONTEXT));
  context -> h = h;
  context -> command = command;
95
  memset(context -> shift_count, 0x00, sizeof(context -> shift_count));
96
  context -> prev_context = prev_context;
97
  context -> skip_rest = FALSE;
98

99 100
  /* If processing a call :label, 'goto' the label in question */
  if (startLabel) {
101
    strcpyW(param1, startLabel);
102
    WCMD_goto(NULL);
103 104
  }

105 106 107 108 109
/*
 * 	Work through the file line by line. Specific batch commands are processed here,
 * 	the rest are handled by the main command processor.
 */

110 111 112 113
  while (context -> skip_rest == FALSE) {
      CMD_LIST *toExecute = NULL;         /* Commands left to be executed */
      if (WCMD_ReadAndParseLine(NULL, &toExecute, h) == NULL)
        break;
114
      WCMD_process_commands(toExecute, FALSE, NULL, NULL);
115 116
      WCMD_free_commands(toExecute);
      toExecute = NULL;
117 118
  }
  CloseHandle (h);
119 120 121 122 123 124 125 126

/*
 *	If invoked by a CALL, we return to the context of our caller. Otherwise return
 *	to the caller's caller.
 */

  LocalFree ((HANDLE)context);
  if ((prev_context != NULL) && (!called)) {
127
    prev_context -> skip_rest = TRUE;
128 129
    context = prev_context;
  }
130
  context = prev_context;
131 132 133 134 135
}

/*******************************************************************
 * WCMD_parameter - extract a parameter from a command line.
 *
136
 *	Returns the 'n'th delimited parameter on the command line (zero-based).
137
 *	Parameter is in static storage overwritten on the next call.
138
 *	Parameters in quotes (and brackets) are handled.
139
 *	Also returns a pointer to the location of the parameter in the command line.
140 141
 */

142
WCHAR *WCMD_parameter (WCHAR *s, int n, WCHAR **where) {
143

144
  int i = 0;
145 146
  static WCHAR param[MAX_PATH];
  WCHAR *p;
147

148
  if (where != NULL) *where = NULL;
149 150 151
  p = param;
  while (TRUE) {
    switch (*s) {
152
      case ' ': /* Skip leading spaces */
153 154 155
	s++;
	break;
      case '"':
156
        if (where != NULL && i==n) *where = s;
157 158 159 160 161 162 163 164
	s++;
	while ((*s != '\0') && (*s != '"')) {
	  *p++ = *s++;
	}
        if (i == n) {
          *p = '\0';
          return param;
        }
165
	if (*s == '"') s++;
166 167
          param[0] = '\0';
          i++;
168
        p = param;
169
	break;
170 171 172
      /* The code to handle bracketed parms is removed because it should no longer
         be necessary after the multiline support has been added and the for loop
         set of data is now parseable individually. */
173 174 175
      case '\0':
        return param;
      default:
176 177
        /* Only return where if it is for the right parameter */
        if (where != NULL && i==n) *where = s;
178
	while ((*s != '\0') && (*s != ' ') && (*s != ',') && (*s != '=')) {
179 180
	  *p++ = *s++;
	}
181
        if (i == n && (p!=param)) {
182 183 184
          *p = '\0';
          return param;
        }
185 186
        /* Skip double delimiters, eg. dir a.a,,,,,b.b */
        if (p != param) {
187 188
          param[0] = '\0';
          i++;
189
        } else {
190
          s++; /* Skip delimiter */
191
        }
192
        p = param;
193 194 195 196
    }
  }
}

197
/****************************************************************************
198 199 200
 * WCMD_fgets
 *
 * Get one line from a batch file. We can't use the native f* functions because
201 202
 * of the filename syntax differences between DOS and Unix. Also need to lose
 * the LF (or CRLF) from the line.
203 204
 */

205
WCHAR *WCMD_fgets (WCHAR *s, int noChars, HANDLE h) {
206

207 208
  DWORD bytes;
  BOOL status;
209
  WCHAR *p;
210 211 212

  p = s;
  do {
213
    status = WCMD_ReadFile (h, s, 1, &bytes, NULL);
214
    if ((status == 0) || ((bytes == 0) && (s == p))) return NULL;
215
    if (*s == '\n') bytes = 0;
216 217
    else if (*s != '\r') {
      s++;
218
      noChars--;
219 220
    }
    *s = '\0';
221
  } while ((bytes == 1) && (noChars > 1));
222 223
  return p;
}
224

225
/* WCMD_splitpath - copied from winefile as no obvious way to use it otherwise */
226
void WCMD_splitpath(const WCHAR* path, WCHAR* drv, WCHAR* dir, WCHAR* name, WCHAR* ext)
227
{
228 229 230
        const WCHAR* end; /* end of processed string */
	const WCHAR* p;	 /* search pointer */
	const WCHAR* s;	 /* copy pointer */
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300

	/* extract drive name */
	if (path[0] && path[1]==':') {
		if (drv) {
			*drv++ = *path++;
			*drv++ = *path++;
			*drv = '\0';
		}
	} else if (drv)
		*drv = '\0';

	/* search for end of string or stream separator */
	for(end=path; *end && *end!=':'; )
		end++;

	/* search for begin of file extension */
	for(p=end; p>path && *--p!='\\' && *p!='/'; )
		if (*p == '.') {
			end = p;
			break;
		}

	if (ext)
		for(s=end; (*ext=*s++); )
			ext++;

	/* search for end of directory name */
	for(p=end; p>path; )
		if (*--p=='\\' || *p=='/') {
			p++;
			break;
		}

	if (name) {
		for(s=p; s<end; )
			*name++ = *s++;

		*name = '\0';
	}

	if (dir) {
		for(s=path; s<p; )
			*dir++ = *s++;

		*dir = '\0';
	}
}

/****************************************************************************
 * WCMD_HandleTildaModifiers
 *
 * Handle the ~ modifiers when expanding %0-9 or (%a-z in for command)
 *    %~xxxxxV  (V=0-9 or A-Z)
 * Where xxxx is any combination of:
 *    ~ - Removes quotes
 *    f - Fully qualified path (assumes current dir if not drive\dir)
 *    d - drive letter
 *    p - path
 *    n - filename
 *    x - file extension
 *    s - path with shortnames
 *    a - attributes
 *    t - date/time
 *    z - size
 *    $ENVVAR: - Searches ENVVAR for (contents of V) and expands to fully
 *                   qualified path
 *
 *  To work out the length of the modifier:
 *
 *  Note: In the case of %0-9 knowing the end of the modifier is easy,
301
 *    but in a for loop, the for end WCHARacter may also be a modifier
302 303 304 305 306 307 308 309 310
 *    eg. for %a in (c:\a.a) do echo XXX
 *             where XXX = %~a    (just ~)
 *                         %~aa   (~ and attributes)
 *                         %~aaxa (~, attributes and extension)
 *                   BUT   %~aax  (~ and attributes followed by 'x')
 *
 *  Hence search forwards until find an invalid modifier, and then
 *  backwards until find for variable or 0-9
 */
311
void WCMD_HandleTildaModifiers(WCHAR **start, WCHAR *forVariable, WCHAR *forValue, BOOL justFors) {
312 313

#define NUMMODIFIERS 11
314
  static const WCHAR validmodifiers[NUMMODIFIERS] = {
315 316
        '~', 'f', 'd', 'p', 'n', 'x', 's', 'a', 't', 'z', '$'
  };
317
  static const WCHAR space[] = {' ', '\0'};
318 319

  WIN32_FILE_ATTRIBUTE_DATA fileInfo;
320 321 322 323 324 325 326
  WCHAR  outputparam[MAX_PATH];
  WCHAR  finaloutput[MAX_PATH];
  WCHAR  fullfilename[MAX_PATH];
  WCHAR  thisoutput[MAX_PATH];
  WCHAR  *pos            = *start+1;
  WCHAR  *firstModifier  = pos;
  WCHAR  *lastModifier   = NULL;
327 328 329 330 331 332 333
  int   modifierLen     = 0;
  BOOL  finished        = FALSE;
  int   i               = 0;
  BOOL  exists          = TRUE;
  BOOL  skipFileParsing = FALSE;
  BOOL  doneModifier    = FALSE;

334
  /* Search forwards until find invalid character modifier */
335 336
  while (!finished) {

337
    /* Work on the previous character */
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
    if (lastModifier != NULL) {

      for (i=0; i<NUMMODIFIERS; i++) {
        if (validmodifiers[i] == *lastModifier) {

          /* Special case '$' to skip until : found */
          if (*lastModifier == '$') {
            while (*pos != ':' && *pos) pos++;
            if (*pos == 0x00) return; /* Invalid syntax */
            pos++;                    /* Skip ':'       */
          }
          break;
        }
      }

      if (i==NUMMODIFIERS) {
        finished = TRUE;
      }
    }

    /* Save this one away */
    if (!finished) {
      lastModifier = pos;
      pos++;
    }
  }

365 366 367 368 369 370 371 372 373 374 375
  while (lastModifier > firstModifier) {
    WINE_TRACE("Looking backwards for parameter id: %s / %s\n",
               wine_dbgstr_w(lastModifier), wine_dbgstr_w(forVariable));

    if (!justFors && context && (*lastModifier >= '0' || *lastModifier <= '9')) {
      /* Its a valid parameter identifier - OK */
      break;

    } else if (forVariable && *lastModifier == *(forVariable+1)) {
      /* Its a valid parameter identifier - OK */
      break;
376

377
    } else {
378 379 380
      lastModifier--;
    }
  }
381
  if (lastModifier == firstModifier) return; /* Invalid syntax */
382 383 384

  /* Extract the parameter to play with */
  if ((*lastModifier >= '0' && *lastModifier <= '9')) {
385
    strcpyW(outputparam, WCMD_parameter (context -> command,
386
                 *lastModifier-'0' + context -> shift_count[*lastModifier-'0'], NULL));
387
  } else {
388
    strcpyW(outputparam, forValue);
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403
  }

  /* So now, firstModifier points to beginning of modifiers, lastModifier
     points to the variable just after the modifiers. Process modifiers
     in a specific order, remembering there could be duplicates           */
  modifierLen = lastModifier - firstModifier;
  finaloutput[0] = 0x00;

  /* Useful for debugging purposes: */
  /*printf("Modifier string '%*.*s' and variable is %c\n Param starts as '%s'\n",
             (modifierLen), (modifierLen), firstModifier, *lastModifier,
             outputparam);*/

  /* 1. Handle '~' : Strip surrounding quotes */
  if (outputparam[0]=='"' &&
404 405
      memchrW(firstModifier, '~', modifierLen) != NULL) {
    int len = strlenW(outputparam);
406 407 408 409
    if (outputparam[len-1] == '"') {
        outputparam[len-1]=0x00;
        len = len - 1;
    }
410
    memmove(outputparam, &outputparam[1], (len * sizeof(WCHAR))-1);
411 412 413
  }

  /* 2. Handle the special case of a $ */
414
  if (memchrW(firstModifier, '$', modifierLen) != NULL) {
415 416
    /* Special Case: Search envar specified in $[envvar] for outputparam
       Note both $ and : are guaranteed otherwise check above would fail */
417 418 419 420
    WCHAR *start = strchrW(firstModifier, '$') + 1;
    WCHAR *end   = strchrW(firstModifier, ':');
    WCHAR env[MAX_PATH];
    WCHAR fullpath[MAX_PATH];
421 422

    /* Extract the env var */
423
    memcpy(env, start, (end-start) * sizeof(WCHAR));
424 425
    env[(end-start)] = 0x00;

426
    /* If env var not found, return empty string */
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441
    if ((GetEnvironmentVariable(env, fullpath, MAX_PATH) == 0) ||
        (SearchPath(fullpath, outputparam, NULL,
                    MAX_PATH, outputparam, NULL) == 0)) {
      finaloutput[0] = 0x00;
      outputparam[0] = 0x00;
      skipFileParsing = TRUE;
    }
  }

  /* After this, we need full information on the file,
    which is valid not to exist.  */
  if (!skipFileParsing) {
    if (GetFullPathName(outputparam, MAX_PATH, fullfilename, NULL) == 0)
      return;

442
    exists = GetFileAttributesExW(fullfilename, GetFileExInfoStandard,
443 444 445 446
                                  &fileInfo);

    /* 2. Handle 'a' : Output attributes */
    if (exists &&
447
        memchrW(firstModifier, 'a', modifierLen) != NULL) {
448

449
      WCHAR defaults[] = {'-','-','-','-','-','-','-','-','-','\0'};
450
      doneModifier = TRUE;
451
      strcpyW(thisoutput, defaults);
452 453 454 455 456 457 458 459 460 461 462 463 464 465 466
      if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        thisoutput[0]='d';
      if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
        thisoutput[1]='r';
      if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
        thisoutput[2]='a';
      if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
        thisoutput[3]='h';
      if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
        thisoutput[4]='s';
      if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED)
        thisoutput[5]='c';
      /* FIXME: What are 6 and 7? */
      if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
        thisoutput[8]='l';
467
      strcatW(finaloutput, thisoutput);
468 469 470 471
    }

    /* 3. Handle 't' : Date+time */
    if (exists &&
472
        memchrW(firstModifier, 't', modifierLen) != NULL) {
473 474 475 476 477

      SYSTEMTIME systime;
      int datelen;

      doneModifier = TRUE;
478
      if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
479 480 481 482 483

      /* Format the time */
      FileTimeToSystemTime(&fileInfo.ftLastWriteTime, &systime);
      GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &systime,
                        NULL, thisoutput, MAX_PATH);
484 485
      strcatW(thisoutput, space);
      datelen = strlenW(thisoutput);
486 487
      GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &systime,
                        NULL, (thisoutput+datelen), MAX_PATH-datelen);
488
      strcatW(finaloutput, thisoutput);
489 490 491 492
    }

    /* 4. Handle 'z' : File length */
    if (exists &&
493
        memchrW(firstModifier, 'z', modifierLen) != NULL) {
494
      /* FIXME: Output full 64 bit size (sprintf does not support I64 here) */
495 496
      ULONG/*64*/ fullsize = /*(fileInfo.nFileSizeHigh << 32) +*/
                                  fileInfo.nFileSizeLow;
497
      static const WCHAR fmt[] = {'%','u','\0'};
498 499

      doneModifier = TRUE;
500 501 502
      if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
      wsprintf(thisoutput, fmt, fullsize);
      strcatW(finaloutput, thisoutput);
503 504
    }

505
    /* 4. Handle 's' : Use short paths (File doesn't have to exist) */
506 507
    if (memchrW(firstModifier, 's', modifierLen) != NULL) {
      if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
508
      /* Don't flag as doneModifier - %~s on its own is processed later */
509 510
      GetShortPathName(outputparam, outputparam,
                       sizeof(outputparam)/sizeof(outputparam[0]));
511 512
    }

513
    /* 5. Handle 'f' : Fully qualified path (File doesn't have to exist) */
514
    /*      Note this overrides d,p,n,x                                 */
515
    if (memchrW(firstModifier, 'f', modifierLen) != NULL) {
516
      doneModifier = TRUE;
517 518
      if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
      strcatW(finaloutput, fullfilename);
519 520
    } else {

521 522 523 524
      WCHAR drive[10];
      WCHAR dir[MAX_PATH];
      WCHAR fname[MAX_PATH];
      WCHAR ext[MAX_PATH];
525 526
      BOOL doneFileModifier = FALSE;

527
      if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
528 529

      /* Split into components */
530
      WCMD_splitpath(fullfilename, drive, dir, fname, ext);
531 532

      /* 5. Handle 'd' : Drive Letter */
533 534
      if (memchrW(firstModifier, 'd', modifierLen) != NULL) {
        strcatW(finaloutput, drive);
535 536 537 538 539
        doneModifier = TRUE;
        doneFileModifier = TRUE;
      }

      /* 6. Handle 'p' : Path */
540 541
      if (memchrW(firstModifier, 'p', modifierLen) != NULL) {
        strcatW(finaloutput, dir);
542 543 544 545 546
        doneModifier = TRUE;
        doneFileModifier = TRUE;
      }

      /* 7. Handle 'n' : Name */
547 548
      if (memchrW(firstModifier, 'n', modifierLen) != NULL) {
        strcatW(finaloutput, fname);
549 550 551 552 553
        doneModifier = TRUE;
        doneFileModifier = TRUE;
      }

      /* 8. Handle 'x' : Ext */
554 555
      if (memchrW(firstModifier, 'x', modifierLen) != NULL) {
        strcatW(finaloutput, ext);
556 557 558 559 560 561
        doneModifier = TRUE;
        doneFileModifier = TRUE;
      }

      /* If 's' but no other parameter, dump the whole thing */
      if (!doneFileModifier &&
562
          memchrW(firstModifier, 's', modifierLen) != NULL) {
563
        doneModifier = TRUE;
564 565
        if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
        strcatW(finaloutput, outputparam);
566 567 568 569 570
      }
    }
  }

  /* If No other modifier processed,  just add in parameter */
571
  if (!doneModifier) strcpyW(finaloutput, outputparam);
572 573

  /* Finish by inserting the replacement into the string */
574 575 576
  pos = WCMD_strdupW(lastModifier+1);
  strcpyW(*start, finaloutput);
  strcatW(*start, pos);
577 578
  free(pos);
}
579 580 581 582 583 584 585

/*******************************************************************
 * WCMD_call - processes a batch call statement
 *
 *	If there is a leading ':', calls within this batch program
 *	otherwise launches another program.
 */
586
void WCMD_call (WCHAR *command) {
587 588 589 590 591 592

  /* Run other program if no leading ':' */
  if (*command != ':') {
    WCMD_run_program(command, 1);
  } else {

593
    WCHAR gotoLabel[MAX_PATH];
594

595
    strcpyW(gotoLabel, param1);
596 597 598 599 600 601 602 603

    if (context) {

      LARGE_INTEGER li;

      /* Save the current file position, call the same file,
         restore position                                    */
      li.QuadPart = 0;
604 605
      li.u.LowPart = SetFilePointer(context -> h, li.u.LowPart,
                     &li.u.HighPart, FILE_CURRENT);
606 607 608

      WCMD_batch (param1, command, 1, gotoLabel, context->h);

609 610
      SetFilePointer(context -> h, li.u.LowPart,
                     &li.u.HighPart, FILE_BEGIN);
611
    } else {
612
      WCMD_output_asis( WCMD_LoadMessage(WCMD_CALLINSCRIPT));
613 614 615
    }
  }
}