winemenubuilder: Add a trailing '\n' to a FIXME() call.
[wine/testsucceed.git] / programs / cmd / batch.c
blob585ea5e45341a50fcff76f420c4b2539522a1793
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 HANDLE h = INVALID_HANDLE_VALUE;
51 BATCH_CONTEXT *prev_context;
53 if (startLabel == NULL) {
54 h = CreateFileW (file, GENERIC_READ, FILE_SHARE_READ,
55 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
56 if (h == INVALID_HANDLE_VALUE) {
57 SetLastError (ERROR_FILE_NOT_FOUND);
58 WCMD_print_error ();
59 return;
61 } else {
62 DuplicateHandle(GetCurrentProcess(), pgmHandle,
63 GetCurrentProcess(), &h,
64 0, FALSE, DUPLICATE_SAME_ACCESS);
68 * Create a context structure for this batch file.
71 prev_context = context;
72 context = LocalAlloc (LMEM_FIXED, sizeof (BATCH_CONTEXT));
73 context -> h = h;
74 context->batchfileW = WCMD_strdupW(file);
75 context -> command = command;
76 memset(context -> shift_count, 0x00, sizeof(context -> shift_count));
77 context -> prev_context = prev_context;
78 context -> skip_rest = FALSE;
80 /* If processing a call :label, 'goto' the label in question */
81 if (startLabel) {
82 strcpyW(param1, startLabel);
83 WCMD_goto(NULL);
87 * Work through the file line by line. Specific batch commands are processed here,
88 * the rest are handled by the main command processor.
91 while (context -> skip_rest == FALSE) {
92 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
93 if (WCMD_ReadAndParseLine(NULL, &toExecute, h) == NULL)
94 break;
95 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
96 WCMD_free_commands(toExecute);
97 toExecute = NULL;
99 CloseHandle (h);
102 * If invoked by a CALL, we return to the context of our caller. Otherwise return
103 * to the caller's caller.
106 HeapFree(GetProcessHeap(), 0, context->batchfileW);
107 LocalFree (context);
108 if ((prev_context != NULL) && (!called)) {
109 prev_context -> skip_rest = TRUE;
110 context = prev_context;
112 context = prev_context;
115 /*******************************************************************
116 * WCMD_parameter - extract a parameter from a command line.
118 * Returns the 'n'th delimited parameter on the command line (zero-based).
119 * Parameter is in static storage overwritten on the next call.
120 * Parameters in quotes (and brackets) are handled.
121 * Also returns a pointer to the location of the parameter in the command line.
124 WCHAR *WCMD_parameter (WCHAR *s, int n, WCHAR **where) {
126 int i = 0;
127 static WCHAR param[MAX_PATH];
128 WCHAR *p;
130 if (where != NULL) *where = NULL;
131 p = param;
132 while (TRUE) {
133 switch (*s) {
134 case ' ': /* Skip leading spaces */
135 case '\t': /* Treat tabs as spaces */
136 s++;
137 break;
138 case '"':
139 if (where != NULL && i==n) *where = s;
140 s++;
141 while ((*s != '\0') && (*s != '"')) {
142 *p++ = *s++;
144 if (i == n) {
145 *p = '\0';
146 return param;
148 if (*s == '"') s++;
149 param[0] = '\0';
150 i++;
151 p = param;
152 break;
153 /* The code to handle bracketed parms is removed because it should no longer
154 be necessary after the multiline support has been added and the for loop
155 set of data is now parseable individually. */
156 case '\0':
157 return param;
158 default:
159 /* Only return where if it is for the right parameter */
160 if (where != NULL && i==n) *where = s;
161 while ((*s != '\0') && (*s != ' ') && (*s != ',') && (*s != '=') && (*s != '\t')) {
162 *p++ = *s++;
164 if (i == n && (p!=param)) {
165 *p = '\0';
166 return param;
168 /* Skip double delimiters, eg. dir a.a,,,,,b.b */
169 if (p != param) {
170 param[0] = '\0';
171 i++;
172 } else {
173 s++; /* Skip delimiter */
175 p = param;
180 /****************************************************************************
181 * WCMD_fgets
183 * Get one line from a batch file. We can't use the native f* functions because
184 * of the filename syntax differences between DOS and Unix. Also need to lose
185 * the LF (or CRLF) from the line.
188 WCHAR *WCMD_fgets (WCHAR *s, int noChars, HANDLE h) {
190 DWORD bytes;
191 BOOL status;
192 WCHAR *p;
194 p = s;
195 do {
196 status = WCMD_ReadFile (h, s, 1, &bytes, NULL);
197 if ((status == 0) || ((bytes == 0) && (s == p))) return NULL;
198 if (*s == '\n') bytes = 0;
199 else if (*s != '\r') {
200 s++;
201 noChars--;
203 *s = '\0';
204 } while ((bytes == 1) && (noChars > 1));
205 return p;
208 /* WCMD_splitpath - copied from winefile as no obvious way to use it otherwise */
209 void WCMD_splitpath(const WCHAR* path, WCHAR* drv, WCHAR* dir, WCHAR* name, WCHAR* ext)
211 const WCHAR* end; /* end of processed string */
212 const WCHAR* p; /* search pointer */
213 const WCHAR* s; /* copy pointer */
215 /* extract drive name */
216 if (path[0] && path[1]==':') {
217 if (drv) {
218 *drv++ = *path++;
219 *drv++ = *path++;
220 *drv = '\0';
222 } else if (drv)
223 *drv = '\0';
225 /* search for end of string or stream separator */
226 for(end=path; *end && *end!=':'; )
227 end++;
229 /* search for begin of file extension */
230 for(p=end; p>path && *--p!='\\' && *p!='/'; )
231 if (*p == '.') {
232 end = p;
233 break;
236 if (ext)
237 for(s=end; (*ext=*s++); )
238 ext++;
240 /* search for end of directory name */
241 for(p=end; p>path; )
242 if (*--p=='\\' || *p=='/') {
243 p++;
244 break;
247 if (name) {
248 for(s=p; s<end; )
249 *name++ = *s++;
251 *name = '\0';
254 if (dir) {
255 for(s=path; s<p; )
256 *dir++ = *s++;
258 *dir = '\0';
262 /****************************************************************************
263 * WCMD_HandleTildaModifiers
265 * Handle the ~ modifiers when expanding %0-9 or (%a-z in for command)
266 * %~xxxxxV (V=0-9 or A-Z)
267 * Where xxxx is any combination of:
268 * ~ - Removes quotes
269 * f - Fully qualified path (assumes current dir if not drive\dir)
270 * d - drive letter
271 * p - path
272 * n - filename
273 * x - file extension
274 * s - path with shortnames
275 * a - attributes
276 * t - date/time
277 * z - size
278 * $ENVVAR: - Searches ENVVAR for (contents of V) and expands to fully
279 * qualified path
281 * To work out the length of the modifier:
283 * Note: In the case of %0-9 knowing the end of the modifier is easy,
284 * but in a for loop, the for end WCHARacter may also be a modifier
285 * eg. for %a in (c:\a.a) do echo XXX
286 * where XXX = %~a (just ~)
287 * %~aa (~ and attributes)
288 * %~aaxa (~, attributes and extension)
289 * BUT %~aax (~ and attributes followed by 'x')
291 * Hence search forwards until find an invalid modifier, and then
292 * backwards until find for variable or 0-9
294 void WCMD_HandleTildaModifiers(WCHAR **start, WCHAR *forVariable, WCHAR *forValue, BOOL justFors) {
296 #define NUMMODIFIERS 11
297 static const WCHAR validmodifiers[NUMMODIFIERS] = {
298 '~', 'f', 'd', 'p', 'n', 'x', 's', 'a', 't', 'z', '$'
300 static const WCHAR space[] = {' ', '\0'};
302 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
303 WCHAR outputparam[MAX_PATH];
304 WCHAR finaloutput[MAX_PATH];
305 WCHAR fullfilename[MAX_PATH];
306 WCHAR thisoutput[MAX_PATH];
307 WCHAR *pos = *start+1;
308 WCHAR *firstModifier = pos;
309 WCHAR *lastModifier = NULL;
310 int modifierLen = 0;
311 BOOL finished = FALSE;
312 int i = 0;
313 BOOL exists = TRUE;
314 BOOL skipFileParsing = FALSE;
315 BOOL doneModifier = FALSE;
317 /* Search forwards until find invalid character modifier */
318 while (!finished) {
320 /* Work on the previous character */
321 if (lastModifier != NULL) {
323 for (i=0; i<NUMMODIFIERS; i++) {
324 if (validmodifiers[i] == *lastModifier) {
326 /* Special case '$' to skip until : found */
327 if (*lastModifier == '$') {
328 while (*pos != ':' && *pos) pos++;
329 if (*pos == 0x00) return; /* Invalid syntax */
330 pos++; /* Skip ':' */
332 break;
336 if (i==NUMMODIFIERS) {
337 finished = TRUE;
341 /* Save this one away */
342 if (!finished) {
343 lastModifier = pos;
344 pos++;
348 while (lastModifier > firstModifier) {
349 WINE_TRACE("Looking backwards for parameter id: %s / %s\n",
350 wine_dbgstr_w(lastModifier), wine_dbgstr_w(forVariable));
352 if (!justFors && context && (*lastModifier >= '0' && *lastModifier <= '9')) {
353 /* Its a valid parameter identifier - OK */
354 break;
356 } else if (forVariable && *lastModifier == *(forVariable+1)) {
357 /* Its a valid parameter identifier - OK */
358 break;
360 } else {
361 lastModifier--;
364 if (lastModifier == firstModifier) return; /* Invalid syntax */
366 /* Extract the parameter to play with */
367 if (*lastModifier == '0') {
368 strcpyW(outputparam, context->batchfileW);
369 } else if ((*lastModifier >= '1' && *lastModifier <= '9')) {
370 strcpyW(outputparam, WCMD_parameter (context -> command,
371 *lastModifier-'0' + context -> shift_count[*lastModifier-'0'], NULL));
372 } else {
373 strcpyW(outputparam, forValue);
376 /* So now, firstModifier points to beginning of modifiers, lastModifier
377 points to the variable just after the modifiers. Process modifiers
378 in a specific order, remembering there could be duplicates */
379 modifierLen = lastModifier - firstModifier;
380 finaloutput[0] = 0x00;
382 /* Useful for debugging purposes: */
383 /*printf("Modifier string '%*.*s' and variable is %c\n Param starts as '%s'\n",
384 (modifierLen), (modifierLen), firstModifier, *lastModifier,
385 outputparam);*/
387 /* 1. Handle '~' : Strip surrounding quotes */
388 if (outputparam[0]=='"' &&
389 memchrW(firstModifier, '~', modifierLen) != NULL) {
390 int len = strlenW(outputparam);
391 if (outputparam[len-1] == '"') {
392 outputparam[len-1]=0x00;
393 len = len - 1;
395 memmove(outputparam, &outputparam[1], (len * sizeof(WCHAR))-1);
398 /* 2. Handle the special case of a $ */
399 if (memchrW(firstModifier, '$', modifierLen) != NULL) {
400 /* Special Case: Search envar specified in $[envvar] for outputparam
401 Note both $ and : are guaranteed otherwise check above would fail */
402 WCHAR *start = strchrW(firstModifier, '$') + 1;
403 WCHAR *end = strchrW(firstModifier, ':');
404 WCHAR env[MAX_PATH];
405 WCHAR fullpath[MAX_PATH];
407 /* Extract the env var */
408 memcpy(env, start, (end-start) * sizeof(WCHAR));
409 env[(end-start)] = 0x00;
411 /* If env var not found, return empty string */
412 if ((GetEnvironmentVariableW(env, fullpath, MAX_PATH) == 0) ||
413 (SearchPathW(fullpath, outputparam, NULL, MAX_PATH, outputparam, NULL) == 0)) {
414 finaloutput[0] = 0x00;
415 outputparam[0] = 0x00;
416 skipFileParsing = TRUE;
420 /* After this, we need full information on the file,
421 which is valid not to exist. */
422 if (!skipFileParsing) {
423 if (GetFullPathNameW(outputparam, MAX_PATH, fullfilename, NULL) == 0)
424 return;
426 exists = GetFileAttributesExW(fullfilename, GetFileExInfoStandard,
427 &fileInfo);
429 /* 2. Handle 'a' : Output attributes */
430 if (exists &&
431 memchrW(firstModifier, 'a', modifierLen) != NULL) {
433 WCHAR defaults[] = {'-','-','-','-','-','-','-','-','-','\0'};
434 doneModifier = TRUE;
435 strcpyW(thisoutput, defaults);
436 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
437 thisoutput[0]='d';
438 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
439 thisoutput[1]='r';
440 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
441 thisoutput[2]='a';
442 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
443 thisoutput[3]='h';
444 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
445 thisoutput[4]='s';
446 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED)
447 thisoutput[5]='c';
448 /* FIXME: What are 6 and 7? */
449 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
450 thisoutput[8]='l';
451 strcatW(finaloutput, thisoutput);
454 /* 3. Handle 't' : Date+time */
455 if (exists &&
456 memchrW(firstModifier, 't', modifierLen) != NULL) {
458 SYSTEMTIME systime;
459 int datelen;
461 doneModifier = TRUE;
462 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
464 /* Format the time */
465 FileTimeToSystemTime(&fileInfo.ftLastWriteTime, &systime);
466 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &systime,
467 NULL, thisoutput, MAX_PATH);
468 strcatW(thisoutput, space);
469 datelen = strlenW(thisoutput);
470 GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &systime,
471 NULL, (thisoutput+datelen), MAX_PATH-datelen);
472 strcatW(finaloutput, thisoutput);
475 /* 4. Handle 'z' : File length */
476 if (exists &&
477 memchrW(firstModifier, 'z', modifierLen) != NULL) {
478 /* FIXME: Output full 64 bit size (sprintf does not support I64 here) */
479 ULONG/*64*/ fullsize = /*(fileInfo.nFileSizeHigh << 32) +*/
480 fileInfo.nFileSizeLow;
481 static const WCHAR fmt[] = {'%','u','\0'};
483 doneModifier = TRUE;
484 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
485 wsprintfW(thisoutput, fmt, fullsize);
486 strcatW(finaloutput, thisoutput);
489 /* 4. Handle 's' : Use short paths (File doesn't have to exist) */
490 if (memchrW(firstModifier, 's', modifierLen) != NULL) {
491 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
492 /* Don't flag as doneModifier - %~s on its own is processed later */
493 GetShortPathNameW(outputparam, outputparam, sizeof(outputparam)/sizeof(outputparam[0]));
496 /* 5. Handle 'f' : Fully qualified path (File doesn't have to exist) */
497 /* Note this overrides d,p,n,x */
498 if (memchrW(firstModifier, 'f', modifierLen) != NULL) {
499 doneModifier = TRUE;
500 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
501 strcatW(finaloutput, fullfilename);
502 } else {
504 WCHAR drive[10];
505 WCHAR dir[MAX_PATH];
506 WCHAR fname[MAX_PATH];
507 WCHAR ext[MAX_PATH];
508 BOOL doneFileModifier = FALSE;
510 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
512 /* Split into components */
513 WCMD_splitpath(fullfilename, drive, dir, fname, ext);
515 /* 5. Handle 'd' : Drive Letter */
516 if (memchrW(firstModifier, 'd', modifierLen) != NULL) {
517 strcatW(finaloutput, drive);
518 doneModifier = TRUE;
519 doneFileModifier = TRUE;
522 /* 6. Handle 'p' : Path */
523 if (memchrW(firstModifier, 'p', modifierLen) != NULL) {
524 strcatW(finaloutput, dir);
525 doneModifier = TRUE;
526 doneFileModifier = TRUE;
529 /* 7. Handle 'n' : Name */
530 if (memchrW(firstModifier, 'n', modifierLen) != NULL) {
531 strcatW(finaloutput, fname);
532 doneModifier = TRUE;
533 doneFileModifier = TRUE;
536 /* 8. Handle 'x' : Ext */
537 if (memchrW(firstModifier, 'x', modifierLen) != NULL) {
538 strcatW(finaloutput, ext);
539 doneModifier = TRUE;
540 doneFileModifier = TRUE;
543 /* If 's' but no other parameter, dump the whole thing */
544 if (!doneFileModifier &&
545 memchrW(firstModifier, 's', modifierLen) != NULL) {
546 doneModifier = TRUE;
547 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
548 strcatW(finaloutput, outputparam);
553 /* If No other modifier processed, just add in parameter */
554 if (!doneModifier) strcpyW(finaloutput, outputparam);
556 /* Finish by inserting the replacement into the string */
557 WCMD_strsubstW(*start, lastModifier+1, finaloutput, -1);
560 /*******************************************************************
561 * WCMD_call - processes a batch call statement
563 * If there is a leading ':', calls within this batch program
564 * otherwise launches another program.
566 void WCMD_call (WCHAR *command) {
568 /* Run other program if no leading ':' */
569 if (*command != ':') {
570 WCMD_run_program(command, 1);
571 } else {
573 WCHAR gotoLabel[MAX_PATH];
575 strcpyW(gotoLabel, param1);
577 if (context) {
579 LARGE_INTEGER li;
581 /* Save the current file position, call the same file,
582 restore position */
583 li.QuadPart = 0;
584 li.u.LowPart = SetFilePointer(context -> h, li.u.LowPart,
585 &li.u.HighPart, FILE_CURRENT);
587 WCMD_batch (param1, command, 1, gotoLabel, context->h);
589 SetFilePointer(context -> h, li.u.LowPart,
590 &li.u.HighPart, FILE_BEGIN);
591 } else {
592 WCMD_output_asis( WCMD_LoadMessage(WCMD_CALLINSCRIPT));