cmd.exe: Implement a basic 'more'.
[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       BOOL ok = TRUE;
787
788       /* If destination exists, prompt unless /Y supplied */
789       if (GetFileAttributesA(dest) != INVALID_FILE_ATTRIBUTES) {
790         BOOL force = FALSE;
791         char copycmd[MAXSTRING];
792         int len;
793
794         /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
795         if (strstr (quals, "/-Y"))
796           force = FALSE;
797         else if (strstr (quals, "/Y"))
798           force = TRUE;
799         else {
800           len = GetEnvironmentVariable ("COPYCMD", copycmd, sizeof(copycmd));
801           force = (len && len < sizeof(copycmd) && ! lstrcmpi (copycmd, "/Y"));
802         }
803
804         /* Prompt if overwriting */
805         if (!force) {
806           char  question[MAXSTRING];
807           char  overwrite[MAXSTRING];
808
809           LoadString (hinst, WCMD_OVERWRITE, overwrite, sizeof(overwrite));
810
811           /* Ask for confirmation */
812           sprintf(question, "%s %s? ", overwrite, dest);
813           ok = WCMD_ask_confirm(question, TRUE);
814
815           /* So delete the destination prior to the move */
816           if (ok) {
817             if (!DeleteFile (dest)) {
818               WCMD_print_error ();
819               errorlevel = 1;
820               ok = FALSE;
821             }
822           }
823         }
824       }
825
826       if (ok) {
827         status = MoveFile (src, dest);
828       } else {
829         status = 1; /* Anything other than 0 to prevent error msg below */
830       }
831     }
832
833     if (!status) {
834       WCMD_print_error ();
835       errorlevel = 1;
836     }
837
838     /* Step on to next match */
839     if (FindNextFile(hff, &fd) == 0) {
840       FindClose(hff);
841       hff = INVALID_HANDLE_VALUE;
842       break;
843     }
844   }
845 }
846
847 /****************************************************************************
848  * WCMD_pause
849  *
850  * Wait for keyboard input.
851  */
852
853 void WCMD_pause (void) {
854
855   DWORD count;
856   char string[32];
857
858   WCMD_output (anykey);
859   ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string), &count, NULL);
860 }
861
862 /****************************************************************************
863  * WCMD_remove_dir
864  *
865  * Delete a directory.
866  */
867
868 void WCMD_remove_dir (char *command) {
869
870   int   argno         = 0;
871   int   argsProcessed = 0;
872   char *argN          = command;
873
874   /* Loop through all args */
875   while (argN) {
876     char *thisArg = WCMD_parameter (command, argno++, &argN);
877     if (argN && argN[0] != '/') {
878       WINE_TRACE("rd: Processing arg %s (quals:%s)\n", thisArg, quals);
879       argsProcessed++;
880
881       /* If subdirectory search not supplied, just try to remove
882          and report error if it fails (eg if it contains a file) */
883       if (strstr (quals, "/S") == NULL) {
884         if (!RemoveDirectory (thisArg)) WCMD_print_error ();
885
886       /* Otherwise use ShFileOp to recursively remove a directory */
887       } else {
888
889         SHFILEOPSTRUCT lpDir;
890
891         /* Ask first */
892         if (strstr (quals, "/Q") == NULL) {
893           BOOL  ok;
894           char  question[MAXSTRING];
895
896           /* Ask for confirmation */
897           sprintf(question, "%s, ", thisArg);
898           ok = WCMD_ask_confirm(question, TRUE);
899
900           /* Abort if answer is 'N' */
901           if (!ok) return;
902         }
903
904         /* Do the delete */
905         lpDir.hwnd   = NULL;
906         lpDir.pTo    = NULL;
907         lpDir.pFrom  = thisArg;
908         lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
909         lpDir.wFunc  = FO_DELETE;
910         if (SHFileOperationA(&lpDir)) WCMD_print_error ();
911       }
912     }
913   }
914
915   /* Handle no valid args */
916   if (argsProcessed == 0) {
917     WCMD_output ("Argument missing\n");
918     return;
919   }
920
921 }
922
923 /****************************************************************************
924  * WCMD_rename
925  *
926  * Rename a file.
927  */
928
929 void WCMD_rename (void) {
930
931   int             status;
932   HANDLE          hff;
933   WIN32_FIND_DATA fd;
934   char            input[MAX_PATH];
935   char           *dotDst = NULL;
936   char            drive[10];
937   char            dir[MAX_PATH];
938   char            fname[MAX_PATH];
939   char            ext[MAX_PATH];
940   DWORD           attribs;
941
942   errorlevel = 0;
943
944   /* Must be at least two args */
945   if (param1[0] == 0x00 || param2[0] == 0x00) {
946     WCMD_output ("Argument missing\n");
947     errorlevel = 1;
948     return;
949   }
950
951   /* Destination cannot contain a drive letter or directory separator */
952   if ((strchr(param1,':') != NULL) || (strchr(param1,'\\') != NULL)) {
953       SetLastError(ERROR_INVALID_PARAMETER);
954       WCMD_print_error();
955       errorlevel = 1;
956       return;
957   }
958
959   /* Convert partial path to full path */
960   GetFullPathName (param1, sizeof(input), input, NULL);
961   WINE_TRACE("Rename from '%s'('%s') to '%s'\n", input, param1, param2);
962   dotDst = strchr(param2, '.');
963
964   /* Split into components */
965   WCMD_splitpath(input, drive, dir, fname, ext);
966
967   hff = FindFirstFile (input, &fd);
968   while (hff != INVALID_HANDLE_VALUE) {
969     char  dest[MAX_PATH];
970     char  src[MAX_PATH];
971     char *dotSrc = NULL;
972     int   dirLen;
973
974     WINE_TRACE("Processing file '%s'\n", fd.cFileName);
975
976     /* FIXME: If dest name or extension is *, replace with filename/ext
977        part otherwise use supplied name. This supports:
978           ren *.fred *.jim
979           ren jim.* fred.* etc
980        However, windows has a more complex algorithum supporting eg
981           ?'s and *'s mid name                                         */
982     dotSrc = strchr(fd.cFileName, '.');
983
984     /* Build src & dest name */
985     strcpy(src, drive);
986     strcat(src, dir);
987     strcpy(dest, src);
988     dirLen = strlen(src);
989     strcat(src, fd.cFileName);
990
991     /* Build name */
992     if (param2[0] == '*') {
993       strcat(dest, fd.cFileName);
994       if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
995     } else {
996       strcat(dest, param2);
997       if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
998     }
999
1000     /* Build Extension */
1001     if (dotDst && (*(dotDst+1)=='*')) {
1002       if (dotSrc) strcat(dest, dotSrc);
1003     } else if (dotDst) {
1004       if (dotDst) strcat(dest, dotDst);
1005     }
1006
1007     WINE_TRACE("Source '%s'\n", src);
1008     WINE_TRACE("Dest   '%s'\n", dest);
1009
1010     /* Check if file is read only, otherwise move it */
1011     attribs = GetFileAttributesA(src);
1012     if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1013         (attribs & FILE_ATTRIBUTE_READONLY)) {
1014       SetLastError(ERROR_ACCESS_DENIED);
1015       status = 0;
1016     } else {
1017       status = MoveFile (src, dest);
1018     }
1019
1020     if (!status) {
1021       WCMD_print_error ();
1022       errorlevel = 1;
1023     }
1024
1025     /* Step on to next match */
1026     if (FindNextFile(hff, &fd) == 0) {
1027       FindClose(hff);
1028       hff = INVALID_HANDLE_VALUE;
1029       break;
1030     }
1031   }
1032 }
1033
1034 /*****************************************************************************
1035  * WCMD_dupenv
1036  *
1037  * Make a copy of the environment.
1038  */
1039 static WCHAR *WCMD_dupenv( const WCHAR *env )
1040 {
1041   WCHAR *env_copy;
1042   int len;
1043
1044   if( !env )
1045     return NULL;
1046
1047   len = 0;
1048   while ( env[len] )
1049     len += (lstrlenW(&env[len]) + 1);
1050
1051   env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1052   if (!env_copy)
1053   {
1054     WCMD_output ("out of memory\n");
1055     return env_copy;
1056   }
1057   memcpy (env_copy, env, len*sizeof (WCHAR));
1058   env_copy[len] = 0;
1059
1060   return env_copy;
1061 }
1062
1063 /*****************************************************************************
1064  * WCMD_setlocal
1065  *
1066  *  setlocal pushes the environment onto a stack
1067  *  Save the environment as unicode so we don't screw anything up.
1068  */
1069 void WCMD_setlocal (const char *s) {
1070   WCHAR *env;
1071   struct env_stack *env_copy;
1072   char cwd[MAX_PATH];
1073
1074   /* DISABLEEXTENSIONS ignored */
1075
1076   env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1077   if( !env_copy )
1078   {
1079     WCMD_output ("out of memory\n");
1080     return;
1081   }
1082
1083   env = GetEnvironmentStringsW ();
1084
1085   env_copy->strings = WCMD_dupenv (env);
1086   if (env_copy->strings)
1087   {
1088     env_copy->next = saved_environment;
1089     saved_environment = env_copy;
1090
1091     /* Save the current drive letter */
1092     GetCurrentDirectory (MAX_PATH, cwd);
1093     env_copy->u.cwd = cwd[0];
1094   }
1095   else
1096     LocalFree (env_copy);
1097
1098   FreeEnvironmentStringsW (env);
1099
1100 }
1101
1102 /*****************************************************************************
1103  * WCMD_strchrW
1104  */
1105 static inline WCHAR *WCMD_strchrW(WCHAR *str, WCHAR ch)
1106 {
1107    while(*str)
1108    {
1109      if(*str == ch)
1110        return str;
1111      str++;
1112    }
1113    return NULL;
1114 }
1115
1116 /*****************************************************************************
1117  * WCMD_endlocal
1118  *
1119  *  endlocal pops the environment off a stack
1120  *  Note: When searching for '=', search from char position 1, to handle
1121  *        special internal environment variables =C:, =D: etc
1122  */
1123 void WCMD_endlocal (void) {
1124   WCHAR *env, *old, *p;
1125   struct env_stack *temp;
1126   int len, n;
1127
1128   if (!saved_environment)
1129     return;
1130
1131   /* pop the old environment from the stack */
1132   temp = saved_environment;
1133   saved_environment = temp->next;
1134
1135   /* delete the current environment, totally */
1136   env = GetEnvironmentStringsW ();
1137   old = WCMD_dupenv (GetEnvironmentStringsW ());
1138   len = 0;
1139   while (old[len]) {
1140     n = lstrlenW(&old[len]) + 1;
1141     p = WCMD_strchrW(&old[len] + 1, '=');
1142     if (p)
1143     {
1144       *p++ = 0;
1145       SetEnvironmentVariableW (&old[len], NULL);
1146     }
1147     len += n;
1148   }
1149   LocalFree (old);
1150   FreeEnvironmentStringsW (env);
1151
1152   /* restore old environment */
1153   env = temp->strings;
1154   len = 0;
1155   while (env[len]) {
1156     n = lstrlenW(&env[len]) + 1;
1157     p = WCMD_strchrW(&env[len] + 1, '=');
1158     if (p)
1159     {
1160       *p++ = 0;
1161       SetEnvironmentVariableW (&env[len], p);
1162     }
1163     len += n;
1164   }
1165
1166   /* Restore current drive letter */
1167   if (IsCharAlpha(temp->u.cwd)) {
1168     char envvar[4];
1169     char cwd[MAX_PATH];
1170     sprintf(envvar, "=%c:", temp->u.cwd);
1171     if (GetEnvironmentVariable(envvar, cwd, MAX_PATH)) {
1172       WINE_TRACE("Resetting cwd to %s\n", cwd);
1173       SetCurrentDirectory(cwd);
1174     }
1175   }
1176
1177   LocalFree (env);
1178   LocalFree (temp);
1179 }
1180
1181 /*****************************************************************************
1182  * WCMD_setshow_attrib
1183  *
1184  * Display and optionally sets DOS attributes on a file or directory
1185  *
1186  * FIXME: Wine currently uses the Unix stat() function to get file attributes.
1187  * As a result only the Readonly flag is correctly reported, the Archive bit
1188  * is always set and the rest are not implemented. We do the Right Thing anyway.
1189  *
1190  * FIXME: No SET functionality.
1191  *
1192  */
1193
1194 void WCMD_setshow_attrib (void) {
1195
1196   DWORD count;
1197   HANDLE hff;
1198   WIN32_FIND_DATA fd;
1199   char flags[9] = {"        "};
1200
1201   if (param1[0] == '-') {
1202     WCMD_output (nyi);
1203     return;
1204   }
1205
1206   if (lstrlen(param1) == 0) {
1207     GetCurrentDirectory (sizeof(param1), param1);
1208     strcat (param1, "\\*");
1209   }
1210
1211   hff = FindFirstFile (param1, &fd);
1212   if (hff == INVALID_HANDLE_VALUE) {
1213     WCMD_output ("%s: File Not Found\n",param1);
1214   }
1215   else {
1216     do {
1217       if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1218         if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
1219           flags[0] = 'H';
1220         }
1221         if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
1222           flags[1] = 'S';
1223         }
1224         if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
1225           flags[2] = 'A';
1226         }
1227         if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
1228           flags[3] = 'R';
1229         }
1230         if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
1231           flags[4] = 'T';
1232         }
1233         if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
1234           flags[5] = 'C';
1235         }
1236         WCMD_output ("%s   %s\n", flags, fd.cFileName);
1237         for (count=0; count < 8; count++) flags[count] = ' ';
1238       }
1239     } while (FindNextFile(hff, &fd) != 0);
1240   }
1241   FindClose (hff);
1242 }
1243
1244 /*****************************************************************************
1245  * WCMD_setshow_default
1246  *
1247  *      Set/Show the current default directory
1248  */
1249
1250 void WCMD_setshow_default (char *command) {
1251
1252   BOOL status;
1253   char string[1024];
1254   char cwd[1024];
1255   char *pos;
1256   WIN32_FIND_DATA fd;
1257   HANDLE hff;
1258
1259   WINE_TRACE("Request change to directory '%s'\n", command);
1260
1261   /* Skip /D and trailing whitespace if on the front of the command line */
1262   if (CompareString (LOCALE_USER_DEFAULT,
1263                      NORM_IGNORECASE | SORT_STRINGSORT,
1264                      command, 2, "/D", -1) == 2) {
1265     command += 2;
1266     while (*command && *command==' ') command++;
1267   }
1268
1269   GetCurrentDirectory (sizeof(cwd), cwd);
1270   if (strlen(command) == 0) {
1271     strcat (cwd, "\n");
1272     WCMD_output (cwd);
1273   }
1274   else {
1275     /* Remove any double quotes, which may be in the
1276        middle, eg. cd "C:\Program Files"\Microsoft is ok */
1277     pos = string;
1278     while (*command) {
1279       if (*command != '"') *pos++ = *command;
1280       command++;
1281     }
1282     *pos = 0x00;
1283
1284     /* Search for approprate directory */
1285     WINE_TRACE("Looking for directory '%s'\n", string);
1286     hff = FindFirstFile (string, &fd);
1287     while (hff != INVALID_HANDLE_VALUE) {
1288       if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1289         char fpath[MAX_PATH];
1290         char drive[10];
1291         char dir[MAX_PATH];
1292         char fname[MAX_PATH];
1293         char ext[MAX_PATH];
1294
1295         /* Convert path into actual directory spec */
1296         GetFullPathName (string, sizeof(fpath), fpath, NULL);
1297         WCMD_splitpath(fpath, drive, dir, fname, ext);
1298
1299         /* Rebuild path */
1300         sprintf(string, "%s%s%s", drive, dir, fd.cFileName);
1301
1302         FindClose(hff);
1303         hff = INVALID_HANDLE_VALUE;
1304         break;
1305       }
1306
1307       /* Step on to next match */
1308       if (FindNextFile(hff, &fd) == 0) {
1309         FindClose(hff);
1310         hff = INVALID_HANDLE_VALUE;
1311         break;
1312       }
1313     }
1314
1315     /* Change to that directory */
1316     WINE_TRACE("Really changing to directory '%s'\n", string);
1317
1318     status = SetCurrentDirectory (string);
1319     if (!status) {
1320       errorlevel = 1;
1321       WCMD_print_error ();
1322       return;
1323     } else {
1324
1325       /* Restore old directory if drive letter would change, and
1326            CD x:\directory /D (or pushd c:\directory) not supplied */
1327       if ((strstr(quals, "/D") == NULL) &&
1328           (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
1329         SetCurrentDirectory(cwd);
1330       }
1331     }
1332
1333     /* Set special =C: type environment variable, for drive letter of
1334        change of directory, even if path was restored due to missing
1335        /D (allows changing drive letter when not resident on that
1336        drive                                                          */
1337     if ((string[1] == ':') && IsCharAlpha (string[0])) {
1338       char env[4];
1339       strcpy(env, "=");
1340       strncpy(env+1, string, 2);
1341       env[3] = 0x00;
1342       SetEnvironmentVariable(env, string);
1343     }
1344
1345    }
1346   return;
1347 }
1348
1349 /****************************************************************************
1350  * WCMD_setshow_date
1351  *
1352  * Set/Show the system date
1353  * FIXME: Can't change date yet
1354  */
1355
1356 void WCMD_setshow_date (void) {
1357
1358   char curdate[64], buffer[64];
1359   DWORD count;
1360
1361   if (lstrlen(param1) == 0) {
1362     if (GetDateFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL,
1363                 curdate, sizeof(curdate))) {
1364       WCMD_output ("Current Date is %s\n", curdate);
1365       if (strstr (quals, "/T") == NULL) {
1366         WCMD_output("Enter new date: ");
1367         ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer), &count, NULL);
1368         if (count > 2) {
1369           WCMD_output (nyi);
1370         }
1371       }
1372     }
1373     else WCMD_print_error ();
1374   }
1375   else {
1376     WCMD_output (nyi);
1377   }
1378 }
1379
1380 /****************************************************************************
1381  * WCMD_compare
1382  */
1383 static int WCMD_compare( const void *a, const void *b )
1384 {
1385     int r;
1386     const char * const *str_a = a, * const *str_b = b;
1387     r = CompareString( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1388           *str_a, -1, *str_b, -1 );
1389     if( r == CSTR_LESS_THAN ) return -1;
1390     if( r == CSTR_GREATER_THAN ) return 1;
1391     return 0;
1392 }
1393
1394 /****************************************************************************
1395  * WCMD_setshow_sortenv
1396  *
1397  * sort variables into order for display
1398  * Optionally only display those who start with a stub
1399  * returns the count displayed
1400  */
1401 static int WCMD_setshow_sortenv(const char *s, const char *stub)
1402 {
1403   UINT count=0, len=0, i, displayedcount=0, stublen=0;
1404   const char **str;
1405
1406   if (stub) stublen = strlen(stub);
1407
1408   /* count the number of strings, and the total length */
1409   while ( s[len] ) {
1410     len += (lstrlen(&s[len]) + 1);
1411     count++;
1412   }
1413
1414   /* add the strings to an array */
1415   str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (char*) );
1416   if( !str )
1417     return 0;
1418   str[0] = s;
1419   for( i=1; i<count; i++ )
1420     str[i] = str[i-1] + lstrlen(str[i-1]) + 1;
1421
1422   /* sort the array */
1423   qsort( str, count, sizeof (char*), WCMD_compare );
1424
1425   /* print it */
1426   for( i=0; i<count; i++ ) {
1427     if (!stub || CompareString (LOCALE_USER_DEFAULT,
1428                                 NORM_IGNORECASE | SORT_STRINGSORT,
1429                                 str[i], stublen, stub, -1) == 2) {
1430       /* Don't display special internal variables */
1431       if (str[i][0] != '=') {
1432         WCMD_output_asis(str[i]);
1433         WCMD_output_asis("\n");
1434         displayedcount++;
1435       }
1436     }
1437   }
1438
1439   LocalFree( str );
1440   return displayedcount;
1441 }
1442
1443 /****************************************************************************
1444  * WCMD_setshow_env
1445  *
1446  * Set/Show the environment variables
1447  */
1448
1449 void WCMD_setshow_env (char *s) {
1450
1451   LPVOID env;
1452   char *p;
1453   int status;
1454
1455   errorlevel = 0;
1456   if (param1[0] == 0x00 && quals[0] == 0x00) {
1457     env = GetEnvironmentStrings ();
1458     WCMD_setshow_sortenv( env, NULL );
1459     return;
1460   }
1461
1462   /* See if /P supplied, and if so echo the prompt, and read in a reply */
1463   if (CompareString (LOCALE_USER_DEFAULT,
1464                      NORM_IGNORECASE | SORT_STRINGSORT,
1465                      s, 2, "/P", -1) == 2) {
1466     char string[MAXSTRING];
1467     DWORD count;
1468
1469     s += 2;
1470     while (*s && *s==' ') s++;
1471
1472     /* If no parameter, or no '=' sign, return an error */
1473     if (!(*s) || ((p = strchr (s, '=')) == NULL )) {
1474       WCMD_output ("Argument missing\n");
1475       return;
1476     }
1477
1478     /* Output the prompt */
1479     *p++ = '\0';
1480     if (strlen(p) != 0) WCMD_output(p);
1481
1482     /* Read the reply */
1483     ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string), &count, NULL);
1484     if (count > 1) {
1485       string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
1486       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
1487       WINE_TRACE("set /p: Setting var '%s' to '%s'\n", s, string);
1488       status = SetEnvironmentVariable (s, string);
1489     }
1490
1491   } else {
1492     DWORD gle;
1493     p = strchr (s, '=');
1494     if (p == NULL) {
1495       env = GetEnvironmentStrings ();
1496       if (WCMD_setshow_sortenv( env, s ) == 0) {
1497         WCMD_output ("Environment variable %s not defined\n", s);
1498         errorlevel = 1;
1499       }
1500       return;
1501     }
1502     *p++ = '\0';
1503
1504     if (strlen(p) == 0) p = NULL;
1505     status = SetEnvironmentVariable (s, p);
1506     gle = GetLastError();
1507     if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
1508       errorlevel = 1;
1509     } else if ((!status)) WCMD_print_error();
1510   }
1511 }
1512
1513 /****************************************************************************
1514  * WCMD_setshow_path
1515  *
1516  * Set/Show the path environment variable
1517  */
1518
1519 void WCMD_setshow_path (char *command) {
1520
1521   char string[1024];
1522   DWORD status;
1523
1524   if (strlen(param1) == 0) {
1525     status = GetEnvironmentVariable ("PATH", string, sizeof(string));
1526     if (status != 0) {
1527       WCMD_output_asis ( "PATH=");
1528       WCMD_output_asis ( string);
1529       WCMD_output_asis ( "\n");
1530     }
1531     else {
1532       WCMD_output ("PATH not found\n");
1533     }
1534   }
1535   else {
1536     if (*command == '=') command++; /* Skip leading '=' */
1537     status = SetEnvironmentVariable ("PATH", command);
1538     if (!status) WCMD_print_error();
1539   }
1540 }
1541
1542 /****************************************************************************
1543  * WCMD_setshow_prompt
1544  *
1545  * Set or show the command prompt.
1546  */
1547
1548 void WCMD_setshow_prompt (void) {
1549
1550   char *s;
1551
1552   if (strlen(param1) == 0) {
1553     SetEnvironmentVariable ("PROMPT", NULL);
1554   }
1555   else {
1556     s = param1;
1557     while ((*s == '=') || (*s == ' ')) s++;
1558     if (strlen(s) == 0) {
1559       SetEnvironmentVariable ("PROMPT", NULL);
1560     }
1561     else SetEnvironmentVariable ("PROMPT", s);
1562   }
1563 }
1564
1565 /****************************************************************************
1566  * WCMD_setshow_time
1567  *
1568  * Set/Show the system time
1569  * FIXME: Can't change time yet
1570  */
1571
1572 void WCMD_setshow_time (void) {
1573
1574   char curtime[64], buffer[64];
1575   DWORD count;
1576   SYSTEMTIME st;
1577
1578   if (strlen(param1) == 0) {
1579     GetLocalTime(&st);
1580     if (GetTimeFormat (LOCALE_USER_DEFAULT, 0, &st, NULL,
1581                 curtime, sizeof(curtime))) {
1582       WCMD_output ("Current Time is %s\n", curtime);
1583       if (strstr (quals, "/T") == NULL) {
1584         WCMD_output ("Enter new time: ", curtime);
1585         ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer), &count, NULL);
1586         if (count > 2) {
1587           WCMD_output (nyi);
1588         }
1589       }
1590     }
1591     else WCMD_print_error ();
1592   }
1593   else {
1594     WCMD_output (nyi);
1595   }
1596 }
1597
1598 /****************************************************************************
1599  * WCMD_shift
1600  *
1601  * Shift batch parameters.
1602  * Optional /n says where to start shifting (n=0-8)
1603  */
1604
1605 void WCMD_shift (char *command) {
1606   int start;
1607
1608   if (context != NULL) {
1609     char *pos = strchr(command, '/');
1610     int   i;
1611
1612     if (pos == NULL) {
1613       start = 0;
1614     } else if (*(pos+1)>='0' && *(pos+1)<='8') {
1615       start = (*(pos+1) - '0');
1616     } else {
1617       SetLastError(ERROR_INVALID_PARAMETER);
1618       WCMD_print_error();
1619       return;
1620     }
1621
1622     WINE_TRACE("Shifting variables, starting at %d\n", start);
1623     for (i=start;i<=8;i++) {
1624       context -> shift_count[i] = context -> shift_count[i+1] + 1;
1625     }
1626     context -> shift_count[9] = context -> shift_count[9] + 1;
1627   }
1628
1629 }
1630
1631 /****************************************************************************
1632  * WCMD_title
1633  *
1634  * Set the console title
1635  */
1636 void WCMD_title (char *command) {
1637   SetConsoleTitle(command);
1638 }
1639
1640 /****************************************************************************
1641  * WCMD_type
1642  *
1643  * Copy a file to standard output.
1644  */
1645
1646 void WCMD_type (char *command) {
1647
1648   int   argno         = 0;
1649   char *argN          = command;
1650   BOOL  writeHeaders  = FALSE;
1651
1652   if (param1[0] == 0x00) {
1653     WCMD_output ("Argument missing\n");
1654     return;
1655   }
1656
1657   if (param2[0] != 0x00) writeHeaders = TRUE;
1658
1659   /* Loop through all args */
1660   errorlevel = 0;
1661   while (argN) {
1662     char *thisArg = WCMD_parameter (command, argno++, &argN);
1663
1664     HANDLE h;
1665     char buffer[512];
1666     DWORD count;
1667
1668     if (!argN) break;
1669
1670     WINE_TRACE("type: Processing arg '%s'\n", thisArg);
1671     h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
1672                 FILE_ATTRIBUTE_NORMAL, NULL);
1673     if (h == INVALID_HANDLE_VALUE) {
1674       WCMD_print_error ();
1675       WCMD_output ("%s :Failed\n", thisArg);
1676       errorlevel = 1;
1677     } else {
1678       if (writeHeaders) {
1679         WCMD_output("\n%s\n\n", thisArg);
1680       }
1681       while (ReadFile (h, buffer, sizeof(buffer), &count, NULL)) {
1682         if (count == 0) break;  /* ReadFile reports success on EOF! */
1683         buffer[count] = 0;
1684         WCMD_output_asis (buffer);
1685       }
1686       CloseHandle (h);
1687     }
1688   }
1689 }
1690
1691 /****************************************************************************
1692  * WCMD_more
1693  *
1694  * Output either a file or stdin to screen in pages
1695  */
1696
1697 void WCMD_more (char *command) {
1698
1699   int   argno         = 0;
1700   char *argN          = command;
1701   BOOL  useinput      = FALSE;
1702   char  moreStr[100];
1703   char  moreStrPage[100];
1704   char  buffer[512];
1705   DWORD count;
1706
1707   /* Prefix the NLS more with '-- ', then load the text */
1708   errorlevel = 0;
1709   strcpy(moreStr, "-- ");
1710   LoadString (hinst, WCMD_MORESTR, &moreStr[3], sizeof(moreStr)-3);
1711
1712   if (param1[0] == 0x00) {
1713
1714     /* Wine implements pipes via temporary files, and hence stdin is
1715        effectively reading from the file. This means the prompts for
1716        more are satistied by the next line from the input (file). To
1717        avoid this, ensure stdin is to the console                    */
1718     HANDLE hstdin  = GetStdHandle(STD_INPUT_HANDLE);
1719     HANDLE hConIn = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE,
1720                          FILE_SHARE_READ, NULL, OPEN_EXISTING,
1721                          FILE_ATTRIBUTE_NORMAL, 0);
1722     SetStdHandle(STD_INPUT_HANDLE, hConIn);
1723
1724     /* Warning: No easy way of ending the stream (ctrl+z on windows) so
1725        once you get in this bit unless due to a pipe, its going to end badly...  */
1726     useinput = TRUE;
1727     sprintf(moreStrPage, "%s --\n", moreStr);
1728
1729     WCMD_enter_paged_mode(moreStrPage);
1730     while (ReadFile (hstdin, buffer, sizeof(buffer)-1, &count, NULL)) {
1731       if (count == 0) break;    /* ReadFile reports success on EOF! */
1732       buffer[count] = 0;
1733       WCMD_output_asis (buffer);
1734     }
1735     WCMD_leave_paged_mode();
1736
1737     /* Restore stdin to what it was */
1738     SetStdHandle(STD_INPUT_HANDLE, hstdin);
1739     CloseHandle(hConIn);
1740
1741     return;
1742   } else {
1743     BOOL needsPause = FALSE;
1744
1745     /* Loop through all args */
1746     WCMD_enter_paged_mode(moreStrPage);
1747
1748     while (argN) {
1749       char *thisArg = WCMD_parameter (command, argno++, &argN);
1750       HANDLE h;
1751
1752       if (!argN) break;
1753
1754       if (needsPause) {
1755
1756         /* Wait */
1757         sprintf(moreStrPage, "%s (100%%) --\n", moreStr);
1758         WCMD_leave_paged_mode();
1759         WCMD_output_asis(moreStrPage);
1760         ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer), &count, NULL);
1761         WCMD_enter_paged_mode(moreStrPage);
1762       }
1763
1764
1765       WINE_TRACE("more: Processing arg '%s'\n", thisArg);
1766       h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
1767                 FILE_ATTRIBUTE_NORMAL, NULL);
1768       if (h == INVALID_HANDLE_VALUE) {
1769         WCMD_print_error ();
1770         WCMD_output ("%s :Failed\n", thisArg);
1771         errorlevel = 1;
1772       } else {
1773         ULONG64 curPos  = 0;
1774         ULONG64 fileLen = 0;
1775         WIN32_FILE_ATTRIBUTE_DATA   fileInfo;
1776
1777         /* Get the file size */
1778         GetFileAttributesEx(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
1779         fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
1780
1781         needsPause = TRUE;
1782         while (ReadFile (h, buffer, sizeof(buffer), &count, NULL)) {
1783           if (count == 0) break;        /* ReadFile reports success on EOF! */
1784           buffer[count] = 0;
1785           curPos += count;
1786
1787           /* Update % count (would be used in WCMD_output_asis as prompt) */
1788           sprintf(moreStrPage, "%s (%2.2d%%) --\n", moreStr, (int) min(99, (curPos * 100)/fileLen));
1789
1790           WCMD_output_asis (buffer);
1791         }
1792         CloseHandle (h);
1793       }
1794     }
1795
1796     WCMD_leave_paged_mode();
1797   }
1798 }
1799
1800 /****************************************************************************
1801  * WCMD_verify
1802  *
1803  * Display verify flag.
1804  * FIXME: We don't actually do anything with the verify flag other than toggle
1805  * it...
1806  */
1807
1808 void WCMD_verify (char *command) {
1809
1810   static const char von[] = "Verify is ON\n", voff[] = "Verify is OFF\n";
1811   int count;
1812
1813   count = strlen(command);
1814   if (count == 0) {
1815     if (verify_mode) WCMD_output (von);
1816     else WCMD_output (voff);
1817     return;
1818   }
1819   if (lstrcmpi(command, "ON") == 0) {
1820     verify_mode = 1;
1821     return;
1822   }
1823   else if (lstrcmpi(command, "OFF") == 0) {
1824     verify_mode = 0;
1825     return;
1826   }
1827   else WCMD_output ("Verify must be ON or OFF\n");
1828 }
1829
1830 /****************************************************************************
1831  * WCMD_version
1832  *
1833  * Display version info.
1834  */
1835
1836 void WCMD_version (void) {
1837
1838   WCMD_output (version_string);
1839
1840 }
1841
1842 /****************************************************************************
1843  * WCMD_volume
1844  *
1845  * Display volume info and/or set volume label. Returns 0 if error.
1846  */
1847
1848 int WCMD_volume (int mode, char *path) {
1849
1850   DWORD count, serial;
1851   char string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
1852   BOOL status;
1853
1854   if (lstrlen(path) == 0) {
1855     status = GetCurrentDirectory (sizeof(curdir), curdir);
1856     if (!status) {
1857       WCMD_print_error ();
1858       return 0;
1859     }
1860     status = GetVolumeInformation (NULL, label, sizeof(label), &serial, NULL,
1861         NULL, NULL, 0);
1862   }
1863   else {
1864     if ((path[1] != ':') || (lstrlen(path) != 2)) {
1865       WCMD_output_asis("Syntax Error\n\n");
1866       return 0;
1867     }
1868     wsprintf (curdir, "%s\\", path);
1869     status = GetVolumeInformation (curdir, label, sizeof(label), &serial, NULL,
1870         NULL, NULL, 0);
1871   }
1872   if (!status) {
1873     WCMD_print_error ();
1874     return 0;
1875   }
1876   WCMD_output ("Volume in drive %c is %s\nVolume Serial Number is %04x-%04x\n\n",
1877         curdir[0], label, HIWORD(serial), LOWORD(serial));
1878   if (mode) {
1879     WCMD_output ("Volume label (11 characters, ENTER for none)?");
1880     ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string), &count, NULL);
1881     if (count > 1) {
1882       string[count-1] = '\0';           /* ReadFile output is not null-terminated! */
1883       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
1884     }
1885     if (lstrlen(path) != 0) {
1886       if (!SetVolumeLabel (curdir, string)) WCMD_print_error ();
1887     }
1888     else {
1889       if (!SetVolumeLabel (NULL, string)) WCMD_print_error ();
1890     }
1891   }
1892   return 1;
1893 }
1894
1895 /**************************************************************************
1896  * WCMD_exit
1897  *
1898  * Exit either the process, or just this batch program
1899  *
1900  */
1901
1902 void WCMD_exit (void) {
1903
1904     int rc = atoi(param1); /* Note: atoi of empty parameter is 0 */
1905
1906     if (context && lstrcmpi(quals, "/B") == 0) {
1907         errorlevel = rc;
1908         context -> skip_rest = TRUE;
1909     } else {
1910         ExitProcess(rc);
1911     }
1912 }
1913
1914 /**************************************************************************
1915  * WCMD_ask_confirm
1916  *
1917  * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
1918  * answer.
1919  *
1920  * Returns True if Y answer is selected
1921  *
1922  */
1923 BOOL WCMD_ask_confirm (char *message, BOOL showSureText) {
1924
1925     char  msgbuffer[MAXSTRING];
1926     char  Ybuffer[MAXSTRING];
1927     char  Nbuffer[MAXSTRING];
1928     char  answer[MAX_PATH] = "";
1929     DWORD count = 0;
1930
1931     /* Load the translated 'Are you sure', plus valid answers */
1932     LoadString (hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer));
1933     LoadString (hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer));
1934     LoadString (hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer));
1935
1936     /* Loop waiting on a Y or N */
1937     while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
1938       WCMD_output_asis (message);
1939       if (showSureText) {
1940         WCMD_output_asis (msgbuffer);
1941       }
1942       WCMD_output_asis (" (");
1943       WCMD_output_asis (Ybuffer);
1944       WCMD_output_asis ("/");
1945       WCMD_output_asis (Nbuffer);
1946       WCMD_output_asis (")?");
1947       ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
1948                 &count, NULL);
1949       answer[0] = toupper(answer[0]);
1950     }
1951
1952     /* Return the answer */
1953     return (answer[0] == Ybuffer[0]);
1954 }
1955
1956 /*****************************************************************************
1957  * WCMD_assoc
1958  *
1959  *      Lists or sets file associations  (assoc = TRUE)
1960  *      Lists or sets file types         (assoc = FALSE)
1961  */
1962 void WCMD_assoc (char *command, BOOL assoc) {
1963
1964     HKEY    key;
1965     DWORD   accessOptions = KEY_READ;
1966     char   *newValue;
1967     LONG    rc = ERROR_SUCCESS;
1968     char    keyValue[MAXSTRING];
1969     DWORD   valueLen = MAXSTRING;
1970     HKEY    readKey;
1971
1972
1973     /* See if parameter includes '=' */
1974     errorlevel = 0;
1975     newValue = strchr(command, '=');
1976     if (newValue) accessOptions |= KEY_WRITE;
1977
1978     /* Open a key to HKEY_CLASSES_ROOT for enumerating */
1979     if (RegOpenKeyEx(HKEY_CLASSES_ROOT, "", 0,
1980                      accessOptions, &key) != ERROR_SUCCESS) {
1981       WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
1982       return;
1983     }
1984
1985     /* If no parameters then list all associations */
1986     if (*command == 0x00) {
1987       int index = 0;
1988
1989       /* Enumerate all the keys */
1990       while (rc != ERROR_NO_MORE_ITEMS) {
1991         char  keyName[MAXSTRING];
1992         DWORD nameLen;
1993
1994         /* Find the next value */
1995         nameLen = MAXSTRING;
1996         rc = RegEnumKeyEx(key, index++,
1997                           keyName, &nameLen,
1998                           NULL, NULL, NULL, NULL);
1999
2000         if (rc == ERROR_SUCCESS) {
2001
2002           /* Only interested in extension ones if assoc, or others
2003              if not assoc                                          */
2004           if ((keyName[0] == '.' && assoc) ||
2005               (!(keyName[0] == '.') && (!assoc)))
2006           {
2007             char subkey[MAXSTRING];
2008             strcpy(subkey, keyName);
2009             if (!assoc) strcat(subkey, "\\Shell\\Open\\Command");
2010
2011             if (RegOpenKeyEx(key, subkey, 0,
2012                              accessOptions, &readKey) == ERROR_SUCCESS) {
2013
2014               valueLen = sizeof(keyValue);
2015               rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2016                                    (LPBYTE)keyValue, &valueLen);
2017               WCMD_output_asis(keyName);
2018               WCMD_output_asis("=");
2019               /* If no default value found, leave line empty after '=' */
2020               if (rc == ERROR_SUCCESS) {
2021                 WCMD_output_asis(keyValue);
2022               }
2023               WCMD_output_asis("\n");
2024             }
2025           }
2026         }
2027       }
2028       RegCloseKey(readKey);
2029
2030     } else {
2031
2032       /* Parameter supplied - if no '=' on command line, its a query */
2033       if (newValue == NULL) {
2034         char *space;
2035         char subkey[MAXSTRING];
2036
2037         /* Query terminates the parameter at the first space */
2038         strcpy(keyValue, command);
2039         space = strchr(keyValue, ' ');
2040         if (space) *space=0x00;
2041
2042         /* Set up key name */
2043         strcpy(subkey, keyValue);
2044         if (!assoc) strcat(subkey, "\\Shell\\Open\\Command");
2045
2046         if (RegOpenKeyEx(key, subkey, 0,
2047                          accessOptions, &readKey) == ERROR_SUCCESS) {
2048
2049           rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2050                                (LPBYTE)keyValue, &valueLen);
2051           WCMD_output_asis(command);
2052           WCMD_output_asis("=");
2053           /* If no default value found, leave line empty after '=' */
2054           if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2055           WCMD_output_asis("\n");
2056           RegCloseKey(readKey);
2057
2058         } else {
2059           char  msgbuffer[MAXSTRING];
2060           char  outbuffer[MAXSTRING];
2061
2062           /* Load the translated 'File association not found' */
2063           if (assoc) {
2064             LoadString (hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer));
2065           } else {
2066             LoadString (hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer));
2067           }
2068           sprintf(outbuffer, msgbuffer, keyValue);
2069           WCMD_output_asis(outbuffer);
2070           errorlevel = 2;
2071         }
2072
2073       /* Not a query - its a set or clear of a value */
2074       } else {
2075
2076         char subkey[MAXSTRING];
2077
2078         /* Get pointer to new value */
2079         *newValue = 0x00;
2080         newValue++;
2081
2082         /* Set up key name */
2083         strcpy(subkey, command);
2084         if (!assoc) strcat(subkey, "\\Shell\\Open\\Command");
2085
2086         /* If nothing after '=' then clear value - only valid for ASSOC */
2087         if (*newValue == 0x00) {
2088
2089           if (assoc) rc = RegDeleteKey(key, command);
2090           if (assoc && rc == ERROR_SUCCESS) {
2091             WINE_TRACE("HKCR Key '%s' deleted\n", command);
2092
2093           } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2094             WCMD_print_error();
2095             errorlevel = 2;
2096
2097           } else {
2098             char  msgbuffer[MAXSTRING];
2099             char  outbuffer[MAXSTRING];
2100
2101             /* Load the translated 'File association not found' */
2102             if (assoc) {
2103               LoadString (hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer));
2104             } else {
2105               LoadString (hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer));
2106             }
2107             sprintf(outbuffer, msgbuffer, keyValue);
2108             WCMD_output_asis(outbuffer);
2109             errorlevel = 2;
2110           }
2111
2112         /* It really is a set value = contents */
2113         } else {
2114           rc = RegCreateKeyEx(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2115                               accessOptions, NULL, &readKey, NULL);
2116           if (rc == ERROR_SUCCESS) {
2117             rc = RegSetValueEx(readKey, NULL, 0, REG_SZ,
2118                                  (LPBYTE)newValue, strlen(newValue));
2119             RegCloseKey(readKey);
2120           }
2121
2122           if (rc != ERROR_SUCCESS) {
2123             WCMD_print_error();
2124             errorlevel = 2;
2125           } else {
2126             WCMD_output_asis(command);
2127             WCMD_output_asis("=");
2128             WCMD_output_asis(newValue);
2129             WCMD_output_asis("\n");
2130           }
2131         }
2132       }
2133     }
2134
2135     /* Clean up */
2136     RegCloseKey(key);
2137 }
2138
2139 /****************************************************************************
2140  * WCMD_color
2141  *
2142  * Clear the terminal screen.
2143  */
2144
2145 void WCMD_color (void) {
2146
2147   /* Emulate by filling the screen from the top left to bottom right with
2148         spaces, then moving the cursor to the top left afterwards */
2149   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2150   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2151
2152   if (param1[0] != 0x00 && strlen(param1) > 2) {
2153     WCMD_output ("Argument invalid\n");
2154     return;
2155   }
2156
2157   if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2158   {
2159       COORD topLeft;
2160       DWORD screenSize;
2161       DWORD color = 0;
2162
2163       screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2164
2165       topLeft.X = 0;
2166       topLeft.Y = 0;
2167
2168       /* Convert the color hex digits */
2169       if (param1[0] == 0x00) {
2170         color = defaultColor;
2171       } else {
2172         color = strtoul(param1, NULL, 16);
2173       }
2174
2175       /* Fail if fg == bg color */
2176       if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2177         errorlevel = 1;
2178         return;
2179       }
2180
2181       /* Set the current screen contents and ensure all future writes
2182          remain this color                                             */
2183       FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2184       SetConsoleTextAttribute(hStdOut, color);
2185   }
2186 }