msxml3/tests: Tests for default properties values.
[wine/multimedia.git] / programs / cmd / batch.c
blob03c92ee394a72ce42e5e6683d717d5d4628164df
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, const WCHAR *forVariable,
295 const WCHAR *forValue, BOOL justFors) {
297 #define NUMMODIFIERS 11
298 static const WCHAR validmodifiers[NUMMODIFIERS] = {
299 '~', 'f', 'd', 'p', 'n', 'x', 's', 'a', 't', 'z', '$'
301 static const WCHAR space[] = {' ', '\0'};
303 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
304 WCHAR outputparam[MAX_PATH];
305 WCHAR finaloutput[MAX_PATH];
306 WCHAR fullfilename[MAX_PATH];
307 WCHAR thisoutput[MAX_PATH];
308 WCHAR *pos = *start+1;
309 WCHAR *firstModifier = pos;
310 WCHAR *lastModifier = NULL;
311 int modifierLen = 0;
312 BOOL finished = FALSE;
313 int i = 0;
314 BOOL exists = TRUE;
315 BOOL skipFileParsing = FALSE;
316 BOOL doneModifier = FALSE;
318 /* Search forwards until find invalid character modifier */
319 while (!finished) {
321 /* Work on the previous character */
322 if (lastModifier != NULL) {
324 for (i=0; i<NUMMODIFIERS; i++) {
325 if (validmodifiers[i] == *lastModifier) {
327 /* Special case '$' to skip until : found */
328 if (*lastModifier == '$') {
329 while (*pos != ':' && *pos) pos++;
330 if (*pos == 0x00) return; /* Invalid syntax */
331 pos++; /* Skip ':' */
333 break;
337 if (i==NUMMODIFIERS) {
338 finished = TRUE;
342 /* Save this one away */
343 if (!finished) {
344 lastModifier = pos;
345 pos++;
349 while (lastModifier > firstModifier) {
350 WINE_TRACE("Looking backwards for parameter id: %s / %s\n",
351 wine_dbgstr_w(lastModifier), wine_dbgstr_w(forVariable));
353 if (!justFors && context && (*lastModifier >= '0' && *lastModifier <= '9')) {
354 /* Its a valid parameter identifier - OK */
355 break;
357 } else if (forVariable && *lastModifier == *(forVariable+1)) {
358 /* Its a valid parameter identifier - OK */
359 break;
361 } else {
362 lastModifier--;
365 if (lastModifier == firstModifier) return; /* Invalid syntax */
367 /* Extract the parameter to play with */
368 if (*lastModifier == '0') {
369 strcpyW(outputparam, context->batchfileW);
370 } else if ((*lastModifier >= '1' && *lastModifier <= '9')) {
371 strcpyW(outputparam, WCMD_parameter (context -> command,
372 *lastModifier-'0' + context -> shift_count[*lastModifier-'0'], NULL));
373 } else {
374 strcpyW(outputparam, forValue);
377 /* So now, firstModifier points to beginning of modifiers, lastModifier
378 points to the variable just after the modifiers. Process modifiers
379 in a specific order, remembering there could be duplicates */
380 modifierLen = lastModifier - firstModifier;
381 finaloutput[0] = 0x00;
383 /* Useful for debugging purposes: */
384 /*printf("Modifier string '%*.*s' and variable is %c\n Param starts as '%s'\n",
385 (modifierLen), (modifierLen), firstModifier, *lastModifier,
386 outputparam);*/
388 /* 1. Handle '~' : Strip surrounding quotes */
389 if (outputparam[0]=='"' &&
390 memchrW(firstModifier, '~', modifierLen) != NULL) {
391 int len = strlenW(outputparam);
392 if (outputparam[len-1] == '"') {
393 outputparam[len-1]=0x00;
394 len = len - 1;
396 memmove(outputparam, &outputparam[1], (len * sizeof(WCHAR))-1);
399 /* 2. Handle the special case of a $ */
400 if (memchrW(firstModifier, '$', modifierLen) != NULL) {
401 /* Special Case: Search envar specified in $[envvar] for outputparam
402 Note both $ and : are guaranteed otherwise check above would fail */
403 WCHAR *begin = strchrW(firstModifier, '$') + 1;
404 WCHAR *end = strchrW(firstModifier, ':');
405 WCHAR env[MAX_PATH];
406 WCHAR fullpath[MAX_PATH];
408 /* Extract the env var */
409 memcpy(env, begin, (end-begin) * sizeof(WCHAR));
410 env[(end-begin)] = 0x00;
412 /* If env var not found, return empty string */
413 if ((GetEnvironmentVariableW(env, fullpath, MAX_PATH) == 0) ||
414 (SearchPathW(fullpath, outputparam, NULL, MAX_PATH, outputparam, NULL) == 0)) {
415 finaloutput[0] = 0x00;
416 outputparam[0] = 0x00;
417 skipFileParsing = TRUE;
421 /* After this, we need full information on the file,
422 which is valid not to exist. */
423 if (!skipFileParsing) {
424 if (GetFullPathNameW(outputparam, MAX_PATH, fullfilename, NULL) == 0)
425 return;
427 exists = GetFileAttributesExW(fullfilename, GetFileExInfoStandard,
428 &fileInfo);
430 /* 2. Handle 'a' : Output attributes */
431 if (exists &&
432 memchrW(firstModifier, 'a', modifierLen) != NULL) {
434 WCHAR defaults[] = {'-','-','-','-','-','-','-','-','-','\0'};
435 doneModifier = TRUE;
436 strcpyW(thisoutput, defaults);
437 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
438 thisoutput[0]='d';
439 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
440 thisoutput[1]='r';
441 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
442 thisoutput[2]='a';
443 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
444 thisoutput[3]='h';
445 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
446 thisoutput[4]='s';
447 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED)
448 thisoutput[5]='c';
449 /* FIXME: What are 6 and 7? */
450 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
451 thisoutput[8]='l';
452 strcatW(finaloutput, thisoutput);
455 /* 3. Handle 't' : Date+time */
456 if (exists &&
457 memchrW(firstModifier, 't', modifierLen) != NULL) {
459 SYSTEMTIME systime;
460 int datelen;
462 doneModifier = TRUE;
463 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
465 /* Format the time */
466 FileTimeToSystemTime(&fileInfo.ftLastWriteTime, &systime);
467 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &systime,
468 NULL, thisoutput, MAX_PATH);
469 strcatW(thisoutput, space);
470 datelen = strlenW(thisoutput);
471 GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &systime,
472 NULL, (thisoutput+datelen), MAX_PATH-datelen);
473 strcatW(finaloutput, thisoutput);
476 /* 4. Handle 'z' : File length */
477 if (exists &&
478 memchrW(firstModifier, 'z', modifierLen) != NULL) {
479 /* FIXME: Output full 64 bit size (sprintf does not support I64 here) */
480 ULONG/*64*/ fullsize = /*(fileInfo.nFileSizeHigh << 32) +*/
481 fileInfo.nFileSizeLow;
482 static const WCHAR fmt[] = {'%','u','\0'};
484 doneModifier = TRUE;
485 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
486 wsprintfW(thisoutput, fmt, fullsize);
487 strcatW(finaloutput, thisoutput);
490 /* 4. Handle 's' : Use short paths (File doesn't have to exist) */
491 if (memchrW(firstModifier, 's', modifierLen) != NULL) {
492 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
493 /* Don't flag as doneModifier - %~s on its own is processed later */
494 GetShortPathNameW(outputparam, outputparam, sizeof(outputparam)/sizeof(outputparam[0]));
497 /* 5. Handle 'f' : Fully qualified path (File doesn't have to exist) */
498 /* Note this overrides d,p,n,x */
499 if (memchrW(firstModifier, 'f', modifierLen) != NULL) {
500 doneModifier = TRUE;
501 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
502 strcatW(finaloutput, fullfilename);
503 } else {
505 WCHAR drive[10];
506 WCHAR dir[MAX_PATH];
507 WCHAR fname[MAX_PATH];
508 WCHAR ext[MAX_PATH];
509 BOOL doneFileModifier = FALSE;
511 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
513 /* Split into components */
514 WCMD_splitpath(fullfilename, drive, dir, fname, ext);
516 /* 5. Handle 'd' : Drive Letter */
517 if (memchrW(firstModifier, 'd', modifierLen) != NULL) {
518 strcatW(finaloutput, drive);
519 doneModifier = TRUE;
520 doneFileModifier = TRUE;
523 /* 6. Handle 'p' : Path */
524 if (memchrW(firstModifier, 'p', modifierLen) != NULL) {
525 strcatW(finaloutput, dir);
526 doneModifier = TRUE;
527 doneFileModifier = TRUE;
530 /* 7. Handle 'n' : Name */
531 if (memchrW(firstModifier, 'n', modifierLen) != NULL) {
532 strcatW(finaloutput, fname);
533 doneModifier = TRUE;
534 doneFileModifier = TRUE;
537 /* 8. Handle 'x' : Ext */
538 if (memchrW(firstModifier, 'x', modifierLen) != NULL) {
539 strcatW(finaloutput, ext);
540 doneModifier = TRUE;
541 doneFileModifier = TRUE;
544 /* If 's' but no other parameter, dump the whole thing */
545 if (!doneFileModifier &&
546 memchrW(firstModifier, 's', modifierLen) != NULL) {
547 doneModifier = TRUE;
548 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
549 strcatW(finaloutput, outputparam);
554 /* If No other modifier processed, just add in parameter */
555 if (!doneModifier) strcpyW(finaloutput, outputparam);
557 /* Finish by inserting the replacement into the string */
558 WCMD_strsubstW(*start, lastModifier+1, finaloutput, -1);
561 /*******************************************************************
562 * WCMD_call - processes a batch call statement
564 * If there is a leading ':', calls within this batch program
565 * otherwise launches another program.
567 void WCMD_call (WCHAR *command) {
569 /* Run other program if no leading ':' */
570 if (*command != ':') {
571 WCMD_run_program(command, 1);
572 } else {
574 WCHAR gotoLabel[MAX_PATH];
576 strcpyW(gotoLabel, param1);
578 if (context) {
580 LARGE_INTEGER li;
582 /* Save the current file position, call the same file,
583 restore position */
584 li.QuadPart = 0;
585 li.u.LowPart = SetFilePointer(context -> h, li.u.LowPart,
586 &li.u.HighPart, FILE_CURRENT);
588 WCMD_batch (param1, command, 1, gotoLabel, context->h);
590 SetFilePointer(context -> h, li.u.LowPart,
591 &li.u.HighPart, FILE_BEGIN);
592 } else {
593 WCMD_output_asis( WCMD_LoadMessage(WCMD_CALLINSCRIPT));