crypt32: Introduce function to encode an array of items as a set.
[wine] / programs / cmd / batch.c
1 /*
2  * CMD - Wine-compatible command line interface - batch interface.
3  *
4  * Copyright (C) 1999 D A Pickles
5  *
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.
10  *
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.
15  *
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
19  */
20
21 #include "wcmd.h"
22
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;
27
28 /****************************************************************************
29  * WCMD_batch
30  *
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.
36  *
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.
39  *
40  * To support call within the same batch program, another input parameter is
41  * a label to goto once opened.
42  */
43
44 void WCMD_batch (WCHAR *file, WCHAR *command, int called, WCHAR *startLabel, HANDLE pgmHandle) {
45
46 #define WCMD_BATCH_EXT_SIZE 5
47
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;
55
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);
64     }
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 ();
76       }
77       return;
78     }
79   } else {
80     DuplicateHandle(GetCurrentProcess(), pgmHandle,
81                     GetCurrentProcess(), &h,
82                     0, FALSE, DUPLICATE_SAME_ACCESS);
83   }
84
85 /*
86  *      Create a context structure for this batch file.
87  */
88
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;
96
97   /* If processing a call :label, 'goto' the label in question */
98   if (startLabel) {
99     strcpyW(param1, startLabel);
100     WCMD_goto(NULL);
101   }
102
103 /*
104  *      Work through the file line by line. Specific batch commands are processed here,
105  *      the rest are handled by the main command processor.
106  */
107
108   while (context -> skip_rest == FALSE) {
109       CMD_LIST *toExecute = NULL;         /* Commands left to be executed */
110       if (WCMD_ReadAndParseLine(NULL, &toExecute, h) == NULL)
111         break;
112       WCMD_process_commands(toExecute, FALSE, NULL, NULL);
113       WCMD_free_commands(toExecute);
114       toExecute = NULL;
115   }
116   CloseHandle (h);
117
118 /*
119  *      If invoked by a CALL, we return to the context of our caller. Otherwise return
120  *      to the caller's caller.
121  */
122
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);
128   }
129   else {
130     context = prev_context;
131   }
132 }
133
134 /*******************************************************************
135  * WCMD_parameter - extract a parameter from a command line.
136  *
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.
141  */
142
143 WCHAR *WCMD_parameter (WCHAR *s, int n, WCHAR **where) {
144
145   int i = 0;
146   static WCHAR param[MAX_PATH];
147   WCHAR *p;
148
149   if (where != NULL) *where = NULL;
150   p = param;
151   while (TRUE) {
152     switch (*s) {
153       case ' ':
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 != ' ')) {
180           *p++ = *s++;
181         }
182         if (i == n) {
183           *p = '\0';
184           return param;
185         }
186           param[0] = '\0';
187           i++;
188         p = param;
189     }
190   }
191 }
192
193 /****************************************************************************
194  * WCMD_fgets
195  *
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.
199  */
200
201 WCHAR *WCMD_fgets (WCHAR *s, int noChars, HANDLE h) {
202
203   DWORD bytes;
204   BOOL status;
205   WCHAR *p;
206
207   p = s;
208   do {
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') {
213       s++;
214       noChars--;
215     }
216     *s = '\0';
217   } while ((bytes == 1) && (noChars > 1));
218   return p;
219 }
220
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)
223 {
224         const WCHAR* end; /* end of processed string */
225         const WCHAR* p;  /* search pointer */
226         const WCHAR* s;  /* copy pointer */
227
228         /* extract drive name */
229         if (path[0] && path[1]==':') {
230                 if (drv) {
231                         *drv++ = *path++;
232                         *drv++ = *path++;
233                         *drv = '\0';
234                 }
235         } else if (drv)
236                 *drv = '\0';
237
238         /* search for end of string or stream separator */
239         for(end=path; *end && *end!=':'; )
240                 end++;
241
242         /* search for begin of file extension */
243         for(p=end; p>path && *--p!='\\' && *p!='/'; )
244                 if (*p == '.') {
245                         end = p;
246                         break;
247                 }
248
249         if (ext)
250                 for(s=end; (*ext=*s++); )
251                         ext++;
252
253         /* search for end of directory name */
254         for(p=end; p>path; )
255                 if (*--p=='\\' || *p=='/') {
256                         p++;
257                         break;
258                 }
259
260         if (name) {
261                 for(s=p; s<end; )
262                         *name++ = *s++;
263
264                 *name = '\0';
265         }
266
267         if (dir) {
268                 for(s=path; s<p; )
269                         *dir++ = *s++;
270
271                 *dir = '\0';
272         }
273 }
274
275 /****************************************************************************
276  * WCMD_HandleTildaModifiers
277  *
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:
281  *    ~ - Removes quotes
282  *    f - Fully qualified path (assumes current dir if not drive\dir)
283  *    d - drive letter
284  *    p - path
285  *    n - filename
286  *    x - file extension
287  *    s - path with shortnames
288  *    a - attributes
289  *    t - date/time
290  *    z - size
291  *    $ENVVAR: - Searches ENVVAR for (contents of V) and expands to fully
292  *                   qualified path
293  *
294  *  To work out the length of the modifier:
295  *
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')
303  *
304  *  Hence search forwards until find an invalid modifier, and then
305  *  backwards until find for variable or 0-9
306  */
307 void WCMD_HandleTildaModifiers(WCHAR **start, WCHAR *forVariable) {
308
309 #define NUMMODIFIERS 11
310   static const WCHAR validmodifiers[NUMMODIFIERS] = {
311         '~', 'f', 'd', 'p', 'n', 'x', 's', 'a', 't', 'z', '$'
312   };
313   static const WCHAR space[] = {' ', '\0'};
314
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;
323   int   modifierLen     = 0;
324   BOOL  finished        = FALSE;
325   int   i               = 0;
326   BOOL  exists          = TRUE;
327   BOOL  skipFileParsing = FALSE;
328   BOOL  doneModifier    = FALSE;
329
330   /* Search forwards until find invalid WCHARacter modifier */
331   while (!finished) {
332
333     /* Work on the previous WCHARacter */
334     if (lastModifier != NULL) {
335
336       for (i=0; i<NUMMODIFIERS; i++) {
337         if (validmodifiers[i] == *lastModifier) {
338
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 ':'       */
344           }
345           break;
346         }
347       }
348
349       if (i==NUMMODIFIERS) {
350         finished = TRUE;
351       }
352     }
353
354     /* Save this one away */
355     if (!finished) {
356       lastModifier = pos;
357       pos++;
358     }
359   }
360
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)))  {
365
366     /* Its not... Step backwards until it matches or we get to the start */
367     while (toupperW(*lastModifier) != toupperW(*forVariable) &&
368           lastModifier > firstModifier) {
369       lastModifier--;
370     }
371     if (lastModifier == firstModifier) return; /* Invalid syntax */
372   }
373
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));
378   } else {
379     /* FIXME: Retrieve 'for' variable %c\n", *lastModifier); */
380     /* Need to get 'for' loop variable into outputparam      */
381     return;
382   }
383
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;
389
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,
393              outputparam);*/
394
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;
401         len = len - 1;
402     }
403     memmove(outputparam, &outputparam[1], (len * sizeof(WCHAR))-1);
404   }
405
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, ':');
412     WCHAR env[MAX_PATH];
413     WCHAR fullpath[MAX_PATH];
414
415     /* Extract the env var */
416     memcpy(env, start, (end-start) * sizeof(WCHAR));
417     env[(end-start)] = 0x00;
418
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;
426     }
427   }
428
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)
433       return;
434
435     exists = GetFileAttributesExW(fullfilename, GetFileExInfoStandard,
436                                   &fileInfo);
437
438     /* 2. Handle 'a' : Output attributes */
439     if (exists &&
440         memchrW(firstModifier, 'a', modifierLen) != NULL) {
441
442       WCHAR defaults[] = {'-','-','-','-','-','-','-','-','-','\0'};
443       doneModifier = TRUE;
444       strcpyW(thisoutput, defaults);
445       if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
446         thisoutput[0]='d';
447       if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
448         thisoutput[1]='r';
449       if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
450         thisoutput[2]='a';
451       if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
452         thisoutput[3]='h';
453       if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
454         thisoutput[4]='s';
455       if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED)
456         thisoutput[5]='c';
457       /* FIXME: What are 6 and 7? */
458       if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
459         thisoutput[8]='l';
460       strcatW(finaloutput, thisoutput);
461     }
462
463     /* 3. Handle 't' : Date+time */
464     if (exists &&
465         memchrW(firstModifier, 't', modifierLen) != NULL) {
466
467       SYSTEMTIME systime;
468       int datelen;
469
470       doneModifier = TRUE;
471       if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
472
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);
482     }
483
484     /* 4. Handle 'z' : File length */
485     if (exists &&
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'};
491
492       doneModifier = TRUE;
493       if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
494       wsprintf(thisoutput, fmt, fullsize);
495       strcatW(finaloutput, thisoutput);
496     }
497
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));
503     }
504
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) {
508       doneModifier = TRUE;
509       if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
510       strcatW(finaloutput, fullfilename);
511     } else {
512
513       WCHAR drive[10];
514       WCHAR dir[MAX_PATH];
515       WCHAR fname[MAX_PATH];
516       WCHAR ext[MAX_PATH];
517       BOOL doneFileModifier = FALSE;
518
519       if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
520
521       /* Split into components */
522       WCMD_splitpath(fullfilename, drive, dir, fname, ext);
523
524       /* 5. Handle 'd' : Drive Letter */
525       if (memchrW(firstModifier, 'd', modifierLen) != NULL) {
526         strcatW(finaloutput, drive);
527         doneModifier = TRUE;
528         doneFileModifier = TRUE;
529       }
530
531       /* 6. Handle 'p' : Path */
532       if (memchrW(firstModifier, 'p', modifierLen) != NULL) {
533         strcatW(finaloutput, dir);
534         doneModifier = TRUE;
535         doneFileModifier = TRUE;
536       }
537
538       /* 7. Handle 'n' : Name */
539       if (memchrW(firstModifier, 'n', modifierLen) != NULL) {
540         strcatW(finaloutput, fname);
541         doneModifier = TRUE;
542         doneFileModifier = TRUE;
543       }
544
545       /* 8. Handle 'x' : Ext */
546       if (memchrW(firstModifier, 'x', modifierLen) != NULL) {
547         strcatW(finaloutput, ext);
548         doneModifier = TRUE;
549         doneFileModifier = TRUE;
550       }
551
552       /* If 's' but no other parameter, dump the whole thing */
553       if (!doneFileModifier &&
554           memchrW(firstModifier, 's', modifierLen) != NULL) {
555         doneModifier = TRUE;
556         if (finaloutput[0] != 0x00) strcatW(finaloutput, space);
557         strcatW(finaloutput, outputparam);
558       }
559     }
560   }
561
562   /* If No other modifier processed,  just add in parameter */
563   if (!doneModifier) strcpyW(finaloutput, outputparam);
564
565   /* Finish by inserting the replacement into the string */
566   pos = WCMD_strdupW(lastModifier+1);
567   strcpyW(*start, finaloutput);
568   strcatW(*start, pos);
569   free(pos);
570 }
571
572 /*******************************************************************
573  * WCMD_call - processes a batch call statement
574  *
575  *      If there is a leading ':', calls within this batch program
576  *      otherwise launches another program.
577  */
578 void WCMD_call (WCHAR *command) {
579
580   /* Run other program if no leading ':' */
581   if (*command != ':') {
582     WCMD_run_program(command, 1);
583   } else {
584
585     WCHAR gotoLabel[MAX_PATH];
586
587     strcpyW(gotoLabel, param1);
588
589     if (context) {
590
591       LARGE_INTEGER li;
592
593       /* Save the current file position, call the same file,
594          restore position                                    */
595       li.QuadPart = 0;
596       li.u.LowPart = SetFilePointer(context -> h, li.u.LowPart,
597                      &li.u.HighPart, FILE_CURRENT);
598
599       WCMD_batch (param1, command, 1, gotoLabel, context->h);
600
601       SetFilePointer(context -> h, li.u.LowPart,
602                      &li.u.HighPart, FILE_BEGIN);
603     } else {
604       WCMD_output_asis( WCMD_LoadMessage(WCMD_CALLINSCRIPT));
605     }
606   }
607 }