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