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