batch.c 19 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) * sizeof(WCHAR))/WCMD_BATCH_EXT_SIZE)) &&
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 74 75 76 77 78 79 80 81 82 83 84 85 86
      h = CreateFile (string, GENERIC_READ, FILE_SHARE_READ,
                      NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
      if (h != INVALID_HANDLE_VALUE) {
        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);
87 88
  }

89 90 91 92 93 94 95 96
/*
 *	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;
97
  memset(context -> shift_count, 0x00, sizeof(context -> shift_count));
98
  context -> prev_context = prev_context;
99
  context -> skip_rest = FALSE;
100

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

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

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

/*
 *	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)) {
129
    prev_context -> skip_rest = TRUE;
130 131
    context = prev_context;
  }
132
  context = prev_context;
133 134 135 136 137
}

/*******************************************************************
 * WCMD_parameter - extract a parameter from a command line.
 *
138
 *	Returns the 'n'th 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.
142 143
 */

144
WCHAR *WCMD_parameter (WCHAR *s, int n, WCHAR **where) {
145

146
  int i = 0;
147 148
  static WCHAR param[MAX_PATH];
  WCHAR *p;
149

150
  if (where != NULL) *where = NULL;
151 152 153
  p = param;
  while (TRUE) {
    switch (*s) {
154
      case ' ': /* Skip leading spaces */
155 156 157
	s++;
	break;
      case '"':
158
        if (where != NULL && i==n) *where = s;
159 160 161 162 163 164 165 166
	s++;
	while ((*s != '\0') && (*s != '"')) {
	  *p++ = *s++;
	}
        if (i == n) {
          *p = '\0';
          return param;
        }
167
	if (*s == '"') s++;
168 169
          param[0] = '\0';
          i++;
170
        p = param;
171
	break;
172 173 174
      /* 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. */
175 176 177
      case '\0':
        return param;
      default:
178 179
        /* Only return where if it is for the right parameter */
        if (where != NULL && i==n) *where = s;
180
	while ((*s != '\0') && (*s != ' ') && (*s != ',') && (*s != '=')) {
181 182
	  *p++ = *s++;
	}
183
        if (i == n && (p!=param)) {
184 185 186
          *p = '\0';
          return param;
        }
187 188
        /* Skip double delimiters, eg. dir a.a,,,,,b.b */
        if (p != param) {
189 190
          param[0] = '\0';
          i++;
191 192 193
        } else {
          s++; /* Skip delimter */
        }
194
        p = param;
195 196 197 198
    }
  }
}

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

207
WCHAR *WCMD_fgets (WCHAR *s, int noChars, HANDLE h) {
208

209 210
  DWORD bytes;
  BOOL status;
211
  WCHAR *p;
212 213 214

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

227
/* WCMD_splitpath - copied from winefile as no obvious way to use it otherwise */
228
void WCMD_splitpath(const WCHAR* path, WCHAR* drv, WCHAR* dir, WCHAR* name, WCHAR* ext)
229
{
230 231 232
        const WCHAR* end; /* end of processed string */
	const WCHAR* p;	 /* search pointer */
	const WCHAR* s;	 /* copy pointer */
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 301 302

	/* 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,
303
 *    but in a for loop, the for end WCHARacter may also be a modifier
304 305 306 307 308 309 310 311 312
 *    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
 */
313
void WCMD_HandleTildaModifiers(WCHAR **start, WCHAR *forVariable, WCHAR *forValue, BOOL justFors) {
314 315

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

  WIN32_FILE_ATTRIBUTE_DATA fileInfo;
322 323 324 325 326 327 328
  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;
329 330 331 332 333 334 335
  int   modifierLen     = 0;
  BOOL  finished        = FALSE;
  int   i               = 0;
  BOOL  exists          = TRUE;
  BOOL  skipFileParsing = FALSE;
  BOOL  doneModifier    = FALSE;

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

339
    /* Work on the previous character */
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 365 366
    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++;
    }
  }

367 368 369 370 371 372 373 374 375 376 377
  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;
378

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

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

  /* 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]=='"' &&
406 407
      memchrW(firstModifier, '~', modifierLen) != NULL) {
    int len = strlenW(outputparam);
408 409 410 411
    if (outputparam[len-1] == '"') {
        outputparam[len-1]=0x00;
        len = len - 1;
    }
412
    memmove(outputparam, &outputparam[1], (len * sizeof(WCHAR))-1);
413 414 415
  }

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

    /* Extract the env var */
425
    memcpy(env, start, (end-start) * sizeof(WCHAR));
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
    env[(end-start)] = 0x00;

    /* If env var not found, return emptry string */
    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;

444
    exists = GetFileAttributesExW(fullfilename, GetFileExInfoStandard,
445 446 447 448
                                  &fileInfo);

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

451
      WCHAR defaults[] = {'-','-','-','-','-','-','-','-','-','\0'};
452
      doneModifier = TRUE;
453
      strcpyW(thisoutput, defaults);
454 455 456 457 458 459 460 461 462 463 464 465 466 467 468
      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';
469
      strcatW(finaloutput, thisoutput);
470 471 472 473
    }

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

      SYSTEMTIME systime;
      int datelen;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

594
    WCHAR gotoLabel[MAX_PATH];
595

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

    if (context) {

      LARGE_INTEGER li;

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

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

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