1 /* 2 * SET.C - set internal command. 3 * 4 * 5 * History: 6 * 7 * 06/14/97 (Tim Norman) 8 * changed static var in set() to a cmd_alloc'd space to pass to putenv. 9 * need to find a better way to do this, since it seems it is wasting 10 * memory when variables are redefined. 11 * 12 * 07/08/1998 (John P. Price) 13 * removed call to show_environment in set command. 14 * moved test for syntax before allocating memory in set command. 15 * misc clean up and optimization. 16 * 17 * 27-Jul-1998 (John P Price <linux-guru@gcfl.net>) 18 * added config.h include 19 * 20 * 28-Jul-1998 (John P Price <linux-guru@gcfl.net>) 21 * added set_env function to set env. variable without needing set command 22 * 23 * 09-Dec-1998 (Eric Kohl) 24 * Added help text ("/?"). 25 * 26 * 24-Jan-1999 (Eric Kohl) 27 * Fixed Win32 environment handling. 28 * Unicode and redirection safe! 29 * 30 * 25-Feb-1999 (Eric Kohl) 31 * Fixed little bug. 32 * 33 * 30-Apr-2005 (Magnus Olsen <magnus@greatlord.com>) 34 * Remove all hardcoded strings in En.rc 35 */ 36 37 #include "precomp.h" 38 39 #ifdef INCLUDE_CMD_SET 40 41 /* Initial size of environment variable buffer */ 42 #define ENV_BUFFER_SIZE 1024 43 44 static BOOL 45 seta_eval(LPCTSTR expr); 46 47 static LPCTSTR 48 skip_ws(LPCTSTR p) 49 { 50 while (*p && *p <= _T(' ')) 51 ++p; 52 return p; 53 } 54 55 /* Used to check for and handle: 56 * SET "var=value", SET /P "var=prompt", and SET /P var="prompt" */ 57 static LPTSTR 58 GetQuotedString(TCHAR *p) 59 { 60 TCHAR *end; 61 if (*p == _T('"')) 62 { 63 p = (LPTSTR)skip_ws(p + 1); 64 /* If a matching quote is found, truncate the string */ 65 end = _tcsrchr(p, _T('"')); 66 if (end) 67 *end = _T('\0'); 68 } 69 return p; 70 } 71 72 INT cmd_set(LPTSTR param) 73 { 74 INT retval = 0; 75 LPTSTR p; 76 LPTSTR lpEnv; 77 LPTSTR lpOutput; 78 79 if (!_tcsncmp(param, _T("/?"), 2)) 80 { 81 ConOutResPaging(TRUE,STRING_SET_HELP); 82 return 0; 83 } 84 85 param = (LPTSTR)skip_ws(param); 86 87 /* If no parameters, show the environment */ 88 if (param[0] == _T('\0')) 89 { 90 lpEnv = (LPTSTR)GetEnvironmentStrings(); 91 if (lpEnv) 92 { 93 lpOutput = lpEnv; 94 while (*lpOutput) 95 { 96 /* Do not display the special '=X:' environment variables */ 97 if (*lpOutput != _T('=')) 98 { 99 ConOutPuts(lpOutput); 100 ConOutChar(_T('\n')); 101 } 102 lpOutput += _tcslen(lpOutput) + 1; 103 } 104 FreeEnvironmentStrings(lpEnv); 105 } 106 107 retval = 0; 108 goto Quit; 109 } 110 111 /* The /A does *NOT* have to be followed by a whitespace */ 112 if (!_tcsnicmp(param, _T("/A"), 2)) 113 { 114 BOOL Success; 115 116 /* Save error level since seta_eval() modifies it, as 117 * we need to set it later according to specific rules. */ 118 INT nOldErrorLevel = nErrorLevel; 119 120 StripQuotes(param); 121 Success = seta_eval(skip_ws(param + 2)); 122 if (!Success) 123 { 124 #if 0 125 /* Might seem random but this is what windows xp does -- This is a message ID */ 126 retval = 9165; 127 #endif 128 retval = nErrorLevel; 129 nErrorLevel = nOldErrorLevel; 130 } 131 else 132 { 133 retval = 0; 134 } 135 goto Quit; 136 } 137 138 if (!_tcsnicmp(param, _T("/P"), 2)) 139 { 140 TCHAR value[1023]; 141 param = GetQuotedString((LPTSTR)skip_ws(param + 2)); 142 p = _tcschr(param, _T('=')); 143 if (!p) 144 { 145 ConErrResPuts(STRING_SYNTAX_COMMAND_INCORRECT); 146 retval = 1; 147 goto Quit; 148 } 149 150 *p++ = _T('\0'); 151 ConOutPrintf(_T("%s"), GetQuotedString(p)); 152 ConInString(value, ARRAYSIZE(value)); 153 154 if (!*value || !SetEnvironmentVariable(param, value)) 155 { 156 retval = 1; 157 goto Quit; 158 } 159 retval = 0; 160 goto Quit; 161 } 162 163 param = GetQuotedString(param); 164 165 p = _tcschr(param, _T('=')); 166 if (p) 167 { 168 /* Set or remove the environment variable */ 169 if (p == param) 170 { 171 /* Handle set =val case */ 172 ConErrResPuts(STRING_SYNTAX_COMMAND_INCORRECT); 173 retval = 1; 174 goto Quit; 175 } 176 177 *p++ = _T('\0'); 178 179 #ifdef FEATURE_DYNAMIC_TRACE 180 /* Check for dynamic TRACE ON/OFF */ 181 if (!_tcsicmp(param, _T("CMDTRACE"))) 182 g_bDynamicTrace = !_tcsicmp(p, _T("ON")); 183 #endif 184 185 if (!SetEnvironmentVariable(param, *p ? p : NULL)) 186 { 187 retval = 1; 188 goto Quit; 189 } 190 } 191 else 192 { 193 /* Display all the environment variables with the given prefix */ 194 LPTSTR pOrgParam = param; 195 BOOLEAN bFound = FALSE; 196 BOOLEAN bRestoreSpace; 197 198 /* 199 * Trim the prefix from "special" characters (only when displaying the 200 * environment variables), so that e.g. "SET ,; ,;FOO" will display all 201 * the variables starting by "FOO". 202 * The SET command allows as well to set an environment variable whose name 203 * actually contains these characters (e.g. "SET ,; ,;FOO=42"); however, 204 * by trimming the characters, doing "SET ,; ,;FOO" would not allow seeing 205 * such variables. 206 * Thus, we also save a pointer to the original variable name prefix, that 207 * we will look it up as well below. 208 */ 209 while (_istspace(*param) || *param == _T(',') || *param == _T(';')) 210 ++param; 211 212 /* Just remove the very last space, if present */ 213 p = _tcsrchr(param, _T(' ')); 214 bRestoreSpace = (p != NULL); 215 if (!p) 216 p = param + _tcslen(param); 217 *p = _T('\0'); 218 219 lpEnv = GetEnvironmentStrings(); 220 if (lpEnv) 221 { 222 lpOutput = lpEnv; 223 while (*lpOutput) 224 { 225 /* Look up for both the original and truncated variable name prefix */ 226 if (!_tcsnicmp(lpOutput, pOrgParam, p - pOrgParam) || 227 !_tcsnicmp(lpOutput, param, p - param)) 228 { 229 ConOutPuts(lpOutput); 230 ConOutChar(_T('\n')); 231 bFound = TRUE; 232 } 233 lpOutput += _tcslen(lpOutput) + 1; 234 } 235 FreeEnvironmentStrings(lpEnv); 236 } 237 238 /* Restore the truncated space for correctly 239 * displaying the error message, if any. */ 240 if (bRestoreSpace) 241 *p = _T(' '); 242 243 if (!bFound) 244 { 245 ConErrResPrintf(STRING_SET_ENV_ERROR, param); 246 retval = 1; 247 goto Quit; 248 } 249 } 250 251 Quit: 252 if (BatType != CMD_TYPE) 253 { 254 if (retval != 0) 255 nErrorLevel = retval; 256 } 257 else 258 { 259 nErrorLevel = retval; 260 } 261 262 return retval; 263 } 264 265 static INT 266 ident_len(LPCTSTR p) 267 { 268 LPCTSTR p2 = p; 269 if (__iscsymf(*p)) 270 { 271 ++p2; 272 while (__iscsym(*p2)) 273 ++p2; 274 } 275 return (INT)(p2-p); 276 } 277 278 #define PARSE_IDENT(ident, identlen, p) \ 279 do { \ 280 identlen = ident_len(p); \ 281 ident = (LPTSTR)_alloca((identlen + 1) * sizeof(TCHAR)); \ 282 memmove(ident, p, identlen * sizeof(TCHAR)); \ 283 ident[identlen] = 0; \ 284 p += identlen; \ 285 } while (0) 286 287 static INT 288 seta_identval(LPCTSTR ident) 289 { 290 LPCTSTR identVal = GetEnvVarOrSpecial(ident); 291 if (!identVal) 292 return 0; 293 else 294 return _tcstol(identVal, NULL, 0); 295 } 296 297 static BOOL 298 calc(INT* lval, TCHAR op, INT rval) 299 { 300 switch (op) 301 { 302 case '*': 303 *lval *= rval; 304 break; 305 306 case '/': 307 { 308 if (rval == 0) 309 { 310 ConErrResPuts(STRING_ERROR_DIVISION_BY_ZERO); 311 nErrorLevel = 0x400023D1; // 1073750993; 312 return FALSE; 313 } 314 *lval /= rval; 315 break; 316 } 317 318 case '%': 319 { 320 if (rval == 0) 321 { 322 ConErrResPuts(STRING_ERROR_DIVISION_BY_ZERO); 323 nErrorLevel = 0x400023D1; // 1073750993; 324 return FALSE; 325 } 326 *lval %= rval; 327 break; 328 } 329 330 case '+': 331 *lval += rval; 332 break; 333 case '-': 334 *lval -= rval; 335 break; 336 case '&': 337 *lval &= rval; 338 break; 339 case '^': 340 *lval ^= rval; 341 break; 342 case '|': 343 *lval |= rval; 344 break; 345 346 default: 347 ConErrResPuts(STRING_INVALID_OPERAND); 348 nErrorLevel = 0x400023CE; // 1073750990; 349 return FALSE; 350 } 351 return TRUE; 352 } 353 354 static BOOL 355 seta_stmt(LPCTSTR* p_, INT* result); 356 357 static BOOL 358 seta_unaryTerm(LPCTSTR* p_, INT* result) 359 { 360 LPCTSTR p = *p_; 361 INT rval; 362 363 if (*p == _T('(')) 364 { 365 p = skip_ws(p + 1); 366 if (!seta_stmt(&p, &rval)) 367 return FALSE; 368 if (*p++ != _T(')')) 369 { 370 ConErrResPuts(STRING_EXPECTED_CLOSE_PAREN); 371 nErrorLevel = 0x400023CC; // 1073750988; 372 return FALSE; 373 } 374 *result = rval; 375 } 376 else if (_istdigit(*p)) 377 { 378 errno = 0; 379 rval = _tcstol(p, (LPTSTR*)&p, 0); 380 381 /* Check for overflow / underflow */ 382 if (errno == ERANGE) 383 { 384 ConErrResPuts(STRING_ERROR_INVALID_NUMBER2); 385 nErrorLevel = 0x400023D0; // 1073750992; 386 return FALSE; 387 } 388 /* 389 * _tcstol() stopped at the first non-digit character. If it's not a whitespace, 390 * or if it's the start of a possible identifier, this means the number being 391 * interpreted was invalid. 392 */ 393 else if (*p && !_istspace(*p) && __iscsymf(*p)) 394 { 395 ConErrResPuts(STRING_ERROR_INVALID_NUMBER1); 396 nErrorLevel = 0x400023CF; // 1073750991; 397 return FALSE; 398 } 399 *result = rval; 400 } 401 else if (__iscsymf(*p)) 402 { 403 LPTSTR ident; 404 INT identlen; 405 PARSE_IDENT(ident, identlen, p); 406 *result = seta_identval(ident); 407 } 408 else 409 { 410 ConErrResPuts(STRING_EXPECTED_NUMBER_OR_VARIABLE); 411 nErrorLevel = 0x400023CD; // 1073750989; 412 return FALSE; 413 } 414 *p_ = skip_ws(p); 415 return TRUE; 416 } 417 418 static BOOL 419 seta_mulTerm(LPCTSTR* p_, INT* result) 420 { 421 LPCTSTR p = *p_; 422 TCHAR op = 0; 423 INT rval; 424 425 if (_tcschr(_T("!~-+"), *p)) 426 { 427 op = *p; 428 p = skip_ws(p + 1); 429 430 if (!seta_mulTerm(&p, &rval)) 431 return FALSE; 432 433 switch (op) 434 { 435 case '!': 436 rval = !rval; 437 break; 438 case '~': 439 rval = ~rval; 440 break; 441 case '-': 442 rval = -rval; 443 break; 444 #if 0 445 case '+': 446 rval = rval; 447 break; 448 #endif 449 } 450 } 451 else 452 { 453 if (!seta_unaryTerm(&p, &rval)) 454 return FALSE; 455 } 456 457 *result = rval; 458 *p_ = p; 459 return TRUE; 460 } 461 462 static BOOL 463 seta_ltorTerm(LPCTSTR* p_, INT* result, LPCTSTR ops, BOOL (*subTerm)(LPCTSTR*,INT*)) 464 { 465 LPCTSTR p = *p_; 466 INT lval; 467 468 /* Evaluate the left-hand side */ 469 if (!subTerm(&p, &lval)) 470 return FALSE; 471 472 while (*p && _tcschr(ops, *p)) 473 { 474 INT rval; 475 TCHAR op = *p; 476 477 p = skip_ws(p + 1); 478 479 /* Evaluate the immediate right-hand side */ 480 if (!subTerm(&p, &rval)) 481 return FALSE; 482 483 /* This becomes the new left-hand side for the next iteration */ 484 if (!calc(&lval, op, rval)) 485 return FALSE; 486 } 487 488 *result = lval; 489 *p_ = p; 490 return TRUE; 491 } 492 493 static BOOL 494 seta_addTerm(LPCTSTR* p_, INT* result) 495 { 496 return seta_ltorTerm(p_, result, _T("*/%"), seta_mulTerm); 497 } 498 499 static BOOL 500 seta_logShiftTerm(LPCTSTR* p_, INT* result) 501 { 502 return seta_ltorTerm(p_, result, _T("+-"), seta_addTerm); 503 } 504 505 static BOOL 506 seta_bitAndTerm(LPCTSTR* p_, INT* result) 507 { 508 LPCTSTR p = *p_; 509 INT lval; 510 511 /* Evaluate the left-hand side */ 512 if (!seta_logShiftTerm(&p, &lval)) 513 return FALSE; 514 515 /* Handle << >> operators */ 516 while (*p && _tcschr(_T("<>"), *p)) 517 { 518 INT rval; 519 TCHAR op = *p; 520 521 /* Check whether the next non-whitespace character is the same operator */ 522 p = skip_ws(p + 1); 523 if (*p != op) 524 break; 525 526 /* Skip it */ 527 p = skip_ws(p + 1); 528 529 /* Evaluate the immediate right-hand side */ 530 if (!seta_logShiftTerm(&p, &rval)) 531 return FALSE; 532 533 /* This becomes the new left-hand side for the next iteration */ 534 switch (op) 535 { 536 case '<': 537 { 538 /* Shift left has to be a positive number, 0-31 otherwise 0 is returned, 539 * which differs from the compiler (for example gcc) so being explicit. */ 540 if (rval < 0 || rval >= (8 * sizeof(lval))) 541 lval = 0; 542 else 543 lval <<= rval; 544 break; 545 } 546 547 case '>': 548 lval >>= rval; 549 break; 550 551 default: 552 ConErrResPuts(STRING_INVALID_OPERAND); 553 nErrorLevel = 0x400023CE; // 1073750990; 554 return FALSE; 555 } 556 } 557 558 *result = lval; 559 *p_ = p; 560 return TRUE; 561 } 562 563 static BOOL 564 seta_bitExclOrTerm(LPCTSTR* p_, INT* result) 565 { 566 return seta_ltorTerm(p_, result, _T("&"), seta_bitAndTerm); 567 } 568 569 static BOOL 570 seta_bitOrTerm(LPCTSTR* p_, INT* result) 571 { 572 return seta_ltorTerm(p_, result, _T("^"), seta_bitExclOrTerm); 573 } 574 575 static BOOL 576 seta_expr(LPCTSTR* p_, INT* result) 577 { 578 return seta_ltorTerm(p_, result, _T("|"), seta_bitOrTerm); 579 } 580 581 static BOOL 582 seta_assignment(LPCTSTR* p_, INT* result) 583 { 584 LPCTSTR p = *p_; 585 LPTSTR ident; 586 TCHAR op = 0; 587 INT identlen, exprval; 588 589 PARSE_IDENT(ident, identlen, p); 590 if (identlen) 591 { 592 p = skip_ws(p); 593 594 /* Handle = assignment */ 595 if (*p == _T('=')) 596 { 597 op = *p; 598 p = skip_ws(p + 1); 599 } 600 /* Handle *= /= %= += -= &= ^= |= assignments */ 601 else if (_tcschr(_T("*/%+-&^|"), *p)) 602 { 603 op = *p; 604 605 /* Find the '=', there may be some spaces before it */ 606 p = skip_ws(p + 1); 607 if (*p != _T('=')) 608 { 609 op = 0; 610 goto evaluate; 611 } 612 613 /* Skip it */ 614 p = skip_ws(p + 1); 615 } 616 /* Handle <<= >>= assignments */ 617 else if (_tcschr(_T("<>"), *p)) 618 { 619 op = *p; 620 621 /* Check whether the next non-whitespace character is the same operator */ 622 p = skip_ws(p + 1); 623 if (*p != op) 624 { 625 op = 0; 626 goto evaluate; 627 } 628 629 /* Find the '=', there may be some spaces before it */ 630 p = skip_ws(p + 1); 631 if (*p != _T('=')) 632 { 633 op = 0; 634 goto evaluate; 635 } 636 637 /* Skip it */ 638 p = skip_ws(p + 1); 639 } 640 } 641 642 evaluate: 643 /* Allow to chain multiple assignments, such as: a=b=1 */ 644 if (ident && op) 645 { 646 INT identval; 647 LPTSTR buf; 648 649 if (!seta_assignment(&p, &exprval)) 650 return FALSE; 651 652 identval = seta_identval(ident); 653 654 switch (op) 655 { 656 /* Handle = assignment */ 657 case '=': 658 identval = exprval; 659 break; 660 661 /* Handle <<= assignment */ 662 case '<': 663 { 664 /* Shift left has to be a positive number, 0-31 otherwise 0 is returned, 665 * which differs from the compiler (for example gcc) so being explicit. */ 666 if (exprval < 0 || exprval >= (8 * sizeof(identval))) 667 identval = 0; 668 else 669 identval <<= exprval; 670 break; 671 } 672 673 /* Handle >>= assignment */ 674 case '>': 675 identval >>= exprval; 676 break; 677 678 /* Other assignments */ 679 default: 680 if (!calc(&identval, op, exprval)) 681 return FALSE; 682 } 683 684 buf = (LPTSTR)_alloca(32 * sizeof(TCHAR)); 685 _sntprintf(buf, 32, _T("%i"), identval); 686 SetEnvironmentVariable(ident, buf); // TODO FIXME - check return value 687 exprval = identval; 688 } 689 else 690 { 691 /* Restore p in case we found an identifier but not an operator */ 692 p = *p_; 693 if (!seta_expr(&p, &exprval)) 694 return FALSE; 695 } 696 697 *result = exprval; 698 *p_ = p; 699 return TRUE; 700 } 701 702 static BOOL 703 seta_stmt(LPCTSTR* p_, INT* result) 704 { 705 LPCTSTR p = *p_; 706 INT rval; 707 708 if (!seta_assignment(&p, &rval)) 709 return FALSE; 710 711 /* Loop over each statement */ 712 while (*p == _T(',')) 713 { 714 p = skip_ws(p + 1); 715 716 if (!seta_assignment(&p, &rval)) 717 return FALSE; 718 } 719 720 *result = rval; 721 *p_ = p; 722 return TRUE; 723 } 724 725 static BOOL 726 seta_eval(LPCTSTR p) 727 { 728 INT rval; 729 730 if (!*p) 731 { 732 ConErrResPuts(STRING_SYNTAX_COMMAND_INCORRECT); 733 nErrorLevel = 1; 734 return FALSE; 735 } 736 if (!seta_stmt(&p, &rval)) 737 return FALSE; 738 739 /* If unparsed data remains, fail and bail out */ 740 if (*p) 741 { 742 ConErrResPuts(STRING_SYNTAX_COMMAND_INCORRECT); // Actually syntax error / missing operand. 743 nErrorLevel = 0x400023CE; // 1073750990; 744 return FALSE; 745 } 746 747 /* Echo the result of the evaluation only in interactive (non-batch) mode */ 748 if (!bc) 749 ConOutPrintf(_T("%i"), rval); 750 751 return TRUE; 752 } 753 754 #endif 755