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 if (!SetEnvironmentVariable(param, *p ? p : NULL)) 179 { 180 retval = 1; 181 goto Quit; 182 } 183 } 184 else 185 { 186 /* Display all the environment variables with the given prefix */ 187 LPTSTR pOrgParam = param; 188 BOOLEAN bFound = FALSE; 189 BOOLEAN bRestoreSpace; 190 191 /* 192 * Trim the prefix from "special" characters (only when displaying the 193 * environment variables), so that e.g. "SET ,; ,;FOO" will display all 194 * the variables starting by "FOO". 195 * The SET command allows as well to set an environment variable whose name 196 * actually contains these characters (e.g. "SET ,; ,;FOO=42"); however, 197 * by trimming the characters, doing "SET ,; ,;FOO" would not allow seeing 198 * such variables. 199 * Thus, we also save a pointer to the original variable name prefix, that 200 * we will look it up as well below. 201 */ 202 while (_istspace(*param) || *param == _T(',') || *param == _T(';')) 203 ++param; 204 205 /* Just remove the very last space, if present */ 206 p = _tcsrchr(param, _T(' ')); 207 bRestoreSpace = (p != NULL); 208 if (!p) 209 p = param + _tcslen(param); 210 *p = _T('\0'); 211 212 lpEnv = GetEnvironmentStrings(); 213 if (lpEnv) 214 { 215 lpOutput = lpEnv; 216 while (*lpOutput) 217 { 218 /* Look up for both the original and truncated variable name prefix */ 219 if (!_tcsnicmp(lpOutput, pOrgParam, p - pOrgParam) || 220 !_tcsnicmp(lpOutput, param, p - param)) 221 { 222 ConOutPuts(lpOutput); 223 ConOutChar(_T('\n')); 224 bFound = TRUE; 225 } 226 lpOutput += _tcslen(lpOutput) + 1; 227 } 228 FreeEnvironmentStrings(lpEnv); 229 } 230 231 /* Restore the truncated space for correctly 232 * displaying the error message, if any. */ 233 if (bRestoreSpace) 234 *p = _T(' '); 235 236 if (!bFound) 237 { 238 ConErrResPrintf(STRING_SET_ENV_ERROR, param); 239 retval = 1; 240 goto Quit; 241 } 242 } 243 244 Quit: 245 if (BatType != CMD_TYPE) 246 { 247 if (retval != 0) 248 nErrorLevel = retval; 249 } 250 else 251 { 252 nErrorLevel = retval; 253 } 254 255 return retval; 256 } 257 258 static INT 259 ident_len(LPCTSTR p) 260 { 261 LPCTSTR p2 = p; 262 if (__iscsymf(*p)) 263 { 264 ++p2; 265 while (__iscsym(*p2)) 266 ++p2; 267 } 268 return (INT)(p2-p); 269 } 270 271 #define PARSE_IDENT(ident, identlen, p) \ 272 do { \ 273 identlen = ident_len(p); \ 274 ident = (LPTSTR)alloca((identlen + 1) * sizeof(TCHAR)); \ 275 memmove(ident, p, identlen * sizeof(TCHAR)); \ 276 ident[identlen] = 0; \ 277 p += identlen; \ 278 } while (0) 279 280 static INT 281 seta_identval(LPCTSTR ident) 282 { 283 LPCTSTR identVal = GetEnvVarOrSpecial(ident); 284 if (!identVal) 285 return 0; 286 else 287 return _tcstol(identVal, NULL, 0); 288 } 289 290 static BOOL 291 calc(INT* lval, TCHAR op, INT rval) 292 { 293 switch (op) 294 { 295 case '*': 296 *lval *= rval; 297 break; 298 299 case '/': 300 { 301 if (rval == 0) 302 { 303 // FIXME: Localize 304 ConErrPuts(_T("Division by zero error.\n")); 305 nErrorLevel = 0x400023D1; // 1073750993; 306 return FALSE; 307 } 308 *lval /= rval; 309 break; 310 } 311 312 case '%': 313 { 314 if (rval == 0) 315 { 316 // FIXME: Localize 317 ConErrPuts(_T("Division by zero error.\n")); 318 nErrorLevel = 0x400023D1; // 1073750993; 319 return FALSE; 320 } 321 *lval %= rval; 322 break; 323 } 324 325 case '+': 326 *lval += rval; 327 break; 328 case '-': 329 *lval -= rval; 330 break; 331 case '&': 332 *lval &= rval; 333 break; 334 case '^': 335 *lval ^= rval; 336 break; 337 case '|': 338 *lval |= rval; 339 break; 340 341 default: 342 ConErrResPuts(STRING_INVALID_OPERAND); 343 nErrorLevel = 0x400023CE; // 1073750990; 344 return FALSE; 345 } 346 return TRUE; 347 } 348 349 static BOOL 350 seta_stmt(LPCTSTR* p_, INT* result); 351 352 static BOOL 353 seta_unaryTerm(LPCTSTR* p_, INT* result) 354 { 355 LPCTSTR p = *p_; 356 INT rval; 357 358 if (*p == _T('(')) 359 { 360 p = skip_ws(p + 1); 361 if (!seta_stmt(&p, &rval)) 362 return FALSE; 363 if (*p++ != _T(')')) 364 { 365 ConErrResPuts(STRING_EXPECTED_CLOSE_PAREN); 366 nErrorLevel = 0x400023CC; // 1073750988; 367 return FALSE; 368 } 369 *result = rval; 370 } 371 else if (_istdigit(*p)) 372 { 373 errno = 0; 374 rval = _tcstol(p, (LPTSTR*)&p, 0); 375 376 /* Check for overflow / underflow */ 377 if (errno == ERANGE) 378 { 379 // FIXME: Localize 380 ConErrPuts(_T("Invalid number. Numbers are limited to 32-bits of precision.\n")); 381 nErrorLevel = 0x400023D0; // 1073750992; 382 return FALSE; 383 } 384 /* 385 * _tcstol() stopped at the first non-digit character. If it's not a whitespace, 386 * or if it's the start of a possible identifier, this means the number being 387 * interpreted was invalid. 388 */ 389 else if (*p && !_istspace(*p) && __iscsymf(*p)) 390 { 391 // FIXME: Localize 392 ConErrPuts(_T("Invalid number. Numeric constants are either decimal (42), hexadecimal (0x2A), or octal (052).\n")); 393 nErrorLevel = 0x400023CF; // 1073750991; 394 return FALSE; 395 } 396 *result = rval; 397 } 398 else if (__iscsymf(*p)) 399 { 400 LPTSTR ident; 401 INT identlen; 402 PARSE_IDENT(ident, identlen, p); 403 *result = seta_identval(ident); 404 } 405 else 406 { 407 ConErrResPuts(STRING_EXPECTED_NUMBER_OR_VARIABLE); 408 nErrorLevel = 0x400023CD; // 1073750989; 409 return FALSE; 410 } 411 *p_ = skip_ws(p); 412 return TRUE; 413 } 414 415 static BOOL 416 seta_mulTerm(LPCTSTR* p_, INT* result) 417 { 418 LPCTSTR p = *p_; 419 TCHAR op = 0; 420 INT rval; 421 422 if (_tcschr(_T("!~-+"), *p)) 423 { 424 op = *p; 425 p = skip_ws(p + 1); 426 427 if (!seta_mulTerm(&p, &rval)) 428 return FALSE; 429 430 switch (op) 431 { 432 case '!': 433 rval = !rval; 434 break; 435 case '~': 436 rval = ~rval; 437 break; 438 case '-': 439 rval = -rval; 440 break; 441 #if 0 442 case '+': 443 rval = rval; 444 break; 445 #endif 446 } 447 } 448 else 449 { 450 if (!seta_unaryTerm(&p, &rval)) 451 return FALSE; 452 } 453 454 *result = rval; 455 *p_ = p; 456 return TRUE; 457 } 458 459 static BOOL 460 seta_ltorTerm(LPCTSTR* p_, INT* result, LPCTSTR ops, BOOL (*subTerm)(LPCTSTR*,INT*)) 461 { 462 LPCTSTR p = *p_; 463 INT lval; 464 465 /* Evaluate the left-hand side */ 466 if (!subTerm(&p, &lval)) 467 return FALSE; 468 469 while (*p && _tcschr(ops, *p)) 470 { 471 INT rval; 472 TCHAR op = *p; 473 474 p = skip_ws(p + 1); 475 476 /* Evaluate the immediate right-hand side */ 477 if (!subTerm(&p, &rval)) 478 return FALSE; 479 480 /* This becomes the new left-hand side for the next iteration */ 481 if (!calc(&lval, op, rval)) 482 return FALSE; 483 } 484 485 *result = lval; 486 *p_ = p; 487 return TRUE; 488 } 489 490 static BOOL 491 seta_addTerm(LPCTSTR* p_, INT* result) 492 { 493 return seta_ltorTerm(p_, result, _T("*/%"), seta_mulTerm); 494 } 495 496 static BOOL 497 seta_logShiftTerm(LPCTSTR* p_, INT* result) 498 { 499 return seta_ltorTerm(p_, result, _T("+-"), seta_addTerm); 500 } 501 502 static BOOL 503 seta_bitAndTerm(LPCTSTR* p_, INT* result) 504 { 505 LPCTSTR p = *p_; 506 INT lval; 507 508 /* Evaluate the left-hand side */ 509 if (!seta_logShiftTerm(&p, &lval)) 510 return FALSE; 511 512 /* Handle << >> operators */ 513 while (*p && _tcschr(_T("<>"), *p)) 514 { 515 INT rval; 516 TCHAR op = *p; 517 518 /* Check whether the next non-whitespace character is the same operator */ 519 p = skip_ws(p + 1); 520 if (*p != op) 521 break; 522 523 /* Skip it */ 524 p = skip_ws(p + 1); 525 526 /* Evaluate the immediate right-hand side */ 527 if (!seta_logShiftTerm(&p, &rval)) 528 return FALSE; 529 530 /* This becomes the new left-hand side for the next iteration */ 531 switch (op) 532 { 533 case '<': 534 { 535 /* Shift left has to be a positive number, 0-31 otherwise 0 is returned, 536 * which differs from the compiler (for example gcc) so being explicit. */ 537 if (rval < 0 || rval >= (8 * sizeof(lval))) 538 lval = 0; 539 else 540 lval <<= rval; 541 break; 542 } 543 544 case '>': 545 lval >>= rval; 546 break; 547 548 default: 549 ConErrResPuts(STRING_INVALID_OPERAND); 550 nErrorLevel = 0x400023CE; // 1073750990; 551 return FALSE; 552 } 553 } 554 555 *result = lval; 556 *p_ = p; 557 return TRUE; 558 } 559 560 static BOOL 561 seta_bitExclOrTerm(LPCTSTR* p_, INT* result) 562 { 563 return seta_ltorTerm(p_, result, _T("&"), seta_bitAndTerm); 564 } 565 566 static BOOL 567 seta_bitOrTerm(LPCTSTR* p_, INT* result) 568 { 569 return seta_ltorTerm(p_, result, _T("^"), seta_bitExclOrTerm); 570 } 571 572 static BOOL 573 seta_expr(LPCTSTR* p_, INT* result) 574 { 575 return seta_ltorTerm(p_, result, _T("|"), seta_bitOrTerm); 576 } 577 578 static BOOL 579 seta_assignment(LPCTSTR* p_, INT* result) 580 { 581 LPCTSTR p = *p_; 582 LPTSTR ident; 583 TCHAR op = 0; 584 INT identlen, exprval; 585 586 PARSE_IDENT(ident, identlen, p); 587 if (identlen) 588 { 589 p = skip_ws(p); 590 591 /* Handle = assignment */ 592 if (*p == _T('=')) 593 { 594 op = *p; 595 p = skip_ws(p + 1); 596 } 597 /* Handle *= /= %= += -= &= ^= |= assignments */ 598 else if (_tcschr(_T("*/%+-&^|"), *p)) 599 { 600 op = *p; 601 602 /* Find the '=', there may be some spaces before it */ 603 p = skip_ws(p + 1); 604 if (*p != _T('=')) 605 { 606 op = 0; 607 goto evaluate; 608 } 609 610 /* Skip it */ 611 p = skip_ws(p + 1); 612 } 613 /* Handle <<= >>= assignments */ 614 else if (_tcschr(_T("<>"), *p)) 615 { 616 op = *p; 617 618 /* Check whether the next non-whitespace character is the same operator */ 619 p = skip_ws(p + 1); 620 if (*p != op) 621 { 622 op = 0; 623 goto evaluate; 624 } 625 626 /* Find the '=', there may be some spaces before it */ 627 p = skip_ws(p + 1); 628 if (*p != _T('=')) 629 { 630 op = 0; 631 goto evaluate; 632 } 633 634 /* Skip it */ 635 p = skip_ws(p + 1); 636 } 637 } 638 639 evaluate: 640 /* Allow to chain multiple assignments, such as: a=b=1 */ 641 if (ident && op) 642 { 643 INT identval; 644 LPTSTR buf; 645 646 if (!seta_assignment(&p, &exprval)) 647 return FALSE; 648 649 identval = seta_identval(ident); 650 651 switch (op) 652 { 653 /* Handle = assignment */ 654 case '=': 655 identval = exprval; 656 break; 657 658 /* Handle <<= assignment */ 659 case '<': 660 { 661 /* Shift left has to be a positive number, 0-31 otherwise 0 is returned, 662 * which differs from the compiler (for example gcc) so being explicit. */ 663 if (exprval < 0 || exprval >= (8 * sizeof(identval))) 664 identval = 0; 665 else 666 identval <<= exprval; 667 break; 668 } 669 670 /* Handle >>= assignment */ 671 case '>': 672 identval >>= exprval; 673 break; 674 675 /* Other assignments */ 676 default: 677 if (!calc(&identval, op, exprval)) 678 return FALSE; 679 } 680 681 buf = (LPTSTR)alloca(32 * sizeof(TCHAR)); 682 _sntprintf(buf, 32, _T("%i"), identval); 683 SetEnvironmentVariable(ident, buf); // TODO FIXME - check return value 684 exprval = identval; 685 } 686 else 687 { 688 /* Restore p in case we found an identifier but not an operator */ 689 p = *p_; 690 if (!seta_expr(&p, &exprval)) 691 return FALSE; 692 } 693 694 *result = exprval; 695 *p_ = p; 696 return TRUE; 697 } 698 699 static BOOL 700 seta_stmt(LPCTSTR* p_, INT* result) 701 { 702 LPCTSTR p = *p_; 703 INT rval; 704 705 if (!seta_assignment(&p, &rval)) 706 return FALSE; 707 708 /* Loop over each statement */ 709 while (*p == _T(',')) 710 { 711 p = skip_ws(p + 1); 712 713 if (!seta_assignment(&p, &rval)) 714 return FALSE; 715 } 716 717 *result = rval; 718 *p_ = p; 719 return TRUE; 720 } 721 722 static BOOL 723 seta_eval(LPCTSTR p) 724 { 725 INT rval; 726 727 if (!*p) 728 { 729 ConErrResPuts(STRING_SYNTAX_COMMAND_INCORRECT); 730 nErrorLevel = 1; 731 return FALSE; 732 } 733 if (!seta_stmt(&p, &rval)) 734 return FALSE; 735 736 /* If unparsed data remains, fail and bail out */ 737 if (*p) 738 { 739 ConErrResPuts(STRING_SYNTAX_COMMAND_INCORRECT); // Actually syntax error / missing operand. 740 nErrorLevel = 0x400023CE; // 1073750990; 741 return FALSE; 742 } 743 744 /* Echo the result of the evaluation only in interactive (non-batch) mode */ 745 if (!bc) 746 ConOutPrintf(_T("%i"), rval); 747 748 return TRUE; 749 } 750 751 #endif 752