wcmdmain.c 80 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 32 33
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(cmd);
34

35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
const WCHAR inbuilt[][10] = {
        {'A','T','T','R','I','B','\0'},
        {'C','A','L','L','\0'},
        {'C','D','\0'},
        {'C','H','D','I','R','\0'},
        {'C','L','S','\0'},
        {'C','O','P','Y','\0'},
        {'C','T','T','Y','\0'},
        {'D','A','T','E','\0'},
        {'D','E','L','\0'},
        {'D','I','R','\0'},
        {'E','C','H','O','\0'},
        {'E','R','A','S','E','\0'},
        {'F','O','R','\0'},
        {'G','O','T','O','\0'},
        {'H','E','L','P','\0'},
        {'I','F','\0'},
        {'L','A','B','E','L','\0'},
        {'M','D','\0'},
        {'M','K','D','I','R','\0'},
        {'M','O','V','E','\0'},
        {'P','A','T','H','\0'},
        {'P','A','U','S','E','\0'},
        {'P','R','O','M','P','T','\0'},
        {'R','E','M','\0'},
        {'R','E','N','\0'},
        {'R','E','N','A','M','E','\0'},
        {'R','D','\0'},
        {'R','M','D','I','R','\0'},
        {'S','E','T','\0'},
        {'S','H','I','F','T','\0'},
        {'T','I','M','E','\0'},
        {'T','I','T','L','E','\0'},
        {'T','Y','P','E','\0'},
        {'V','E','R','I','F','Y','\0'},
        {'V','E','R','\0'},
        {'V','O','L','\0'},
        {'E','N','D','L','O','C','A','L','\0'},
        {'S','E','T','L','O','C','A','L','\0'},
        {'P','U','S','H','D','\0'},
        {'P','O','P','D','\0'},
        {'A','S','S','O','C','\0'},
        {'C','O','L','O','R','\0'},
        {'F','T','Y','P','E','\0'},
        {'M','O','R','E','\0'},
        {'E','X','I','T','\0'}
};
82

83 84
HINSTANCE hinst;
DWORD errorlevel;
85
int echo_mode = 1, verify_mode = 0, defaultColor = 7;
86
static int opt_c, opt_k, opt_s;
87 88
const WCHAR newline[] = {'\n','\0'};
static const WCHAR equalsW[] = {'=','\0'};
89
static const WCHAR closeBW[] = {')','\0'};
90 91 92
WCHAR anykey[100];
WCHAR version_string[100];
WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
93
BATCH_CONTEXT *context = NULL;
94
extern struct env_stack *pushd_directories;
95 96 97
static const WCHAR *pagedMessage = NULL;
static char  *output_bufA = NULL;
#define MAX_WRITECONSOLE_SIZE 65535
98
BOOL unicodePipes = FALSE;
99

100
static WCHAR *WCMD_expand_envvar(WCHAR *start, WCHAR *forvar, WCHAR *forVal);
101
static void WCMD_output_asis_len(const WCHAR *message, int len, HANDLE device);
102

103 104 105 106 107
/*****************************************************************************
 * Main entry point. This is a console application so we have a main() not a
 * winmain().
 */

108
int wmain (int argc, WCHAR *argvW[])
109
{
110
  int     args;
111 112 113
  WCHAR  *cmd   = NULL;
  WCHAR string[1024];
  WCHAR envvar[4];
114
  HANDLE h;
115
  int opt_q;
116
  int opt_t = 0;
117 118 119
  static const WCHAR autoexec[] = {'\\','a','u','t','o','e','x','e','c','.',
                                   'b','a','t','\0'};
  char ansiVersion[100];
120
  CMD_LIST *toExecute = NULL;         /* Commands left to be executed */
121

122 123
  srand(time(NULL));

124 125 126 127 128
  /* Pre initialize some messages */
  strcpy(ansiVersion, PACKAGE_VERSION);
  MultiByteToWideChar(CP_ACP, 0, ansiVersion, -1, string, 1024);
  wsprintf(version_string, WCMD_LoadMessage(WCMD_VERSION), string);
  strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
129

130
  args  = argc;
131
  opt_c=opt_k=opt_q=opt_s=0;
132
  while (args > 0)
133
  {
134 135 136 137 138
      WCHAR c;
      WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(*argvW));
      if ((*argvW)[0]!='/' || (*argvW)[1]=='\0') {
          argvW++;
          args--;
139 140 141
          continue;
      }

142 143
      c=(*argvW)[1];
      if (tolowerW(c)=='c') {
144
          opt_c=1;
145
      } else if (tolowerW(c)=='q') {
146
          opt_q=1;
147
      } else if (tolowerW(c)=='k') {
148
          opt_k=1;
149
      } else if (tolowerW(c)=='s') {
150
          opt_s=1;
151 152 153 154
      } else if (tolowerW(c)=='a') {
          unicodePipes=FALSE;
      } else if (tolowerW(c)=='u') {
          unicodePipes=TRUE;
155 156 157
      } else if (tolowerW(c)=='t' && (*argvW)[2]==':') {
          opt_t=strtoulW(&(*argvW)[3], NULL, 16);
      } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
158 159
          /* Ignored for compatibility with Windows */
      }
160

161 162 163 164
      if ((*argvW)[2]==0) {
          argvW++;
          args--;
      }
165
      else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
166 167 168
      {
          *argvW+=2;
      }
169 170 171

      if (opt_c || opt_k) /* break out of parsing immediately after c or k */
          break;
172 173
  }

174
  if (opt_q) {
175 176
    const WCHAR eoff[] = {'O','F','F','\0'};
    WCMD_echo(eoff);
177 178
  }

179
  if (opt_c || opt_k) {
180 181 182 183
      int     len,qcount;
      WCHAR** arg;
      int     argsLeft;
      WCHAR*  p;
184

185 186 187 188 189
      /* 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: &<>()@^| */

190 191
      /* Build the command to execute */
      len = 0;
192
      qcount = 0;
193 194
      argsLeft = args;
      for (arg = argvW; argsLeft>0; arg++,argsLeft--)
195 196
      {
          int has_space,bcount;
197
          WCHAR* a;
198 199 200 201

          has_space=0;
          bcount=0;
          a=*arg;
202
          if( !*a ) has_space=1;
203 204 205 206 207 208 209
          while (*a!='\0') {
              if (*a=='\\') {
                  bcount++;
              } else {
                  if (*a==' ' || *a=='\t') {
                      has_space=1;
                  } else if (*a=='"') {
210
                      /* doubling of '\' preceding a '"',
211 212 213
                       * plus escaping of said '"'
                       */
                      len+=2*bcount+1;
214
                      qcount++;
215 216 217 218 219
                  }
                  bcount=0;
              }
              a++;
          }
220
          len+=(a-*arg) + 1; /* for the separating space */
221
          if (has_space)
222
          {
223
              len+=2; /* for the quotes */
224 225 226 227 228 229 230
              qcount+=2;
          }
      }

      if (qcount!=2)
          opt_s=1;

231
      /* check argvW[0] for a space and invalid characters */
232 233
      if (!opt_s) {
          opt_s=1;
234
          p=*argvW;
235 236 237 238 239 240 241 242 243 244
          while (*p!='\0') {
              if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
                  || *p=='@' || *p=='^' || *p=='|') {
                  opt_s=1;
                  break;
              }
              if (*p==' ')
                  opt_s=0;
              p++;
          }
245
      }
246

247 248
      cmd = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
      if (!cmd)
249 250
          exit(1);

251
      p = cmd;
252 253
      argsLeft = args;
      for (arg = argvW; argsLeft>0; arg++,argsLeft--)
254 255
      {
          int has_space,has_quote;
256
          WCHAR* a;
257 258 259 260

          /* Check for quotes and spaces in this argument */
          has_space=has_quote=0;
          a=*arg;
261
          if( !*a ) has_space=1;
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
          while (*a!='\0') {
              if (*a==' ' || *a=='\t') {
                  has_space=1;
                  if (has_quote)
                      break;
              } else if (*a=='"') {
                  has_quote=1;
                  if (has_space)
                      break;
              }
              a++;
          }

          /* Now transfer it to the command line */
          if (has_space)
              *p++='"';
          if (has_quote) {
              int bcount;
280
              WCHAR* a;
281 282 283 284 285 286 287 288 289 290 291

              bcount=0;
              a=*arg;
              while (*a!='\0') {
                  if (*a=='\\') {
                      *p++=*a;
                      bcount++;
                  } else {
                      if (*a=='"') {
                          int i;

292
                          /* Double all the '\\' preceding this '"', plus one */
293 294 295 296 297 298 299 300 301 302 303
                          for (i=0;i<=bcount;i++)
                              *p++='\\';
                          *p++='"';
                      } else {
                          *p++=*a;
                      }
                      bcount=0;
                  }
                  a++;
              }
          } else {
304 305
              strcpyW(p,*arg);
              p+=strlenW(*arg);
306 307 308 309 310
          }
          if (has_space)
              *p++='"';
          *p++=' ';
      }
311
      if (p > cmd)
312 313
          p--;  /* remove last space */
      *p = '\0';
314

315
      WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
316

317 318 319 320
      /* strip first and last quote characters if opt_s; check for invalid
       * executable is done later */
      if (opt_s && *cmd=='\"')
          WCMD_opt_s_strip_quotes(cmd);
321
  }
322

323 324 325 326 327 328
  if (opt_c) {
      /* If we do a "wcmd /c command", we don't want to allocate a new
       * 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.
       */
329 330 331

      /* Parse the command string, without reading any more input */
      WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
332
      WCMD_process_commands(toExecute, FALSE, NULL, NULL);
333 334 335
      WCMD_free_commands(toExecute);
      toExecute = NULL;

336
      HeapFree(GetProcessHeap(), 0, cmd);
337
      return errorlevel;
338 339
  }

340 341
  SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
                 ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
342
  SetConsoleTitle(WCMD_LoadMessage(WCMD_CONSTITLE));
343

