xref: /reactos/base/shell/cmd/set.c (revision b707be90)
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