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