xref: /reactos/base/shell/cmd/call.c (revision 0c2cdcae)
1 /*
2  *  CALL.C - call internal batch command.
3  *
4  *
5  *  History:
6  *
7  *    16 Jul 1998 (Hans B Pufal)
8  *        started.
9  *
10  *    16 Jul 1998 (John P Price)
11  *        Separated commands into individual files.
12  *
13  *    27-Jul-1998 (John P Price <linux-guru@gcfl.net>)
14  *        added config.h include
15  *
16  *    04-Aug-1998 (Hans B Pufal)
17  *        added lines to initialize for pointers (HBP004)  This fixed the
18  *        lock-up that happened sometimes when calling a batch file from
19  *        another batch file.
20  *
21  *    07-Jan-1999 (Eric Kohl)
22  *        Added help text ("call /?") and cleaned up.
23  *
24  *    20-Jan-1999 (Eric Kohl)
25  *        Unicode and redirection safe!
26  *
27  *    02-Apr-2005 (Magnus Olsen <magnus@greatlord.com>)
28  *        Remove all hardcoded strings in En.rc
29  */
30 
31 #include "precomp.h"
32 
33 /* Enable this define for "buggy" Windows' CMD CALL-command compatibility */
34 #define MSCMD_CALL_QUIRKS
35 
36 
37 /*
38  * Perform CALL command.
39  */
40 INT cmd_call(LPTSTR param)
41 {
42     PARSED_COMMAND* Cmd = NULL;
43     BOOL bOldIgnoreParserComments;
44 #ifndef MSCMD_CALL_QUIRKS
45     BOOL bOldHandleContinuations;
46 #else
47     SIZE_T nNumCarets;
48 #endif
49     PTSTR first;
50 
51     TRACE("cmd_call(\'%s\')\n", debugstr_aw(param));
52 
53     if (!_tcsncmp(param, _T("/?"), 2))
54     {
55         ConOutResPaging(TRUE, STRING_CALL_HELP);
56         return 0;
57     }
58 
59     /* Fail if no command or label has been provided */
60     if (*param == _T('\0'))
61         return (nErrorLevel = 1);
62 
63     /* Ignore parser comments (starting with a colon) */
64     bOldIgnoreParserComments = bIgnoreParserComments;
65     bIgnoreParserComments = FALSE;
66 
67 #ifndef MSCMD_CALL_QUIRKS
68     /* Disable parsing of escape carets */
69     bOldHandleContinuations = bHandleContinuations;
70     bHandleContinuations = FALSE;
71     first = param;
72 #else
73     /*
74      * As the original escape carets have been dealt with during the first
75      * command parsing step, the remaining ones need to be doubled so that
76      * they can again survive the new parsing step done below.
77      * But do it the Windows' CMD "buggy" way: **all** carets are doubled,
78      * even those inside quotes. However, this causes carets inside quotes
79      * to remain doubled after the parsing step...
80      */
81 
82     /* Count all the carets */
83     nNumCarets = 0;
84     first = param;
85     while (first)
86     {
87         first = _tcschr(first, _T('^'));
88         if (first)
89         {
90             ++nNumCarets;
91             ++first;
92         }
93     }
94 
95     /* Re-allocate a large enough parameter string if needed */
96     if (nNumCarets > 0)
97     {
98         PTCHAR Src, Dest, End;
99 
100         // TODO: Improvement: Use the scratch TempBuf if the string is not too long.
101         first = cmd_alloc((_tcslen(param) + nNumCarets + 1) * sizeof(TCHAR));
102         if (!first)
103         {
104             WARN("Cannot allocate memory for new CALL parameter string!\n");
105             error_out_of_memory();
106             return (nErrorLevel = 1);
107         }
108 
109         /* Copy the parameter string and double the escape carets */
110         Src = param;
111         Dest = first;
112         while (*Src)
113         {
114             if (*Src != _T('^'))
115             {
116                 /* Copy everything before the next caret (or the end of the string) */
117                 End = _tcschr(Src, _T('^'));
118                 if (!End)
119                     End = Src + _tcslen(Src);
120                 memcpy(Dest, Src, (End - Src) * sizeof(TCHAR));
121                 Dest += End - Src;
122                 Src = End;
123                 continue;
124             }
125 
126             /* Copy the original caret and double it */
127             *Dest++ = *Src;
128             *Dest++ = *Src++;
129         }
130         *Dest = _T('\0');
131     }
132     else
133     {
134         first = param;
135     }
136 #endif
137 
138     /*
139      * Reparse the CALL parameter string as a command.
140      * Note that this will trigger a second round of %-variable substitutions.
141      */
142     Cmd = ParseCommand(first);
143 
144     /* Restore the global parsing state */
145 #ifndef MSCMD_CALL_QUIRKS
146     bHandleContinuations = bOldHandleContinuations;
147 #endif
148     bIgnoreParserComments = bOldIgnoreParserComments;
149 
150     /*
151      * If no command is there, yet no error occurred, this means that
152      * a whitespace label was given. Do not consider this as a failure.
153      */
154     if (!Cmd && !bParseError)
155     {
156 #ifdef MSCMD_CALL_QUIRKS
157         if (first != param)
158             cmd_free(first);
159 #endif
160         return (nErrorLevel = 0);
161     }
162 
163     /* Reset bParseError so as to continue running afterwards */
164     bParseError = FALSE;
165 
166     /*
167      * Otherwise, if no command is there because a parse error occurred,
168      * or if this an unsupported command: not a standard one, including
169      * FOR and IF, fail and bail out.
170      */
171     if (!Cmd || (Cmd->Type == C_FOR) || (Cmd->Type == C_IF) ||
172         ((Cmd->Type != C_COMMAND) && (Cmd->Type != C_REM)))
173     {
174         ConErrResPrintf(STRING_ERROR_UNEXPECTED, first);
175 
176 #ifdef MSCMD_CALL_QUIRKS
177         if (first != param)
178             cmd_free(first);
179 #endif
180         if (Cmd) FreeCommand(Cmd);
181         return (nErrorLevel = 1);
182     }
183 
184 #ifdef MSCMD_CALL_QUIRKS
185     if (first != param)
186         cmd_free(first);
187 #endif
188 
189     first = Cmd->Command.First;
190     param = Cmd->Command.Rest;
191 
192     /* "CALL :label args ..." - Call a subroutine of the current batch file, only if extensions are enabled */
193     if (bEnableExtensions && (*first == _T(':')))
194     {
195         INT ret;
196 
197         /* A batch context must be present */
198         if (!bc)
199         {
200             ConErrResPuts(STRING_ERROR_CALL_BAD_LABEL);
201             FreeCommand(Cmd);
202             return (nErrorLevel = 1);
203         }
204 
205         ret = Batch(bc->BatchFilePath, first, param, NULL);
206         nErrorLevel = (ret != 0 ? ret : nErrorLevel);
207     }
208     else
209     {
210         nErrorLevel = DoCommand(first, param, NULL);
211     }
212 
213     FreeCommand(Cmd);
214     return nErrorLevel;
215 }
216 
217 /* EOF */
218