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 if (!Redir) 276 { 277 WARN("Cannot allocate memory for Redir!\n"); 278 goto fail; 279 } 280 Redir->Next = NULL; 281 Redir->OldHandle = INVALID_HANDLE_VALUE; 282 Redir->Number = Number; 283 Redir->Mode = RedirMode; 284 _tcscpy(Redir->Filename, Tok); 285 *List = Redir; 286 return TRUE; 287 288 fail: 289 ParseError(); 290 FreeRedirection(*List); 291 *List = NULL; 292 return FALSE; 293 } 294 295 static PARSED_COMMAND *ParseCommandOp(int OpType); 296 297 /* Parse a parenthesized block */ 298 static PARSED_COMMAND *ParseBlock(REDIRECTION *RedirList) 299 { 300 PARSED_COMMAND *Cmd, *Sub, **NextPtr; 301 302 Cmd = cmd_alloc(sizeof(PARSED_COMMAND)); 303 if (!Cmd) 304 { 305 WARN("Cannot allocate memory for Cmd!\n"); 306 ParseError(); 307 FreeRedirection(RedirList); 308 return NULL; 309 } 310 Cmd->Type = C_BLOCK; 311 Cmd->Next = NULL; 312 Cmd->Subcommands = NULL; 313 Cmd->Redirections = RedirList; 314 315 /* Read the block contents */ 316 NextPtr = &Cmd->Subcommands; 317 InsideBlock++; 318 while (1) 319 { 320 Sub = ParseCommandOp(C_OP_LOWEST); 321 if (Sub) 322 { 323 *NextPtr = Sub; 324 NextPtr = &Sub->Next; 325 } 326 else if (bParseError) 327 { 328 InsideBlock--; 329 FreeCommand(Cmd); 330 return NULL; 331 } 332 333 if (CurrentTokenType == TOK_END_BLOCK) 334 break; 335 336 /* Skip past the \n */ 337 ParseChar(); 338 } 339 InsideBlock--; 340 341 /* Process any trailing redirections */ 342 while (ParseToken(0, STANDARD_SEPS) == TOK_REDIRECTION) 343 { 344 if (!ParseRedirection(&Cmd->Redirections)) 345 { 346 FreeCommand(Cmd); 347 return NULL; 348 } 349 } 350 return Cmd; 351 } 352 353 /* Parse an IF statement */ 354 static PARSED_COMMAND *ParseIf(void) 355 { 356 int Type; 357 PARSED_COMMAND *Cmd; 358 359 Cmd = cmd_alloc(sizeof(PARSED_COMMAND)); 360 if (!Cmd) 361 { 362 WARN("Cannot allocate memory for Cmd!\n"); 363 ParseError(); 364 return NULL; 365 } 366 memset(Cmd, 0, sizeof(PARSED_COMMAND)); 367 Cmd->Type = C_IF; 368 369 Type = CurrentTokenType; 370 if (_tcsicmp(CurrentToken, _T("/I")) == 0) 371 { 372 Cmd->If.Flags |= IFFLAG_IGNORECASE; 373 Type = ParseToken(0, STANDARD_SEPS); 374 } 375 if (_tcsicmp(CurrentToken, _T("not")) == 0) 376 { 377 Cmd->If.Flags |= IFFLAG_NEGATE; 378 Type = ParseToken(0, STANDARD_SEPS); 379 } 380 381 if (Type != TOK_NORMAL) 382 { 383 FreeCommand(Cmd); 384 ParseError(); 385 return NULL; 386 } 387 388 /* Check for unary operators */ 389 for (; Cmd->If.Operator <= IF_MAX_UNARY; Cmd->If.Operator++) 390 { 391 if (_tcsicmp(CurrentToken, IfOperatorString[Cmd->If.Operator]) == 0) 392 { 393 if (ParseToken(0, STANDARD_SEPS) != TOK_NORMAL) 394 { 395 FreeCommand(Cmd); 396 ParseError(); 397 return NULL; 398 } 399 Cmd->If.RightArg = cmd_dup(CurrentToken); 400 goto condition_done; 401 } 402 } 403 404 /* It must be a two-argument (comparison) operator. It could be ==, so 405 * the equals sign can't be treated as whitespace here. */ 406 Cmd->If.LeftArg = cmd_dup(CurrentToken); 407 ParseToken(0, _T(",;")); 408 409 /* The right argument can come immediately after == */ 410 if (_tcsnicmp(CurrentToken, _T("=="), 2) == 0 && CurrentToken[2]) 411 { 412 Cmd->If.RightArg = cmd_dup(&CurrentToken[2]); 413 goto condition_done; 414 } 415 416 for (; Cmd->If.Operator <= IF_MAX_COMPARISON; Cmd->If.Operator++) 417 { 418 if (_tcsicmp(CurrentToken, IfOperatorString[Cmd->If.Operator]) == 0) 419 { 420 if (ParseToken(0, STANDARD_SEPS) != TOK_NORMAL) 421 break; 422 Cmd->If.RightArg = cmd_dup(CurrentToken); 423 goto condition_done; 424 } 425 } 426 FreeCommand(Cmd); 427 ParseError(); 428 return NULL; 429 430 condition_done: 431 Cmd->Subcommands = ParseCommandOp(C_OP_LOWEST); 432 if (Cmd->Subcommands == NULL) 433 { 434 FreeCommand(Cmd); 435 return NULL; 436 } 437 if (_tcsicmp(CurrentToken, _T("else")) == 0) 438 { 439 Cmd->Subcommands->Next = ParseCommandOp(C_OP_LOWEST); 440 if (Cmd->Subcommands->Next == NULL) 441 { 442 FreeCommand(Cmd); 443 return NULL; 444 } 445 } 446 447 return Cmd; 448 } 449 450 /* 451 * Parse a FOR command. 452 * Syntax is: FOR [options] %var IN (list) DO command 453 */ 454 static PARSED_COMMAND *ParseFor(void) 455 { 456 PARSED_COMMAND *Cmd; 457 TCHAR List[CMDLINE_LENGTH]; 458 TCHAR *Pos = List; 459 460 Cmd = cmd_alloc(sizeof(PARSED_COMMAND)); 461 if (!Cmd) 462 { 463 WARN("Cannot allocate memory for Cmd!\n"); 464 ParseError(); 465 return NULL; 466 } 467 memset(Cmd, 0, sizeof(PARSED_COMMAND)); 468 Cmd->Type = C_FOR; 469 470 while (1) 471 { 472 if (_tcsicmp(CurrentToken, _T("/D")) == 0) 473 Cmd->For.Switches |= FOR_DIRS; 474 else if (_tcsicmp(CurrentToken, _T("/F")) == 0) 475 { 476 Cmd->For.Switches |= FOR_F; 477 if (!Cmd->For.Params) 478 { 479 ParseToken(0, STANDARD_SEPS); 480 if (CurrentToken[0] == _T('/') || CurrentToken[0] == _T('%')) 481 break; 482 Cmd->For.Params = cmd_dup(CurrentToken); 483 } 484 } 485 else if (_tcsicmp(CurrentToken, _T("/L")) == 0) 486 Cmd->For.Switches |= FOR_LOOP; 487 else if (_tcsicmp(CurrentToken, _T("/R")) == 0) 488 { 489 Cmd->For.Switches |= FOR_RECURSIVE; 490 if (!Cmd->For.Params) 491 { 492 ParseToken(0, STANDARD_SEPS); 493 if (CurrentToken[0] == _T('/') || CurrentToken[0] == _T('%')) 494 break; 495 StripQuotes(CurrentToken); 496 Cmd->For.Params = cmd_dup(CurrentToken); 497 } 498 } 499 else 500 break; 501 ParseToken(0, STANDARD_SEPS); 502 } 503 504 /* Make sure there aren't two different switches specified 505 * at the same time, unless they're /D and /R */ 506 if ((Cmd->For.Switches & (Cmd->For.Switches - 1)) != 0 507 && Cmd->For.Switches != (FOR_DIRS | FOR_RECURSIVE)) 508 { 509 goto error; 510 } 511 512 /* Variable name should be % and just one other character */ 513 if (CurrentToken[0] != _T('%') || _tcslen(CurrentToken) != 2) 514 goto error; 515 Cmd->For.Variable = CurrentToken[1]; 516 517 ParseToken(0, STANDARD_SEPS); 518 if (_tcsicmp(CurrentToken, _T("in")) != 0) 519 goto error; 520 521 if (ParseToken(_T('('), STANDARD_SEPS) != TOK_BEGIN_BLOCK) 522 goto error; 523 524 while (1) 525 { 526 int Type; 527 528 /* Pretend we're inside a block so the tokenizer will stop on ')' */ 529 InsideBlock++; 530 Type = ParseToken(0, STANDARD_SEPS); 531 InsideBlock--; 532 533 if (Type == TOK_END_BLOCK) 534 break; 535 536 if (Type == TOK_END) 537 { 538 /* Skip past the \n */ 539 ParseChar(); 540 continue; 541 } 542 543 if (Type != TOK_NORMAL) 544 goto error; 545 546 if (Pos != List) 547 *Pos++ = _T(' '); 548 549 if (Pos + _tcslen(CurrentToken) >= &List[CMDLINE_LENGTH]) 550 goto error; 551 Pos = _stpcpy(Pos, CurrentToken); 552 } 553 *Pos = _T('\0'); 554 Cmd->For.List = cmd_dup(List); 555 556 ParseToken(0, STANDARD_SEPS); 557 if (_tcsicmp(CurrentToken, _T("do")) != 0) 558 goto error; 559 560 Cmd->Subcommands = ParseCommandOp(C_OP_LOWEST); 561 if (Cmd->Subcommands == NULL) 562 { 563 FreeCommand(Cmd); 564 return NULL; 565 } 566 567 return Cmd; 568 569 error: 570 FreeCommand(Cmd); 571 ParseError(); 572 return NULL; 573 } 574 575 /* Parse a REM command */ 576 static PARSED_COMMAND *ParseRem(void) 577 { 578 /* Just ignore the rest of the line */ 579 while (CurChar && CurChar != _T('\n')) 580 ParseChar(); 581 return NULL; 582 } 583 584 static DECLSPEC_NOINLINE PARSED_COMMAND *ParseCommandPart(REDIRECTION *RedirList) 585 { 586 TCHAR ParsedLine[CMDLINE_LENGTH]; 587 PARSED_COMMAND *Cmd; 588 PARSED_COMMAND *(*Func)(void); 589 590 TCHAR *Pos = _stpcpy(ParsedLine, CurrentToken) + 1; 591 DWORD_PTR TailOffset = Pos - ParsedLine; 592 593 /* Check for special forms */ 594 if ((Func = ParseFor, _tcsicmp(ParsedLine, _T("for")) == 0) || 595 (Func = ParseIf, _tcsicmp(ParsedLine, _T("if")) == 0) || 596 (Func = ParseRem, _tcsicmp(ParsedLine, _T("rem")) == 0)) 597 { 598 ParseToken(0, STANDARD_SEPS); 599 /* Do special parsing only if it's not followed by /? */ 600 if (_tcscmp(CurrentToken, _T("/?")) != 0) 601 { 602 if (RedirList) 603 { 604 ParseError(); 605 FreeRedirection(RedirList); 606 return NULL; 607 } 608 return Func(); 609 } 610 Pos = _stpcpy(Pos, _T(" /?")); 611 } 612 613 /* Now get the tail */ 614 while (1) 615 { 616 int Type = ParseToken(0, NULL); 617 if (Type == TOK_NORMAL) 618 { 619 if (Pos + _tcslen(CurrentToken) >= &ParsedLine[CMDLINE_LENGTH]) 620 { 621 ParseError(); 622 FreeRedirection(RedirList); 623 return NULL; 624 } 625 Pos = _stpcpy(Pos, CurrentToken); 626 } 627 else if (Type == TOK_REDIRECTION) 628 { 629 if (!ParseRedirection(&RedirList)) 630 return NULL; 631 } 632 else 633 { 634 break; 635 } 636 } 637 *Pos++ = _T('\0'); 638 639 Cmd = cmd_alloc(FIELD_OFFSET(PARSED_COMMAND, Command.First[Pos - ParsedLine])); 640 if (!Cmd) 641 { 642 WARN("Cannot allocate memory for Cmd!\n"); 643 ParseError(); 644 FreeRedirection(RedirList); 645 return NULL; 646 } 647 Cmd->Type = C_COMMAND; 648 Cmd->Next = NULL; 649 Cmd->Subcommands = NULL; 650 Cmd->Redirections = RedirList; 651 memcpy(Cmd->Command.First, ParsedLine, (Pos - ParsedLine) * sizeof(TCHAR)); 652 Cmd->Command.Rest = Cmd->Command.First + TailOffset; 653 return Cmd; 654 } 655 656 static PARSED_COMMAND *ParsePrimary(void) 657 { 658 REDIRECTION *RedirList = NULL; 659 int Type; 660 661 while (IsSeparator(CurChar)) 662 { 663 if (CurChar == _T('\n')) 664 return NULL; 665 ParseChar(); 666 } 667 668 if (!CurChar) 669 return NULL; 670 671 if (CurChar == _T(':')) 672 { 673 /* "Ignore" the rest of the line. 674 * (Line continuations will still be parsed, though.) */ 675 while (ParseToken(0, NULL) != TOK_END) 676 ; 677 return NULL; 678 } 679 680 if (CurChar == _T('@')) 681 { 682 PARSED_COMMAND *Cmd; 683 ParseChar(); 684 Cmd = cmd_alloc(sizeof(PARSED_COMMAND)); 685 if (!Cmd) 686 { 687 WARN("Cannot allocate memory for Cmd!\n"); 688 ParseError(); 689 return NULL; 690 } 691 Cmd->Type = C_QUIET; 692 Cmd->Next = NULL; 693 /* @ acts like a unary operator with low precedence, 694 * so call the top-level parser */ 695 Cmd->Subcommands = ParseCommandOp(C_OP_LOWEST); 696 Cmd->Redirections = NULL; 697 return Cmd; 698 } 699 700 /* Process leading redirections and get the head of the command */ 701 while ((Type = ParseToken(_T('('), STANDARD_SEPS)) == TOK_REDIRECTION) 702 { 703 if (!ParseRedirection(&RedirList)) 704 return NULL; 705 } 706 707 if (Type == TOK_NORMAL) 708 return ParseCommandPart(RedirList); 709 else if (Type == TOK_BEGIN_BLOCK) 710 return ParseBlock(RedirList); 711 else if (Type == TOK_END_BLOCK && !RedirList) 712 return NULL; 713 714 ParseError(); 715 FreeRedirection(RedirList); 716 return NULL; 717 } 718 719 static PARSED_COMMAND *ParseCommandOp(int OpType) 720 { 721 PARSED_COMMAND *Cmd, *Left, *Right; 722 723 if (OpType == C_OP_HIGHEST) 724 Cmd = ParsePrimary(); 725 else 726 Cmd = ParseCommandOp(OpType + 1); 727 728 if (Cmd && !_tcscmp(CurrentToken, OpString[OpType - C_OP_LOWEST])) 729 { 730 Left = Cmd; 731 Right = ParseCommandOp(OpType); 732 if (!Right) 733 { 734 if (!bParseError) 735 { 736 /* & is allowed to have an empty RHS */ 737 if (OpType == C_MULTI) 738 return Left; 739 ParseError(); 740 } 741 FreeCommand(Left); 742 return NULL; 743 } 744 745 Cmd = cmd_alloc(sizeof(PARSED_COMMAND)); 746 if (!Cmd) 747 { 748 WARN("Cannot allocate memory for Cmd!\n"); 749 ParseError(); 750 FreeCommand(Left); 751 FreeCommand(Right); 752 return NULL; 753 } 754 Cmd->Type = OpType; 755 Cmd->Next = NULL; 756 Cmd->Redirections = NULL; 757 Cmd->Subcommands = Left; 758 Left->Next = Right; 759 Right->Next = NULL; 760 } 761 762 return Cmd; 763 } 764 765 PARSED_COMMAND * 766 ParseCommand(LPTSTR Line) 767 { 768 PARSED_COMMAND *Cmd; 769 770 if (Line) 771 { 772 if (!SubstituteVars(Line, ParseLine, _T('%'))) 773 return NULL; 774 bLineContinuations = FALSE; 775 } 776 else 777 { 778 if (!ReadLine(ParseLine, FALSE)) 779 return NULL; 780 bLineContinuations = TRUE; 781 } 782 bParseError = FALSE; 783 ParsePos = ParseLine; 784 CurChar = _T(' '); 785 786 Cmd = ParseCommandOp(C_OP_LOWEST); 787 if (Cmd) 788 { 789 if (CurrentTokenType != TOK_END) 790 ParseError(); 791 if (bParseError) 792 { 793 FreeCommand(Cmd); 794 Cmd = NULL; 795 } 796 bIgnoreEcho = FALSE; 797 } 798 else 799 { 800 bIgnoreEcho = TRUE; 801 } 802 return Cmd; 803 } 804 805 806 /* 807 * Reconstruct a parse tree into text form; used for echoing 808 * batch file commands and FOR instances. 809 */ 810 VOID 811 EchoCommand(PARSED_COMMAND *Cmd) 812 { 813 TCHAR Buf[CMDLINE_LENGTH]; 814 PARSED_COMMAND *Sub; 815 REDIRECTION *Redir; 816 817 switch (Cmd->Type) 818 { 819 case C_COMMAND: 820 if (SubstituteForVars(Cmd->Command.First, Buf)) 821 ConOutPrintf(_T("%s"), Buf); 822 if (SubstituteForVars(Cmd->Command.Rest, Buf)) 823 ConOutPrintf(_T("%s"), Buf); 824 break; 825 case C_QUIET: 826 return; 827 case C_BLOCK: 828 ConOutChar(_T('(')); 829 Sub = Cmd->Subcommands; 830 if (Sub && !Sub->Next) 831 { 832 /* Single-command block: display all on one line */ 833 EchoCommand(Sub); 834 } 835 else if (Sub) 836 { 837 /* Multi-command block: display parenthesis on separate lines */ 838 ConOutChar(_T('\n')); 839 do 840 { 841 EchoCommand(Sub); 842 ConOutChar(_T('\n')); 843 Sub = Sub->Next; 844 } while (Sub); 845 } 846 ConOutChar(_T(')')); 847 break; 848 case C_MULTI: 849 case C_IFFAILURE: 850 case C_IFSUCCESS: 851 case C_PIPE: 852 Sub = Cmd->Subcommands; 853 EchoCommand(Sub); 854 ConOutPrintf(_T(" %s "), OpString[Cmd->Type - C_OP_LOWEST]); 855 EchoCommand(Sub->Next); 856 break; 857 case C_IF: 858 ConOutPrintf(_T("if")); 859 if (Cmd->If.Flags & IFFLAG_IGNORECASE) 860 ConOutPrintf(_T(" /I")); 861 if (Cmd->If.Flags & IFFLAG_NEGATE) 862 ConOutPrintf(_T(" not")); 863 if (Cmd->If.LeftArg && SubstituteForVars(Cmd->If.LeftArg, Buf)) 864 ConOutPrintf(_T(" %s"), Buf); 865 ConOutPrintf(_T(" %s"), IfOperatorString[Cmd->If.Operator]); 866 if (SubstituteForVars(Cmd->If.RightArg, Buf)) 867 ConOutPrintf(_T(" %s "), Buf); 868 Sub = Cmd->Subcommands; 869 EchoCommand(Sub); 870 if (Sub->Next) 871 { 872 ConOutPrintf(_T(" else ")); 873 EchoCommand(Sub->Next); 874 } 875 break; 876 case C_FOR: 877 ConOutPrintf(_T("for")); 878 if (Cmd->For.Switches & FOR_DIRS) ConOutPrintf(_T(" /D")); 879 if (Cmd->For.Switches & FOR_F) ConOutPrintf(_T(" /F")); 880 if (Cmd->For.Switches & FOR_LOOP) ConOutPrintf(_T(" /L")); 881 if (Cmd->For.Switches & FOR_RECURSIVE) ConOutPrintf(_T(" /R")); 882 if (Cmd->For.Params) 883 ConOutPrintf(_T(" %s"), Cmd->For.Params); 884 ConOutPrintf(_T(" %%%c in (%s) do "), Cmd->For.Variable, Cmd->For.List); 885 EchoCommand(Cmd->Subcommands); 886 break; 887 } 888 889 for (Redir = Cmd->Redirections; Redir; Redir = Redir->Next) 890 { 891 if (SubstituteForVars(Redir->Filename, Buf)) 892 { 893 ConOutPrintf(_T(" %c%s%s"), _T('0') + Redir->Number, 894 RedirString[Redir->Mode], Buf); 895 } 896 } 897 } 898 899 /* 900 * "Unparse" a command into a text form suitable for passing to CMD /C. 901 * Used for pipes. This is basically the same thing as EchoCommand, but 902 * writing into a string instead of to standard output. 903 */ 904 TCHAR * 905 Unparse(PARSED_COMMAND *Cmd, TCHAR *Out, TCHAR *OutEnd) 906 { 907 TCHAR Buf[CMDLINE_LENGTH]; 908 PARSED_COMMAND *Sub; 909 REDIRECTION *Redir; 910 911 /* 912 * Since this function has the annoying requirement that it must avoid 913 * overflowing the supplied buffer, define some helper macros to make 914 * this less painful. 915 */ 916 #define CHAR(Char) \ 917 do { \ 918 if (Out == OutEnd) return NULL; \ 919 *Out++ = Char; \ 920 } while (0) 921 #define STRING(String) \ 922 do { \ 923 if (Out + _tcslen(String) > OutEnd) return NULL; \ 924 Out = _stpcpy(Out, String); \ 925 } while (0) 926 #define PRINTF(Format, ...) \ 927 do { \ 928 UINT Len = _sntprintf(Out, OutEnd - Out, Format, __VA_ARGS__); \ 929 if (Len > (UINT)(OutEnd - Out)) return NULL; \ 930 Out += Len; \ 931 } while (0) 932 #define RECURSE(Subcommand) \ 933 do { \ 934 Out = Unparse(Subcommand, Out, OutEnd); \ 935 if (!Out) return NULL; \ 936 } while (0) 937 938 switch (Cmd->Type) 939 { 940 case C_COMMAND: 941 /* This is fragile since there could be special characters, but 942 * Windows doesn't bother escaping them, so for compatibility 943 * we probably shouldn't do it either */ 944 if (!SubstituteForVars(Cmd->Command.First, Buf)) return NULL; 945 STRING(Buf); 946 if (!SubstituteForVars(Cmd->Command.Rest, Buf)) return NULL; 947 STRING(Buf); 948 break; 949 case C_QUIET: 950 CHAR(_T('@')); 951 RECURSE(Cmd->Subcommands); 952 break; 953 case C_BLOCK: 954 CHAR(_T('(')); 955 for (Sub = Cmd->Subcommands; Sub; Sub = Sub->Next) 956 { 957 RECURSE(Sub); 958 if (Sub->Next) 959 CHAR(_T('&')); 960 } 961 CHAR(_T(')')); 962 break; 963 case C_MULTI: 964 case C_IFFAILURE: 965 case C_IFSUCCESS: 966 case C_PIPE: 967 Sub = Cmd->Subcommands; 968 RECURSE(Sub); 969 PRINTF(_T(" %s "), OpString[Cmd->Type - C_OP_LOWEST]); 970 RECURSE(Sub->Next); 971 break; 972 case C_IF: 973 STRING(_T("if")); 974 if (Cmd->If.Flags & IFFLAG_IGNORECASE) 975 STRING(_T(" /I")); 976 if (Cmd->If.Flags & IFFLAG_NEGATE) 977 STRING(_T(" not")); 978 if (Cmd->If.LeftArg && SubstituteForVars(Cmd->If.LeftArg, Buf)) 979 PRINTF(_T(" %s"), Buf); 980 PRINTF(_T(" %s"), IfOperatorString[Cmd->If.Operator]); 981 if (!SubstituteForVars(Cmd->If.RightArg, Buf)) return NULL; 982 PRINTF(_T(" %s "), Buf); 983 Sub = Cmd->Subcommands; 984 RECURSE(Sub); 985 if (Sub->Next) 986 { 987 STRING(_T(" else ")); 988 RECURSE(Sub->Next); 989 } 990 break; 991 case C_FOR: 992 STRING(_T("for")); 993 if (Cmd->For.Switches & FOR_DIRS) STRING(_T(" /D")); 994 if (Cmd->For.Switches & FOR_F) STRING(_T(" /F")); 995 if (Cmd->For.Switches & FOR_LOOP) STRING(_T(" /L")); 996 if (Cmd->For.Switches & FOR_RECURSIVE) STRING(_T(" /R")); 997 if (Cmd->For.Params) 998 PRINTF(_T(" %s"), Cmd->For.Params); 999 PRINTF(_T(" %%%c in (%s) do "), Cmd->For.Variable, Cmd->For.List); 1000 RECURSE(Cmd->Subcommands); 1001 break; 1002 } 1003 1004 for (Redir = Cmd->Redirections; Redir; Redir = Redir->Next) 1005 { 1006 if (!SubstituteForVars(Redir->Filename, Buf)) 1007 return NULL; 1008 PRINTF(_T(" %c%s%s"), _T('0') + Redir->Number, 1009 RedirString[Redir->Mode], Buf); 1010 } 1011 return Out; 1012 } 1013 1014 VOID 1015 FreeCommand(PARSED_COMMAND *Cmd) 1016 { 1017 if (Cmd->Subcommands) 1018 FreeCommand(Cmd->Subcommands); 1019 if (Cmd->Next) 1020 FreeCommand(Cmd->Next); 1021 FreeRedirection(Cmd->Redirections); 1022 if (Cmd->Type == C_IF) 1023 { 1024 cmd_free(Cmd->If.LeftArg); 1025 cmd_free(Cmd->If.RightArg); 1026 } 1027 else if (Cmd->Type == C_FOR) 1028 { 1029 cmd_free(Cmd->For.Params); 1030 cmd_free(Cmd->For.List); 1031 } 1032 cmd_free(Cmd); 1033 } 1034