push b72af2511d67bded8ece08d825ff0eb4a60c20a6
[wine/hacks.git] / programs / cmd / batch.c
blob1d12706876f10eb5f43fd3114f2e0837cf6dc34c
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) * sizeof(WCHAR))/WCMD_BATCH_EXT_SIZE)) &&
62 (h == INVALID_HANDLE_VALUE); i++) {
63 strcpyW (string, file);
64 CharLower (string);
65 if (strstrW (string, extension_batch[i]) == NULL) strcatW (string, extension_batch[i]);
66 h = CreateFile (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 CharLower (string);
72 if (strstrW (string, extension_exe) == NULL) strcatW (string, extension_exe);
73 h = CreateFile (string, GENERIC_READ, FILE_SHARE_READ,
74 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
75 if (h != INVALID_HANDLE_VALUE) {
76 WCMD_run_program (command, 0);
77 } else {
78 SetLastError (ERROR_FILE_NOT_FOUND);
79 WCMD_print_error ();
81 return;
83 } else {
84 DuplicateHandle(GetCurrentProcess(), pgmHandle,
85 GetCurrentProcess(), &h,
86 0, FALSE, DUPLICATE_SAME_ACCESS);
90 * Create a context structure for this batch file.
93 prev_context = context;
94 context = (BATCH_CONTEXT *)LocalAlloc (LMEM_FIXED, sizeof (BATCH_CONTEXT));
95 context -> h = h;
96 context -> command = command;
97 memset(context -> shift_count, 0x00, sizeof(context -> shift_count));
98 context -> prev_context = prev_context;
99 context -> skip_rest = FALSE;
101 /* If processing a call :label, 'goto' the label in question */
102 if (startLabel) {
103 strcpyW(param1, startLabel);
104 WCMD_goto(NULL);
108 * Work through the file line by line. Specific batch commands are processed here,
109 * the rest are handled by the main command processor.
112 while (context -> skip_rest == FALSE) {
113 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
114 if (WCMD_ReadAndParseLine(NULL, &toExecute, h) == NULL)
115 break;
116 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
117 WCMD_free_commands(toExecute);
118 toExecute = NULL;
120 CloseHandle (h);
123 * If invoked by a CALL, we return to the context of our caller. Otherwise return
124 * to the caller's caller.
127 LocalFree ((HANDLE)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 s++;
156 break;
157 case '"':
158 if (where != NULL && i==n) *where = s;
159 s++;
160 while ((*s != '\0') && (*s != '"')) {
161 *p++ = *s++;
163 if (i == n) {
164 *p = '\0';
165 return param;
167 if (*s == '"') s++;
168 param[0] = '\0';
169 i++;
170 p = param;
171 break;
172 /* The code to handle bracketed parms is removed because it should no longer
173 be necessary after the multiline support has been added and the for loop
174 set of data is now parseable individually. */
175 case '\0':
176 return param;
177 default:
178 /* Only return where if it is for the right parameter */
179 if (where != NULL && i==n) *where = s;
180 while ((*s != '\0') && (*s != ' ') && (*s != ',') && (*s != '=')) {
181 *p++ = *s++;
183 if (i == n && (p!=param)) {
184 *p = '\0';
185 return param;
187 /* Skip double delimiters, eg. dir a.a,,,,,b.b */
188 if (p != param) {
189 param[0] = '\0';
190 i++;
191 } else {
192 s++; /* Skip delimter */
194 p = param;
199 /****************************************************************************
200 * WCMD_fgets
202 * Get one line from a batch file. We can't use the native f* functions because
203 * of the filename syntax differences between DOS and Unix. Also need to lose
204 * the LF (or CRLF) from the line.
207 WCHAR *WCMD_fgets (WCHAR *s, int noChars, HANDLE h) {
209 DWORD bytes;
210 BOOL status;
211 WCHAR *p;
213 p = s;
214 do {
215 status = WCMD_ReadFile (h, s, 1, &bytes, NULL);
216 if ((status == 0) || ((bytes == 0) && (s == p))) return NULL;
217 if (*s == '\n') bytes = 0;
218 else if (*s != '\r') {
219 s++;
220 noChars--;
222 *s = '\0';
223 } while ((bytes == 1) && (noChars > 1));
224 return p;
227 /* WCMD_splitpath - copied from winefile as no obvious way to use it otherwise */
228 void WCMD_splitpath(const WCHAR* path, WCHAR* drv, WCHAR* dir, WCHAR* name, WCHAR* ext)
230 const WCHAR* end; /* end of processed string */
231 const WCHAR* p; /* search pointer */
232 const WCHAR* s; /* copy pointer */
234 /* extract drive name */
235 if (path[0] && path[1]==':') {
236 if (drv) {
237 *drv++ = *path++;
238 *drv++ = *path++;
239 *drv = '\0';
241 } else if (drv)
242 *drv = '\0';
244 /* search for end of string or stream separator */
245 for(end=path; *end && *end!=':'; )
246 end++;
248 /* search for begin of file extension */
249 for(p=end; p>path && *--p!='\\' && *p!='/'; )
250 if (*p == '.') {
251 end = p;
252 break;
255 if (ext)
256 for(s=end; (*ext=*s++); )
257 ext++;
259 /* search for end of directory name */
260 for(p=end; p>path; )
261 if (*--p=='\\' || *p=='/') {
262 p++;
263 break;
266 if (name) {
267 for(s=p; s<end; )
268 *name++ = *s++;
270 *name = '\0';
273 if (dir) {
274 for(s=path; s<p; )
275 *dir++ = *s++;
277 *dir = '\0';
281 /****************************************************************************
282 * WCMD_HandleTildaModifiers
284 * Handle the ~ modifiers when expanding %0-9 or (%a-z in for command)
285 * %~xxxxxV (V=0-9 or A-Z)
286 * Where xxxx is any combination of:
287 * ~ - Removes quotes
288 * f - Fully qualified path (assumes current dir if not drive\dir)
289 * d - drive letter
290 * p - path
291 * n - filename
292 * x - file extension
293 * s - path with shortnames
294 * a - attributes
295 * t - date/time
296 * z - size
297 * $ENVVAR: - Searches ENVVAR for (contents of V) and expands to fully
298 * qualified path
300 * To work out the length of the modifier:
302 * Note: In the case of %0-9 knowing the end of the modifier is easy,
303 * but in a for loop, the for end WCHARacter may also be a modifier
304 * eg. for %a in (c:\a.a) do echo XXX
305 * where XXX = %~a (just ~)
306 * %~aa (~ and attributes)
307 * %~aaxa (~, attributes and extension)
308 * BUT %~aax (~ and attributes followed by 'x')
310 * Hence search forwards until find an invalid modifier, and then
311 * backwards until find for variable or 0-9
313 void WCMD_HandleTildaModifiers(WCHAR **start, WCHAR *forVariable, WCHAR *forValue, BOOL justFors) {
315 #define NUMMODIFIERS 11
316 static const WCHAR validmodifiers[NUMMODIFIERS] = {
317 '~', 'f', 'd', 'p', 'n', 'x', 's', 'a', 't', 'z', '$'
319 static const WCHAR space[] = {' ', '\0'};
321 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
322 WCHAR outputparam[MAX_PATH];
323 WCHAR finaloutput[MAX_PATH];
324 WCHAR fullfilename[MAX_PATH];
325 WCHAR thisoutput[MAX_PATH];
326 WCHAR *pos = *start+1;
327 WCHAR *firstModifier = pos;
328 WCHAR *lastModifier = NULL;
329 int modifierLen = 0;
330 BOOL finished = FALSE;
331 int i = 0;
332 BOOL exists = TRUE;
333 BOOL skipFileParsing = FALSE;
334 BOOL doneModifier = FALSE;
336 /* Search forwards until find invalid character modifier */
337 while (!finished) {
339 /* Work on the previous character */
340 if (lastModifier != NULL) {
342 for (i=0; i<NUMMODIFIERS; i++) {
343 if (validmodifiers[i] == *lastModifier) {
345 /* Special case '$' to skip until : found */
346 if (*lastModifier == '$') {
347 while (*pos != ':' && *pos) pos++;
348 if (*pos == 0x00) return; /* Invalid syntax */
349 pos++; /* Skip ':' */
351 break;
355 if (i==NUMMODIFIERS) {
356 finished = TRUE;
360 /* Save this one away */
361 if (!finished) {
362 lastModifier = pos;
363 pos++;
367 while (lastModifier > firstModifier) {
368 WINE_TRACE("Looking backwards for parameter id: %s / %s\n",
369 wine_dbgstr_w(lastModifier), wine_dbgstr_w(forVariable));
371 if (!justFors && context && (*lastModifier >= '0' || *lastModifier <= '9')) {
372 /* Its a valid parameter identifier - OK */
373 break;
375 } else if (forVariable && *lastModifier == *(forVariable+1)) {
376 /* Its a valid parameter identifier - OK */
377 break;
379 } else {
380 lastModifier--;
383 if (lastModifier == firstModifier) return; /* Invalid syntax */
385 /* Extract the parameter to play with */
386 if ((*lastModifier >= '0' && *lastModifier <= '9')) {
387 strcpyW(outputparam, WCMD_parameter (context -> command,
388 *lastModifier-'0' + context -> shift_count[*lastModifier-'0'], NULL));
389 } else {
390 strcpyW(outputparam, forValue);
393 /* So now, firstModifier points to beginning of modifiers, lastModifier
394 points to the variable just after the modifiers. Process modifiers
395 in a specific order, remembering there could be duplicates */
396 modifierLen = lastModifier - firstModifier;
397 finaloutput[0] = 0x00;
399 /* Useful for debugging purposes: */
400 /*printf("Modifier string '%*.*s' and variable is %c\n Param starts as '%s'\n",
401 (modifierLen), (modifierLen), firstModifier, *lastModifier,
402 outputparam);*/
404 /* 1. Handle '~' : Strip surrounding quotes */
405 if (outputparam[0]=='"' &&
406 memchrW(firstModifier, '~', modifierLen) != NULL) {
407 int len = strlenW(outputparam);
408 if (outputparam[len-1] == '"') {
409 outputparam[len-1]=0x00;
410 len = len - 1;
412 memmove(outputparam, &outputparam[1], (len * sizeof(WCHAR))-1);
415 /* 2. Handle the special case of a $ */
416 if (memchrW(firstModifier, '$', modifierLen) != NULL) {
417 /* Special Case: Search envar specified in $[envvar] for outputparam
418 Note both $ and : are guaranteed otherwise check above would fail */
419 WCHAR *start = strchrW(firstModifier, '$') + 1;
420 WCHAR *end = strchrW(firstModifier, ':');
421 WCHAR env[MAX_PATH];
422 WCHAR fullpath[MAX_PATH];
424 /* Extract the env var */
425 memcpy(env, start, (end-start) * sizeof(WCHAR));
426 env[(end-start)] = 0x00;
428 /* If env var not found, return emptry string */
429 if ((GetEnvironmentVariable(env, fullpath, MAX_PATH) == 0) ||
430 (SearchPath(fullpath, outputparam, NULL,
431 MAX_PATH, outputparam, NULL) == 0)) {
432 finaloutput[0] = 0x00;
433 outputparam[0] = 0x00;
434 skipFileParsing = TRUE;
438 /* After this, we need full information on the file,
439 which is valid not to exist. */
440 if (!skipFileParsing) {
441 if (GetFullPathName(outputparam, MAX_PATH, fullfilename, NULL) == 0)
442 return;
444 exists = GetFileAttributesExW(fullfilename, GetFileExInfoStandard,
445 &fileInfo);
447 /* 2. Handle 'a' : Output attributes */
448 if (exists &&
449 memchrW(firstModifier, 'a', modifierLen) != NULL) {
451 WCHAR defaults[] = {'-','-','-','-','-','-','-','-','-','\0'};
452 doneModifier = TRUE;
453 strcpyW(thisoutput, defaults);
454 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
455 thisoutput[0]='d';
456 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
457 thisoutput[1]='r';
458 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
459 thisoutput[2]='a';
460 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
461 thisoutput[3]='h';
462 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
463 thisoutput[4]='s';
464 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED)
465 thisoutput[5]='c';
466 /* FIXME: What are 6 and 7? */
467 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
468 thisoutput[8]='l';
469 strcatW(finaloutput, thisoutput);
472 /* 3. Handle 't' : Date+time */
473 if (exists &&
474 memchrW(firstModifier, 't', modifierLen) != NULL) {
476 SYSTEMTIME systime;
477 int datelen;
479 doneModifier = TRUE;
480 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
482 /* Format the time */
483 FileTimeToSystemTime(&fileInfo.ftLastWriteTime, &systime);
484 GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &systime,
485 NULL, thisoutput, MAX_PATH);
486 strcatW(thisoutput, space);
487 datelen = strlenW(thisoutput);
488 GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &systime,
489 NULL, (thisoutput+datelen), MAX_PATH-datelen);
490 strcatW(finaloutput, thisoutput);
493 /* 4. Handle 'z' : File length */
494 if (exists &&
495 memchrW(firstModifier, 'z', modifierLen) != NULL) {
496 /* FIXME: Output full 64 bit size (sprintf does not support I64 here) */
497 ULONG/*64*/ fullsize = /*(fileInfo.nFileSizeHigh << 32) +*/
498 fileInfo.nFileSizeLow;
499 static const WCHAR fmt[] = {'%','u','\0'};
501 doneModifier = TRUE;
502 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
503 wsprintf(thisoutput, fmt, fullsize);
504 strcatW(finaloutput, thisoutput);
507 /* 4. Handle 's' : Use short paths (File doesn't have to exist) */
508 if (memchrW(firstModifier, 's', modifierLen) != NULL) {
509 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
510 /* Don't flag as doneModifier - %~s on its own is processed later */
511 GetShortPathName(outputparam, outputparam, sizeof(outputparam));
514 /* 5. Handle 'f' : Fully qualified path (File doesn't have to exist) */
515 /* Note this overrides d,p,n,x */
516 if (memchrW(firstModifier, 'f', modifierLen) != NULL) {
517 doneModifier = TRUE;
518 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
519 strcatW(finaloutput, fullfilename);
520 } else {
522 WCHAR drive[10];
523 WCHAR dir[MAX_PATH];
524 WCHAR fname[MAX_PATH];
525 WCHAR ext[MAX_PATH];
526 BOOL doneFileModifier = FALSE;
528 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
530 /* Split into components */
531 WCMD_splitpath(fullfilename, drive, dir, fname, ext);
533 /* 5. Handle 'd' : Drive Letter */
534 if (memchrW(firstModifier, 'd', modifierLen) != NULL) {
535 strcatW(finaloutput, drive);
536 doneModifier = TRUE;
537 doneFileModifier = TRUE;
540 /* 6. Handle 'p' : Path */
541 if (memchrW(firstModifier, 'p', modifierLen) != NULL) {
542 strcatW(finaloutput, dir);
543 doneModifier = TRUE;
544 doneFileModifier = TRUE;
547 /* 7. Handle 'n' : Name */
548 if (memchrW(firstModifier, 'n', modifierLen) != NULL) {
549 strcatW(finaloutput, fname);
550 doneModifier = TRUE;
551 doneFileModifier = TRUE;
554 /* 8. Handle 'x' : Ext */
555 if (memchrW(firstModifier, 'x', modifierLen) != NULL) {
556 strcatW(finaloutput, ext);
557 doneModifier = TRUE;
558 doneFileModifier = TRUE;
561 /* If 's' but no other parameter, dump the whole thing */
562 if (!doneFileModifier &&
563 memchrW(firstModifier, 's', modifierLen) != NULL) {
564 doneModifier = TRUE;
565 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
566 strcatW(finaloutput, outputparam);
571 /* If No other modifier processed, just add in parameter */
572 if (!doneModifier) strcpyW(finaloutput, outputparam);
574 /* Finish by inserting the replacement into the string */
575 pos = WCMD_strdupW(lastModifier+1);
576 strcpyW(*start, finaloutput);
577 strcatW(*start, pos);
578 free(pos);
581 /*******************************************************************
582 * WCMD_call - processes a batch call statement
584 * If there is a leading ':', calls within this batch program
585 * otherwise launches another program.
587 void WCMD_call (WCHAR *command) {
589 /* Run other program if no leading ':' */
590 if (*command != ':') {
591 WCMD_run_program(command, 1);
592 } else {
594 WCHAR gotoLabel[MAX_PATH];
596 strcpyW(gotoLabel, param1);
598 if (context) {
600 LARGE_INTEGER li;
602 /* Save the current file position, call the same file,
603 restore position */
604 li.QuadPart = 0;
605 li.u.LowPart = SetFilePointer(context -> h, li.u.LowPart,
606 &li.u.HighPart, FILE_CURRENT);
608 WCMD_batch (param1, command, 1, gotoLabel, context->h);
610 SetFilePointer(context -> h, li.u.LowPart,
611 &li.u.HighPart, FILE_BEGIN);
612 } else {
613 WCMD_output_asis( WCMD_LoadMessage(WCMD_CALLINSCRIPT));