wcmdmain.c 87.7 KB
Newer Older
1
/*
2
 * CMD - Wine-compatible command line interface.
3
 *
4
 * Copyright (C) 1999 - 2001 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 23 24 25 26 27
 */

/*
 * FIXME:
 * - Cannot handle parameters in quotes
 * - Lots of functionality missing from builtins
 */

28
#include "config.h"
29
#include <time.h>
30
#include "wcmd.h"
31
#include "shellapi.h"
32 33 34
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(cmd);
35

36
extern const WCHAR inbuilt[][10];
37 38 39
extern struct env_stack *pushd_directories;

BATCH_CONTEXT *context = NULL;
40
DWORD errorlevel;
41
WCHAR quals[MAX_PATH], param1[MAXSTRING], param2[MAXSTRING];
42
BOOL  interactive;
43
FOR_CONTEXT forloopcontext; /* The 'for' loop context */
44
BOOL delayedsubst = FALSE; /* The current delayed substitution setting */
45

46
int defaultColor = 7;
47
BOOL echo_mode = TRUE;
48 49

WCHAR anykey[100], version_string[100];
50 51
const WCHAR newlineW[] = {'\r','\n','\0'};
const WCHAR spaceW[]   = {' ','\0'};
52 53 54 55 56
static const WCHAR envPathExt[] = {'P','A','T','H','E','X','T','\0'};
static const WCHAR dfltPathExt[] = {'.','b','a','t',';',
                                    '.','c','o','m',';',
                                    '.','c','m','d',';',
                                    '.','e','x','e','\0'};
57 58 59 60 61

static BOOL opt_c, opt_k, opt_s, unicodeOutput = FALSE;

/* Variables pertaining to paging */
static BOOL paged_mode;
62
static const WCHAR *pagedMessage = NULL;
63 64 65 66
static int line_count;
static int max_height;
static int max_width;
static int numChars;
67

68 69
#define MAX_WRITECONSOLE_SIZE 65535

70 71 72 73 74 75
/*
 * Returns a buffer for reading from/writing to file
 * Never freed
 */
static char *get_file_buffer(void)
{
76
    static char *output_bufA = NULL;
77 78
    if (!output_bufA)
        output_bufA = heap_alloc(MAX_WRITECONSOLE_SIZE);
79 80
    return output_bufA;
}
81

82 83 84 85 86 87
/*******************************************************************
 * WCMD_output_asis_len - send output to current standard output
 *
 * Output a formatted unicode string. Ideally this will go to the console
 *  and hence required WriteConsoleW to output it, however if file i/o is
 *  redirected, it needs to be WriteFile'd using OEM (not ANSI) format
88
 */
89 90
static void WCMD_output_asis_len(const WCHAR *message, DWORD len, HANDLE device)
{
91 92
    DWORD   nOut= 0;
    DWORD   res = 0;
93

94 95
    /* If nothing to write, return (MORE does this sometimes) */
    if (!len) return;
96

97 98
    /* Try to write as unicode assuming it is to a console */
    res = WriteConsoleW(device, message, len, &nOut, NULL);
99

100
    /* If writing to console fails, assume it's file
101 102 103 104
       i/o so convert to OEM codepage and output                  */
    if (!res) {
      BOOL usedDefaultChar = FALSE;
      DWORD convertedChars;
105
      char *buffer;
106

107
      if (!unicodeOutput) {
108 109 110

        if (!(buffer = get_file_buffer()))
            return;
111

112 113
        /* Convert to OEM, then output */
        convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
114
                            len, buffer, MAX_WRITECONSOLE_SIZE,
115
                            "?", &usedDefaultChar);
116
        WriteFile(device, buffer, convertedChars,
117 118 119 120
                  &nOut, FALSE);
      } else {
        WriteFile(device, message, len*sizeof(WCHAR),
                  &nOut, FALSE);
121
      }
122 123 124
    }
    return;
}
125

126 127 128 129
/*******************************************************************
 * WCMD_output - send output to current standard output device.
 *
 */
130

131
void CDECL WCMD_output (const WCHAR *format, ...) {
132

133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
  __ms_va_list ap;
  WCHAR* string;
  DWORD len;

  __ms_va_start(ap,format);
  SetLastError(NO_ERROR);
  string = NULL;
  len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
                       format, 0, 0, (LPWSTR)&string, 0, &ap);
  __ms_va_end(ap);
  if (len == 0 && GetLastError() != NO_ERROR)
    WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
  else
  {
    WCMD_output_asis_len(string, len, GetStdHandle(STD_OUTPUT_HANDLE));
    LocalFree(string);
149 150
  }
}
151

152 153 154 155 156
/*******************************************************************
 * WCMD_output_stderr - send output to current standard error device.
 *
 */

157
void CDECL WCMD_output_stderr (const WCHAR *format, ...) {
158

159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
  __ms_va_list ap;
  WCHAR* string;
  DWORD len;

  __ms_va_start(ap,format);
  SetLastError(NO_ERROR);
  string = NULL;
  len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
                       format, 0, 0, (LPWSTR)&string, 0, &ap);
  __ms_va_end(ap);
  if (len == 0 && GetLastError() != NO_ERROR)
    WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
  else
  {
    WCMD_output_asis_len(string, len, GetStdHandle(STD_ERROR_HANDLE));
    LocalFree(string);
  }
}

/*******************************************************************
 * WCMD_format_string - allocate a buffer and format a string
 *
 */

WCHAR* CDECL WCMD_format_string (const WCHAR *format, ...) {

  __ms_va_list ap;
  WCHAR* string;
  DWORD len;

  __ms_va_start(ap,format);
  SetLastError(NO_ERROR);
  len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
                       format, 0, 0, (LPWSTR)&string, 0, &ap);
  __ms_va_end(ap);
  if (len == 0 && GetLastError() != NO_ERROR) {
    WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
    string = (WCHAR*)LocalAlloc(LMEM_FIXED, 2);
    *string = 0;
198
  }
199
  return string;
200
}
201

202 203 204
void WCMD_enter_paged_mode(const WCHAR *msg)
{
  CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
205

206 207 208 209 210 211 212 213 214 215 216 217
  if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
    max_height = consoleInfo.dwSize.Y;
    max_width  = consoleInfo.dwSize.X;
  } else {
    max_height = 25;
    max_width  = 80;
  }
  paged_mode = TRUE;
  line_count = 0;
  numChars   = 0;
  pagedMessage = (msg==NULL)? anykey : msg;
}
218

219 220 221 222 223
void WCMD_leave_paged_mode(void)
{
  paged_mode = FALSE;
  pagedMessage = NULL;
}
224

225 226 227 228 229
/***************************************************************************
 * WCMD_Readfile
 *
 *	Read characters in from a console/file, returning result in Unicode
 */
230 231
BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars, LPDWORD charsRead)
{
232 233
    DWORD numRead;
    char *buffer;
234

235
    if (WCMD_is_console_handle(hIn))
236 237
        /* Try to read from console as Unicode */
        return ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);
238

239 240 241
    /* We assume it's a file handle and read then convert from assumed OEM codepage */
    if (!(buffer = get_file_buffer()))
        return FALSE;
242

243 244
    if (!ReadFile(hIn, buffer, maxChars, &numRead, NULL))
        return FALSE;
245

246
    *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, buffer, numRead, intoBuf, maxChars);
247

248
    return TRUE;
249
}
250

251
/*******************************************************************
252 253 254
 * WCMD_output_asis_handle
 *
 * Send output to specified handle without formatting e.g. when message contains '%'
255
 */
256
static void WCMD_output_asis_handle (DWORD std_handle, const WCHAR *message) {
257 258 259
  DWORD count;
  const WCHAR* ptr;
  WCHAR string[1024];
260
  HANDLE handle = GetStdHandle(std_handle);
261

262 263 264 265 266 267 268 269
  if (paged_mode) {
    do {
      ptr = message;
      while (*ptr && *ptr!='\n' && (numChars < max_width)) {
        numChars++;
        ptr++;
      };
      if (*ptr == '\n') ptr++;
270 271 272 273 274 275
      WCMD_output_asis_len(message, ptr - message, handle);
      numChars = 0;
      if (++line_count >= max_height - 1) {
        line_count = 0;
        WCMD_output_asis_len(pagedMessage, strlenW(pagedMessage), handle);
        WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
276
      }
277
    } while (((message = ptr) != NULL) && (*ptr));
278
  } else {
279
    WCMD_output_asis_len(message, lstrlenW(message), handle);
280 281
  }
}
282

283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
/*******************************************************************
 * WCMD_output_asis
 *
 * Send output to current standard output device, without formatting
 * e.g. when message contains '%'
 */
void WCMD_output_asis (const WCHAR *message) {
    WCMD_output_asis_handle(STD_OUTPUT_HANDLE, message);
}

/*******************************************************************
 * WCMD_output_asis_stderr
 *
 * Send output to current standard error device, without formatting
 * e.g. when message contains '%'
 */
void WCMD_output_asis_stderr (const WCHAR *message) {
    WCMD_output_asis_handle(STD_ERROR_HANDLE, message);
}

303 304 305 306 307
/****************************************************************************
 * WCMD_print_error
 *
 * Print the message for GetLastError
 */
308

309 310 311 312
void WCMD_print_error (void) {
  LPVOID lpMsgBuf;
  DWORD error_code;
  int status;
313

314
  error_code = GetLastError ();
315 316
  status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
			  NULL, error_code, 0, (LPWSTR) &lpMsgBuf, 0, NULL);
317 318 319 320
  if (!status) {
    WINE_FIXME ("Cannot display message for error %d, status %d\n",
			error_code, GetLastError());
    return;
321 322
  }

323
  WCMD_output_asis_len(lpMsgBuf, lstrlenW(lpMsgBuf),
324 325
                       GetStdHandle(STD_ERROR_HANDLE));
  LocalFree (lpMsgBuf);
326
  WCMD_output_asis_len (newlineW, lstrlenW(newlineW),
327 328 329
                        GetStdHandle(STD_ERROR_HANDLE));
  return;
}
330

331 332 333 334 335
/******************************************************************************
 * WCMD_show_prompt
 *
 *	Display the prompt on STDout
 *
336 337
 */

338
static void WCMD_show_prompt (void) {
339

340 341 342 343 344
  int status;
  WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
  WCHAR *p, *q;
  DWORD len;
  static const WCHAR envPrompt[] = {'P','R','O','M','P','T','\0'};
345

346
  len = GetEnvironmentVariableW(envPrompt, prompt_string,
347 348
                                sizeof(prompt_string)/sizeof(WCHAR));
  if ((len == 0) || (len >= (sizeof(prompt_string)/sizeof(WCHAR)))) {
349
    static const WCHAR dfltPrompt[] = {'$','P','$','G','\0'};
350
    strcpyW (prompt_string, dfltPrompt);
351
  }
352 353
  p = prompt_string;
  q = out_string;
354 355
  *q++ = '\r';
  *q++ = '\n';
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377
  *q = '\0';
  while (*p != '\0') {
    if (*p != '$') {
      *q++ = *p++;
      *q = '\0';
    }
    else {
      p++;
      switch (toupper(*p)) {
        case '$':
	  *q++ = '$';
	  break;
	case 'A':
	  *q++ = '&';
	  break;
	case 'B':
	  *q++ = '|';
	  break;
	case 'C':
	  *q++ = '(';
	  break;
	case 'D':
378
	  GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
	  while (*q) q++;
	  break;
	case 'E':
	  *q++ = '\E';
	  break;
	case 'F':
	  *q++ = ')';
	  break;
	case 'G':
	  *q++ = '>';
	  break;
	case 'H':
	  *q++ = '\b';
	  break;
	case 'L':
	  *q++ = '<';
	  break;
	case 'N':
397
          status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
398 399 400 401 402
	  if (status) {
	    *q++ = curdir[0];
	  }
	  break;
	case 'P':
403
          status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
404 405 406 407 408 409 410 411 412 413 414 415
	  if (status) {
	    strcatW (q, curdir);
	    while (*q) q++;
	  }
	  break;
	case 'Q':
	  *q++ = '=';
	  break;
	case 'S':
	  *q++ = ' ';
	  break;
	case 'T':
416
	  GetTimeFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
	  while (*q) q++;
	  break;
        case 'V':
	  strcatW (q, version_string);
	  while (*q) q++;
          break;
	case '_':
	  *q++ = '\n';
	  break;
	case '+':
	  if (pushd_directories) {
	    memset(q, '+', pushd_directories->u.stackdepth);
	    q = q + pushd_directories->u.stackdepth;
	  }
	  break;
      }
      p++;
      *q = '\0';
    }
  }
  WCMD_output_asis (out_string);
}
439

