cmd.exe: Support del /p.
[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
40 void WCMD_execute (char *orig_command, char *parameter, char *substitution);
41
42 struct env_stack
43 {
44   struct env_stack *next;
45   WCHAR *strings;
46 };
47
48 struct env_stack *saved_environment;
49 struct env_stack *pushd_directories;
50
51 extern HINSTANCE hinst;
52 extern char *inbuilt[];
53 extern int echo_mode, verify_mode;
54 extern char quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
55 extern BATCH_CONTEXT *context;
56 extern DWORD errorlevel;
57
58
59
60 /****************************************************************************
61  * WCMD_clear_screen
62  *
63  * Clear the terminal screen.
64  */
65
66 void WCMD_clear_screen (void) {
67
68   /* Emulate by filling the screen from the top left to bottom right with
69         spaces, then moving the cursor to the top left afterwards */
70   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
71   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
72
73   if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
74   {
75       COORD topLeft;
76       DWORD screenSize;
77
78       screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
79
80       topLeft.X = 0;
81       topLeft.Y = 0;
82       FillConsoleOutputCharacter(hStdOut, ' ', screenSize, topLeft, &screenSize);
83       SetConsoleCursorPosition(hStdOut, topLeft);
84   }
85 }
86
87 /****************************************************************************
88  * WCMD_change_tty
89  *
90  * Change the default i/o device (ie redirect STDin/STDout).
91  */
92
93 void WCMD_change_tty (void) {
94
95   WCMD_output (nyi);
96
97 }
98
99 /****************************************************************************
100  * WCMD_copy
101  *
102  * Copy a file or wildcarded set.
103  * FIXME: No wildcard support
104  */
105
106 void WCMD_copy (void) {
107
108 DWORD count;
109 WIN32_FIND_DATA fd;
110 HANDLE hff;
111 BOOL force, status;
112 static const char overwrite[] = "Overwrite file (Y/N)?";
113 char string[8], outpath[MAX_PATH], inpath[MAX_PATH], *infile;
114
115   if (param1[0] == 0x00) {
116     WCMD_output ("Argument missing\n");
117     return;
118   }
119
120   if ((strchr(param1,'*') != NULL) && (strchr(param1,'%') != NULL)) {
121     WCMD_output ("Wildcards not yet supported\n");
122     return;
123   }
124
125   /* If no destination supplied, assume current directory */
126   if (param2[0] == 0x00) {
127       strcpy(param2, ".");
128   }
129
130   GetFullPathName (param2, sizeof(outpath), outpath, NULL);
131   if (outpath[strlen(outpath) - 1] == '\\')
132       outpath[strlen(outpath) - 1] = '\0';
133   hff = FindFirstFile (outpath, &fd);
134   if (hff != INVALID_HANDLE_VALUE) {
135     if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
136       GetFullPathName (param1, sizeof(inpath), inpath, &infile);
137       strcat (outpath, "\\");
138       strcat (outpath, infile);
139     }
140     FindClose (hff);
141   }
142
143   force = (strstr (quals, "/Y") != NULL);
144   if (!force) {
145     hff = FindFirstFile (outpath, &fd);
146     if (hff != INVALID_HANDLE_VALUE) {
147       FindClose (hff);
148       WCMD_output (overwrite);
149       ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string), &count, NULL);
150       if (toupper(string[0]) == 'Y') force = TRUE;
151     }
152     else force = TRUE;
153   }
154   if (force) {
155     status = CopyFile (param1, outpath, FALSE);
156     if (!status) WCMD_print_error ();
157   }
158 }
159
160 /****************************************************************************
161  * WCMD_create_dir
162  *
163  * Create a directory.
164  *
165  * this works recursivly. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if
166  * they do not already exist.
167  */
168
169 BOOL create_full_path(CHAR* path)
170 {
171     int len;
172     CHAR *new_path;
173     BOOL ret = TRUE;
174
175     new_path = HeapAlloc(GetProcessHeap(),0,strlen(path)+1);
176     strcpy(new_path,path);
177
178     while ((len = strlen(new_path)) && new_path[len - 1] == '\\')
179         new_path[len - 1] = 0;
180
181     while (!CreateDirectory(new_path,NULL))
182     {
183         CHAR *slash;
184         DWORD last_error = GetLastError();
185         if (last_error == ERROR_ALREADY_EXISTS)
186             break;
187
188         if (last_error != ERROR_PATH_NOT_FOUND)
189         {
190             ret = FALSE;
191             break;
192         }
193
194         if (!(slash = strrchr(new_path,'\\')) && ! (slash = strrchr(new_path,'/')))
195         {
196             ret = FALSE;
197             break;
198         }
199
200         len = slash - new_path;
201         new_path[len] = 0;
202         if (!create_full_path(new_path))
203         {
204             ret = FALSE;
205             break;
206         }
207         new_path[len] = '\\';
208     }
209     HeapFree(GetProcessHeap(),0,new_path);
210     return ret;
211 }
212
213 void WCMD_create_dir (void) {
214
215     if (param1[0] == 0x00) {
216         WCMD_output ("Argument missing\n");
217         return;
218     }
219     if (!create_full_path(param1)) WCMD_print_error ();
220 }
221
222 /****************************************************************************
223  * WCMD_delete
224  *
225  * Delete a file or wildcarded set.
226  *
227  */
228
229 void WCMD_delete (int recurse) {
230
231 WIN32_FIND_DATA fd;
232 HANDLE hff;
233 char fpath[MAX_PATH];
234 char *p;
235
236   if (param1[0] == 0x00) {
237     WCMD_output ("Argument missing\n");
238     return;
239   }
240
241   /* If filename part of parameter is * or *.*, prompt unless
242      /Q supplied.                                            */
243   if ((strstr (quals, "/Q") == NULL) && (strstr (quals, "/P") == NULL)) {
244
245     char drive[10];
246     char dir[MAX_PATH];
247     char fname[MAX_PATH];
248     char ext[MAX_PATH];
249
250     /* Convert path into actual directory spec */
251     GetFullPathName (param1, sizeof(fpath), fpath, NULL);
252     WCMD_splitpath(fpath, drive, dir, fname, ext);
253
254     /* Only prompt for * and *.*, not *a, a*, *.a* etc */
255     if ((strcmp(fname, "*") == 0) &&
256         (*ext == 0x00 || (strcmp(ext, ".*") == 0))) {
257       BOOL  ok;
258       char  question[MAXSTRING];
259
260       /* Ask for confirmation */
261       sprintf(question, "%s, ", fpath);
262       ok = WCMD_ask_confirm(question, TRUE);
263
264       /* Abort if answer is 'N' */
265       if (!ok) return;
266     }
267   }
268
269   hff = FindFirstFile (param1, &fd);
270   if (hff == INVALID_HANDLE_VALUE) {
271     WCMD_output ("%s :File Not Found\n",param1);
272     return;
273   }
274   if ((strchr(param1,'*') == NULL) && (strchr(param1,'?') == NULL)
275         && (!recurse) && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
276     strcat (param1, "\\*");
277     FindClose(hff);
278     WCMD_delete (1);
279     return;
280   }
281   if ((strchr(param1,'*') != NULL) || (strchr(param1,'?') != NULL)) {
282     strcpy (fpath, param1);
283     do {
284       p = strrchr (fpath, '\\');
285       if (p != NULL) {
286         *++p = '\0';
287         strcat (fpath, fd.cFileName);
288       }
289       else strcpy (fpath, fd.cFileName);
290       if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
291         /* /P means prompt for each file */
292         if (strstr (quals, "/P") != NULL) {
293           BOOL  ok;
294           char  question[MAXSTRING];
295
296           /* Ask for confirmation */
297           sprintf(question, "%s, Delete", fpath);
298           ok = WCMD_ask_confirm(question, FALSE);
299
300           /* Only delete if answer is 'Y' */
301           if (ok && !DeleteFile (fpath)) WCMD_print_error ();
302         } else {
303           if (!DeleteFile (fpath)) WCMD_print_error ();
304         }
305       }
306     } while (FindNextFile(hff, &fd) != 0);
307     FindClose (hff);
308   }
309   else {
310     if (!DeleteFile (param1)) WCMD_print_error ();
311     FindClose (hff);
312   }
313 }
314
315 /****************************************************************************
316  * WCMD_echo
317  *
318  * Echo input to the screen (or not). We don't try to emulate the bugs
319  * in DOS (try typing "ECHO ON AGAIN" for an example).
320  */
321
322 void WCMD_echo (const char *command) {
323
324 static const char eon[] = "Echo is ON\n", eoff[] = "Echo is OFF\n";
325 int count;
326
327   if ((command[0] == '.') && (command[1] == 0)) {
328     WCMD_output (newline);
329     return;
330   }
331   if (command[0]==' ')
332     command++;
333   count = strlen(command);
334   if (count == 0) {
335     if (echo_mode) WCMD_output (eon);
336     else WCMD_output (eoff);
337     return;
338   }
339   if (lstrcmpi(command, "ON") == 0) {
340     echo_mode = 1;
341     return;
342   }
343   if (lstrcmpi(command, "OFF") == 0) {
344     echo_mode = 0;
345     return;
346   }
347   WCMD_output_asis (command);
348   WCMD_output (newline);
349
350 }
351
352 /**************************************************************************
353  * WCMD_for
354  *
355  * Batch file loop processing.
356  * FIXME: We don't exhaustively check syntax. Any command which works in MessDOS
357  * will probably work here, but the reverse is not necessarily the case...
358  */
359
360 void WCMD_for (char *p) {
361
362 WIN32_FIND_DATA fd;
363 HANDLE hff;
364 char *cmd, *item;
365 char set[MAX_PATH], param[MAX_PATH];
366 int i;
367
368   if (lstrcmpi (WCMD_parameter (p, 1, NULL), "in")
369         || lstrcmpi (WCMD_parameter (p, 3, NULL), "do")
370         || (param1[0] != '%')) {
371     WCMD_output ("Syntax error\n");
372     return;
373   }
374   lstrcpyn (set, WCMD_parameter (p, 2, NULL), sizeof(set));
375   WCMD_parameter (p, 4, &cmd);
376   lstrcpy (param, param1);
377
378 /*
379  *      If the parameter within the set has a wildcard then search for matching files
380  *      otherwise do a literal substitution.
381  */
382
383   i = 0;
384   while (*(item = WCMD_parameter (set, i, NULL))) {
385     if (strpbrk (item, "*?")) {
386       hff = FindFirstFile (item, &fd);
387       if (hff == INVALID_HANDLE_VALUE) {
388         return;
389       }
390       do {
391         WCMD_execute (cmd, param, fd.cFileName);
392       } while (FindNextFile(hff, &fd) != 0);
393       FindClose (hff);
394 }
395     else {
396       WCMD_execute (cmd, param, item);
397     }
398     i++;
399   }
400 }
401
402 /*****************************************************************************
403  * WCMD_Execute
404  *
405  *      Execute a command after substituting variable text for the supplied parameter
406  */
407
408 void WCMD_execute (char *orig_cmd, char *param, char *subst) {
409
410 char *new_cmd, *p, *s, *dup;
411 int size;
412
413   size = lstrlen (orig_cmd);
414   new_cmd = (char *) LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, size);
415   dup = s = strdup (orig_cmd);
416
417   while ((p = strstr (s, param))) {
418     *p = '\0';
419     size += lstrlen (subst);
420     new_cmd = (char *) LocalReAlloc ((HANDLE)new_cmd, size, 0);
421     strcat (new_cmd, s);
422     strcat (new_cmd, subst);
423     s = p + lstrlen (param);
424   }
425   strcat (new_cmd, s);
426   WCMD_process_command (new_cmd);
427   free (dup);
428   LocalFree ((HANDLE)new_cmd);
429 }
430
431
432 /**************************************************************************
433  * WCMD_give_help
434  *
435  *      Simple on-line help. Help text is stored in the resource file.
436  */
437
438 void WCMD_give_help (char *command) {
439
440 int i;
441 char buffer[2048];
442
443   command = WCMD_strtrim_leading_spaces(command);
444   if (lstrlen(command) == 0) {
445     LoadString (hinst, 1000, buffer, sizeof(buffer));
446     WCMD_output_asis (buffer);
447   }
448   else {
449     for (i=0; i<=WCMD_EXIT; i++) {
450       if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
451           param1, -1, inbuilt[i], -1) == 2) {
452         LoadString (hinst, i, buffer, sizeof(buffer));
453         WCMD_output_asis (buffer);
454         return;
455       }
456     }
457     WCMD_output ("No help available for %s\n", param1);
458   }
459   return;
460 }
461
462 /****************************************************************************
463  * WCMD_go_to
464  *
465  * Batch file jump instruction. Not the most efficient algorithm ;-)
466  * Prints error message if the specified label cannot be found - the file pointer is
467  * then at EOF, effectively stopping the batch file.
468  * FIXME: DOS is supposed to allow labels with spaces - we don't.
469  */
470
471 void WCMD_goto (void) {
472
473 char string[MAX_PATH];
474
475   if (param1[0] == 0x00) {
476     WCMD_output ("Argument missing\n");
477     return;
478   }
479   if (context != NULL) {
480     char *paramStart = param1;
481
482     /* Handle special :EOF label */
483     if (lstrcmpi (":eof", param1) == 0) {
484       context -> skip_rest = TRUE;
485       return;
486     }
487
488     /* Support goto :label as well as goto label */
489     if (*paramStart == ':') paramStart++;
490
491     SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
492     while (WCMD_fgets (string, sizeof(string), context -> h)) {
493       if ((string[0] == ':') && (lstrcmpi (&string[1], paramStart) == 0)) return;
494     }
495     WCMD_output ("Target to GOTO not found\n");
496   }
497   return;
498 }
499
500 /*****************************************************************************
501  * WCMD_pushd
502  *
503  *      Push a directory onto the stack
504  */
505
506 void WCMD_pushd (void) {
507     struct env_stack *curdir;
508     BOOL   status;
509     WCHAR *thisdir;
510
511     curdir  = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
512     thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
513     if( !curdir || !thisdir ) {
514       LocalFree(curdir);
515       LocalFree(thisdir);
516       WCMD_output ("out of memory\n");
517       return;
518     }
519
520     GetCurrentDirectoryW (1024, thisdir);
521     status = SetCurrentDirectoryA (param1);
522     if (!status) {
523       WCMD_print_error ();
524       LocalFree(curdir);
525       LocalFree(thisdir);
526       return;
527     } else {
528       curdir -> next    = pushd_directories;
529       curdir -> strings = thisdir;
530       pushd_directories = curdir;
531     }
532 }
533
534
535 /*****************************************************************************
536  * WCMD_popd
537  *
538  *      Pop a directory from the stack
539  */
540
541 void WCMD_popd (void) {
542     struct env_stack *temp = pushd_directories;
543
544     if (!pushd_directories)
545       return;
546
547     /* pop the old environment from the stack, and make it the current dir */
548     pushd_directories = temp->next;
549     SetCurrentDirectoryW(temp->strings);
550     LocalFree (temp->strings);
551     LocalFree (temp);
552 }
553
554 /****************************************************************************
555  * WCMD_if
556  *
557  * Batch file conditional.
558  * FIXME: Much more syntax checking needed!
559  */
560
561 void WCMD_if (char *p) {
562
563 int negate = 0, test = 0;
564 char condition[MAX_PATH], *command, *s;
565
566   if (!lstrcmpi (param1, "not")) {
567     negate = 1;
568     lstrcpy (condition, param2);
569 }
570   else {
571     lstrcpy (condition, param1);
572   }
573   if (!lstrcmpi (condition, "errorlevel")) {
574     if (errorlevel >= atoi(WCMD_parameter (p, 1+negate, NULL))) test = 1;
575     WCMD_parameter (p, 2+negate, &command);
576   }
577   else if (!lstrcmpi (condition, "exist")) {
578     if (GetFileAttributesA(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
579         test = 1;
580     }
581     WCMD_parameter (p, 2+negate, &command);
582   }
583   else if (!lstrcmpi (condition, "defined")) {
584     if (GetEnvironmentVariableA(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
585         test = 1;
586     }
587     WCMD_parameter (p, 2+negate, &command);
588   }
589   else if ((s = strstr (p, "=="))) {
590     s += 2;
591     if (!lstrcmpi (condition, WCMD_parameter (s, 0, NULL))) test = 1;
592     WCMD_parameter (s, 1, &command);
593   }
594   else {
595     WCMD_output ("Syntax error\n");
596     return;
597   }
598   if (test != negate) {
599     command = strdup (command);
600     WCMD_process_command (command);
601     free (command);
602   }
603 }
604
605 /****************************************************************************
606  * WCMD_move
607  *
608  * Move a file, directory tree or wildcarded set of files.
609  * FIXME: Needs input and output files to be fully specified.
610  */
611
612 void WCMD_move (void) {
613
614 int status;
615 char outpath[MAX_PATH], inpath[MAX_PATH], *infile;
616 WIN32_FIND_DATA fd;
617 HANDLE hff;
618
619   if (param1[0] == 0x00) {
620     WCMD_output ("Argument missing\n");
621     return;
622   }
623
624   if ((strchr(param1,'*') != NULL) || (strchr(param1,'%') != NULL)) {
625     WCMD_output ("Wildcards not yet supported\n");
626     return;
627   }
628
629   /* If no destination supplied, assume current directory */
630   if (param2[0] == 0x00) {
631       strcpy(param2, ".");
632   }
633
634   /* If 2nd parm is directory, then use original filename */
635   GetFullPathName (param2, sizeof(outpath), outpath, NULL);
636   if (outpath[strlen(outpath) - 1] == '\\')
637       outpath[strlen(outpath) - 1] = '\0';
638   hff = FindFirstFile (outpath, &fd);
639   if (hff != INVALID_HANDLE_VALUE) {
640     if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
641       GetFullPathName (param1, sizeof(inpath), inpath, &infile);
642       strcat (outpath, "\\");
643       strcat (outpath, infile);
644     }
645     FindClose (hff);
646   }
647
648   status = MoveFile (param1, outpath);
649   if (!status) WCMD_print_error ();
650 }
651
652 /****************************************************************************
653  * WCMD_pause
654  *
655  * Wait for keyboard input.
656  */
657
658 void WCMD_pause (void) {
659
660 DWORD count;
661 char string[32];
662
663   WCMD_output (anykey);
664   ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string), &count, NULL);
665 }
666
667 /****************************************************************************
668  * WCMD_remove_dir
669  *
670  * Delete a directory.
671  */
672
673 void WCMD_remove_dir (void) {
674
675   if (param1[0] == 0x00) {
676     WCMD_output ("Argument missing\n");
677     return;
678   }
679
680   /* If subdirectory search not supplied, just try to remove
681      and report error if it fails (eg if it contains a file) */
682   if (strstr (quals, "/S") == NULL) {
683     if (!RemoveDirectory (param1)) WCMD_print_error ();
684
685   /* Otherwise use ShFileOp to recursively remove a directory */
686   } else {
687
688     SHFILEOPSTRUCT lpDir;
689
690     /* Ask first */
691     if (strstr (quals, "/Q") == NULL) {
692       BOOL  ok;
693       char  question[MAXSTRING];
694
695       /* Ask for confirmation */
696       sprintf(question, "%s, ", param1);
697       ok = WCMD_ask_confirm(question, TRUE);
698
699       /* Abort if answer is 'N' */
700       if (!ok) return;
701     }
702
703     /* Do the delete */
704     lpDir.hwnd   = NULL;
705     lpDir.pTo    = NULL;
706     lpDir.pFrom  = param1;
707     lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
708     lpDir.wFunc  = FO_DELETE;
709     if (SHFileOperationA(&lpDir)) WCMD_print_error ();
710   }
711 }
712
713 /****************************************************************************
714  * WCMD_rename
715  *
716  * Rename a file.
717  * FIXME: Needs input and output files to be fully specified.
718  */
719
720 void WCMD_rename (void) {
721
722 int status;
723
724   if (param1[0] == 0x00 || param2[0] == 0x00) {
725     WCMD_output ("Argument missing\n");
726     return;
727   }
728   if ((strchr(param1,'*') != NULL) || (strchr(param1,'%') != NULL)) {
729     WCMD_output ("Wildcards not yet supported\n");
730     return;
731   }
732   status = MoveFile (param1, param2);
733   if (!status) WCMD_print_error ();
734 }
735
736 /*****************************************************************************
737  * WCMD_dupenv
738  *
739  * Make a copy of the environment.
740  */
741 static WCHAR *WCMD_dupenv( const WCHAR *env )
742 {
743   WCHAR *env_copy;
744   int len;
745
746   if( !env )
747     return NULL;
748
749   len = 0;
750   while ( env[len] )
751     len += (lstrlenW(&env[len]) + 1);
752
753   env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
754   if (!env_copy)
755   {
756     WCMD_output ("out of memory\n");
757     return env_copy;
758   }
759   memcpy (env_copy, env, len*sizeof (WCHAR));
760   env_copy[len] = 0;
761
762   return env_copy;
763 }
764
765 /*****************************************************************************
766  * WCMD_setlocal
767  *
768  *  setlocal pushes the environment onto a stack
769  *  Save the environment as unicode so we don't screw anything up.
770  */
771 void WCMD_setlocal (const char *s) {
772   WCHAR *env;
773   struct env_stack *env_copy;
774
775   /* DISABLEEXTENSIONS ignored */
776
777   env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
778   if( !env_copy )
779   {
780     WCMD_output ("out of memory\n");
781     return;
782   }
783
784   env = GetEnvironmentStringsW ();
785
786   env_copy->strings = WCMD_dupenv (env);
787   if (env_copy->strings)
788   {
789     env_copy->next = saved_environment;
790     saved_environment = env_copy;
791   }
792   else
793     LocalFree (env_copy);
794
795   FreeEnvironmentStringsW (env);
796 }
797
798 /*****************************************************************************
799  * WCMD_strchrW
800  */
801 static inline WCHAR *WCMD_strchrW(WCHAR *str, WCHAR ch)
802 {
803    while(*str)
804    {
805      if(*str == ch)
806        return str;
807      str++;
808    }
809    return NULL;
810 }
811
812 /*****************************************************************************
813  * WCMD_endlocal
814  *
815  *  endlocal pops the environment off a stack
816  */
817 void WCMD_endlocal (void) {
818   WCHAR *env, *old, *p;
819   struct env_stack *temp;
820   int len, n;
821
822   if (!saved_environment)
823     return;
824
825   /* pop the old environment from the stack */
826   temp = saved_environment;
827   saved_environment = temp->next;
828
829   /* delete the current environment, totally */
830   env = GetEnvironmentStringsW ();
831   old = WCMD_dupenv (GetEnvironmentStringsW ());
832   len = 0;
833   while (old[len]) {
834     n = lstrlenW(&old[len]) + 1;
835     p = WCMD_strchrW(&old[len], '=');
836     if (p)
837     {
838       *p++ = 0;
839       SetEnvironmentVariableW (&old[len], NULL);
840     }
841     len += n;
842   }
843   LocalFree (old);
844   FreeEnvironmentStringsW (env);
845
846   /* restore old environment */
847   env = temp->strings;
848   len = 0;
849   while (env[len]) {
850     n = lstrlenW(&env[len]) + 1;
851     p = WCMD_strchrW(&env[len], '=');
852     if (p)
853     {
854       *p++ = 0;
855       SetEnvironmentVariableW (&env[len], p);
856     }
857     len += n;
858   }
859   LocalFree (env);
860   LocalFree (temp);
861 }
862
863 /*****************************************************************************
864  * WCMD_setshow_attrib
865  *
866  * Display and optionally sets DOS attributes on a file or directory
867  *
868  * FIXME: Wine currently uses the Unix stat() function to get file attributes.
869  * As a result only the Readonly flag is correctly reported, the Archive bit
870  * is always set and the rest are not implemented. We do the Right Thing anyway.
871  *
872  * FIXME: No SET functionality.
873  *
874  */
875
876 void WCMD_setshow_attrib (void) {
877
878 DWORD count;
879 HANDLE hff;
880 WIN32_FIND_DATA fd;
881 char flags[9] = {"        "};
882
883   if (param1[0] == '-') {
884     WCMD_output (nyi);
885     return;
886   }
887
888   if (lstrlen(param1) == 0) {
889     GetCurrentDirectory (sizeof(param1), param1);
890     strcat (param1, "\\*");
891   }
892
893   hff = FindFirstFile (param1, &fd);
894   if (hff == INVALID_HANDLE_VALUE) {
895     WCMD_output ("%s: File Not Found\n",param1);
896   }
897   else {
898     do {
899       if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
900         if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
901           flags[0] = 'H';
902         }
903         if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
904           flags[1] = 'S';
905         }
906         if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
907           flags[2] = 'A';
908         }
909         if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
910           flags[3] = 'R';
911         }
912         if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
913           flags[4] = 'T';
914         }
915         if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
916           flags[5] = 'C';
917         }
918         WCMD_output ("%s   %s\n", flags, fd.cFileName);
919         for (count=0; count < 8; count++) flags[count] = ' ';
920       }
921     } while (FindNextFile(hff, &fd) != 0);
922   }
923   FindClose (hff);
924 }
925
926 /*****************************************************************************
927  * WCMD_setshow_default
928  *
929  *      Set/Show the current default directory
930  */
931
932 void WCMD_setshow_default (void) {
933
934 BOOL status;
935 char string[1024];
936
937   if (strlen(param1) == 0) {
938     GetCurrentDirectory (sizeof(string), string);
939     strcat (string, "\n");
940     WCMD_output (string);
941   }
942   else {
943     status = SetCurrentDirectory (param1);
944     if (!status) {
945       WCMD_print_error ();
946       return;
947     }
948    }
949   return;
950 }
951
952 /****************************************************************************
953  * WCMD_setshow_date
954  *
955  * Set/Show the system date
956  * FIXME: Can't change date yet
957  */
958
959 void WCMD_setshow_date (void) {
960
961 char curdate[64], buffer[64];
962 DWORD count;
963
964   if (lstrlen(param1) == 0) {
965     if (GetDateFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL,
966                 curdate, sizeof(curdate))) {
967       WCMD_output ("Current Date is %s\nEnter new date: ", curdate);
968       ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer), &count, NULL);
969       if (count > 2) {
970         WCMD_output (nyi);
971       }
972     }
973     else WCMD_print_error ();
974   }
975   else {
976     WCMD_output (nyi);
977   }
978 }
979
980 /****************************************************************************
981  * WCMD_compare
982  */
983 static int WCMD_compare( const void *a, const void *b )
984 {
985     int r;
986     const char * const *str_a = a, * const *str_b = b;
987     r = CompareString( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
988           *str_a, -1, *str_b, -1 );
989     if( r == CSTR_LESS_THAN ) return -1;
990     if( r == CSTR_GREATER_THAN ) return 1;
991     return 0;
992 }
993
994 /****************************************************************************
995  * WCMD_setshow_sortenv
996  *
997  * sort variables into order for display
998  * Optionally only display those who start with a stub
999  * returns the count displayed
1000  */
1001 static int WCMD_setshow_sortenv(const char *s, const char *stub)
1002 {
1003   UINT count=0, len=0, i, displayedcount=0, stublen=0;
1004   const char **str;
1005
1006   if (stub) stublen = strlen(stub);
1007
1008   /* count the number of strings, and the total length */
1009   while ( s[len] ) {
1010     len += (lstrlen(&s[len]) + 1);
1011     count++;
1012   }
1013
1014   /* add the strings to an array */
1015   str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (char*) );
1016   if( !str )
1017     return 0;
1018   str[0] = s;
1019   for( i=1; i<count; i++ )
1020     str[i] = str[i-1] + lstrlen(str[i-1]) + 1;
1021
1022   /* sort the array */
1023   qsort( str, count, sizeof (char*), WCMD_compare );
1024
1025   /* print it */
1026   for( i=0; i<count; i++ ) {
1027     if (!stub || CompareString (LOCALE_USER_DEFAULT,
1028                                 NORM_IGNORECASE | SORT_STRINGSORT,
1029                                 str[i], stublen, stub, -1) == 2) {
1030       WCMD_output_asis(str[i]);
1031       WCMD_output_asis("\n");
1032       displayedcount++;
1033     }
1034   }
1035
1036   LocalFree( str );
1037   return displayedcount;
1038 }
1039
1040 /****************************************************************************
1041  * WCMD_setshow_env
1042  *
1043  * Set/Show the environment variables
1044  */
1045
1046 void WCMD_setshow_env (char *s) {
1047
1048 LPVOID env;
1049 char *p;
1050 int status;
1051
1052   if (strlen(param1) == 0) {
1053     env = GetEnvironmentStrings ();
1054     WCMD_setshow_sortenv( env, NULL );
1055   }
1056   else {
1057     p = strchr (s, '=');
1058     if (p == NULL) {
1059       env = GetEnvironmentStrings ();
1060       if (WCMD_setshow_sortenv( env, s ) == 0) {
1061         WCMD_output ("Environment variable %s not defined\n", s);
1062       }
1063       return;
1064     }
1065     *p++ = '\0';
1066
1067     if (strlen(p) == 0) p = NULL;
1068     status = SetEnvironmentVariable (s, p);
1069     if ((!status) & (GetLastError() != ERROR_ENVVAR_NOT_FOUND)) WCMD_print_error();
1070   }
1071 }
1072
1073 /****************************************************************************
1074  * WCMD_setshow_path
1075  *
1076  * Set/Show the path environment variable
1077  */
1078
1079 void WCMD_setshow_path (char *command) {
1080
1081 char string[1024];
1082 DWORD status;
1083
1084   if (strlen(param1) == 0) {
1085     status = GetEnvironmentVariable ("PATH", string, sizeof(string));
1086     if (status != 0) {
1087       WCMD_output_asis ( "PATH=");
1088       WCMD_output_asis ( string);
1089       WCMD_output_asis ( "\n");
1090     }
1091     else {
1092       WCMD_output ("PATH not found\n");
1093     }
1094   }
1095   else {
1096     if (*command == '=') command++; /* Skip leading '=' */
1097     status = SetEnvironmentVariable ("PATH", command);
1098     if (!status) WCMD_print_error();
1099   }
1100 }
1101
1102 /****************************************************************************
1103  * WCMD_setshow_prompt
1104  *
1105  * Set or show the command prompt.
1106  */
1107
1108 void WCMD_setshow_prompt (void) {
1109
1110 char *s;
1111
1112   if (strlen(param1) == 0) {
1113     SetEnvironmentVariable ("PROMPT", NULL);
1114   }
1115   else {
1116     s = param1;
1117     while ((*s == '=') || (*s == ' ')) s++;
1118     if (strlen(s) == 0) {
1119       SetEnvironmentVariable ("PROMPT", NULL);
1120     }
1121     else SetEnvironmentVariable ("PROMPT", s);
1122   }
1123 }
1124
1125 /****************************************************************************
1126  * WCMD_setshow_time
1127  *
1128  * Set/Show the system time
1129  * FIXME: Can't change time yet
1130  */
1131
1132 void WCMD_setshow_time (void) {
1133
1134 char curtime[64], buffer[64];
1135 DWORD count;
1136 SYSTEMTIME st;
1137
1138   if (strlen(param1) == 0) {
1139     GetLocalTime(&st);
1140     if (GetTimeFormat (LOCALE_USER_DEFAULT, 0, &st, NULL,
1141                 curtime, sizeof(curtime))) {
1142       WCMD_output ("Current Time is %s\nEnter new time: ", curtime);
1143       ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer), &count, NULL);
1144       if (count > 2) {
1145         WCMD_output (nyi);
1146       }
1147     }
1148     else WCMD_print_error ();
1149   }
1150   else {
1151     WCMD_output (nyi);
1152   }
1153 }
1154
1155 /****************************************************************************
1156  * WCMD_shift
1157  *
1158  * Shift batch parameters.
1159  */
1160
1161 void WCMD_shift (void) {
1162
1163   if (context != NULL) context -> shift_count++;
1164
1165 }
1166
1167 /****************************************************************************
1168  * WCMD_title
1169  *
1170  * Set the console title
1171  */
1172 void WCMD_title (char *command) {
1173   SetConsoleTitle(command);
1174 }
1175
1176 /****************************************************************************
1177  * WCMD_type
1178  *
1179  * Copy a file to standard output.
1180  */
1181
1182 void WCMD_type (void) {
1183
1184 HANDLE h;
1185 char buffer[512];
1186 DWORD count;
1187
1188   if (param1[0] == 0x00) {
1189     WCMD_output ("Argument missing\n");
1190     return;
1191   }
1192   h = CreateFile (param1, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
1193                 FILE_ATTRIBUTE_NORMAL, NULL);
1194   if (h == INVALID_HANDLE_VALUE) {
1195     WCMD_print_error ();
1196     return;
1197   }
1198   while (ReadFile (h, buffer, sizeof(buffer), &count, NULL)) {
1199     if (count == 0) break;      /* ReadFile reports success on EOF! */
1200     buffer[count] = 0;
1201     WCMD_output_asis (buffer);
1202   }
1203   CloseHandle (h);
1204 }
1205
1206 /****************************************************************************
1207  * WCMD_verify
1208  *
1209  * Display verify flag.
1210  * FIXME: We don't actually do anything with the verify flag other than toggle
1211  * it...
1212  */
1213
1214 void WCMD_verify (char *command) {
1215
1216 static const char von[] = "Verify is ON\n", voff[] = "Verify is OFF\n";
1217 int count;
1218
1219   count = strlen(command);
1220   if (count == 0) {
1221     if (verify_mode) WCMD_output (von);
1222     else WCMD_output (voff);
1223     return;
1224   }
1225   if (lstrcmpi(command, "ON") == 0) {
1226     verify_mode = 1;
1227     return;
1228   }
1229   else if (lstrcmpi(command, "OFF") == 0) {
1230     verify_mode = 0;
1231     return;
1232   }
1233   else WCMD_output ("Verify must be ON or OFF\n");
1234 }
1235
1236 /****************************************************************************
1237  * WCMD_version
1238  *
1239  * Display version info.
1240  */
1241
1242 void WCMD_version (void) {
1243
1244   WCMD_output (version_string);
1245
1246 }
1247
1248 /****************************************************************************
1249  * WCMD_volume
1250  *
1251  * Display volume info and/or set volume label. Returns 0 if error.
1252  */
1253
1254 int WCMD_volume (int mode, char *path) {
1255
1256 DWORD count, serial;
1257 char string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
1258 BOOL status;
1259
1260   if (lstrlen(path) == 0) {
1261     status = GetCurrentDirectory (sizeof(curdir), curdir);
1262     if (!status) {
1263       WCMD_print_error ();
1264       return 0;
1265     }
1266     status = GetVolumeInformation (NULL, label, sizeof(label), &serial, NULL,
1267         NULL, NULL, 0);
1268   }
1269   else {
1270     if ((path[1] != ':') || (lstrlen(path) != 2)) {
1271       WCMD_output_asis("Syntax Error\n\n");
1272       return 0;
1273     }
1274     wsprintf (curdir, "%s\\", path);
1275     status = GetVolumeInformation (curdir, label, sizeof(label), &serial, NULL,
1276         NULL, NULL, 0);
1277   }
1278   if (!status) {
1279     WCMD_print_error ();
1280     return 0;
1281   }
1282   WCMD_output ("Volume in drive %c is %s\nVolume Serial Number is %04x-%04x\n\n",
1283         curdir[0], label, HIWORD(serial), LOWORD(serial));
1284   if (mode) {
1285     WCMD_output ("Volume label (11 characters, ENTER for none)?");
1286     ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string), &count, NULL);
1287     if (count > 1) {
1288       string[count-1] = '\0';           /* ReadFile output is not null-terminated! */
1289       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
1290     }
1291     if (lstrlen(path) != 0) {
1292       if (!SetVolumeLabel (curdir, string)) WCMD_print_error ();
1293     }
1294     else {
1295       if (!SetVolumeLabel (NULL, string)) WCMD_print_error ();
1296     }
1297   }
1298   return 1;
1299 }
1300
1301 /**************************************************************************
1302  * WCMD_exit
1303  *
1304  * Exit either the process, or just this batch program
1305  *
1306  */
1307
1308 void WCMD_exit (void) {
1309
1310     int rc = atoi(param1); /* Note: atoi of empty parameter is 0 */
1311
1312     if (context && lstrcmpi(quals, "/B") == 0) {
1313         errorlevel = rc;
1314         context -> skip_rest = TRUE;
1315     } else {
1316         ExitProcess(rc);
1317     }
1318 }
1319
1320 /**************************************************************************
1321  * WCMD_ask_confirm
1322  *
1323  * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
1324  * answer.
1325  *
1326  * Returns True if Y answer is selected
1327  *
1328  */
1329 BOOL WCMD_ask_confirm (char *message, BOOL showSureText) {
1330
1331     char  msgbuffer[MAXSTRING];
1332     char  Ybuffer[MAXSTRING];
1333     char  Nbuffer[MAXSTRING];
1334     char  answer[MAX_PATH] = "";
1335     DWORD count = 0;
1336
1337     /* Load the translated 'Are you sure', plus valid answers */
1338     LoadString (hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer));
1339     LoadString (hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer));
1340     LoadString (hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer));
1341
1342     /* Loop waiting on a Y or N */
1343     while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
1344       WCMD_output_asis (message);
1345       if (showSureText) {
1346         WCMD_output_asis (msgbuffer);
1347       }
1348       WCMD_output_asis (" (");
1349       WCMD_output_asis (Ybuffer);
1350       WCMD_output_asis ("/");
1351       WCMD_output_asis (Nbuffer);
1352       WCMD_output_asis (")?");
1353       ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
1354                 &count, NULL);
1355       answer[0] = toupper(answer[0]);
1356     }
1357
1358     /* Return the answer */
1359     return (answer[0] == Ybuffer[0]);
1360 }