344 345 346
  /* 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))) {
347
          defaultColor = opt_t & 0xFF;
348 349 350
          param1[0] = 0x00;
          WCMD_color();
      }
351 352 353 354 355 356 357 358 359
  } 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;
360 361 362 363
      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'};
364

365
      if (RegOpenKeyEx(HKEY_CURRENT_USER, regKeyW,
366
                       0, KEY_READ, &key) == ERROR_SUCCESS) {
367
          WCHAR  strvalue[4];
368 369

          /* See if DWORD or REG_SZ */
370
          if (RegQueryValueEx(key, dfltColorW, NULL, &type,
371 372 373
                     NULL, NULL) == ERROR_SUCCESS) {
              if (type == REG_DWORD) {
                  size = sizeof(DWORD);
374
                  RegQueryValueEx(key, dfltColorW, NULL, NULL,
375 376
                                  (LPBYTE)&value, &size);
              } else if (type == REG_SZ) {
377 378
                  size = sizeof(strvalue)/sizeof(WCHAR);
                  RegQueryValueEx(key, dfltColorW, NULL, NULL,
379
                                  (LPBYTE)strvalue, &size);
380
                  value = strtoulW(strvalue, NULL, 10);
381 382
              }
          }
383
          RegCloseKey(key);
384 385
      }

386
      if (value == 0 && RegOpenKeyEx(HKEY_LOCAL_MACHINE, regKeyW,
387
                       0, KEY_READ, &key) == ERROR_SUCCESS) {
388
          WCHAR  strvalue[4];
389 390

          /* See if DWORD or REG_SZ */
391
          if (RegQueryValueEx(key, dfltColorW, NULL, &type,
392 393 394
                     NULL, NULL) == ERROR_SUCCESS) {
              if (type == REG_DWORD) {
                  size = sizeof(DWORD);
395
                  RegQueryValueEx(key, dfltColorW, NULL, NULL,
396 397
                                  (LPBYTE)&value, &size);
              } else if (type == REG_SZ) {
398 399
                  size = sizeof(strvalue)/sizeof(WCHAR);
                  RegQueryValueEx(key, dfltColorW, NULL, NULL,
400
                                  (LPBYTE)strvalue, &size);
401
                  value = strtoulW(strvalue, NULL, 10);
402 403
              }
          }
404
          RegCloseKey(key);
405 406 407 408 409 410 411 412 413
      }

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

414 415
  }

416 417 418
  /* Save cwd into appropriate env var */
  GetCurrentDirectory(1024, string);
  if (IsCharAlpha(string[0]) && string[1] == ':') {
419 420
    static const WCHAR fmt[] = {'=','%','c',':','\0'};
    wsprintf(envvar, fmt, string[0]);
421
    SetEnvironmentVariable(envvar, string);
422
    WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
423 424
  }

425
  if (opt_k) {
426 427
      /* Parse the command string, without reading any more input */
      WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
428
      WCMD_process_commands(toExecute, FALSE, NULL, NULL);
429 430
      WCMD_free_commands(toExecute);
      toExecute = NULL;
431
      HeapFree(GetProcessHeap(), 0, cmd);
432 433 434 435 436 437
  }

/*
 *	If there is an AUTOEXEC.BAT file, try to execute it.
 */

438
  GetFullPathName (autoexec, sizeof(string)/sizeof(WCHAR), string, NULL);
439
  h = CreateFile (string, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
440 441
  if (h != INVALID_HANDLE_VALUE) {
    CloseHandle (h);
442
#if 0
443
    WCMD_batch (autoexec, autoexec, 0, NULL, INVALID_HANDLE_VALUE);
444
#endif
445 446 447 448 449 450 451 452
  }

/*
 *	Loop forever getting commands and executing them.
 */

  WCMD_version ();
  while (TRUE) {
453 454 455

    /* Read until EOF (which for std input is never, but if redirect
       in place, may occur                                          */
456
    WCMD_show_prompt ();
457 458 459
    if (WCMD_ReadAndParseLine(NULL, &toExecute,
                              GetStdHandle(STD_INPUT_HANDLE)) == NULL)
      break;
460
    WCMD_process_commands(toExecute, FALSE, NULL, NULL);
461 462
    WCMD_free_commands(toExecute);
    toExecute = NULL;
463
  }
464
  return 0;
465 466
}

467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 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
/*****************************************************************************
 * 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%%"
 */
void handleExpansion(WCHAR *cmd, BOOL justFors, WCHAR *forVariable, WCHAR *forValue) {

  /* 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;
  WCHAR *s, *t;
  int   i;

  while ((p = strchrW(p, '%'))) {

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

    /* Don't touch %% unless its in Batch */
    if (!justFors && *(p+1) == '%') {
      if (context) {
        s = WCMD_strdupW(p+1);
        strcpyW (p, s);
        free (s);
      }
      p+=1;

    /* Replace %~ modifications if in batch program */
    } else if (*(p+1) == '~') {
      WCMD_HandleTildaModifiers(&p, forVariable, forValue, justFors);
      p++;

    /* Replace use of %0...%9 if in batch program*/
    } else if (!justFors && context && (i >= 0) && (i <= 9)) {
      s = WCMD_strdupW(p+2);
      t = WCMD_parameter (context -> command, i + context -> shift_count[i], NULL);
      strcpyW (p, t);
      strcatW (p, s);
      free (s);

    /* Replace use of %* if in batch program*/
    } else if (!justFors && context && *(p+1)=='*') {
      WCHAR *startOfParms = NULL;
      s = WCMD_strdupW(p+2);
      t = WCMD_parameter (context -> command, 1, &startOfParms);
      if (startOfParms != NULL) strcpyW (p, startOfParms);
      else *p = 0x00;
      strcatW (p, s);
      free (s);

    } else if (forVariable &&
               (CompareString (LOCALE_USER_DEFAULT,
                               SORT_STRINGSORT,
                               p,
                               strlenW(forVariable),
                               forVariable, -1) == 2)) {
      s = WCMD_strdupW(p + strlenW(forVariable));
      strcpyW(p, forValue);
      strcatW(p, s);
      free(s);

    } else if (!justFors) {
      p = WCMD_expand_envvar(p, forVariable, forValue);

    /* In a FOR loop, see if this is the variable to replace */
    } else { /* Ignore %'s on second pass of batch program */
      p++;
    }
  }

  return;
}

548 549 550 551 552 553 554

/*****************************************************************************
 * Process one command. If the command is EXIT this routine does not return.
 * We will recurse through here executing batch files.
 */


555
void WCMD_execute (WCHAR *command, WCHAR *redirects,
556 557
                   WCHAR *forVariable, WCHAR *forValue,
                   CMD_LIST **cmdList)
558
{
559
    WCHAR *cmd, *p, *redir;
560
    int status, i;
561
    DWORD count, creationDisposition;
562
    HANDLE h;
563
    WCHAR *whichcmd;
564
    SECURITY_ATTRIBUTES sa;
Jason Edmeades's avatar
Jason Edmeades committed
565 566
    WCHAR *new_cmd = NULL;
    WCHAR *new_redir = NULL;
567 568 569 570 571 572
    HANDLE old_stdhandles[3] = {INVALID_HANDLE_VALUE,
                                INVALID_HANDLE_VALUE,
                                INVALID_HANDLE_VALUE};
    DWORD  idx_stdhandles[3] = {STD_INPUT_HANDLE,
                                STD_OUTPUT_HANDLE,
                                STD_ERROR_HANDLE};
Jason Edmeades's avatar
Jason Edmeades committed
573
    BOOL piped = FALSE;
574

575 576 577 578
    WINE_TRACE("command on entry:%s (%p), with '%s'='%s'\n",
               wine_dbgstr_w(command), cmdList,
               wine_dbgstr_w(forVariable), wine_dbgstr_w(forValue));

Jason Edmeades's avatar
Jason Edmeades committed
579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601
    /* 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) {

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

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

        /* Generate a unique temporary filename */
        GetTempPath (sizeof(temp_path)/sizeof(WCHAR), temp_path);
        GetTempFileName (temp_path, cmdW, 0, (*cmdList)->nextcommand->pipeFile);
        WINE_TRACE("Using temporary file of %s\n",
                   wine_dbgstr_w((*cmdList)->nextcommand->pipeFile));
    }

602
    /* Move copy of the command onto the heap so it can be expanded */
603 604
    new_cmd = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
    strcpyW(new_cmd, command);
605

Jason Edmeades's avatar
Jason Edmeades committed
606 607 608 609 610 611 612 613 614 615 616 617
    /* Move copy of the redirects onto the heap so it can be expanded */
    new_redir = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));

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

618
    /* Expand variables in command line mode only (batch mode will
Jason Edmeades's avatar
Jason Edmeades committed
619
       be expanded as the line is read in, except for 'for' loops) */
620
    handleExpansion(new_cmd, (context != NULL), forVariable, forValue);
Jason Edmeades's avatar
Jason Edmeades committed
621
    handleExpansion(new_redir, (context != NULL), forVariable, forValue);
622
    cmd = new_cmd;
623

624 625 626 627 628
    /* Show prompt before batch line IF echo is on and in batch program */
    if (context && echo_mode && (cmd[0] != '@')) {
      WCMD_show_prompt();
      WCMD_output_asis ( cmd);
      WCMD_output_asis ( newline);
629 630 631 632 633 634
    }

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

635 636 637
    if ((cmd[1] == ':') && IsCharAlpha (cmd[0]) && (strlenW(cmd) == 2)) {
      WCHAR envvar[5];
      WCHAR dir[MAX_PATH];
638 639 640 641

      /* 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             */
642 643
      strcpyW(envvar, equalsW);
      strcatW(envvar, cmd);
644
      if (GetEnvironmentVariable(envvar, dir, MAX_PATH) == 0) {
645 646
        static const WCHAR fmt[] = {'%','s','\\','\0'};
        wsprintf(cmd, fmt, cmd);
647
        WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd));
648
      }
649
      WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd));
650 651
      status = SetCurrentDirectory (cmd);
      if (!status) WCMD_print_error ();
652
      HeapFree( GetProcessHeap(), 0, cmd );
Jason Edmeades's avatar
Jason Edmeades committed
653
      HeapFree( GetProcessHeap(), 0, new_redir );
654 655
      return;
    }
656

657 658 659
    sa.nLength = sizeof(sa);
    sa.lpSecurityDescriptor = NULL;
    sa.bInheritHandle = TRUE;
