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
skip_ws(LPCTSTR p)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
GetQuotedString(TCHAR * p)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
cmd_set(LPTSTR param)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
ident_len(LPCTSTR p)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
seta_identval(LPCTSTR ident)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
calc(INT * lval,TCHAR op,INT rval)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
seta_unaryTerm(LPCTSTR * p_,INT * result)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
seta_mulTerm(LPCTSTR * p_,INT * result)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
seta_ltorTerm(LPCTSTR * p_,INT * result,LPCTSTR ops,BOOL (* subTerm)(LPCTSTR *,INT *))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
seta_addTerm(LPCTSTR * p_,INT * result)494 seta_addTerm(LPCTSTR* p_, INT* result)
495 {
496 return seta_ltorTerm(p_, result, _T("*/%"), seta_mulTerm);
497 }
498
499 static BOOL
seta_logShiftTerm(LPCTSTR * p_,INT * result)500 seta_logShiftTerm(LPCTSTR* p_, INT* result)
501 {
502 return seta_ltorTerm(p_, result, _T("+-"), seta_addTerm);
503 }
504
505 static BOOL
seta_bitAndTerm(LPCTSTR * p_,INT * result)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
seta_bitExclOrTerm(LPCTSTR * p_,INT * result)564 seta_bitExclOrTerm(LPCTSTR* p_, INT* result)
565 {
566 return seta_ltorTerm(p_, result, _T("&"), seta_bitAndTerm);
567 }
568
569 static BOOL
seta_bitOrTerm(LPCTSTR * p_,INT * result)570 seta_bitOrTerm(LPCTSTR* p_, INT* result)
571 {
572 return seta_ltorTerm(p_, result, _T("^"), seta_bitExclOrTerm);
573 }
574
575 static BOOL
seta_expr(LPCTSTR * p_,INT * result)576 seta_expr(LPCTSTR* p_, INT* result)
577 {
578 return seta_ltorTerm(p_, result, _T("|"), seta_bitOrTerm);
579 }
580
581 static BOOL
seta_assignment(LPCTSTR * p_,INT * result)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
seta_stmt(LPCTSTR * p_,INT * result)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
seta_eval(LPCTSTR p)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