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