660

661
/*
662
 *	Redirect stdin, stdout and/or stderr if required.
663 664
 */

665
    /* STDIN could come from a preceding pipe, so delete on close if it does */
Jason Edmeades's avatar
Jason Edmeades committed
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684
    if (cmdList && (*cmdList)->pipeFile[0] != 0x00) {
        WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile));
        h = CreateFile ((*cmdList)->pipeFile, GENERIC_READ,
                  FILE_SHARE_READ, &sa, OPEN_EXISTING,
                  FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
        if (h == INVALID_HANDLE_VALUE) {
          WCMD_print_error ();
          HeapFree( GetProcessHeap(), 0, cmd );
          HeapFree( GetProcessHeap(), 0, new_redir );
          return;
        }
        old_stdhandles[0] = GetStdHandle (STD_INPUT_HANDLE);
        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) {
685
      h = CreateFile (WCMD_parameter (++p, 0, NULL), GENERIC_READ, FILE_SHARE_READ, &sa, OPEN_EXISTING,
686
		FILE_ATTRIBUTE_NORMAL, NULL);
687 688
      if (h == INVALID_HANDLE_VALUE) {
	WCMD_print_error ();
689
        HeapFree( GetProcessHeap(), 0, cmd );
Jason Edmeades's avatar
Jason Edmeades committed
690
        HeapFree( GetProcessHeap(), 0, new_redir );
691 692
	return;
      }
693
      old_stdhandles[0] = GetStdHandle (STD_INPUT_HANDLE);
694 695
      SetStdHandle (STD_INPUT_HANDLE, h);
    }
696 697

    /* Scan the whole command looking for > and 2> */
Jason Edmeades's avatar
Jason Edmeades committed
698
    redir = new_redir;
699
    while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
700 701 702 703 704 705 706 707 708
      int handle = 0;

      if (*(p-1)!='2') {
        handle = 1;
      } else {
        handle = 2;
      }

      p++;
709 710 711 712 713 714 715
      if ('>' == *p) {
        creationDisposition = OPEN_ALWAYS;
        p++;
      }
      else {
        creationDisposition = CREATE_ALWAYS;
      }
716 717

      /* Add support for 2>&1 */
718
      redir = p;
719 720 721 722 723 724 725 726 727 728 729 730 731
      if (*p == '&') {
        int idx = *(p+1) - '0';

        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);

      } else {
732
        WCHAR *param = WCMD_parameter (p, 0, NULL);
733 734 735 736 737
        h = CreateFile (param, GENERIC_WRITE, 0, &sa, creationDisposition,
                        FILE_ATTRIBUTE_NORMAL, NULL);
        if (h == INVALID_HANDLE_VALUE) {
          WCMD_print_error ();
          HeapFree( GetProcessHeap(), 0, cmd );
Jason Edmeades's avatar
Jason Edmeades committed
738
          HeapFree( GetProcessHeap(), 0, new_redir );
739 740 741 742 743 744
          return;
        }
        if (SetFilePointer (h, 0, NULL, FILE_END) ==
              INVALID_SET_FILE_POINTER) {
          WCMD_print_error ();
        }
745
        WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
746
      }
747

748 749
      old_stdhandles[handle] = GetStdHandle (idx_stdhandles[handle]);
      SetStdHandle (idx_stdhandles[handle], h);
750
    }
751

752 753 754 755
/*
 * Strip leading whitespaces, and a '@' if supplied
 */
    whichcmd = WCMD_strtrim_leading_spaces(cmd);
756
    WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
757
    if (whichcmd[0] == '@') whichcmd++;
758

759 760 761 762 763 764
/*
 *	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.
 */

    count = 0;
765
    while (IsCharAlphaNumeric(whichcmd[count])) {
766 767 768 769
      count++;
    }
    for (i=0; i<=WCMD_EXIT; i++) {
      if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
770
      	  whichcmd, count, inbuilt[i], -1) == 2) break;
771
    }
772
    p = WCMD_strtrim_leading_spaces (&whichcmd[count]);
773
    WCMD_parse (p, quals, param1, param2);
774 775
    WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));

776 777 778 779 780 781
    switch (i) {

      case WCMD_ATTRIB:
        WCMD_setshow_attrib ();
        break;
      case WCMD_CALL:
782
        WCMD_call (p);
783 784 785
        break;
      case WCMD_CD:
      case WCMD_CHDIR:
786
        WCMD_setshow_default (p);
787 788 789 790 791 792 793 794 795 796 797 798 799 800 801
        break;
      case WCMD_CLS:
        WCMD_clear_screen ();
        break;
      case WCMD_COPY:
        WCMD_copy ();
        break;
      case WCMD_CTTY:
        WCMD_change_tty ();
        break;
      case WCMD_DATE:
        WCMD_setshow_date ();
	break;
      case WCMD_DEL:
      case WCMD_ERASE:
802
        WCMD_delete (p, TRUE);
803 804
        break;
      case WCMD_DIR:
805
        WCMD_directory (p);
806 807
        break;
      case WCMD_ECHO:
808
        WCMD_echo(&whichcmd[count]);
809
        break;
810
      case WCMD_FOR:
811
        WCMD_for (p, cmdList);
812 813
        break;
      case WCMD_GOTO:
814
        WCMD_goto (cmdList);
815 816 817 818 819
        break;
      case WCMD_HELP:
        WCMD_give_help (p);
	break;
      case WCMD_IF:
820
	WCMD_if (p, cmdList);
821 822 823 824 825 826 827 828 829 830 831 832
        break;
      case WCMD_LABEL:
        WCMD_volume (1, p);
        break;
      case WCMD_MD:
      case WCMD_MKDIR:
        WCMD_create_dir ();
	break;
      case WCMD_MOVE:
        WCMD_move ();
        break;
      case WCMD_PATH:
833
        WCMD_setshow_path (p);
834 835 836 837 838 839 840 841 842 843 844 845 846 847 848
        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:
849
        WCMD_remove_dir (p);
850
        break;
851 852 853 854 855 856
      case WCMD_SETLOCAL:
        WCMD_setlocal(p);
        break;
      case WCMD_ENDLOCAL:
        WCMD_endlocal();
        break;
857 858 859 860
      case WCMD_SET:
        WCMD_setshow_env (p);
	break;
      case WCMD_SHIFT:
861
        WCMD_shift (p);
862 863 864
        break;
      case WCMD_TIME:
        WCMD_setshow_time ();
865 866
        break;
      case WCMD_TITLE:
867
        if (strlenW(&whichcmd[count]) > 0)
868
          WCMD_title(&whichcmd[count+1]);
869
        break;
870
      case WCMD_TYPE:
871
        WCMD_type (p);
872 873 874 875 876
	break;
      case WCMD_VER:
        WCMD_version ();
        break;
      case WCMD_VERIFY:
877
        WCMD_verify (p);
878 879 880 881
        break;
      case WCMD_VOL:
        WCMD_volume (0, p);
        break;
882
      case WCMD_PUSHD:
883
        WCMD_pushd(p);
884 885 886 887
        break;
      case WCMD_POPD:
        WCMD_popd();
        break;
888
      case WCMD_ASSOC:
889
        WCMD_assoc(p, TRUE);
890
        break;
891 892 893
      case WCMD_COLOR:
        WCMD_color();
        break;
894 895 896
      case WCMD_FTYPE:
        WCMD_assoc(p, FALSE);
        break;
897 898 899
      case WCMD_MORE:
        WCMD_more(p);
        break;
900
      case WCMD_EXIT:
901
        WCMD_exit (cmdList);
902
        break;
903
      default:
904
        WCMD_run_program (whichcmd, 0);
905 906
    }
    HeapFree( GetProcessHeap(), 0, cmd );
Jason Edmeades's avatar
Jason Edmeades committed
907
    HeapFree( GetProcessHeap(), 0, new_redir );
908 909 910 911 912 913 914

    /* Restore old handles */
    for (i=0; i<3; i++) {
      if (old_stdhandles[i] != INVALID_HANDLE_VALUE) {
        CloseHandle (GetStdHandle (idx_stdhandles[i]));
        SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
      }
915 916 917 918 919 920 921 922 923 924 925 926 927
    }
}

static void init_msvcrt_io_block(STARTUPINFO* st)
{
    STARTUPINFO st_p;
    /* fetch the parent MSVCRT info block if any, so that the child can use the
     * same handles as its grand-father
     */
    st_p.cb = sizeof(STARTUPINFO);
    GetStartupInfo(&st_p);
    st->cbReserved2 = st_p.cbReserved2;
    st->lpReserved2 = st_p.lpReserved2;
928
    if (st_p.cbReserved2 && st_p.lpReserved2)
929 930 931 932 933
    {
        /* Override the entries for fd 0,1,2 if we happened
         * to change those std handles (this depends on the way wcmd sets
         * it's new input & output handles)
         */
934
        size_t sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
Mike McCormack's avatar
Mike McCormack committed
935
        BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
936 937 938
        if (ptr)
        {
            unsigned num = *(unsigned*)st_p.lpReserved2;
939 940
            char* flags = (char*)(ptr + sizeof(unsigned));
            HANDLE* handles = (HANDLE*)(flags + num * sizeof(char));
941 942 943 944

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

946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963
#define WX_OPEN 0x01    /* see dlls/msvcrt/file.c */
            if (num <= 0 || (flags[0] & WX_OPEN))
            {
                handles[0] = GetStdHandle(STD_INPUT_HANDLE);
                flags[0] |= WX_OPEN;
            }
            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
        }
964
    }
965
}
966 967 968 969

/******************************************************************************
 * WCMD_run_program
 *
970
 *	Execute a command line as an external program. Must allow recursion.
971
 *
972 973 974 975 976 977 978 979 980 981 982 983 984 985 986
 *      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
987
 *          findexecutable to achieve this which is left untouched.
988 989
 */

