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