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