990
void WCMD_run_program (WCHAR *command, int called) {
991

992 993 994 995 996 997
  WCHAR  temp[MAX_PATH];
  WCHAR  pathtosearch[MAXSTRING];
  WCHAR *pathposn;
  WCHAR  stemofsearch[MAX_PATH];
  WCHAR *lastSlash;
  WCHAR  pathext[MAXSTRING];
998 999 1000
  BOOL  extensionsupplied = FALSE;
  BOOL  launched = FALSE;
  BOOL  status;
1001
  BOOL  assumeInternal = FALSE;
1002
  DWORD len;
1003 1004 1005
  static const WCHAR envPath[] = {'P','A','T','H','\0'};
  static const WCHAR envPathExt[] = {'P','A','T','H','E','X','T','\0'};
  static const WCHAR delims[] = {'/','\\',':','\0'};
1006 1007

  WCMD_parse (command, quals, param1, param2);	/* Quick way to get the filename */
1008 1009
  if (!(*param1) && !(*param2))
    return;
1010 1011

  /* Calculate the search path and stem to search for */
1012 1013 1014 1015 1016 1017 1018
  if (strpbrkW (param1, delims) == NULL) {  /* No explicit path given, search path */
    static const WCHAR curDir[] = {'.',';','\0'};
    strcpyW(pathtosearch, curDir);
    len = GetEnvironmentVariable (envPath, &pathtosearch[2], (sizeof(pathtosearch)/sizeof(WCHAR))-2);
    if ((len == 0) || (len >= (sizeof(pathtosearch)/sizeof(WCHAR)) - 2)) {
      static const WCHAR curDir[] = {'.','\0'};
      strcpyW (pathtosearch, curDir);
1019
    }
1020 1021
    if (strchrW(param1, '.') != NULL) extensionsupplied = TRUE;
    strcpyW(stemofsearch, param1);
1022 1023 1024 1025

  } else {

    /* Convert eg. ..\fred to include a directory by removing file part */
1026 1027 1028 1029
    GetFullPathName(param1, sizeof(pathtosearch)/sizeof(WCHAR), pathtosearch, NULL);
    lastSlash = strrchrW(pathtosearch, '\\');
    if (lastSlash && strchrW(lastSlash, '.') != NULL) extensionsupplied = TRUE;
    strcpyW(stemofsearch, lastSlash+1);
1030 1031 1032 1033

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

  /* Now extract PATHEXT */
1037 1038 1039 1040 1041 1042 1043
  len = GetEnvironmentVariable (envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
  if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
    static const WCHAR dfltPathExt[] = {'.','b','a','t',';',
                                        '.','c','o','m',';',
                                        '.','c','m','d',';',
                                        '.','e','x','e','\0'};
    strcpyW (pathext, dfltPathExt);
1044 1045 1046 1047
  }

  /* Loop through the search path, dir by dir */
  pathposn = pathtosearch;
1048 1049
  WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
             wine_dbgstr_w(stemofsearch));
1050 1051
  while (!launched && pathposn) {

1052 1053
    WCHAR  thisDir[MAX_PATH] = {'\0'};
    WCHAR *pos               = NULL;
1054
    BOOL  found             = FALSE;
1055
    const WCHAR slashW[] = {'\\','\0'};
1056 1057

    /* Work on the first directory on the search path */
1058
    pos = strchrW(pathposn, ';');
1059
    if (pos) {
1060
      memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
1061 1062 1063 1064
      thisDir[(pos-pathposn)] = 0x00;
      pathposn = pos+1;

    } else {
1065
      strcpyW(thisDir, pathposn);
1066
      pathposn = NULL;
1067
    }
1068

1069 1070
    /* Since you can have eg. ..\.. on the path, need to expand
       to full information                                      */
1071
    strcpyW(temp, thisDir);
1072 1073 1074
    GetFullPathName(temp, MAX_PATH, thisDir, NULL);

    /* 1. If extension supplied, see if that file exists */
1075 1076 1077
    strcatW(thisDir, slashW);
    strcatW(thisDir, stemofsearch);
    pos = &thisDir[strlenW(thisDir)]; /* Pos = end of name */
1078

1079 1080 1081 1082 1083
    /* 1. If extension supplied, see if that file exists */
    if (extensionsupplied) {
      if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
        found = TRUE;
      }
1084 1085 1086 1087 1088 1089
    }

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

1092
      strcatW(thisDir,allFiles);
1093 1094
      h = FindFirstFile(thisDir, &finddata);
      FindClose(h);
1095
      if (h != INVALID_HANDLE_VALUE) {
1096

1097
        WCHAR *thisExt = pathext;
1098 1099 1100

        /* 3. Yes - Try each path ext */
        while (thisExt) {
1101
          WCHAR *nextExt = strchrW(thisExt, ';');
1102 1103

          if (nextExt) {
1104
            memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
1105 1106 1107
            pos[(nextExt-thisExt)] = 0x00;
            thisExt = nextExt+1;
          } else {
1108
            strcpyW(pos, thisExt);
1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119
            thisExt = NULL;
          }

          if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
            found = TRUE;
            thisExt = NULL;
          }
        }
      }
    }

1120 1121 1122 1123 1124 1125 1126 1127 1128
   /* Internal programs won't be picked up by this search, so even
      though not found, try one last createprocess and wait for it
      to complete.
      Note: Ideally we could tell between a console app (wait) and a
      windows app, but the API's for it fail in this case           */
    if (!found && pathposn == NULL) {
        WINE_TRACE("ASSUMING INTERNAL\n");
        assumeInternal = TRUE;
    } else {
1129
        WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
1130 1131
    }

1132
    /* Once found, launch it */
1133
    if (found || assumeInternal) {
1134 1135 1136 1137 1138
      STARTUPINFO st;
      PROCESS_INFORMATION pe;
      SHFILEINFO psfi;
      DWORD console;
      HINSTANCE hinst;
1139 1140 1141 1142
      WCHAR *ext = strrchrW( thisDir, '.' );
      static const WCHAR batExt[] = {'.','b','a','t','\0'};
      static const WCHAR cmdExt[] = {'.','c','m','d','\0'};

1143 1144 1145
      launched = TRUE;

      /* Special case BAT and CMD */
1146
      if (ext && !strcmpiW(ext, batExt)) {
1147 1148
        WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
        return;
1149
      } else if (ext && !strcmpiW(ext, cmdExt)) {
1150 1151 1152 1153 1154 1155
        WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
        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 */
1156
        hinst = FindExecutable (thisDir, NULL, temp);
1157 1158 1159 1160 1161 1162 1163 1164 1165
        if ((INT_PTR)hinst < 32)
          console = 0;
        else
          console = SHGetFileInfo (temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);

        ZeroMemory (&st, sizeof(STARTUPINFO));
        st.cb = sizeof(STARTUPINFO);
        init_msvcrt_io_block(&st);

1166 1167 1168 1169
        /* Launch the process and if a CUI wait on it to complete
           Note: Launching internal wine processes cannot specify a full path to exe */
        status = CreateProcess (assumeInternal?NULL : thisDir,
                                command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1170 1171
        if ((opt_c || opt_k) && !opt_s && !status
            && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1172
          /* strip first and last quote WCHARacters and try again */
1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184
          WCMD_opt_s_strip_quotes(command);
          opt_s=1;
          WCMD_run_program(command, called);
          return;
        }
        if (!status) {
          WCMD_print_error ();
          /* If a command fails to launch, it sets errorlevel 9009 - which
             does not seem to have any associated constant definition     */
          errorlevel = 9009;
          return;
        }
1185
        if (!assumeInternal && !console) errorlevel = 0;
1186 1187
        else
        {
1188 1189
            /* Always wait when called in a batch program context */
            if (assumeInternal || context || !HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
1190 1191 1192 1193 1194
            GetExitCodeProcess (pe.hProcess, &errorlevel);
            if (errorlevel == STILL_ACTIVE) errorlevel = 0;
        }
        CloseHandle(pe.hProcess);
        CloseHandle(pe.hThread);
1195 1196 1197 1198 1199
        return;
      }
    }
  }

1200 1201 1202 1203 1204 1205 1206 1207 1208
  /* Not found anywhere - give up */
  SetLastError(ERROR_FILE_NOT_FOUND);
  WCMD_print_error ();

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

1209 1210 1211 1212 1213 1214 1215 1216 1217
}

/******************************************************************************
 * WCMD_show_prompt
 *
 *	Display the prompt on STDout
 *
 */

1218
void WCMD_show_prompt (void) {
1219

1220
  int status;
1221 1222
  WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
  WCHAR *p, *q;
1223
  DWORD len;
1224
  static const WCHAR envPrompt[] = {'P','R','O','M','P','T','\0'};
1225

1226 1227 1228 1229 1230
  len = GetEnvironmentVariable (envPrompt, prompt_string,
                                sizeof(prompt_string)/sizeof(WCHAR));
  if ((len == 0) || (len >= (sizeof(prompt_string)/sizeof(WCHAR)))) {
    const WCHAR dfltPrompt[] = {'$','P','$','G','\0'};
    strcpyW (prompt_string, dfltPrompt);
1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245
  }
  p = prompt_string;
  q = out_string;
  *q = '\0';
  while (*p != '\0') {
    if (*p != '$') {
      *q++ = *p++;
      *q = '\0';
    }
    else {
      p++;
      switch (toupper(*p)) {
        case '$':
	  *q++ = '$';
	  break;
1246 1247 1248
	case 'A':
	  *q++ = '&';
	  break;
1249 1250 1251
	case 'B':
	  *q++ = '|';
	  break;
1252 1253 1254
	case 'C':
	  *q++ = '(';
	  break;
1255 1256 1257 1258 1259 1260 1261
	case 'D':
	  GetDateFormat (LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
	  while (*q) q++;
	  break;
	case 'E':
	  *q++ = '\E';
	  break;
1262 1263 1264
	case 'F':
	  *q++ = ')';
	  break;
1265 1266 1267
	case 'G':
	  *q++ = '>';
	  break;
1268 1269 1270
	case 'H':
	  *q++ = '\b';
	  break;
1271 1272 1273 1274
	case 'L':
	  *q++ = '<';
	  break;
	case 'N':
1275
          status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
1276 1277 1278 1279 1280
	  if (status) {
	    *q++ = curdir[0];
	  }
	  break;
	case 'P':
1281
          status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
1282
	  if (status) {
1283
	    strcatW (q, curdir);
1284 1285 1286 1287 1288 1289
	    while (*q) q++;
	  }
	  break;
	case 'Q':
	  *q++ = '=';
	  break;
1290 1291 1292
	case 'S':
	  *q++ = ' ';
	  break;
1293 1294 1295 1296
	case 'T':
	  GetTimeFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
	  while (*q) q++;
	  break;
1297 1298
        case 'V':
	  strcatW (q, version_string);
1299
	  while (*q) q++;
1300
          break;
1301 1302 1303
	case '_':
	  *q++ = '\n';
	  break;
1304 1305
	case '+':
	  if (pushd_directories) {
1306 1307
	    memset(q, '+', pushd_directories->u.stackdepth);
	    q = q + pushd_directories->u.stackdepth;
1308 1309
	  }
	  break;
1310 1311 1312 1313 1314
      }
      p++;
      *q = '\0';
    }
  }
1315
  WCMD_output_asis (out_string);
1316 1317 1318 1319 1320
}

/****************************************************************************
 * WCMD_print_error
 *
1321
 * Print the message for GetLastError
1322 1323
 */

1324
void WCMD_print_error (void) {
1325 1326 1327
  LPVOID lpMsgBuf;
  DWORD error_code;
  int status;
1328 1329

  error_code = GetLastError ();
1330 1331 1332
  status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
    			NULL, error_code, 0, (LPTSTR) &lpMsgBuf, 0, NULL);
  if (!status) {
1333
    WINE_FIXME ("Cannot display message for error %d, status %d\n",
1334 1335
			error_code, GetLastError());
    return;
1336
  }
1337 1338 1339

  WCMD_output_asis_len(lpMsgBuf, lstrlen(lpMsgBuf),
                       GetStdHandle(STD_ERROR_HANDLE));
1340
  LocalFree ((HLOCAL)lpMsgBuf);
1341 1342
  WCMD_output_asis_len (newline, lstrlen(newline),
                        GetStdHandle(STD_ERROR_HANDLE));
1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354
  return;
}

