mshtml: Forward all IsDirty implementations to one function.
[wine/wine64.git] / programs / cmd / batch.c
blobc301bcc62826ae17712fc8bb84fec00e0a8fc581
1 /*
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
21 #include "wcmd.h"
23 extern int echo_mode;
24 extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
25 extern BATCH_CONTEXT *context;
26 extern DWORD errorlevel;
28 /****************************************************************************
29 * WCMD_batch
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'};
53 unsigned int i;
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);
60 CharLower (string);
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);
67 CharLower (string);
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);
73 } else {
74 SetLastError (ERROR_FILE_NOT_FOUND);
75 WCMD_print_error ();
77 return;
79 } else {
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));
91 context -> h = h;
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 */
98 if (startLabel) {
99 strcpyW(param1, startLabel);
100 WCMD_goto();
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);
118 CloseHandle (h);
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);
131 else {
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) {
147 int i = 0;
148 static WCHAR param[MAX_PATH];
149 WCHAR *p;
151 if (where != NULL) *where = NULL;
152 p = param;
153 while (TRUE) {
154 switch (*s) {
155 case ' ':
156 s++;
157 break;
158 case '"':
159 if (where != NULL && i==n) *where = s;
160 s++;
161 while ((*s != '\0') && (*s != '"')) {
162 *p++ = *s++;
164 if (i == n) {
165 *p = '\0';
166 return param;
168 if (*s == '"') s++;
169 param[0] = '\0';
170 i++;
171 p = param;
172 break;
173 case '(':
174 if (where != NULL && i==n) *where = s;
175 s++;
176 while ((*s != '\0') && (*s != ')')) {
177 *p++ = *s++;
179 if (i == n) {
180 *p = '\0';
181 return param;
183 if (*s == ')') s++;
184 param[0] = '\0';
185 i++;
186 p = param;
187 break;
188 case '\0':
189 return param;
190 default:
191 /* Only return where if it is for the right parameter */
192 if (where != NULL && i==n) *where = s;
193 while ((*s != '\0') && (*s != ' ')) {
194 *p++ = *s++;
196 if (i == n) {
197 *p = '\0';
198 return param;
200 param[0] = '\0';
201 i++;
202 p = param;
207 /****************************************************************************
208 * WCMD_fgets
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) {
217 DWORD bytes;
218 BOOL status;
219 WCHAR *p;
221 p = s;
222 do {
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') {
227 s++;
228 noChars--;
230 *s = '\0';
231 } while ((bytes == 1) && (noChars > 1));
232 return p;
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]==':') {
244 if (drv) {
245 *drv++ = *path++;
246 *drv++ = *path++;
247 *drv = '\0';
249 } else if (drv)
250 *drv = '\0';
252 /* search for end of string or stream separator */
253 for(end=path; *end && *end!=':'; )
254 end++;
256 /* search for begin of file extension */
257 for(p=end; p>path && *--p!='\\' && *p!='/'; )
258 if (*p == '.') {
259 end = p;
260 break;
263 if (ext)
264 for(s=end; (*ext=*s++); )
265 ext++;
267 /* search for end of directory name */
268 for(p=end; p>path; )
269 if (*--p=='\\' || *p=='/') {
270 p++;
271 break;
274 if (name) {
275 for(s=p; s<end; )
276 *name++ = *s++;
278 *name = '\0';
281 if (dir) {
282 for(s=path; s<p; )
283 *dir++ = *s++;
285 *dir = '\0';
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:
295 * ~ - Removes quotes
296 * f - Fully qualified path (assumes current dir if not drive\dir)
297 * d - drive letter
298 * p - path
299 * n - filename
300 * x - file extension
301 * s - path with shortnames
302 * a - attributes
303 * t - date/time
304 * z - size
305 * $ENVVAR: - Searches ENVVAR for (contents of V) and expands to fully
306 * qualified path
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;
337 int modifierLen = 0;
338 BOOL finished = FALSE;
339 int i = 0;
340 BOOL exists = TRUE;
341 BOOL skipFileParsing = FALSE;
342 BOOL doneModifier = FALSE;
344 /* Search forwards until find invalid WCHARacter modifier */
345 while (!finished) {
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 ':' */
359 break;
363 if (i==NUMMODIFIERS) {
364 finished = TRUE;
368 /* Save this one away */
369 if (!finished) {
370 lastModifier = pos;
371 pos++;
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) {
383 lastModifier--;
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));
392 } else {
393 /* FIXME: Retrieve 'for' variable %c\n", *lastModifier); */
394 /* Need to get 'for' loop variable into outputparam */
395 return;
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,
407 outputparam);*/
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;
415 len = len - 1;
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, ':');
426 WCHAR env[MAX_PATH];
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)
447 return;
449 exists = GetFileAttributesExW(fullfilename, GetFileExInfoStandard,
450 &fileInfo);
452 /* 2. Handle 'a' : Output attributes */
453 if (exists &&
454 memchrW(firstModifier, 'a', modifierLen) != NULL) {
456 WCHAR defaults[] = {'-','-','-','-','-','-','-','-','-','\0'};
457 doneModifier = TRUE;
458 strcpyW(thisoutput, defaults);
459 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
460 thisoutput[0]='d';
461 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
462 thisoutput[1]='r';
463 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
464 thisoutput[2]='a';
465 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
466 thisoutput[3]='h';
467 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
468 thisoutput[4]='s';
469 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED)
470 thisoutput[5]='c';
471 /* FIXME: What are 6 and 7? */
472 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
473 thisoutput[8]='l';
474 strcatW(finaloutput, thisoutput);
477 /* 3. Handle 't' : Date+time */
478 if (exists &&
479 memchrW(firstModifier, 't', modifierLen) != NULL) {
481 SYSTEMTIME systime;
482 int datelen;
484 doneModifier = TRUE;
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 */
499 if (exists &&
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'};
506 doneModifier = TRUE;
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) {
522 doneModifier = TRUE;
523 if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
524 strcatW(finaloutput, fullfilename);
525 } else {
527 WCHAR drive[10];
528 WCHAR dir[MAX_PATH];
529 WCHAR fname[MAX_PATH];
530 WCHAR ext[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);
541 doneModifier = TRUE;
542 doneFileModifier = TRUE;
545 /* 6. Handle 'p' : Path */
546 if (memchrW(firstModifier, 'p', modifierLen) != NULL) {
547 strcatW(finaloutput, dir);
548 doneModifier = TRUE;
549 doneFileModifier = TRUE;
552 /* 7. Handle 'n' : Name */
553 if (memchrW(firstModifier, 'n', modifierLen) != NULL) {
554 strcatW(finaloutput, fname);
555 doneModifier = TRUE;
556 doneFileModifier = TRUE;
559 /* 8. Handle 'x' : Ext */
560 if (memchrW(firstModifier, 'x', modifierLen) != NULL) {
561 strcatW(finaloutput, ext);
562 doneModifier = TRUE;
563 doneFileModifier = TRUE;
566 /* If 's' but no other parameter, dump the whole thing */
567 if (!doneFileModifier &&
568 memchrW(firstModifier, 's', modifierLen) != NULL) {
569 doneModifier = TRUE;
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);
583 free(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);
597 } else {
599 WCHAR gotoLabel[MAX_PATH];
601 strcpyW(gotoLabel, param1);
603 if (context) {
605 LARGE_INTEGER li;
607 /* Save the current file position, call the same file,
608 restore position */
609 li.QuadPart = 0;
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);
617 } else {
618 WCMD_output_asis( WCMD_LoadMessage(WCMD_CALLINSCRIPT));