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
&& WCMD_fgets (string
, sizeof(string
), h
)) {
109 if (strlenW(string
) == MAXSTRING
-1) {
110 WCMD_output_asis( WCMD_LoadMessage(WCMD_TRUNCATEDLINE
));
111 WCMD_output_asis( string
);
112 WCMD_output_asis( newline
);
114 if (string
[0] != ':') { /* Skip over labels */
115 WCMD_process_command (string
);
121 * If invoked by a CALL, we return to the context of our caller. Otherwise return
122 * to the caller's caller.
125 LocalFree ((HANDLE
)context
);
126 if ((prev_context
!= NULL
) && (!called
)) {
127 CloseHandle (prev_context
-> h
);
128 context
= prev_context
-> prev_context
;
129 LocalFree ((HANDLE
)prev_context
);
132 context
= prev_context
;
136 /*******************************************************************
137 * WCMD_parameter - extract a parameter from a command line.
139 * Returns the 'n'th space-delimited parameter on the command line (zero-based).
140 * Parameter is in static storage overwritten on the next call.
141 * Parameters in quotes (and brackets) are handled.
142 * Also returns a pointer to the location of the parameter in the command line.
145 WCHAR
*WCMD_parameter (WCHAR
*s
, int n
, WCHAR
**where
) {
148 static WCHAR param
[MAX_PATH
];
151 if (where
!= NULL
) *where
= NULL
;
159 if (where
!= NULL
&& i
==n
) *where
= s
;
161 while ((*s
!= '\0') && (*s
!= '"')) {
174 if (where
!= NULL
&& i
==n
) *where
= s
;
176 while ((*s
!= '\0') && (*s
!= ')')) {
191 /* Only return where if it is for the right parameter */
192 if (where
!= NULL
&& i
==n
) *where
= s
;
193 while ((*s
!= '\0') && (*s
!= ' ')) {
207 /****************************************************************************
210 * Get one line from a batch file. We can't use the native f* functions because
211 * of the filename syntax differences between DOS and Unix. Also need to lose
212 * the LF (or CRLF) from the line.
215 WCHAR
*WCMD_fgets (WCHAR
*s
, int noChars
, HANDLE h
) {
223 status
= WCMD_ReadFile (h
, s
, 1, &bytes
, NULL
);
224 if ((status
== 0) || ((bytes
== 0) && (s
== p
))) return NULL
;
225 if (*s
== '\n') bytes
= 0;
226 else if (*s
!= '\r') {
231 } while ((bytes
== 1) && (noChars
> 1));
235 /* WCMD_splitpath - copied from winefile as no obvious way to use it otherwise */
236 void WCMD_splitpath(const WCHAR
* path
, WCHAR
* drv
, WCHAR
* dir
, WCHAR
* name
, WCHAR
* ext
)
238 const WCHAR
* end
; /* end of processed string */
239 const WCHAR
* p
; /* search pointer */
240 const WCHAR
* s
; /* copy pointer */
242 /* extract drive name */
243 if (path
[0] && path
[1]==':') {
252 /* search for end of string or stream separator */
253 for(end
=path
; *end
&& *end
!=':'; )
256 /* search for begin of file extension */
257 for(p
=end
; p
>path
&& *--p
!='\\' && *p
!='/'; )
264 for(s
=end
; (*ext
=*s
++); )
267 /* search for end of directory name */
269 if (*--p
=='\\' || *p
=='/') {
289 /****************************************************************************
290 * WCMD_HandleTildaModifiers
292 * Handle the ~ modifiers when expanding %0-9 or (%a-z in for command)
293 * %~xxxxxV (V=0-9 or A-Z)
294 * Where xxxx is any combination of:
296 * f - Fully qualified path (assumes current dir if not drive\dir)
301 * s - path with shortnames
305 * $ENVVAR: - Searches ENVVAR for (contents of V) and expands to fully
308 * To work out the length of the modifier:
310 * Note: In the case of %0-9 knowing the end of the modifier is easy,
311 * but in a for loop, the for end WCHARacter may also be a modifier
312 * eg. for %a in (c:\a.a) do echo XXX
313 * where XXX = %~a (just ~)
314 * %~aa (~ and attributes)
315 * %~aaxa (~, attributes and extension)
316 * BUT %~aax (~ and attributes followed by 'x')
318 * Hence search forwards until find an invalid modifier, and then
319 * backwards until find for variable or 0-9
321 void WCMD_HandleTildaModifiers(WCHAR
**start
, WCHAR
*forVariable
) {
323 #define NUMMODIFIERS 11
324 static const WCHAR validmodifiers
[NUMMODIFIERS
] = {
325 '~', 'f', 'd', 'p', 'n', 'x', 's', 'a', 't', 'z', '$'
327 static const WCHAR space
[] = {' ', '\0'};
329 WIN32_FILE_ATTRIBUTE_DATA fileInfo
;
330 WCHAR outputparam
[MAX_PATH
];
331 WCHAR finaloutput
[MAX_PATH
];
332 WCHAR fullfilename
[MAX_PATH
];
333 WCHAR thisoutput
[MAX_PATH
];
334 WCHAR
*pos
= *start
+1;
335 WCHAR
*firstModifier
= pos
;
336 WCHAR
*lastModifier
= NULL
;
338 BOOL finished
= FALSE
;
341 BOOL skipFileParsing
= FALSE
;
342 BOOL doneModifier
= FALSE
;
344 /* Search forwards until find invalid WCHARacter modifier */
347 /* Work on the previous WCHARacter */
348 if (lastModifier
!= NULL
) {
350 for (i
=0; i
<NUMMODIFIERS
; i
++) {
351 if (validmodifiers
[i
] == *lastModifier
) {
353 /* Special case '$' to skip until : found */
354 if (*lastModifier
== '$') {
355 while (*pos
!= ':' && *pos
) pos
++;
356 if (*pos
== 0x00) return; /* Invalid syntax */
357 pos
++; /* Skip ':' */
363 if (i
==NUMMODIFIERS
) {
368 /* Save this one away */
375 /* Now make sure the position we stopped at is a valid parameter */
376 if (!(*lastModifier
>= '0' || *lastModifier
<= '9') &&
377 (forVariable
!= NULL
) &&
378 (toupperW(*lastModifier
) != toupperW(*forVariable
))) {
380 /* Its not... Step backwards until it matches or we get to the start */
381 while (toupperW(*lastModifier
) != toupperW(*forVariable
) &&
382 lastModifier
> firstModifier
) {
385 if (lastModifier
== firstModifier
) return; /* Invalid syntax */
388 /* Extract the parameter to play with */
389 if ((*lastModifier
>= '0' && *lastModifier
<= '9')) {
390 strcpyW(outputparam
, WCMD_parameter (context
-> command
,
391 *lastModifier
-'0' + context
-> shift_count
[*lastModifier
-'0'], NULL
));
393 /* FIXME: Retrieve 'for' variable %c\n", *lastModifier); */
394 /* Need to get 'for' loop variable into outputparam */
398 /* So now, firstModifier points to beginning of modifiers, lastModifier
399 points to the variable just after the modifiers. Process modifiers
400 in a specific order, remembering there could be duplicates */
401 modifierLen
= lastModifier
- firstModifier
;
402 finaloutput
[0] = 0x00;
404 /* Useful for debugging purposes: */
405 /*printf("Modifier string '%*.*s' and variable is %c\n Param starts as '%s'\n",
406 (modifierLen), (modifierLen), firstModifier, *lastModifier,
409 /* 1. Handle '~' : Strip surrounding quotes */
410 if (outputparam
[0]=='"' &&
411 memchrW(firstModifier
, '~', modifierLen
) != NULL
) {
412 int len
= strlenW(outputparam
);
413 if (outputparam
[len
-1] == '"') {
414 outputparam
[len
-1]=0x00;
417 memmove(outputparam
, &outputparam
[1], (len
* sizeof(WCHAR
))-1);
420 /* 2. Handle the special case of a $ */
421 if (memchrW(firstModifier
, '$', modifierLen
) != NULL
) {
422 /* Special Case: Search envar specified in $[envvar] for outputparam
423 Note both $ and : are guaranteed otherwise check above would fail */
424 WCHAR
*start
= strchrW(firstModifier
, '$') + 1;
425 WCHAR
*end
= strchrW(firstModifier
, ':');
427 WCHAR fullpath
[MAX_PATH
];
429 /* Extract the env var */
430 memcpy(env
, start
, (end
-start
) * sizeof(WCHAR
));
431 env
[(end
-start
)] = 0x00;
433 /* If env var not found, return emptry string */
434 if ((GetEnvironmentVariable(env
, fullpath
, MAX_PATH
) == 0) ||
435 (SearchPath(fullpath
, outputparam
, NULL
,
436 MAX_PATH
, outputparam
, NULL
) == 0)) {
437 finaloutput
[0] = 0x00;
438 outputparam
[0] = 0x00;
439 skipFileParsing
= TRUE
;
443 /* After this, we need full information on the file,
444 which is valid not to exist. */
445 if (!skipFileParsing
) {
446 if (GetFullPathName(outputparam
, MAX_PATH
, fullfilename
, NULL
) == 0)
449 exists
= GetFileAttributesExW(fullfilename
, GetFileExInfoStandard
,
452 /* 2. Handle 'a' : Output attributes */
454 memchrW(firstModifier
, 'a', modifierLen
) != NULL
) {
456 WCHAR defaults
[] = {'-','-','-','-','-','-','-','-','-','\0'};
458 strcpyW(thisoutput
, defaults
);
459 if (fileInfo
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
461 if (fileInfo
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
)
463 if (fileInfo
.dwFileAttributes
& FILE_ATTRIBUTE_ARCHIVE
)
465 if (fileInfo
.dwFileAttributes
& FILE_ATTRIBUTE_HIDDEN
)
467 if (fileInfo
.dwFileAttributes
& FILE_ATTRIBUTE_SYSTEM
)
469 if (fileInfo
.dwFileAttributes
& FILE_ATTRIBUTE_COMPRESSED
)
471 /* FIXME: What are 6 and 7? */
472 if (fileInfo
.dwFileAttributes
& FILE_ATTRIBUTE_REPARSE_POINT
)
474 strcatW(finaloutput
, thisoutput
);
477 /* 3. Handle 't' : Date+time */
479 memchrW(firstModifier
, 't', modifierLen
) != NULL
) {
485 if (finaloutput
[0] != 0x00) strcatW(finaloutput
, space
);
487 /* Format the time */
488 FileTimeToSystemTime(&fileInfo
.ftLastWriteTime
, &systime
);
489 GetDateFormat(LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, &systime
,
490 NULL
, thisoutput
, MAX_PATH
);
491 strcatW(thisoutput
, space
);
492 datelen
= strlenW(thisoutput
);
493 GetTimeFormat(LOCALE_USER_DEFAULT
, TIME_NOSECONDS
, &systime
,
494 NULL
, (thisoutput
+datelen
), MAX_PATH
-datelen
);
495 strcatW(finaloutput
, thisoutput
);
498 /* 4. Handle 'z' : File length */
500 memchrW(firstModifier
, 'z', modifierLen
) != NULL
) {
501 /* FIXME: Output full 64 bit size (sprintf does not support I64 here) */
502 ULONG
/*64*/ fullsize
= /*(fileInfo.nFileSizeHigh << 32) +*/
503 fileInfo
.nFileSizeLow
;
504 static const WCHAR fmt
[] = {'%','u','\0'};
507 if (finaloutput
[0] != 0x00) strcatW(finaloutput
, space
);
508 wsprintf(thisoutput
, fmt
, fullsize
);
509 strcatW(finaloutput
, thisoutput
);
512 /* 4. Handle 's' : Use short paths (File doesn't have to exist) */
513 if (memchrW(firstModifier
, 's', modifierLen
) != NULL
) {
514 if (finaloutput
[0] != 0x00) strcatW(finaloutput
, space
);
515 /* Don't flag as doneModifier - %~s on its own is processed later */
516 GetShortPathName(outputparam
, outputparam
, sizeof(outputparam
));
519 /* 5. Handle 'f' : Fully qualified path (File doesn't have to exist) */
520 /* Note this overrides d,p,n,x */
521 if (memchrW(firstModifier
, 'f', modifierLen
) != NULL
) {
523 if (finaloutput
[0] != 0x00) strcatW(finaloutput
, space
);
524 strcatW(finaloutput
, fullfilename
);
529 WCHAR fname
[MAX_PATH
];
531 BOOL doneFileModifier
= FALSE
;
533 if (finaloutput
[0] != 0x00) strcatW(finaloutput
, space
);
535 /* Split into components */
536 WCMD_splitpath(fullfilename
, drive
, dir
, fname
, ext
);
538 /* 5. Handle 'd' : Drive Letter */
539 if (memchrW(firstModifier
, 'd', modifierLen
) != NULL
) {
540 strcatW(finaloutput
, drive
);
542 doneFileModifier
= TRUE
;
545 /* 6. Handle 'p' : Path */
546 if (memchrW(firstModifier
, 'p', modifierLen
) != NULL
) {
547 strcatW(finaloutput
, dir
);
549 doneFileModifier
= TRUE
;
552 /* 7. Handle 'n' : Name */
553 if (memchrW(firstModifier
, 'n', modifierLen
) != NULL
) {
554 strcatW(finaloutput
, fname
);
556 doneFileModifier
= TRUE
;
559 /* 8. Handle 'x' : Ext */
560 if (memchrW(firstModifier
, 'x', modifierLen
) != NULL
) {
561 strcatW(finaloutput
, ext
);
563 doneFileModifier
= TRUE
;
566 /* If 's' but no other parameter, dump the whole thing */
567 if (!doneFileModifier
&&
568 memchrW(firstModifier
, 's', modifierLen
) != NULL
) {
570 if (finaloutput
[0] != 0x00) strcatW(finaloutput
, space
);
571 strcatW(finaloutput
, outputparam
);
576 /* If No other modifier processed, just add in parameter */
577 if (!doneModifier
) strcpyW(finaloutput
, outputparam
);
579 /* Finish by inserting the replacement into the string */
580 pos
= WCMD_strdupW(lastModifier
+1);
581 strcpyW(*start
, finaloutput
);
582 strcatW(*start
, pos
);
586 /*******************************************************************
587 * WCMD_call - processes a batch call statement
589 * If there is a leading ':', calls within this batch program
590 * otherwise launches another program.
592 void WCMD_call (WCHAR
*command
) {
594 /* Run other program if no leading ':' */
595 if (*command
!= ':') {
596 WCMD_run_program(command
, 1);
599 WCHAR gotoLabel
[MAX_PATH
];
601 strcpyW(gotoLabel
, param1
);
607 /* Save the current file position, call the same file,
610 li
.u
.LowPart
= SetFilePointer(context
-> h
, li
.u
.LowPart
,
611 &li
.u
.HighPart
, FILE_CURRENT
);
613 WCMD_batch (param1
, command
, 1, gotoLabel
, context
->h
);
615 SetFilePointer(context
-> h
, li
.u
.LowPart
,
616 &li
.u
.HighPart
, FILE_BEGIN
);
618 WCMD_output_asis( WCMD_LoadMessage(WCMD_CALLINSCRIPT
));