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