cmd: Add support for wildcards in copy.
[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   BOOL isDirs = FALSE;
658
659   /* Check:
660      the first line includes the % variable name as first parm
661      we have been provided with more parts to the command
662      and there is at least some set data
663      and IN as the one after that                                   */
664   if (lstrcmpiW (WCMD_parameter (p, 1, NULL), inW)
665       || (*cmdList) == NULL
666       || (*cmdList)->nextcommand == NULL
667       || (param1[0] != '%')
668       || (strlenW(param1) > 3)) {
669     WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
670     return;
671   }
672
673   /* Save away where the set of data starts and the variable */
674   strcpyW(variable, param1);
675   thisDepth = (*cmdList)->bracketDepth;
676   *cmdList = (*cmdList)->nextcommand;
677   setStart = (*cmdList);
678
679   /* Skip until the close bracket */
680   WINE_TRACE("Searching %p as the set\n", *cmdList);
681   while (*cmdList &&
682          (*cmdList)->command != NULL &&
683          (*cmdList)->bracketDepth > thisDepth) {
684     WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
685     *cmdList = (*cmdList)->nextcommand;
686   }
687
688   /* Skip the close bracket, if there is one */
689   if (*cmdList) *cmdList = (*cmdList)->nextcommand;
690
691   /* Syntax error if missing close bracket, or nothing following it
692      and once we have the complete set, we expect a DO              */
693   WINE_TRACE("Looking for 'do' in %p\n", *cmdList);
694   if ((*cmdList == NULL) ||
695       (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
696                             (*cmdList)->command, 3, doW, -1) != 2)) {
697       WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
698       return;
699   }
700
701   /* Save away the starting position for the commands (and offset for the
702      first one                                                           */
703   cmdStart = *cmdList;
704   cmdEnd   = *cmdList;
705   firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
706
707   thisSet = setStart;
708   /* Loop through all set entries */
709   while (thisSet &&
710          thisSet->command != NULL &&
711          thisSet->bracketDepth >= thisDepth) {
712
713     /* Loop through all entries on the same line */
714     WCHAR *item;
715
716     WINE_TRACE("Processing for set %p\n", thisSet);
717     i = 0;
718     while (*(item = WCMD_parameter (thisSet->command, i, NULL))) {
719
720       /*
721        * If the parameter within the set has a wildcard then search for matching files
722        * otherwise do a literal substitution.
723        */
724       static const WCHAR wildcards[] = {'*','?','\0'};
725       CMD_LIST *thisCmdStart = cmdStart;
726
727       WINE_TRACE("Processing for item '%s'\n", wine_dbgstr_w(item));
728       if (strpbrkW (item, wildcards)) {
729         hff = FindFirstFile (item, &fd);
730         if (hff != INVALID_HANDLE_VALUE) {
731           do {
732             BOOL isDirectory = (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
733             if ((isDirs && isDirectory) ||
734                 (!isDirs && !isDirectory))
735             {
736               thisCmdStart = cmdStart;
737               WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
738               WCMD_part_execute (&thisCmdStart, firstCmd, variable,
739                                            fd.cFileName, FALSE, TRUE);
740             }
741
742           } while (FindNextFile(hff, &fd) != 0);
743           FindClose (hff);
744         }
745       } else {
746         WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
747       }
748
749       WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
750       cmdEnd = thisCmdStart;
751       i++;
752     }
753
754     /* Move onto the next set line */
755     thisSet = thisSet->nextcommand;
756   }
757
758   /* When the loop ends, either something like a GOTO or EXIT /b has terminated
759      all processing, OR it should be pointing to the end of && processing OR
760      it should be pointing at the NULL end of bracket for the DO. The return
761      value needs to be the NEXT command to execute, which it either is, or
762      we need to step over the closing bracket                                  */
763   *cmdList = cmdEnd;
764   if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
765 }
766
767
768 /*****************************************************************************
769  * WCMD_part_execute
770  *
771  * Execute a command, and any && or bracketed follow on to the command. The
772  * first command to be executed may not be at the front of the
773  * commands->thiscommand string (eg. it may point after a DO or ELSE
774  * Returns TRUE if something like exit or goto has aborted all processing
775  */
776 void WCMD_part_execute(CMD_LIST **cmdList, WCHAR *firstcmd, WCHAR *variable,
777                        WCHAR *value, BOOL isIF, BOOL conditionTRUE) {
778
779   CMD_LIST *curPosition = *cmdList;
780   int myDepth = (*cmdList)->bracketDepth;
781
782   WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n",
783              cmdList, wine_dbgstr_w(firstcmd),
784              wine_dbgstr_w(variable), wine_dbgstr_w(value),
785              conditionTRUE);
786
787   /* Skip leading whitespace between condition and the command */
788   while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
789
790   /* Process the first command, if there is one */
791   if (conditionTRUE && firstcmd && *firstcmd) {
792     WCHAR *command = WCMD_strdupW(firstcmd);
793     WCMD_execute (firstcmd, variable, value, cmdList);
794     free (command);
795   }
796
797
798   /* If it didn't move the position, step to next command */
799   if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
800
801   /* Process any other parts of the command */
802   if (*cmdList) {
803     BOOL processThese = TRUE;
804
805     if (isIF) processThese = conditionTRUE;
806
807     while (*cmdList) {
808       const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
809
810       /* execute all appropriate commands */
811       curPosition = *cmdList;
812
813       WINE_TRACE("Processing cmdList(%p) - &(%d) bd(%d / %d)\n",
814                  *cmdList,
815                  (*cmdList)->isAmphersand,
816                  (*cmdList)->bracketDepth, myDepth);
817
818       /* Execute any appended to the statement with &&'s */
819       if ((*cmdList)->isAmphersand) {
820         if (processThese) {
821           WCMD_execute ((*cmdList)->command, variable, value, cmdList);
822         }
823         if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
824
825       /* Execute any appended to the statement with (...) */
826       } else if ((*cmdList)->bracketDepth > myDepth) {
827         if (processThese) {
828           *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
829           WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
830         }
831         if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
832
833       /* End of the command - does 'ELSE ' follow as the next command? */
834       } else {
835         if (isIF && CompareString (LOCALE_USER_DEFAULT,
836                                    NORM_IGNORECASE | SORT_STRINGSORT,
837                            (*cmdList)->command, 5, ifElse, -1) == 2) {
838
839           /* Swap between if and else processing */
840           processThese = !processThese;
841
842           /* Process the ELSE part */
843           if (processThese) {
844             WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
845
846             /* Skip leading whitespace between condition and the command */
847             while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
848             if (*cmd) {
849               WCMD_execute (cmd, variable, value, cmdList);
850             }
851           }
852           if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
853         } else {
854           WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
855           break;
856         }
857       }
858     }
859   }
860   return;
861 }
862
863 /*****************************************************************************
864  * WCMD_Execute
865  *
866  *      Execute a command after substituting variable text for the supplied parameter
867  */
868
869 void WCMD_execute (WCHAR *orig_cmd, WCHAR *param, WCHAR *subst, CMD_LIST **cmdList) {
870
871   WCHAR *new_cmd, *p, *s, *dup;
872   int size;
873
874   if (param) {
875     size = (strlenW (orig_cmd) + 1) * sizeof(WCHAR);
876     new_cmd = (WCHAR *) LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, size);
877     dup = s = WCMD_strdupW(orig_cmd);
878
879     while ((p = strstrW (s, param))) {
880       *p = '\0';
881       size += strlenW (subst) * sizeof(WCHAR);
882       new_cmd = (WCHAR *) LocalReAlloc ((HANDLE)new_cmd, size, 0);
883       strcatW (new_cmd, s);
884       strcatW (new_cmd, subst);
885       s = p + strlenW (param);
886     }
887     strcatW (new_cmd, s);
888     WCMD_process_command (new_cmd, cmdList);
889     free (dup);
890     LocalFree ((HANDLE)new_cmd);
891   } else {
892     WCMD_process_command (orig_cmd, cmdList);
893   }
894 }
895
896
897 /**************************************************************************
898  * WCMD_give_help
899  *
900  *      Simple on-line help. Help text is stored in the resource file.
901  */
902
903 void WCMD_give_help (WCHAR *command) {
904
905   int i;
906
907   command = WCMD_strtrim_leading_spaces(command);
908   if (strlenW(command) == 0) {
909     WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
910   }
911   else {
912     for (i=0; i<=WCMD_EXIT; i++) {
913       if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
914           param1, -1, inbuilt[i], -1) == 2) {
915         WCMD_output_asis (WCMD_LoadMessage(i));
916         return;
917       }
918     }
919     WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), param1);
920   }
921   return;
922 }
923
924 /****************************************************************************
925  * WCMD_go_to
926  *
927  * Batch file jump instruction. Not the most efficient algorithm ;-)
928  * Prints error message if the specified label cannot be found - the file pointer is
929  * then at EOF, effectively stopping the batch file.
930  * FIXME: DOS is supposed to allow labels with spaces - we don't.
931  */
932
933 void WCMD_goto (CMD_LIST **cmdList) {
934
935   WCHAR string[MAX_PATH];
936
937   /* Do not process any more parts of a processed multipart or multilines command */
938   *cmdList = NULL;
939
940   if (param1[0] == 0x00) {
941     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
942     return;
943   }
944   if (context != NULL) {
945     WCHAR *paramStart = param1;
946     static const WCHAR eofW[] = {':','e','o','f','\0'};
947
948     /* Handle special :EOF label */
949     if (lstrcmpiW (eofW, param1) == 0) {
950       context -> skip_rest = TRUE;
951       return;
952     }
953
954     /* Support goto :label as well as goto label */
955     if (*paramStart == ':') paramStart++;
956
957     SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
958     while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
959       if ((string[0] == ':') && (lstrcmpiW (&string[1], paramStart) == 0)) return;
960     }
961     WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
962   }
963   return;
964 }
965
966 /*****************************************************************************
967  * WCMD_pushd
968  *
969  *      Push a directory onto the stack
970  */
971
972 void WCMD_pushd (WCHAR *command) {
973     struct env_stack *curdir;
974     WCHAR *thisdir;
975     static const WCHAR parmD[] = {'/','D','\0'};
976
977     if (strchrW(command, '/') != NULL) {
978       SetLastError(ERROR_INVALID_PARAMETER);
979       WCMD_print_error();
980       return;
981     }
982
983     curdir  = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
984     thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
985     if( !curdir || !thisdir ) {
986       LocalFree(curdir);
987       LocalFree(thisdir);
988       WINE_ERR ("out of memory\n");
989       return;
990     }
991
992     /* Change directory using CD code with /D parameter */
993     strcpyW(quals, parmD);
994     GetCurrentDirectoryW (1024, thisdir);
995     errorlevel = 0;
996     WCMD_setshow_default(command);
997     if (errorlevel) {
998       LocalFree(curdir);
999       LocalFree(thisdir);
1000       return;
1001     } else {
1002       curdir -> next    = pushd_directories;
1003       curdir -> strings = thisdir;
1004       if (pushd_directories == NULL) {
1005         curdir -> u.stackdepth = 1;
1006       } else {
1007         curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1008       }
1009       pushd_directories = curdir;
1010     }
1011 }
1012
1013
1014 /*****************************************************************************
1015  * WCMD_popd
1016  *
1017  *      Pop a directory from the stack
1018  */
1019
1020 void WCMD_popd (void) {
1021     struct env_stack *temp = pushd_directories;
1022
1023     if (!pushd_directories)
1024       return;
1025
1026     /* pop the old environment from the stack, and make it the current dir */
1027     pushd_directories = temp->next;
1028     SetCurrentDirectoryW(temp->strings);
1029     LocalFree (temp->strings);
1030     LocalFree (temp);
1031 }
1032
1033 /****************************************************************************
1034  * WCMD_if
1035  *
1036  * Batch file conditional.
1037  *
1038  * On entry, cmdlist will point to command containing the IF, and optionally
1039  *   the first command to execute (if brackets not found)
1040  *   If &&'s were found, this may be followed by a record flagged as isAmpersand
1041  *   If ('s were found, execute all within that bracket
1042  *   Command may optionally be followed by an ELSE - need to skip instructions
1043  *   in the else using the same logic
1044  *
1045  * FIXME: Much more syntax checking needed!
1046  */
1047
1048 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1049
1050   int negate = 0, test = 0;
1051   WCHAR condition[MAX_PATH], *command, *s;
1052   static const WCHAR notW[]    = {'n','o','t','\0'};
1053   static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1054   static const WCHAR existW[]  = {'e','x','i','s','t','\0'};
1055   static const WCHAR defdW[]   = {'d','e','f','i','n','e','d','\0'};
1056   static const WCHAR eqeqW[]   = {'=','=','\0'};
1057
1058   if (!lstrcmpiW (param1, notW)) {
1059     negate = 1;
1060     strcpyW (condition, param2);
1061   }
1062   else {
1063     strcpyW (condition, param1);
1064   }
1065   if (!lstrcmpiW (condition, errlvlW)) {
1066     if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1067     WCMD_parameter (p, 2+negate, &command);
1068   }
1069   else if (!lstrcmpiW (condition, existW)) {
1070     if (GetFileAttributes(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1071         test = 1;
1072     }
1073     WCMD_parameter (p, 2+negate, &command);
1074   }
1075   else if (!lstrcmpiW (condition, defdW)) {
1076     if (GetEnvironmentVariable(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1077         test = 1;
1078     }
1079     WCMD_parameter (p, 2+negate, &command);
1080   }
1081   else if ((s = strstrW (p, eqeqW))) {
1082     s += 2;
1083     if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1084     WCMD_parameter (s, 1, &command);
1085   }
1086   else {
1087     WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1088     return;
1089   }
1090
1091   /* Process rest of IF statement which is on the same line
1092      Note: This may process all or some of the cmdList (eg a GOTO) */
1093   WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1094 }
1095
1096 /****************************************************************************
1097  * WCMD_move
1098  *
1099  * Move a file, directory tree or wildcarded set of files.
1100  */
1101
1102 void WCMD_move (void) {
1103
1104   int             status;
1105   WIN32_FIND_DATA fd;
1106   HANDLE          hff;
1107   WCHAR            input[MAX_PATH];
1108   WCHAR            output[MAX_PATH];
1109   WCHAR            drive[10];
1110   WCHAR            dir[MAX_PATH];
1111   WCHAR            fname[MAX_PATH];
1112   WCHAR            ext[MAX_PATH];
1113
1114   if (param1[0] == 0x00) {
1115     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1116     return;
1117   }
1118
1119   /* If no destination supplied, assume current directory */
1120   if (param2[0] == 0x00) {
1121       strcpyW(param2, dotW);
1122   }
1123
1124   /* If 2nd parm is directory, then use original filename */
1125   /* Convert partial path to full path */
1126   GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1127   GetFullPathName (param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1128   WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1129              wine_dbgstr_w(param1), wine_dbgstr_w(output));
1130
1131   /* Split into components */
1132   WCMD_splitpath(input, drive, dir, fname, ext);
1133
1134   hff = FindFirstFile (input, &fd);
1135   while (hff != INVALID_HANDLE_VALUE) {
1136     WCHAR  dest[MAX_PATH];
1137     WCHAR  src[MAX_PATH];
1138     DWORD attribs;
1139
1140     WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1141
1142     /* Build src & dest name */
1143     strcpyW(src, drive);
1144     strcatW(src, dir);
1145
1146     /* See if dest is an existing directory */
1147     attribs = GetFileAttributes(output);
1148     if (attribs != INVALID_FILE_ATTRIBUTES &&
1149        (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1150       strcpyW(dest, output);
1151       strcatW(dest, slashW);
1152       strcatW(dest, fd.cFileName);
1153     } else {
1154       strcpyW(dest, output);
1155     }
1156
1157     strcatW(src, fd.cFileName);
1158
1159     WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1160     WINE_TRACE("Dest   '%s'\n", wine_dbgstr_w(dest));
1161
1162     /* Check if file is read only, otherwise move it */
1163     attribs = GetFileAttributes(src);
1164     if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1165         (attribs & FILE_ATTRIBUTE_READONLY)) {
1166       SetLastError(ERROR_ACCESS_DENIED);
1167       status = 0;
1168     } else {
1169       BOOL ok = TRUE;
1170
1171       /* If destination exists, prompt unless /Y supplied */
1172       if (GetFileAttributes(dest) != INVALID_FILE_ATTRIBUTES) {
1173         BOOL force = FALSE;
1174         WCHAR copycmd[MAXSTRING];
1175         int len;
1176
1177         /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1178         if (strstrW (quals, parmNoY))
1179           force = FALSE;
1180         else if (strstrW (quals, parmY))
1181           force = TRUE;
1182         else {
1183           const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1184           len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1185           force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1186                        && ! lstrcmpiW (copycmd, parmY));
1187         }
1188
1189         /* Prompt if overwriting */
1190         if (!force) {
1191           WCHAR  question[MAXSTRING];
1192           WCHAR  yesChar[10];
1193
1194           strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1195
1196           /* Ask for confirmation */
1197           wsprintf(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1198           ok = WCMD_ask_confirm(question, FALSE, NULL);
1199
1200           /* So delete the destination prior to the move */
1201           if (ok) {
1202             if (!DeleteFile (dest)) {
1203               WCMD_print_error ();
1204               errorlevel = 1;
1205               ok = FALSE;
1206             }
1207           }
1208         }
1209       }
1210
1211       if (ok) {
1212         status = MoveFile (src, dest);
1213       } else {
1214         status = 1; /* Anything other than 0 to prevent error msg below */
1215       }
1216     }
1217
1218     if (!status) {
1219       WCMD_print_error ();
1220       errorlevel = 1;
1221     }
1222
1223     /* Step on to next match */
1224     if (FindNextFile(hff, &fd) == 0) {
1225       FindClose(hff);
1226       hff = INVALID_HANDLE_VALUE;
1227       break;
1228     }
1229   }
1230 }
1231
1232 /****************************************************************************
1233  * WCMD_pause
1234  *
1235  * Wait for keyboard input.
1236  */
1237
1238 void WCMD_pause (void) {
1239
1240   DWORD count;
1241   WCHAR string[32];
1242
1243   WCMD_output (anykey);
1244   WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1245                  sizeof(string)/sizeof(WCHAR), &count, NULL);
1246 }
1247
1248 /****************************************************************************
1249  * WCMD_remove_dir
1250  *
1251  * Delete a directory.
1252  */
1253
1254 void WCMD_remove_dir (WCHAR *command) {
1255
1256   int   argno         = 0;
1257   int   argsProcessed = 0;
1258   WCHAR *argN          = command;
1259   static const WCHAR parmS[] = {'/','S','\0'};
1260   static const WCHAR parmQ[] = {'/','Q','\0'};
1261
1262   /* Loop through all args */
1263   while (argN) {
1264     WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1265     if (argN && argN[0] != '/') {
1266       WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1267                  wine_dbgstr_w(quals));
1268       argsProcessed++;
1269
1270       /* If subdirectory search not supplied, just try to remove
1271          and report error if it fails (eg if it contains a file) */
1272       if (strstrW (quals, parmS) == NULL) {
1273         if (!RemoveDirectory (thisArg)) WCMD_print_error ();
1274
1275       /* Otherwise use ShFileOp to recursively remove a directory */
1276       } else {
1277
1278         SHFILEOPSTRUCT lpDir;
1279
1280         /* Ask first */
1281         if (strstrW (quals, parmQ) == NULL) {
1282           BOOL  ok;
1283           WCHAR  question[MAXSTRING];
1284           static const WCHAR fmt[] = {'%','s',' ','\0'};
1285
1286           /* Ask for confirmation */
1287           wsprintf(question, fmt, thisArg);
1288           ok = WCMD_ask_confirm(question, TRUE, NULL);
1289
1290           /* Abort if answer is 'N' */
1291           if (!ok) return;
1292         }
1293
1294         /* Do the delete */
1295         lpDir.hwnd   = NULL;
1296         lpDir.pTo    = NULL;
1297         lpDir.pFrom  = thisArg;
1298         lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1299         lpDir.wFunc  = FO_DELETE;
1300         if (SHFileOperation(&lpDir)) WCMD_print_error ();
1301       }
1302     }
1303   }
1304
1305   /* Handle no valid args */
1306   if (argsProcessed == 0) {
1307     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1308     return;
1309   }
1310
1311 }
1312
1313 /****************************************************************************
1314  * WCMD_rename
1315  *
1316  * Rename a file.
1317  */
1318
1319 void WCMD_rename (void) {
1320
1321   int             status;
1322   HANDLE          hff;
1323   WIN32_FIND_DATA fd;
1324   WCHAR            input[MAX_PATH];
1325   WCHAR           *dotDst = NULL;
1326   WCHAR            drive[10];
1327   WCHAR            dir[MAX_PATH];
1328   WCHAR            fname[MAX_PATH];
1329   WCHAR            ext[MAX_PATH];
1330   DWORD           attribs;
1331
1332   errorlevel = 0;
1333
1334   /* Must be at least two args */
1335   if (param1[0] == 0x00 || param2[0] == 0x00) {
1336     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1337     errorlevel = 1;
1338     return;
1339   }
1340
1341   /* Destination cannot contain a drive letter or directory separator */
1342   if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1343       SetLastError(ERROR_INVALID_PARAMETER);
1344       WCMD_print_error();
1345       errorlevel = 1;
1346       return;
1347   }
1348
1349   /* Convert partial path to full path */
1350   GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1351   WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1352              wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1353   dotDst = strchrW(param2, '.');
1354
1355   /* Split into components */
1356   WCMD_splitpath(input, drive, dir, fname, ext);
1357
1358   hff = FindFirstFile (input, &fd);
1359   while (hff != INVALID_HANDLE_VALUE) {
1360     WCHAR  dest[MAX_PATH];
1361     WCHAR  src[MAX_PATH];
1362     WCHAR *dotSrc = NULL;
1363     int   dirLen;
1364
1365     WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1366
1367     /* FIXME: If dest name or extension is *, replace with filename/ext
1368        part otherwise use supplied name. This supports:
1369           ren *.fred *.jim
1370           ren jim.* fred.* etc
1371        However, windows has a more complex algorithum supporting eg
1372           ?'s and *'s mid name                                         */
1373     dotSrc = strchrW(fd.cFileName, '.');
1374
1375     /* Build src & dest name */
1376     strcpyW(src, drive);
1377     strcatW(src, dir);
1378     strcpyW(dest, src);
1379     dirLen = strlenW(src);
1380     strcatW(src, fd.cFileName);
1381
1382     /* Build name */
1383     if (param2[0] == '*') {
1384       strcatW(dest, fd.cFileName);
1385       if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1386     } else {
1387       strcatW(dest, param2);
1388       if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1389     }
1390
1391     /* Build Extension */
1392     if (dotDst && (*(dotDst+1)=='*')) {
1393       if (dotSrc) strcatW(dest, dotSrc);
1394     } else if (dotDst) {
1395       if (dotDst) strcatW(dest, dotDst);
1396     }
1397
1398     WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1399     WINE_TRACE("Dest   '%s'\n", wine_dbgstr_w(dest));
1400
1401     /* Check if file is read only, otherwise move it */
1402     attribs = GetFileAttributes(src);
1403     if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1404         (attribs & FILE_ATTRIBUTE_READONLY)) {
1405       SetLastError(ERROR_ACCESS_DENIED);
1406       status = 0;
1407     } else {
1408       status = MoveFile (src, dest);
1409     }
1410
1411     if (!status) {
1412       WCMD_print_error ();
1413       errorlevel = 1;
1414     }
1415
1416     /* Step on to next match */
1417     if (FindNextFile(hff, &fd) == 0) {
1418       FindClose(hff);
1419       hff = INVALID_HANDLE_VALUE;
1420       break;
1421     }
1422   }
1423 }
1424
1425 /*****************************************************************************
1426  * WCMD_dupenv
1427  *
1428  * Make a copy of the environment.
1429  */
1430 static WCHAR *WCMD_dupenv( const WCHAR *env )
1431 {
1432   WCHAR *env_copy;
1433   int len;
1434
1435   if( !env )
1436     return NULL;
1437
1438   len = 0;
1439   while ( env[len] )
1440     len += (strlenW(&env[len]) + 1);
1441
1442   env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1443   if (!env_copy)
1444   {
1445     WINE_ERR("out of memory\n");
1446     return env_copy;
1447   }
1448   memcpy (env_copy, env, len*sizeof (WCHAR));
1449   env_copy[len] = 0;
1450
1451   return env_copy;
1452 }
1453
1454 /*****************************************************************************
1455  * WCMD_setlocal
1456  *
1457  *  setlocal pushes the environment onto a stack
1458  *  Save the environment as unicode so we don't screw anything up.
1459  */
1460 void WCMD_setlocal (const WCHAR *s) {
1461   WCHAR *env;
1462   struct env_stack *env_copy;
1463   WCHAR cwd[MAX_PATH];
1464
1465   /* DISABLEEXTENSIONS ignored */
1466
1467   env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1468   if( !env_copy )
1469   {
1470     WINE_ERR ("out of memory\n");
1471     return;
1472   }
1473
1474   env = GetEnvironmentStringsW ();
1475
1476   env_copy->strings = WCMD_dupenv (env);
1477   if (env_copy->strings)
1478   {
1479     env_copy->next = saved_environment;
1480     saved_environment = env_copy;
1481
1482     /* Save the current drive letter */
1483     GetCurrentDirectory (MAX_PATH, cwd);
1484     env_copy->u.cwd = cwd[0];
1485   }
1486   else
1487     LocalFree (env_copy);
1488
1489   FreeEnvironmentStringsW (env);
1490
1491 }
1492
1493 /*****************************************************************************
1494  * WCMD_endlocal
1495  *
1496  *  endlocal pops the environment off a stack
1497  *  Note: When searching for '=', search from WCHAR position 1, to handle
1498  *        special internal environment variables =C:, =D: etc
1499  */
1500 void WCMD_endlocal (void) {
1501   WCHAR *env, *old, *p;
1502   struct env_stack *temp;
1503   int len, n;
1504
1505   if (!saved_environment)
1506     return;
1507
1508   /* pop the old environment from the stack */
1509   temp = saved_environment;
1510   saved_environment = temp->next;
1511
1512   /* delete the current environment, totally */
1513   env = GetEnvironmentStringsW ();
1514   old = WCMD_dupenv (GetEnvironmentStringsW ());
1515   len = 0;
1516   while (old[len]) {
1517     n = strlenW(&old[len]) + 1;
1518     p = strchrW(&old[len] + 1, '=');
1519     if (p)
1520     {
1521       *p++ = 0;
1522       SetEnvironmentVariableW (&old[len], NULL);
1523     }
1524     len += n;
1525   }
1526   LocalFree (old);
1527   FreeEnvironmentStringsW (env);
1528
1529   /* restore old environment */
1530   env = temp->strings;
1531   len = 0;
1532   while (env[len]) {
1533     n = strlenW(&env[len]) + 1;
1534     p = strchrW(&env[len] + 1, '=');
1535     if (p)
1536     {
1537       *p++ = 0;
1538       SetEnvironmentVariableW (&env[len], p);
1539     }
1540     len += n;
1541   }
1542
1543   /* Restore current drive letter */
1544   if (IsCharAlpha(temp->u.cwd)) {
1545     WCHAR envvar[4];
1546     WCHAR cwd[MAX_PATH];
1547     static const WCHAR fmt[] = {'=','%','c',':','\0'};
1548
1549     wsprintf(envvar, fmt, temp->u.cwd);
1550     if (GetEnvironmentVariable(envvar, cwd, MAX_PATH)) {
1551       WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1552       SetCurrentDirectory(cwd);
1553     }
1554   }
1555
1556   LocalFree (env);
1557   LocalFree (temp);
1558 }
1559
1560 /*****************************************************************************
1561  * WCMD_setshow_attrib
1562  *
1563  * Display and optionally sets DOS attributes on a file or directory
1564  *
1565  * FIXME: Wine currently uses the Unix stat() function to get file attributes.
1566  * As a result only the Readonly flag is correctly reported, the Archive bit
1567  * is always set and the rest are not implemented. We do the Right Thing anyway.
1568  *
1569  * FIXME: No SET functionality.
1570  *
1571  */
1572
1573 void WCMD_setshow_attrib (void) {
1574
1575   DWORD count;
1576   HANDLE hff;
1577   WIN32_FIND_DATA fd;
1578   WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1579
1580   if (param1[0] == '-') {
1581     WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1582     return;
1583   }
1584
1585   if (strlenW(param1) == 0) {
1586     static const WCHAR slashStarW[]  = {'\\','*','\0'};
1587
1588     GetCurrentDirectory (sizeof(param1)/sizeof(WCHAR), param1);
1589     strcatW (param1, slashStarW);
1590   }
1591
1592   hff = FindFirstFile (param1, &fd);
1593   if (hff == INVALID_HANDLE_VALUE) {
1594     WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), param1);
1595   }
1596   else {
1597     do {
1598       if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1599         static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1600         if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
1601           flags[0] = 'H';
1602         }
1603         if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
1604           flags[1] = 'S';
1605         }
1606         if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
1607           flags[2] = 'A';
1608         }
1609         if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
1610           flags[3] = 'R';
1611         }
1612         if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
1613           flags[4] = 'T';
1614         }
1615         if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
1616           flags[5] = 'C';
1617         }
1618         WCMD_output (fmt, flags, fd.cFileName);
1619         for (count=0; count < 8; count++) flags[count] = ' ';
1620       }
1621     } while (FindNextFile(hff, &fd) != 0);
1622   }
1623   FindClose (hff);
1624 }
1625
1626 /*****************************************************************************
1627  * WCMD_setshow_default
1628  *
1629  *      Set/Show the current default directory
1630  */
1631
1632 void WCMD_setshow_default (WCHAR *command) {
1633
1634   BOOL status;
1635   WCHAR string[1024];
1636   WCHAR cwd[1024];
1637   WCHAR *pos;
1638   WIN32_FIND_DATA fd;
1639   HANDLE hff;
1640   static const WCHAR parmD[] = {'/','D','\0'};
1641
1642   WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
1643
1644   /* Skip /D and trailing whitespace if on the front of the command line */
1645   if (CompareString (LOCALE_USER_DEFAULT,
1646                      NORM_IGNORECASE | SORT_STRINGSORT,
1647                      command, 2, parmD, -1) == 2) {
1648     command += 2;
1649     while (*command && *command==' ') command++;
1650   }
1651
1652   GetCurrentDirectory (sizeof(cwd)/sizeof(WCHAR), cwd);
1653   if (strlenW(command) == 0) {
1654     strcatW (cwd, newline);
1655     WCMD_output (cwd);
1656   }
1657   else {
1658     /* Remove any double quotes, which may be in the
1659        middle, eg. cd "C:\Program Files"\Microsoft is ok */
1660     pos = string;
1661     while (*command) {
1662       if (*command != '"') *pos++ = *command;
1663       command++;
1664     }
1665     *pos = 0x00;
1666
1667     /* Search for approprate directory */
1668     WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
1669     hff = FindFirstFile (string, &fd);
1670     while (hff != INVALID_HANDLE_VALUE) {
1671       if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1672         WCHAR fpath[MAX_PATH];
1673         WCHAR drive[10];
1674         WCHAR dir[MAX_PATH];
1675         WCHAR fname[MAX_PATH];
1676         WCHAR ext[MAX_PATH];
1677         static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
1678
1679         /* Convert path into actual directory spec */
1680         GetFullPathName (string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1681         WCMD_splitpath(fpath, drive, dir, fname, ext);
1682
1683         /* Rebuild path */
1684         wsprintf(string, fmt, drive, dir, fd.cFileName);
1685
1686         FindClose(hff);
1687         hff = INVALID_HANDLE_VALUE;
1688         break;
1689       }
1690
1691       /* Step on to next match */
1692       if (FindNextFile(hff, &fd) == 0) {
1693         FindClose(hff);
1694         hff = INVALID_HANDLE_VALUE;
1695         break;
1696       }
1697     }
1698
1699     /* Change to that directory */
1700     WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
1701
1702     status = SetCurrentDirectory (string);
1703     if (!status) {
1704       errorlevel = 1;
1705       WCMD_print_error ();
1706       return;
1707     } else {
1708
1709       /* Restore old directory if drive letter would change, and
1710            CD x:\directory /D (or pushd c:\directory) not supplied */
1711       if ((strstrW(quals, parmD) == NULL) &&
1712           (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
1713         SetCurrentDirectory(cwd);
1714       }
1715     }
1716
1717     /* Set special =C: type environment variable, for drive letter of
1718        change of directory, even if path was restored due to missing
1719        /D (allows changing drive letter when not resident on that
1720        drive                                                          */
1721     if ((string[1] == ':') && IsCharAlpha (string[0])) {
1722       WCHAR env[4];
1723       strcpyW(env, equalW);
1724       memcpy(env+1, string, 2 * sizeof(WCHAR));
1725       env[3] = 0x00;
1726       WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
1727       SetEnvironmentVariable(env, string);
1728     }
1729
1730    }
1731   return;
1732 }
1733
1734 /****************************************************************************
1735  * WCMD_setshow_date
1736  *
1737  * Set/Show the system date
1738  * FIXME: Can't change date yet
1739  */
1740
1741 void WCMD_setshow_date (void) {
1742
1743   WCHAR curdate[64], buffer[64];
1744   DWORD count;
1745   static const WCHAR parmT[] = {'/','T','\0'};
1746
1747   if (strlenW(param1) == 0) {
1748     if (GetDateFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL,
1749                 curdate, sizeof(curdate)/sizeof(WCHAR))) {
1750       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
1751       if (strstrW (quals, parmT) == NULL) {
1752         WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
1753         WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
1754                        buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1755         if (count > 2) {
1756           WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1757         }
1758       }
1759     }
1760     else WCMD_print_error ();
1761   }
1762   else {
1763     WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1764   }
1765 }
1766
1767 /****************************************************************************
1768  * WCMD_compare
1769  */
1770 static int WCMD_compare( const void *a, const void *b )
1771 {
1772     int r;
1773     const WCHAR * const *str_a = a, * const *str_b = b;
1774     r = CompareString( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1775           *str_a, -1, *str_b, -1 );
1776     if( r == CSTR_LESS_THAN ) return -1;
1777     if( r == CSTR_GREATER_THAN ) return 1;
1778     return 0;
1779 }
1780
1781 /****************************************************************************
1782  * WCMD_setshow_sortenv
1783  *
1784  * sort variables into order for display
1785  * Optionally only display those who start with a stub
1786  * returns the count displayed
1787  */
1788 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
1789 {
1790   UINT count=0, len=0, i, displayedcount=0, stublen=0;
1791   const WCHAR **str;
1792
1793   if (stub) stublen = strlenW(stub);
1794
1795   /* count the number of strings, and the total length */
1796   while ( s[len] ) {
1797     len += (strlenW(&s[len]) + 1);
1798     count++;
1799   }
1800
1801   /* add the strings to an array */
1802   str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
1803   if( !str )
1804     return 0;
1805   str[0] = s;
1806   for( i=1; i<count; i++ )
1807     str[i] = str[i-1] + strlenW(str[i-1]) + 1;
1808
1809   /* sort the array */
1810   qsort( str, count, sizeof (WCHAR*), WCMD_compare );
1811
1812   /* print it */
1813   for( i=0; i<count; i++ ) {
1814     if (!stub || CompareString (LOCALE_USER_DEFAULT,
1815                                 NORM_IGNORECASE | SORT_STRINGSORT,
1816                                 str[i], stublen, stub, -1) == 2) {
1817       /* Don't display special internal variables */
1818       if (str[i][0] != '=') {
1819         WCMD_output_asis(str[i]);
1820         WCMD_output_asis(newline);
1821         displayedcount++;
1822       }
1823     }
1824   }
1825
1826   LocalFree( str );
1827   return displayedcount;
1828 }
1829
1830 /****************************************************************************
1831  * WCMD_setshow_env
1832  *
1833  * Set/Show the environment variables
1834  */
1835
1836 void WCMD_setshow_env (WCHAR *s) {
1837
1838   LPVOID env;
1839   WCHAR *p;
1840   int status;
1841   static const WCHAR parmP[] = {'/','P','\0'};
1842
1843   errorlevel = 0;
1844   if (param1[0] == 0x00 && quals[0] == 0x00) {
1845     env = GetEnvironmentStrings ();
1846     WCMD_setshow_sortenv( env, NULL );
1847     return;
1848   }
1849
1850   /* See if /P supplied, and if so echo the prompt, and read in a reply */
1851   if (CompareString (LOCALE_USER_DEFAULT,
1852                      NORM_IGNORECASE | SORT_STRINGSORT,
1853                      s, 2, parmP, -1) == 2) {
1854     WCHAR string[MAXSTRING];
1855     DWORD count;
1856
1857     s += 2;
1858     while (*s && *s==' ') s++;
1859
1860     /* If no parameter, or no '=' sign, return an error */
1861     if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
1862       WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1863       return;
1864     }
1865
1866     /* Output the prompt */
1867     *p++ = '\0';
1868     if (strlenW(p) != 0) WCMD_output(p);
1869
1870     /* Read the reply */
1871     WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1872                    sizeof(string)/sizeof(WCHAR), &count, NULL);
1873     if (count > 1) {
1874       string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
1875       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
1876       WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
1877                  wine_dbgstr_w(string));
1878       status = SetEnvironmentVariable (s, string);
1879     }
1880
1881   } else {
1882     DWORD gle;
1883     p = strchrW (s, '=');
1884     if (p == NULL) {
1885       env = GetEnvironmentStrings ();
1886       if (WCMD_setshow_sortenv( env, s ) == 0) {
1887         WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
1888         errorlevel = 1;
1889       }
1890       return;
1891     }
1892     *p++ = '\0';
1893
1894     if (strlenW(p) == 0) p = NULL;
1895     status = SetEnvironmentVariable (s, p);
1896     gle = GetLastError();
1897     if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
1898       errorlevel = 1;
1899     } else if ((!status)) WCMD_print_error();
1900   }
1901 }
1902
1903 /****************************************************************************
1904  * WCMD_setshow_path
1905  *
1906  * Set/Show the path environment variable
1907  */
1908
1909 void WCMD_setshow_path (WCHAR *command) {
1910
1911   WCHAR string[1024];
1912   DWORD status;
1913   static const WCHAR pathW[] = {'P','A','T','H','\0'};
1914   static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
1915
1916   if (strlenW(param1) == 0) {
1917     status = GetEnvironmentVariable (pathW, string, sizeof(string)/sizeof(WCHAR));
1918     if (status != 0) {
1919       WCMD_output_asis ( pathEqW);
1920       WCMD_output_asis ( string);
1921       WCMD_output_asis ( newline);
1922     }
1923     else {
1924       WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
1925     }
1926   }
1927   else {
1928     if (*command == '=') command++; /* Skip leading '=' */
1929     status = SetEnvironmentVariable (pathW, command);
1930     if (!status) WCMD_print_error();
1931   }
1932 }
1933
1934 /****************************************************************************
1935  * WCMD_setshow_prompt
1936  *
1937  * Set or show the command prompt.
1938  */
1939
1940 void WCMD_setshow_prompt (void) {
1941
1942   WCHAR *s;
1943   static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
1944
1945   if (strlenW(param1) == 0) {
1946     SetEnvironmentVariable (promptW, NULL);
1947   }
1948   else {
1949     s = param1;
1950     while ((*s == '=') || (*s == ' ')) s++;
1951     if (strlenW(s) == 0) {
1952       SetEnvironmentVariable (promptW, NULL);
1953     }
1954     else SetEnvironmentVariable (promptW, s);
1955   }
1956 }
1957
1958 /****************************************************************************
1959  * WCMD_setshow_time
1960  *
1961  * Set/Show the system time
1962  * FIXME: Can't change time yet
1963  */
1964
1965 void WCMD_setshow_time (void) {
1966
1967   WCHAR curtime[64], buffer[64];
1968   DWORD count;
1969   SYSTEMTIME st;
1970   static const WCHAR parmT[] = {'/','T','\0'};
1971
1972   if (strlenW(param1) == 0) {
1973     GetLocalTime(&st);
1974     if (GetTimeFormat (LOCALE_USER_DEFAULT, 0, &st, NULL,
1975                 curtime, sizeof(curtime)/sizeof(WCHAR))) {
1976       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curtime);
1977       if (strstrW (quals, parmT) == NULL) {
1978         WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
1979         WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
1980                        sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1981         if (count > 2) {
1982           WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1983         }
1984       }
1985     }
1986     else WCMD_print_error ();
1987   }
1988   else {
1989     WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1990   }
1991 }
1992
1993 /****************************************************************************
1994  * WCMD_shift
1995  *
1996  * Shift batch parameters.
1997  * Optional /n says where to start shifting (n=0-8)
1998  */
1999
2000 void WCMD_shift (WCHAR *command) {
2001   int start;
2002
2003   if (context != NULL) {
2004     WCHAR *pos = strchrW(command, '/');
2005     int   i;
2006
2007     if (pos == NULL) {
2008       start = 0;
2009     } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2010       start = (*(pos+1) - '0');
2011     } else {
2012       SetLastError(ERROR_INVALID_PARAMETER);
2013       WCMD_print_error();
2014       return;
2015     }
2016
2017     WINE_TRACE("Shifting variables, starting at %d\n", start);
2018     for (i=start;i<=8;i++) {
2019       context -> shift_count[i] = context -> shift_count[i+1] + 1;
2020     }
2021     context -> shift_count[9] = context -> shift_count[9] + 1;
2022   }
2023
2024 }
2025
2026 /****************************************************************************
2027  * WCMD_title
2028  *
2029  * Set the console title
2030  */
2031 void WCMD_title (WCHAR *command) {
2032   SetConsoleTitle(command);
2033 }
2034
2035 /****************************************************************************
2036  * WCMD_type
2037  *
2038  * Copy a file to standard output.
2039  */
2040
2041 void WCMD_type (WCHAR *command) {
2042
2043   int   argno         = 0;
2044   WCHAR *argN          = command;
2045   BOOL  writeHeaders  = FALSE;
2046
2047   if (param1[0] == 0x00) {
2048     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2049     return;
2050   }
2051
2052   if (param2[0] != 0x00) writeHeaders = TRUE;
2053
2054   /* Loop through all args */
2055   errorlevel = 0;
2056   while (argN) {
2057     WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2058
2059     HANDLE h;
2060     WCHAR buffer[512];
2061     DWORD count;
2062
2063     if (!argN) break;
2064
2065     WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2066     h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2067                 FILE_ATTRIBUTE_NORMAL, NULL);
2068     if (h == INVALID_HANDLE_VALUE) {
2069       WCMD_print_error ();
2070       WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2071       errorlevel = 1;
2072     } else {
2073       if (writeHeaders) {
2074         static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2075         WCMD_output(fmt, thisArg);
2076       }
2077       while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL)) {
2078         if (count == 0) break;  /* ReadFile reports success on EOF! */
2079         buffer[count] = 0;
2080         WCMD_output_asis (buffer);
2081       }
2082       CloseHandle (h);
2083     }
2084   }
2085 }
2086
2087 /****************************************************************************
2088  * WCMD_more
2089  *
2090  * Output either a file or stdin to screen in pages
2091  */
2092
2093 void WCMD_more (WCHAR *command) {
2094
2095   int   argno         = 0;
2096   WCHAR *argN          = command;
2097   BOOL  useinput      = FALSE;
2098   WCHAR  moreStr[100];
2099   WCHAR  moreStrPage[100];
2100   WCHAR  buffer[512];
2101   DWORD count;
2102   static const WCHAR moreStart[] = {'-','-',' ','\0'};
2103   static const WCHAR moreFmt[]   = {'%','s',' ','-','-','\n','\0'};
2104   static const WCHAR moreFmt2[]  = {'%','s',' ','(','%','2','.','2','d','%','%',
2105                                     ')',' ','-','-','\n','\0'};
2106   static const WCHAR conInW[]    = {'C','O','N','I','N','$','\0'};
2107
2108   /* Prefix the NLS more with '-- ', then load the text */
2109   errorlevel = 0;
2110   strcpyW(moreStr, moreStart);
2111   LoadString (hinst, WCMD_MORESTR, &moreStr[3],
2112               (sizeof(moreStr)/sizeof(WCHAR))-3);
2113
2114   if (param1[0] == 0x00) {
2115
2116     /* Wine implements pipes via temporary files, and hence stdin is
2117        effectively reading from the file. This means the prompts for
2118        more are satistied by the next line from the input (file). To
2119        avoid this, ensure stdin is to the console                    */
2120     HANDLE hstdin  = GetStdHandle(STD_INPUT_HANDLE);
2121     HANDLE hConIn = CreateFile(conInW, GENERIC_READ | GENERIC_WRITE,
2122                          FILE_SHARE_READ, NULL, OPEN_EXISTING,
2123                          FILE_ATTRIBUTE_NORMAL, 0);
2124     SetStdHandle(STD_INPUT_HANDLE, hConIn);
2125
2126     /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2127        once you get in this bit unless due to a pipe, its going to end badly...  */
2128     useinput = TRUE;
2129     wsprintf(moreStrPage, moreFmt, moreStr);
2130
2131     WCMD_enter_paged_mode(moreStrPage);
2132     while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2133       if (count == 0) break;    /* ReadFile reports success on EOF! */
2134       buffer[count] = 0;
2135       WCMD_output_asis (buffer);
2136     }
2137     WCMD_leave_paged_mode();
2138
2139     /* Restore stdin to what it was */
2140     SetStdHandle(STD_INPUT_HANDLE, hstdin);
2141     CloseHandle(hConIn);
2142
2143     return;
2144   } else {
2145     BOOL needsPause = FALSE;
2146
2147     /* Loop through all args */
2148     WCMD_enter_paged_mode(moreStrPage);
2149
2150     while (argN) {
2151       WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2152       HANDLE h;
2153
2154       if (!argN) break;
2155
2156       if (needsPause) {
2157
2158         /* Wait */
2159         wsprintf(moreStrPage, moreFmt2, moreStr, 100);
2160         WCMD_leave_paged_mode();
2161         WCMD_output_asis(moreStrPage);
2162         WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2163                        sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2164         WCMD_enter_paged_mode(moreStrPage);
2165       }
2166
2167
2168       WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2169       h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2170                 FILE_ATTRIBUTE_NORMAL, NULL);
2171       if (h == INVALID_HANDLE_VALUE) {
2172         WCMD_print_error ();
2173         WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2174         errorlevel = 1;
2175       } else {
2176         ULONG64 curPos  = 0;
2177         ULONG64 fileLen = 0;
2178         WIN32_FILE_ATTRIBUTE_DATA   fileInfo;
2179
2180         /* Get the file size */
2181         GetFileAttributesEx(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2182         fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2183
2184         needsPause = TRUE;
2185         while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2186           if (count == 0) break;        /* ReadFile reports success on EOF! */
2187           buffer[count] = 0;
2188           curPos += count;
2189
2190           /* Update % count (would be used in WCMD_output_asis as prompt) */
2191           wsprintf(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2192
2193           WCMD_output_asis (buffer);
2194         }
2195         CloseHandle (h);
2196       }
2197     }
2198
2199     WCMD_leave_paged_mode();
2200   }
2201 }
2202
2203 /****************************************************************************
2204  * WCMD_verify
2205  *
2206  * Display verify flag.
2207  * FIXME: We don't actually do anything with the verify flag other than toggle
2208  * it...
2209  */
2210
2211 void WCMD_verify (WCHAR *command) {
2212
2213   int count;
2214
2215   count = strlenW(command);
2216   if (count == 0) {
2217     if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2218     else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2219     return;
2220   }
2221   if (lstrcmpiW(command, onW) == 0) {
2222     verify_mode = 1;
2223     return;
2224   }
2225   else if (lstrcmpiW(command, offW) == 0) {
2226     verify_mode = 0;
2227     return;
2228   }
2229   else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2230 }
2231
2232 /****************************************************************************
2233  * WCMD_version
2234  *
2235  * Display version info.
2236  */
2237
2238 void WCMD_version (void) {
2239
2240   WCMD_output (version_string);
2241
2242 }
2243
2244 /****************************************************************************
2245  * WCMD_volume
2246  *
2247  * Display volume info and/or set volume label. Returns 0 if error.
2248  */
2249
2250 int WCMD_volume (int mode, WCHAR *path) {
2251
2252   DWORD count, serial;
2253   WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2254   BOOL status;
2255
2256   if (strlenW(path) == 0) {
2257     status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
2258     if (!status) {
2259       WCMD_print_error ();
2260       return 0;
2261     }
2262     status = GetVolumeInformation (NULL, label, sizeof(label)/sizeof(WCHAR),
2263                                    &serial, NULL, NULL, NULL, 0);
2264   }
2265   else {
2266     static const WCHAR fmt[] = {'%','s','\\','\0'};
2267     if ((path[1] != ':') || (strlenW(path) != 2)) {
2268       WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2269       return 0;
2270     }
2271     wsprintf (curdir, fmt, path);
2272     status = GetVolumeInformation (curdir, label, sizeof(label)/sizeof(WCHAR),
2273                                    &serial, NULL,
2274         NULL, NULL, 0);
2275   }
2276   if (!status) {
2277     WCMD_print_error ();
2278     return 0;
2279   }
2280   WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2281         curdir[0], label, HIWORD(serial), LOWORD(serial));
2282   if (mode) {
2283     WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2284     WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2285                    sizeof(string)/sizeof(WCHAR), &count, NULL);
2286     if (count > 1) {
2287       string[count-1] = '\0';           /* ReadFile output is not null-terminated! */
2288       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2289     }
2290     if (strlenW(path) != 0) {
2291       if (!SetVolumeLabel (curdir, string)) WCMD_print_error ();
2292     }
2293     else {
2294       if (!SetVolumeLabel (NULL, string)) WCMD_print_error ();
2295     }
2296   }
2297   return 1;
2298 }
2299
2300 /**************************************************************************
2301  * WCMD_exit
2302  *
2303  * Exit either the process, or just this batch program
2304  *
2305  */
2306
2307 void WCMD_exit (CMD_LIST **cmdList) {
2308
2309     static const WCHAR parmB[] = {'/','B','\0'};
2310     int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2311
2312     if (context && lstrcmpiW(quals, parmB) == 0) {
2313         errorlevel = rc;
2314         context -> skip_rest = TRUE;
2315         *cmdList = NULL;
2316     } else {
2317         ExitProcess(rc);
2318     }
2319 }
2320
2321 /**************************************************************************
2322  * WCMD_ask_confirm
2323  *
2324  * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
2325  * answer.
2326  *
2327  * Returns True if Y (or A) answer is selected
2328  *         If optionAll contains a pointer, ALL is allowed, and if answered
2329  *                   set to TRUE
2330  *
2331  */
2332 BOOL WCMD_ask_confirm (WCHAR *message, BOOL showSureText, BOOL *optionAll) {
2333
2334     WCHAR  msgbuffer[MAXSTRING];
2335     WCHAR  Ybuffer[MAXSTRING];
2336     WCHAR  Nbuffer[MAXSTRING];
2337     WCHAR  Abuffer[MAXSTRING];
2338     WCHAR  answer[MAX_PATH] = {'\0'};
2339     DWORD count = 0;
2340
2341     /* Load the translated 'Are you sure', plus valid answers */
2342     LoadString (hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2343     LoadString (hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
2344     LoadString (hinst, WCMD_NO,  Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
2345     LoadString (hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
2346
2347     /* Loop waiting on a Y or N */
2348     while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
2349       static const WCHAR startBkt[] = {' ','(','\0'};
2350       static const WCHAR endBkt[]   = {')','?','\0'};
2351
2352       WCMD_output_asis (message);
2353       if (showSureText) {
2354         WCMD_output_asis (msgbuffer);
2355       }
2356       WCMD_output_asis (startBkt);
2357       WCMD_output_asis (Ybuffer);
2358       WCMD_output_asis (fslashW);
2359       WCMD_output_asis (Nbuffer);
2360       if (optionAll) {
2361           WCMD_output_asis (fslashW);
2362           WCMD_output_asis (Abuffer);
2363       }
2364       WCMD_output_asis (endBkt);
2365       WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer,
2366                      sizeof(answer)/sizeof(WCHAR), &count, NULL);
2367       answer[0] = toupperW(answer[0]);
2368     }
2369
2370     /* Return the answer */
2371     return ((answer[0] == Ybuffer[0]) ||
2372             (optionAll && (answer[0] == Abuffer[0])));
2373 }
2374
2375 /*****************************************************************************
2376  * WCMD_assoc
2377  *
2378  *      Lists or sets file associations  (assoc = TRUE)
2379  *      Lists or sets file types         (assoc = FALSE)
2380  */
2381 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2382
2383     HKEY    key;
2384     DWORD   accessOptions = KEY_READ;
2385     WCHAR   *newValue;
2386     LONG    rc = ERROR_SUCCESS;
2387     WCHAR    keyValue[MAXSTRING];
2388     DWORD   valueLen = MAXSTRING;
2389     HKEY    readKey;
2390     static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2391                                      'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2392
2393     /* See if parameter includes '=' */
2394     errorlevel = 0;
2395     newValue = strchrW(command, '=');
2396     if (newValue) accessOptions |= KEY_WRITE;
2397
2398     /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2399     if (RegOpenKeyEx(HKEY_CLASSES_ROOT, nullW, 0,
2400                      accessOptions, &key) != ERROR_SUCCESS) {
2401       WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2402       return;
2403     }
2404
2405     /* If no parameters then list all associations */
2406     if (*command == 0x00) {
2407       int index = 0;
2408
2409       /* Enumerate all the keys */
2410       while (rc != ERROR_NO_MORE_ITEMS) {
2411         WCHAR  keyName[MAXSTRING];
2412         DWORD nameLen;
2413
2414         /* Find the next value */
2415         nameLen = MAXSTRING;
2416         rc = RegEnumKeyEx(key, index++,
2417                           keyName, &nameLen,
2418                           NULL, NULL, NULL, NULL);
2419
2420         if (rc == ERROR_SUCCESS) {
2421
2422           /* Only interested in extension ones if assoc, or others
2423              if not assoc                                          */
2424           if ((keyName[0] == '.' && assoc) ||
2425               (!(keyName[0] == '.') && (!assoc)))
2426           {
2427             WCHAR subkey[MAXSTRING];
2428             strcpyW(subkey, keyName);
2429             if (!assoc) strcatW(subkey, shOpCmdW);
2430
2431             if (RegOpenKeyEx(key, subkey, 0,
2432                              accessOptions, &readKey) == ERROR_SUCCESS) {
2433
2434               valueLen = sizeof(keyValue)/sizeof(WCHAR);
2435               rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2436                                    (LPBYTE)keyValue, &valueLen);
2437               WCMD_output_asis(keyName);
2438               WCMD_output_asis(equalW);
2439               /* If no default value found, leave line empty after '=' */
2440               if (rc == ERROR_SUCCESS) {
2441                 WCMD_output_asis(keyValue);
2442               }
2443               WCMD_output_asis(newline);
2444             }
2445           }
2446         }
2447       }
2448       RegCloseKey(readKey);
2449
2450     } else {
2451
2452       /* Parameter supplied - if no '=' on command line, its a query */
2453       if (newValue == NULL) {
2454         WCHAR *space;
2455         WCHAR subkey[MAXSTRING];
2456
2457         /* Query terminates the parameter at the first space */
2458         strcpyW(keyValue, command);
2459         space = strchrW(keyValue, ' ');
2460         if (space) *space=0x00;
2461
2462         /* Set up key name */
2463         strcpyW(subkey, keyValue);
2464         if (!assoc) strcatW(subkey, shOpCmdW);
2465
2466         if (RegOpenKeyEx(key, subkey, 0,
2467                          accessOptions, &readKey) == ERROR_SUCCESS) {
2468
2469           rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2470                                (LPBYTE)keyValue, &valueLen);
2471           WCMD_output_asis(command);
2472           WCMD_output_asis(equalW);
2473           /* If no default value found, leave line empty after '=' */
2474           if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2475           WCMD_output_asis(newline);
2476           RegCloseKey(readKey);
2477
2478         } else {
2479           WCHAR  msgbuffer[MAXSTRING];
2480           WCHAR  outbuffer[MAXSTRING];
2481
2482           /* Load the translated 'File association not found' */
2483           if (assoc) {
2484             LoadString (hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2485           } else {
2486             LoadString (hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2487           }
2488           wsprintf(outbuffer, msgbuffer, keyValue);
2489           WCMD_output_asis(outbuffer);
2490           errorlevel = 2;
2491         }
2492
2493       /* Not a query - its a set or clear of a value */
2494       } else {
2495
2496         WCHAR subkey[MAXSTRING];
2497
2498         /* Get pointer to new value */
2499         *newValue = 0x00;
2500         newValue++;
2501
2502         /* Set up key name */
2503         strcpyW(subkey, command);
2504         if (!assoc) strcatW(subkey, shOpCmdW);
2505
2506         /* If nothing after '=' then clear value - only valid for ASSOC */
2507         if (*newValue == 0x00) {
2508
2509           if (assoc) rc = RegDeleteKey(key, command);
2510           if (assoc && rc == ERROR_SUCCESS) {
2511             WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2512
2513           } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2514             WCMD_print_error();
2515             errorlevel = 2;
2516
2517           } else {
2518             WCHAR  msgbuffer[MAXSTRING];
2519             WCHAR  outbuffer[MAXSTRING];
2520
2521             /* Load the translated 'File association not found' */
2522             if (assoc) {
2523               LoadString (hinst, WCMD_NOASSOC, msgbuffer,
2524                           sizeof(msgbuffer)/sizeof(WCHAR));
2525             } else {
2526               LoadString (hinst, WCMD_NOFTYPE, msgbuffer,
2527                           sizeof(msgbuffer)/sizeof(WCHAR));
2528             }
2529             wsprintf(outbuffer, msgbuffer, keyValue);
2530             WCMD_output_asis(outbuffer);
2531             errorlevel = 2;
2532           }
2533
2534         /* It really is a set value = contents */
2535         } else {
2536           rc = RegCreateKeyEx(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2537                               accessOptions, NULL, &readKey, NULL);
2538           if (rc == ERROR_SUCCESS) {
2539             rc = RegSetValueEx(readKey, NULL, 0, REG_SZ,
2540                                  (LPBYTE)newValue, strlenW(newValue));
2541             RegCloseKey(readKey);
2542           }
2543
2544           if (rc != ERROR_SUCCESS) {
2545             WCMD_print_error();
2546             errorlevel = 2;
2547           } else {
2548             WCMD_output_asis(command);
2549             WCMD_output_asis(equalW);
2550             WCMD_output_asis(newValue);
2551             WCMD_output_asis(newline);
2552           }
2553         }
2554       }
2555     }
2556
2557     /* Clean up */
2558     RegCloseKey(key);
2559 }
2560
2561 /****************************************************************************
2562  * WCMD_color
2563  *
2564  * Clear the terminal screen.
2565  */
2566
2567 void WCMD_color (void) {
2568
2569   /* Emulate by filling the screen from the top left to bottom right with
2570         spaces, then moving the cursor to the top left afterwards */
2571   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2572   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2573
2574   if (param1[0] != 0x00 && strlenW(param1) > 2) {
2575     WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2576     return;
2577   }
2578
2579   if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2580   {
2581       COORD topLeft;
2582       DWORD screenSize;
2583       DWORD color = 0;
2584
2585       screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2586
2587       topLeft.X = 0;
2588       topLeft.Y = 0;
2589
2590       /* Convert the color hex digits */
2591       if (param1[0] == 0x00) {
2592         color = defaultColor;
2593       } else {
2594         color = strtoulW(param1, NULL, 16);
2595       }
2596
2597       /* Fail if fg == bg color */
2598       if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2599         errorlevel = 1;
2600         return;
2601       }
2602
2603       /* Set the current screen contents and ensure all future writes
2604          remain this color                                             */
2605       FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2606       SetConsoleTextAttribute(hStdOut, color);
2607   }
2608 }