440 441 442
void *heap_alloc(size_t size)
{
    void *ret;
443

444 445 446 447 448 449 450
    ret = HeapAlloc(GetProcessHeap(), 0, size);
    if(!ret) {
        ERR("Out of memory\n");
        ExitProcess(1);
    }

    return ret;
451
}
452

453 454 455 456 457 458
/*************************************************************************
 * WCMD_strsubstW
 *    Replaces a portion of a Unicode string with the specified string.
 *    It's up to the caller to ensure there is enough space in the
 *    destination buffer.
 */
459
void WCMD_strsubstW(WCHAR *start, const WCHAR *next, const WCHAR *insert, int len) {
460 461 462 463 464 465 466 467 468

   if (len < 0)
      len=insert ? lstrlenW(insert) : 0;
   if (start+len != next)
       memmove(start+len, next, (strlenW(next) + 1) * sizeof(*next));
   if (insert)
       memcpy(start, insert, len * sizeof(*insert));
}

469
/***************************************************************************
470
 * WCMD_skip_leading_spaces
471
 *
472
 *  Return a pointer to the first non-whitespace character of string.
473
 *  Does not modify the input string.
474
 */
475
WCHAR *WCMD_skip_leading_spaces (WCHAR *string) {
476

477
  WCHAR *ptr;
478

479
  ptr = string;
480
  while (*ptr == ' ' || *ptr == '\t') ptr++;
481 482
  return ptr;
}
483

484 485 486 487 488 489 490 491 492 493 494 495
/***************************************************************************
 * WCMD_keyword_ws_found
 *
 *  Checks if the string located at ptr matches a keyword (of length len)
 *  followed by a whitespace character (space or tab)
 */
BOOL WCMD_keyword_ws_found(const WCHAR *keyword, int len, const WCHAR *ptr) {
    return (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
                           ptr, len, keyword, len) == CSTR_EQUAL)
            && ((*(ptr + len) == ' ') || (*(ptr + len) == '\t'));
}

496
/*************************************************************************
497
 * WCMD_strip_quotes
498
 *
499 500
 *  Remove first and last quote WCHARacters, preserving all other text
 *  Returns the location of the final quote
501
 */
502 503
WCHAR *WCMD_strip_quotes(WCHAR *cmd) {
  WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL, *lastquote;
504 505 506 507 508
  while((*dest=*src) != '\0') {
      if (*src=='\"')
          lastq=dest;
      dest++, src++;
  }
509
  lastquote = lastq;
510 511 512 513 514
  if (lastq) {
      dest=lastq++;
      while ((*dest++=*lastq++) != 0)
          ;
  }
515
  return lastquote;
516 517
}

518

519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549
/*************************************************************************
 * WCMD_is_magic_envvar
 * Return TRUE if s is '%'magicvar'%'
 * and is not masked by a real environment variable.
 */

static inline BOOL WCMD_is_magic_envvar(const WCHAR *s, const WCHAR *magicvar)
{
    int len;

    if (s[0] != '%')
        return FALSE;         /* Didn't begin with % */
    len = strlenW(s);
    if (len < 2 || s[len-1] != '%')
        return FALSE;         /* Didn't end with another % */

    if (CompareStringW(LOCALE_USER_DEFAULT,
                       NORM_IGNORECASE | SORT_STRINGSORT,
                       s+1, len-2, magicvar, -1) != CSTR_EQUAL) {
        /* Name doesn't match. */
        return FALSE;
    }

    if (GetEnvironmentVariableW(magicvar, NULL, 0) > 0) {
        /* Masked by real environment variable. */
        return FALSE;
    }

    return TRUE;
}

550 551 552 553
/*************************************************************************
 * WCMD_expand_envvar
 *
 *	Expands environment variables, allowing for WCHARacter substitution
554
 */
555
static WCHAR *WCMD_expand_envvar(WCHAR *start, WCHAR startchar)
556
{
557 558 559 560 561 562
    WCHAR *endOfVar = NULL, *s;
    WCHAR *colonpos = NULL;
    WCHAR thisVar[MAXSTRING];
    WCHAR thisVarContents[MAXSTRING];
    WCHAR savedchar = 0x00;
    int len;
563

564 565 566 567 568
    static const WCHAR ErrorLvl[]  = {'E','R','R','O','R','L','E','V','E','L','\0'};
    static const WCHAR Date[]      = {'D','A','T','E','\0'};
    static const WCHAR Time[]      = {'T','I','M','E','\0'};
    static const WCHAR Cd[]        = {'C','D','\0'};
    static const WCHAR Random[]    = {'R','A','N','D','O','M','\0'};
569
    WCHAR Delims[]    = {'%',':','\0'}; /* First char gets replaced appropriately */
570

571
    WINE_TRACE("Expanding: %s (%c)\n", wine_dbgstr_w(start), startchar);
572

573
    /* Find the end of the environment variable, and extract name */
574
    Delims[0] = startchar;
575
    endOfVar = strpbrkW(start+1, Delims);
576

577
    if (endOfVar == NULL || *endOfVar==' ') {
Jason Edmeades's avatar
Jason Edmeades committed
578

579 580 581
      /* In batch program, missing terminator for % and no following
         ':' just removes the '%'                                   */
      if (context) {
582
        WCMD_strsubstW(start, start + 1, NULL, 0);
583 584
        return start;
      } else {
Jason Edmeades's avatar
Jason Edmeades committed
585

586 587 588 589
        /* In command processing, just ignore it - allows command line
           syntax like: for %i in (a.a) do echo %i                     */
        return start+1;
      }
Jason Edmeades's avatar
Jason Edmeades committed
590 591
    }

592 593 594
    /* If ':' found, process remaining up until '%' (or stop at ':' if
       a missing '%' */
    if (*endOfVar==':') {
595
        WCHAR *endOfVar2 = strchrW(endOfVar+1, startchar);
596
        if (endOfVar2 != NULL) endOfVar = endOfVar2;
597
    }
598

599 600 601
    memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
    thisVar[(endOfVar - start)+1] = 0x00;
    colonpos = strchrW(thisVar+1, ':');
Jason Edmeades's avatar
Jason Edmeades committed
602

603 604 605
    /* If there's complex substitution, just need %var% for now
       to get the expanded data to play with                    */
    if (colonpos) {
606
        *colonpos = startchar;
607 608
        savedchar = *(colonpos+1);
        *(colonpos+1) = 0x00;
Jason Edmeades's avatar
Jason Edmeades committed
609 610
    }

611 612 613 614 615 616 617
    /* By now, we know the variable we want to expand but it may be
       surrounded by '!' if we are in delayed expansion - if so convert
       to % signs.                                                      */
    if (startchar=='!') {
      thisVar[0]                  = '%';
      thisVar[(endOfVar - start)] = '%';
    }
618
    WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
619

620 621 622
    /* Expand to contents, if unchanged, return */
    /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
    /* override if existing env var called that name              */
623
    if (WCMD_is_magic_envvar(thisVar, ErrorLvl)) {
624
      static const WCHAR fmt[] = {'%','d','\0'};
625
      wsprintfW(thisVarContents, fmt, errorlevel);
626
      len = strlenW(thisVarContents);
627
    } else if (WCMD_is_magic_envvar(thisVar, Date)) {
628
      GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
629 630
                    NULL, thisVarContents, MAXSTRING);
      len = strlenW(thisVarContents);
631
    } else if (WCMD_is_magic_envvar(thisVar, Time)) {
632
      GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
633 634
                        NULL, thisVarContents, MAXSTRING);
      len = strlenW(thisVarContents);
635
    } else if (WCMD_is_magic_envvar(thisVar, Cd)) {
636
      GetCurrentDirectoryW(MAXSTRING, thisVarContents);
637
      len = strlenW(thisVarContents);
638
    } else if (WCMD_is_magic_envvar(thisVar, Random)) {
639
      static const WCHAR fmt[] = {'%','d','\0'};
640
      wsprintfW(thisVarContents, fmt, rand() % 32768);
641 642
      len = strlenW(thisVarContents);
    } else {
Jason Edmeades's avatar
Jason Edmeades committed
643

644
      len = ExpandEnvironmentStringsW(thisVar, thisVarContents,
645
                               sizeof(thisVarContents)/sizeof(WCHAR));
646
    }
647

648 649
    if (len == 0)
      return endOfVar+1;
650

651 652 653 654 655
    /* In a batch program, unknown env vars are replaced with nothing,
         note syntax %garbage:1,3% results in anything after the ':'
         except the %
       From the command line, you just get back what you entered      */
    if (lstrcmpiW(thisVar, thisVarContents) == 0) {
656

657 658 659 660
      /* Restore the complex part after the compare */
      if (colonpos) {
        *colonpos = ':';
        *(colonpos+1) = savedchar;
661
      }
662

663 664
      /* Command line - just ignore this */
      if (context == NULL) return endOfVar+1;
665

666 667 668

      /* Batch - replace unknown env var with nothing */
      if (colonpos == NULL) {
669
        WCMD_strsubstW(start, endOfVar + 1, NULL, 0);
670
      } else {
671 672 673 674
        len = strlenW(thisVar);
        thisVar[len-1] = 0x00;
        /* If %:...% supplied, : is retained */
        if (colonpos == thisVar+1) {
675
          WCMD_strsubstW(start, endOfVar + 1, colonpos, -1);
676
        } else {
677
          WCMD_strsubstW(start, endOfVar + 1, colonpos + 1, -1);
678
        }
679
      }
680
      return start;
681

682
    }
683

684 685 686
    /* See if we need to do complex substitution (any ':'s), if not
       then our work here is done                                  */
    if (colonpos == NULL) {
687
      WCMD_strsubstW(start, endOfVar + 1, thisVarContents, -1);
688 689
      return start;
    }
690

691 692 693
    /* Restore complex bit */
    *colonpos = ':';
    *(colonpos+1) = savedchar;
694

695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722
    /*
        Handle complex substitutions:
           xxx=yyy    (replace xxx with yyy)
           *xxx=yyy   (replace up to and including xxx with yyy)
           ~x         (from x WCHARs in)
           ~-x        (from x WCHARs from the end)
           ~x,y       (from x WCHARs in for y WCHARacters)
           ~x,-y      (from x WCHARs in until y WCHARacters from the end)
     */

    /* ~ is substring manipulation */
    if (savedchar == '~') {

      int   substrposition, substrlength = 0;
      WCHAR *commapos = strchrW(colonpos+2, ',');
      WCHAR *startCopy;

      substrposition = atolW(colonpos+2);
      if (commapos) substrlength = atolW(commapos+1);

      /* Check bounds */
      if (substrposition >= 0) {
        startCopy = &thisVarContents[min(substrposition, len)];
      } else {
        startCopy = &thisVarContents[max(0, len+substrposition-1)];
      }

      if (commapos == NULL) {
723 724
        /* Copy the lot */
        WCMD_strsubstW(start, endOfVar + 1, startCopy, -1);
725 726 727 728 729
      } else if (substrlength < 0) {

        int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
        if (copybytes > len) copybytes = len;
        else if (copybytes < 0) copybytes = 0;
730
        WCMD_strsubstW(start, endOfVar + 1, startCopy, copybytes);
731
      } else {
732
        substrlength = min(substrlength, len - (startCopy- thisVarContents + 1));
733
        WCMD_strsubstW(start, endOfVar + 1, startCopy, substrlength);
734 735 736 737
      }

    /* search and replace manipulation */
    } else {
738
      WCHAR *equalspos = strstrW(colonpos, equalW);
739 740 741 742 743 744
      WCHAR *replacewith = equalspos+1;
      WCHAR *found       = NULL;
      WCHAR *searchIn;
      WCHAR *searchFor;

      if (equalspos == NULL) return start+1;
745
      s = heap_strdupW(endOfVar + 1);
746 747 748 749 750 751

      /* Null terminate both strings */
      thisVar[strlenW(thisVar)-1] = 0x00;
      *equalspos = 0x00;

      /* Since we need to be case insensitive, copy the 2 buffers */
752
      searchIn  = heap_strdupW(thisVarContents);
753
      CharUpperBuffW(searchIn, strlenW(thisVarContents));
754
      searchFor = heap_strdupW(colonpos+1);
755
      CharUpperBuffW(searchFor, strlenW(colonpos+1));
756 757 758 759 760 761 762 763 764 765 766 767

      /* Handle wildcard case */
      if (*(colonpos+1) == '*') {
        /* Search for string to replace */
        found = strstrW(searchIn, searchFor+1);

        if (found) {
          /* Do replacement */
          strcpyW(start, replacewith);
          strcatW(start, thisVarContents + (found-searchIn) + strlenW(searchFor+1));
          strcatW(start, s);
        } else {
768
          /* Copy as is */
769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791
          strcpyW(start, thisVarContents);
          strcatW(start, s);
        }

      } else {
        /* Loop replacing all instances */
        WCHAR *lastFound = searchIn;
        WCHAR *outputposn = start;

        *start = 0x00;
        while ((found = strstrW(lastFound, searchFor))) {
            lstrcpynW(outputposn,
                    thisVarContents + (lastFound-searchIn),
                    (found - lastFound)+1);
            outputposn  = outputposn + (found - lastFound);
            strcatW(outputposn, replacewith);
            outputposn = outputposn + strlenW(replacewith);
            lastFound = found + strlenW(searchFor);
        }
        strcatW(outputposn,
                thisVarContents + (lastFound-searchIn));
        strcatW(outputposn, s);
      }
792 793 794
      heap_free(s);
      heap_free(searchIn);
      heap_free(searchFor);
795
    }