/*******************************************************************
 * 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.
 */

1355
void WCMD_parse (WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2) {
1356 1357 1358 1359 1360 1361 1362 1363 1364

int p = 0;

  *q = *p1 = *p2 = '\0';
  while (TRUE) {
    switch (*s) {
      case '/':
        *q++ = *s++;
	while ((*s != '\0') && (*s != ' ') && *s != '/') {
1365
	  *q++ = toupperW (*s++);
1366 1367 1368 1369
	}
        *q = '\0';
	break;
      case ' ':
Brian Grayson's avatar
Brian Grayson committed
1370
      case '\t':
1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387
	s++;
	break;
      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++;
	break;
      case '\0':
        return;
      default:
1388 1389
	while ((*s != '\0') && (*s != ' ') && (*s != '\t')
               && (*s != '=')  && (*s != ',') ) {
1390 1391 1392 1393
	  if (p == 0) *p1++ = *s++;
	  else if (p == 1) *p2++ = *s++;
	  else s++;
	}
1394 1395 1396
        /* Skip concurrent parms */
	while ((*s == ' ') || (*s == '\t') || (*s == '=')  || (*s == ',') ) s++;

1397 1398 1399 1400 1401 1402 1403
        if (p == 0) *p1 = '\0';
        if (p == 1) *p2 = '\0';
	p++;
    }
  }
}

1404 1405
/*******************************************************************
 * WCMD_output_asis_len - send output to current standard output
1406 1407 1408 1409
 *
 * 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
1410
 */
1411
static void WCMD_output_asis_len(const WCHAR *message, int len, HANDLE device) {
1412 1413 1414 1415 1416 1417 1418 1419

    DWORD   nOut= 0;
    DWORD   res = 0;

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

    /* Try to write as unicode assuming it is to a console */
1420
    res = WriteConsoleW(device, message, len, &nOut, NULL);
1421 1422 1423 1424 1425 1426 1427

    /* If writing to console fails, assume its file
       i/o so convert to OEM codepage and output                  */
    if (!res) {
      BOOL usedDefaultChar = FALSE;
      DWORD convertedChars;

1428 1429 1430 1431 1432 1433 1434 1435 1436 1437
      if (!unicodePipes) {
        /*
         * Allocate buffer to use when writing to file. (Not freed, as one off)
         */
        if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
                                                  MAX_WRITECONSOLE_SIZE);
        if (!output_bufA) {
          WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
          return;
        }
1438

1439 1440 1441 1442
        /* Convert to OEM, then output */
        convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
                            len, output_bufA, MAX_WRITECONSOLE_SIZE,
                            "?", &usedDefaultChar);
1443
        WriteFile(device, output_bufA, convertedChars,
1444 1445
                  &nOut, FALSE);
      } else {
1446
        WriteFile(device, message, len*sizeof(WCHAR),
1447 1448
                  &nOut, FALSE);
      }
1449 1450
    }
    return;
1451 1452
}

1453 1454 1455 1456 1457
/*******************************************************************
 * WCMD_output - send output to current standard output device.
 *
 */

1458
void WCMD_output (const WCHAR *format, ...) {
1459

1460
  va_list ap;
1461
  WCHAR string[1024];
1462
  int ret;
1463 1464

  va_start(ap,format);
1465 1466
  ret = wvsprintf (string, format, ap);
  if( ret >= (sizeof(string)/sizeof(WCHAR))) {
1467
       WINE_ERR("Output truncated in WCMD_output\n" );
1468
       ret = (sizeof(string)/sizeof(WCHAR)) - 1;
1469
       string[ret] = '\0';
1470
  }
1471
  va_end(ap);
1472
  WCMD_output_asis_len(string, ret, GetStdHandle(STD_OUTPUT_HANDLE));
1473 1474 1475 1476 1477
}


static int line_count;
static int max_height;
1478
static int max_width;
1479
static BOOL paged_mode;
1480
static int numChars;
1481

1482
void WCMD_enter_paged_mode(const WCHAR *msg)
1483
{
1484
  CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
1485

1486
  if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
1487
    max_height = consoleInfo.dwSize.Y;
1488 1489
    max_width  = consoleInfo.dwSize.X;
  } else {
1490
    max_height = 25;
1491 1492
    max_width  = 80;
  }
1493
  paged_mode = TRUE;
1494
  line_count = 0;
1495
  numChars   = 0;
1496
  pagedMessage = (msg==NULL)? anykey : msg;
1497 1498 1499 1500 1501
}

void WCMD_leave_paged_mode(void)
{
  paged_mode = FALSE;
1502
  pagedMessage = NULL;
1503 1504
}

1505
/*******************************************************************
1506 1507 1508 1509
 * WCMD_output_asis - send output to current standard output device.
 *        without formatting eg. when message contains '%'
 */

1510
void WCMD_output_asis (const WCHAR *message) {
1511
  DWORD count;
1512 1513
  const WCHAR* ptr;
  WCHAR string[1024];
1514 1515 1516

  if (paged_mode) {
    do {
1517 1518 1519 1520 1521 1522
      ptr = message;
      while (*ptr && *ptr!='\n' && (numChars < max_width)) {
        numChars++;
        ptr++;
      };
      if (*ptr == '\n') ptr++;
1523 1524
      WCMD_output_asis_len(message, (ptr) ? ptr - message : strlenW(message),
                           GetStdHandle(STD_OUTPUT_HANDLE));
1525
      if (ptr) {
1526
        numChars = 0;
1527
        if (++line_count >= max_height - 1) {
1528
          line_count = 0;
1529 1530
          WCMD_output_asis_len(pagedMessage, strlenW(pagedMessage),
                               GetStdHandle(STD_OUTPUT_HANDLE));
1531 1532
          WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
                         sizeof(string)/sizeof(WCHAR), &count, NULL);
1533 1534
        }
      }
1535
    } while (((message = ptr) != NULL) && (*ptr));
1536
  } else {
1537 1538
    WCMD_output_asis_len(message, lstrlen(message),
                         GetStdHandle(STD_OUTPUT_HANDLE));
1539
  }
1540 1541
}

1542

1543 1544 1545 1546
/***************************************************************************
 * WCMD_strtrim_leading_spaces
 *
 *	Remove leading spaces from a string. Return a pointer to the first
1547 1548 1549
 *	non-space character. Does not modify the input string
 */

1550
WCHAR *WCMD_strtrim_leading_spaces (WCHAR *string) {
1551

1552
  WCHAR *ptr;
1553 1554 1555 1556 1557 1558

  ptr = string;
  while (*ptr == ' ') ptr++;
  return ptr;
}

1559 1560 1561 1562
/*************************************************************************
 * WCMD_strtrim_trailing_spaces
 *
 *	Remove trailing spaces from a string. This routine modifies the input
1563
 *	string by placing a null after the last non-space WCHARacter
1564 1565
 */

1566
void WCMD_strtrim_trailing_spaces (WCHAR *string) {
1567

1568
  WCHAR *ptr;
1569

1570
  ptr = string + strlenW (string) - 1;
1571 1572 1573 1574 1575
  while ((*ptr == ' ') && (ptr >= string)) {
    *ptr = '\0';
    ptr--;
  }
}
1576

1577 1578 1579
/*************************************************************************
 * WCMD_opt_s_strip_quotes
 *
1580
 *	Remove first and last quote WCHARacters, preserving all other text
1581 1582
 */

1583 1584
void WCMD_opt_s_strip_quotes(WCHAR *cmd) {
  WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL;
1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596
  while((*dest=*src) != '\0') {
      if (*src=='\"')
          lastq=dest;
      dest++, src++;
  }
  if (lastq) {
      dest=lastq++;
      while ((*dest++=*lastq++) != 0)
          ;
  }
}

1597 1598 1599
/*************************************************************************
 * WCMD_expand_envvar
 *
1600
 *	Expands environment variables, allowing for WCHARacter substitution
1601
 */
