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