796
    return start;
797 798 799 800 801 802
}

/*****************************************************************************
 * Expand the command. Native expands lines from batch programs as they are
 * read in and not again, except for 'for' variable substitution.
 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
803 804
 * atExecute is TRUE when the expansion is occurring as the command is executed
 * rather than at parse time, i.e. delayed expansion and for loops need to be
805
 * processed
806
 */
807
static void handleExpansion(WCHAR *cmd, BOOL atExecute, BOOL delayed) {
808 809 810 811 812 813 814 815 816 817 818

  /* For commands in a context (batch program):                  */
  /*   Expand environment variables in a batch file %{0-9} first */
  /*     including support for any ~ modifiers                   */
  /* Additionally:                                               */
  /*   Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special  */
  /*     names allowing environment variable overrides           */
  /* NOTE: To support the %PATH:xxx% syntax, also perform        */
  /*   manual expansion of environment variables here            */

  WCHAR *p = cmd;
819
  WCHAR *t;
820
  int   i;
821 822 823
  WCHAR *delayedp = NULL;
  WCHAR  startchar = '%';
  WCHAR *normalp;
824

825 826 827 828 829 830 831 832 833
  /* Display the FOR variables in effect */
  for (i=0;i<52;i++) {
    if (forloopcontext.variable[i]) {
      WINE_TRACE("FOR variable context: %c = '%s'\n",
                 i<26?i+'a':(i-26)+'A',
                 wine_dbgstr_w(forloopcontext.variable[i]));
    }
  }

834 835 836 837 838 839 840 841 842
  /* Find the next environment variable delimiter */
  normalp = strchrW(p, '%');
  if (delayed) delayedp = strchrW(p, '!');
  if (!normalp) p = delayedp;
  else if (!delayedp) p = normalp;
  else p = min(p,delayedp);
  if (p) startchar = *p;

  while (p) {
843 844

    WINE_TRACE("Translate command:%s %d (at: %s)\n",
845
                   wine_dbgstr_w(cmd), atExecute, wine_dbgstr_w(p));
846 847
    i = *(p+1) - '0';

848
    /* Don't touch %% unless it's in Batch */
849
    if (!atExecute && *(p+1) == startchar) {
850
      if (context) {
851
        WCMD_strsubstW(p, p+1, NULL, 0);
852 853 854 855 856
      }
      p+=1;

    /* Replace %~ modifications if in batch program */
    } else if (*(p+1) == '~') {
857
      WCMD_HandleTildaModifiers(&p, atExecute);
858 859 860
      p++;

    /* Replace use of %0...%9 if in batch program*/
861
    } else if (!atExecute && context && (i >= 0) && (i <= 9) && startchar == '%') {
862
      t = WCMD_parameter(context -> command, i + context -> shift_count[i],
863
                         NULL, TRUE, TRUE);
864
      WCMD_strsubstW(p, p+2, t, -1);
865 866

    /* Replace use of %* if in batch program*/
867
    } else if (!atExecute && context && *(p+1)=='*' && startchar == '%') {
868
      WCHAR *startOfParms = NULL;
869
      WCHAR *thisParm = WCMD_parameter(context -> command, 0, &startOfParms, TRUE, TRUE);
870
      if (startOfParms != NULL) {
871
        startOfParms += strlenW(thisParm);
872
        while (*startOfParms==' ' || *startOfParms == '\t') startOfParms++;
873
        WCMD_strsubstW(p, p+2, startOfParms, -1);
874
      } else
875
        WCMD_strsubstW(p, p+2, NULL, 0);
876

877 878
    } else {
      int forvaridx = FOR_VAR_IDX(*(p+1));
879
      if (startchar == '%' && forvaridx != -1 && forloopcontext.variable[forvaridx]) {
880 881
        /* Replace the 2 characters, % and for variable character */
        WCMD_strsubstW(p, p + 2, forloopcontext.variable[forvaridx], -1);
882
      } else if (!atExecute || startchar == '!') {
883
        p = WCMD_expand_envvar(p, startchar);
884 885 886 887 888

      /* In a FOR loop, see if this is the variable to replace */
      } else { /* Ignore %'s on second pass of batch program */
        p++;
      }
889
    }
890 891 892 893 894 895 896 897

    /* Find the next environment variable delimiter */
    normalp = strchrW(p, '%');
    if (delayed) delayedp = strchrW(p, '!');
    if (!normalp) p = delayedp;
    else if (!delayedp) p = normalp;
    else p = min(p,delayedp);
    if (p) startchar = *p;
898
  }
899

900 901
  return;
}
902

903 904 905 906 907 908 909 910 911

/*******************************************************************
 * WCMD_parse - parse a command into parameters and qualifiers.
 *
 *	On exit, all qualifiers are concatenated into q, the first string
 *	not beginning with "/" is in p1 and the
 *	second in p2. Any subsequent non-qualifier strings are lost.
 *	Parameters in quotes are handled.
 */
912
static void WCMD_parse (const WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2)
913 914 915 916 917 918 919 920 921 922 923 924
{
  int p = 0;

  *q = *p1 = *p2 = '\0';
  while (TRUE) {
    switch (*s) {
      case '/':
        *q++ = *s++;
	while ((*s != '\0') && (*s != ' ') && *s != '/') {
	  *q++ = toupperW (*s++);
	}
        *q = '\0';
925
	break;
926 927 928
      case ' ':
      case '\t':
	s++;
929
	break;
930 931 932 933 934 935 936 937 938 939 940
      case '"':
	s++;
	while ((*s != '\0') && (*s != '"')) {
	  if (p == 0) *p1++ = *s++;
	  else if (p == 1) *p2++ = *s++;
	  else s++;
	}
        if (p == 0) *p1 = '\0';
        if (p == 1) *p2 = '\0';
        p++;
	if (*s == '"') s++;
941
	break;
942 943
      case '\0':
        return;
944
      default:
945 946 947 948 949 950 951 952
	while ((*s != '\0') && (*s != ' ') && (*s != '\t')
               && (*s != '=')  && (*s != ',') ) {
	  if (p == 0) *p1++ = *s++;
	  else if (p == 1) *p2++ = *s++;
	  else s++;
	}
        /* Skip concurrent parms */
	while ((*s == ' ') || (*s == '\t') || (*s == '=')  || (*s == ',') ) s++;
953

954 955 956
        if (p == 0) *p1 = '\0';
        if (p == 1) *p2 = '\0';
	p++;
957
    }
958
  }
959 960
}

961
static void init_msvcrt_io_block(STARTUPINFOW* st)
962
{
963
    STARTUPINFOW st_p;
964 965 966
    /* fetch the parent MSVCRT info block if any, so that the child can use the
     * same handles as its grand-father
     */
967 968
    st_p.cb = sizeof(STARTUPINFOW);
    GetStartupInfoW(&st_p);
969 970
    st->cbReserved2 = st_p.cbReserved2;
    st->lpReserved2 = st_p.lpReserved2;
971
    if (st_p.cbReserved2 && st_p.lpReserved2)
972
    {
973 974 975 976 977 978
        unsigned num = *(unsigned*)st_p.lpReserved2;
        char* flags;
        HANDLE* handles;
        BYTE *ptr;
        size_t sz;

979
        /* Override the entries for fd 0,1,2 if we happened
980 981
         * to change those std handles (this depends on the way cmd sets
         * its new input & output handles)
982
         */
983 984 985 986
        sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
        ptr = heap_alloc(sz);
        flags = (char*)(ptr + sizeof(unsigned));
        handles = (HANDLE*)(flags + num * sizeof(char));
987

988 989 990
        memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
        st->cbReserved2 = sz;
        st->lpReserved2 = ptr;
Mike McCormack's avatar
Mike McCormack committed
991

992
#define WX_OPEN 0x01    /* see dlls/msvcrt/file.c */
993 994 995 996
        if (num <= 0 || (flags[0] & WX_OPEN))
        {
            handles[0] = GetStdHandle(STD_INPUT_HANDLE);
            flags[0] |= WX_OPEN;
997
        }
998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008
        if (num <= 1 || (flags[1] & WX_OPEN))
        {
            handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
            flags[1] |= WX_OPEN;
        }
        if (num <= 2 || (flags[2] & WX_OPEN))
        {
            handles[2] = GetStdHandle(STD_ERROR_HANDLE);
            flags[2] |= WX_OPEN;
        }
#undef WX_OPEN
1009
    }
1010
}
1011 1012 1013 1014

/******************************************************************************
 * WCMD_run_program
 *
1015
 *	Execute a command line as an external program. Must allow recursion.
1016
 *
1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031
 *      Precedence:
 *        Manual testing under windows shows PATHEXT plays a key part in this,
 *      and the search algorithm and precedence appears to be as follows.
 *
 *      Search locations:
 *        If directory supplied on command, just use that directory
 *        If extension supplied on command, look for that explicit name first
 *        Otherwise, search in each directory on the path
 *      Precedence:
 *        If extension supplied on command, look for that explicit name first
 *        Then look for supplied name .* (even if extension supplied, so
 *          'garbage.exe' will match 'garbage.exe.cmd')
 *        If any found, cycle through PATHEXT looking for name.exe one by one
 *      Launching
 *        Once a match has been found, it is launched - Code currently uses
1032
 *          findexecutable to achieve this which is left untouched.
1033 1034 1035
 *        If an executable has not been found, and we were launched through
 *          a call, we need to check if the command is an internal command,
 *          so go back through wcmd_execute.
1036 1037
 */

1038 1039
void WCMD_run_program (WCHAR *command, BOOL called)
{
1040 1041 1042
  WCHAR  temp[MAX_PATH];
  WCHAR  pathtosearch[MAXSTRING];
  WCHAR *pathposn;
1043 1044
  WCHAR  stemofsearch[MAX_PATH];    /* maximum allowed executable name is
                                       MAX_PATH, including null character */
1045 1046
  WCHAR *lastSlash;
  WCHAR  pathext[MAXSTRING];
1047
  WCHAR *firstParam;
1048 1049
  BOOL  extensionsupplied = FALSE;
  BOOL  status;
1050
  DWORD len;
1051 1052
  static const WCHAR envPath[] = {'P','A','T','H','\0'};
  static const WCHAR delims[] = {'/','\\',':','\0'};
1053

1054 1055
  /* Quick way to get the filename is to extract the first argument. */
  WINE_TRACE("Running '%s' (%d)\n", wine_dbgstr_w(command), called);
1056
  firstParam = WCMD_parameter(command, 0, NULL, FALSE, TRUE);
1057
  if (!firstParam) return;
1058 1059

  /* Calculate the search path and stem to search for */
1060
  if (strpbrkW (firstParam, delims) == NULL) {  /* No explicit path given, search path */
1061 1062
    static const WCHAR curDir[] = {'.',';','\0'};
    strcpyW(pathtosearch, curDir);
1063
    len = GetEnvironmentVariableW(envPath, &pathtosearch[2], (sizeof(pathtosearch)/sizeof(WCHAR))-2);
1064 1065 1066
    if ((len == 0) || (len >= (sizeof(pathtosearch)/sizeof(WCHAR)) - 2)) {
      static const WCHAR curDir[] = {'.','\0'};
      strcpyW (pathtosearch, curDir);
1067
    }
1068 1069
    if (strchrW(firstParam, '.') != NULL) extensionsupplied = TRUE;
    if (strlenW(firstParam) >= MAX_PATH)
1070
    {
1071
        WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_LINETOOLONG));
1072 1073 1074
        return;
    }

