1 /*
2 * CMD - Wine-compatible command line interface - batch interface.
3 *
4 * Copyright (C) 1999 D A Pickles
5 * Copyright (C) 2007 J Edmeades
6 *
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.
11 *
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.
16 *
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
20 */
21
22 #include "wcmd.h"
23 #include "wine/debug.h"
24
25 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
26
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;
31
32 /****************************************************************************
33 * WCMD_batch
34 *
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.
40 *
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.
43 *
44 * To support call within the same batch program, another input parameter is
45 * a label to goto once opened.
46 */
47
48 void WCMD_batch (WCHAR *file, WCHAR *command, int called, WCHAR *startLabel, HANDLE pgmHandle) {
49
50 #define WCMD_BATCH_EXT_SIZE 5
51
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;
59
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 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);
68 }
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 if (GetFileAttributes (string) != INVALID_FILE_ATTRIBUTES) {
74 WCMD_run_program (command, 0);
75 } else {
76 SetLastError (ERROR_FILE_NOT_FOUND);
77 WCMD_print_error ();
78 }
79 return;
80 }
81 } else {
82 DuplicateHandle(GetCurrentProcess(), pgmHandle,
83 GetCurrentProcess(), &h,
84 0, FALSE, DUPLICATE_SAME_ACCESS);
85 }
86
87 /*
88 * Create a context structure for this batch file.
89 */
90
91 prev_context = context;
92 context = LocalAlloc (LMEM_FIXED, sizeof (BATCH_CONTEXT));
93 context -> h = h;
94 context -> command = command;
95 memset(context -> shift_count, 0x00, sizeof(context -> shift_count));
96 context -> prev_context = prev_context;
97 context -> skip_rest = FALSE;
98
99 /* If processing a call :label, 'goto' the label in question */
100 if (startLabel) {
101 strcpyW(param1, startLabel);
102 WCMD_goto(NULL);
103 }
104
105 /*
106 * Work through the file line by line. Specific batch commands are processed here,
107 * the rest are handled by the main command processor.
108 */
109
110 while (context -> skip_rest == FALSE) {
111 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
112 if (WCMD_ReadAndParseLine(NULL, &toExecute, h) == NULL)
113 break;
114 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
115 WCMD_free_commands(toExecute);
116 toExecute = NULL;
117 }
118 CloseHandle (h);
119
120 /*
121 * If invoked by a CALL, we return to the context of our caller. Otherwise return
122 * to the caller's caller.
123 */
124
125 LocalFree (context);
126 if ((prev_context != NULL) && (!called)) {
127 prev_context -> skip_rest = TRUE;
128 context = prev_context;
129 }
130 context = prev_context;
131 }
132
133 /*******************************************************************
134 * WCMD_parameter - extract a parameter from a command line.
135 *
136 * Returns the 'n'th delimited parameter on the command line (zero-based).
137 * Parameter is in static storage overwritten on the next call.
138 * Parameters in quotes (and brackets) are handled.
139 * Also returns a pointer to the location of the parameter in the command line.
140 */
141
142 WCHAR *WCMD_parameter (WCHAR *s, int n, WCHAR **where) {
143
144 int i = 0;
145 static WCHAR param[MAX_PATH];
146 WCHAR *p;
147
148 if (where != NULL) *where = NULL;
149 p = param;
150 while (TRUE) {
151 switch (*s) {
152 case ' ': /* Skip leading spaces */
153 case '\t': /* Treat tabs as spaces */
154 s++;
155 break;
156 case '"':
157 if (where != NULL && i==n) *where = s;
158 s++;
159 while ((*s != '\0') && (*s != '"')) {
160 *p++ = *s++;
161 }
162 if (i == n) {
163 *p = '\0';
164 return param;
165 }
166 if (*s == '"') s++;
167 param[0] = '\0';
168 i++;
169 p = param;
170 break;
171 /* The code to handle bracketed parms is removed because it should no longer
172 be necessary after the multiline support has been added and the for loop
173 set of data is now parseable individually. */
174 case '\0':
175 return param;
176 default:
177 /* Only return where if it is for the right parameter */
178 if (where != NULL && i==n) *where = s;
179 while ((*s != '\0') && (*s != ' ') && (*s != ',') && (*s != '=') && (*s != '\t')) {
180 *p++ = *s++;
181 }
182 if (i == n && (p!=param)) {
183 *p = '\0';
184 return param;
185 }
186 /* Skip double delimiters, eg. dir a.a,,,,,b.b */
187 if (p != param) {
188 param[0] = '\0';
189 i++;
190 } else {
191 s++; /* Skip delimiter */
192 }
193 p = param;
194 }
195 }
196 }
197
198 /****************************************************************************
199 * WCMD_fgets
200 *
201 * Get one line from a batch file. We can't use the native f* functions because
202 * of the filename syntax differences between DOS and Unix. Also need to lose
203 * the LF (or CRLF) from the line.
204 */
205
206 WCHAR *WCMD_fgets (WCHAR *s, int noChars, HANDLE h) {
207
208 DWORD bytes;
209 BOOL status;
210 WCHAR *p;
211
212 p = s;
213 do {
214 status = WCMD_ReadFile (h, s, 1, &bytes, NULL);
215 if ((status == 0) || ((bytes == 0) && (s == p))) return NULL;
216 if (*s == '\n') bytes = 0;
217 else if (*s != '\r') {
218 s++;
219 noChars--;
220 }
221 *s = '\0';
222 } while ((bytes == 1) && (noChars > 1));
223 return p;
224 }
225
226 /* WCMD_splitpath - copied from winefile as no obvious way to use it otherwise */
227 void WCMD_splitpath(const WCHAR* path, WCHAR* drv, WCHAR* dir, WCHAR* name, WCHAR* ext)
228 {
229 const WCHAR* end; /* end of processed string */
230 const WCHAR* p; /* search pointer */
231 const WCHAR* s; /* copy pointer */
232
233 /* extract drive name */
234 if (path[0] && path[1]==':') {
235 if (drv) {
236 *drv++ = *path++;
237 *drv++ = *path++;
238 *drv = '\0';
239 }
240 } else if (drv)
241 *drv = '\0';
242
243 /* search for end of string or stream separator */
244 for(end=path; *end && *end!=':'; )
245 end++;
246
247 /* search for begin of file extension */
248 for(p=end; p>path && *--p!='\\' && *p!='/'; )
249 if (*p == '.') {
250 end = p;
251 break;
252 }
253
254 if (ext)
255 for(s=end; (*ext=*s++); )
256 ext++;
257
258 /* search for end of directory name */
259 for(p=end; p>path; )
260 if (*--p=='\\' || *p=='/') {
261 p++;
262 break;
263 }
264
265 if (name) {
266 for(s=p; s<end; )
267 *name++ = *s++;
268
269 *name = '\0';
270 }
271
272 if (dir) {
273 for(s=path; s<p; )
274 *dir++ = *s++;
275
276 *dir = '\0';
277 }
278 }
279
280 /****************************************************************************
281 * WCMD_HandleTildaModifiers
282 *
283 * Handle the ~ modifiers when expanding %0-9 or (%a-z in for command)
284 * %~xxxxxV (V=0-9 or A-Z)
285 * Where xxxx is any combination of:
286 * ~ - Removes quotes
287 * f - Fully qualified path (assumes current dir if not drive\dir)
288 * d - drive letter
289 * p - path
290 * n - filename
291 * x - file extension
292 * s - path with shortnames
293 * a - attributes
294 * t - date/time
295 * z - size
296 * $ENVVAR: - Searches ENVVAR for (contents of V) and expands to fully
297 * qualified path
298 *
299 * To work out the length of the modifier:
300 *
301 * Note: In the case of %0-9 knowing the end of the modifier is easy,
302 * but in a for loop, the for end WCHARacter may also be a modifier
303 * eg. for %a in (c:\a.a) do echo XXX
304 * where XXX = %~a (just ~)
305 * %~aa (~ and attributes)
306 * %~aaxa (~, attributes and extension)
307 * BUT %~aax (~ and attributes followed by 'x')
308 *
309 * Hence search forwards until find an invalid modifier, and then
310 * backwards until find for variable or 0-9
311 */
312 void WCMD_HandleTildaModifiers(WCHAR **start, WCHAR *forVariable, WCHAR *forValue, BOOL justFors) {
313
314 #define NUMMODIFIERS 11
315 static const WCHAR validmodifiers[NUMMODIFIERS] = {
316 '~', 'f', 'd', 'p', 'n', 'x', 's', 'a', 't', 'z', '$'
317 };
318 static const WCHAR space[] = {' ', '\0'};
319
320 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
321 WCHAR outputparam[MAX_PATH];
322 WCHAR finaloutput[MAX_PATH];
323 WCHAR fullfilename[MAX_PATH];
324 WCHAR thisoutput[MAX_PATH];
325 WCHAR *pos = *start+1;
326 WCHAR *firstModifier = pos;
327 WCHAR *lastModifier = NULL;
328 int modifierLen = 0;
329 BOOL finished = FALSE;
330 int i = 0;
331 BOOL exists = TRUE;
332 BOOL skipFileParsing = FALSE;
333 BOOL doneModifier = FALSE;
334
335 /* Search forwards until find invalid character modifier */
336 while (!finished) {
337
338 /* Work on the previous character */
339 if (lastModifier != NULL) {
340
341 for (i=0; i<NUMMODIFIERS; i++) {
342 if (validmodifiers[i] == *lastModifier) {
343
344 /* Special case '$' to skip until : found */
345 if (*lastModifier == '$') {
346 while (*pos != ':' && *pos) pos++;
347 if (*pos == 0x00) return; /* Invalid syntax */
348 pos++; /* Skip ':' */
349 }
350 break;
351 }
352 }
353
354 if (i==NUMMODIFIERS) {
355 finished = TRUE;
356 }
357 }
358
359 /* Save this one away */
360 if (!finished) {
361 lastModifier = pos;
362 pos++;
363 }
364 }
365
366 while (lastModifier > firstModifier) {
367 WINE_TRACE("Looking backwards for parameter id: %s / %s\n",
368 wine_dbgstr_w(lastModifier), wine_dbgstr_w(forVariable));
369
370 if (!justFors && context && (*lastModifier >= '' && *lastModifier <= '9')) {
371 /* Its a valid parameter identifier - OK */
372 break;
373
374 } else if (forVariable && *lastModifier == *(forVariable+1)) {
375 /* Its a valid parameter identifier - OK */
376 break;
377
378 } else {
379 lastModifier--;
380 }
381 }
382 if (lastModifier == firstModifier) return; /* Invalid syntax */
383
384 /* Extract the parameter to play with */
385 if ((*lastModifier >= '' && *lastModifier <= '9')) {
386 strcpyW(outputparam, WCMD_parameter (context -> command,
387 *lastModifier-'' + context -> shift_count[*lastModifier-''], NULL));
388 } else {
389 strcpyW(outputparam, forValue);
390 }
391
392 /* So now, firstModifier points to beginning of modifiers, lastModifier
393 points to the variable just after the modifiers. Process modifiers
394 in a specific order, remembering there could be duplicates */
395 modifierLen = lastModifier - firstModifier;
396 finaloutput[0] = 0x00;
397
398 /* Useful for debugging purposes: */
399 /*printf("Modifier string '%*.*s' and variable is %c\n Param starts as '%s'\n",
400 (modifierLen), (modifierLen), firstModifier, *lastModifier,
401 outputparam);*/
402
403 /* 1. Handle '~' : Strip surrounding quotes */
404 if (outputparam[0]=='"' &&
405 memchrW(firstModifier, '~', modifierLen) != NULL) {
406 int len = strlenW(outputparam);
407 if (outputparam[len-1] == '"') {
408 outputparam[len-1]=0x00;
409 len = len - 1;
410 }
411 memmove(outputparam, &outputparam[1], (len * sizeof(WCHAR))-1);
412 }
413
414 /* 2. Handle the special case of a $ */
415 if (memchrW(firstModifier, '$', modifierLen) != NULL) {
416 /* Special Case: Search envar specified in $[envvar] for outputparam
417 Note both $ and : are guaranteed otherwise check above would fail */
418 WCHAR *start = strchrW(firstModifier, '$') + 1;
419 WCHAR *end = strchrW(firstModifier, ':');
420 WCHAR env[MAX_PATH];
421 WCHAR fullpath[MAX_PATH];
422
423 /* Extract the env var */
424 memcpy(env, start, (end-start) * sizeof(WCHAR));
425 env[(end-start)] = 0x00;
426
427 /* If env var not found, return empty string */
428 if ((GetEnvironmentVariable(env, fullpath, MAX_PATH) == 0) ||
429 (SearchPath(fullpath, outputparam, NULL,
430 MAX_PATH, outputparam, NULL) == 0)) {
431 finaloutput[0] = 0x00;
432 outputparam[0] = 0x00;
433 skipFileParsing = TRUE;
434 }
435 }
436
437 /* After this, we need full information on the file,
438 which is valid not to exist. */
439 if (!skipFileParsing) {
440 if (GetFullPathName(outputparam, MAX_PATH, fullfilename, NULL) == 0)
441 return;
442
443 exists = GetFileAttributesExW(fullfilename, GetFileExInfoStandard,
444 &fileInfo);
445
446 /* 2. Handle 'a' : Output attributes */
447 if (exists &&
448 memchrW(firstModifier, 'a', modifierLen) != NULL) {
449
450 WCHAR defaults[] = {'-','-','-','-','-','-','-','-','-','\0'};
451 doneModifier = TRUE;
452 strcpyW(thisoutput, defaults);
453 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
454 thisoutput[0]='d';
455 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
456 thisoutput[1]='r';
457 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
458 thisoutput[2]='a';
459 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
460 thisoutput[3]='h';
461 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
462 thisoutput[4]='s';
463 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED)
464 thisoutput[5]='c';
465 /* FIXME: What are 6 and 7? */
466 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
467 thisoutput[8]='l';
468 strcatW(finaloutput, thisoutput);
469 }
470
471 /* 3. Handle 't' : Date+time */
472 if (exists &&
473 memchrW(firstModifier, 't', modifierLen) != NULL) {
474
475 SYSTEMTIME systime;
476 int datelen;
477
478 doneModifier = TRUE;
479 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
480
481 /* Format the time */
482 FileTimeToSystemTime(&fileInfo.ftLastWriteTime, &systime);
483 GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &systime,
484 NULL, thisoutput, MAX_PATH);
485 strcatW(thisoutput, space);
486 datelen = strlenW(thisoutput);
487 GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &systime,
488 NULL, (thisoutput+datelen), MAX_PATH-datelen);
489 strcatW(finaloutput, thisoutput);
490 }
491
492 /* 4. Handle 'z' : File length */
493 if (exists &&
494 memchrW(firstModifier, 'z', modifierLen) != NULL) {
495 /* FIXME: Output full 64 bit size (sprintf does not support I64 here) */
496 ULONG/*64*/ fullsize = /*(fileInfo.nFileSizeHigh << 32) +*/
497 fileInfo.nFileSizeLow;
498 static const WCHAR fmt[] = {'%','u','\0'};
499
500 doneModifier = TRUE;
501 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
502 wsprintf(thisoutput, fmt, fullsize);
503 strcatW(finaloutput, thisoutput);
504 }
505
506 /* 4. Handle 's' : Use short paths (File doesn't have to exist) */
507 if (memchrW(firstModifier, 's', modifierLen) != NULL) {
508 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
509 /* Don't flag as doneModifier - %~s on its own is processed later */
510 GetShortPathName(outputparam, outputparam,
511 sizeof(outputparam)/sizeof(outputparam[0]));
512 }
513
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 {
521
522 WCHAR drive[10];
523 WCHAR dir[MAX_PATH];
524 WCHAR fname[MAX_PATH];
525 WCHAR ext[MAX_PATH];
526 BOOL doneFileModifier = FALSE;
527
528 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
529
530 /* Split into components */
531 WCMD_splitpath(fullfilename, drive, dir, fname, ext);
532
533 /* 5. Handle 'd' : Drive Letter */
534 if (memchrW(firstModifier, 'd', modifierLen) != NULL) {
535 strcatW(finaloutput, drive);
536 doneModifier = TRUE;
537 doneFileModifier = TRUE;
538 }
539
540 /* 6. Handle 'p' : Path */
541 if (memchrW(firstModifier, 'p', modifierLen) != NULL) {
542 strcatW(finaloutput, dir);
543 doneModifier = TRUE;
544 doneFileModifier = TRUE;
545 }
546
547 /* 7. Handle 'n' : Name */
548 if (memchrW(firstModifier, 'n', modifierLen) != NULL) {
549 strcatW(finaloutput, fname);
550 doneModifier = TRUE;
551 doneFileModifier = TRUE;
552 }
553
554 /* 8. Handle 'x' : Ext */
555 if (memchrW(firstModifier, 'x', modifierLen) != NULL) {
556 strcatW(finaloutput, ext);
557 doneModifier = TRUE;
558 doneFileModifier = TRUE;
559 }
560
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);
567 }
568 }
569 }
570
571 /* If No other modifier processed, just add in parameter */
572 if (!doneModifier) strcpyW(finaloutput, outputparam);
573
574 /* Finish by inserting the replacement into the string */
575 WCMD_strsubstW(*start, lastModifier+1, finaloutput, -1);
576 }
577
578 /*******************************************************************
579 * WCMD_call - processes a batch call statement
580 *
581 * If there is a leading ':', calls within this batch program
582 * otherwise launches another program.
583 */
584 void WCMD_call (WCHAR *command) {
585
586 /* Run other program if no leading ':' */
587 if (*command != ':') {
588 WCMD_run_program(command, 1);
589 } else {
590
591 WCHAR gotoLabel[MAX_PATH];
592
593 strcpyW(gotoLabel, param1);
594
595 if (context) {
596
597 LARGE_INTEGER li;
598
599 /* Save the current file position, call the same file,
600 restore position */
601 li.QuadPart = 0;
602 li.u.LowPart = SetFilePointer(context -> h, li.u.LowPart,
603 &li.u.HighPart, FILE_CURRENT);
604
605 WCMD_batch (param1, command, 1, gotoLabel, context->h);
606
607 SetFilePointer(context -> h, li.u.LowPart,
608 &li.u.HighPart, FILE_BEGIN);
609 } else {
610 WCMD_output_asis( WCMD_LoadMessage(WCMD_CALLINSCRIPT));
611 }
612 }
613 }
614
This page was automatically generated by the
LXR engine.
Visit the LXR main site for more
information.