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 28 29 30 31 32 33

/****************************************************************************
 * 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
34
 * .BAT suffix on. Called is 1 for a CALL, 0 otherwise.
35 36
 *
 * We need to handle recursion correctly, since one batch program might call another.
37
 * So parameters for this batch file are held in a BATCH_CONTEXT structure.
38 39 40
 *
 * To support call within the same batch program, another input parameter is
 * a label to goto once opened.
41 42
 */

43
void WCMD_batch (WCHAR *file, WCHAR *command, int called, WCHAR *startLabel, HANDLE pgmHandle) {
44

45 46
  HANDLE h = INVALID_HANDLE_VALUE;
  BATCH_CONTEXT *prev_context;
47

48
  if (startLabel == NULL) {
49
    h = CreateFileW (file, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
50
                     NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
51
    if (h == INVALID_HANDLE_VALUE) {
52 53
      SetLastError (ERROR_FILE_NOT_FOUND);
      WCMD_print_error ();
54 55 56 57 58 59
      return;
    }
  } else {
    DuplicateHandle(GetCurrentProcess(), pgmHandle,
                    GetCurrentProcess(), &h,
                    0, FALSE, DUPLICATE_SAME_ACCESS);
60 61
  }

62 63 64 65 66
/*
 *	Create a context structure for this batch file.
 */

  prev_context = context;
67
  context = LocalAlloc (LMEM_FIXED, sizeof (BATCH_CONTEXT));
68
  context -> h = h;
69
  context->batchfileW = WCMD_strdupW(file);
70
  context -> command = command;
71
  memset(context -> shift_count, 0x00, sizeof(context -> shift_count));
72
  context -> prev_context = prev_context;
73
  context -> skip_rest = FALSE;
74

75 76
  /* If processing a call :label, 'goto' the label in question */
  if (startLabel) {
77
    strcpyW(param1, startLabel);
78
    WCMD_goto(NULL);
79 80
  }

81 82 83 84 85
/*
 * 	Work through the file line by line. Specific batch commands are processed here,
 * 	the rest are handled by the main command processor.
 */

86 87
  while (context -> skip_rest == FALSE) {
      CMD_LIST *toExecute = NULL;         /* Commands left to be executed */
88
      if (!WCMD_ReadAndParseLine(NULL, &toExecute, h))
89
        break;
90
      WCMD_process_commands(toExecute, FALSE, NULL, NULL);
91 92
      WCMD_free_commands(toExecute);
      toExecute = NULL;
93 94
  }
  CloseHandle (h);
95 96 97 98 99 100

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

101
  HeapFree(GetProcessHeap(), 0, context->batchfileW);
102
  LocalFree (context);
103
  if ((prev_context != NULL) && (!called)) {
104
    prev_context -> skip_rest = TRUE;
105 106
    context = prev_context;
  }
107
  context = prev_context;
108 109 110
}

/*******************************************************************
111
 * WCMD_parameter
112
 *
113 114 115 116 117 118
 * Extracts a delimited parameter from an input string
 *
 * PARAMS
 *  s     [I] input string, non NULL
 *  n     [I] # of the (possibly double quotes-delimited) parameter to return
 *            Starts at 0
119
 *  start [O] if non NULL, pointer to the start of the nth parameter in s,
120
 *            potentially a " character
121 122
 *  end   [O] if non NULL, pointer to the last char of
 *            the nth parameter in s, potentially a " character
123 124 125
 *
 * RETURNS
 *  Success: Returns the nth delimited parameter found in s.
126
 *           *start points to the start of the param, possibly a starting
127 128
 *           double quotes character
 *  Failure: Returns an empty string if the param is not found.
129
 *           *start is set to NULL
130 131 132 133 134
 *
 * NOTES
 *  Return value is stored in static storage, hence is overwritten
 *  after each call.
 *  Doesn't include any potentially delimiting double quotes
135
 */
136
WCHAR *WCMD_parameter (WCHAR *s, int n, WCHAR **start, WCHAR **end) {
137 138 139 140 141
    int curParamNb = 0;
    static WCHAR param[MAX_PATH];
    WCHAR *p = s, *q;
    BOOL quotesDelimited;

142
    if (start != NULL) *start = NULL;
143
    if (end != NULL) *end = NULL;
144 145 146 147 148 149 150
    param[0] = '\0';
    while (TRUE) {
        while (*p && ((*p == ' ') || (*p == ',') || (*p == '=') || (*p == '\t')))
            p++;
        if (*p == '\0') return param;

        quotesDelimited = (*p == '"');
151
        if (start != NULL && curParamNb == n) *start = p;
152 153 154 155

        if (quotesDelimited) {
            q = ++p;
            while (*p && *p != '"') p++;
156
        } else {
157 158 159 160 161 162 163
            q = p;
            while (*p && (*p != ' ') && (*p != ',') && (*p != '=') && (*p != '\t'))
                p++;
        }
        if (curParamNb == n) {
            memcpy(param, q, (p - q) * sizeof(WCHAR));
            param[p-q] = '\0';
164
            if (end) *end = p - 1 + quotesDelimited;
165
            return param;
166
        }
167 168
        if (quotesDelimited && *p == '"') p++;
        curParamNb++;
169 170 171
    }
}

172
/****************************************************************************
173 174
 * WCMD_fgets
 *
175 176
 * Gets one line from a file/console and puts it into buffer buf
 * Pre:  buf has size noChars
177
 *       1 <= noChars <= MAXSTRING
178 179
 * Post: buf is filled with at most noChars-1 characters, and gets nul-terminated
         buf does not include EOL terminator
180
 * Returns:
181
 *       buf on success
182
 *       NULL on error or EOF
183 184
 */

185
WCHAR *WCMD_fgets(WCHAR *buf, DWORD noChars, HANDLE h)
186
{
187
  DWORD charsRead;
188
  BOOL status;
189
  LARGE_INTEGER filepos;
190
  DWORD i;
191

192 193 194
  /* We can't use the native f* functions because of the filename syntax differences
     between DOS and Unix. Also need to lose the LF (or CRLF) from the line. */

195 196 197 198
  if (!WCMD_is_console_handle(h)) {
    /* Save current file position */
    filepos.QuadPart = 0;
    SetFilePointerEx(h, filepos, &filepos, FILE_CURRENT);
199 200
  }

201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
  status = WCMD_ReadFile(h, buf, noChars, &charsRead);
  if (!status || charsRead == 0) return NULL;

  /* Find first EOL */
  for (i = 0; i < charsRead; i++) {
    if (buf[i] == '\n' || buf[i] == '\r')
      break;
  }

  if (!WCMD_is_console_handle(h) && i != charsRead) {
    /* Sets file pointer to the start of the next line, if any */
    filepos.QuadPart += i + 1 + (buf[i] == '\r' ? 1 : 0);
    SetFilePointerEx(h, filepos, NULL, FILE_BEGIN);
  }

  /* Truncate at EOL (or end of buffer) */
  if (i == noChars)
    i--;

  buf[i] = '\0';

  return buf;
223
}
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

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

242
        end = path + strlenW(path);
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

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

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

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

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

335
    /* Work on the previous character */
336 337 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
    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++;
    }
  }