1075
    strcpyW(stemofsearch, firstParam);
1076 1077 1078 1079

  } else {

    /* Convert eg. ..\fred to include a directory by removing file part */
1080
    GetFullPathNameW(firstParam, sizeof(pathtosearch)/sizeof(WCHAR), pathtosearch, NULL);
1081 1082 1083
    lastSlash = strrchrW(pathtosearch, '\\');
    if (lastSlash && strchrW(lastSlash, '.') != NULL) extensionsupplied = TRUE;
    strcpyW(stemofsearch, lastSlash+1);
1084 1085 1086 1087

    /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
       c:\windows\a.bat syntax                                                 */
    if (lastSlash) *(lastSlash + 1) = 0x00;
1088
  }
1089 1090

  /* Now extract PATHEXT */
1091
  len = GetEnvironmentVariableW(envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
1092 1093
  if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
    strcpyW (pathext, dfltPathExt);
1094 1095 1096 1097
  }

  /* Loop through the search path, dir by dir */
  pathposn = pathtosearch;
1098 1099
  WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
             wine_dbgstr_w(stemofsearch));
1100
  while (pathposn) {
1101 1102
    WCHAR  thisDir[MAX_PATH] = {'\0'};
    WCHAR *pos               = NULL;
1103 1104 1105
    BOOL  found             = FALSE;

    /* Work on the first directory on the search path */
1106
    pos = strchrW(pathposn, ';');
1107
    if (pos) {
1108
      memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
1109 1110 1111 1112
      thisDir[(pos-pathposn)] = 0x00;
      pathposn = pos+1;

    } else {
1113
      strcpyW(thisDir, pathposn);
1114
      pathposn = NULL;
1115
    }
1116

1117 1118
    /* Since you can have eg. ..\.. on the path, need to expand
       to full information                                      */
1119
    strcpyW(temp, thisDir);
1120
    GetFullPathNameW(temp, MAX_PATH, thisDir, NULL);
1121 1122

    /* 1. If extension supplied, see if that file exists */
1123 1124 1125
    strcatW(thisDir, slashW);
    strcatW(thisDir, stemofsearch);
    pos = &thisDir[strlenW(thisDir)]; /* Pos = end of name */
1126

1127 1128
    /* 1. If extension supplied, see if that file exists */
    if (extensionsupplied) {
1129
      if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1130 1131
        found = TRUE;
      }
1132 1133 1134 1135 1136
    }

    /* 2. Any .* matches? */
    if (!found) {
      HANDLE          h;
1137
      WIN32_FIND_DATAW finddata;
1138
      static const WCHAR allFiles[] = {'.','*','\0'};
1139

1140
      strcatW(thisDir,allFiles);
1141
      h = FindFirstFileW(thisDir, &finddata);
1142
      FindClose(h);
1143
      if (h != INVALID_HANDLE_VALUE) {
1144

1145
        WCHAR *thisExt = pathext;
1146 1147 1148

        /* 3. Yes - Try each path ext */
        while (thisExt) {
1149
          WCHAR *nextExt = strchrW(thisExt, ';');
1150 1151

          if (nextExt) {
1152
            memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
1153 1154 1155
            pos[(nextExt-thisExt)] = 0x00;
            thisExt = nextExt+1;
          } else {
1156
            strcpyW(pos, thisExt);
1157 1158 1159
            thisExt = NULL;
          }

1160
          if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1161 1162 1163 1164 1165 1166 1167 1168
            found = TRUE;
            thisExt = NULL;
          }
        }
      }
    }

    /* Once found, launch it */
1169
    if (found) {
1170
      STARTUPINFOW st;
1171
      PROCESS_INFORMATION pe;
1172
      SHFILEINFOW psfi;
1173 1174
      DWORD console;
      HINSTANCE hinst;
1175 1176 1177 1178
      WCHAR *ext = strrchrW( thisDir, '.' );
      static const WCHAR batExt[] = {'.','b','a','t','\0'};
      static const WCHAR cmdExt[] = {'.','c','m','d','\0'};

1179 1180
      WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));

1181
      /* Special case BAT and CMD */
1182
      if (ext && (!strcmpiW(ext, batExt) || !strcmpiW(ext, cmdExt))) {
1183 1184
        BOOL oldinteractive = interactive;
        interactive = FALSE;
1185
        WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1186
        interactive = oldinteractive;
1187 1188 1189 1190 1191
        return;
      } else {

        /* thisDir contains the file to be launched, but with what?
           eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1192
        hinst = FindExecutableW (thisDir, NULL, temp);
1193 1194 1195
        if ((INT_PTR)hinst < 32)
          console = 0;
        else
1196
          console = SHGetFileInfoW(temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1197

1198 1199
        ZeroMemory (&st, sizeof(STARTUPINFOW));
        st.cb = sizeof(STARTUPINFOW);
1200 1201
        init_msvcrt_io_block(&st);

1202 1203
        /* Launch the process and if a CUI wait on it to complete
           Note: Launching internal wine processes cannot specify a full path to exe */
1204
        status = CreateProcessW(thisDir,
1205
                                command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1206
        heap_free(st.lpReserved2);
1207 1208
        if ((opt_c || opt_k) && !opt_s && !status
            && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1209
          /* strip first and last quote WCHARacters and try again */
1210
          WCMD_strip_quotes(command);
1211
          opt_s = TRUE;
1212 1213 1214
          WCMD_run_program(command, called);
          return;
        }
1215 1216 1217 1218

        if (!status)
          break;

1219 1220
        /* Always wait when non-interactive (cmd /c or in batch program),
           or for console applications                                    */
1221
        if (!interactive || (console && !HIWORD(console)))
1222 1223 1224 1225
            WaitForSingleObject (pe.hProcess, INFINITE);
        GetExitCodeProcess (pe.hProcess, &errorlevel);
        if (errorlevel == STILL_ACTIVE) errorlevel = 0;

1226 1227 1228
        CloseHandle(pe.hProcess);
        CloseHandle(pe.hThread);
        return;
1229
      }
1230
    }
1231 1232
  }

1233 1234 1235 1236 1237 1238
  /* Not found anywhere - were we called? */
  if (called) {
    CMD_LIST *toExecute = NULL;         /* Commands left to be executed */

    /* Parse the command string, without reading any more input */
    WCMD_ReadAndParseLine(command, &toExecute, INVALID_HANDLE_VALUE);
1239
    WCMD_process_commands(toExecute, FALSE, called);
1240 1241 1242 1243 1244
    WCMD_free_commands(toExecute);
    toExecute = NULL;
    return;
  }

1245
  /* Not found anywhere - give up */
1246
  WCMD_output_stderr(WCMD_LoadMessage(WCMD_NO_COMMAND_FOUND), command);
1247 1248 1249 1250 1251 1252

  /* If a command fails to launch, it sets errorlevel 9009 - which
     does not seem to have any associated constant definition     */
  errorlevel = 9009;
  return;

1253 1254
}

1255 1256 1257
/*****************************************************************************
 * Process one command. If the command is EXIT this routine does not return.
 * We will recurse through here executing batch files.
1258 1259 1260
 * Note: If call is used to a non-existing program, we reparse the line and
 *       try to run it as an internal command. 'retrycall' represents whether
 *       we are attempting this retry.
1261
 */
1262
void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
1263
                   CMD_LIST **cmdList, BOOL retrycall)
1264 1265
{
    WCHAR *cmd, *p, *redir;
1266
    int status, i;
1267 1268 1269 1270 1271 1272
    DWORD count, creationDisposition;
    HANDLE h;
    WCHAR *whichcmd;
    SECURITY_ATTRIBUTES sa;
    WCHAR *new_cmd = NULL;
    WCHAR *new_redir = NULL;
1273 1274 1275
    HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE),
                                GetStdHandle (STD_OUTPUT_HANDLE),
                                GetStdHandle (STD_ERROR_HANDLE)};
1276 1277 1278
    DWORD  idx_stdhandles[3] = {STD_INPUT_HANDLE,
                                STD_OUTPUT_HANDLE,
                                STD_ERROR_HANDLE};
1279
    BOOL prev_echo_mode, piped = FALSE;
1280

1281 1282
    WINE_TRACE("command on entry:%s (%p)\n",
               wine_dbgstr_w(command), cmdList);
1283

1284 1285 1286 1287 1288 1289 1290 1291
    /* If the next command is a pipe then we implement pipes by redirecting
       the output from this command to a temp file and input into the
       next command from that temp file.
       FIXME: Use of named pipes would make more sense here as currently this
       process has to finish before the next one can start but this requires
       a change to not wait for the first app to finish but rather the pipe  */
    if (cmdList && (*cmdList)->nextcommand &&
        (*cmdList)->nextcommand->prevDelim == CMD_PIPE) {
1292

1293 1294
        WCHAR temp_path[MAX_PATH];
        static const WCHAR cmdW[]     = {'C','M','D','\0'};
1295

1296 1297 1298
        /* Remember piping is in action */
        WINE_TRACE("Output needs to be piped\n");
        piped = TRUE;
1299

1300
        /* Generate a unique temporary filename */
1301 1302
        GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
        GetTempFileNameW(temp_path, cmdW, 0, (*cmdList)->nextcommand->pipeFile);
1303 1304 1305
        WINE_TRACE("Using temporary file of %s\n",
                   wine_dbgstr_w((*cmdList)->nextcommand->pipeFile));
    }
1306

1307
    /* Move copy of the command onto the heap so it can be expanded */
1308
    new_cmd = heap_alloc(MAXSTRING * sizeof(WCHAR));
1309
    strcpyW(new_cmd, command);
1310

1311
    /* Move copy of the redirects onto the heap so it can be expanded */
1312
    new_redir = heap_alloc(MAXSTRING * sizeof(WCHAR));
1313

1314 1315 1316
    /* If piped output, send stdout to the pipe by appending >filename to redirects */
    if (piped) {
        static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
1317
        wsprintfW (new_redir, redirOut, redirects, (*cmdList)->nextcommand->pipeFile);
1318 1319 1320 1321
        WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir));
    } else {
        strcpyW(new_redir, redirects);
    }
1322

1323 1324
    /* Expand variables in command line mode only (batch mode will
       be expanded as the line is read in, except for 'for' loops) */
1325 1326
    handleExpansion(new_cmd, (context != NULL), delayedsubst);
    handleExpansion(new_redir, (context != NULL), delayedsubst);
1327 1328 1329 1330
    cmd = new_cmd;

/*
 *	Changing default drive has to be handled as a special case.
1331 1332
 */

1333
    if ((strlenW(cmd) == 2) && (cmd[1] == ':') && IsCharAlphaW(cmd[0])) {
1334 1335
      WCHAR envvar[5];
      WCHAR dir[MAX_PATH];
1336

1337 1338 1339
      /* According to MSDN CreateProcess docs, special env vars record
         the current directory on each drive, in the form =C:
         so see if one specified, and if so go back to it             */
1340
      strcpyW(envvar, equalW);
1341
      strcatW(envvar, cmd);
1342
      if (GetEnvironmentVariableW(envvar, dir, MAX_PATH) == 0) {
1343
        static const WCHAR fmt[] = {'%','s','\\','\0'};
1344
        wsprintfW(cmd, fmt, cmd);
1345
        WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd));
1346
      }
1347
      WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd));
1348
      status = SetCurrentDirectoryW(cmd);
1349
      if (!status) WCMD_print_error ();
1350 1351
      heap_free(cmd );
      heap_free(new_redir);
1352 1353
      return;
    }
1354

1355 1356 1357
    sa.nLength = sizeof(sa);
    sa.lpSecurityDescriptor = NULL;
    sa.bInheritHandle = TRUE;
1358

1359 1360
/*
 *	Redirect stdin, stdout and/or stderr if required.
1361 1362
 */

1363 1364 1365
    /* STDIN could come from a preceding pipe, so delete on close if it does */
    if (cmdList && (*cmdList)->pipeFile[0] != 0x00) {
        WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile));
