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