1602
static WCHAR *WCMD_expand_envvar(WCHAR *start, WCHAR *forVar, WCHAR *forVal) {
1603 1604 1605 1606 1607
    WCHAR *endOfVar = NULL, *s;
    WCHAR *colonpos = NULL;
    WCHAR thisVar[MAXSTRING];
    WCHAR thisVarContents[MAXSTRING];
    WCHAR savedchar = 0x00;
1608 1609
    int len;

1610 1611 1612 1613 1614 1615 1616 1617 1618 1619
    static const WCHAR ErrorLvl[]  = {'E','R','R','O','R','L','E','V','E','L','\0'};
    static const WCHAR ErrorLvlP[] = {'%','E','R','R','O','R','L','E','V','E','L','%','\0'};
    static const WCHAR Date[]      = {'D','A','T','E','\0'};
    static const WCHAR DateP[]     = {'%','D','A','T','E','%','\0'};
    static const WCHAR Time[]      = {'T','I','M','E','\0'};
    static const WCHAR TimeP[]     = {'%','T','I','M','E','%','\0'};
    static const WCHAR Cd[]        = {'C','D','\0'};
    static const WCHAR CdP[]       = {'%','C','D','%','\0'};
    static const WCHAR Random[]    = {'R','A','N','D','O','M','\0'};
    static const WCHAR RandomP[]   = {'%','R','A','N','D','O','M','%','\0'};
1620 1621 1622 1623
    static const WCHAR Delims[]    = {'%',' ',':','\0'};

    WINE_TRACE("Expanding: %s (%s,%s)\n", wine_dbgstr_w(start),
               wine_dbgstr_w(forVal), wine_dbgstr_w(forVar));
1624

1625
    /* Find the end of the environment variable, and extract name */
1626 1627 1628 1629
    endOfVar = strpbrkW(start+1, Delims);

    if (endOfVar == NULL || *endOfVar==' ') {

1630 1631
      /* In batch program, missing terminator for % and no following
         ':' just removes the '%'                                   */
1632 1633 1634 1635 1636 1637
      if (context) {
        s = WCMD_strdupW(start + 1);
        strcpyW (start, s);
        free(s);
        return start;
      } else {
1638

1639 1640 1641 1642
        /* In command processing, just ignore it - allows command line
           syntax like: for %i in (a.a) do echo %i                     */
        return start+1;
      }
1643
    }
1644 1645 1646 1647 1648 1649 1650 1651

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

1652
    memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
1653
    thisVar[(endOfVar - start)+1] = 0x00;
1654
    colonpos = strchrW(thisVar+1, ':');
1655 1656 1657 1658 1659 1660 1661 1662 1663

    /* If there's complex substitution, just need %var% for now
       to get the expanded data to play with                    */
    if (colonpos) {
        *colonpos = '%';
        savedchar = *(colonpos+1);
        *(colonpos+1) = 0x00;
    }

1664 1665
    WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));

1666
    /* Expand to contents, if unchanged, return */
1667 1668 1669 1670
    /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
    /* override if existing env var called that name              */
    if ((CompareString (LOCALE_USER_DEFAULT,
                        NORM_IGNORECASE | SORT_STRINGSORT,
1671 1672
                        thisVar, 12, ErrorLvlP, -1) == 2) &&
                (GetEnvironmentVariable(ErrorLvl, thisVarContents, 1) == 0) &&
1673
                (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1674 1675 1676
      static const WCHAR fmt[] = {'%','d','\0'};
      wsprintf(thisVarContents, fmt, errorlevel);
      len = strlenW(thisVarContents);
1677 1678 1679

    } else if ((CompareString (LOCALE_USER_DEFAULT,
                               NORM_IGNORECASE | SORT_STRINGSORT,
1680 1681
                               thisVar, 6, DateP, -1) == 2) &&
                (GetEnvironmentVariable(Date, thisVarContents, 1) == 0) &&
1682 1683 1684 1685
                (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {

      GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
                    NULL, thisVarContents, MAXSTRING);
1686
      len = strlenW(thisVarContents);
1687 1688 1689

    } else if ((CompareString (LOCALE_USER_DEFAULT,
                               NORM_IGNORECASE | SORT_STRINGSORT,
1690 1691
                               thisVar, 6, TimeP, -1) == 2) &&
                (GetEnvironmentVariable(Time, thisVarContents, 1) == 0) &&
1692 1693 1694
                (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
      GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
                        NULL, thisVarContents, MAXSTRING);
1695
      len = strlenW(thisVarContents);
1696 1697 1698

    } else if ((CompareString (LOCALE_USER_DEFAULT,
                               NORM_IGNORECASE | SORT_STRINGSORT,
1699 1700
                               thisVar, 4, CdP, -1) == 2) &&
                (GetEnvironmentVariable(Cd, thisVarContents, 1) == 0) &&
1701 1702
                (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
      GetCurrentDirectory (MAXSTRING, thisVarContents);
1703
      len = strlenW(thisVarContents);
1704 1705 1706

    } else if ((CompareString (LOCALE_USER_DEFAULT,
                               NORM_IGNORECASE | SORT_STRINGSORT,
1707 1708
                               thisVar, 8, RandomP, -1) == 2) &&
                (GetEnvironmentVariable(Random, thisVarContents, 1) == 0) &&
1709
                (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1710 1711 1712
      static const WCHAR fmt[] = {'%','d','\0'};
      wsprintf(thisVarContents, fmt, rand() % 32768);
      len = strlenW(thisVarContents);
1713

1714 1715 1716 1717 1718 1719 1720 1721 1722 1723
    /* Look for a matching 'for' variable */
    } else if (forVar &&
               (CompareString (LOCALE_USER_DEFAULT,
                               SORT_STRINGSORT,
                               thisVar,
                               (colonpos - thisVar) - 1,
                               forVar, -1) == 2)) {
      strcpyW(thisVarContents, forVal);
      len = strlenW(thisVarContents);

1724 1725 1726
    } else {

      len = ExpandEnvironmentStrings(thisVar, thisVarContents,
1727
                               sizeof(thisVarContents)/sizeof(WCHAR));
1728 1729
    }

1730 1731 1732 1733 1734 1735 1736
    if (len == 0)
      return endOfVar+1;

    /* 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      */
1737
    if (lstrcmpiW(thisVar, thisVarContents) == 0) {
1738 1739 1740 1741 1742 1743 1744 1745 1746 1747

      /* Restore the complex part after the compare */
      if (colonpos) {
        *colonpos = ':';
        *(colonpos+1) = savedchar;
      }

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

1748 1749
      s = WCMD_strdupW(endOfVar + 1);

1750 1751
      /* Batch - replace unknown env var with nothing */
      if (colonpos == NULL) {
1752
        strcpyW (start, s);
1753 1754

      } else {
1755
        len = strlenW(thisVar);
1756 1757 1758
        thisVar[len-1] = 0x00;
        /* If %:...% supplied, : is retained */
        if (colonpos == thisVar+1) {
1759
          strcpyW (start, colonpos);
1760
        } else {
1761
          strcpyW (start, colonpos+1);
1762
        }
1763
        strcatW (start, s);
1764 1765 1766 1767 1768 1769 1770 1771 1772
      }
      free (s);
      return start;

    }

    /* See if we need to do complex substitution (any ':'s), if not
       then our work here is done                                  */
    if (colonpos == NULL) {
1773 1774 1775
      s = WCMD_strdupW(endOfVar + 1);
      strcpyW (start, thisVarContents);
      strcatW (start, s);
1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787
      free(s);
      return start;
    }

    /* Restore complex bit */
    *colonpos = ':';
    *(colonpos+1) = savedchar;

    /*
        Handle complex substitutions:
           xxx=yyy    (replace xxx with yyy)
           *xxx=yyy   (replace up to and including xxx with yyy)
1788 1789 1790 1791
           ~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)
1792 1793 1794 1795 1796
     */

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

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

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

1804
      s = WCMD_strdupW(endOfVar + 1);
1805 1806 1807 1808 1809 1810 1811 1812 1813

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

      if (commapos == NULL) {
1814
        strcpyW (start, startCopy); /* Copy the lot */
1815 1816 1817 1818 1819
      } else if (substrlength < 0) {

        int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
        if (copybytes > len) copybytes = len;
        else if (copybytes < 0) copybytes = 0;
1820
        memcpy (start, startCopy, copybytes * sizeof(WCHAR)); /* Copy the lot */
1821 1822
        start[copybytes] = 0x00;
      } else {
1823
        memcpy (start, startCopy, substrlength * sizeof(WCHAR)); /* Copy the lot */
1824 1825 1826
        start[substrlength] = 0x00;
      }

1827
      strcatW (start, s);
1828 1829 1830 1831 1832
      free(s);
      return start;

    /* search and replace manipulation */
    } else {
1833 1834 1835 1836 1837
      WCHAR *equalspos = strstrW(colonpos, equalsW);
      WCHAR *replacewith = equalspos+1;
      WCHAR *found       = NULL;
      WCHAR *searchIn;
      WCHAR *searchFor;
1838

1839
      s = WCMD_strdupW(endOfVar + 1);
1840 1841 1842
      if (equalspos == NULL) return start+1;

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

      /* Since we need to be case insensitive, copy the 2 buffers */
1847 1848 1849 1850
      searchIn  = WCMD_strdupW(thisVarContents);
      CharUpperBuff(searchIn, strlenW(thisVarContents));
      searchFor = WCMD_strdupW(colonpos+1);
      CharUpperBuff(searchFor, strlenW(colonpos+1));
1851 1852 1853 1854 1855


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

        if (found) {
          /* Do replacement */
1860 1861 1862
          strcpyW(start, replacewith);
          strcatW(start, thisVarContents + (found-searchIn) + strlenW(searchFor+1));
          strcatW(start, s);
1863 1864 1865
          free(s);
        } else {
          /* Copy as it */
1866 1867
          strcpyW(start, thisVarContents);
          strcatW(start, s);
1868 1869 1870 1871
        }

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

        *start = 0x00;
1876 1877
        while ((found = strstrW(lastFound, searchFor))) {
            lstrcpynW(outputposn,
1878
                    thisVarContents + (lastFound-searchIn),
1879
                    (found - lastFound)+1);
1880
            outputposn  = outputposn + (found - lastFound);
1881 1882 1883
            strcatW(outputposn, replacewith);
            outputposn = outputposn + strlenW(replacewith);
            lastFound = found + strlenW(searchFor);
1884
        }
1885
        strcatW(outputposn,
1886
                thisVarContents + (lastFound-searchIn));
1887
        strcatW(outputposn, s);
1888 1889 1890 1891 1892 1893 1894
      }
      free(searchIn);
      free(searchFor);
      return start;
    }
    return start+1;
}
1895 1896 1897 1898 1899 1900

/*************************************************************************
 * WCMD_LoadMessage
 *    Load a string from the resource file, handling any error
 *    Returns string retrieved from resource file
 */
1901 1902 1903
WCHAR *WCMD_LoadMessage(UINT id) {
    static WCHAR msg[2048];
    static const WCHAR failedMsg[]  = {'F','a','i','l','e','d','!','\0'};
1904

1905
    if (!LoadString(GetModuleHandle(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1906
       WINE_FIXME("LoadString failed with %d\n", GetLastError());
1907
       strcpyW(msg, failedMsg);
1908 1909 1910
    }
    return msg;
}
1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962

/*************************************************************************
 * WCMD_strdupW
 *    A wide version of strdup as its missing from unicode.h
 */
WCHAR *WCMD_strdupW(WCHAR *input) {
   int len=strlenW(input)+1;
   /* Note: Use malloc not HeapAlloc to emulate strdup */
   WCHAR *result = malloc(len * sizeof(WCHAR));
   memcpy(result, input, len * sizeof(WCHAR));
   return result;
}

/***************************************************************************
 * WCMD_Readfile
 *
 *	Read characters in from a console/file, returning result in Unicode
 *      with signature identical to ReadFile
 */
BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars,
                          LPDWORD charsRead, const LPOVERLAPPED unused) {

    BOOL   res;

    /* Try to read from console as Unicode */
    res = ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);

    /* If reading from console has failed we assume its file
       i/o so read in and convert from OEM codepage               */
    if (!res) {

        DWORD numRead;
        /*
         * Allocate buffer to use when reading from file. Not freed
         */
        if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
                                                  MAX_WRITECONSOLE_SIZE);
        if (!output_bufA) {
          WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
          return 0;
        }

        /* Read from file (assume OEM codepage) */
        res = ReadFile(hIn, output_bufA, maxChars, &numRead, unused);

        /* Convert from OEM */
        *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, output_bufA, numRead,
                         intoBuf, maxChars);

    }
    return res;
}
1963 1964 1965 1966 1967 1968 1969 1970 1971