1366
        h = CreateFileW((*cmdList)->pipeFile, GENERIC_READ,
1367
                  FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, OPEN_EXISTING,
1368 1369 1370
                  FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
        if (h == INVALID_HANDLE_VALUE) {
          WCMD_print_error ();
1371 1372
          heap_free(cmd);
          heap_free(new_redir);
1373 1374 1375 1376 1377 1378 1379 1380 1381
          return;
        }
        SetStdHandle (STD_INPUT_HANDLE, h);

        /* No need to remember the temporary name any longer once opened */
        (*cmdList)->pipeFile[0] = 0x00;

    /* Otherwise STDIN could come from a '<' redirect */
    } else if ((p = strchrW(new_redir,'<')) != NULL) {
1382
      h = CreateFileW(WCMD_parameter(++p, 0, NULL, FALSE, FALSE), GENERIC_READ, FILE_SHARE_READ,
1383
                      &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1384 1385
      if (h == INVALID_HANDLE_VALUE) {
	WCMD_print_error ();
1386 1387
        heap_free(cmd);
        heap_free(new_redir);
1388 1389 1390 1391 1392 1393 1394 1395 1396
	return;
      }
      SetStdHandle (STD_INPUT_HANDLE, h);
    }

    /* Scan the whole command looking for > and 2> */
    redir = new_redir;
    while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
      int handle = 0;
1397

1398
      if (p > redir && (*(p-1)=='2'))
1399
        handle = 2;
1400 1401
      else
        handle = 1;
1402

1403 1404 1405 1406 1407 1408 1409 1410
      p++;
      if ('>' == *p) {
        creationDisposition = OPEN_ALWAYS;
        p++;
      }
      else {
        creationDisposition = CREATE_ALWAYS;
      }
1411

1412 1413 1414 1415
      /* Add support for 2>&1 */
      redir = p;
      if (*p == '&') {
        int idx = *(p+1) - '0';
1416

1417 1418 1419 1420 1421 1422 1423 1424
        if (DuplicateHandle(GetCurrentProcess(),
                        GetStdHandle(idx_stdhandles[idx]),
                        GetCurrentProcess(),
                        &h,
                        0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
          WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
        }
        WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
1425

1426
      } else {
1427
        WCHAR *param = WCMD_parameter(p, 0, NULL, FALSE, FALSE);
1428 1429
        h = CreateFileW(param, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE,
                        &sa, creationDisposition, FILE_ATTRIBUTE_NORMAL, NULL);
1430 1431
        if (h == INVALID_HANDLE_VALUE) {
          WCMD_print_error ();
1432 1433
          heap_free(cmd);
          heap_free(new_redir);
1434 1435 1436 1437 1438 1439 1440 1441
          return;
        }
        if (SetFilePointer (h, 0, NULL, FILE_END) ==
              INVALID_SET_FILE_POINTER) {
          WCMD_print_error ();
        }
        WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
      }
1442

1443 1444
      SetStdHandle (idx_stdhandles[handle], h);
    }
1445

1446 1447
/*
 * Strip leading whitespaces, and a '@' if supplied
1448
 */
1449
    whichcmd = WCMD_skip_leading_spaces(cmd);
1450 1451
    WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
    if (whichcmd[0] == '@') whichcmd++;
1452

1453 1454 1455
/*
 *	Check if the command entered is internal. If it is, pass the rest of the
 *	line down to the command. If not try to run a program.
1456
 */
1457

1458
    count = 0;
1459
    while (IsCharAlphaNumericW(whichcmd[count])) {
1460 1461 1462
      count++;
    }
    for (i=0; i<=WCMD_EXIT; i++) {
1463
      if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1464
        whichcmd, count, inbuilt[i], -1) == CSTR_EQUAL) break;
1465
    }
1466
    p = WCMD_skip_leading_spaces (&whichcmd[count]);
1467 1468
    WCMD_parse (p, quals, param1, param2);
    WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1469

1470 1471
    if (i <= WCMD_EXIT && (p[0] == '/') && (p[1] == '?')) {
      /* this is a help request for a builtin program */
1472 1473 1474 1475 1476 1477
      i = WCMD_HELP;
      memcpy(p, whichcmd, count * sizeof(WCHAR));
      p[count] = '\0';

    }

1478
    switch (i) {
1479

1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490
      case WCMD_CALL:
        WCMD_call (p);
        break;
      case WCMD_CD:
      case WCMD_CHDIR:
        WCMD_setshow_default (p);
        break;
      case WCMD_CLS:
        WCMD_clear_screen ();
        break;
      case WCMD_COPY:
1491
        WCMD_copy (p);
1492 1493 1494 1495 1496 1497 1498 1499 1500
        break;
      case WCMD_CTTY:
        WCMD_change_tty ();
        break;
      case WCMD_DATE:
        WCMD_setshow_date ();
	break;
      case WCMD_DEL:
      case WCMD_ERASE:
1501
        WCMD_delete (p);
1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515
        break;
      case WCMD_DIR:
        WCMD_directory (p);
        break;
      case WCMD_ECHO:
        WCMD_echo(&whichcmd[count]);
        break;
      case WCMD_GOTO:
        WCMD_goto (cmdList);
        break;
      case WCMD_HELP:
        WCMD_give_help (p);
	break;
      case WCMD_LABEL:
1516
        WCMD_volume (TRUE, p);
1517 1518 1519
        break;
      case WCMD_MD:
      case WCMD_MKDIR:
1520
        WCMD_create_dir (p);
1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555
	break;
      case WCMD_MOVE:
        WCMD_move ();
        break;
      case WCMD_PATH:
        WCMD_setshow_path (p);
        break;
      case WCMD_PAUSE:
        WCMD_pause ();
        break;
      case WCMD_PROMPT:
        WCMD_setshow_prompt ();
        break;
      case WCMD_REM:
        break;
      case WCMD_REN:
      case WCMD_RENAME:
        WCMD_rename ();
	break;
      case WCMD_RD:
      case WCMD_RMDIR:
        WCMD_remove_dir (p);
        break;
      case WCMD_SETLOCAL:
        WCMD_setlocal(p);
        break;
      case WCMD_ENDLOCAL:
        WCMD_endlocal();
        break;
      case WCMD_SET:
        WCMD_setshow_env (p);
	break;
      case WCMD_SHIFT:
        WCMD_shift (p);
        break;
1556 1557 1558
      case WCMD_START:
        WCMD_start (p);
        break;
1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569
      case WCMD_TIME:
        WCMD_setshow_time ();
        break;
      case WCMD_TITLE:
        if (strlenW(&whichcmd[count]) > 0)
          WCMD_title(&whichcmd[count+1]);
        break;
      case WCMD_TYPE:
        WCMD_type (p);
	break;
      case WCMD_VER:
1570
        WCMD_output_asis(newlineW);
1571 1572 1573 1574 1575 1576
        WCMD_version ();
        break;
      case WCMD_VERIFY:
        WCMD_verify (p);
        break;
      case WCMD_VOL:
1577
        WCMD_volume (FALSE, p);
1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596
        break;
      case WCMD_PUSHD:
        WCMD_pushd(p);
        break;
      case WCMD_POPD:
        WCMD_popd();
        break;
      case WCMD_ASSOC:
        WCMD_assoc(p, TRUE);
        break;
      case WCMD_COLOR:
        WCMD_color();
        break;
      case WCMD_FTYPE:
        WCMD_assoc(p, FALSE);
        break;
      case WCMD_MORE:
        WCMD_more(p);
        break;
1597 1598 1599
      case WCMD_CHOICE:
        WCMD_choice(p);
        break;
1600 1601 1602
      case WCMD_EXIT:
        WCMD_exit (cmdList);
        break;
1603 1604 1605
      case WCMD_FOR:
      case WCMD_IF:
        /* Very oddly, probably because of all the special parsing required for
1606 1607
           these two commands, neither 'for' nor 'if' is supported when called,
           i.e. 'call if 1==1...' will fail.                                    */
1608 1609 1610 1611 1612 1613
        if (!retrycall) {
          if (i==WCMD_FOR) WCMD_for (p, cmdList);
          else if (i==WCMD_IF) WCMD_if (p, cmdList);
          break;
        }
        /* else: drop through */
1614
      default:
1615
        prev_echo_mode = echo_mode;
1616
        WCMD_run_program (whichcmd, FALSE);
1617
        echo_mode = prev_echo_mode;
1618
    }
1619 1620
    heap_free(cmd);
    heap_free(new_redir);
1621

1622 1623
    /* Restore old handles */
    for (i=0; i<3; i++) {
1624
      if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i])) {
1625 1626
        CloseHandle (GetStdHandle (idx_stdhandles[i]));
        SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
1627
      }
1628
    }
1629
}
1630

1631 1632 1633 1634 1635 1636 1637 1638
/*************************************************************************
 * WCMD_LoadMessage
 *    Load a string from the resource file, handling any error
 *    Returns string retrieved from resource file
 */
WCHAR *WCMD_LoadMessage(UINT id) {
    static WCHAR msg[2048];
    static const WCHAR failedMsg[]  = {'F','a','i','l','e','d','!','\0'};
1639

1640
    if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1641 1642
       WINE_FIXME("LoadString failed with %d\n", GetLastError());
       strcpyW(msg, failedMsg);
1643
    }
1644 1645
    return msg;
}
1646

1647 1648 1649 1650 1651 1652 1653
/***************************************************************************
 * WCMD_DumpCommands
 *
 *	Dumps out the parsed command line to ensure syntax is correct
 */
static void WCMD_DumpCommands(CMD_LIST *commands) {
    CMD_LIST *thisCmd = commands;
1654

1655 1656
    WINE_TRACE("Parsed line:\n");
    while (thisCmd != NULL) {
1657
      WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
1658 1659 1660 1661
               thisCmd,
               thisCmd->prevDelim,
               thisCmd->bracketDepth,
               thisCmd->nextcommand,
1662 1663
               wine_dbgstr_w(thisCmd->command),
               wine_dbgstr_w(thisCmd->redirects));
1664
      thisCmd = thisCmd->nextcommand;
1665
    }
1666
}
1667

1668 1669 1670 1671 1672
/***************************************************************************
 * WCMD_addCommand
 *
 *   Adds a command to the current command list
 */
1673
static void WCMD_addCommand(WCHAR *command, int *commandLen,
1674 1675 1676 1677
                     WCHAR *redirs,  int *redirLen,
                     WCHAR **copyTo, int **copyToLen,
                     CMD_DELIMITERS prevDelim, int curDepth,
                     CMD_LIST **lastEntry, CMD_LIST **output) {
1678

1679
    CMD_LIST *thisEntry = NULL;
1680

1681
    /* Allocate storage for command */
1682
    thisEntry = heap_alloc(sizeof(CMD_LIST));
1683

1684 1685
    /* Copy in the command */
    if (command) {
1686
        thisEntry->command = heap_alloc((*commandLen+1) * sizeof(WCHAR));
1687 1688
        memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
        thisEntry->command[*commandLen] = 0x00;
1689

1690
        /* Copy in the redirects */
1691
        thisEntry->redirects = heap_alloc((*redirLen+1) * sizeof(WCHAR));
1692 1693 1694
        memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
        thisEntry->redirects[*redirLen] = 0x00;
        thisEntry->pipeFile[0] = 0x00;
1695

1696 1697 1698 1699 1700
        /* Reset the lengths */
        *commandLen   = 0;
        *redirLen     = 0;
        *copyToLen    = commandLen;
        *copyTo       = command;
1701

1702
    } else {
1703
        thisEntry->command = NULL;
1704 1705
        thisEntry->redirects = NULL;
        thisEntry->pipeFile[0] = 0x00;
1706 1707
    }

1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718
    /* Fill in other fields */
    thisEntry->nextcommand = NULL;
    thisEntry->prevDelim = prevDelim;
    thisEntry->bracketDepth = curDepth;
    if (*lastEntry) {
        (*lastEntry)->nextcommand = thisEntry;
    } else {
        *output = thisEntry;
    }
    *lastEntry = thisEntry;
}
1719

1720 1721 1722 1723

/***************************************************************************
 * WCMD_IsEndQuote
 *
1724
 *   Checks if the quote pointed to is the end-quote.
1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735
 *
 *   Quotes end if:
 *
 *   1) The current parameter ends at EOL or at the beginning
 *      of a redirection or pipe and not in a quote section.
 *
 *   2) If the next character is a space and not in a quote section.
 *
 *   Returns TRUE if this is an end quote, and FALSE if it is not.
 *
 */
1736
static BOOL WCMD_IsEndQuote(const WCHAR *quote, int quoteIndex)
1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773
{
    int quoteCount = quoteIndex;
    int i;

    /* If we are not in a quoted section, then we are not an end-quote */
    if(quoteIndex == 0)
    {
        return FALSE;
    }

    /* Check how many quotes are left for this parameter */
    for(i=0;quote[i];i++)
    {
        if(quote[i] == '"')
        {
            quoteCount++;
        }

        /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
        else if(((quoteCount % 2) == 0)
            && ((quote[i] == '<') || (quote[i] == '>') || (quote[i] == '|') || (quote[i] == ' ')))
        {
            break;
        }
    }

    /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
       be an end-quote */
    if(quoteIndex >= (quoteCount / 2))
    {
        return TRUE;
    }

    /* No cigar */
    return FALSE;
}

