2 * CMD - Wine-compatible command line interface - batch interface.
4 * Copyright (C) 1999 D A Pickles
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 extern WCHAR quals
[MAX_PATH
], param1
[MAX_PATH
], param2
[MAX_PATH
];
25 extern BATCH_CONTEXT
*context
;
26 extern DWORD errorlevel
;
28 /****************************************************************************
31 * Open and execute a batch file.
32 * On entry *command includes the complete command line beginning with the name
33 * of the batch file (if a CALL command was entered the CALL has been removed).
34 * *file is the name of the file, which might not exist and may not have the
35 * .BAT suffix on. Called is 1 for a CALL, 0 otherwise.
37 * We need to handle recursion correctly, since one batch program might call another.
38 * So parameters for this batch file are held in a BATCH_CONTEXT structure.
40 * To support call within the same batch program, another input parameter is
41 * a label to goto once opened.
44 void WCMD_batch (WCHAR
*file
, WCHAR
*command
, int called
, WCHAR
*startLabel
, HANDLE pgmHandle
) {
46 #define WCMD_BATCH_EXT_SIZE 5
48 HANDLE h
= INVALID_HANDLE_VALUE
;
49 WCHAR string
[MAXSTRING
];
50 static const WCHAR extension_batch
[][WCMD_BATCH_EXT_SIZE
] = {{'.','b','a','t','\0'},
51 {'.','c','m','d','\0'}};
52 static const WCHAR extension_exe
[WCMD_BATCH_EXT_SIZE
] = {'.','e','x','e','\0'};
54 BATCH_CONTEXT
*prev_context
;
56 if (startLabel
== NULL
) {
57 for(i
=0; (i
<((sizeof(extension_batch
) * sizeof(WCHAR
))/WCMD_BATCH_EXT_SIZE
)) &&
58 (h
== INVALID_HANDLE_VALUE
); i
++) {
59 strcpyW (string
, file
);
61 if (strstrW (string
, extension_batch
[i
]) == NULL
) strcatW (string
, extension_batch
[i
]);
62 h
= CreateFile (string
, GENERIC_READ
, FILE_SHARE_READ
,
63 NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
65 if (h
== INVALID_HANDLE_VALUE
) {
66 strcpyW (string
, file
);
68 if (strstrW (string
, extension_exe
) == NULL
) strcatW (string
, extension_exe
);
69 h
= CreateFile (string
, GENERIC_READ
, FILE_SHARE_READ
,
70 NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
71 if (h
!= INVALID_HANDLE_VALUE
) {
72 WCMD_run_program (command
, 0);
74 SetLastError (ERROR_FILE_NOT_FOUND
);
80 DuplicateHandle(GetCurrentProcess(), pgmHandle
,
81 GetCurrentProcess(), &h
,
82 0, FALSE
, DUPLICATE_SAME_ACCESS
);
86 * Create a context structure for this batch file.
89 prev_context
= context
;
90 context
= (BATCH_CONTEXT
*)LocalAlloc (LMEM_FIXED
, sizeof (BATCH_CONTEXT
));
92 context
-> command
= command
;
93 memset(context
-> shift_count
, 0x00, sizeof(context
-> shift_count
));
94 context
-> prev_context
= prev_context
;
95 context
-> skip_rest
= FALSE
;
97 /* If processing a call :label, 'goto' the label in question */
99 strcpyW(param1
, startLabel
);
104 * Work through the file line by line. Specific batch commands are processed here,
105 * the rest are handled by the main command processor.
108 while (context
-> skip_rest
== FALSE
) {
109 CMD_LIST
*toExecute
= NULL
; /* Commands left to be executed */
110 if (WCMD_ReadAndParseLine(NULL
, &toExecute
, h
) == NULL
)
112 WCMD_process_commands(toExecute
, FALSE
, NULL
, NULL
);
113 WCMD_free_commands(toExecute
);
119 * If invoked by a CALL, we return to the context of our caller. Otherwise return
120 * to the caller's caller.
123 LocalFree ((HANDLE
)context
);
124 if ((prev_context
!= NULL
) && (!called
)) {
125 CloseHandle (prev_context
-> h
);
126 context
= prev_context
-> prev_context
;
127 LocalFree ((HANDLE
)prev_context
);
130 context
= prev_context
;
134 /*******************************************************************
135 * WCMD_parameter - extract a parameter from a command line.
137 * Returns the 'n'th space-delimited parameter on the command line (zero-based).
138 * Parameter is in static storage overwritten on the next call.
139 * Parameters in quotes (and brackets) are handled.
140 * Also returns a pointer to the location of the parameter in the command line.
143 WCHAR
*WCMD_parameter (WCHAR
*s
, int n
, WCHAR
**where
) {
146 static WCHAR param
[MAX_PATH
];
149 if (where
!= NULL
) *where
= NULL
;
157 if (where
!= NULL
&& i
==n
) *where
= s
;
159 while ((*s
!= '\0') && (*s
!= '"')) {
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. */
177 /* Only return where if it is for the right parameter */
178 if (where
!= NULL
&& i
==n
) *where
= s
;
179 while ((*s
!= '\0') && (*s
!= ' ')) {
193 /****************************************************************************
196 * Get one line from a batch file. We can't use the native f* functions because
197 * of the filename syntax differences between DOS and Unix. Also need to lose
198 * the LF (or CRLF) from the line.
201 WCHAR
*WCMD_fgets (WCHAR
*s
, int noChars
, HANDLE h
) {
209 status
= WCMD_ReadFile (h
, s
, 1, &bytes
, NULL
);
210 if ((status
== 0) || ((bytes
== 0) && (s
== p
))) return NULL
;
211 if (*s
== '\n') bytes
= 0;
212 else if (*s
!= '\r') {
217 } while ((bytes
== 1) && (noChars
> 1));
221 /* WCMD_splitpath - copied from winefile as no obvious way to use it otherwise */
222 void WCMD_splitpath(const WCHAR
* path
, WCHAR
* drv
, WCHAR
* dir
, WCHAR
* name
, WCHAR
* ext
)
224 const WCHAR
* end
; /* end of processed string */
225 const WCHAR
* p
; /* search pointer */
226 const WCHAR
* s
; /* copy pointer */
228 /* extract drive name */
229 if (path
[0] && path
[1]==':') {
238 /* search for end of string or stream separator */
239 for(end
=path
; *end
&& *end
!=':'; )
242 /* search for begin of file extension */
243 for(p
=end
; p
>path
&& *--p
!='\\' && *p
!='/'; )
250 for(s
=end
; (*ext
=*s
++); )
253 /* search for end of directory name */
255 if (*--p
=='\\' || *p
=='/') {
275 /****************************************************************************
276 * WCMD_HandleTildaModifiers
278 * Handle the ~ modifiers when expanding %0-9 or (%a-z in for command)
279 * %~xxxxxV (V=0-9 or A-Z)
280 * Where xxxx is any combination of:
282 * f - Fully qualified path (assumes current dir if not drive\dir)
287 * s - path with shortnames
291 * $ENVVAR: - Searches ENVVAR for (contents of V) and expands to fully
294 * To work out the length of the modifier:
296 * Note: In the case of %0-9 knowing the end of the modifier is easy,
297 * but in a for loop, the for end WCHARacter may also be a modifier
298 * eg. for %a in (c:\a.a) do echo XXX
299 * where XXX = %~a (just ~)
300 * %~aa (~ and attributes)
301 * %~aaxa (~, attributes and extension)
302 * BUT %~aax (~ and attributes followed by 'x')
304 * Hence search forwards until find an invalid modifier, and then
305 * backwards until find for variable or 0-9
307 void WCMD_HandleTildaModifiers(WCHAR
**start
, WCHAR
*forVariable
) {
309 #define NUMMODIFIERS 11
310 static const WCHAR validmodifiers
[NUMMODIFIERS
] = {
311 '~', 'f', 'd', 'p', 'n', 'x', 's', 'a', 't', 'z', '$'
313 static const WCHAR space
[] = {' ', '\0'};
315 WIN32_FILE_ATTRIBUTE_DATA fileInfo
;
316 WCHAR outputparam
[MAX_PATH
];
317 WCHAR finaloutput
[MAX_PATH
];
318 WCHAR fullfilename
[MAX_PATH
];
319 WCHAR thisoutput
[MAX_PATH
];
320 WCHAR
*pos
= *start
+1;
321 WCHAR
*firstModifier
= pos
;
322 WCHAR
*lastModifier
= NULL
;
324 BOOL finished
= FALSE
;
327 BOOL skipFileParsing
= FALSE
;
328 BOOL doneModifier
= FALSE
;
330 /* Search forwards until find invalid WCHARacter modifier */
333 /* Work on the previous WCHARacter */
334 if (lastModifier
!= NULL
) {
336 for (i
=0; i
<NUMMODIFIERS
; i
++) {
337 if (validmodifiers
[i
] == *lastModifier
) {
339 /* Special case '$' to skip until : found */
340 if (*lastModifier
== '$') {
341 while (*pos
!= ':' && *pos
) pos
++;
342 if (*pos
== 0x00) return; /* Invalid syntax */
343 pos
++; /* Skip ':' */
349 if (i
==NUMMODIFIERS
) {
354 /* Save this one away */
361 /* Now make sure the position we stopped at is a valid parameter */
362 if (!(*lastModifier
>= '0' || *lastModifier
<= '9') &&
363 (forVariable
!= NULL
) &&
364 (toupperW(*lastModifier
) != toupperW(*forVariable
))) {
366 /* Its not... Step backwards until it matches or we get to the start */
367 while (toupperW(*lastModifier
) != toupperW(*forVariable
) &&
368 lastModifier
> firstModifier
) {
371 if (lastModifier
== firstModifier
) return; /* Invalid syntax */
374 /* Extract the parameter to play with */
375 if ((*lastModifier
>= '0' && *lastModifier
<= '9')) {
376 strcpyW(outputparam
, WCMD_parameter (context
-> command
,
377 *lastModifier
-'0' + context
-> shift_count
[*lastModifier
-'0'], NULL
));
379 /* FIXME: Retrieve 'for' variable %c\n", *lastModifier); */
380 /* Need to get 'for' loop variable into outputparam */
384 /* So now, firstModifier points to beginning of modifiers, lastModifier
385 points to the variable just after the modifiers. Process modifiers
386 in a specific order, remembering there could be duplicates */
387 modifierLen
= lastModifier
- firstModifier
;
388 finaloutput
[0] = 0x00;
390 /* Useful for debugging purposes: */
391 /*printf("Modifier string '%*.*s' and variable is %c\n Param starts as '%s'\n",
392 (modifierLen), (modifierLen), firstModifier, *lastModifier,
395 /* 1. Handle '~' : Strip surrounding quotes */
396 if (outputparam
[0]=='"' &&
397 memchrW(firstModifier
, '~', modifierLen
) != NULL
) {
398 int len
= strlenW(outputparam
);
399 if (outputparam
[len
-1] == '"') {
400 outputparam
[len
-1]=0x00;
403 memmove(outputparam
, &outputparam
[1], (len
* sizeof(WCHAR
))-1);
406 /* 2. Handle the special case of a $ */
407 if (memchrW(firstModifier
, '$', modifierLen
) != NULL
) {
408 /* Special Case: Search envar specified in $[envvar] for outputparam
409 Note both $ and : are guaranteed otherwise check above would fail */
410 WCHAR
*start
= strchrW(firstModifier
, '$') + 1;
411 WCHAR
*end
= strchrW(firstModifier
, ':');
413 WCHAR fullpath
[MAX_PATH
];
415 /* Extract the env var */
416 memcpy(env
, start
, (end
-start
) * sizeof(WCHAR
));
417 env
[(end
-start
)] = 0x00;
419 /* If env var not found, return emptry string */
420 if ((GetEnvironmentVariable(env
, fullpath
, MAX_PATH
) == 0) ||
421 (SearchPath(fullpath
, outputparam
, NULL
,
422 MAX_PATH
, outputparam
, NULL
) == 0)) {
423 finaloutput
[0] = 0x00;
424 outputparam
[0] = 0x00;
425 skipFileParsing
= TRUE
;
429 /* After this, we need full information on the file,
430 which is valid not to exist. */
431 if (!skipFileParsing
) {
432 if (GetFullPathName(outputparam
, MAX_PATH
, fullfilename
, NULL
) == 0)
435 exists
= GetFileAttributesExW(fullfilename
, GetFileExInfoStandard
,
438 /* 2. Handle 'a' : Output attributes */
440 memchrW(firstModifier
, 'a', modifierLen
) != NULL
) {
442 WCHAR defaults
[] = {'-','-','-','-','-','-','-','-','-','\0'};
444 strcpyW(thisoutput
, defaults
);
445 if (fileInfo
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
447 if (fileInfo
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
)
449 if (fileInfo
.dwFileAttributes
& FILE_ATTRIBUTE_ARCHIVE
)
451 if (fileInfo
.dwFileAttributes
& FILE_ATTRIBUTE_HIDDEN
)
453 if (fileInfo
.dwFileAttributes
& FILE_ATTRIBUTE_SYSTEM
)
455 if (fileInfo
.dwFileAttributes
& FILE_ATTRIBUTE_COMPRESSED
)
457 /* FIXME: What are 6 and 7? */
458 if (fileInfo
.dwFileAttributes
& FILE_ATTRIBUTE_REPARSE_POINT
)
460 strcatW(finaloutput
, thisoutput
);
463 /* 3. Handle 't' : Date+time */
465 memchrW(firstModifier
, 't', modifierLen
) != NULL
) {
471 if (finaloutput
[0] != 0x00) strcatW(finaloutput
, space
);
473 /* Format the time */
474 FileTimeToSystemTime(&fileInfo
.ftLastWriteTime
, &systime
);
475 GetDateFormat(LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, &systime
,
476 NULL
, thisoutput
, MAX_PATH
);
477 strcatW(thisoutput
, space
);
478 datelen
= strlenW(thisoutput
);
479 GetTimeFormat(LOCALE_USER_DEFAULT
, TIME_NOSECONDS
, &systime
,
480 NULL
, (thisoutput
+datelen
), MAX_PATH
-datelen
);
481 strcatW(finaloutput
, thisoutput
);
484 /* 4. Handle 'z' : File length */
486 memchrW(firstModifier
, 'z', modifierLen
) != NULL
) {
487 /* FIXME: Output full 64 bit size (sprintf does not support I64 here) */
488 ULONG
/*64*/ fullsize
= /*(fileInfo.nFileSizeHigh << 32) +*/
489 fileInfo
.nFileSizeLow
;
490 static const WCHAR fmt
[] = {'%','u','\0'};
493 if (finaloutput
[0] != 0x00) strcatW(finaloutput
, space
);
494 wsprintf(thisoutput
, fmt
, fullsize
);
495 strcatW(finaloutput
, thisoutput
);
498 /* 4. Handle 's' : Use short paths (File doesn't have to exist) */
499 if (memchrW(firstModifier
, 's', modifierLen
) != NULL
) {
500 if (finaloutput
[0] != 0x00) strcatW(finaloutput
, space
);
501 /* Don't flag as doneModifier - %~s on its own is processed later */
502 GetShortPathName(outputparam
, outputparam
, sizeof(outputparam
));
505 /* 5. Handle 'f' : Fully qualified path (File doesn't have to exist) */
506 /* Note this overrides d,p,n,x */
507 if (memchrW(firstModifier
, 'f', modifierLen
) != NULL
) {
509 if (finaloutput
[0] != 0x00) strcatW(finaloutput
, space
);
510 strcatW(finaloutput
, fullfilename
);
515 WCHAR fname
[MAX_PATH
];
517 BOOL doneFileModifier
= FALSE
;
519 if (finaloutput
[0] != 0x00) strcatW(finaloutput
, space
);
521 /* Split into components */
522 WCMD_splitpath(fullfilename
, drive
, dir
, fname
, ext
);
524 /* 5. Handle 'd' : Drive Letter */
525 if (memchrW(firstModifier
, 'd', modifierLen
) != NULL
) {
526 strcatW(finaloutput
, drive
);
528 doneFileModifier
= TRUE
;
531 /* 6. Handle 'p' : Path */
532 if (memchrW(firstModifier
, 'p', modifierLen
) != NULL
) {
533 strcatW(finaloutput
, dir
);
535 doneFileModifier
= TRUE
;
538 /* 7. Handle 'n' : Name */
539 if (memchrW(firstModifier
, 'n', modifierLen
) != NULL
) {
540 strcatW(finaloutput
, fname
);
542 doneFileModifier
= TRUE
;
545 /* 8. Handle 'x' : Ext */
546 if (memchrW(firstModifier
, 'x', modifierLen
) != NULL
) {
547 strcatW(finaloutput
, ext
);
549 doneFileModifier
= TRUE
;
552 /* If 's' but no other parameter, dump the whole thing */
553 if (!doneFileModifier
&&
554 memchrW(firstModifier
, 's', modifierLen
) != NULL
) {
556 if (finaloutput
[0] != 0x00) strcatW(finaloutput
, space
);
557 strcatW(finaloutput
, outputparam
);
562 /* If No other modifier processed, just add in parameter */
563 if (!doneModifier
) strcpyW(finaloutput
, outputparam
);
565 /* Finish by inserting the replacement into the string */
566 pos
= WCMD_strdupW(lastModifier
+1);
567 strcpyW(*start
, finaloutput
);
568 strcatW(*start
, pos
);
572 /*******************************************************************
573 * WCMD_call - processes a batch call statement
575 * If there is a leading ':', calls within this batch program
576 * otherwise launches another program.
578 void WCMD_call (WCHAR
*command
) {
580 /* Run other program if no leading ':' */
581 if (*command
!= ':') {
582 WCMD_run_program(command
, 1);
585 WCHAR gotoLabel
[MAX_PATH
];
587 strcpyW(gotoLabel
, param1
);
593 /* Save the current file position, call the same file,
596 li
.u
.LowPart
= SetFilePointer(context
-> h
, li
.u
.LowPart
,
597 &li
.u
.HighPart
, FILE_CURRENT
);
599 WCMD_batch (param1
, command
, 1, gotoLabel
, context
->h
);
601 SetFilePointer(context
-> h
, li
.u
.LowPart
,
602 &li
.u
.HighPart
, FILE_BEGIN
);
604 WCMD_output_asis( WCMD_LoadMessage(WCMD_CALLINSCRIPT
));