/***************************************************************************
 * WCMD_DumpCommands
 *
 *	Domps out the parsed command line to ensure syntax is correct
 */
void WCMD_DumpCommands(CMD_LIST *commands) {
    WCHAR buffer[MAXSTRING];
    CMD_LIST *thisCmd = commands;
Jason Edmeades's avatar
Jason Edmeades committed
1972
    const WCHAR fmt[] = {'%','p',' ','%','d',' ','%','2','.','2','d',' ',
1973 1974
                         '%','p',' ','%','s',' ','R','e','d','i','r',':',
                         '%','s','\0'};
1975 1976 1977 1978 1979

    WINE_TRACE("Parsed line:\n");
    while (thisCmd != NULL) {
      sprintfW(buffer, fmt,
               thisCmd,
Jason Edmeades's avatar
Jason Edmeades committed
1980
               thisCmd->prevDelim,
1981 1982
               thisCmd->bracketDepth,
               thisCmd->nextcommand,
1983 1984
               thisCmd->command,
               thisCmd->redirects);
1985 1986 1987 1988 1989
      WINE_TRACE("%s\n", wine_dbgstr_w(buffer));
      thisCmd = thisCmd->nextcommand;
    }
}

1990 1991 1992 1993 1994 1995 1996 1997
/***************************************************************************
 * WCMD_addCommand
 *
 *   Adds a command to the current command list
 */
void WCMD_addCommand(WCHAR *command, int *commandLen,
                     WCHAR *redirs,  int *redirLen,
                     WCHAR **copyTo, int **copyToLen,
Jason Edmeades's avatar
Jason Edmeades committed
1998
                     CMD_DELIMITERS prevDelim, int curDepth,
1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017
                     CMD_LIST **lastEntry, CMD_LIST **output) {

    CMD_LIST *thisEntry = NULL;

    /* Allocate storage for command */
    thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));

    /* Copy in the command */
    if (command) {
        thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
                                      (*commandLen+1) * sizeof(WCHAR));
        memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
        thisEntry->command[*commandLen] = 0x00;

        /* Copy in the redirects */
        thisEntry->redirects = HeapAlloc(GetProcessHeap(), 0,
                                         (*redirLen+1) * sizeof(WCHAR));
        memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
        thisEntry->redirects[*redirLen] = 0x00;
Jason Edmeades's avatar
Jason Edmeades committed
2018
        thisEntry->pipeFile[0] = 0x00;
2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031

        /* Reset the lengths */
        *commandLen   = 0;
        *redirLen     = 0;
        *copyToLen    = commandLen;
        *copyTo       = command;

    } else {
        thisEntry->command = NULL;
    }

    /* Fill in other fields */
    thisEntry->nextcommand = NULL;
Jason Edmeades's avatar
Jason Edmeades committed
2032
    thisEntry->prevDelim = prevDelim;
2033 2034 2035 2036 2037 2038 2039 2040 2041
    thisEntry->bracketDepth = curDepth;
    if (*lastEntry) {
        (*lastEntry)->nextcommand = thisEntry;
    } else {
        *output = thisEntry;
    }
    *lastEntry = thisEntry;
}

2042 2043 2044 2045 2046
/***************************************************************************
 * WCMD_ReadAndParseLine
 *
 *   Either uses supplied input or
 *     Reads a file from the handle, and then...
2047
 *   Parse the text buffer, spliting into separate commands
2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060
 *     - 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)
 */
WCHAR *WCMD_ReadAndParseLine(WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom) {

    WCHAR    *curPos;
    BOOL      inQuotes = FALSE;
    WCHAR     curString[MAXSTRING];
2061 2062 2063 2064 2065
    int       curStringLen = 0;
    WCHAR     curRedirs[MAXSTRING];
    int       curRedirsLen = 0;
    WCHAR    *curCopyTo;
    int      *curLen;
2066 2067
    int       curDepth = 0;
    CMD_LIST *lastEntry = NULL;
Jason Edmeades's avatar
Jason Edmeades committed
2068
    CMD_DELIMITERS prevDelim = CMD_NONE;
2069
    static WCHAR    *extraSpace = NULL;  /* Deliberately never freed */
2070 2071
    const WCHAR remCmd[] = {'r','e','m',' ','\0'};
    const WCHAR forCmd[] = {'f','o','r',' ','\0'};
2072 2073
    const WCHAR ifCmd[]  = {'i','f',' ','\0'};
    const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
2074
    BOOL      inRem = FALSE;
2075
    BOOL      inFor = FALSE;
2076
    BOOL      inIn  = FALSE;
2077 2078
    BOOL      inIf  = FALSE;
    BOOL      inElse= FALSE;
2079 2080
    BOOL      onlyWhiteSpace = FALSE;
    BOOL      lastWasWhiteSpace = FALSE;
2081 2082 2083
    BOOL      lastWasDo   = FALSE;
    BOOL      lastWasIn   = FALSE;
    BOOL      lastWasElse = FALSE;
2084
    BOOL      lastWasRedirect = TRUE;
2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106

    /* Allocate working space for a command read from keyboard, file etc */
    if (!extraSpace)
      extraSpace = HeapAlloc(GetProcessHeap(), 0, (MAXSTRING+1) * sizeof(WCHAR));

    /* 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 {
        if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) return NULL;
    }
    curPos = extraSpace;

    /* Handle truncated input - issue warning */
    if (strlenW(extraSpace) == MAXSTRING -1) {
        WCMD_output_asis(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
        WCMD_output_asis(extraSpace);
        WCMD_output_asis(newline);
    }

2107 2108 2109
    /* Replace env vars if in a batch context */
    if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);

2110 2111 2112 2113 2114 2115
    /* Start with an empty string, copying to the command string */
    curStringLen = 0;
    curRedirsLen = 0;
    curCopyTo    = curString;
    curLen       = &curStringLen;
    lastWasRedirect = FALSE;  /* Required for eg spaces between > and filename */
2116 2117 2118

    /* Parse every character on the line being processed */
    while (*curPos != 0x00) {
2119 2120 2121

      WCHAR thisChar;

2122
      /* Debugging AID:
2123
      WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
2124 2125 2126
                 lastWasWhiteSpace, onlyWhiteSpace);
      */

2127 2128
      /* Certain commands need special handling */
      if (curStringLen == 0 && curCopyTo == curString) {
2129
        const WCHAR forDO[]  = {'d','o',' ','\0'};
2130 2131

        /* If command starts with 'rem', ignore any &&, ( etc */
2132
        if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2133
          curPos, 4, remCmd, -1) == 2) {
2134
          inRem = TRUE;
2135 2136 2137 2138 2139 2140

        /* If command starts with 'for', handle ('s mid line after IN or DO */
        } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
          curPos, 4, forCmd, -1) == 2) {
          inFor = TRUE;

2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155
        /* If command starts with 'if' or 'else', handle ('s mid line. We should ensure this
           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 */
        } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
          curPos, 3, ifCmd, -1) == 2) {
          inIf = TRUE;

        } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
          curPos, 5, ifElse, -1) == 2) {
          inElse = TRUE;
          lastWasElse = TRUE;
          onlyWhiteSpace = TRUE;
2156 2157
          memcpy(&curCopyTo[*curLen], curPos, 5*sizeof(WCHAR));
          (*curLen)+=5;
2158 2159 2160
          curPos+=5;
          continue;

2161 2162 2163 2164 2165 2166 2167 2168 2169
        /* 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 &&
                   (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
                    curPos, 3, forDO, -1) == 2)) {
          WINE_TRACE("Found DO\n");
          lastWasDo = TRUE;
          onlyWhiteSpace = TRUE;
2170 2171
          memcpy(&curCopyTo[*curLen], curPos, 3*sizeof(WCHAR));
          (*curLen)+=3;
2172 2173 2174
          curPos+=3;
          continue;
        }
2175
      } else if (curCopyTo == curString) {
2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187

        /* Special handling for the 'FOR' command */
        if (inFor && lastWasWhiteSpace) {
          const WCHAR forIN[] = {'i','n',' ','\0'};

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

          if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
              curPos, 3, forIN, -1) == 2) {
            WINE_TRACE("Found IN\n");
            lastWasIn = TRUE;
            onlyWhiteSpace = TRUE;
2188 2189
            memcpy(&curCopyTo[*curLen], curPos, 3*sizeof(WCHAR));
            (*curLen)+=3;
2190 2191 2192
            curPos+=3;
            continue;
          }