1774 1775 1776 1777 1778
/***************************************************************************
 * WCMD_ReadAndParseLine
 *
 *   Either uses supplied input or
 *     Reads a file from the handle, and then...
1779
 *   Parse the text buffer, splitting into separate commands
1780 1781 1782 1783 1784 1785 1786 1787
 *     - unquoted && strings split 2 commands but the 2nd is flagged as
 *            following an &&
 *     - ( as the first character just ups the bracket depth
 *     - unquoted ) when bracket depth > 0 terminates a bracket and
 *            adds a CMD_LIST structure with null command
 *     - Anything else gets put into the command string (including
 *            redirects)
 */
1788
WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom)
1789
{
1790
    WCHAR    *curPos;
1791
    int       inQuotes = 0;
1792 1793 1794 1795 1796 1797 1798 1799 1800 1801
    WCHAR     curString[MAXSTRING];
    int       curStringLen = 0;
    WCHAR     curRedirs[MAXSTRING];
    int       curRedirsLen = 0;
    WCHAR    *curCopyTo;
    int      *curLen;
    int       curDepth = 0;
    CMD_LIST *lastEntry = NULL;
    CMD_DELIMITERS prevDelim = CMD_NONE;
    static WCHAR    *extraSpace = NULL;  /* Deliberately never freed */
1802 1803 1804 1805
    static const WCHAR remCmd[] = {'r','e','m'};
    static const WCHAR forCmd[] = {'f','o','r'};
    static const WCHAR ifCmd[]  = {'i','f'};
    static const WCHAR ifElse[] = {'e','l','s','e'};
1806
    BOOL      inOneLine = FALSE;
1807 1808 1809 1810 1811 1812 1813 1814 1815 1816
    BOOL      inFor = FALSE;
    BOOL      inIn  = FALSE;
    BOOL      inIf  = FALSE;
    BOOL      inElse= FALSE;
    BOOL      onlyWhiteSpace = FALSE;
    BOOL      lastWasWhiteSpace = FALSE;
    BOOL      lastWasDo   = FALSE;
    BOOL      lastWasIn   = FALSE;
    BOOL      lastWasElse = FALSE;
    BOOL      lastWasRedirect = TRUE;
1817
    BOOL      lastWasCaret = FALSE;
1818

1819 1820
    /* Allocate working space for a command read from keyboard, file etc */
    if (!extraSpace)
1821
        extraSpace = heap_alloc((MAXSTRING+1) * sizeof(WCHAR));
1822 1823 1824 1825
    if (!extraSpace)
    {
        WINE_ERR("Could not allocate memory for extraSpace\n");
        return NULL;
1826 1827
    }

1828 1829 1830 1831 1832 1833
    /* If initial command read in, use that, otherwise get input from handle */
    if (optionalcmd != NULL) {
        strcpyW(extraSpace, optionalcmd);
    } else if (readFrom == INVALID_HANDLE_VALUE) {
        WINE_FIXME("No command nor handle supplied\n");
    } else {
1834
        if (!WCMD_fgets(extraSpace, MAXSTRING, readFrom))
1835
          return NULL;
1836
    }
1837
    curPos = extraSpace;
1838

1839 1840
    /* Handle truncated input - issue warning */
    if (strlenW(extraSpace) == MAXSTRING -1) {
1841 1842
        WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
        WCMD_output_asis_stderr(extraSpace);
1843
        WCMD_output_asis_stderr(newlineW);
1844
    }
1845

1846
    /* Replace env vars if in a batch context */
1847
    if (context) handleExpansion(extraSpace, FALSE, FALSE);
1848

1849
    /* Skip preceding whitespace */
1850 1851
    while (*curPos == ' ' || *curPos == '\t') curPos++;

1852
    /* Show prompt before batch line IF echo is on and in batch program */
1853
    if (context && echo_mode && *curPos && (*curPos != '@')) {
1854 1855
      static const WCHAR echoDot[] = {'e','c','h','o','.'};
      static const WCHAR echoCol[] = {'e','c','h','o',':'};
1856
      static const WCHAR echoSlash[] = {'e','c','h','o','/'};
1857
      const DWORD len = sizeof(echoDot)/sizeof(echoDot[0]);
1858
      DWORD curr_size = strlenW(curPos);
1859
      DWORD min_len = (curr_size < len ? curr_size : len);
1860
      WCMD_show_prompt();
1861
      WCMD_output_asis(curPos);
1862
      /* I don't know why Windows puts a space here but it does */
1863
      /* Except for lines starting with 'echo.', 'echo:' or 'echo/'. Ask MS why */
1864
      if (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1865
                         curPos, min_len, echoDot, len) != CSTR_EQUAL
1866
          && CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1867 1868 1869
                         curPos, min_len, echoCol, len) != CSTR_EQUAL
          && CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
                         curPos, min_len, echoSlash, len) != CSTR_EQUAL)
1870
      {
1871
          WCMD_output_asis(spaceW);
1872
      }
1873
      WCMD_output_asis(newlineW);
1874
    }
1875

1876 1877 1878
    /* Skip repeated 'no echo' characters */
    while (*curPos == '@') curPos++;

1879 1880 1881 1882 1883
    /* Start with an empty string, copying to the command string */
    curStringLen = 0;
    curRedirsLen = 0;
    curCopyTo    = curString;
    curLen       = &curStringLen;
1884
    lastWasRedirect = FALSE;  /* Required e.g. for spaces between > and filename */
1885

