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