cmd.exe: Add pushd and popd.
[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
438     /* Handle special :EOF label */
439     if (lstrcmpi (":eof", param1) == 0) {
440       context -> skip_rest = TRUE;
441       return;
442     }
443
444     SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
445     while (WCMD_fgets (string, sizeof(string), context -> h)) {
446       if ((string[0] == ':') && (lstrcmpi (&string[1], param1) == 0)) return;
447     }
448     WCMD_output ("Target to GOTO not found\n");
449   }
450   return;
451 }
452
453 /*****************************************************************************
454  * WCMD_pushd
455  *
456  *      Push a directory onto the stack
457  */
458
459 void WCMD_pushd (void) {
460     struct env_stack *curdir;
461     BOOL   status;
462     WCHAR *thisdir;
463
464     curdir  = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
465     thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
466     if( !curdir || !thisdir ) {
467       LocalFree(curdir);
468       LocalFree(thisdir);
469       WCMD_output ("out of memory\n");
470       return;
471     }
472
473     GetCurrentDirectoryW (1024, thisdir);
474     status = SetCurrentDirectoryA (param1);
475     if (!status) {
476       WCMD_print_error ();
477       LocalFree(curdir);
478       LocalFree(thisdir);
479       return;
480     } else {
481       curdir -> next    = pushd_directories;
482       curdir -> strings = thisdir;
483       pushd_directories = curdir;
484     }
485 }
486
487
488 /*****************************************************************************
489  * WCMD_popd
490  *
491  *      Pop a directory from the stack
492  */
493
494 void WCMD_popd (void) {
495     struct env_stack *temp = pushd_directories;
496
497     if (!pushd_directories)
498       return;
499
500     /* pop the old environment from the stack, and make it the current dir */
501     pushd_directories = temp->next;
502     SetCurrentDirectoryW(temp->strings);
503     LocalFree (temp->strings);
504     LocalFree (temp);
505 }
506
507 /****************************************************************************
508  * WCMD_if
509  *
510  * Batch file conditional.
511  * FIXME: Much more syntax checking needed!
512  */
513
514 void WCMD_if (char *p) {
515
516 int negate = 0, test = 0;
517 char condition[MAX_PATH], *command, *s;
518
519   if (!lstrcmpi (param1, "not")) {
520     negate = 1;
521     lstrcpy (condition, param2);
522 }
523   else {
524     lstrcpy (condition, param1);
525   }
526   if (!lstrcmpi (condition, "errorlevel")) {
527     if (errorlevel >= atoi(WCMD_parameter (p, 1+negate, NULL))) test = 1;
528     WCMD_parameter (p, 2+negate, &command);
529   }
530   else if (!lstrcmpi (condition, "exist")) {
531     if (GetFileAttributesA(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
532         test = 1;
533     }
534     WCMD_parameter (p, 2+negate, &command);
535   }
536   else if (!lstrcmpi (condition, "defined")) {
537     if (GetEnvironmentVariableA(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
538         test = 1;
539     }
540     WCMD_parameter (p, 2+negate, &command);
541   }
542   else if ((s = strstr (p, "=="))) {
543     s += 2;
544     if (!lstrcmpi (condition, WCMD_parameter (s, 0, NULL))) test = 1;
545     WCMD_parameter (s, 1, &command);
546   }
547   else {
548     WCMD_output ("Syntax error\n");
549     return;
550   }
551   if (test != negate) {
552     command = strdup (command);
553     WCMD_process_command (command);
554     free (command);
555   }
556 }
557
558 /****************************************************************************
559  * WCMD_move
560  *
561  * Move a file, directory tree or wildcarded set of files.
562  * FIXME: Needs input and output files to be fully specified.
563  */
564
565 void WCMD_move (void) {
566
567 int status;
568 char outpath[MAX_PATH], inpath[MAX_PATH], *infile;
569 WIN32_FIND_DATA fd;
570 HANDLE hff;
571
572   if (param1[0] == 0x00) {
573     WCMD_output ("Argument missing\n");
574     return;
575   }
576
577   if ((strchr(param1,'*') != NULL) || (strchr(param1,'%') != NULL)) {
578     WCMD_output ("Wildcards not yet supported\n");
579     return;
580   }
581
582   /* If no destination supplied, assume current directory */
583   if (param2[0] == 0x00) {
584       strcpy(param2, ".");
585   }
586
587   /* If 2nd parm is directory, then use original filename */
588   GetFullPathName (param2, sizeof(outpath), outpath, NULL);
589   if (outpath[strlen(outpath) - 1] == '\\')
590       outpath[strlen(outpath) - 1] = '\0';
591   hff = FindFirstFile (outpath, &fd);
592   if (hff != INVALID_HANDLE_VALUE) {
593     if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
594       GetFullPathName (param1, sizeof(inpath), inpath, &infile);
595       strcat (outpath, "\\");
596       strcat (outpath, infile);
597     }
598     FindClose (hff);
599   }
600
601   status = MoveFile (param1, outpath);
602   if (!status) WCMD_print_error ();
603 }
604
605 /****************************************************************************
606  * WCMD_pause
607  *
608  * Wait for keyboard input.
609  */
610
611 void WCMD_pause (void) {
612
613 DWORD count;
614 char string[32];
615
616   WCMD_output (anykey);
617   ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string), &count, NULL);
618 }
619
620 /****************************************************************************
621  * WCMD_remove_dir
622  *
623  * Delete a directory.
624  */
625
626 void WCMD_remove_dir (void) {
627
628   if (param1[0] == 0x00) {
629     WCMD_output ("Argument missing\n");
630     return;
631   }
632   if (!RemoveDirectory (param1)) WCMD_print_error ();
633 }
634
635 /****************************************************************************
636  * WCMD_rename
637  *
638  * Rename a file.
639  * FIXME: Needs input and output files to be fully specified.
640  */
641
642 void WCMD_rename (void) {
643
644 int status;
645
646   if (param1[0] == 0x00 || param2[0] == 0x00) {
647     WCMD_output ("Argument missing\n");
648     return;
649   }
650   if ((strchr(param1,'*') != NULL) || (strchr(param1,'%') != NULL)) {
651     WCMD_output ("Wildcards not yet supported\n");
652     return;
653   }
654   status = MoveFile (param1, param2);
655   if (!status) WCMD_print_error ();
656 }
657
658 /*****************************************************************************
659  * WCMD_dupenv
660  *
661  * Make a copy of the environment.
662  */
663 static WCHAR *WCMD_dupenv( const WCHAR *env )
664 {
665   WCHAR *env_copy;
666   int len;
667
668   if( !env )
669     return NULL;
670
671   len = 0;
672   while ( env[len] )
673     len += (lstrlenW(&env[len]) + 1);
674
675   env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
676   if (!env_copy)
677   {
678     WCMD_output ("out of memory\n");
679     return env_copy;
680   }
681   memcpy (env_copy, env, len*sizeof (WCHAR));
682   env_copy[len] = 0;
683
684   return env_copy;
685 }
686
687 /*****************************************************************************
688  * WCMD_setlocal
689  *
690  *  setlocal pushes the environment onto a stack
691  *  Save the environment as unicode so we don't screw anything up.
692  */
693 void WCMD_setlocal (const char *s) {
694   WCHAR *env;
695   struct env_stack *env_copy;
696
697   /* DISABLEEXTENSIONS ignored */
698
699   env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
700   if( !env_copy )
701   {
702     WCMD_output ("out of memory\n");
703     return;
704   }
705
706   env = GetEnvironmentStringsW ();
707
708   env_copy->strings = WCMD_dupenv (env);
709   if (env_copy->strings)
710   {
711     env_copy->next = saved_environment;
712     saved_environment = env_copy;
713   }
714   else
715     LocalFree (env_copy);
716
717   FreeEnvironmentStringsW (env);
718 }
719
720 /*****************************************************************************
721  * WCMD_strchrW
722  */
723 static inline WCHAR *WCMD_strchrW(WCHAR *str, WCHAR ch)
724 {
725    while(*str)
726    {
727      if(*str == ch)
728        return str;
729      str++;
730    }
731    return NULL;
732 }
733
734 /*****************************************************************************
735  * WCMD_endlocal
736  *
737  *  endlocal pops the environment off a stack
738  */
739 void WCMD_endlocal (void) {
740   WCHAR *env, *old, *p;
741   struct env_stack *temp;
742   int len, n;
743
744   if (!saved_environment)
745     return;
746
747   /* pop the old environment from the stack */
748   temp = saved_environment;
749   saved_environment = temp->next;
750
751   /* delete the current environment, totally */
752   env = GetEnvironmentStringsW ();
753   old = WCMD_dupenv (GetEnvironmentStringsW ());
754   len = 0;
755   while (old[len]) {
756     n = lstrlenW(&old[len]) + 1;
757     p = WCMD_strchrW(&old[len], '=');
758     if (p)
759     {
760       *p++ = 0;
761       SetEnvironmentVariableW (&old[len], NULL);
762     }
763     len += n;
764   }
765   LocalFree (old);
766   FreeEnvironmentStringsW (env);
767   
768   /* restore old environment */
769   env = temp->strings;
770   len = 0;
771   while (env[len]) {
772     n = lstrlenW(&env[len]) + 1;
773     p = WCMD_strchrW(&env[len], '=');
774     if (p)
775     {
776       *p++ = 0;
777       SetEnvironmentVariableW (&env[len], p);
778     }
779     len += n;
780   }
781   LocalFree (env);
782   LocalFree (temp);
783 }
784
785 /*****************************************************************************
786  * WCMD_setshow_attrib
787  *
788  * Display and optionally sets DOS attributes on a file or directory
789  *
790  * FIXME: Wine currently uses the Unix stat() function to get file attributes.
791  * As a result only the Readonly flag is correctly reported, the Archive bit
792  * is always set and the rest are not implemented. We do the Right Thing anyway.
793  *
794  * FIXME: No SET functionality.
795  *
796  */
797
798 void WCMD_setshow_attrib (void) {
799
800 DWORD count;
801 HANDLE hff;
802 WIN32_FIND_DATA fd;
803 char flags[9] = {"        "};
804
805   if (param1[0] == '-') {
806     WCMD_output (nyi);
807     return;
808   }
809
810   if (lstrlen(param1) == 0) {
811     GetCurrentDirectory (sizeof(param1), param1);
812     strcat (param1, "\\*");
813   }
814
815   hff = FindFirstFile (param1, &fd);
816   if (hff == INVALID_HANDLE_VALUE) {
817     WCMD_output ("%s: File Not Found\n",param1);
818   }
819   else {
820     do {
821       if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
822         if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
823           flags[0] = 'H';
824         }
825         if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
826           flags[1] = 'S';
827         }
828         if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
829           flags[2] = 'A';
830         }
831         if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
832           flags[3] = 'R';
833         }
834         if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
835           flags[4] = 'T';
836         }
837         if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
838           flags[5] = 'C';
839         }
840         WCMD_output ("%s   %s\n", flags, fd.cFileName);
841         for (count=0; count < 8; count++) flags[count] = ' ';
842       }
843     } while (FindNextFile(hff, &fd) != 0);
844   }
845   FindClose (hff);
846 }
847
848 /*****************************************************************************
849  * WCMD_setshow_default
850  *
851  *      Set/Show the current default directory
852  */
853
854 void WCMD_setshow_default (void) {
855
856 BOOL status;
857 char string[1024];
858
859   if (strlen(param1) == 0) {
860     GetCurrentDirectory (sizeof(string), string);
861     strcat (string, "\n");
862     WCMD_output (string);
863   }
864   else {
865     status = SetCurrentDirectory (param1);
866     if (!status) {
867       WCMD_print_error ();
868       return;
869     }
870    }
871   return;
872 }
873
874 /****************************************************************************
875  * WCMD_setshow_date
876  *
877  * Set/Show the system date
878  * FIXME: Can't change date yet
879  */
880
881 void WCMD_setshow_date (void) {
882
883 char curdate[64], buffer[64];
884 DWORD count;
885
886   if (lstrlen(param1) == 0) {
887     if (GetDateFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL,
888                 curdate, sizeof(curdate))) {
889       WCMD_output ("Current Date is %s\nEnter new date: ", curdate);
890       ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer), &count, NULL);
891       if (count > 2) {
892         WCMD_output (nyi);
893       }
894     }
895     else WCMD_print_error ();
896   }
897   else {
898     WCMD_output (nyi);
899   }
900 }
901
902 /****************************************************************************
903  * WCMD_compare
904  */
905 static int WCMD_compare( const void *a, const void *b )
906 {
907     int r;
908     const char * const *str_a = a, * const *str_b = b;
909     r = CompareString( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
910           *str_a, -1, *str_b, -1 );
911     if( r == CSTR_LESS_THAN ) return -1;
912     if( r == CSTR_GREATER_THAN ) return 1;
913     return 0;
914 }
915
916 /****************************************************************************
917  * WCMD_setshow_sortenv
918  *
919  * sort variables into order for display
920  */
921 static void WCMD_setshow_sortenv(const char *s)
922 {
923   UINT count=0, len=0, i;
924   const char **str;
925
926   /* count the number of strings, and the total length */
927   while ( s[len] ) {
928     len += (lstrlen(&s[len]) + 1);
929     count++;
930   }
931
932   /* add the strings to an array */
933   str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (char*) );
934   if( !str )
935     return;
936   str[0] = s;
937   for( i=1; i<count; i++ )
938     str[i] = str[i-1] + lstrlen(str[i-1]) + 1;
939
940   /* sort the array */
941   qsort( str, count, sizeof (char*), WCMD_compare );
942
943   /* print it */
944   for( i=0; i<count; i++ ) {
945       WCMD_output_asis(str[i]);
946       WCMD_output_asis("\n");
947   }
948
949   LocalFree( str );
950 }
951
952 /****************************************************************************
953  * WCMD_setshow_env
954  *
955  * Set/Show the environment variables
956  */
957
958 void WCMD_setshow_env (char *s) {
959
960 LPVOID env;
961 char *p;
962 int status;
963 char buffer[1048];
964
965   if (strlen(param1) == 0) {
966     env = GetEnvironmentStrings ();
967     WCMD_setshow_sortenv( env );
968   }
969   else {
970     p = strchr (s, '=');
971     if (p == NULL) {
972
973       /* FIXME: Emulate Win98 for now, ie "SET C" looks ONLY for an
974          environment variable C, whereas on NT it shows ALL variables
975          starting with C.
976        */
977       status = GetEnvironmentVariable(s, buffer, sizeof(buffer));
978       if (status) {
979         WCMD_output_asis( s);
980         WCMD_output_asis( "=");
981         WCMD_output_asis( buffer);
982         WCMD_output_asis( "\n");
983       } else {
984         WCMD_output ("Environment variable %s not defined\n", s);
985       }
986       return;
987     }
988     *p++ = '\0';
989
990     if (strlen(p) == 0) p = NULL;
991     status = SetEnvironmentVariable (s, p);
992     if ((!status) & (GetLastError() != ERROR_ENVVAR_NOT_FOUND)) WCMD_print_error();
993   }
994   /* WCMD_output (newline);   @JED*/
995 }
996
997 /****************************************************************************
998  * WCMD_setshow_path
999  *
1000  * Set/Show the path environment variable
1001  */
1002
1003 void WCMD_setshow_path (char *command) {
1004
1005 char string[1024];
1006 DWORD status;
1007
1008   if (strlen(param1) == 0) {
1009     status = GetEnvironmentVariable ("PATH", string, sizeof(string));
1010     if (status != 0) {
1011       WCMD_output_asis ( "PATH=");
1012       WCMD_output_asis ( string);
1013       WCMD_output_asis ( "\n");
1014     }
1015     else {
1016       WCMD_output ("PATH not found\n");
1017     }
1018   }
1019   else {
1020     if (*command == '=') command++; /* Skip leading '=' */
1021     status = SetEnvironmentVariable ("PATH", command);
1022     if (!status) WCMD_print_error();
1023   }
1024 }
1025
1026 /****************************************************************************
1027  * WCMD_setshow_prompt
1028  *
1029  * Set or show the command prompt.
1030  */
1031
1032 void WCMD_setshow_prompt (void) {
1033
1034 char *s;
1035
1036   if (strlen(param1) == 0) {
1037     SetEnvironmentVariable ("PROMPT", NULL);
1038   }
1039   else {
1040     s = param1;
1041     while ((*s == '=') || (*s == ' ')) s++;
1042     if (strlen(s) == 0) {
1043       SetEnvironmentVariable ("PROMPT", NULL);
1044     }
1045     else SetEnvironmentVariable ("PROMPT", s);
1046   }
1047 }
1048
1049 /****************************************************************************
1050  * WCMD_setshow_time
1051  *
1052  * Set/Show the system time
1053  * FIXME: Can't change time yet
1054  */
1055
1056 void WCMD_setshow_time (void) {
1057
1058 char curtime[64], buffer[64];
1059 DWORD count;
1060 SYSTEMTIME st;
1061
1062   if (strlen(param1) == 0) {
1063     GetLocalTime(&st);
1064     if (GetTimeFormat (LOCALE_USER_DEFAULT, 0, &st, NULL,
1065                 curtime, sizeof(curtime))) {
1066       WCMD_output ("Current Time is %s\nEnter new time: ", curtime);
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_shift
1081  *
1082  * Shift batch parameters.
1083  */
1084
1085 void WCMD_shift (void) {
1086
1087   if (context != NULL) context -> shift_count++;
1088
1089 }
1090
1091 /****************************************************************************
1092  * WCMD_title
1093  *
1094  * Set the console title
1095  */
1096 void WCMD_title (char *command) {
1097   SetConsoleTitle(command);
1098 }
1099
1100 /****************************************************************************
1101  * WCMD_type
1102  *
1103  * Copy a file to standard output.
1104  */
1105
1106 void WCMD_type (void) {
1107
1108 HANDLE h;
1109 char buffer[512];
1110 DWORD count;
1111
1112   if (param1[0] == 0x00) {
1113     WCMD_output ("Argument missing\n");
1114     return;
1115   }
1116   h = CreateFile (param1, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
1117                 FILE_ATTRIBUTE_NORMAL, NULL);
1118   if (h == INVALID_HANDLE_VALUE) {
1119     WCMD_print_error ();
1120     return;
1121   }
1122   while (ReadFile (h, buffer, sizeof(buffer), &count, NULL)) {
1123     if (count == 0) break;      /* ReadFile reports success on EOF! */
1124     buffer[count] = 0;
1125     WCMD_output_asis (buffer);
1126   }
1127   CloseHandle (h);
1128 }
1129
1130 /****************************************************************************
1131  * WCMD_verify
1132  *
1133  * Display verify flag.
1134  * FIXME: We don't actually do anything with the verify flag other than toggle
1135  * it...
1136  */
1137
1138 void WCMD_verify (char *command) {
1139
1140 static const char von[] = "Verify is ON\n", voff[] = "Verify is OFF\n";
1141 int count;
1142
1143   count = strlen(command);
1144   if (count == 0) {
1145     if (verify_mode) WCMD_output (von);
1146     else WCMD_output (voff);
1147     return;
1148   }
1149   if (lstrcmpi(command, "ON") == 0) {
1150     verify_mode = 1;
1151     return;
1152   }
1153   else if (lstrcmpi(command, "OFF") == 0) {
1154     verify_mode = 0;
1155     return;
1156   }
1157   else WCMD_output ("Verify must be ON or OFF\n");
1158 }
1159
1160 /****************************************************************************
1161  * WCMD_version
1162  *
1163  * Display version info.
1164  */
1165
1166 void WCMD_version (void) {
1167
1168   WCMD_output (version_string);
1169
1170 }
1171
1172 /****************************************************************************
1173  * WCMD_volume
1174  *
1175  * Display volume info and/or set volume label. Returns 0 if error.
1176  */
1177
1178 int WCMD_volume (int mode, char *path) {
1179
1180 DWORD count, serial;
1181 char string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
1182 BOOL status;
1183
1184   if (lstrlen(path) == 0) {
1185     status = GetCurrentDirectory (sizeof(curdir), curdir);
1186     if (!status) {
1187       WCMD_print_error ();
1188       return 0;
1189     }
1190     status = GetVolumeInformation (NULL, label, sizeof(label), &serial, NULL,
1191         NULL, NULL, 0);
1192   }
1193   else {
1194     if ((path[1] != ':') || (lstrlen(path) != 2)) {
1195       WCMD_output_asis("Syntax Error\n\n");
1196       return 0;
1197     }
1198     wsprintf (curdir, "%s\\", path);
1199     status = GetVolumeInformation (curdir, label, sizeof(label), &serial, NULL,
1200         NULL, NULL, 0);
1201   }
1202   if (!status) {
1203     WCMD_print_error ();
1204     return 0;
1205   }
1206   WCMD_output ("Volume in drive %c is %s\nVolume Serial Number is %04x-%04x\n\n",
1207         curdir[0], label, HIWORD(serial), LOWORD(serial));
1208   if (mode) {
1209     WCMD_output ("Volume label (11 characters, ENTER for none)?");
1210     ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string), &count, NULL);
1211     if (count > 1) {
1212       string[count-1] = '\0';           /* ReadFile output is not null-terminated! */
1213       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
1214     }
1215     if (lstrlen(path) != 0) {
1216       if (!SetVolumeLabel (curdir, string)) WCMD_print_error ();
1217     }
1218     else {
1219       if (!SetVolumeLabel (NULL, string)) WCMD_print_error ();
1220     }
1221   }
1222   return 1;
1223 }
1224
1225 /**************************************************************************
1226  * WCMD_exit
1227  *
1228  * Exit either the process, or just this batch program
1229  *
1230  */
1231
1232 void WCMD_exit (void) {
1233
1234     int rc = atoi(param1); /* Note: atoi of empty parameter is 0 */
1235
1236     if (context && lstrcmpi(quals, "/B") == 0) {
1237         errorlevel = rc;
1238         context -> skip_rest = TRUE;
1239     } else {
1240         ExitProcess(rc);
1241     }
1242 }