1886 1887
    /* Parse every character on the line being processed */
    while (*curPos != 0x00) {
1888

1889
      WCHAR thisChar;
1890

1891 1892 1893 1894
      /* Debugging AID:
      WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
                 lastWasWhiteSpace, onlyWhiteSpace);
      */
1895

1896
      /* Prevent overflow caused by the caret escape char */
1897 1898 1899 1900 1901
      if (*curLen >= MAXSTRING) {
        WINE_ERR("Overflow detected in command\n");
        return NULL;
      }

1902 1903
      /* Certain commands need special handling */
      if (curStringLen == 0 && curCopyTo == curString) {
1904
        static const WCHAR forDO[] = {'d','o'};
1905

1906 1907 1908 1909
        /* If command starts with 'rem ' or identifies a label, ignore any &&, ( etc. */
        if (WCMD_keyword_ws_found(remCmd, sizeof(remCmd)/sizeof(remCmd[0]), curPos) ||
            *curPos == ':') {
          inOneLine = TRUE;
1910

1911
        } else if (WCMD_keyword_ws_found(forCmd, sizeof(forCmd)/sizeof(forCmd[0]), curPos)) {
1912
          inFor = TRUE;
1913

1914
        /* If command starts with 'if ' or 'else ', handle ('s mid line. We should ensure this
1915 1916 1917 1918 1919
           is only true in the command portion of the IF statement, but this
           should suffice for now
            FIXME: Silly syntax like "if 1(==1( (
                                        echo they equal
                                      )" will be parsed wrong */
1920
        } else if (WCMD_keyword_ws_found(ifCmd, sizeof(ifCmd)/sizeof(ifCmd[0]), curPos)) {
1921
          inIf = TRUE;
1922

1923 1924
        } else if (WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]), curPos)) {
          const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1925 1926 1927
          inElse = TRUE;
          lastWasElse = TRUE;
          onlyWhiteSpace = TRUE;
1928 1929 1930
          memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
          (*curLen)+=keyw_len;
          curPos+=keyw_len;
1931
          continue;
1932

1933 1934 1935 1936
        /* In a for loop, the DO command will follow a close bracket followed by
           whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
           is then 0, and all whitespace is skipped                                */
        } else if (inFor &&
1937 1938 1939
                   WCMD_keyword_ws_found(forDO, sizeof(forDO)/sizeof(forDO[0]), curPos)) {
          const int keyw_len = sizeof(forDO)/sizeof(forDO[0]) + 1;
          WINE_TRACE("Found 'DO '\n");
1940 1941
          lastWasDo = TRUE;
          onlyWhiteSpace = TRUE;
1942 1943 1944
          memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
          (*curLen)+=keyw_len;
          curPos+=keyw_len;
1945 1946 1947
          continue;
        }
      } else if (curCopyTo == curString) {
1948

1949 1950
        /* Special handling for the 'FOR' command */
        if (inFor && lastWasWhiteSpace) {
1951
          static const WCHAR forIN[] = {'i','n'};
1952

1953
          WINE_TRACE("Found 'FOR ', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
1954

1955 1956 1957
          if (WCMD_keyword_ws_found(forIN, sizeof(forIN)/sizeof(forIN[0]), curPos)) {
            const int keyw_len = sizeof(forIN)/sizeof(forIN[0]) + 1;
            WINE_TRACE("Found 'IN '\n");
1958 1959
            lastWasIn = TRUE;
            onlyWhiteSpace = TRUE;
1960 1961 1962
            memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
            (*curLen)+=keyw_len;
            curPos+=keyw_len;
1963 1964 1965 1966
            continue;
          }
        }
      }
1967

1968 1969 1970 1971 1972 1973
      /* Nothing 'ends' a one line statement (e.g. REM or :labels mean
         the &&, quotes and redirection etc are ineffective, so just force
         the use of the default processing by skipping character specific
         matching below)                                                   */
      if (!inOneLine) thisChar = *curPos;
      else            thisChar = 'X';  /* Character with no special processing */
1974

1975
      lastWasWhiteSpace = FALSE; /* Will be reset below */
1976
      lastWasCaret = FALSE;
1977

1978
      switch (thisChar) {
1979

1980 1981 1982 1983 1984 1985
      case '=': /* drop through - ignore token delimiters at the start of a command */
      case ',': /* drop through - ignore token delimiters at the start of a command */
      case '\t':/* drop through - ignore token delimiters at the start of a command */
      case ' ':
                /* If a redirect in place, it ends here */
                if (!inQuotes && !lastWasRedirect) {
1986

1987 1988 1989 1990 1991 1992 1993 1994 1995 1996
                  /* If finishing off a redirect, add a whitespace delimiter */
                  if (curCopyTo == curRedirs) {
                      curCopyTo[(*curLen)++] = ' ';
                  }
                  curCopyTo = curString;
                  curLen = &curStringLen;
                }
                if (*curLen > 0) {
                  curCopyTo[(*curLen)++] = *curPos;
                }
1997

1998 1999
                /* Remember just processed whitespace */
                lastWasWhiteSpace = TRUE;
2000

2001
                break;
2002

2003 2004 2005 2006 2007 2008 2009 2010
      case '>': /* drop through - handle redirect chars the same */
      case '<':
                /* Make a redirect start here */
                if (!inQuotes) {
                  curCopyTo = curRedirs;
                  curLen = &curRedirsLen;
                  lastWasRedirect = TRUE;
                }
2011

2012
                /* See if 1>, 2> etc, in which case we have some patching up
2013 2014 2015 2016 2017
                   to do (provided there's a preceding whitespace, and enough
                   chars read so far) */
                if (curStringLen > 2
                        && (*(curPos-1)>='1') && (*(curPos-1)<='9')
                        && ((*(curPos-2)==' ') || (*(curPos-2)=='\t'))) {
2018 2019 2020 2021
                    curStringLen--;
                    curString[curStringLen] = 0x00;
                    curCopyTo[(*curLen)++] = *(curPos-1);
                }
2022

2023
                curCopyTo[(*curLen)++] = *curPos;
2024 2025 2026 2027 2028 2029 2030

                /* If a redirect is immediately followed by '&' (ie. 2>&1) then
                    do not process that ampersand as an AND operator */
                if (thisChar == '>' && *(curPos+1) == '&') {
                    curCopyTo[(*curLen)++] = *(curPos+1);
                    curPos++;
                }
2031
                break;
2032

2033 2034 2035
      case '|': /* Pipe character only if not || */
                if (!inQuotes) {
                  lastWasRedirect = FALSE;
2036

2037 2038
                  /* Add an entry to the command list */
                  if (curStringLen > 0) {
2039

2040 2041 2042 2043 2044 2045
                    /* Add the current command */
                    WCMD_addCommand(curString, &curStringLen,
                          curRedirs, &curRedirsLen,
                          &curCopyTo, &curLen,
                          prevDelim, curDepth,
                          &lastEntry, output);
2046

2047
                  }
2048

2049 2050 2051 2052 2053 2054 2055 2056 2057 2058
                  if (*(curPos+1) == '|') {
                    curPos++; /* Skip other | */
                    prevDelim = CMD_ONFAILURE;
                  } else {
                    prevDelim = CMD_PIPE;
                  }
                } else {
                  curCopyTo[(*curLen)++] = *curPos;
                }
                break;
2059

2060 2061
      case '"': if (WCMD_IsEndQuote(curPos, inQuotes)) {
                    inQuotes--;
2062 2063 2064
                } else {
                    inQuotes++; /* Quotes within quotes are fun! */
                }
2065 2066 2067
                curCopyTo[(*curLen)++] = *curPos;
                lastWasRedirect = FALSE;
                break;
2068

2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079
      case '(': /* If a '(' is the first non whitespace in a command portion
                   ie start of line or just after &&, then we read until an
                   unquoted ) is found                                       */
                WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
                           ", for(%d, In:%d, Do:%d)"
                           ", if(%d, else:%d, lwe:%d)\n",
                           *curLen, inQuotes,
                           onlyWhiteSpace,
                           inFor, lastWasIn, lastWasDo,
                           inIf, inElse, lastWasElse);
                lastWasRedirect = FALSE;
2080

2081 2082 2083
                /* Ignore open brackets inside the for set */
                if (*curLen == 0 && !inIn) {
                  curDepth++;
2084

2085 2086 2087
                /* If in quotes, ignore brackets */
                } else if (inQuotes) {
                  curCopyTo[(*curLen)++] = *curPos;
2088

2089 2090 2091 2092 2093 2094 2095 2096 2097 2098
                /* In a FOR loop, an unquoted '(' may occur straight after
                      IN or DO
                   In an IF statement just handle it regardless as we don't
                      parse the operands
                   In an ELSE statement, only allow it straight away after
                      the ELSE and whitespace
                 */
                } else if (inIf ||
                           (inElse && lastWasElse && onlyWhiteSpace) ||
                           (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2099

2100 2101 2102 2103 2104
                   /* If entering into an 'IN', set inIn */
                  if (inFor && lastWasIn && onlyWhiteSpace) {
                    WINE_TRACE("Inside an IN\n");
                    inIn = TRUE;
                  }
2105

2106 2107 2108 2109 2110 2111
                  /* Add the current command */
                  WCMD_addCommand(curString, &curStringLen,
                                  curRedirs, &curRedirsLen,
                                  &curCopyTo, &curLen,
                                  prevDelim, curDepth,
                                  &lastEntry, output);
2112

2113 2114 2115 2116 2117
                  curDepth++;
                } else {
                  curCopyTo[(*curLen)++] = *curPos;
                }
                break;
2118

2119 2120 2121 2122 2123 2124 2125 2126 2127
      case '^': if (!inQuotes) {
                  /* If we reach the end of the input, we need to wait for more */
                  if (*(curPos+1) == 0x00) {
                    lastWasCaret = TRUE;
                    WINE_TRACE("Caret found at end of line\n");
                    break;
                  }
                  curPos++;
                }
2128 2129 2130
                curCopyTo[(*curLen)++] = *curPos;
                break;

2131 2132
      case '&': if (!inQuotes) {
                  lastWasRedirect = FALSE;
2133

2134 2135
                  /* Add an entry to the command list */
                  if (curStringLen > 0) {
2136

2137 2138 2139 2140 2141 2142
                    /* Add the current command */
                    WCMD_addCommand(curString, &curStringLen,
                          curRedirs, &curRedirsLen,
                          &curCopyTo, &curLen,
                          prevDelim, curDepth,
                          &lastEntry, output);
2143

2144
                  }
2145

2146 2147 2148 2149 2150 2151 2152 2153 2154 2155
                  if (*(curPos+1) == '&') {
                    curPos++; /* Skip other & */
                    prevDelim = CMD_ONSUCCESS;
                  } else {
                    prevDelim = CMD_NONE;
                  }
                } else {
                  curCopyTo[(*curLen)++] = *curPos;
                }
                break;
2156

2157 2158
      case ')': if (!inQuotes && curDepth > 0) {
                  lastWasRedirect = FALSE;
2159

2160 2161
                  /* Add the current command if there is one */
                  if (curStringLen) {
2162

2163 2164 2165 2166 2167 2168 2169
                      /* Add the current command */
                      WCMD_addCommand(curString, &curStringLen,
                            curRedirs, &curRedirsLen,
                            &curCopyTo, &curLen,
                            prevDelim, curDepth,
                            &lastEntry, output);
                  }
2170

2171 2172 2173 2174 2175 2176 2177 2178
                  /* Add an empty entry to the command list */
                  prevDelim = CMD_NONE;
                  WCMD_addCommand(NULL, &curStringLen,
                        curRedirs, &curRedirsLen,
                        &curCopyTo, &curLen,
                        prevDelim, curDepth,
                        &lastEntry, output);
                  curDepth--;
2179

2180 2181 2182 2183 2184 2185 2186 2187 2188 2189
                  /* Leave inIn if necessary */
                  if (inIn) inIn =  FALSE;
                } else {
                  curCopyTo[(*curLen)++] = *curPos;
                }
                break;
      default:
                lastWasRedirect = FALSE;
                curCopyTo[(*curLen)++] = *curPos;
      }
2190

2191
      curPos++;
2192

2193 2194 2195
      /* At various times we need to know if we have only skipped whitespace,
         so reset this variable and then it will remain true until a non
         whitespace is found                                               */
2196 2197
      if ((thisChar != ' ') && (thisChar != '\t') && (thisChar != '\n'))
        onlyWhiteSpace = FALSE;
2198

2199 2200 2201 2202
      /* Flag end of interest in FOR DO and IN parms once something has been processed */
      if (!lastWasWhiteSpace) {
        lastWasIn = lastWasDo = FALSE;
      }
2203

2204 2205 2206
      /* If we have reached the end, add this command into the list
         Do not add command to list if escape char ^ was last */
      if (*curPos == 0x00 && !lastWasCaret && *curLen > 0) {
2207

2208 2209 2210 2211 2212 2213 2214
          /* Add an entry to the command list */
          WCMD_addCommand(curString, &curStringLen,
                curRedirs, &curRedirsLen,
                &curCopyTo, &curLen,
                prevDelim, curDepth,
                &lastEntry, output);
      }
2215

2216 2217 2218 2219 2220 2221 2222
      /* If we have reached the end of the string, see if bracketing or
         final caret is outstanding */
      if (*curPos == 0x00 && (curDepth > 0 || lastWasCaret) &&
          readFrom != INVALID_HANDLE_VALUE) {
        WCHAR *extraData;

        WINE_TRACE("Need to read more data as outstanding brackets or carets\n");
2223
        inOneLine = FALSE;
2224
        prevDelim = CMD_NONE;
2225
        inQuotes = 0;
2226
        memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2227
        extraData = extraSpace;
2228

2229
        /* Read more, skipping any blank lines */
2230 2231
        do {
          WINE_TRACE("Read more input\n");
2232
          if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2233
          if (!WCMD_fgets(extraData, MAXSTRING, readFrom))
2234
            break;
2235

2236
          /* Edge case for carets - a completely blank line (i.e. was just
2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247
             CRLF) is oddly added as an LF but then more data is received (but
             only once more!) */
          if (lastWasCaret) {
            if (*extraSpace == 0x00) {
              WINE_TRACE("Read nothing, so appending LF char and will try again\n");
              *extraData++ = '\r';
              *extraData = 0x00;
            } else break;
          }

        } while (*extraData == 0x00);
2248
        curPos = extraSpace;
2249
        if (context) handleExpansion(extraSpace, FALSE, FALSE);
2250 2251 2252
        /* Continue to echo commands IF echo is on and in batch program */
        if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
          WCMD_output_asis(extraSpace);
2253
          WCMD_output_asis(newlineW);
2254
        }
2255 2256
      }
    }
2257

2258 2259
    /* Dump out the parsed output */
    WCMD_DumpCommands(*output);
2260

2261 2262
    return extraSpace;
}
2263

2264 2265 2266 2267 2268 2269
/***************************************************************************
 * WCMD_process_commands
 *
 * Process all the commands read in so far
 */
CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2270
                                BOOL retrycall) {
2271

2272
    int bdepth = -1;
2273

2274
    if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2275

2276 2277
    /* Loop through the commands, processing them one by one */
    while (thisCmd) {
2278

2279
      CMD_LIST *origCmd = thisCmd;
2280

2281 2282 2283 2284 2285 2286 2287 2288
      /* If processing one bracket only, and we find the end bracket
         entry (or less), return                                    */
      if (oneBracket && !thisCmd->command &&
          bdepth <= thisCmd->bracketDepth) {
        WINE_TRACE("Finished bracket @ %p, next command is %p\n",
                   thisCmd, thisCmd->nextcommand);
        return thisCmd->nextcommand;
      }
2289

2290 2291 2292 2293 2294
      /* Ignore the NULL entries a ')' inserts (Only 'if' cares
         about them and it will be handled in there)
         Also, skip over any batch labels (eg. :fred)          */
      if (thisCmd->command && thisCmd->command[0] != ':') {
        WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2295
        WCMD_execute (thisCmd->command, thisCmd->redirects, &thisCmd, retrycall);
2296
      }
2297

2298 2299 2300 2301 2302
      /* Step on unless the command itself already stepped on */
      if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
    }
    return NULL;
}
2303

2304 2305 2306 2307 2308 2309 2310 2311 2312
/***************************************************************************
 * WCMD_free_commands
 *
 * Frees the storage held for a parsed command line
 * - This is not done in the process_commands, as eventually the current
 *   pointer will be modified within the commands, and hence a single free
 *   routine is simpler
 */
void WCMD_free_commands(CMD_LIST *cmds) {
2313

2314 2315 2316 2317
    /* Loop through the commands, freeing them one by one */
    while (cmds) {
      CMD_LIST *thisCmd = cmds;
      cmds = cmds->nextcommand;
2318 2319 2320
      heap_free(thisCmd->command);
      heap_free(thisCmd->redirects);
      heap_free(thisCmd);
2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332
    }
}


/*****************************************************************************
 * Main entry point. This is a console application so we have a main() not a
 * winmain().
 */

int wmain (int argc, WCHAR *argvW[])
{
  int     args;
2333 2334 2335
  WCHAR  *cmdLine = NULL;
  WCHAR  *cmd     = NULL;
  WCHAR  *argPos  = NULL;
2336 2337
  WCHAR string[1024];
  WCHAR envvar[4];
2338
  BOOL opt_q;
2339
  int opt_t = 0;
2340
  static const WCHAR offW[] = {'O','F','F','\0'};
2341 2342
  static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
  static const WCHAR defaultpromptW[] = {'$','P','$','G','\0'};
2343
  CMD_LIST *toExecute = NULL;         /* Commands left to be executed */
2344 2345
  OSVERSIONINFOW osv;
  char osver[50];
2346 2347

  srand(time(NULL));
2348

2349 2350 2351 2352
  /* Get the windows version being emulated */
  osv.dwOSVersionInfoSize = sizeof(osv);
  GetVersionExW(&osv);

2353 2354
  /* Pre initialize some messages */
  strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
2355 2356 2357
  sprintf(osver, "%d.%d.%d (%s)", osv.dwMajorVersion, osv.dwMinorVersion,
          osv.dwBuildNumber, PACKAGE_VERSION);
  cmd = WCMD_format_string(WCMD_LoadMessage(WCMD_VERSION), osver);
2358 2359 2360
  strcpyW(version_string, cmd);
  LocalFree(cmd);
  cmd = NULL;
2361

2362 2363 2364 2365 2366
  /* Can't use argc/argv as it will have stripped quotes from parameters
   * meaning cmd.exe /C echo "quoted string" is impossible
   */
  cmdLine = GetCommandLineW();
  WINE_TRACE("Full commandline '%s'\n", wine_dbgstr_w(cmdLine));
2367
  args = 0;
2368

2369
  opt_c = opt_k = opt_q = opt_s = FALSE;
2370
  WCMD_parameter(cmdLine, args, &argPos, TRUE, TRUE);
2371
  while (argPos && argPos[0] != 0x00)
2372 2373
  {
      WCHAR c;
2374 2375 2376
      WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(argPos));
      if (argPos[0]!='/' || argPos[1]=='\0') {
          args++;
2377
          WCMD_parameter(cmdLine, args, &argPos, TRUE, TRUE);
2378 2379
          continue;
      }
2380

2381
      c=argPos[1];
2382
      if (tolowerW(c)=='c') {
2383
          opt_c = TRUE;
2384
      } else if (tolowerW(c)=='q') {
2385
          opt_q = TRUE;
2386
      } else if (tolowerW(c)=='k') {
2387
          opt_k = TRUE;
2388
      } else if (tolowerW(c)=='s') {
2389
          opt_s = TRUE;
2390
      } else if (tolowerW(c)=='a') {
2391
          unicodeOutput = FALSE;
2392
      } else if (tolowerW(c)=='u') {
2393
          unicodeOutput = TRUE;
2394 2395 2396
      } else if (tolowerW(c)=='v' && argPos[2]==':') {
          delayedsubst = strncmpiW(&argPos[3], offW, 3);
          if (delayedsubst) WINE_TRACE("Delayed substitution is on\n");
2397 2398
      } else if (tolowerW(c)=='t' && argPos[2]==':') {
          opt_t=strtoulW(&argPos[3], NULL, 16);
2399 2400 2401
      } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
          /* Ignored for compatibility with Windows */
      }