363 364 365 366
  while (lastModifier > firstModifier) {
    WINE_TRACE("Looking backwards for parameter id: %s / %s\n",
               wine_dbgstr_w(lastModifier), wine_dbgstr_w(forVariable));

367
    if (!justFors && context && (*lastModifier >= '0' && *lastModifier <= '9')) {
368 369 370 371 372 373
      /* Its a valid parameter identifier - OK */
      break;

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

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

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

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

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

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

427
    /* If env var not found, return empty string */
428 429
    if ((GetEnvironmentVariableW(env, fullpath, MAX_PATH) == 0) ||
        (SearchPathW(fullpath, outputparam, NULL, MAX_PATH, outputparam, NULL) == 0)) {
430 431 432 433 434 435 436 437 438
      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) {
439
    if (GetFullPathNameW(outputparam, MAX_PATH, fullfilename, NULL) == 0)
440 441
      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

      /* Format the time */
      FileTimeToSystemTime(&fileInfo.ftLastWriteTime, &systime);
482
      GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &systime,
483
                        NULL, thisoutput, MAX_PATH);
484 485
      strcatW(thisoutput, space);
      datelen = strlenW(thisoutput);
486
      GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &systime,
487
                        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
      if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
501
      wsprintfW(thisoutput, fmt, fullsize);
502
      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
      GetShortPathNameW(outputparam, outputparam, sizeof(outputparam)/sizeof(outputparam[0]));
510 511
    }

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

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

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

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

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

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

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

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

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

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

  /* Finish by inserting the replacement into the string */
573
  WCMD_strsubstW(*start, lastModifier+1, finaloutput, -1);
574
}
575 576 577 578 579 580 581

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

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

589
    WCHAR gotoLabel[MAX_PATH];
590

591
    strcpyW(gotoLabel, param1);
592 593 594 595 596 597 598 599

    if (context) {

      LARGE_INTEGER li;

      /* Save the current file position, call the same file,
         restore position                                    */
      li.QuadPart = 0;
600 601
      li.u.LowPart = SetFilePointer(context -> h, li.u.LowPart,
                     &li.u.HighPart, FILE_CURRENT);
602 603 604

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

605 606
      SetFilePointer(context -> h, li.u.LowPart,
                     &li.u.HighPart, FILE_BEGIN);
607
    } else {
608
      WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_CALLINSCRIPT));
609 610 611
    }
  }
}