programs/cmd: Do not use hardcoded constants when corresponding macro is already...
[wine] / programs / cmd / builtins.c
1 /*
2  * CMD - Wine-compatible command line interface - built-in functions.
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 /*
22  * NOTES:
23  * On entry to each function, global variables quals, param1, param2 contain
24  * the qualifiers (uppercased and concatenated) and parameters entered, with
25  * environment-variable and batch parameter substitution already done.
26  */
27
28 /*
29  * FIXME:
30  * - No support for pipes, shell parameters
31  * - Lots of functionality missing from builtins
32  * - Messages etc need international support
33  */
34
35 #define WIN32_LEAN_AND_MEAN
36
37 #include "wcmd.h"
38 #include <shellapi.h>
39 #include "wine/debug.h"
40
41 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
42
43 static void WCMD_part_execute(CMD_LIST **commands, WCHAR *firstcmd, WCHAR *variable,
44                                WCHAR *value, BOOL isIF, BOOL conditionTRUE);
45
46 struct env_stack *saved_environment;
47 struct env_stack *pushd_directories;
48
49 extern HINSTANCE hinst;
50 extern WCHAR inbuilt[][10];
51 extern int echo_mode, verify_mode, defaultColor;
52 extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
53 extern BATCH_CONTEXT *context;
54 extern DWORD errorlevel;
55
56 static const WCHAR dotW[]    = {'.','\0'};
57 static const WCHAR dotdotW[] = {'.','.','\0'};
58 static const WCHAR slashW[]  = {'\\','\0'};
59 static const WCHAR starW[]   = {'*','\0'};
60 static const WCHAR equalW[]  = {'=','\0'};
61 static const WCHAR fslashW[] = {'/','\0'};
62 static const WCHAR onW[]  = {'O','N','\0'};
63 static const WCHAR offW[] = {'O','F','F','\0'};
64 static const WCHAR parmY[] = {'/','Y','\0'};
65 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
66 static const WCHAR nullW[] = {'\0'};
67
68 /****************************************************************************
69  * WCMD_clear_screen
70  *
71  * Clear the terminal screen.
72  */
73
74 void WCMD_clear_screen (void) {
75
76   /* Emulate by filling the screen from the top left to bottom right with
77         spaces, then moving the cursor to the top left afterwards */
78   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
79   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
80
81   if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
82   {
83       COORD topLeft;
84       DWORD screenSize;
85
86       screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
87
88       topLeft.X = 0;
89       topLeft.Y = 0;
90       FillConsoleOutputCharacter(hStdOut, ' ', screenSize, topLeft, &screenSize);
91       SetConsoleCursorPosition(hStdOut, topLeft);
92   }
93 }
94
95 /****************************************************************************
96  * WCMD_change_tty
97  *
98  * Change the default i/o device (ie redirect STDin/STDout).
99  */
100
101 void WCMD_change_tty (void) {
102
103   WCMD_output (WCMD_LoadMessage(WCMD_NYI));
104
105 }
106
107 /****************************************************************************
108  * WCMD_copy
109  *
110  * Copy a file or wildcarded set.
111  * FIXME: Add support for a+b+c type syntax
112  */
113
114 void WCMD_copy (void) {
115
116   WIN32_FIND_DATA fd;
117   HANDLE hff;
118   BOOL force, status;
119   WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[3];
120   DWORD len;
121   static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
122   BOOL copyToDir = FALSE;
123   BOOL copyFromDir = FALSE;
124   WCHAR srcspec[MAX_PATH];
125   DWORD attribs;
126   WCHAR drive[10];
127   WCHAR dir[MAX_PATH];
128   WCHAR fname[MAX_PATH];
129   WCHAR ext[MAX_PATH];
130
131   if (param1[0] == 0x00) {
132     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
133     return;
134   }
135
136   /* Convert source into full spec */
137   WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
138   GetFullPathName (param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
139   if (srcpath[strlenW(srcpath) - 1] == '\\')
140       srcpath[strlenW(srcpath) - 1] = '\0';
141
142   if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
143     attribs = GetFileAttributes(srcpath);
144   } else {
145     attribs = 0;
146   }
147   strcpyW(srcspec, srcpath);
148
149   /* If a directory, then add \* on the end when searching */
150   if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
151     strcatW(srcpath, slashW);
152     copyFromDir = TRUE;
153     strcatW(srcspec, slashW);
154     strcatW(srcspec, starW);
155   } else {
156     WCMD_splitpath(srcpath, drive, dir, fname, ext);
157     strcpyW(srcpath, drive);
158     strcatW(srcpath, dir);
159   }
160
161   WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
162
163   /* If no destination supplied, assume current directory */
164   WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
165   if (param2[0] == 0x00) {
166       strcpyW(param2, dotW);
167   }
168
169   GetFullPathName (param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
170   if (outpath[strlenW(outpath) - 1] == '\\')
171       outpath[strlenW(outpath) - 1] = '\0';
172   attribs = GetFileAttributes(outpath);
173   if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
174     strcatW (outpath, slashW);
175     copyToDir = TRUE;
176   }
177   WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
178              wine_dbgstr_w(outpath), copyToDir);
179
180   /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
181   if (strstrW (quals, parmNoY))
182     force = FALSE;
183   else if (strstrW (quals, parmY))
184     force = TRUE;
185   else {
186     len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
187     force = (len && len < (sizeof(copycmd)/sizeof(WCHAR)) && ! lstrcmpiW (copycmd, parmY));
188   }
189
190   /* Loop through all source files */
191   WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
192   hff = FindFirstFile (srcspec, &fd);
193   if (hff != INVALID_HANDLE_VALUE) {
194       do {
195         WCHAR outname[MAX_PATH];
196         WCHAR srcname[MAX_PATH];
197         BOOL  overwrite = force;
198
199         /* Destination is either supplied filename, or source name in
200            supplied destination directory                             */
201         strcpyW(outname, outpath);
202         if (copyToDir) strcatW(outname, fd.cFileName);
203         strcpyW(srcname, srcpath);
204         strcatW(srcname, fd.cFileName);
205
206         WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
207         WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
208
209         /* Skip . and .., and directories */
210         if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
211           overwrite = FALSE;
212           WINE_TRACE("Skipping directories\n");
213         }
214
215         /* Prompt before overwriting */
216         else if (!overwrite) {
217           attribs = GetFileAttributes(outname);
218           if (attribs != INVALID_FILE_ATTRIBUTES) {
219             WCHAR buffer[MAXSTRING];
220             wsprintf(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outname);
221             overwrite = WCMD_ask_confirm(buffer, FALSE, NULL);
222           }
223           else overwrite = TRUE;
224         }
225
226         /* Do the copy as appropriate */
227         if (overwrite) {
228           status = CopyFile (srcname, outname, FALSE);
229           if (!status) WCMD_print_error ();
230         }
231
232       } while (FindNextFile(hff, &fd) != 0);
233       FindClose (hff);
234   } else {
235       status = ERROR_FILE_NOT_FOUND;
236       WCMD_print_error ();
237   }
238 }
239
240 /****************************************************************************
241  * WCMD_create_dir
242  *
243  * Create a directory.
244  *
245  * this works recursivly. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if
246  * they do not already exist.
247  */
248
249 static BOOL create_full_path(WCHAR* path)
250 {
251     int len;
252     WCHAR *new_path;
253     BOOL ret = TRUE;
254
255     new_path = HeapAlloc(GetProcessHeap(),0,(strlenW(path) * sizeof(WCHAR))+1);
256     strcpyW(new_path,path);
257
258     while ((len = strlenW(new_path)) && new_path[len - 1] == '\\')
259         new_path[len - 1] = 0;
260
261     while (!CreateDirectory(new_path,NULL))
262     {
263         WCHAR *slash;
264         DWORD last_error = GetLastError();
265         if (last_error == ERROR_ALREADY_EXISTS)
266             break;
267
268         if (last_error != ERROR_PATH_NOT_FOUND)
269         {
270             ret = FALSE;
271             break;
272         }
273
274         if (!(slash = strrchrW(new_path,'\\')) && ! (slash = strrchrW(new_path,'/')))
275         {
276             ret = FALSE;
277             break;
278         }
279
280         len = slash - new_path;
281         new_path[len] = 0;
282         if (!create_full_path(new_path))
283         {
284             ret = FALSE;
285             break;
286         }
287         new_path[len] = '\\';
288     }
289     HeapFree(GetProcessHeap(),0,new_path);
290     return ret;
291 }
292
293 void WCMD_create_dir (void) {
294
295     if (param1[0] == 0x00) {
296         WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
297         return;
298     }
299     if (!create_full_path(param1)) WCMD_print_error ();
300 }
301
302 /****************************************************************************
303  * WCMD_delete
304  *
305  * Delete a file or wildcarded set.
306  *
307  * Note on /A:
308  *  - Testing shows /A is repeatable, eg. /a-r /ar matches all files
309  *  - Each set is a pattern, eg /ahr /as-r means
310  *         readonly+hidden OR nonreadonly system files
311  *  - The '-' applies to a single field, ie /a:-hr means read only
312  *         non-hidden files
313  */
314
315 BOOL WCMD_delete (WCHAR *command, BOOL expectDir) {
316
317     int   argno         = 0;
318     int   argsProcessed = 0;
319     WCHAR *argN          = command;
320     BOOL  foundAny      = FALSE;
321     static const WCHAR parmA[] = {'/','A','\0'};
322     static const WCHAR parmQ[] = {'/','Q','\0'};
323     static const WCHAR parmP[] = {'/','P','\0'};
324     static const WCHAR parmS[] = {'/','S','\0'};
325     static const WCHAR parmF[] = {'/','F','\0'};
326
327     /* If not recursing, clear error flag */
328     if (expectDir) errorlevel = 0;
329
330     /* Loop through all args */
331     while (argN) {
332       WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
333       WCHAR argCopy[MAX_PATH];
334
335       if (argN && argN[0] != '/') {
336
337         WIN32_FIND_DATA fd;
338         HANDLE hff;
339         WCHAR fpath[MAX_PATH];
340         WCHAR *p;
341         BOOL handleParm = TRUE;
342         BOOL found = FALSE;
343         static const WCHAR anyExt[]= {'.','*','\0'};
344
345         strcpyW(argCopy, thisArg);
346         WINE_TRACE("del: Processing arg %s (quals:%s)\n",
347                    wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
348         argsProcessed++;
349
350         /* If filename part of parameter is * or *.*, prompt unless
351            /Q supplied.                                            */
352         if ((strstrW (quals, parmQ) == NULL) && (strstrW (quals, parmP) == NULL)) {
353
354           WCHAR drive[10];
355           WCHAR dir[MAX_PATH];
356           WCHAR fname[MAX_PATH];
357           WCHAR ext[MAX_PATH];
358
359           /* Convert path into actual directory spec */
360           GetFullPathName (argCopy, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
361           WCMD_splitpath(fpath, drive, dir, fname, ext);
362
363           /* Only prompt for * and *.*, not *a, a*, *.a* etc */
364           if ((strcmpW(fname, starW) == 0) &&
365               (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
366             BOOL  ok;
367             WCHAR  question[MAXSTRING];
368             static const WCHAR fmt[] = {'%','s',' ','\0'};
369
370             /* Note: Flag as found, to avoid file not found message */
371             found = TRUE;
372
373             /* Ask for confirmation */
374             wsprintf(question, fmt, fpath);
375             ok = WCMD_ask_confirm(question, TRUE, NULL);
376
377             /* Abort if answer is 'N' */
378             if (!ok) continue;
379           }
380         }
381
382         /* First, try to delete in the current directory */
383         hff = FindFirstFile (argCopy, &fd);
384         if (hff == INVALID_HANDLE_VALUE) {
385           handleParm = FALSE;
386         } else {
387           found = TRUE;
388         }
389
390         /* Support del <dirname> by just deleting all files dirname\* */
391         if (handleParm && (strchrW(argCopy,'*') == NULL) && (strchrW(argCopy,'?') == NULL)
392                 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
393           WCHAR modifiedParm[MAX_PATH];
394           static const WCHAR slashStar[] = {'\\','*','\0'};
395
396           strcpyW(modifiedParm, argCopy);
397           strcatW(modifiedParm, slashStar);
398           FindClose(hff);
399           found = TRUE;
400           WCMD_delete(modifiedParm, FALSE);
401
402         } else if (handleParm) {
403
404           /* Build the filename to delete as <supplied directory>\<findfirst filename> */
405           strcpyW (fpath, argCopy);
406           do {
407             p = strrchrW (fpath, '\\');
408             if (p != NULL) {
409               *++p = '\0';
410               strcatW (fpath, fd.cFileName);
411             }
412             else strcpyW (fpath, fd.cFileName);
413             if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
414               BOOL  ok = TRUE;
415               WCHAR *nextA = strstrW (quals, parmA);
416
417               /* Handle attribute matching (/A) */
418               if (nextA != NULL) {
419                 ok = FALSE;
420                 while (nextA != NULL && !ok) {
421
422                   WCHAR *thisA = (nextA+2);
423                   BOOL  stillOK = TRUE;
424
425                   /* Skip optional : */
426                   if (*thisA == ':') thisA++;
427
428                   /* Parse each of the /A[:]xxx in turn */
429                   while (*thisA && *thisA != '/') {
430                     BOOL negate    = FALSE;
431                     BOOL attribute = FALSE;
432
433                     /* Match negation of attribute first */
434                     if (*thisA == '-') {
435                       negate=TRUE;
436                       thisA++;
437                     }
438
439                     /* Match attribute */
440                     switch (*thisA) {
441                     case 'R': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
442                               break;
443                     case 'H': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN);
444                               break;
445                     case 'S': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM);
446                               break;
447                     case 'A': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE);
448                               break;
449                     default:
450                         WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
451                     }
452
453                     /* Now check result, keeping a running boolean about whether it
454                        matches all parsed attribues so far                         */
455                     if (attribute && !negate) {
456                         stillOK = stillOK;
457                     } else if (!attribute && negate) {
458                         stillOK = stillOK;
459                     } else {
460                         stillOK = FALSE;
461                     }
462                     thisA++;
463                   }
464
465                   /* Save the running total as the final result */
466                   ok = stillOK;
467
468                   /* Step on to next /A set */
469                   nextA = strstrW (nextA+1, parmA);
470                 }
471               }
472
473               /* /P means prompt for each file */
474               if (ok && strstrW (quals, parmP) != NULL) {
475                 WCHAR  question[MAXSTRING];
476
477                 /* Ask for confirmation */
478                 wsprintf(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
479                 ok = WCMD_ask_confirm(question, FALSE, NULL);
480               }
481
482               /* Only proceed if ok to */
483               if (ok) {
484
485                 /* If file is read only, and /F supplied, delete it */
486                 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
487                     strstrW (quals, parmF) != NULL) {
488                     SetFileAttributes(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
489                 }
490
491                 /* Now do the delete */
492                 if (!DeleteFile (fpath)) WCMD_print_error ();
493               }
494
495             }
496           } while (FindNextFile(hff, &fd) != 0);
497           FindClose (hff);
498         }
499
500         /* Now recurse into all subdirectories handling the parameter in the same way */
501         if (strstrW (quals, parmS) != NULL) {
502
503           WCHAR thisDir[MAX_PATH];
504           int cPos;
505
506           WCHAR drive[10];
507           WCHAR dir[MAX_PATH];
508           WCHAR fname[MAX_PATH];
509           WCHAR ext[MAX_PATH];
510
511           /* Convert path into actual directory spec */
512           GetFullPathName (argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
513           WCMD_splitpath(thisDir, drive, dir, fname, ext);
514
515           strcpyW(thisDir, drive);
516           strcatW(thisDir, dir);
517           cPos = strlenW(thisDir);
518
519           WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
520
521           /* Append '*' to the directory */
522           thisDir[cPos] = '*';
523           thisDir[cPos+1] = 0x00;
524
525           hff = FindFirstFile (thisDir, &fd);
526
527           /* Remove residual '*' */
528           thisDir[cPos] = 0x00;
529
530           if (hff != INVALID_HANDLE_VALUE) {
531             DIRECTORY_STACK *allDirs = NULL;
532             DIRECTORY_STACK *lastEntry = NULL;
533
534             do {
535               if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
536                   (strcmpW(fd.cFileName, dotdotW) != 0) &&
537                   (strcmpW(fd.cFileName, dotW) != 0)) {
538
539                 DIRECTORY_STACK *nextDir;
540                 WCHAR subParm[MAX_PATH];
541
542                 /* Work out search parameter in sub dir */
543                 strcpyW (subParm, thisDir);
544                 strcatW (subParm, fd.cFileName);
545                 strcatW (subParm, slashW);
546                 strcatW (subParm, fname);
547                 strcatW (subParm, ext);
548                 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
549
550                 /* Allocate memory, add to list */
551                 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
552                 if (allDirs == NULL) allDirs = nextDir;
553                 if (lastEntry != NULL) lastEntry->next = nextDir;
554                 lastEntry = nextDir;
555                 nextDir->next = NULL;
556                 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
557                                              (strlenW(subParm)+1) * sizeof(WCHAR));
558                 strcpyW(nextDir->dirName, subParm);
559               }
560             } while (FindNextFile(hff, &fd) != 0);
561             FindClose (hff);
562
563             /* Go through each subdir doing the delete */
564             while (allDirs != NULL) {
565               DIRECTORY_STACK *tempDir;
566
567               tempDir = allDirs->next;
568               found |= WCMD_delete (allDirs->dirName, FALSE);
569
570               HeapFree(GetProcessHeap(),0,allDirs->dirName);
571               HeapFree(GetProcessHeap(),0,allDirs);
572               allDirs = tempDir;
573             }
574           }
575         }
576         /* Keep running total to see if any found, and if not recursing
577            issue error message                                         */
578         if (expectDir) {
579           if (!found) {
580             errorlevel = 1;
581             WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), argCopy);
582           }
583         }
584         foundAny |= found;
585       }
586     }
587
588     /* Handle no valid args */
589     if (argsProcessed == 0) {
590       WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
591     }
592
593     return foundAny;
594 }
595
596 /****************************************************************************
597  * WCMD_echo
598  *
599  * Echo input to the screen (or not). We don't try to emulate the bugs
600  * in DOS (try typing "ECHO ON AGAIN" for an example).
601  */
602
603 void WCMD_echo (const WCHAR *command) {
604
605   int count;
606
607   if ((command[0] == '.') && (command[1] == 0)) {
608     WCMD_output (newline);
609     return;
610   }
611   if (command[0]==' ')
612     command++;
613   count = strlenW(command);
614   if (count == 0) {
615     if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
616     else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
617     return;
618   }
619   if (lstrcmpiW(command, onW) == 0) {
620     echo_mode = 1;
621     return;
622   }
623   if (lstrcmpiW(command, offW) == 0) {
624     echo_mode = 0;
625     return;
626   }
627   WCMD_output_asis (command);
628   WCMD_output (newline);
629
630 }
631
632 /**************************************************************************
633  * WCMD_for
634  *
635  * Batch file loop processing.
636  *
637  * On entry: cmdList       contains the syntax up to the set
638  *           next cmdList and all in that bracket contain the set data
639  *           next cmdlist  contains the DO cmd
640  *           following that is either brackets or && entries (as per if)
641  *
642  * FIXME: We don't exhaustively check syntax. Any command which works in MessDOS
643  * will probably work here, but the reverse is not necessarily the case...
644  */
645
646 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
647
648   WIN32_FIND_DATA fd;
649   HANDLE hff;
650   int i;
651   const WCHAR inW[] = {'i', 'n', '\0'};
652   const WCHAR doW[] = {'d', 'o', ' ','\0'};
653   CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
654   WCHAR variable[4];
655   WCHAR *firstCmd;
656   int thisDepth;
657
658   /* Check:
659      the first line includes the % variable name as first parm
660      we have been provided with more parts to the command
661      and there is at least some set data
662      and IN as the one after that                                   */
663   if (lstrcmpiW (WCMD_parameter (p, 1, NULL), inW)
664       || (*cmdList) == NULL
665       || (*cmdList)->nextcommand == NULL
666       || (param1[0] != '%')
667       || (strlenW(param1) > 3)) {
668     WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
669     return;
670   }
671
672   /* Save away where the set of data starts and the variable */
673   strcpyW(variable, param1);
674   thisDepth = (*cmdList)->bracketDepth;
675   *cmdList = (*cmdList)->nextcommand;
676   setStart = (*cmdList);
677
678   /* Skip until the close bracket */
679   WINE_TRACE("Searching %p as the set\n", *cmdList);
680   while (*cmdList &&
681          (*cmdList)->command != NULL &&
682          (*cmdList)->bracketDepth > thisDepth) {
683     WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
684     *cmdList = (*cmdList)->nextcommand;
685   }
686
687   /* Skip the close bracket, if there is one */
688   if (*cmdList) *cmdList = (*cmdList)->nextcommand;
689
690   /* Syntax error if missing close bracket, or nothing following it
691      and once we have the complete set, we expect a DO              */
692   WINE_TRACE("Looking for 'do' in %p\n", *cmdList);
693   if ((*cmdList == NULL) ||
694       (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
695                             (*cmdList)->command, 3, doW, -1) != 2)) {
696       WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
697       return;
698   }
699
700   /* Save away the starting position for the commands (and offset for the
701      first one                                                           */
702   cmdStart = *cmdList;
703   cmdEnd   = *cmdList;
704   firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
705
706   thisSet = setStart;
707   /* Loop through all set entries */
708   while (thisSet &&
709          thisSet->command != NULL &&
710          thisSet->bracketDepth >= thisDepth) {
711
712     /* Loop through all entries on the same line */
713     WCHAR *item;
714
715     WINE_TRACE("Processing for set %p\n", thisSet);
716     i = 0;
717     while (*(item = WCMD_parameter (thisSet->command, i, NULL))) {
718
719       /*
720        * If the parameter within the set has a wildcard then search for matching files
721        * otherwise do a literal substitution.
722        */
723       static const WCHAR wildcards[] = {'*','?','\0'};
724       CMD_LIST *thisCmdStart = cmdStart;
725
726       WINE_TRACE("Processing for item '%s'\n", wine_dbgstr_w(item));
727       if (strpbrkW (item, wildcards)) {
728         hff = FindFirstFile (item, &fd);
729         if (hff != INVALID_HANDLE_VALUE) {
730           do {
731             BOOL isDirectory = (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
732             if (!isDirectory)
733             {
734               thisCmdStart = cmdStart;
735               WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
736               WCMD_part_execute (&thisCmdStart, firstCmd, variable,
737                                            fd.cFileName, FALSE, TRUE);
738             }
739
740           } while (FindNextFile(hff, &fd) != 0);
741           FindClose (hff);
742         }
743       } else {
744         WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
745       }
746
747       WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
748       cmdEnd = thisCmdStart;
749       i++;
750     }
751
752     /* Move onto the next set line */
753     thisSet = thisSet->nextcommand;
754   }
755
756   /* When the loop ends, either something like a GOTO or EXIT /b has terminated
757      all processing, OR it should be pointing to the end of && processing OR
758      it should be pointing at the NULL end of bracket for the DO. The return
759      value needs to be the NEXT command to execute, which it either is, or
760      we need to step over the closing bracket                                  */
761   *cmdList = cmdEnd;
762   if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
763 }
764
765
766 /*****************************************************************************
767  * WCMD_part_execute
768  *
769  * Execute a command, and any && or bracketed follow on to the command. The
770  * first command to be executed may not be at the front of the
771  * commands->thiscommand string (eg. it may point after a DO or ELSE)
772  */
773 void WCMD_part_execute(CMD_LIST **cmdList, WCHAR *firstcmd, WCHAR *variable,
774                        WCHAR *value, BOOL isIF, BOOL conditionTRUE) {
775
776   CMD_LIST *curPosition = *cmdList;
777   int myDepth = (*cmdList)->bracketDepth;
778
779   WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n",
780              cmdList, wine_dbgstr_w(firstcmd),
781              wine_dbgstr_w(variable), wine_dbgstr_w(value),
782              conditionTRUE);
783
784   /* Skip leading whitespace between condition and the command */
785   while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
786
787   /* Process the first command, if there is one */
788   if (conditionTRUE && firstcmd && *firstcmd) {
789     WCHAR *command = WCMD_strdupW(firstcmd);
790     WCMD_execute (firstcmd, variable, value, cmdList);
791     free (command);
792   }
793
794
795   /* If it didn't move the position, step to next command */
796   if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
797
798   /* Process any other parts of the command */
799   if (*cmdList) {
800     BOOL processThese = TRUE;
801
802     if (isIF) processThese = conditionTRUE;
803
804     while (*cmdList) {
805       const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
806
807       /* execute all appropriate commands */
808       curPosition = *cmdList;
809
810       WINE_TRACE("Processing cmdList(%p) - &(%d) bd(%d / %d)\n",
811                  *cmdList,
812                  (*cmdList)->isAmphersand,
813                  (*cmdList)->bracketDepth, myDepth);
814
815       /* Execute any appended to the statement with &&'s */
816       if ((*cmdList)->isAmphersand) {
817         if (processThese) {
818           WCMD_execute ((*cmdList)->command, variable, value, cmdList);
819         }
820         if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
821
822       /* Execute any appended to the statement with (...) */
823       } else if ((*cmdList)->bracketDepth > myDepth) {
824         if (processThese) {
825           *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
826           WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
827         }
828         if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
829
830       /* End of the command - does 'ELSE ' follow as the next command? */
831       } else {
832         if (isIF && CompareString (LOCALE_USER_DEFAULT,
833                                    NORM_IGNORECASE | SORT_STRINGSORT,
834                            (*cmdList)->command, 5, ifElse, -1) == 2) {
835
836           /* Swap between if and else processing */
837           processThese = !processThese;
838
839           /* Process the ELSE part */
840           if (processThese) {
841             WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
842
843             /* Skip leading whitespace between condition and the command */
844             while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
845             if (*cmd) {
846               WCMD_execute (cmd, variable, value, cmdList);
847             }
848           }
849           if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
850         } else {
851           WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
852           break;
853         }
854       }
855     }
856   }
857   return;
858 }
859
860 /*****************************************************************************
861  * WCMD_Execute
862  *
863  *      Execute a command after substituting variable text for the supplied parameter
864  */
865
866 void WCMD_execute (WCHAR *orig_cmd, WCHAR *param, WCHAR *subst, CMD_LIST **cmdList) {
867
868   WCHAR *new_cmd, *p, *s, *dup;
869   int size;
870
871   if (param) {
872     size = (strlenW (orig_cmd) + 1) * sizeof(WCHAR);
873     new_cmd = (WCHAR *) LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, size);
874     dup = s = WCMD_strdupW(orig_cmd);
875
876     while ((p = strstrW (s, param))) {
877       *p = '\0';
878       size += strlenW (subst) * sizeof(WCHAR);
879       new_cmd = (WCHAR *) LocalReAlloc ((HANDLE)new_cmd, size, 0);
880       strcatW (new_cmd, s);
881       strcatW (new_cmd, subst);
882       s = p + strlenW (param);
883     }
884     strcatW (new_cmd, s);
885     WCMD_process_command (new_cmd, cmdList);
886     free (dup);
887     LocalFree ((HANDLE)new_cmd);
888   } else {
889     WCMD_process_command (orig_cmd, cmdList);
890   }
891 }
892
893
894 /**************************************************************************
895  * WCMD_give_help
896  *
897  *      Simple on-line help. Help text is stored in the resource file.
898  */
899
900 void WCMD_give_help (WCHAR *command) {
901
902   int i;
903
904   command = WCMD_strtrim_leading_spaces(command);
905   if (strlenW(command) == 0) {
906     WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
907   }
908   else {
909     for (i=0; i<=WCMD_EXIT; i++) {
910       if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
911           param1, -1, inbuilt[i], -1) == 2) {
912         WCMD_output_asis (WCMD_LoadMessage(i));
913         return;
914       }
915     }
916     WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), param1);
917   }
918   return;
919 }
920
921 /****************************************************************************
922  * WCMD_go_to
923  *
924  * Batch file jump instruction. Not the most efficient algorithm ;-)
925  * Prints error message if the specified label cannot be found - the file pointer is
926  * then at EOF, effectively stopping the batch file.
927  * FIXME: DOS is supposed to allow labels with spaces - we don't.
928  */
929
930 void WCMD_goto (CMD_LIST **cmdList) {
931
932   WCHAR string[MAX_PATH];
933
934   /* Do not process any more parts of a processed multipart or multilines command */
935   *cmdList = NULL;
936
937   if (param1[0] == 0x00) {
938     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
939     return;
940   }
941   if (context != NULL) {
942     WCHAR *paramStart = param1;
943     static const WCHAR eofW[] = {':','e','o','f','\0'};
944
945     /* Handle special :EOF label */
946     if (lstrcmpiW (eofW, param1) == 0) {
947       context -> skip_rest = TRUE;
948       return;
949     }
950
951     /* Support goto :label as well as goto label */
952     if (*paramStart == ':') paramStart++;
953
954     SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
955     while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
956       if ((string[0] == ':') && (lstrcmpiW (&string[1], paramStart) == 0)) return;
957     }
958     WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
959   }
960   return;
961 }
962
963 /*****************************************************************************
964  * WCMD_pushd
965  *
966  *      Push a directory onto the stack
967  */
968
969 void WCMD_pushd (WCHAR *command) {
970     struct env_stack *curdir;
971     WCHAR *thisdir;
972     static const WCHAR parmD[] = {'/','D','\0'};
973
974     if (strchrW(command, '/') != NULL) {
975       SetLastError(ERROR_INVALID_PARAMETER);
976       WCMD_print_error();
977       return;
978     }
979
980     curdir  = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
981     thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
982     if( !curdir || !thisdir ) {
983       LocalFree(curdir);
984       LocalFree(thisdir);
985       WINE_ERR ("out of memory\n");
986       return;
987     }
988
989     /* Change directory using CD code with /D parameter */
990     strcpyW(quals, parmD);
991     GetCurrentDirectoryW (1024, thisdir);
992     errorlevel = 0;
993     WCMD_setshow_default(command);
994     if (errorlevel) {
995       LocalFree(curdir);
996       LocalFree(thisdir);
997       return;
998     } else {
999       curdir -> next    = pushd_directories;
1000       curdir -> strings = thisdir;
1001       if (pushd_directories == NULL) {
1002         curdir -> u.stackdepth = 1;
1003       } else {
1004         curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1005       }
1006       pushd_directories = curdir;
1007     }
1008 }
1009
1010
1011 /*****************************************************************************
1012  * WCMD_popd
1013  *
1014  *      Pop a directory from the stack
1015  */
1016
1017 void WCMD_popd (void) {
1018     struct env_stack *temp = pushd_directories;
1019
1020     if (!pushd_directories)
1021       return;
1022
1023     /* pop the old environment from the stack, and make it the current dir */
1024     pushd_directories = temp->next;
1025     SetCurrentDirectoryW(temp->strings);
1026     LocalFree (temp->strings);
1027     LocalFree (temp);
1028 }
1029
1030 /****************************************************************************
1031  * WCMD_if
1032  *
1033  * Batch file conditional.
1034  *
1035  * On entry, cmdlist will point to command containing the IF, and optionally
1036  *   the first command to execute (if brackets not found)
1037  *   If &&'s were found, this may be followed by a record flagged as isAmpersand
1038  *   If ('s were found, execute all within that bracket
1039  *   Command may optionally be followed by an ELSE - need to skip instructions
1040  *   in the else using the same logic
1041  *
1042  * FIXME: Much more syntax checking needed!
1043  */
1044
1045 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1046
1047   int negate = 0, test = 0;
1048   WCHAR condition[MAX_PATH], *command, *s;
1049   static const WCHAR notW[]    = {'n','o','t','\0'};
1050   static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1051   static const WCHAR existW[]  = {'e','x','i','s','t','\0'};
1052   static const WCHAR defdW[]   = {'d','e','f','i','n','e','d','\0'};
1053   static const WCHAR eqeqW[]   = {'=','=','\0'};
1054
1055   if (!lstrcmpiW (param1, notW)) {
1056     negate = 1;
1057     strcpyW (condition, param2);
1058   }
1059   else {
1060     strcpyW (condition, param1);
1061   }
1062   if (!lstrcmpiW (condition, errlvlW)) {
1063     if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1064     WCMD_parameter (p, 2+negate, &command);
1065   }
1066   else if (!lstrcmpiW (condition, existW)) {
1067     if (GetFileAttributes(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1068         test = 1;
1069     }
1070     WCMD_parameter (p, 2+negate, &command);
1071   }
1072   else if (!lstrcmpiW (condition, defdW)) {
1073     if (GetEnvironmentVariable(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1074         test = 1;
1075     }
1076     WCMD_parameter (p, 2+negate, &command);
1077   }
1078   else if ((s = strstrW (p, eqeqW))) {
1079     s += 2;
1080     if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1081     WCMD_parameter (s, 1, &command);
1082   }
1083   else {
1084     WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1085     return;
1086   }
1087
1088   /* Process rest of IF statement which is on the same line
1089      Note: This may process all or some of the cmdList (eg a GOTO) */
1090   WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1091 }
1092
1093 /****************************************************************************
1094  * WCMD_move
1095  *
1096  * Move a file, directory tree or wildcarded set of files.
1097  */
1098
1099 void WCMD_move (void) {
1100
1101   int             status;
1102   WIN32_FIND_DATA fd;
1103   HANDLE          hff;
1104   WCHAR            input[MAX_PATH];
1105   WCHAR            output[MAX_PATH];
1106   WCHAR            drive[10];
1107   WCHAR            dir[MAX_PATH];
1108   WCHAR            fname[MAX_PATH];
1109   WCHAR            ext[MAX_PATH];
1110
1111   if (param1[0] == 0x00) {
1112     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1113     return;
1114   }
1115
1116   /* If no destination supplied, assume current directory */
1117   if (param2[0] == 0x00) {
1118       strcpyW(param2, dotW);
1119   }
1120
1121   /* If 2nd parm is directory, then use original filename */
1122   /* Convert partial path to full path */
1123   GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1124   GetFullPathName (param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1125   WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1126              wine_dbgstr_w(param1), wine_dbgstr_w(output));
1127
1128   /* Split into components */
1129   WCMD_splitpath(input, drive, dir, fname, ext);
1130
1131   hff = FindFirstFile (input, &fd);
1132   while (hff != INVALID_HANDLE_VALUE) {
1133     WCHAR  dest[MAX_PATH];
1134     WCHAR  src[MAX_PATH];
1135     DWORD attribs;
1136
1137     WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1138
1139     /* Build src & dest name */
1140     strcpyW(src, drive);
1141     strcatW(src, dir);
1142
1143     /* See if dest is an existing directory */
1144     attribs = GetFileAttributes(output);
1145     if (attribs != INVALID_FILE_ATTRIBUTES &&
1146        (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1147       strcpyW(dest, output);
1148       strcatW(dest, slashW);
1149       strcatW(dest, fd.cFileName);
1150     } else {
1151       strcpyW(dest, output);
1152     }
1153
1154     strcatW(src, fd.cFileName);
1155
1156     WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1157     WINE_TRACE("Dest   '%s'\n", wine_dbgstr_w(dest));
1158
1159     /* Check if file is read only, otherwise move it */
1160     attribs = GetFileAttributes(src);
1161     if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1162         (attribs & FILE_ATTRIBUTE_READONLY)) {
1163       SetLastError(ERROR_ACCESS_DENIED);
1164       status = 0;
1165     } else {
1166       BOOL ok = TRUE;
1167
1168       /* If destination exists, prompt unless /Y supplied */
1169       if (GetFileAttributes(dest) != INVALID_FILE_ATTRIBUTES) {
1170         BOOL force = FALSE;
1171         WCHAR copycmd[MAXSTRING];
1172         int len;
1173
1174         /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1175         if (strstrW (quals, parmNoY))
1176           force = FALSE;
1177         else if (strstrW (quals, parmY))
1178           force = TRUE;
1179         else {
1180           const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1181           len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1182           force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1183                        && ! lstrcmpiW (copycmd, parmY));
1184         }
1185
1186         /* Prompt if overwriting */
1187         if (!force) {
1188           WCHAR  question[MAXSTRING];
1189           WCHAR  yesChar[10];
1190
1191           strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1192
1193           /* Ask for confirmation */
1194           wsprintf(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1195           ok = WCMD_ask_confirm(question, FALSE, NULL);
1196
1197           /* So delete the destination prior to the move */
1198           if (ok) {
1199             if (!DeleteFile (dest)) {
1200               WCMD_print_error ();
1201               errorlevel = 1;
1202               ok = FALSE;
1203             }
1204           }
1205         }
1206       }
1207
1208       if (ok) {
1209         status = MoveFile (src, dest);
1210       } else {
1211         status = 1; /* Anything other than 0 to prevent error msg below */
1212       }
1213     }
1214
1215     if (!status) {
1216       WCMD_print_error ();
1217       errorlevel = 1;
1218     }
1219
1220     /* Step on to next match */
1221     if (FindNextFile(hff, &fd) == 0) {
1222       FindClose(hff);
1223       hff = INVALID_HANDLE_VALUE;
1224       break;
1225     }
1226   }
1227 }
1228
1229 /****************************************************************************
1230  * WCMD_pause
1231  *
1232  * Wait for keyboard input.
1233  */
1234
1235 void WCMD_pause (void) {
1236
1237   DWORD count;
1238   WCHAR string[32];
1239
1240   WCMD_output (anykey);
1241   WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1242                  sizeof(string)/sizeof(WCHAR), &count, NULL);
1243 }
1244
1245 /****************************************************************************
1246  * WCMD_remove_dir
1247  *
1248  * Delete a directory.
1249  */
1250
1251 void WCMD_remove_dir (WCHAR *command) {
1252
1253   int   argno         = 0;
1254   int   argsProcessed = 0;
1255   WCHAR *argN          = command;
1256   static const WCHAR parmS[] = {'/','S','\0'};
1257   static const WCHAR parmQ[] = {'/','Q','\0'};
1258
1259   /* Loop through all args */
1260   while (argN) {
1261     WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1262     if (argN && argN[0] != '/') {
1263       WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1264                  wine_dbgstr_w(quals));
1265       argsProcessed++;
1266
1267       /* If subdirectory search not supplied, just try to remove
1268          and report error if it fails (eg if it contains a file) */
1269       if (strstrW (quals, parmS) == NULL) {
1270         if (!RemoveDirectory (thisArg)) WCMD_print_error ();
1271
1272       /* Otherwise use ShFileOp to recursively remove a directory */
1273       } else {
1274
1275         SHFILEOPSTRUCT lpDir;
1276
1277         /* Ask first */
1278         if (strstrW (quals, parmQ) == NULL) {
1279           BOOL  ok;
1280           WCHAR  question[MAXSTRING];
1281           static const WCHAR fmt[] = {'%','s',' ','\0'};
1282
1283           /* Ask for confirmation */
1284           wsprintf(question, fmt, thisArg);
1285           ok = WCMD_ask_confirm(question, TRUE, NULL);
1286
1287           /* Abort if answer is 'N' */
1288           if (!ok) return;
1289         }
1290
1291         /* Do the delete */
1292         lpDir.hwnd   = NULL;
1293         lpDir.pTo    = NULL;
1294         lpDir.pFrom  = thisArg;
1295         lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1296         lpDir.wFunc  = FO_DELETE;
1297         if (SHFileOperation(&lpDir)) WCMD_print_error ();
1298       }
1299     }
1300   }
1301
1302   /* Handle no valid args */
1303   if (argsProcessed == 0) {
1304     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1305     return;
1306   }
1307
1308 }
1309
1310 /****************************************************************************
1311  * WCMD_rename
1312  *
1313  * Rename a file.
1314  */
1315
1316 void WCMD_rename (void) {
1317
1318   int             status;
1319   HANDLE          hff;
1320   WIN32_FIND_DATA fd;
1321   WCHAR            input[MAX_PATH];
1322   WCHAR           *dotDst = NULL;
1323   WCHAR            drive[10];
1324   WCHAR            dir[MAX_PATH];
1325   WCHAR            fname[MAX_PATH];
1326   WCHAR            ext[MAX_PATH];
1327   DWORD           attribs;
1328
1329   errorlevel = 0;
1330
1331   /* Must be at least two args */
1332   if (param1[0] == 0x00 || param2[0] == 0x00) {
1333     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1334     errorlevel = 1;
1335     return;
1336   }
1337
1338   /* Destination cannot contain a drive letter or directory separator */
1339   if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1340       SetLastError(ERROR_INVALID_PARAMETER);
1341       WCMD_print_error();
1342       errorlevel = 1;
1343       return;
1344   }
1345
1346   /* Convert partial path to full path */
1347   GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1348   WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1349              wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1350   dotDst = strchrW(param2, '.');
1351
1352   /* Split into components */
1353   WCMD_splitpath(input, drive, dir, fname, ext);
1354
1355   hff = FindFirstFile (input, &fd);
1356   while (hff != INVALID_HANDLE_VALUE) {
1357     WCHAR  dest[MAX_PATH];
1358     WCHAR  src[MAX_PATH];
1359     WCHAR *dotSrc = NULL;
1360     int   dirLen;
1361
1362     WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1363
1364     /* FIXME: If dest name or extension is *, replace with filename/ext
1365        part otherwise use supplied name. This supports:
1366           ren *.fred *.jim
1367           ren jim.* fred.* etc
1368        However, windows has a more complex algorithum supporting eg
1369           ?'s and *'s mid name                                         */
1370     dotSrc = strchrW(fd.cFileName, '.');
1371
1372     /* Build src & dest name */
1373     strcpyW(src, drive);
1374     strcatW(src, dir);
1375     strcpyW(dest, src);
1376     dirLen = strlenW(src);
1377     strcatW(src, fd.cFileName);
1378
1379     /* Build name */
1380     if (param2[0] == '*') {
1381       strcatW(dest, fd.cFileName);
1382       if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1383     } else {
1384       strcatW(dest, param2);
1385       if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1386     }
1387
1388     /* Build Extension */
1389     if (dotDst && (*(dotDst+1)=='*')) {
1390       if (dotSrc) strcatW(dest, dotSrc);
1391     } else if (dotDst) {
1392       if (dotDst) strcatW(dest, dotDst);
1393     }
1394
1395     WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1396     WINE_TRACE("Dest   '%s'\n", wine_dbgstr_w(dest));
1397
1398     /* Check if file is read only, otherwise move it */
1399     attribs = GetFileAttributes(src);
1400     if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1401         (attribs & FILE_ATTRIBUTE_READONLY)) {
1402       SetLastError(ERROR_ACCESS_DENIED);
1403       status = 0;
1404     } else {
1405       status = MoveFile (src, dest);
1406     }
1407
1408     if (!status) {
1409       WCMD_print_error ();
1410       errorlevel = 1;
1411     }
1412
1413     /* Step on to next match */
1414     if (FindNextFile(hff, &fd) == 0) {
1415       FindClose(hff);
1416       hff = INVALID_HANDLE_VALUE;
1417       break;
1418     }
1419   }
1420 }
1421
1422 /*****************************************************************************
1423  * WCMD_dupenv
1424  *
1425  * Make a copy of the environment.
1426  */
1427 static WCHAR *WCMD_dupenv( const WCHAR *env )
1428 {
1429   WCHAR *env_copy;
1430   int len;
1431
1432   if( !env )
1433     return NULL;
1434
1435   len = 0;
1436   while ( env[len] )
1437     len += (strlenW(&env[len]) + 1);
1438
1439   env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1440   if (!env_copy)
1441   {
1442     WINE_ERR("out of memory\n");
1443     return env_copy;
1444   }
1445   memcpy (env_copy, env, len*sizeof (WCHAR));
1446   env_copy[len] = 0;
1447
1448   return env_copy;
1449 }
1450
1451 /*****************************************************************************
1452  * WCMD_setlocal
1453  *
1454  *  setlocal pushes the environment onto a stack
1455  *  Save the environment as unicode so we don't screw anything up.
1456  */
1457 void WCMD_setlocal (const WCHAR *s) {
1458   WCHAR *env;
1459   struct env_stack *env_copy;
1460   WCHAR cwd[MAX_PATH];
1461
1462   /* DISABLEEXTENSIONS ignored */
1463
1464   env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1465   if( !env_copy )
1466   {
1467     WINE_ERR ("out of memory\n");
1468     return;
1469   }
1470
1471   env = GetEnvironmentStringsW ();
1472
1473   env_copy->strings = WCMD_dupenv (env);
1474   if (env_copy->strings)
1475   {
1476     env_copy->next = saved_environment;
1477     saved_environment = env_copy;
1478
1479     /* Save the current drive letter */
1480     GetCurrentDirectory (MAX_PATH, cwd);
1481     env_copy->u.cwd = cwd[0];
1482   }
1483   else
1484     LocalFree (env_copy);
1485
1486   FreeEnvironmentStringsW (env);
1487
1488 }
1489
1490 /*****************************************************************************
1491  * WCMD_endlocal
1492  *
1493  *  endlocal pops the environment off a stack
1494  *  Note: When searching for '=', search from WCHAR position 1, to handle
1495  *        special internal environment variables =C:, =D: etc
1496  */
1497 void WCMD_endlocal (void) {
1498   WCHAR *env, *old, *p;
1499   struct env_stack *temp;
1500   int len, n;
1501
1502   if (!saved_environment)
1503     return;
1504
1505   /* pop the old environment from the stack */
1506   temp = saved_environment;
1507   saved_environment = temp->next;
1508
1509   /* delete the current environment, totally */
1510   env = GetEnvironmentStringsW ();
1511   old = WCMD_dupenv (GetEnvironmentStringsW ());
1512   len = 0;
1513   while (old[len]) {
1514     n = strlenW(&old[len]) + 1;
1515     p = strchrW(&old[len] + 1, '=');
1516     if (p)
1517     {
1518       *p++ = 0;
1519       SetEnvironmentVariableW (&old[len], NULL);
1520     }
1521     len += n;
1522   }
1523   LocalFree (old);
1524   FreeEnvironmentStringsW (env);
1525
1526   /* restore old environment */
1527   env = temp->strings;
1528   len = 0;
1529   while (env[len]) {
1530     n = strlenW(&env[len]) + 1;
1531     p = strchrW(&env[len] + 1, '=');
1532     if (p)
1533     {
1534       *p++ = 0;
1535       SetEnvironmentVariableW (&env[len], p);
1536     }
1537     len += n;
1538   }
1539
1540   /* Restore current drive letter */
1541   if (IsCharAlpha(temp->u.cwd)) {
1542     WCHAR envvar[4];
1543     WCHAR cwd[MAX_PATH];
1544     static const WCHAR fmt[] = {'=','%','c',':','\0'};
1545
1546     wsprintf(envvar, fmt, temp->u.cwd);
1547     if (GetEnvironmentVariable(envvar, cwd, MAX_PATH)) {
1548       WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1549       SetCurrentDirectory(cwd);
1550     }
1551   }
1552
1553   LocalFree (env);
1554   LocalFree (temp);
1555 }
1556
1557 /*****************************************************************************
1558  * WCMD_setshow_attrib
1559  *
1560  * Display and optionally sets DOS attributes on a file or directory
1561  *
1562  * FIXME: Wine currently uses the Unix stat() function to get file attributes.
1563  * As a result only the Readonly flag is correctly reported, the Archive bit
1564  * is always set and the rest are not implemented. We do the Right Thing anyway.
1565  *
1566  * FIXME: No SET functionality.
1567  *
1568  */
1569
1570 void WCMD_setshow_attrib (void) {
1571
1572   DWORD count;
1573   HANDLE hff;
1574   WIN32_FIND_DATA fd;
1575   WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1576
1577   if (param1[0] == '-') {
1578     WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1579     return;
1580   }
1581
1582   if (strlenW(param1) == 0) {
1583     static const WCHAR slashStarW[]  = {'\\','*','\0'};
1584
1585     GetCurrentDirectory (sizeof(param1)/sizeof(WCHAR), param1);
1586     strcatW (param1, slashStarW);
1587   }
1588
1589   hff = FindFirstFile (param1, &fd);
1590   if (hff == INVALID_HANDLE_VALUE) {
1591     WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), param1);
1592   }
1593   else {
1594     do {
1595       if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1596         static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1597         if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
1598           flags[0] = 'H';
1599         }
1600         if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
1601           flags[1] = 'S';
1602         }
1603         if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
1604           flags[2] = 'A';
1605         }
1606         if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
1607           flags[3] = 'R';
1608         }
1609         if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
1610           flags[4] = 'T';
1611         }
1612         if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
1613           flags[5] = 'C';
1614         }
1615         WCMD_output (fmt, flags, fd.cFileName);
1616         for (count=0; count < 8; count++) flags[count] = ' ';
1617       }
1618     } while (FindNextFile(hff, &fd) != 0);
1619   }
1620   FindClose (hff);
1621 }
1622
1623 /*****************************************************************************
1624  * WCMD_setshow_default
1625  *
1626  *      Set/Show the current default directory
1627  */
1628
1629 void WCMD_setshow_default (WCHAR *command) {
1630
1631   BOOL status;
1632   WCHAR string[1024];
1633   WCHAR cwd[1024];
1634   WCHAR *pos;
1635   WIN32_FIND_DATA fd;
1636   HANDLE hff;
1637   static const WCHAR parmD[] = {'/','D','\0'};
1638
1639   WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
1640
1641   /* Skip /D and trailing whitespace if on the front of the command line */
1642   if (CompareString (LOCALE_USER_DEFAULT,
1643                      NORM_IGNORECASE | SORT_STRINGSORT,
1644                      command, 2, parmD, -1) == 2) {
1645     command += 2;
1646     while (*command && *command==' ') command++;
1647   }
1648
1649   GetCurrentDirectory (sizeof(cwd)/sizeof(WCHAR), cwd);
1650   if (strlenW(command) == 0) {
1651     strcatW (cwd, newline);
1652     WCMD_output (cwd);
1653   }
1654   else {
1655     /* Remove any double quotes, which may be in the
1656        middle, eg. cd "C:\Program Files"\Microsoft is ok */
1657     pos = string;
1658     while (*command) {
1659       if (*command != '"') *pos++ = *command;
1660       command++;
1661     }
1662     *pos = 0x00;
1663
1664     /* Search for approprate directory */
1665     WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
1666     hff = FindFirstFile (string, &fd);
1667     while (hff != INVALID_HANDLE_VALUE) {
1668       if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1669         WCHAR fpath[MAX_PATH];
1670         WCHAR drive[10];
1671         WCHAR dir[MAX_PATH];
1672         WCHAR fname[MAX_PATH];
1673         WCHAR ext[MAX_PATH];
1674         static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
1675
1676         /* Convert path into actual directory spec */
1677         GetFullPathName (string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1678         WCMD_splitpath(fpath, drive, dir, fname, ext);
1679
1680         /* Rebuild path */
1681         wsprintf(string, fmt, drive, dir, fd.cFileName);
1682
1683         FindClose(hff);
1684         hff = INVALID_HANDLE_VALUE;
1685         break;
1686       }
1687
1688       /* Step on to next match */
1689       if (FindNextFile(hff, &fd) == 0) {
1690         FindClose(hff);
1691         hff = INVALID_HANDLE_VALUE;
1692         break;
1693       }
1694     }
1695
1696     /* Change to that directory */
1697     WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
1698
1699     status = SetCurrentDirectory (string);
1700     if (!status) {
1701       errorlevel = 1;
1702       WCMD_print_error ();
1703       return;
1704     } else {
1705
1706       /* Restore old directory if drive letter would change, and
1707            CD x:\directory /D (or pushd c:\directory) not supplied */
1708       if ((strstrW(quals, parmD) == NULL) &&
1709           (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
1710         SetCurrentDirectory(cwd);
1711       }
1712     }
1713
1714     /* Set special =C: type environment variable, for drive letter of
1715        change of directory, even if path was restored due to missing
1716        /D (allows changing drive letter when not resident on that
1717        drive                                                          */
1718     if ((string[1] == ':') && IsCharAlpha (string[0])) {
1719       WCHAR env[4];
1720       strcpyW(env, equalW);
1721       memcpy(env+1, string, 2 * sizeof(WCHAR));
1722       env[3] = 0x00;
1723       WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
1724       SetEnvironmentVariable(env, string);
1725     }
1726
1727    }
1728   return;
1729 }
1730
1731 /****************************************************************************
1732  * WCMD_setshow_date
1733  *
1734  * Set/Show the system date
1735  * FIXME: Can't change date yet
1736  */
1737
1738 void WCMD_setshow_date (void) {
1739
1740   WCHAR curdate[64], buffer[64];
1741   DWORD count;
1742   static const WCHAR parmT[] = {'/','T','\0'};
1743
1744   if (strlenW(param1) == 0) {
1745     if (GetDateFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL,
1746                 curdate, sizeof(curdate)/sizeof(WCHAR))) {
1747       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
1748       if (strstrW (quals, parmT) == NULL) {
1749         WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
1750         WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
1751                        buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1752         if (count > 2) {
1753           WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1754         }
1755       }
1756     }
1757     else WCMD_print_error ();
1758   }
1759   else {
1760     WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1761   }
1762 }
1763
1764 /****************************************************************************
1765  * WCMD_compare
1766  */
1767 static int WCMD_compare( const void *a, const void *b )
1768 {
1769     int r;
1770     const WCHAR * const *str_a = a, * const *str_b = b;
1771     r = CompareString( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1772           *str_a, -1, *str_b, -1 );
1773     if( r == CSTR_LESS_THAN ) return -1;
1774     if( r == CSTR_GREATER_THAN ) return 1;
1775     return 0;
1776 }
1777
1778 /****************************************************************************
1779  * WCMD_setshow_sortenv
1780  *
1781  * sort variables into order for display
1782  * Optionally only display those who start with a stub
1783  * returns the count displayed
1784  */
1785 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
1786 {
1787   UINT count=0, len=0, i, displayedcount=0, stublen=0;
1788   const WCHAR **str;
1789
1790   if (stub) stublen = strlenW(stub);
1791
1792   /* count the number of strings, and the total length */
1793   while ( s[len] ) {
1794     len += (strlenW(&s[len]) + 1);
1795     count++;
1796   }
1797
1798   /* add the strings to an array */
1799   str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
1800   if( !str )
1801     return 0;
1802   str[0] = s;
1803   for( i=1; i<count; i++ )
1804     str[i] = str[i-1] + strlenW(str[i-1]) + 1;
1805
1806   /* sort the array */
1807   qsort( str, count, sizeof (WCHAR*), WCMD_compare );
1808
1809   /* print it */
1810   for( i=0; i<count; i++ ) {
1811     if (!stub || CompareString (LOCALE_USER_DEFAULT,
1812                                 NORM_IGNORECASE | SORT_STRINGSORT,
1813                                 str[i], stublen, stub, -1) == 2) {
1814       /* Don't display special internal variables */
1815       if (str[i][0] != '=') {
1816         WCMD_output_asis(str[i]);
1817         WCMD_output_asis(newline);
1818         displayedcount++;
1819       }
1820     }
1821   }
1822
1823   LocalFree( str );
1824   return displayedcount;
1825 }
1826
1827 /****************************************************************************
1828  * WCMD_setshow_env
1829  *
1830  * Set/Show the environment variables
1831  */
1832
1833 void WCMD_setshow_env (WCHAR *s) {
1834
1835   LPVOID env;
1836   WCHAR *p;
1837   int status;
1838   static const WCHAR parmP[] = {'/','P','\0'};
1839
1840   errorlevel = 0;
1841   if (param1[0] == 0x00 && quals[0] == 0x00) {
1842     env = GetEnvironmentStrings ();
1843     WCMD_setshow_sortenv( env, NULL );
1844     return;
1845   }
1846
1847   /* See if /P supplied, and if so echo the prompt, and read in a reply */
1848   if (CompareString (LOCALE_USER_DEFAULT,
1849                      NORM_IGNORECASE | SORT_STRINGSORT,
1850                      s, 2, parmP, -1) == 2) {
1851     WCHAR string[MAXSTRING];
1852     DWORD count;
1853
1854     s += 2;
1855     while (*s && *s==' ') s++;
1856
1857     /* If no parameter, or no '=' sign, return an error */
1858     if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
1859       WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1860       return;
1861     }
1862
1863     /* Output the prompt */
1864     *p++ = '\0';
1865     if (strlenW(p) != 0) WCMD_output(p);
1866
1867     /* Read the reply */
1868     WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1869                    sizeof(string)/sizeof(WCHAR), &count, NULL);
1870     if (count > 1) {
1871       string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
1872       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
1873       WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
1874                  wine_dbgstr_w(string));
1875       status = SetEnvironmentVariable (s, string);
1876     }
1877
1878   } else {
1879     DWORD gle;
1880     p = strchrW (s, '=');
1881     if (p == NULL) {
1882       env = GetEnvironmentStrings ();
1883       if (WCMD_setshow_sortenv( env, s ) == 0) {
1884         WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
1885         errorlevel = 1;
1886       }
1887       return;
1888     }
1889     *p++ = '\0';
1890
1891     if (strlenW(p) == 0) p = NULL;
1892     status = SetEnvironmentVariable (s, p);
1893     gle = GetLastError();
1894     if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
1895       errorlevel = 1;
1896     } else if ((!status)) WCMD_print_error();
1897   }
1898 }
1899
1900 /****************************************************************************
1901  * WCMD_setshow_path
1902  *
1903  * Set/Show the path environment variable
1904  */
1905
1906 void WCMD_setshow_path (WCHAR *command) {
1907
1908   WCHAR string[1024];
1909   DWORD status;
1910   static const WCHAR pathW[] = {'P','A','T','H','\0'};
1911   static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
1912
1913   if (strlenW(param1) == 0) {
1914     status = GetEnvironmentVariable (pathW, string, sizeof(string)/sizeof(WCHAR));
1915     if (status != 0) {
1916       WCMD_output_asis ( pathEqW);
1917       WCMD_output_asis ( string);
1918       WCMD_output_asis ( newline);
1919     }
1920     else {
1921       WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
1922     }
1923   }
1924   else {
1925     if (*command == '=') command++; /* Skip leading '=' */
1926     status = SetEnvironmentVariable (pathW, command);
1927     if (!status) WCMD_print_error();
1928   }
1929 }
1930
1931 /****************************************************************************
1932  * WCMD_setshow_prompt
1933  *
1934  * Set or show the command prompt.
1935  */
1936
1937 void WCMD_setshow_prompt (void) {
1938
1939   WCHAR *s;
1940   static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
1941
1942   if (strlenW(param1) == 0) {
1943     SetEnvironmentVariable (promptW, NULL);
1944   }
1945   else {
1946     s = param1;
1947     while ((*s == '=') || (*s == ' ')) s++;
1948     if (strlenW(s) == 0) {
1949       SetEnvironmentVariable (promptW, NULL);
1950     }
1951     else SetEnvironmentVariable (promptW, s);
1952   }
1953 }
1954
1955 /****************************************************************************
1956  * WCMD_setshow_time
1957  *
1958  * Set/Show the system time
1959  * FIXME: Can't change time yet
1960  */
1961
1962 void WCMD_setshow_time (void) {
1963
1964   WCHAR curtime[64], buffer[64];
1965   DWORD count;
1966   SYSTEMTIME st;
1967   static const WCHAR parmT[] = {'/','T','\0'};
1968
1969   if (strlenW(param1) == 0) {
1970     GetLocalTime(&st);
1971     if (GetTimeFormat (LOCALE_USER_DEFAULT, 0, &st, NULL,
1972                 curtime, sizeof(curtime)/sizeof(WCHAR))) {
1973       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curtime);
1974       if (strstrW (quals, parmT) == NULL) {
1975         WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
1976         WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
1977                        sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1978         if (count > 2) {
1979           WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1980         }
1981       }
1982     }
1983     else WCMD_print_error ();
1984   }
1985   else {
1986     WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1987   }
1988 }
1989
1990 /****************************************************************************
1991  * WCMD_shift
1992  *
1993  * Shift batch parameters.
1994  * Optional /n says where to start shifting (n=0-8)
1995  */
1996
1997 void WCMD_shift (WCHAR *command) {
1998   int start;
1999
2000   if (context != NULL) {
2001     WCHAR *pos = strchrW(command, '/');
2002     int   i;
2003
2004     if (pos == NULL) {
2005       start = 0;
2006     } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2007       start = (*(pos+1) - '0');
2008     } else {
2009       SetLastError(ERROR_INVALID_PARAMETER);
2010       WCMD_print_error();
2011       return;
2012     }
2013
2014     WINE_TRACE("Shifting variables, starting at %d\n", start);
2015     for (i=start;i<=8;i++) {
2016       context -> shift_count[i] = context -> shift_count[i+1] + 1;
2017     }
2018     context -> shift_count[9] = context -> shift_count[9] + 1;
2019   }
2020
2021 }
2022
2023 /****************************************************************************
2024  * WCMD_title
2025  *
2026  * Set the console title
2027  */
2028 void WCMD_title (WCHAR *command) {
2029   SetConsoleTitle(command);
2030 }
2031
2032 /****************************************************************************
2033  * WCMD_type
2034  *
2035  * Copy a file to standard output.
2036  */
2037
2038 void WCMD_type (WCHAR *command) {
2039
2040   int   argno         = 0;
2041   WCHAR *argN          = command;
2042   BOOL  writeHeaders  = FALSE;
2043
2044   if (param1[0] == 0x00) {
2045     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2046     return;
2047   }
2048
2049   if (param2[0] != 0x00) writeHeaders = TRUE;
2050
2051   /* Loop through all args */
2052   errorlevel = 0;
2053   while (argN) {
2054     WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2055
2056     HANDLE h;
2057     WCHAR buffer[512];
2058     DWORD count;
2059
2060     if (!argN) break;
2061
2062     WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2063     h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2064                 FILE_ATTRIBUTE_NORMAL, NULL);
2065     if (h == INVALID_HANDLE_VALUE) {
2066       WCMD_print_error ();
2067       WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2068       errorlevel = 1;
2069     } else {
2070       if (writeHeaders) {
2071         static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2072         WCMD_output(fmt, thisArg);
2073       }
2074       while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL)) {
2075         if (count == 0) break;  /* ReadFile reports success on EOF! */
2076         buffer[count] = 0;
2077         WCMD_output_asis (buffer);
2078       }
2079       CloseHandle (h);
2080     }
2081   }
2082 }
2083
2084 /****************************************************************************
2085  * WCMD_more
2086  *
2087  * Output either a file or stdin to screen in pages
2088  */
2089
2090 void WCMD_more (WCHAR *command) {
2091
2092   int   argno         = 0;
2093   WCHAR *argN          = command;
2094   BOOL  useinput      = FALSE;
2095   WCHAR  moreStr[100];
2096   WCHAR  moreStrPage[100];
2097   WCHAR  buffer[512];
2098   DWORD count;
2099   static const WCHAR moreStart[] = {'-','-',' ','\0'};
2100   static const WCHAR moreFmt[]   = {'%','s',' ','-','-','\n','\0'};
2101   static const WCHAR moreFmt2[]  = {'%','s',' ','(','%','2','.','2','d','%','%',
2102                                     ')',' ','-','-','\n','\0'};
2103   static const WCHAR conInW[]    = {'C','O','N','I','N','$','\0'};
2104
2105   /* Prefix the NLS more with '-- ', then load the text */
2106   errorlevel = 0;
2107   strcpyW(moreStr, moreStart);
2108   LoadString (hinst, WCMD_MORESTR, &moreStr[3],
2109               (sizeof(moreStr)/sizeof(WCHAR))-3);
2110
2111   if (param1[0] == 0x00) {
2112
2113     /* Wine implements pipes via temporary files, and hence stdin is
2114        effectively reading from the file. This means the prompts for
2115        more are satistied by the next line from the input (file). To
2116        avoid this, ensure stdin is to the console                    */
2117     HANDLE hstdin  = GetStdHandle(STD_INPUT_HANDLE);
2118     HANDLE hConIn = CreateFile(conInW, GENERIC_READ | GENERIC_WRITE,
2119                          FILE_SHARE_READ, NULL, OPEN_EXISTING,
2120                          FILE_ATTRIBUTE_NORMAL, 0);
2121     SetStdHandle(STD_INPUT_HANDLE, hConIn);
2122
2123     /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2124        once you get in this bit unless due to a pipe, its going to end badly...  */
2125     useinput = TRUE;
2126     wsprintf(moreStrPage, moreFmt, moreStr);
2127
2128     WCMD_enter_paged_mode(moreStrPage);
2129     while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2130       if (count == 0) break;    /* ReadFile reports success on EOF! */
2131       buffer[count] = 0;
2132       WCMD_output_asis (buffer);
2133     }
2134     WCMD_leave_paged_mode();
2135
2136     /* Restore stdin to what it was */
2137     SetStdHandle(STD_INPUT_HANDLE, hstdin);
2138     CloseHandle(hConIn);
2139
2140     return;
2141   } else {
2142     BOOL needsPause = FALSE;
2143
2144     /* Loop through all args */
2145     WCMD_enter_paged_mode(moreStrPage);
2146
2147     while (argN) {
2148       WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2149       HANDLE h;
2150
2151       if (!argN) break;
2152
2153       if (needsPause) {
2154
2155         /* Wait */
2156         wsprintf(moreStrPage, moreFmt2, moreStr, 100);
2157         WCMD_leave_paged_mode();
2158         WCMD_output_asis(moreStrPage);
2159         WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2160                        sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2161         WCMD_enter_paged_mode(moreStrPage);
2162       }
2163
2164
2165       WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2166       h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2167                 FILE_ATTRIBUTE_NORMAL, NULL);
2168       if (h == INVALID_HANDLE_VALUE) {
2169         WCMD_print_error ();
2170         WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2171         errorlevel = 1;
2172       } else {
2173         ULONG64 curPos  = 0;
2174         ULONG64 fileLen = 0;
2175         WIN32_FILE_ATTRIBUTE_DATA   fileInfo;
2176
2177         /* Get the file size */
2178         GetFileAttributesEx(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2179         fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2180
2181         needsPause = TRUE;
2182         while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2183           if (count == 0) break;        /* ReadFile reports success on EOF! */
2184           buffer[count] = 0;
2185           curPos += count;
2186
2187           /* Update % count (would be used in WCMD_output_asis as prompt) */
2188           wsprintf(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2189
2190           WCMD_output_asis (buffer);
2191         }
2192         CloseHandle (h);
2193       }
2194     }
2195
2196     WCMD_leave_paged_mode();
2197   }
2198 }
2199
2200 /****************************************************************************
2201  * WCMD_verify
2202  *
2203  * Display verify flag.
2204  * FIXME: We don't actually do anything with the verify flag other than toggle
2205  * it...
2206  */
2207
2208 void WCMD_verify (WCHAR *command) {
2209
2210   int count;
2211
2212   count = strlenW(command);
2213   if (count == 0) {
2214     if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2215     else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2216     return;
2217   }
2218   if (lstrcmpiW(command, onW) == 0) {
2219     verify_mode = 1;
2220     return;
2221   }
2222   else if (lstrcmpiW(command, offW) == 0) {
2223     verify_mode = 0;
2224     return;
2225   }
2226   else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2227 }
2228
2229 /****************************************************************************
2230  * WCMD_version
2231  *
2232  * Display version info.
2233  */
2234
2235 void WCMD_version (void) {
2236
2237   WCMD_output (version_string);
2238
2239 }
2240
2241 /****************************************************************************
2242  * WCMD_volume
2243  *
2244  * Display volume info and/or set volume label. Returns 0 if error.
2245  */
2246
2247 int WCMD_volume (int mode, WCHAR *path) {
2248
2249   DWORD count, serial;
2250   WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2251   BOOL status;
2252
2253   if (strlenW(path) == 0) {
2254     status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
2255     if (!status) {
2256       WCMD_print_error ();
2257       return 0;
2258     }
2259     status = GetVolumeInformation (NULL, label, sizeof(label)/sizeof(WCHAR),
2260                                    &serial, NULL, NULL, NULL, 0);
2261   }
2262   else {
2263     static const WCHAR fmt[] = {'%','s','\\','\0'};
2264     if ((path[1] != ':') || (strlenW(path) != 2)) {
2265       WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2266       return 0;
2267     }
2268     wsprintf (curdir, fmt, path);
2269     status = GetVolumeInformation (curdir, label, sizeof(label)/sizeof(WCHAR),
2270                                    &serial, NULL,
2271         NULL, NULL, 0);
2272   }
2273   if (!status) {
2274     WCMD_print_error ();
2275     return 0;
2276   }
2277   WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2278         curdir[0], label, HIWORD(serial), LOWORD(serial));
2279   if (mode) {
2280     WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2281     WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2282                    sizeof(string)/sizeof(WCHAR), &count, NULL);
2283     if (count > 1) {
2284       string[count-1] = '\0';           /* ReadFile output is not null-terminated! */
2285       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2286     }
2287     if (strlenW(path) != 0) {
2288       if (!SetVolumeLabel (curdir, string)) WCMD_print_error ();
2289     }
2290     else {
2291       if (!SetVolumeLabel (NULL, string)) WCMD_print_error ();
2292     }
2293   }
2294   return 1;
2295 }
2296
2297 /**************************************************************************
2298  * WCMD_exit
2299  *
2300  * Exit either the process, or just this batch program
2301  *
2302  */
2303
2304 void WCMD_exit (CMD_LIST **cmdList) {
2305
2306     static const WCHAR parmB[] = {'/','B','\0'};
2307     int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2308
2309     if (context && lstrcmpiW(quals, parmB) == 0) {
2310         errorlevel = rc;
2311         context -> skip_rest = TRUE;
2312         *cmdList = NULL;
2313     } else {
2314         ExitProcess(rc);
2315     }
2316 }
2317
2318 /**************************************************************************
2319  * WCMD_ask_confirm
2320  *
2321  * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
2322  * answer.
2323  *
2324  * Returns True if Y (or A) answer is selected
2325  *         If optionAll contains a pointer, ALL is allowed, and if answered
2326  *                   set to TRUE
2327  *
2328  */
2329 BOOL WCMD_ask_confirm (WCHAR *message, BOOL showSureText, BOOL *optionAll) {
2330
2331     WCHAR  msgbuffer[MAXSTRING];
2332     WCHAR  Ybuffer[MAXSTRING];
2333     WCHAR  Nbuffer[MAXSTRING];
2334     WCHAR  Abuffer[MAXSTRING];
2335     WCHAR  answer[MAX_PATH] = {'\0'};
2336     DWORD count = 0;
2337
2338     /* Load the translated 'Are you sure', plus valid answers */
2339     LoadString (hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2340     LoadString (hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
2341     LoadString (hinst, WCMD_NO,  Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
2342     LoadString (hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
2343
2344     /* Loop waiting on a Y or N */
2345     while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
2346       static const WCHAR startBkt[] = {' ','(','\0'};
2347       static const WCHAR endBkt[]   = {')','?','\0'};
2348
2349       WCMD_output_asis (message);
2350       if (showSureText) {
2351         WCMD_output_asis (msgbuffer);
2352       }
2353       WCMD_output_asis (startBkt);
2354       WCMD_output_asis (Ybuffer);
2355       WCMD_output_asis (fslashW);
2356       WCMD_output_asis (Nbuffer);
2357       if (optionAll) {
2358           WCMD_output_asis (fslashW);
2359           WCMD_output_asis (Abuffer);
2360       }
2361       WCMD_output_asis (endBkt);
2362       WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer,
2363                      sizeof(answer)/sizeof(WCHAR), &count, NULL);
2364       answer[0] = toupperW(answer[0]);
2365     }
2366
2367     /* Return the answer */
2368     return ((answer[0] == Ybuffer[0]) ||
2369             (optionAll && (answer[0] == Abuffer[0])));
2370 }
2371
2372 /*****************************************************************************
2373  * WCMD_assoc
2374  *
2375  *      Lists or sets file associations  (assoc = TRUE)
2376  *      Lists or sets file types         (assoc = FALSE)
2377  */
2378 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2379
2380     HKEY    key;
2381     DWORD   accessOptions = KEY_READ;
2382     WCHAR   *newValue;
2383     LONG    rc = ERROR_SUCCESS;
2384     WCHAR    keyValue[MAXSTRING];
2385     DWORD   valueLen = MAXSTRING;
2386     HKEY    readKey;
2387     static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2388                                      'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2389
2390     /* See if parameter includes '=' */
2391     errorlevel = 0;
2392     newValue = strchrW(command, '=');
2393     if (newValue) accessOptions |= KEY_WRITE;
2394
2395     /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2396     if (RegOpenKeyEx(HKEY_CLASSES_ROOT, nullW, 0,
2397                      accessOptions, &key) != ERROR_SUCCESS) {
2398       WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2399       return;
2400     }
2401
2402     /* If no parameters then list all associations */
2403     if (*command == 0x00) {
2404       int index = 0;
2405
2406       /* Enumerate all the keys */
2407       while (rc != ERROR_NO_MORE_ITEMS) {
2408         WCHAR  keyName[MAXSTRING];
2409         DWORD nameLen;
2410
2411         /* Find the next value */
2412         nameLen = MAXSTRING;
2413         rc = RegEnumKeyEx(key, index++,
2414                           keyName, &nameLen,
2415                           NULL, NULL, NULL, NULL);
2416
2417         if (rc == ERROR_SUCCESS) {
2418
2419           /* Only interested in extension ones if assoc, or others
2420              if not assoc                                          */
2421           if ((keyName[0] == '.' && assoc) ||
2422               (!(keyName[0] == '.') && (!assoc)))
2423           {
2424             WCHAR subkey[MAXSTRING];
2425             strcpyW(subkey, keyName);
2426             if (!assoc) strcatW(subkey, shOpCmdW);
2427
2428             if (RegOpenKeyEx(key, subkey, 0,
2429                              accessOptions, &readKey) == ERROR_SUCCESS) {
2430
2431               valueLen = sizeof(keyValue)/sizeof(WCHAR);
2432               rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2433                                    (LPBYTE)keyValue, &valueLen);
2434               WCMD_output_asis(keyName);
2435               WCMD_output_asis(equalW);
2436               /* If no default value found, leave line empty after '=' */
2437               if (rc == ERROR_SUCCESS) {
2438                 WCMD_output_asis(keyValue);
2439               }
2440               WCMD_output_asis(newline);
2441             }
2442           }
2443         }
2444       }
2445       RegCloseKey(readKey);
2446
2447     } else {
2448
2449       /* Parameter supplied - if no '=' on command line, its a query */
2450       if (newValue == NULL) {
2451         WCHAR *space;
2452         WCHAR subkey[MAXSTRING];
2453
2454         /* Query terminates the parameter at the first space */
2455         strcpyW(keyValue, command);
2456         space = strchrW(keyValue, ' ');
2457         if (space) *space=0x00;
2458
2459         /* Set up key name */
2460         strcpyW(subkey, keyValue);
2461         if (!assoc) strcatW(subkey, shOpCmdW);
2462
2463         if (RegOpenKeyEx(key, subkey, 0,
2464                          accessOptions, &readKey) == ERROR_SUCCESS) {
2465
2466           rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2467                                (LPBYTE)keyValue, &valueLen);
2468           WCMD_output_asis(command);
2469           WCMD_output_asis(equalW);
2470           /* If no default value found, leave line empty after '=' */
2471           if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2472           WCMD_output_asis(newline);
2473           RegCloseKey(readKey);
2474
2475         } else {
2476           WCHAR  msgbuffer[MAXSTRING];
2477           WCHAR  outbuffer[MAXSTRING];
2478
2479           /* Load the translated 'File association not found' */
2480           if (assoc) {
2481             LoadString (hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2482           } else {
2483             LoadString (hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2484           }
2485           wsprintf(outbuffer, msgbuffer, keyValue);
2486           WCMD_output_asis(outbuffer);
2487           errorlevel = 2;
2488         }
2489
2490       /* Not a query - its a set or clear of a value */
2491       } else {
2492
2493         WCHAR subkey[MAXSTRING];
2494
2495         /* Get pointer to new value */
2496         *newValue = 0x00;
2497         newValue++;
2498
2499         /* Set up key name */
2500         strcpyW(subkey, command);
2501         if (!assoc) strcatW(subkey, shOpCmdW);
2502
2503         /* If nothing after '=' then clear value - only valid for ASSOC */
2504         if (*newValue == 0x00) {
2505
2506           if (assoc) rc = RegDeleteKey(key, command);
2507           if (assoc && rc == ERROR_SUCCESS) {
2508             WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2509
2510           } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2511             WCMD_print_error();
2512             errorlevel = 2;
2513
2514           } else {
2515             WCHAR  msgbuffer[MAXSTRING];
2516             WCHAR  outbuffer[MAXSTRING];
2517
2518             /* Load the translated 'File association not found' */
2519             if (assoc) {
2520               LoadString (hinst, WCMD_NOASSOC, msgbuffer,
2521                           sizeof(msgbuffer)/sizeof(WCHAR));
2522             } else {
2523               LoadString (hinst, WCMD_NOFTYPE, msgbuffer,
2524                           sizeof(msgbuffer)/sizeof(WCHAR));
2525             }
2526             wsprintf(outbuffer, msgbuffer, keyValue);
2527             WCMD_output_asis(outbuffer);
2528             errorlevel = 2;
2529           }
2530
2531         /* It really is a set value = contents */
2532         } else {
2533           rc = RegCreateKeyEx(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2534                               accessOptions, NULL, &readKey, NULL);
2535           if (rc == ERROR_SUCCESS) {
2536             rc = RegSetValueEx(readKey, NULL, 0, REG_SZ,
2537                                  (LPBYTE)newValue, strlenW(newValue));
2538             RegCloseKey(readKey);
2539           }
2540
2541           if (rc != ERROR_SUCCESS) {
2542             WCMD_print_error();
2543             errorlevel = 2;
2544           } else {
2545             WCMD_output_asis(command);
2546             WCMD_output_asis(equalW);
2547             WCMD_output_asis(newValue);
2548             WCMD_output_asis(newline);
2549           }
2550         }
2551       }
2552     }
2553
2554     /* Clean up */
2555     RegCloseKey(key);
2556 }
2557
2558 /****************************************************************************
2559  * WCMD_color
2560  *
2561  * Clear the terminal screen.
2562  */
2563
2564 void WCMD_color (void) {
2565
2566   /* Emulate by filling the screen from the top left to bottom right with
2567         spaces, then moving the cursor to the top left afterwards */
2568   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2569   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2570
2571   if (param1[0] != 0x00 && strlenW(param1) > 2) {
2572     WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2573     return;
2574   }
2575
2576   if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2577   {
2578       COORD topLeft;
2579       DWORD screenSize;
2580       DWORD color = 0;
2581
2582       screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2583
2584       topLeft.X = 0;
2585       topLeft.Y = 0;
2586
2587       /* Convert the color hex digits */
2588       if (param1[0] == 0x00) {
2589         color = defaultColor;
2590       } else {
2591         color = strtoulW(param1, NULL, 16);
2592       }
2593
2594       /* Fail if fg == bg color */
2595       if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2596         errorlevel = 1;
2597         return;
2598       }
2599
2600       /* Set the current screen contents and ensure all future writes
2601          remain this color                                             */
2602       FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2603       SetConsoleTextAttribute(hStdOut, color);
2604   }
2605 }