2402

2403 2404
      if (argPos[2]==0 || argPos[2]==' ' || argPos[2]=='\t' ||
          tolowerW(c)=='v') {
2405
          args++;
2406
          WCMD_parameter(cmdLine, args, &argPos, TRUE, TRUE);
2407 2408 2409
      }
      else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
      {
2410
          /* Do not step to next parameter, instead carry on parsing this one */
2411
          argPos+=2;
2412
      }
2413

2414 2415 2416
      if (opt_c || opt_k) /* break out of parsing immediately after c or k */
          break;
  }
2417

2418
  if (opt_q) {
2419
    WCMD_echo(offW);
2420
  }
2421

2422 2423 2424
  /* Until we start to read from the keyboard, stay as non-interactive */
  interactive = FALSE;

2425 2426
  SetEnvironmentVariableW(promptW, defaultpromptW);

2427
  if (opt_c || opt_k) {
2428 2429 2430
      int     len;
      WCHAR   *q1 = NULL,*q2 = NULL,*p;

2431
      /* Handle very edge case error scenario, "cmd.exe /c" ie when there are no
2432 2433 2434 2435
       * parameters after the /C or /K by pretending there was a single space     */
      if (argPos == NULL) argPos = (WCHAR *)spaceW;

      /* Take a copy */
2436
      cmd = heap_strdupW(argPos);
2437

2438 2439 2440 2441
      /* opt_s left unflagged if the command starts with and contains exactly
       * one quoted string (exactly two quote characters). The quoted string
       * must be an executable name that has whitespace and must not have the
       * following characters: &<>()@^| */
2442

2443 2444 2445 2446
      if (!opt_s) {
        /* 1. Confirm there is at least one quote */
        q1 = strchrW(argPos, '"');
        if (!q1) opt_s=1;
2447
      }
2448

2449 2450 2451 2452 2453
      if (!opt_s) {
          /* 2. Confirm there is a second quote */
          q2 = strchrW(q1+1, '"');
          if (!q2) opt_s=1;
      }
2454

2455 2456 2457 2458 2459 2460 2461
      if (!opt_s) {
          /* 3. Ensure there are no more quotes */
          if (strchrW(q2+1, '"')) opt_s=1;
      }

      /* check first parameter for a space and invalid characters. There must not be any
       * invalid characters, but there must be one or more whitespace                    */
2462
      if (!opt_s) {
2463
          opt_s = TRUE;
2464 2465
          p=q1;
          while (p!=q2) {
2466 2467
              if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
                  || *p=='@' || *p=='^' || *p=='|') {
2468
                  opt_s = TRUE;
2469 2470
                  break;
              }
2471
              if (*p==' ' || *p=='\t')
2472
                  opt_s = FALSE;
2473 2474 2475
              p++;
          }
      }
2476

2477
      WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
Jason Edmeades's avatar
Jason Edmeades committed
2478

2479
      /* Finally, we only stay in new mode IF the first parameter is quoted and
2480
         is a valid executable, i.e. must exist, otherwise drop back to old mode  */
2481
      if (!opt_s) {
2482
        WCHAR *thisArg = WCMD_parameter(cmd, 0, NULL, FALSE, TRUE);
2483
        WCHAR  pathext[MAXSTRING];
2484 2485
        BOOL found = FALSE;

2486 2487 2488 2489 2490 2491
        /* Now extract PATHEXT */
        len = GetEnvironmentVariableW(envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
        if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
          strcpyW (pathext, dfltPathExt);
        }

2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503
        /* If the supplied parameter has any directory information, look there */
        WINE_TRACE("First parameter is '%s'\n", wine_dbgstr_w(thisArg));
        if (strchrW(thisArg, '\\') != NULL) {

          GetFullPathNameW(thisArg, sizeof(string)/sizeof(WCHAR), string, NULL);
          WINE_TRACE("Full path name '%s'\n", wine_dbgstr_w(string));
          p = string + strlenW(string);

          /* Does file exist with this name? */
          if (GetFileAttributesW(string) != INVALID_FILE_ATTRIBUTES) {
            WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string));
            found = TRUE;
2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518
          } else {
            WCHAR *thisExt = pathext;

            /* No - try with each of the PATHEXT extensions */
            while (!found && thisExt) {
              WCHAR *nextExt = strchrW(thisExt, ';');

              if (nextExt) {
                memcpy(p, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
                p[(nextExt-thisExt)] = 0x00;
                thisExt = nextExt+1;
              } else {
                strcpyW(p, thisExt);
                thisExt = NULL;
              }
2519

2520 2521 2522 2523 2524 2525
              /* Does file exist with this extension appended? */
              if (GetFileAttributesW(string) != INVALID_FILE_ATTRIBUTES) {
                WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string));
                found = TRUE;
              }
            }
2526 2527 2528 2529 2530 2531 2532 2533 2534 2535
          }

        /* Otherwise we now need to look in the path to see if we can find it */
        } else {
          p = thisArg + strlenW(thisArg);

          /* Does file exist with this name? */
          if (SearchPathW(NULL, thisArg, NULL, sizeof(string)/sizeof(WCHAR), string, NULL) != 0)  {
            WINE_TRACE("Found on path as '%s'\n", wine_dbgstr_w(string));
            found = TRUE;
2536 2537
          } else {
            WCHAR *thisExt = pathext;
2538

2539 2540 2541
            /* No - try with each of the PATHEXT extensions */
            while (!found && thisExt) {
              WCHAR *nextExt = strchrW(thisExt, ';');
2542

2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557
              if (nextExt) {
                *nextExt = 0;
                nextExt = nextExt+1;
              } else {
                nextExt = NULL;
              }

              /* Does file exist with this extension? */
              if (SearchPathW(NULL, thisArg, thisExt, sizeof(string)/sizeof(WCHAR), string, NULL) != 0)  {
                WINE_TRACE("Found on path as '%s' with extension '%s'\n", wine_dbgstr_w(string),
                           wine_dbgstr_w(thisExt));
                found = TRUE;
              }
              thisExt = nextExt;
            }
2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568
          }
        }

        /* If not found, drop back to old behaviour */
        if (!found) {
          WINE_TRACE("Binary not found, dropping back to old behaviour\n");
          opt_s = TRUE;
        }

      }

2569 2570 2571
      /* strip first and last quote characters if opt_s; check for invalid
       * executable is done later */
      if (opt_s && *cmd=='\"')
2572
          WCMD_strip_quotes(cmd);
2573
  }
2574

2575 2576 2577 2578 2579 2580 2581 2582 2583
  /* Save cwd into appropriate env var (Must be before the /c processing */
  GetCurrentDirectoryW(sizeof(string)/sizeof(WCHAR), string);
  if (IsCharAlphaW(string[0]) && string[1] == ':') {
    static const WCHAR fmt[] = {'=','%','c',':','\0'};
    wsprintfW(envvar, fmt, string[0]);
    SetEnvironmentVariableW(envvar, string);
    WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
  }

2584
  if (opt_c) {
2585
      /* If we do a "cmd /c command", we don't want to allocate a new
2586 2587 2588 2589
       * console since the command returns immediately. Rather, we use
       * the currently allocated input and output handles. This allows
       * us to pipe to and read from the command interpreter.
       */
2590

2591
      /* Parse the command string, without reading any more input */
2592
      WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2593
      WCMD_process_commands(toExecute, FALSE, FALSE);
2594 2595
      WCMD_free_commands(toExecute);
      toExecute = NULL;
2596

2597
      heap_free(cmd);
2598 2599
      return errorlevel;
  }
2600

2601
  SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE));
2602

2603 2604 2605 2606 2607 2608
  /* Note: cmd.exe /c dir does not get a new color, /k dir does */
  if (opt_t) {
      if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
          defaultColor = opt_t & 0xFF;
          param1[0] = 0x00;
          WCMD_color();
2609
      }
2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622
  } else {
      /* Check HKCU\Software\Microsoft\Command Processor
         Then  HKLM\Software\Microsoft\Command Processor
           for defaultcolour value
           Note  Can be supplied as DWORD or REG_SZ
           Note2 When supplied as REG_SZ it's in decimal!!! */
      HKEY key;
      DWORD type;
      DWORD value=0, size=4;
      static const WCHAR regKeyW[] = {'S','o','f','t','w','a','r','e','\\',
                                      'M','i','c','r','o','s','o','f','t','\\',
                                      'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
      static const WCHAR dfltColorW[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
2623

2624
      if (RegOpenKeyExW(HKEY_CURRENT_USER, regKeyW,
2625 2626
                       0, KEY_READ, &key) == ERROR_SUCCESS) {
          WCHAR  strvalue[4];
2627

2628
          /* See if DWORD or REG_SZ */
2629
          if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2630 2631 2632
                     NULL, NULL) == ERROR_SUCCESS) {
              if (type == REG_DWORD) {
                  size = sizeof(DWORD);
2633
                  RegQueryValueExW(key, dfltColorW, NULL, NULL,
2634 2635 2636
                                  (LPBYTE)&value, &size);
              } else if (type == REG_SZ) {
                  size = sizeof(strvalue)/sizeof(WCHAR);
2637
                  RegQueryValueExW(key, dfltColorW, NULL, NULL,
2638 2639 2640 2641 2642
                                  (LPBYTE)strvalue, &size);
                  value = strtoulW(strvalue, NULL, 10);
              }
          }
          RegCloseKey(key);
2643 2644
      }

2645
      if (value == 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE, regKeyW,
2646 2647
                       0, KEY_READ, &key) == ERROR_SUCCESS) {
          WCHAR  strvalue[4];
2648

2649
          /* See if DWORD or REG_SZ */
2650
          if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2651 2652 2653
                     NULL, NULL) == ERROR_SUCCESS) {
              if (type == REG_DWORD) {
                  size = sizeof(DWORD);
2654
                  RegQueryValueExW(key, dfltColorW, NULL, NULL,
2655 2656 2657
                                  (LPBYTE)&value, &size);
              } else if (type == REG_SZ) {
                  size = sizeof(strvalue)/sizeof(WCHAR);
2658
                  RegQueryValueExW(key, dfltColorW, NULL, NULL,
2659 2660 2661 2662 2663
                                  (LPBYTE)strvalue, &size);
                  value = strtoulW(strvalue, NULL, 10);
              }
          }
          RegCloseKey(key);
2664 2665
      }

2666 2667 2668 2669 2670
      /* If one found, set the screen to that colour */
      if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
          defaultColor = value & 0xFF;
          param1[0] = 0x00;
          WCMD_color();
2671
      }
2672

2673
  }
2674

2675 2676
  if (opt_k) {
      /* Parse the command string, without reading any more input */
2677
      WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2678
      WCMD_process_commands(toExecute, FALSE, FALSE);
2679 2680
      WCMD_free_commands(toExecute);
      toExecute = NULL;
2681
      heap_free(cmd);
2682
  }
2683

2684 2685
/*
 *	Loop forever getting commands and executing them.
2686 2687
 */

2688 2689
  interactive = TRUE;
  if (!opt_k) WCMD_version ();
2690 2691 2692 2693
  while (TRUE) {

    /* Read until EOF (which for std input is never, but if redirect
       in place, may occur                                          */
2694
    if (echo_mode) WCMD_show_prompt();
2695
    if (!WCMD_ReadAndParseLine(NULL, &toExecute, GetStdHandle(STD_INPUT_HANDLE)))
2696
      break;
2697
    WCMD_process_commands(toExecute, FALSE, FALSE);
2698 2699 2700 2701
    WCMD_free_commands(toExecute);
    toExecute = NULL;
  }
  return 0;
2702
}