shell32: Don't forget to set stateMask when calling LVM_GETITEM/LVIF_STATE.
[wine] / programs / cmd / batch.c
1 /*
2  * CMD - Wine-compatible command line interface - batch interface.
3  *
4  * Copyright (C) 1999 D A Pickles
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #include "wcmd.h"
22
23 void WCMD_batch_command (char *line);
24
25 extern int echo_mode;
26 extern char quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
27 extern BATCH_CONTEXT *context;
28 extern DWORD errorlevel;
29
30 /* msdn specified max for Win XP */
31 #define MAXSTRING 8192
32
33 /****************************************************************************
34  * WCMD_batch
35  *
36  * Open and execute a batch file.
37  * On entry *command includes the complete command line beginning with the name
38  * of the batch file (if a CALL command was entered the CALL has been removed).
39  * *file is the name of the file, which might not exist and may not have the
40  * .BAT suffix on. Called is 1 for a CALL, 0 otherwise.
41  *
42  * We need to handle recursion correctly, since one batch program might call another.
43  * So parameters for this batch file are held in a BATCH_CONTEXT structure.
44  */
45
46 void WCMD_batch (char *file, char *command, int called) {
47
48 #define WCMD_BATCH_EXT_SIZE 5
49
50 HANDLE h = INVALID_HANDLE_VALUE;
51 char string[MAXSTRING];
52 char extension_batch[][WCMD_BATCH_EXT_SIZE] = {".bat",".cmd"};
53 char extension_exe[WCMD_BATCH_EXT_SIZE] = ".exe";
54 unsigned int  i;
55 BATCH_CONTEXT *prev_context;
56
57   for(i=0; (i<(sizeof(extension_batch)/WCMD_BATCH_EXT_SIZE)) && 
58            (h == INVALID_HANDLE_VALUE); i++) {
59   strcpy (string, file);
60   CharLower (string);
61     if (strstr (string, extension_batch[i]) == NULL) strcat (string, extension_batch[i]);
62   h = CreateFile (string, GENERIC_READ, FILE_SHARE_READ,
63                   NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
64   }
65   if (h == INVALID_HANDLE_VALUE) {
66     strcpy (string, file);
67     CharLower (string);
68     if (strstr (string, extension_exe) == NULL) strcat (string, extension_exe);
69     h = CreateFile (string, GENERIC_READ, FILE_SHARE_READ,
70                     NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
71     if (h != INVALID_HANDLE_VALUE) {
72       WCMD_run_program (command, 0);
73     } else {
74       SetLastError (ERROR_FILE_NOT_FOUND);
75       WCMD_print_error ();
76     }
77     return;
78   }
79
80 /*
81  *      Create a context structure for this batch file.
82  */
83
84   prev_context = context;
85   context = (BATCH_CONTEXT *)LocalAlloc (LMEM_FIXED, sizeof (BATCH_CONTEXT));
86   context -> h = h;
87   context -> command = command;
88   context -> shift_count = 0;
89   context -> prev_context = prev_context;
90
91 /*
92  *      Work through the file line by line. Specific batch commands are processed here,
93  *      the rest are handled by the main command processor.
94  */
95
96   while (WCMD_fgets (string, sizeof(string), h)) {
97       if (strlen(string) == MAXSTRING -1) {
98           WCMD_output_asis( "Line in Batch processing possibly truncated. Using:\n");
99           WCMD_output_asis( string);
100           WCMD_output_asis( "\n");
101       }
102       if (string[0] != ':') {                      /* Skip over labels */
103           WCMD_batch_command (string);
104       }
105   }
106   CloseHandle (h);
107
108 /*
109  *      If invoked by a CALL, we return to the context of our caller. Otherwise return
110  *      to the caller's caller.
111  */
112
113   LocalFree ((HANDLE)context);
114   if ((prev_context != NULL) && (!called)) {
115     CloseHandle (prev_context -> h);
116     context = prev_context -> prev_context;
117     LocalFree ((HANDLE)prev_context);
118   }
119   else {
120     context = prev_context;
121   }
122 }
123
124 /****************************************************************************
125  * WCMD_batch_command
126  *
127  * Execute one line from a batch file, expanding parameters.
128  */
129
130 void WCMD_batch_command (char *line) {
131
132 DWORD status;
133 char cmd1[MAXSTRING],cmd2[MAXSTRING];
134 char *p, *s, *t;
135 int i;
136
137   /* Get working version of command line */
138   strcpy(cmd1, line);
139
140   /* Expand environment variables in a batch file %{0-9} first  */
141   /*   Then env vars, and if any left (ie use of undefined vars,*/
142   /*   replace with spaces                                      */
143   /* FIXME: Winnt would replace %1%fred%1 with first parm, then */
144   /*   contents of fred, then the digit 1. Would need to remove */
145   /*   ExpandEnvStrings to achieve this                         */
146
147   /* Replace use of %0...%9 */
148   p = cmd1;
149   while ((p = strchr(p, '%'))) {
150     i = *(p+1) - '0';
151     if ((i >= 0) && (i <= 9)) {
152       s = strdup (p+2);
153       t = WCMD_parameter (context -> command, i + context -> shift_count, NULL);
154       strcpy (p, t);
155       strcat (p, s);
156       free (s);
157     } else {
158       p++;
159     }
160   }
161
162   /* Now replace environment variables */
163   status = ExpandEnvironmentStrings(cmd1, cmd2, sizeof(cmd2));
164   if (!status) {
165     WCMD_print_error ();
166     return;
167   }
168
169   /* In a batch program, unknown variables are replace by nothing */
170   /* so remove any remaining %var%                                */
171   p = cmd2;
172   while ((p = strchr(p, '%'))) {
173     s = strchr(p+1, '%');
174     if (!s) {
175       *p=0x00;
176     } else {
177       t = strdup(s+1);
178       strcpy(p, t);
179       free(t);
180     }
181   }
182
183   /* Show prompt before batch line IF echo is on */
184   if (echo_mode && (line[0] != '@')) {
185     WCMD_show_prompt();
186     WCMD_output_asis ( cmd2);
187     WCMD_output_asis ( "\n");
188   }
189
190   WCMD_process_command (cmd2);
191 }
192
193 /*******************************************************************
194  * WCMD_parameter - extract a parameter from a command line.
195  *
196  *      Returns the 'n'th space-delimited parameter on the command line (zero-based).
197  *      Parameter is in static storage overwritten on the next call.
198  *      Parameters in quotes (and brackets) are handled.
199  *      Also returns a pointer to the location of the parameter in the command line.
200  */
201
202 char *WCMD_parameter (char *s, int n, char **where) {
203
204 int i = 0;
205 static char param[MAX_PATH];
206 char *p;
207
208   p = param;
209   while (TRUE) {
210     switch (*s) {
211       case ' ':
212         s++;
213         break;
214       case '"':
215         if (where != NULL) *where = s;
216         s++;
217         while ((*s != '\0') && (*s != '"')) {
218           *p++ = *s++;
219         }
220         if (i == n) {
221           *p = '\0';
222           return param;
223         }
224         if (*s == '"') s++;
225           param[0] = '\0';
226           i++;
227         p = param;
228         break;
229       case '(':
230         if (where != NULL) *where = s;
231         s++;
232         while ((*s != '\0') && (*s != ')')) {
233           *p++ = *s++;
234         }
235         if (i == n) {
236           *p = '\0';
237           return param;
238         }
239         if (*s == ')') s++;
240           param[0] = '\0';
241           i++;
242         p = param;
243         break;
244       case '\0':
245         return param;
246       default:
247         if (where != NULL) *where = s;
248         while ((*s != '\0') && (*s != ' ')) {
249           *p++ = *s++;
250         }
251         if (i == n) {
252           *p = '\0';
253           return param;
254         }
255           param[0] = '\0';
256           i++;
257         p = param;
258     }
259   }
260 }
261
262 /****************************************************************************
263  * WCMD_fgets
264  *
265  * Get one line from a batch file. We can't use the native f* functions because
266  * of the filename syntax differences between DOS and Unix. Also need to lose
267  * the LF (or CRLF) from the line.
268  */
269
270 char *WCMD_fgets (char *s, int n, HANDLE h) {
271
272 DWORD bytes;
273 BOOL status;
274 char *p;
275
276   p = s;
277   do {
278     status = ReadFile (h, s, 1, &bytes, NULL);
279     if ((status == 0) || ((bytes == 0) && (s == p))) return NULL;
280     if (*s == '\n') bytes = 0;
281     else if (*s != '\r') {
282       s++;
283       n--;
284     }
285     *s = '\0';
286   } while ((bytes == 1) && (n > 1));
287   return p;
288 }