1 /* 2 * PARSER.C - command parsing. 3 */ 4 5 #include "precomp.h" 6 7 #define C_OP_LOWEST C_MULTI 8 #define C_OP_HIGHEST C_PIPE 9 static const TCHAR OpString[][3] = { _T("&"), _T("||"), _T("&&"), _T("|") }; 10 11 static const TCHAR RedirString[][3] = { _T("<"), _T(">"), _T(">>") }; 12 13 static const TCHAR *const IfOperatorString[] = 14 { 15 _T("cmdextversion"), 16 _T("defined"), 17 _T("errorlevel"), 18 _T("exist"), 19 #define IF_MAX_UNARY IF_EXIST 20 _T("=="), 21 _T("equ"), 22 _T("gtr"), 23 _T("geq"), 24 _T("lss"), 25 _T("leq"), 26 _T("neq"), 27 #define IF_MAX_COMPARISON IF_NEQ 28 }; 29 30 /* These three characters act like spaces to the parser in most contexts */ 31 #define STANDARD_SEPS _T(",;=") 32 33 static BOOL IsSeparator(TCHAR Char) 34 { 35 return _istspace(Char) || (Char && _tcschr(STANDARD_SEPS, Char)); 36 } 37 38 enum 39 { 40 TOK_END, 41 TOK_NORMAL, 42 TOK_OPERATOR, 43 TOK_REDIRECTION, 44 TOK_BEGIN_BLOCK, 45 TOK_END_BLOCK 46 }; 47 48 static BOOL bParseError; 49 static BOOL bLineContinuations; 50 static TCHAR ParseLine[CMDLINE_LENGTH]; 51 static TCHAR *ParsePos; 52 static TCHAR CurChar; 53 54 static TCHAR CurrentToken[CMDLINE_LENGTH]; 55 static int CurrentTokenType; 56 static int InsideBlock; 57 58 static TCHAR ParseChar(void) 59 { 60 TCHAR Char; 61 62 if (bParseError) 63 return CurChar = 0; 64 65 restart: 66 /* 67 * Although CRs can be injected into a line via an environment 68 * variable substitution, the parser ignores them - they won't 69 * even separate tokens. 70 */ 71 do 72 { 73 Char = *ParsePos++; 74 } 75 while (Char == _T('\r')); 76 77 if (!Char) 78 { 79 ParsePos--; 80 if (bLineContinuations) 81 { 82 if (!ReadLine(ParseLine, TRUE)) 83 { 84 /* ^C pressed, or line was too long */ 85 bParseError = TRUE; 86 } 87 else if (*(ParsePos = ParseLine)) 88 { 89 goto restart; 90 } 91 } 92 } 93 return CurChar = Char; 94 } 95 96 static void ParseError(void) 97 { 98 error_syntax(CurrentTokenType != TOK_END ? CurrentToken : NULL); 99 bParseError = TRUE; 100 } 101 102 /* 103 * Yes, cmd has a Lexical Analyzer. Whenever the parser gives an "xxx was 104 * unexpected at this time." message, it shows what the last token read was. 105 */ 106 static int ParseToken(TCHAR ExtraEnd, TCHAR *Separators) 107 { 108 TCHAR *Out = CurrentToken; 109 TCHAR Char; 110 int Type; 111 BOOL bInQuote = FALSE; 112 113 for (Char = CurChar; Char && Char != _T('\n'); Char = ParseChar()) 114 { 115 bInQuote ^= (Char == _T('"')); 116 if (!bInQuote) 117 { 118 if (Separators != NULL) 119 { 120 if (_istspace(Char) || _tcschr(Separators, Char)) 121 { 122 /* Skip leading separators */ 123 if (Out == CurrentToken) 124 continue; 125 break; 126 } 127 } 128 129 /* Check for numbered redirection */ 130 if ((Char >= _T('0') && Char <= _T('9') && 131 (ParsePos == &ParseLine[1] || IsSeparator(ParsePos[-2])) 132 && (*ParsePos == _T('<') || *ParsePos == _T('>')))) 133 { 134 break; 135 } 136 137 if (Char == ExtraEnd) 138 break; 139 if (InsideBlock && Char == _T(')')) 140 break; 141 if (_tcschr(_T("&|<>"), Char)) 142 break; 143 144 if (Char == _T('^')) 145 { 146 Char = ParseChar(); 147 /* Eat up a \n, allowing line continuation */ 148 if (Char == _T('\n')) 149 Char = ParseChar(); 150 /* Next character is a forced literal */ 151 } 152 } 153 if (Out == &CurrentToken[CMDLINE_LENGTH - 1]) 154 break; 155 *Out++ = Char; 156 } 157 158 /* Check if we got at least one character before reaching a special one. 159 * If so, return them and leave the special for the next call. */ 160 if (Out != CurrentToken) 161 { 162 Type = TOK_NORMAL; 163 } 164 else if (Char == _T('(')) 165 { 166 Type = TOK_BEGIN_BLOCK; 167 *Out++ = Char; 168 ParseChar(); 169 } 170 else if (Char == _T(')')) 171 { 172 Type = TOK_END_BLOCK; 173 *Out++ = Char; 174 ParseChar(); 175 } 176 else if (Char == _T('&') || Char == _T('|')) 177 { 178 Type = TOK_OPERATOR; 179 *Out++ = Char; 180 Char = ParseChar(); 181 /* check for && or || */ 182 if (Char == Out[-1]) 183 { 184 *Out++ = Char; 185 ParseChar(); 186 } 187 } 188 else if ((Char >= _T('0') && Char <= _T('9')) 189 || (Char == _T('<') || Char == _T('>'))) 190 { 191 Type = TOK_REDIRECTION; 192 if (Char >= _T('0') && Char <= _T('9')) 193 { 194 *Out++ = Char; 195 Char = ParseChar(); 196 } 197 *Out++ = Char; 198 Char = ParseChar(); 199 if (Char == Out[-1]) 200 { 201 /* Strangely, the tokenizer allows << as well as >>... (it 202 * will cause an error when trying to parse it though) */ 203 *Out++ = Char; 204 Char = ParseChar(); 205 } 206 if (Char == _T('&')) 207 { 208 *Out++ = Char; 209 while (IsSeparator(Char = ParseChar())) 210 ; 211 if (Char >= _T('0') && Char <= _T('9')) 212 { 213 *Out++ = Char; 214 ParseChar(); 215 } 216 } 217 } 218 else 219 { 220 Type = TOK_END; 221 } 222 *Out = _T('\0'); 223 return CurrentTokenType = Type; 224 } 225 226 static BOOL ParseRedirection(REDIRECTION **List) 227 { 228 TCHAR *Tok = CurrentToken; 229 BYTE Number; 230 REDIR_MODE RedirMode; 231 REDIRECTION *Redir; 232 233 if (*Tok >= _T('0') && *Tok <= _T('9')) 234 Number = *Tok++ - _T('0'); 235 else 236 Number = *Tok == _T('<') ? 0 : 1; 237 238 if (*Tok++ == _T('<')) 239 { 240 RedirMode = REDIR_READ; 241 if (*Tok == _T('<')) 242 goto fail; 243 } 244 else 245 { 246 RedirMode = REDIR_WRITE; 247 if (*Tok == _T('>')) 248 { 249 RedirMode = REDIR_APPEND; 250 Tok++; 251 } 252 } 253 254 if (!*Tok) 255 { 256 /* The file name was not part of this token, so it'll be the next one */ 257 if (ParseToken(0, STANDARD_SEPS) != TOK_NORMAL) 258 goto fail; 259 Tok = CurrentToken; 260 } 261 262 /* If a redirection for this handle number already exists, delete it */ 263 while ((Redir = *List)) 264 { 265 if (Redir->Number == Number) 266 { 267 *List = Redir->Next; 268 cmd_free(Redir); 269 continue; 270 } 271 List = &Redir->Next; 272 } 273 274 Redir = cmd_alloc(FIELD_OFFSET(REDIRECTION, Filename[_tcslen(Tok) + 1])); 275 Redir->Next = NULL; 276 Redir->OldHandle = INVALID_HANDLE_VALUE; 277 Redir->Number = Number; 278 Redir->Mode = RedirMode; 279 _tcscpy(Redir->Filename, Tok); 280 *List = Redir; 281 return TRUE; 282 283 fail: 284 ParseError(); 285 FreeRedirection(*List); 286 *List = NULL; 287 return FALSE; 288 } 289 290 static PARSED_COMMAND *ParseCommandOp(int OpType); 291 292 /* Parse a parenthesized block */ 293 static PARSED_COMMAND *ParseBlock(REDIRECTION *RedirList) 294 { 295 PARSED_COMMAND *Cmd, *Sub, **NextPtr; 296 Cmd = cmd_alloc(sizeof(PARSED_COMMAND)); 297 Cmd->Type = C_BLOCK; 298 Cmd->Next = NULL; 299 Cmd->Subcommands = NULL; 300 Cmd->Redirections = RedirList; 301 302 /* Read the block contents */ 303 NextPtr = &Cmd->Subcommands; 304 InsideBlock++; 305 while (1) 306 { 307 Sub = ParseCommandOp(C_OP_LOWEST); 308 if (Sub) 309 { 310 *NextPtr = Sub; 311 NextPtr = &Sub->Next; 312 } 313 else if (bParseError) 314 { 315 InsideBlock--; 316 FreeCommand(Cmd); 317 return NULL; 318 } 319 320 if (CurrentTokenType == TOK_END_BLOCK) 321 break; 322 323 /* Skip past the \n */ 324 ParseChar(); 325 } 326 InsideBlock--; 327 328 /* Process any trailing redirections */ 329 while (ParseToken(0, STANDARD_SEPS) == TOK_REDIRECTION) 330 { 331 if (!ParseRedirection(&Cmd->Redirections)) 332 { 333 FreeCommand(Cmd); 334 return NULL; 335 } 336 } 337 return Cmd; 338 } 339 340 /* Parse an IF statement */ 341 static PARSED_COMMAND *ParseIf(void) 342 { 343 PARSED_COMMAND *Cmd = cmd_alloc(sizeof(PARSED_COMMAND)); 344 int Type; 345 memset(Cmd, 0, sizeof(PARSED_COMMAND)); 346 Cmd->Type = C_IF; 347 348 Type = CurrentTokenType; 349 if (_tcsicmp(CurrentToken, _T("/I")) == 0) 350 { 351 Cmd->If.Flags |= IFFLAG_IGNORECASE; 352 Type = ParseToken(0, STANDARD_SEPS); 353 } 354 if (_tcsicmp(CurrentToken, _T("not")) == 0) 355 { 356 Cmd->If.Flags |= IFFLAG_NEGATE; 357 Type = ParseToken(0, STANDARD_SEPS); 358 } 359 360 if (Type != TOK_NORMAL) 361 { 362 FreeCommand(Cmd); 363 ParseError(); 364 return NULL; 365 } 366 367 /* Check for unary operators */ 368 for (; Cmd->If.Operator <= IF_MAX_UNARY; Cmd->If.Operator++) 369 { 370 if (_tcsicmp(CurrentToken, IfOperatorString[Cmd->If.Operator]) == 0) 371 { 372 if (ParseToken(0, STANDARD_SEPS) != TOK_NORMAL) 373 { 374 FreeCommand(Cmd); 375 ParseError(); 376 return NULL; 377 } 378 Cmd->If.RightArg = cmd_dup(CurrentToken); 379 goto condition_done; 380 } 381 } 382 383 /* It must be a two-argument (comparison) operator. It could be ==, so 384 * the equals sign can't be treated as whitespace here. */ 385 Cmd->If.LeftArg = cmd_dup(CurrentToken); 386 ParseToken(0, _T(",;")); 387 388 /* The right argument can come immediately after == */ 389 if (_tcsnicmp(CurrentToken, _T("=="), 2) == 0 && CurrentToken[2]) 390 { 391 Cmd->If.RightArg = cmd_dup(&CurrentToken[2]); 392 goto condition_done; 393 } 394 395 for (; Cmd->If.Operator <= IF_MAX_COMPARISON; Cmd->If.Operator++) 396 { 397 if (_tcsicmp(CurrentToken, IfOperatorString[Cmd->If.Operator]) == 0) 398 { 399 if (ParseToken(0, STANDARD_SEPS) != TOK_NORMAL) 400 break; 401 Cmd->If.RightArg = cmd_dup(CurrentToken); 402 goto condition_done; 403 } 404 } 405 FreeCommand(Cmd); 406 ParseError(); 407 return NULL; 408 409 condition_done: 410 Cmd->Subcommands = ParseCommandOp(C_OP_LOWEST); 411 if (Cmd->Subcommands == NULL) 412 { 413 FreeCommand(Cmd); 414 return NULL; 415 } 416 if (_tcsicmp(CurrentToken, _T("else")) == 0) 417 { 418 Cmd->Subcommands->Next = ParseCommandOp(C_OP_LOWEST); 419 if (Cmd->Subcommands->Next == NULL) 420 { 421 FreeCommand(Cmd); 422 return NULL; 423 } 424 } 425 426 return Cmd; 427 } 428 429 /* 430 * Parse a FOR command. 431 * Syntax is: FOR [options] %var IN (list) DO command 432 */ 433 static PARSED_COMMAND *ParseFor(void) 434 { 435 PARSED_COMMAND *Cmd = cmd_alloc(sizeof(PARSED_COMMAND)); 436 TCHAR List[CMDLINE_LENGTH]; 437 TCHAR *Pos = List; 438 439 memset(Cmd, 0, sizeof(PARSED_COMMAND)); 440 Cmd->Type = C_FOR; 441 442 while (1) 443 { 444 if (_tcsicmp(CurrentToken, _T("/D")) == 0) 445 Cmd->For.Switches |= FOR_DIRS; 446 else if (_tcsicmp(CurrentToken, _T("/F")) == 0) 447 { 448 Cmd->For.Switches |= FOR_F; 449 if (!Cmd->For.Params) 450 { 451 ParseToken(0, STANDARD_SEPS); 452 if (CurrentToken[0] == _T('/') || CurrentToken[0] == _T('%')) 453 break; 454 Cmd->For.Params = cmd_dup(CurrentToken); 455 } 456 } 457 else if (_tcsicmp(CurrentToken, _T("/L")) == 0) 458 Cmd->For.Switches |= FOR_LOOP; 459 else if (_tcsicmp(CurrentToken, _T("/R")) == 0) 460 { 461 Cmd->For.Switches |= FOR_RECURSIVE; 462 if (!Cmd->For.Params) 463 { 464 ParseToken(0, STANDARD_SEPS); 465 if (CurrentToken[0] == _T('/') || CurrentToken[0] == _T('%')) 466 break; 467 StripQuotes(CurrentToken); 468 Cmd->For.Params = cmd_dup(CurrentToken); 469 } 470 } 471 else 472 break; 473 ParseToken(0, STANDARD_SEPS); 474 } 475 476 /* Make sure there aren't two different switches specified 477 * at the same time, unless they're /D and /R */ 478 if ((Cmd->For.Switches & (Cmd->For.Switches - 1)) != 0 479 && Cmd->For.Switches != (FOR_DIRS | FOR_RECURSIVE)) 480 { 481 goto error; 482 } 483 484 /* Variable name should be % and just one other character */ 485 if (CurrentToken[0] != _T('%') || _tcslen(CurrentToken) != 2) 486 goto error; 487 Cmd->For.Variable = CurrentToken[1]; 488 489 ParseToken(0, STANDARD_SEPS); 490 if (_tcsicmp(CurrentToken, _T("in")) != 0) 491 goto error; 492 493 if (ParseToken(_T('('), STANDARD_SEPS) != TOK_BEGIN_BLOCK) 494 goto error; 495 496 while (1) 497 { 498 int Type; 499 500 /* Pretend we're inside a block so the tokenizer will stop on ')' */ 501 InsideBlock++; 502 Type = ParseToken(0, STANDARD_SEPS); 503 InsideBlock--; 504 505 if (Type == TOK_END_BLOCK) 506 break; 507 508 if (Type == TOK_END) 509 { 510 /* Skip past the \n */ 511 ParseChar(); 512 continue; 513 } 514 515 if (Type != TOK_NORMAL) 516 goto error; 517 518 if (Pos != List) 519 *Pos++ = _T(' '); 520 521 if (Pos + _tcslen(CurrentToken) >= &List[CMDLINE_LENGTH]) 522 goto error; 523 Pos = _stpcpy(Pos, CurrentToken); 524 } 525 *Pos = _T('\0'); 526 Cmd->For.List = cmd_dup(List); 527 528 ParseToken(0, STANDARD_SEPS); 529 if (_tcsicmp(CurrentToken, _T("do")) != 0) 530 goto error; 531 532 Cmd->Subcommands = ParseCommandOp(C_OP_LOWEST); 533 if (Cmd->Subcommands == NULL) 534 { 535 FreeCommand(Cmd); 536 return NULL; 537 } 538 539 return Cmd; 540 541 error: 542 FreeCommand(Cmd); 543 ParseError(); 544 return NULL; 545 } 546 547 /* Parse a REM command */ 548 static PARSED_COMMAND *ParseRem(void) 549 { 550 /* Just ignore the rest of the line */ 551 while (CurChar && CurChar != _T('\n')) 552 ParseChar(); 553 return NULL; 554 } 555 556 static DECLSPEC_NOINLINE PARSED_COMMAND *ParseCommandPart(REDIRECTION *RedirList) 557 { 558 TCHAR ParsedLine[CMDLINE_LENGTH]; 559 PARSED_COMMAND *Cmd; 560 PARSED_COMMAND *(*Func)(void); 561 562 TCHAR *Pos = _stpcpy(ParsedLine, CurrentToken) + 1; 563 DWORD_PTR TailOffset = Pos - ParsedLine; 564 565 /* Check for special forms */ 566 if ((Func = ParseFor, _tcsicmp(ParsedLine, _T("for")) == 0) || 567 (Func = ParseIf, _tcsicmp(ParsedLine, _T("if")) == 0) || 568 (Func = ParseRem, _tcsicmp(ParsedLine, _T("rem")) == 0)) 569 { 570 ParseToken(0, STANDARD_SEPS); 571 /* Do special parsing only if it's not followed by /? */ 572 if (_tcscmp(CurrentToken, _T("/?")) != 0) 573 { 574 if (RedirList) 575 { 576 ParseError(); 577 FreeRedirection(RedirList); 578 return NULL; 579 } 580 return Func(); 581 } 582 Pos = _stpcpy(Pos, _T(" /?")); 583 } 584 585 /* Now get the tail */ 586 while (1) 587 { 588 int Type = ParseToken(0, NULL); 589 if (Type == TOK_NORMAL) 590 { 591 if (Pos + _tcslen(CurrentToken) >= &ParsedLine[CMDLINE_LENGTH]) 592 { 593 ParseError(); 594 FreeRedirection(RedirList); 595 return NULL; 596 } 597 Pos = _stpcpy(Pos, CurrentToken); 598 } 599 else if (Type == TOK_REDIRECTION) 600 { 601 if (!ParseRedirection(&RedirList)) 602 return NULL; 603 } 604 else 605 { 606 break; 607 } 608 } 609 *Pos++ = _T('\0'); 610 611 Cmd = cmd_alloc(FIELD_OFFSET(PARSED_COMMAND, Command.First[Pos - ParsedLine])); 612 Cmd->Type = C_COMMAND; 613 Cmd->Next = NULL; 614 Cmd->Subcommands = NULL; 615 Cmd->Redirections = RedirList; 616 memcpy(Cmd->Command.First, ParsedLine, (Pos - ParsedLine) * sizeof(TCHAR)); 617 Cmd->Command.Rest = Cmd->Command.First + TailOffset; 618 return Cmd; 619 } 620 621 static PARSED_COMMAND *ParsePrimary(void) 622 { 623 REDIRECTION *RedirList = NULL; 624 int Type; 625 626 while (IsSeparator(CurChar)) 627 { 628 if (CurChar == _T('\n')) 629 return NULL; 630 ParseChar(); 631 } 632 633 if (!CurChar) 634 return NULL; 635 636 if (CurChar == _T(':')) 637 { 638 /* "Ignore" the rest of the line. 639 * (Line continuations will still be parsed, though.) */ 640 while (ParseToken(0, NULL) != TOK_END) 641 ; 642 return NULL; 643 } 644 645 if (CurChar == _T('@')) 646 { 647 PARSED_COMMAND *Cmd; 648 ParseChar(); 649 Cmd = cmd_alloc(sizeof(PARSED_COMMAND)); 650 Cmd->Type = C_QUIET; 651 Cmd->Next = NULL; 652 /* @ acts like a unary operator with low precedence, 653 * so call the top-level parser */ 654 Cmd->Subcommands = ParseCommandOp(C_OP_LOWEST); 655 Cmd->Redirections = NULL; 656 return Cmd; 657 } 658 659 /* Process leading redirections and get the head of the command */ 660 while ((Type = ParseToken(_T('('), STANDARD_SEPS)) == TOK_REDIRECTION) 661 { 662 if (!ParseRedirection(&RedirList)) 663 return NULL; 664 } 665 666 if (Type == TOK_NORMAL) 667 return ParseCommandPart(RedirList); 668 else if (Type == TOK_BEGIN_BLOCK) 669 return ParseBlock(RedirList); 670 else if (Type == TOK_END_BLOCK && !RedirList) 671 return NULL; 672 673 ParseError(); 674 FreeRedirection(RedirList); 675 return NULL; 676 } 677 678 static PARSED_COMMAND *ParseCommandOp(int OpType) 679 { 680 PARSED_COMMAND *Cmd, *Left, *Right; 681 682 if (OpType == C_OP_HIGHEST) 683 Cmd = ParsePrimary(); 684 else 685 Cmd = ParseCommandOp(OpType + 1); 686 687 if (Cmd && !_tcscmp(CurrentToken, OpString[OpType - C_OP_LOWEST])) 688 { 689 Left = Cmd; 690 Right = ParseCommandOp(OpType); 691 if (!Right) 692 { 693 if (!bParseError) 694 { 695 /* & is allowed to have an empty RHS */ 696 if (OpType == C_MULTI) 697 return Left; 698 ParseError(); 699 } 700 FreeCommand(Left); 701 return NULL; 702 } 703 704 Cmd = cmd_alloc(sizeof(PARSED_COMMAND)); 705 Cmd->Type = OpType; 706 Cmd->Next = NULL; 707 Cmd->Redirections = NULL; 708 Cmd->Subcommands = Left; 709 Left->Next = Right; 710 Right->Next = NULL; 711 } 712 713 return Cmd; 714 } 715 716 PARSED_COMMAND * 717 ParseCommand(LPTSTR Line) 718 { 719 PARSED_COMMAND *Cmd; 720 721 if (Line) 722 { 723 if (!SubstituteVars(Line, ParseLine, _T('%'))) 724 return NULL; 725 bLineContinuations = FALSE; 726 } 727 else 728 { 729 if (!ReadLine(ParseLine, FALSE)) 730 return NULL; 731 bLineContinuations = TRUE; 732 } 733 bParseError = FALSE; 734 ParsePos = ParseLine; 735 CurChar = _T(' '); 736 737 Cmd = ParseCommandOp(C_OP_LOWEST); 738 if (Cmd) 739 { 740 if (CurrentTokenType != TOK_END) 741 ParseError(); 742 if (bParseError) 743 { 744 FreeCommand(Cmd); 745 Cmd = NULL; 746 } 747 bIgnoreEcho = FALSE; 748 } 749 else 750 { 751 bIgnoreEcho = TRUE; 752 } 753 return Cmd; 754 } 755 756 757 /* 758 * Reconstruct a parse tree into text form; used for echoing 759 * batch file commands and FOR instances. 760 */ 761 VOID 762 EchoCommand(PARSED_COMMAND *Cmd) 763 { 764 TCHAR Buf[CMDLINE_LENGTH]; 765 PARSED_COMMAND *Sub; 766 REDIRECTION *Redir; 767 768 switch (Cmd->Type) 769 { 770 case C_COMMAND: 771 if (SubstituteForVars(Cmd->Command.First, Buf)) 772 ConOutPrintf(_T("%s"), Buf); 773 if (SubstituteForVars(Cmd->Command.Rest, Buf)) 774 ConOutPrintf(_T("%s"), Buf); 775 break; 776 case C_QUIET: 777 return; 778 case C_BLOCK: 779 ConOutChar(_T('(')); 780 Sub = Cmd->Subcommands; 781 if (Sub && !Sub->Next) 782 { 783 /* Single-command block: display all on one line */ 784 EchoCommand(Sub); 785 } 786 else if (Sub) 787 { 788 /* Multi-command block: display parenthesis on separate lines */ 789 ConOutChar(_T('\n')); 790 do 791 { 792 EchoCommand(Sub); 793 ConOutChar(_T('\n')); 794 Sub = Sub->Next; 795 } while (Sub); 796 } 797 ConOutChar(_T(')')); 798 break; 799 case C_MULTI: 800 case C_IFFAILURE: 801 case C_IFSUCCESS: 802 case C_PIPE: 803 Sub = Cmd->Subcommands; 804 EchoCommand(Sub); 805 ConOutPrintf(_T(" %s "), OpString[Cmd->Type - C_OP_LOWEST]); 806 EchoCommand(Sub->Next); 807 break; 808 case C_IF: 809 ConOutPrintf(_T("if")); 810 if (Cmd->If.Flags & IFFLAG_IGNORECASE) 811 ConOutPrintf(_T(" /I")); 812 if (Cmd->If.Flags & IFFLAG_NEGATE) 813 ConOutPrintf(_T(" not")); 814 if (Cmd->If.LeftArg && SubstituteForVars(Cmd->If.LeftArg, Buf)) 815 ConOutPrintf(_T(" %s"), Buf); 816 ConOutPrintf(_T(" %s"), IfOperatorString[Cmd->If.Operator]); 817 if (SubstituteForVars(Cmd->If.RightArg, Buf)) 818 ConOutPrintf(_T(" %s "), Buf); 819 Sub = Cmd->Subcommands; 820 EchoCommand(Sub); 821 if (Sub->Next) 822 { 823 ConOutPrintf(_T(" else ")); 824 EchoCommand(Sub->Next); 825 } 826 break; 827 case C_FOR: 828 ConOutPrintf(_T("for")); 829 if (Cmd->For.Switches & FOR_DIRS) ConOutPrintf(_T(" /D")); 830 if (Cmd->For.Switches & FOR_F) ConOutPrintf(_T(" /F")); 831 if (Cmd->For.Switches & FOR_LOOP) ConOutPrintf(_T(" /L")); 832 if (Cmd->For.Switches & FOR_RECURSIVE) ConOutPrintf(_T(" /R")); 833 if (Cmd->For.Params) 834 ConOutPrintf(_T(" %s"), Cmd->For.Params); 835 ConOutPrintf(_T(" %%%c in (%s) do "), Cmd->For.Variable, Cmd->For.List); 836 EchoCommand(Cmd->Subcommands); 837 break; 838 } 839 840 for (Redir = Cmd->Redirections; Redir; Redir = Redir->Next) 841 { 842 if (SubstituteForVars(Redir->Filename, Buf)) 843 ConOutPrintf(_T(" %c%s%s"), _T('0') + Redir->Number, 844 RedirString[Redir->Mode], Buf); 845 } 846 } 847 848 /* 849 * "Unparse" a command into a text form suitable for passing to CMD /C. 850 * Used for pipes. This is basically the same thing as EchoCommand, but 851 * writing into a string instead of to standard output. 852 */ 853 TCHAR * 854 Unparse(PARSED_COMMAND *Cmd, TCHAR *Out, TCHAR *OutEnd) 855 { 856 TCHAR Buf[CMDLINE_LENGTH]; 857 PARSED_COMMAND *Sub; 858 REDIRECTION *Redir; 859 860 /* 861 * Since this function has the annoying requirement that it must avoid 862 * overflowing the supplied buffer, define some helper macros to make 863 * this less painful. 864 */ 865 #define CHAR(Char) { \ 866 if (Out == OutEnd) return NULL; \ 867 *Out++ = Char; } 868 #define STRING(String) { \ 869 if (Out + _tcslen(String) > OutEnd) return NULL; \ 870 Out = _stpcpy(Out, String); } 871 #define PRINTF(Format, ...) { \ 872 UINT Len = _sntprintf(Out, OutEnd - Out, Format, __VA_ARGS__); \ 873 if (Len > (UINT)(OutEnd - Out)) return NULL; \ 874 Out += Len; } 875 #define RECURSE(Subcommand) { \ 876 Out = Unparse(Subcommand, Out, OutEnd); \ 877 if (!Out) return NULL; } 878 879 switch (Cmd->Type) 880 { 881 case C_COMMAND: 882 /* This is fragile since there could be special characters, but 883 * Windows doesn't bother escaping them, so for compatibility 884 * we probably shouldn't do it either */ 885 if (!SubstituteForVars(Cmd->Command.First, Buf)) return NULL; 886 STRING(Buf) 887 if (!SubstituteForVars(Cmd->Command.Rest, Buf)) return NULL; 888 STRING(Buf) 889 break; 890 case C_QUIET: 891 CHAR(_T('@')) 892 RECURSE(Cmd->Subcommands) 893 break; 894 case C_BLOCK: 895 CHAR(_T('(')) 896 for (Sub = Cmd->Subcommands; Sub; Sub = Sub->Next) 897 { 898 RECURSE(Sub) 899 if (Sub->Next) 900 CHAR(_T('&')) 901 } 902 CHAR(_T(')')) 903 break; 904 case C_MULTI: 905 case C_IFFAILURE: 906 case C_IFSUCCESS: 907 case C_PIPE: 908 Sub = Cmd->Subcommands; 909 RECURSE(Sub) 910 PRINTF(_T(" %s "), OpString[Cmd->Type - C_OP_LOWEST]) 911 RECURSE(Sub->Next) 912 break; 913 case C_IF: 914 STRING(_T("if")) 915 if (Cmd->If.Flags & IFFLAG_IGNORECASE) 916 STRING(_T(" /I")) 917 if (Cmd->If.Flags & IFFLAG_NEGATE) 918 STRING(_T(" not")) 919 if (Cmd->If.LeftArg && SubstituteForVars(Cmd->If.LeftArg, Buf)) 920 PRINTF(_T(" %s"), Buf) 921 PRINTF(_T(" %s"), IfOperatorString[Cmd->If.Operator]); 922 if (!SubstituteForVars(Cmd->If.RightArg, Buf)) return NULL; 923 PRINTF(_T(" %s "), Buf) 924 Sub = Cmd->Subcommands; 925 RECURSE(Sub) 926 if (Sub->Next) 927 { 928 STRING(_T(" else ")) 929 RECURSE(Sub->Next) 930 } 931 break; 932 case C_FOR: 933 STRING(_T("for")) 934 if (Cmd->For.Switches & FOR_DIRS) STRING(_T(" /D")) 935 if (Cmd->For.Switches & FOR_F) STRING(_T(" /F")) 936 if (Cmd->For.Switches & FOR_LOOP) STRING(_T(" /L")) 937 if (Cmd->For.Switches & FOR_RECURSIVE) STRING(_T(" /R")) 938 if (Cmd->For.Params) 939 PRINTF(_T(" %s"), Cmd->For.Params) 940 PRINTF(_T(" %%%c in (%s) do "), Cmd->For.Variable, Cmd->For.List) 941 RECURSE(Cmd->Subcommands) 942 break; 943 } 944 945 for (Redir = Cmd->Redirections; Redir; Redir = Redir->Next) 946 { 947 if (!SubstituteForVars(Redir->Filename, Buf)) return NULL; 948 PRINTF(_T(" %c%s%s"), _T('0') + Redir->Number, 949 RedirString[Redir->Mode], Buf) 950 } 951 return Out; 952 } 953 954 VOID 955 FreeCommand(PARSED_COMMAND *Cmd) 956 { 957 if (Cmd->Subcommands) 958 FreeCommand(Cmd->Subcommands); 959 if (Cmd->Next) 960 FreeCommand(Cmd->Next); 961 FreeRedirection(Cmd->Redirections); 962 if (Cmd->Type == C_IF) 963 { 964 cmd_free(Cmd->If.LeftArg); 965 cmd_free(Cmd->If.RightArg); 966 } 967 else if (Cmd->Type == C_FOR) 968 { 969 cmd_free(Cmd->For.Params); 970 cmd_free(Cmd->For.List); 971 } 972 cmd_free(Cmd); 973 } 974