2193 2194 2195 2196 2197 2198 2199 2200 2201
        }
      }

      /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
         so just use the default processing ie skip character specific
         matching below                                                    */
      if (!inRem) thisChar = *curPos;
      else        thisChar = 'X';  /* Character with no special processing */

2202 2203
      lastWasWhiteSpace = FALSE; /* Will be reset below */

2204
      switch (thisChar) {
2205

2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222
      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) {

                  /* If finishing off a redirect, add a whitespace delimiter */
                  if (curCopyTo == curRedirs) {
                      curCopyTo[(*curLen)++] = ' ';
                  }
                  curCopyTo = curString;
                  curLen = &curStringLen;
                }
                if (*curLen > 0) {
                  curCopyTo[(*curLen)++] = *curPos;
                }
2223 2224 2225 2226

                /* Remember just processed whitespace */
                lastWasWhiteSpace = TRUE;

2227 2228
                break;

2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251
      case '>': /* drop through - handle redirect chars the same */
      case '<':
                /* Make a redirect start here */
                if (!inQuotes) {
                  curCopyTo = curRedirs;
                  curLen = &curRedirsLen;
                  lastWasRedirect = TRUE;
                }

                /* See if 1>, 2> etc, in which case we have some patching up
                   to do                                                     */
                if (curPos != extraSpace &&
                    *(curPos-1)>='1' && *(curPos-1)<='9') {

                    curStringLen--;
                    curString[curStringLen] = 0x00;
                    curCopyTo[(*curLen)++] = *(curPos-1);
                }

                curCopyTo[(*curLen)++] = *curPos;
                break;

      case '|': /* Pipe character only if not || */
Jason Edmeades's avatar
Jason Edmeades committed
2252 2253
                if (!inQuotes) {
                  lastWasRedirect = FALSE;
2254

Jason Edmeades's avatar
Jason Edmeades committed
2255 2256
                  /* Add an entry to the command list */
                  if (curStringLen > 0) {
2257

Jason Edmeades's avatar
Jason Edmeades committed
2258 2259 2260 2261 2262 2263
                    /* Add the current command */
                    WCMD_addCommand(curString, &curStringLen,
                          curRedirs, &curRedirsLen,
                          &curCopyTo, &curLen,
                          prevDelim, curDepth,
                          &lastEntry, output);
2264

Jason Edmeades's avatar
Jason Edmeades committed
2265
                  }
2266

Jason Edmeades's avatar
Jason Edmeades committed
2267 2268 2269 2270 2271 2272
                  if (*(curPos+1) == '|') {
                    curPos++; /* Skip other | */
                    prevDelim = CMD_ONFAILURE;
                  } else {
                    prevDelim = CMD_PIPE;
                  }
2273 2274 2275 2276 2277
                } else {
                  curCopyTo[(*curLen)++] = *curPos;
                }
                break;

2278
      case '"': inQuotes = !inQuotes;
2279 2280
                curCopyTo[(*curLen)++] = *curPos;
                lastWasRedirect = FALSE;
2281 2282
                break;

2283 2284 2285 2286
      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)"
2287 2288
                           ", for(%d, In:%d, Do:%d)"
                           ", if(%d, else:%d, lwe:%d)\n",
2289
                           *curLen, inQuotes,
2290
                           onlyWhiteSpace,
2291 2292
                           inFor, lastWasIn, lastWasDo,
                           inIf, inElse, lastWasElse);
2293
                lastWasRedirect = FALSE;
2294 2295

                /* Ignore open brackets inside the for set */
2296
                if (*curLen == 0 && !inIn) {
2297 2298 2299 2300
                  curDepth++;

                /* If in quotes, ignore brackets */
                } else if (inQuotes) {
2301
                  curCopyTo[(*curLen)++] = *curPos;
2302 2303

                /* In a FOR loop, an unquoted '(' may occur straight after
2304 2305 2306 2307 2308 2309 2310 2311 2312
                      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)) {
2313

2314 2315 2316 2317 2318 2319
                   /* If entering into an 'IN', set inIn */
                  if (inFor && lastWasIn && onlyWhiteSpace) {
                    WINE_TRACE("Inside an IN\n");
                    inIn = TRUE;
                  }

2320
                  /* Add the current command */
2321 2322 2323
                  WCMD_addCommand(curString, &curStringLen,
                                  curRedirs, &curRedirsLen,
                                  &curCopyTo, &curLen,
Jason Edmeades's avatar
Jason Edmeades committed
2324
                                  prevDelim, curDepth,
2325
                                  &lastEntry, output);
2326

2327
                  curDepth++;
2328
                } else {
2329
                  curCopyTo[(*curLen)++] = *curPos;
2330 2331 2332
                }
                break;

Jason Edmeades's avatar
Jason Edmeades committed
2333
      case '&': if (!inQuotes) {
2334
                  lastWasRedirect = FALSE;
2335 2336

                  /* Add an entry to the command list */
2337 2338 2339 2340 2341 2342
                  if (curStringLen > 0) {

                    /* Add the current command */
                    WCMD_addCommand(curString, &curStringLen,
                          curRedirs, &curRedirsLen,
                          &curCopyTo, &curLen,
Jason Edmeades's avatar
Jason Edmeades committed
2343
                          prevDelim, curDepth,
2344 2345
                          &lastEntry, output);

2346
                  }
Jason Edmeades's avatar
Jason Edmeades committed
2347 2348 2349 2350 2351 2352 2353

                  if (*(curPos+1) == '&') {
                    curPos++; /* Skip other & */
                    prevDelim = CMD_ONSUCCESS;
                  } else {
                    prevDelim = CMD_NONE;
                  }
2354
                } else {
2355
                  curCopyTo[(*curLen)++] = *curPos;
2356 2357 2358 2359
                }
                break;

      case ')': if (!inQuotes && curDepth > 0) {
2360
                  lastWasRedirect = FALSE;
2361 2362

                  /* Add the current command if there is one */
2363 2364 2365 2366 2367 2368
                  if (curStringLen) {

                      /* Add the current command */
                      WCMD_addCommand(curString, &curStringLen,
                            curRedirs, &curRedirsLen,
                            &curCopyTo, &curLen,
Jason Edmeades's avatar
Jason Edmeades committed
2369
                            prevDelim, curDepth,
2370
                            &lastEntry, output);
2371 2372 2373
                  }

                  /* Add an empty entry to the command list */
Jason Edmeades's avatar
Jason Edmeades committed
2374
                  prevDelim = CMD_NONE;
2375 2376 2377
                  WCMD_addCommand(NULL, &curStringLen,
                        curRedirs, &curRedirsLen,
                        &curCopyTo, &curLen,
Jason Edmeades's avatar
Jason Edmeades committed
2378
                        prevDelim, curDepth,
2379
                        &lastEntry, output);
2380
                  curDepth--;
2381 2382 2383

                  /* Leave inIn if necessary */
                  if (inIn) inIn =  FALSE;
2384
                } else {
2385
                  curCopyTo[(*curLen)++] = *curPos;
2386 2387 2388
                }
                break;
      default:
2389 2390
                lastWasRedirect = FALSE;
                curCopyTo[(*curLen)++] = *curPos;
2391 2392 2393 2394
      }

      curPos++;

2395 2396 2397 2398 2399 2400 2401 2402 2403 2404
      /* 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                                               */
      if ((thisChar != ' ') && (thisChar != '\n')) onlyWhiteSpace = FALSE;

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

2405
      /* If we have reached the end, add this command into the list */
2406
      if (*curPos == 0x00 && *curLen > 0) {
2407 2408

          /* Add an entry to the command list */
2409 2410 2411
          WCMD_addCommand(curString, &curStringLen,
                curRedirs, &curRedirsLen,
                &curCopyTo, &curLen,
Jason Edmeades's avatar
Jason Edmeades committed
2412
                prevDelim, curDepth,
2413
                &lastEntry, output);
2414 2415 2416 2417
      }

      /* If we have reached the end of the string, see if bracketing outstanding */
      if (*curPos == 0x00 && curDepth > 0 && readFrom != INVALID_HANDLE_VALUE) {
2418
        inRem = FALSE;
Jason Edmeades's avatar
Jason Edmeades committed
2419
        prevDelim = CMD_NONE;
2420 2421 2422 2423 2424 2425 2426 2427 2428
        inQuotes = FALSE;
        memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));

        /* Read more, skipping any blank lines */
        while (*extraSpace == 0x00) {
          if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
          if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) break;
        }
        curPos = extraSpace;
2429
        if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443
      }
    }

    /* Dump out the parsed output */
    WCMD_DumpCommands(*output);

    return extraSpace;
}

/***************************************************************************
 * WCMD_process_commands
 *
 * Process all the commands read in so far
 */
2444 2445
CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
                                WCHAR *var, WCHAR *val) {
2446 2447 2448 2449

    int bdepth = -1;

    if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2450 2451 2452 2453

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

2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464
      CMD_LIST *origCmd = thisCmd;

      /* 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;
      }

2465 2466 2467 2468 2469
      /* 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));
Jason Edmeades's avatar
Jason Edmeades committed
2470
        WCMD_execute (thisCmd->command, thisCmd->redirects, var, val, &thisCmd);
2471
      }
2472 2473 2474

      /* Step on unless the command itself already stepped on */
      if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2475
    }
2476
    return NULL;
2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496
}

/***************************************************************************
 * 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) {

    /* Loop through the commands, freeing them one by one */
    while (cmds) {
      CMD_LIST *thisCmd = cmds;
      cmds = cmds->nextcommand;
      HeapFree(GetProcessHeap(), 0, thisCmd->command);
      HeapFree(GetProcessHeap(), 0, thisCmd);
    }
}