rcprt4/tests: Don't crash on NT4 or lower.
[wine/multimedia.git] / programs / cmd / batch.c
blob28744d4391df32562f245eba05f305806e8448e2
1 /*
2 * CMD - Wine-compatible command line interface - batch interface.
4 * Copyright (C) 1999 D A Pickles
5 * Copyright (C) 2007 J Edmeades
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 #include "wcmd.h"
23 #include "wine/debug.h"
25 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
27 extern int echo_mode;
28 extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
29 extern BATCH_CONTEXT *context;
30 extern DWORD errorlevel;
32 /****************************************************************************
33 * WCMD_batch
35 * Open and execute a batch file.
36 * On entry *command includes the complete command line beginning with the name
37 * of the batch file (if a CALL command was entered the CALL has been removed).
38 * *file is the name of the file, which might not exist and may not have the
39 * .BAT suffix on. Called is 1 for a CALL, 0 otherwise.
41 * We need to handle recursion correctly, since one batch program might call another.
42 * So parameters for this batch file are held in a BATCH_CONTEXT structure.
44 * To support call within the same batch program, another input parameter is
45 * a label to goto once opened.
48 void WCMD_batch (WCHAR *file, WCHAR *command, int called, WCHAR *startLabel, HANDLE pgmHandle) {
50 #define WCMD_BATCH_EXT_SIZE 5
52 HANDLE h = INVALID_HANDLE_VALUE;
53 WCHAR string[MAXSTRING];
54 static const WCHAR extension_batch[][WCMD_BATCH_EXT_SIZE] = {{'.','b','a','t','\0'},
55 {'.','c','m','d','\0'}};
56 static const WCHAR extension_exe[WCMD_BATCH_EXT_SIZE] = {'.','e','x','e','\0'};
57 unsigned int i;
58 BATCH_CONTEXT *prev_context;
60 if (startLabel == NULL) {
61 for(i=0; (i<sizeof(extension_batch)/(WCMD_BATCH_EXT_SIZE * sizeof(WCHAR))) &&
62 (h == INVALID_HANDLE_VALUE); i++) {
63 strcpyW (string, file);
64 CharLowerW (string);
65 if (strstrW (string, extension_batch[i]) == NULL) strcatW (string, extension_batch[i]);
66 h = CreateFileW (string, GENERIC_READ, FILE_SHARE_READ,
67 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
69 if (h == INVALID_HANDLE_VALUE) {
70 strcpyW (string, file);
71 CharLowerW (string);
72 if (strstrW (string, extension_exe) == NULL) strcatW (string, extension_exe);
73 if (GetFileAttributesW (string) != INVALID_FILE_ATTRIBUTES) {
74 WCMD_run_program (command, 0);
75 } else {
76 SetLastError (ERROR_FILE_NOT_FOUND);
77 WCMD_print_error ();
79 return;
81 } else {
82 DuplicateHandle(GetCurrentProcess(), pgmHandle,
83 GetCurrentProcess(), &h,
84 0, FALSE, DUPLICATE_SAME_ACCESS);
88 * Create a context structure for this batch file.
91 prev_context = context;
92 context = LocalAlloc (LMEM_FIXED, sizeof (BATCH_CONTEXT));
93 context -> h = h;
94 context->batchfileW = WCMD_strdupW(string);
95 context -> command = command;
96 memset(context -> shift_count, 0x00, sizeof(context -> shift_count));
97 context -> prev_context = prev_context;
98 context -> skip_rest = FALSE;
100 /* If processing a call :label, 'goto' the label in question */
101 if (startLabel) {
102 strcpyW(param1, startLabel);
103 WCMD_goto(NULL);
107 * Work through the file line by line. Specific batch commands are processed here,
108 * the rest are handled by the main command processor.
111 while (context -> skip_rest == FALSE) {
112 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
113 if (WCMD_ReadAndParseLine(NULL, &toExecute, h) == NULL)
114 break;
115 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
116 WCMD_free_commands(toExecute);
117 toExecute = NULL;
119 CloseHandle (h);
122 * If invoked by a CALL, we return to the context of our caller. Otherwise return
123 * to the caller's caller.
126 HeapFree(GetProcessHeap(), 0, context->batchfileW);
127 LocalFree (context);
128 if ((prev_context != NULL) && (!called)) {
129 prev_context -> skip_rest = TRUE;
130 context = prev_context;
132 context = prev_context;
135 /*******************************************************************
136 * WCMD_parameter - extract a parameter from a command line.
138 * Returns the 'n'th delimited parameter on the command line (zero-based).
139 * Parameter is in static storage overwritten on the next call.
140 * Parameters in quotes (and brackets) are handled.
141 * Also returns a pointer to the location of the parameter in the command line.
144 WCHAR *WCMD_parameter (WCHAR *s, int n, WCHAR **where) {
146 int i = 0;
147 static WCHAR param[MAX_PATH];
148 WCHAR *p;
150 if (where != NULL) *where = NULL;
151 p = param;
152 while (TRUE) {
153 switch (*s) {
154 case ' ': /* Skip leading spaces */
155 case '\t': /* Treat tabs as spaces */
156 s++;
157 break;
158 case '"':
159 if (where != NULL && i==n) *where = s;
160 s++;
161 while ((*s != '\0') && (*s != '"')) {
162 *p++ = *s++;
164 if (i == n) {
165 *p = '\0';
166 return param;
168 if (*s == '"') s++;
169 param[0] = '\0';
170 i++;
171 p = param;
172 break;
173 /* The code to handle bracketed parms is removed because it should no longer
174 be necessary after the multiline support has been added and the for loop
175 set of data is now parseable individually. */
176 case '\0':
177 return param;
178 default:
179 /* Only return where if it is for the right parameter */
180 if (where != NULL && i==n) *where = s;
181 while ((*s != '\0') && (*s != ' ') && (*s != ',') && (*s != '=') && (*s != '\t')) {
182 *p++ = *s++;
184 if (i == n && (p!=param)) {
185 *p = '\0';
186 return param;
188 /* Skip double delimiters, eg. dir a.a,,,,,b.b */
189 if (p != param) {
190 param[0] = '\0';
191 i++;
192 } else {
193 s++; /* Skip delimiter */
195 p = param;
200 /****************************************************************************
201 * WCMD_fgets
203 * Get one line from a batch file. We can't use the native f* functions because
204 * of the filename syntax differences between DOS and Unix. Also need to lose
205 * the LF (or CRLF) from the line.
208 WCHAR *WCMD_fgets (WCHAR *s, int noChars, HANDLE h) {
210 DWORD bytes;
211 BOOL status;
212 WCHAR *p;
214 p = s;
215 do {
216 status = WCMD_ReadFile (h, s, 1, &bytes, NULL);
217 if ((status == 0) || ((bytes == 0) && (s == p))) return NULL;
218 if (*s == '\n') bytes = 0;
219 else if (*s != '\r') {
220 s++;
221 noChars--;
223 *s = '\0';
224 } while ((bytes == 1) && (noChars > 1));
225 return p;
228 /* WCMD_splitpath - copied from winefile as no obvious way to use it otherwise */
229 void WCMD_splitpath(const WCHAR* path, WCHAR* drv, WCHAR* dir, WCHAR* name, WCHAR* ext)
231 const WCHAR* end; /* end of processed string */
232 const WCHAR* p; /* search pointer */
233 const WCHAR* s; /* copy pointer */
235 /* extract drive name */
236 if (path[0] && path[1]==':') {
237 if (drv) {
238 *drv++ = *path++;
239 *drv++ = *path++;
240 *drv = '\0';
242 } else if (drv)
243 *drv = '\0';
245 /* search for end of string or stream separator */
246 for(end=path; *end && *end!=':'; )
247 end++;
249 /* search for begin of file extension */
250 for(p=end; p>path && *--p!='\\' && *p!='/'; )
251 if (*p == '.') {
252 end = p;
253 break;
256 if (ext)
257 for(s=end; (*ext=*s++); )
258 ext++;
260 /* search for end of directory name */
261 for(p=end; p>path; )
262 if (*--p=='\\' || *p=='/') {
263 p++;
264 break;
267 if (name) {
268 for(s=p; s<end; )
269 *name++ = *s++;
271 *name = '\0';
274 if (dir) {
275 for(s=path; s<p; )
276 *dir++ = *s++;
278 *dir = '\0';
282 /****************************************************************************
283 * WCMD_HandleTildaModifiers
285 * Handle the ~ modifiers when expanding %0-9 or (%a-z in for command)
286 * %~xxxxxV (V=0-9 or A-Z)
287 * Where xxxx is any combination of:
288 * ~ - Removes quotes
289 * f - Fully qualified path (assumes current dir if not drive\dir)
290 * d - drive letter
291 * p - path
292 * n - filename
293 * x - file extension
294 * s - path with shortnames
295 * a - attributes
296 * t - date/time
297 * z - size
298 * $ENVVAR: - Searches ENVVAR for (contents of V) and expands to fully
299 * qualified path
301 * To work out the length of the modifier:
303 * Note: In the case of %0-9 knowing the end of the modifier is easy,
304 * but in a for loop, the for end WCHARacter may also be a modifier
305 * eg. for %a in (c:\a.a) do echo XXX
306 * where XXX = %~a (just ~)
307 * %~aa (~ and attributes)
308 * %~aaxa (~, attributes and extension)
309 * BUT %~aax (~ and attributes followed by 'x')
311 * Hence search forwards until find an invalid modifier, and then
312 * backwards until find for variable or 0-9
314 void WCMD_HandleTildaModifiers(WCHAR **start, WCHAR *forVariable, WCHAR *forValue, BOOL justFors) {
316 #define NUMMODIFIERS 11
317 static const WCHAR validmodifiers[NUMMODIFIERS] = {
318 '~', 'f', 'd', 'p', 'n', 'x', 's', 'a', 't', 'z', '$'
320 static const WCHAR space[] = {' ', '\0'};
322 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
323 WCHAR outputparam[MAX_PATH];
324 WCHAR finaloutput[MAX_PATH];
325 WCHAR fullfilename[MAX_PATH];
326 WCHAR thisoutput[MAX_PATH];
327 WCHAR *pos = *start+1;
328 WCHAR *firstModifier = pos;
329 WCHAR *lastModifier = NULL;
330 int modifierLen = 0;
331 BOOL finished = FALSE;
332 int i = 0;
333 BOOL exists = TRUE;
334 BOOL skipFileParsing = FALSE;
335 BOOL doneModifier = FALSE;
337 /* Search forwards until find invalid character modifier */
338 while (!finished) {
340 /* Work on the previous character */
341 if (lastModifier != NULL) {
343 for (i=0; i<NUMMODIFIERS; i++) {
344 if (validmodifiers[i] == *lastModifier) {
346 /* Special case '$' to skip until : found */
347 if (*lastModifier == '$') {
348 while (*pos != ':' && *pos) pos++;
349 if (*pos == 0x00) return; /* Invalid syntax */
350 pos++; /* Skip ':' */
352 break;
356 if (i==NUMMODIFIERS) {
357 finished = TRUE;
361 /* Save this one away */
362 if (!finished) {
363 lastModifier = pos;
364 pos++;
368 while (lastModifier > firstModifier) {
369 WINE_TRACE("Looking backwards for parameter id: %s / %s\n",
370 wine_dbgstr_w(lastModifier), wine_dbgstr_w(forVariable));
372 if (!justFors && context && (*lastModifier >= '0' && *lastModifier <= '9')) {
373 /* Its a valid parameter identifier - OK */
374 break;
376 } else if (forVariable && *lastModifier == *(forVariable+1)) {
377 /* Its a valid parameter identifier - OK */
378 break;
380 } else {
381 lastModifier--;
384 if (lastModifier == firstModifier) return; /* Invalid syntax */
386 /* Extract the parameter to play with */
387 if (*lastModifier == '0') {
388 strcpyW(outputparam, context->batchfileW);
389 } else if ((*lastModifier >= '1' && *lastModifier <= '9')) {
390 strcpyW(outputparam, WCMD_parameter (context -> command,
391 *lastModifier-'0' + context -> shift_count[*lastModifier-'0'], NULL));
392 } else {
393 strcpyW(outputparam, forValue);
396 /* So now, firstModifier points to beginning of modifiers, lastModifier
397 points to the variable just after the modifiers. Process modifiers
398 in a specific order, remembering there could be duplicates */
399 modifierLen = lastModifier - firstModifier;
400 finaloutput[0] = 0x00;
402 /* Useful for debugging purposes: */
403 /*printf("Modifier string '%*.*s' and variable is %c\n Param starts as '%s'\n",
404 (modifierLen), (modifierLen), firstModifier, *lastModifier,
405 outputparam);*/
407 /* 1. Handle '~' : Strip surrounding quotes */
408 if (outputparam[0]=='"' &&
409 memchrW(firstModifier, '~', modifierLen) != NULL) {
410 int len = strlenW(outputparam);
411 if (outputparam[len-1] == '"') {
412 outputparam[len-1]=0x00;
413 len = len - 1;
415 memmove(outputparam, &outputparam[1], (len * sizeof(WCHAR))-1);
418 /* 2. Handle the special case of a $ */
419 if (memchrW(firstModifier, '$', modifierLen) != NULL) {
420 /* Special Case: Search envar specified in $[envvar] for outputparam
421 Note both $ and : are guaranteed otherwise check above would fail */
422 WCHAR *start = strchrW(firstModifier, '$') + 1;
423 WCHAR *end = strchrW(firstModifier, ':');
424 WCHAR env[MAX_PATH];
425 WCHAR fullpath[MAX_PATH];
427 /* Extract the env var */
428 memcpy(env, start, (end-start) * sizeof(WCHAR));
429 env[(end-start)] = 0x00;
431 /* If env var not found, return empty string */
432 if ((GetEnvironmentVariableW(env, fullpath, MAX_PATH) == 0) ||
433 (SearchPathW(fullpath, outputparam, NULL, MAX_PATH, outputparam, NULL) == 0)) {
434 finaloutput[0] = 0x00;
435 outputparam[0] = 0x00;
436 skipFileParsing = TRUE;
440 /* After this, we need full information on the file,
441 which is valid not to exist. */
442 if (!skipFileParsing) {
443 if (GetFullPathNameW(outputparam, MAX_PATH, fullfilename, NULL) == 0)
444 return;
446 exists = GetFileAttributesExW(fullfilename, GetFileExInfoStandard,
447 &fileInfo);
449 /* 2. Handle 'a' : Output attributes */
450 if (exists &&
451 memchrW(firstModifier, 'a', modifierLen) != NULL) {
453 WCHAR defaults[] = {'-','-','-','-','-','-','-','-','-','\0'};
454 doneModifier = TRUE;
455 strcpyW(thisoutput, defaults);
456 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
457 thisoutput[0]='d';
458 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
459 thisoutput[1]='r';
460 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
461 thisoutput[2]='a';
462 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
463 thisoutput[3]='h';
464 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
465 thisoutput[4]='s';
466 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED)
467 thisoutput[5]='c';
468 /* FIXME: What are 6 and 7? */
469 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
470 thisoutput[8]='l';
471 strcatW(finaloutput, thisoutput);
474 /* 3. Handle 't' : Date+time */
475 if (exists &&
476 memchrW(firstModifier, 't', modifierLen) != NULL) {
478 SYSTEMTIME systime;
479 int datelen;
481 doneModifier = TRUE;
482 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
484 /* Format the time */
485 FileTimeToSystemTime(&fileInfo.ftLastWriteTime, &systime);
486 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &systime,
487 NULL, thisoutput, MAX_PATH);
488 strcatW(thisoutput, space);
489 datelen = strlenW(thisoutput);
490 GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &systime,
491 NULL, (thisoutput+datelen), MAX_PATH-datelen);
492 strcatW(finaloutput, thisoutput);
495 /* 4. Handle 'z' : File length */
496 if (exists &&
497 memchrW(firstModifier, 'z', modifierLen) != NULL) {
498 /* FIXME: Output full 64 bit size (sprintf does not support I64 here) */
499 ULONG/*64*/ fullsize = /*(fileInfo.nFileSizeHigh << 32) +*/
500 fileInfo.nFileSizeLow;
501 static const WCHAR fmt[] = {'%','u','\0'};
503 doneModifier = TRUE;
504 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
505 wsprintfW(thisoutput, fmt, fullsize);
506 strcatW(finaloutput, thisoutput);
509 /* 4. Handle 's' : Use short paths (File doesn't have to exist) */
510 if (memchrW(firstModifier, 's', modifierLen) != NULL) {
511 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
512 /* Don't flag as doneModifier - %~s on its own is processed later */
513 GetShortPathNameW(outputparam, outputparam, sizeof(outputparam)/sizeof(outputparam[0]));
516 /* 5. Handle 'f' : Fully qualified path (File doesn't have to exist) */
517 /* Note this overrides d,p,n,x */
518 if (memchrW(firstModifier, 'f', modifierLen) != NULL) {
519 doneModifier = TRUE;
520 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
521 strcatW(finaloutput, fullfilename);
522 } else {
524 WCHAR drive[10];
525 WCHAR dir[MAX_PATH];
526 WCHAR fname[MAX_PATH];
527 WCHAR ext[MAX_PATH];
528 BOOL doneFileModifier = FALSE;
530 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
532 /* Split into components */
533 WCMD_splitpath(fullfilename, drive, dir, fname, ext);
535 /* 5. Handle 'd' : Drive Letter */
536 if (memchrW(firstModifier, 'd', modifierLen) != NULL) {
537 strcatW(finaloutput, drive);
538 doneModifier = TRUE;
539 doneFileModifier = TRUE;
542 /* 6. Handle 'p' : Path */
543 if (memchrW(firstModifier, 'p', modifierLen) != NULL) {
544 strcatW(finaloutput, dir);
545 doneModifier = TRUE;
546 doneFileModifier = TRUE;
549 /* 7. Handle 'n' : Name */
550 if (memchrW(firstModifier, 'n', modifierLen) != NULL) {
551 strcatW(finaloutput, fname);
552 doneModifier = TRUE;
553 doneFileModifier = TRUE;
556 /* 8. Handle 'x' : Ext */
557 if (memchrW(firstModifier, 'x', modifierLen) != NULL) {
558 strcatW(finaloutput, ext);
559 doneModifier = TRUE;
560 doneFileModifier = TRUE;
563 /* If 's' but no other parameter, dump the whole thing */
564 if (!doneFileModifier &&
565 memchrW(firstModifier, 's', modifierLen) != NULL) {
566 doneModifier = TRUE;
567 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
568 strcatW(finaloutput, outputparam);
573 /* If No other modifier processed, just add in parameter */
574 if (!doneModifier) strcpyW(finaloutput, outputparam);
576 /* Finish by inserting the replacement into the string */
577 WCMD_strsubstW(*start, lastModifier+1, finaloutput, -1);
580 /*******************************************************************
581 * WCMD_call - processes a batch call statement
583 * If there is a leading ':', calls within this batch program
584 * otherwise launches another program.
586 void WCMD_call (WCHAR *command) {
588 /* Run other program if no leading ':' */
589 if (*command != ':') {
590 WCMD_run_program(command, 1);
591 } else {
593 WCHAR gotoLabel[MAX_PATH];
595 strcpyW(gotoLabel, param1);
597 if (context) {
599 LARGE_INTEGER li;
601 /* Save the current file position, call the same file,
602 restore position */
603 li.QuadPart = 0;
604 li.u.LowPart = SetFilePointer(context -> h, li.u.LowPart,
605 &li.u.HighPart, FILE_CURRENT);
607 WCMD_batch (param1, command, 1, gotoLabel, context->h);
609 SetFilePointer(context -> h, li.u.LowPart,
610 &li.u.HighPart, FILE_BEGIN);
611 } else {
612 WCMD_output_asis( WCMD_LoadMessage(WCMD_CALLINSCRIPT));