cmd.exe: Additional PROMPT options.
[wine/wine-gecko.git] / programs / cmd / wcmdmain.c
blob4253f317cce6088d4181cbbdffcb928436dbcb06
1 /*
2 * CMD - Wine-compatible command line interface.
4 * Copyright (C) 1999 - 2001 D A Pickles
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.
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.
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
22 * FIXME:
23 * - Cannot handle parameters in quotes
24 * - Lots of functionality missing from builtins
27 #include "config.h"
28 #include "wcmd.h"
30 const char * const inbuilt[] = {"ATTRIB", "CALL", "CD", "CHDIR", "CLS", "COPY", "CTTY",
31 "DATE", "DEL", "DIR", "ECHO", "ERASE", "FOR", "GOTO",
32 "HELP", "IF", "LABEL", "MD", "MKDIR", "MOVE", "PATH", "PAUSE",
33 "PROMPT", "REM", "REN", "RENAME", "RD", "RMDIR", "SET", "SHIFT",
34 "TIME", "TITLE", "TYPE", "VERIFY", "VER", "VOL",
35 "ENDLOCAL", "SETLOCAL", "PUSHD", "POPD", "EXIT" };
37 HINSTANCE hinst;
38 DWORD errorlevel;
39 int echo_mode = 1, verify_mode = 0;
40 static int opt_c, opt_k, opt_s;
41 const char nyi[] = "Not Yet Implemented\n\n";
42 const char newline[] = "\n";
43 const char version_string[] = "CMD Version " PACKAGE_VERSION "\n\n";
44 const char anykey[] = "Press Return key to continue: ";
45 char quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
46 BATCH_CONTEXT *context = NULL;
47 static HANDLE old_stdin = INVALID_HANDLE_VALUE, old_stdout = INVALID_HANDLE_VALUE;
49 static char *WCMD_expand_envvar(char *start);
51 /*****************************************************************************
52 * Main entry point. This is a console application so we have a main() not a
53 * winmain().
56 int main (int argc, char *argv[])
58 char string[1024];
59 char* cmd=NULL;
60 DWORD count;
61 HANDLE h;
62 int opt_q;
64 opt_c=opt_k=opt_q=opt_s=0;
65 while (*argv!=NULL)
67 char c;
68 if ((*argv)[0]!='/' || (*argv)[1]=='\0') {
69 argv++;
70 continue;
73 c=(*argv)[1];
74 if (tolower(c)=='c') {
75 opt_c=1;
76 } else if (tolower(c)=='q') {
77 opt_q=1;
78 } else if (tolower(c)=='k') {
79 opt_k=1;
80 } else if (tolower(c)=='s') {
81 opt_s=1;
82 } else if (tolower(c)=='t' || tolower(c)=='x' || tolower(c)=='y') {
83 /* Ignored for compatibility with Windows */
86 if ((*argv)[2]==0)
87 argv++;
88 else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
89 *argv+=2;
91 if (opt_c || opt_k) /* break out of parsing immediately after c or k */
92 break;
95 if (opt_q) {
96 WCMD_echo("OFF");
99 if (opt_c || opt_k) {
100 int len,qcount;
101 char** arg;
102 char* p;
104 /* opt_s left unflagged if the command starts with and contains exactly
105 * one quoted string (exactly two quote characters). The quoted string
106 * must be an executable name that has whitespace and must not have the
107 * following characters: &<>()@^| */
109 /* Build the command to execute */
110 len = 0;
111 qcount = 0;
112 for (arg = argv; *arg; arg++)
114 int has_space,bcount;
115 char* a;
117 has_space=0;
118 bcount=0;
119 a=*arg;
120 if( !*a ) has_space=1;
121 while (*a!='\0') {
122 if (*a=='\\') {
123 bcount++;
124 } else {
125 if (*a==' ' || *a=='\t') {
126 has_space=1;
127 } else if (*a=='"') {
128 /* doubling of '\' preceding a '"',
129 * plus escaping of said '"'
131 len+=2*bcount+1;
132 qcount++;
134 bcount=0;
136 a++;
138 len+=(a-*arg)+1 /* for the separating space */;
139 if (has_space)
141 len+=2; /* for the quotes */
142 qcount+=2;
146 if (qcount!=2)
147 opt_s=1;
149 /* check argv[0] for a space and invalid characters */
150 if (!opt_s) {
151 opt_s=1;
152 p=*argv;
153 while (*p!='\0') {
154 if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
155 || *p=='@' || *p=='^' || *p=='|') {
156 opt_s=1;
157 break;
159 if (*p==' ')
160 opt_s=0;
161 p++;
165 cmd = HeapAlloc(GetProcessHeap(), 0, len);
166 if (!cmd)
167 exit(1);
169 p = cmd;
170 for (arg = argv; *arg; arg++)
172 int has_space,has_quote;
173 char* a;
175 /* Check for quotes and spaces in this argument */
176 has_space=has_quote=0;
177 a=*arg;
178 if( !*a ) has_space=1;
179 while (*a!='\0') {
180 if (*a==' ' || *a=='\t') {
181 has_space=1;
182 if (has_quote)
183 break;
184 } else if (*a=='"') {
185 has_quote=1;
186 if (has_space)
187 break;
189 a++;
192 /* Now transfer it to the command line */
193 if (has_space)
194 *p++='"';
195 if (has_quote) {
196 int bcount;
197 char* a;
199 bcount=0;
200 a=*arg;
201 while (*a!='\0') {
202 if (*a=='\\') {
203 *p++=*a;
204 bcount++;
205 } else {
206 if (*a=='"') {
207 int i;
209 /* Double all the '\\' preceding this '"', plus one */
210 for (i=0;i<=bcount;i++)
211 *p++='\\';
212 *p++='"';
213 } else {
214 *p++=*a;
216 bcount=0;
218 a++;
220 } else {
221 strcpy(p,*arg);
222 p+=strlen(*arg);
224 if (has_space)
225 *p++='"';
226 *p++=' ';
228 if (p > cmd)
229 p--; /* remove last space */
230 *p = '\0';
232 /* strip first and last quote characters if opt_s; check for invalid
233 * executable is done later */
234 if (opt_s && *cmd=='\"')
235 WCMD_opt_s_strip_quotes(cmd);
238 if (opt_c) {
239 /* If we do a "wcmd /c command", we don't want to allocate a new
240 * console since the command returns immediately. Rather, we use
241 * the currently allocated input and output handles. This allows
242 * us to pipe to and read from the command interpreter.
244 if (strchr(cmd,'|') != NULL)
245 WCMD_pipe(cmd);
246 else
247 WCMD_process_command(cmd);
248 HeapFree(GetProcessHeap(), 0, cmd);
249 return errorlevel;
252 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
253 ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
254 SetConsoleTitle("Wine Command Prompt");
256 if (opt_k) {
257 WCMD_process_command(cmd);
258 HeapFree(GetProcessHeap(), 0, cmd);
262 * If there is an AUTOEXEC.BAT file, try to execute it.
265 GetFullPathName ("\\autoexec.bat", sizeof(string), string, NULL);
266 h = CreateFile (string, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
267 if (h != INVALID_HANDLE_VALUE) {
268 CloseHandle (h);
269 #if 0
270 WCMD_batch ((char *)"\\autoexec.bat", (char *)"\\autoexec.bat", 0, NULL, INVALID_HANDLE_VALUE);
271 #endif
275 * Loop forever getting commands and executing them.
278 WCMD_version ();
279 while (TRUE) {
280 WCMD_show_prompt ();
281 ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string), &count, NULL);
282 if (count > 1) {
283 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
284 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
285 if (lstrlen (string) != 0) {
286 if (strchr(string,'|') != NULL) {
287 WCMD_pipe (string);
289 else {
290 WCMD_process_command (string);
298 /*****************************************************************************
299 * Process one command. If the command is EXIT this routine does not return.
300 * We will recurse through here executing batch files.
304 void WCMD_process_command (char *command)
306 char *cmd, *p, *s, *t;
307 int status, i;
308 DWORD count, creationDisposition;
309 HANDLE h;
310 char *whichcmd;
311 SECURITY_ATTRIBUTES sa;
312 char *new_cmd;
314 /* Move copy of the command onto the heap so it can be expanded */
315 new_cmd = HeapAlloc( GetProcessHeap(), 0, MAXSTRING );
316 strcpy(new_cmd, command);
318 /* For commands in a context (batch program): */
319 /* Expand environment variables in a batch file %{0-9} first */
320 /* including support for any ~ modifiers */
321 /* Additionally: */
322 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
323 /* names allowing environment variable overrides */
324 /* NOTE: To support the %PATH:xxx% syntax, also perform */
325 /* manual expansion of environment variables here */
327 p = new_cmd;
328 while ((p = strchr(p, '%'))) {
329 i = *(p+1) - '0';
331 /* Replace %~ modifications if in batch program */
332 if (context && *(p+1) == '~') {
333 WCMD_HandleTildaModifiers(&p, NULL);
334 p++;
336 /* Replace use of %0...%9 if in batch program*/
337 } else if (context && (i >= 0) && (i <= 9)) {
338 s = strdup (p+2);
339 t = WCMD_parameter (context -> command, i + context -> shift_count, NULL);
340 strcpy (p, t);
341 strcat (p, s);
342 free (s);
344 /* Replace use of %* if in batch program*/
345 } else if (context && *(p+1)=='*') {
346 char *startOfParms = NULL;
347 s = strdup (p+2);
348 t = WCMD_parameter (context -> command, 1, &startOfParms);
349 if (startOfParms != NULL) strcpy (p, startOfParms);
350 else *p = 0x00;
351 strcat (p, s);
352 free (s);
354 } else {
355 p = WCMD_expand_envvar(p);
358 cmd = new_cmd;
360 /* In a batch program, unknown variables are replace by nothing */
361 /* so remove any remaining %var% */
362 if (context) {
363 p = cmd;
364 while ((p = strchr(p, '%'))) {
365 s = strchr(p+1, '%');
366 if (!s) {
367 *p=0x00;
368 } else {
369 t = strdup(s+1);
370 strcpy(p, t);
371 free(t);
375 /* Show prompt before batch line IF echo is on and in batch program */
376 if (echo_mode && (cmd[0] != '@')) {
377 WCMD_show_prompt();
378 WCMD_output_asis ( cmd);
379 WCMD_output_asis ( "\n");
384 * Changing default drive has to be handled as a special case.
387 if ((cmd[1] == ':') && IsCharAlpha (cmd[0]) && (strlen(cmd) == 2)) {
388 status = SetCurrentDirectory (cmd);
389 if (!status) WCMD_print_error ();
390 HeapFree( GetProcessHeap(), 0, cmd );
391 return;
394 /* Don't issue newline WCMD_output (newline); @JED*/
396 sa.nLength = sizeof(sa);
397 sa.lpSecurityDescriptor = NULL;
398 sa.bInheritHandle = TRUE;
400 * Redirect stdin and/or stdout if required.
403 if ((p = strchr(cmd,'<')) != NULL) {
404 h = CreateFile (WCMD_parameter (++p, 0, NULL), GENERIC_READ, FILE_SHARE_READ, &sa, OPEN_EXISTING,
405 FILE_ATTRIBUTE_NORMAL, NULL);
406 if (h == INVALID_HANDLE_VALUE) {
407 WCMD_print_error ();
408 HeapFree( GetProcessHeap(), 0, cmd );
409 return;
411 old_stdin = GetStdHandle (STD_INPUT_HANDLE);
412 SetStdHandle (STD_INPUT_HANDLE, h);
414 if ((p = strchr(cmd,'>')) != NULL) {
415 *p++ = '\0';
416 if ('>' == *p) {
417 creationDisposition = OPEN_ALWAYS;
418 p++;
420 else {
421 creationDisposition = CREATE_ALWAYS;
423 h = CreateFile (WCMD_parameter (p, 0, NULL), GENERIC_WRITE, 0, &sa, creationDisposition,
424 FILE_ATTRIBUTE_NORMAL, NULL);
425 if (h == INVALID_HANDLE_VALUE) {
426 WCMD_print_error ();
427 HeapFree( GetProcessHeap(), 0, cmd );
428 return;
430 if (SetFilePointer (h, 0, NULL, FILE_END) ==
431 INVALID_SET_FILE_POINTER) {
432 WCMD_print_error ();
434 old_stdout = GetStdHandle (STD_OUTPUT_HANDLE);
435 SetStdHandle (STD_OUTPUT_HANDLE, h);
437 if ((p = strchr(cmd,'<')) != NULL) *p = '\0';
440 * Strip leading whitespaces, and a '@' if supplied
442 whichcmd = WCMD_strtrim_leading_spaces(cmd);
443 if (whichcmd[0] == '@') whichcmd++;
446 * Check if the command entered is internal. If it is, pass the rest of the
447 * line down to the command. If not try to run a program.
450 count = 0;
451 while (IsCharAlphaNumeric(whichcmd[count])) {
452 count++;
454 for (i=0; i<=WCMD_EXIT; i++) {
455 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
456 whichcmd, count, inbuilt[i], -1) == 2) break;
458 p = WCMD_strtrim_leading_spaces (&whichcmd[count]);
459 WCMD_parse (p, quals, param1, param2);
460 switch (i) {
462 case WCMD_ATTRIB:
463 WCMD_setshow_attrib ();
464 break;
465 case WCMD_CALL:
466 WCMD_call (p);
467 break;
468 case WCMD_CD:
469 case WCMD_CHDIR:
470 WCMD_setshow_default ();
471 break;
472 case WCMD_CLS:
473 WCMD_clear_screen ();
474 break;
475 case WCMD_COPY:
476 WCMD_copy ();
477 break;
478 case WCMD_CTTY:
479 WCMD_change_tty ();
480 break;
481 case WCMD_DATE:
482 WCMD_setshow_date ();
483 break;
484 case WCMD_DEL:
485 case WCMD_ERASE:
486 WCMD_delete (0);
487 break;
488 case WCMD_DIR:
489 WCMD_directory ();
490 break;
491 case WCMD_ECHO:
492 WCMD_echo(&whichcmd[count]);
493 break;
494 case WCMD_FOR:
495 WCMD_for (p);
496 break;
497 case WCMD_GOTO:
498 WCMD_goto ();
499 break;
500 case WCMD_HELP:
501 WCMD_give_help (p);
502 break;
503 case WCMD_IF:
504 WCMD_if (p);
505 break;
506 case WCMD_LABEL:
507 WCMD_volume (1, p);
508 break;
509 case WCMD_MD:
510 case WCMD_MKDIR:
511 WCMD_create_dir ();
512 break;
513 case WCMD_MOVE:
514 WCMD_move ();
515 break;
516 case WCMD_PATH:
517 WCMD_setshow_path (p);
518 break;
519 case WCMD_PAUSE:
520 WCMD_pause ();
521 break;
522 case WCMD_PROMPT:
523 WCMD_setshow_prompt ();
524 break;
525 case WCMD_REM:
526 break;
527 case WCMD_REN:
528 case WCMD_RENAME:
529 WCMD_rename ();
530 break;
531 case WCMD_RD:
532 case WCMD_RMDIR:
533 WCMD_remove_dir (p);
534 break;
535 case WCMD_SETLOCAL:
536 WCMD_setlocal(p);
537 break;
538 case WCMD_ENDLOCAL:
539 WCMD_endlocal();
540 break;
541 case WCMD_SET:
542 WCMD_setshow_env (p);
543 break;
544 case WCMD_SHIFT:
545 WCMD_shift ();
546 break;
547 case WCMD_TIME:
548 WCMD_setshow_time ();
549 break;
550 case WCMD_TITLE:
551 if (strlen(&whichcmd[count]) > 0)
552 WCMD_title(&whichcmd[count+1]);
553 break;
554 case WCMD_TYPE:
555 WCMD_type ();
556 break;
557 case WCMD_VER:
558 WCMD_version ();
559 break;
560 case WCMD_VERIFY:
561 WCMD_verify (p);
562 break;
563 case WCMD_VOL:
564 WCMD_volume (0, p);
565 break;
566 case WCMD_PUSHD:
567 WCMD_pushd();
568 break;
569 case WCMD_POPD:
570 WCMD_popd();
571 break;
572 case WCMD_EXIT:
573 WCMD_exit ();
574 break;
575 default:
576 WCMD_run_program (whichcmd, 0);
578 HeapFree( GetProcessHeap(), 0, cmd );
579 if (old_stdin != INVALID_HANDLE_VALUE) {
580 CloseHandle (GetStdHandle (STD_INPUT_HANDLE));
581 SetStdHandle (STD_INPUT_HANDLE, old_stdin);
582 old_stdin = INVALID_HANDLE_VALUE;
584 if (old_stdout != INVALID_HANDLE_VALUE) {
585 CloseHandle (GetStdHandle (STD_OUTPUT_HANDLE));
586 SetStdHandle (STD_OUTPUT_HANDLE, old_stdout);
587 old_stdout = INVALID_HANDLE_VALUE;
591 static void init_msvcrt_io_block(STARTUPINFO* st)
593 STARTUPINFO st_p;
594 /* fetch the parent MSVCRT info block if any, so that the child can use the
595 * same handles as its grand-father
597 st_p.cb = sizeof(STARTUPINFO);
598 GetStartupInfo(&st_p);
599 st->cbReserved2 = st_p.cbReserved2;
600 st->lpReserved2 = st_p.lpReserved2;
601 if (st_p.cbReserved2 && st_p.lpReserved2 &&
602 (old_stdin != INVALID_HANDLE_VALUE || old_stdout != INVALID_HANDLE_VALUE))
604 /* Override the entries for fd 0,1,2 if we happened
605 * to change those std handles (this depends on the way wcmd sets
606 * it's new input & output handles)
608 size_t sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
609 BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
610 if (ptr)
612 unsigned num = *(unsigned*)st_p.lpReserved2;
613 char* flags = (char*)(ptr + sizeof(unsigned));
614 HANDLE* handles = (HANDLE*)(flags + num * sizeof(char));
616 memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
617 st->cbReserved2 = sz;
618 st->lpReserved2 = ptr;
620 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
621 if (num <= 0 || (flags[0] & WX_OPEN))
623 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
624 flags[0] |= WX_OPEN;
626 if (num <= 1 || (flags[1] & WX_OPEN))
628 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
629 flags[1] |= WX_OPEN;
631 if (num <= 2 || (flags[2] & WX_OPEN))
633 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
634 flags[2] |= WX_OPEN;
636 #undef WX_OPEN
641 /******************************************************************************
642 * WCMD_run_program
644 * Execute a command line as an external program. Must allow recursion.
646 * Precedence:
647 * Manual testing under windows shows PATHEXT plays a key part in this,
648 * and the search algorithm and precedence appears to be as follows.
650 * Search locations:
651 * If directory supplied on command, just use that directory
652 * If extension supplied on command, look for that explicit name first
653 * Otherwise, search in each directory on the path
654 * Precedence:
655 * If extension supplied on command, look for that explicit name first
656 * Then look for supplied name .* (even if extension supplied, so
657 * 'garbage.exe' will match 'garbage.exe.cmd')
658 * If any found, cycle through PATHEXT looking for name.exe one by one
659 * Launching
660 * Once a match has been found, it is launched - Code currently uses
661 * findexecutable to acheive this which is left untouched.
664 void WCMD_run_program (char *command, int called) {
666 char temp[MAX_PATH];
667 char pathtosearch[MAX_PATH];
668 char *pathposn;
669 char stemofsearch[MAX_PATH];
670 char *lastSlash;
671 char pathext[MAXSTRING];
672 BOOL extensionsupplied = FALSE;
673 BOOL launched = FALSE;
674 BOOL status;
675 DWORD len;
678 WCMD_parse (command, quals, param1, param2); /* Quick way to get the filename */
679 if (!(*param1) && !(*param2))
680 return;
682 /* Calculate the search path and stem to search for */
683 if (strpbrk (param1, "/\\:") == NULL) { /* No explicit path given, search path */
684 strcpy(pathtosearch,".;");
685 len = GetEnvironmentVariable ("PATH", &pathtosearch[2], sizeof(pathtosearch)-2);
686 if ((len == 0) || (len >= sizeof(pathtosearch) - 2)) {
687 lstrcpy (pathtosearch, ".");
689 if (strchr(param1, '.') != NULL) extensionsupplied = TRUE;
690 strcpy(stemofsearch, param1);
692 } else {
694 /* Convert eg. ..\fred to include a directory by removing file part */
695 GetFullPathName(param1, MAX_PATH, pathtosearch, NULL);
696 lastSlash = strrchr(pathtosearch, '\\');
697 if (lastSlash) *lastSlash = 0x00;
698 if (strchr(lastSlash, '.') != NULL) extensionsupplied = TRUE;
699 strcpy(stemofsearch, lastSlash+1);
702 /* Now extract PATHEXT */
703 len = GetEnvironmentVariable ("PATHEXT", pathext, sizeof(pathext));
704 if ((len == 0) || (len >= sizeof(pathext))) {
705 lstrcpy (pathext, ".bat;.com;.cmd;.exe");
708 /* Loop through the search path, dir by dir */
709 pathposn = pathtosearch;
710 while (!launched && pathposn) {
712 char thisDir[MAX_PATH] = "";
713 char *pos = NULL;
714 BOOL found = FALSE;
716 /* Work on the first directory on the search path */
717 pos = strchr(pathposn, ';');
718 if (pos) {
719 strncpy(thisDir, pathposn, (pos-pathposn));
720 thisDir[(pos-pathposn)] = 0x00;
721 pathposn = pos+1;
723 } else {
724 strcpy(thisDir, pathposn);
725 pathposn = NULL;
728 /* Since you can have eg. ..\.. on the path, need to expand
729 to full information */
730 strcpy(temp, thisDir);
731 GetFullPathName(temp, MAX_PATH, thisDir, NULL);
733 /* 1. If extension supplied, see if that file exists */
734 strcat(thisDir, "\\");
735 strcat(thisDir, stemofsearch);
736 pos = &thisDir[strlen(thisDir)]; /* Pos = end of name */
738 if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
739 found = TRUE;
742 /* 2. Any .* matches? */
743 if (!found) {
744 HANDLE h;
745 WIN32_FIND_DATA finddata;
747 strcat(thisDir,".*");
748 h = FindFirstFile(thisDir, &finddata);
749 FindClose(h);
750 if (h != INVALID_HANDLE_VALUE) {
752 char *thisExt = pathext;
754 /* 3. Yes - Try each path ext */
755 while (thisExt) {
756 char *nextExt = strchr(thisExt, ';');
758 if (nextExt) {
759 strncpy(pos, thisExt, (nextExt-thisExt));
760 pos[(nextExt-thisExt)] = 0x00;
761 thisExt = nextExt+1;
762 } else {
763 strcpy(pos, thisExt);
764 thisExt = NULL;
767 if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
768 found = TRUE;
769 thisExt = NULL;
775 /* Once found, launch it */
776 if (found) {
777 STARTUPINFO st;
778 PROCESS_INFORMATION pe;
779 SHFILEINFO psfi;
780 DWORD console;
781 HINSTANCE hinst;
782 char *ext = strrchr( thisDir, '.' );
783 launched = TRUE;
785 /* Special case BAT and CMD */
786 if (ext && !strcasecmp(ext, ".bat")) {
787 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
788 return;
789 } else if (ext && !strcasecmp(ext, ".cmd")) {
790 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
791 return;
792 } else {
794 /* thisDir contains the file to be launched, but with what?
795 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
796 hinst = FindExecutable (param1, NULL, temp);
797 if ((INT_PTR)hinst < 32)
798 console = 0;
799 else
800 console = SHGetFileInfo (temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
802 ZeroMemory (&st, sizeof(STARTUPINFO));
803 st.cb = sizeof(STARTUPINFO);
804 init_msvcrt_io_block(&st);
806 /* Launch the process and if a CUI wait on it to complete */
807 status = CreateProcess (thisDir, command, NULL, NULL, TRUE,
808 0, NULL, NULL, &st, &pe);
809 if ((opt_c || opt_k) && !opt_s && !status
810 && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
811 /* strip first and last quote characters and try again */
812 WCMD_opt_s_strip_quotes(command);
813 opt_s=1;
814 WCMD_run_program(command, called);
815 return;
817 if (!status) {
818 WCMD_print_error ();
819 /* If a command fails to launch, it sets errorlevel 9009 - which
820 does not seem to have any associated constant definition */
821 errorlevel = 9009;
822 return;
824 if (!console) errorlevel = 0;
825 else
827 if (!HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
828 GetExitCodeProcess (pe.hProcess, &errorlevel);
829 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
831 CloseHandle(pe.hProcess);
832 CloseHandle(pe.hThread);
833 return;
838 /* Not found anywhere - give up */
839 SetLastError(ERROR_FILE_NOT_FOUND);
840 WCMD_print_error ();
842 /* If a command fails to launch, it sets errorlevel 9009 - which
843 does not seem to have any associated constant definition */
844 errorlevel = 9009;
845 return;
849 /******************************************************************************
850 * WCMD_show_prompt
852 * Display the prompt on STDout
856 void WCMD_show_prompt (void) {
858 int status;
859 char out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
860 char *p, *q;
861 DWORD len;
863 len = GetEnvironmentVariable ("PROMPT", prompt_string, sizeof(prompt_string));
864 if ((len == 0) || (len >= sizeof(prompt_string))) {
865 lstrcpy (prompt_string, "$P$G");
867 p = prompt_string;
868 q = out_string;
869 *q = '\0';
870 while (*p != '\0') {
871 if (*p != '$') {
872 *q++ = *p++;
873 *q = '\0';
875 else {
876 p++;
877 switch (toupper(*p)) {
878 case '$':
879 *q++ = '$';
880 break;
881 case 'A':
882 *q++ = '&';
883 break;
884 case 'B':
885 *q++ = '|';
886 break;
887 case 'C':
888 *q++ = '(';
889 break;
890 case 'D':
891 GetDateFormat (LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
892 while (*q) q++;
893 break;
894 case 'E':
895 *q++ = '\E';
896 break;
897 case 'F':
898 *q++ = ')';
899 break;
900 case 'G':
901 *q++ = '>';
902 break;
903 case 'H':
904 *q++ = '\b';
905 break;
906 case 'L':
907 *q++ = '<';
908 break;
909 case 'N':
910 status = GetCurrentDirectory (sizeof(curdir), curdir);
911 if (status) {
912 *q++ = curdir[0];
914 break;
915 case 'P':
916 status = GetCurrentDirectory (sizeof(curdir), curdir);
917 if (status) {
918 lstrcat (q, curdir);
919 while (*q) q++;
921 break;
922 case 'Q':
923 *q++ = '=';
924 break;
925 case 'S':
926 *q++ = ' ';
927 break;
928 case 'T':
929 GetTimeFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
930 while (*q) q++;
931 break;
932 case 'V':
933 lstrcat (q, version_string);
934 while (*q) q++;
935 break;
936 case '_':
937 *q++ = '\n';
938 break;
940 p++;
941 *q = '\0';
944 WCMD_output_asis (out_string);
947 /****************************************************************************
948 * WCMD_print_error
950 * Print the message for GetLastError
953 void WCMD_print_error (void) {
954 LPVOID lpMsgBuf;
955 DWORD error_code;
956 int status;
958 error_code = GetLastError ();
959 status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
960 NULL, error_code, 0, (LPTSTR) &lpMsgBuf, 0, NULL);
961 if (!status) {
962 WCMD_output ("FIXME: Cannot display message for error %d, status %d\n",
963 error_code, GetLastError());
964 return;
966 WCMD_output_asis (lpMsgBuf);
967 LocalFree ((HLOCAL)lpMsgBuf);
968 WCMD_output_asis (newline);
969 return;
972 /*******************************************************************
973 * WCMD_parse - parse a command into parameters and qualifiers.
975 * On exit, all qualifiers are concatenated into q, the first string
976 * not beginning with "/" is in p1 and the
977 * second in p2. Any subsequent non-qualifier strings are lost.
978 * Parameters in quotes are handled.
981 void WCMD_parse (char *s, char *q, char *p1, char *p2) {
983 int p = 0;
985 *q = *p1 = *p2 = '\0';
986 while (TRUE) {
987 switch (*s) {
988 case '/':
989 *q++ = *s++;
990 while ((*s != '\0') && (*s != ' ') && *s != '/') {
991 *q++ = toupper (*s++);
993 *q = '\0';
994 break;
995 case ' ':
996 case '\t':
997 s++;
998 break;
999 case '"':
1000 s++;
1001 while ((*s != '\0') && (*s != '"')) {
1002 if (p == 0) *p1++ = *s++;
1003 else if (p == 1) *p2++ = *s++;
1004 else s++;
1006 if (p == 0) *p1 = '\0';
1007 if (p == 1) *p2 = '\0';
1008 p++;
1009 if (*s == '"') s++;
1010 break;
1011 case '\0':
1012 return;
1013 default:
1014 while ((*s != '\0') && (*s != ' ') && (*s != '\t')) {
1015 if (p == 0) *p1++ = *s++;
1016 else if (p == 1) *p2++ = *s++;
1017 else s++;
1019 if (p == 0) *p1 = '\0';
1020 if (p == 1) *p2 = '\0';
1021 p++;
1026 /*******************************************************************
1027 * WCMD_output - send output to current standard output device.
1031 void WCMD_output (const char *format, ...) {
1033 va_list ap;
1034 char string[1024];
1035 int ret;
1037 va_start(ap,format);
1038 ret = vsnprintf (string, sizeof( string), format, ap);
1039 va_end(ap);
1040 if( ret >= sizeof( string)) {
1041 WCMD_output_asis("ERR: output truncated in WCMD_output\n" );
1042 string[sizeof( string) -1] = '\0';
1044 WCMD_output_asis(string);
1048 static int line_count;
1049 static int max_height;
1050 static BOOL paged_mode;
1052 void WCMD_enter_paged_mode(void)
1054 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
1056 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo))
1057 max_height = consoleInfo.dwSize.Y;
1058 else
1059 max_height = 25;
1060 paged_mode = TRUE;
1061 line_count = 5; /* keep 5 lines from previous output */
1064 void WCMD_leave_paged_mode(void)
1066 paged_mode = FALSE;
1069 /*******************************************************************
1070 * WCMD_output_asis - send output to current standard output device.
1071 * without formatting eg. when message contains '%'
1074 void WCMD_output_asis (const char *message) {
1075 DWORD count;
1076 char* ptr;
1077 char string[1024];
1079 if (paged_mode) {
1080 do {
1081 if ((ptr = strchr(message, '\n')) != NULL) ptr++;
1082 WriteFile (GetStdHandle(STD_OUTPUT_HANDLE), message,
1083 (ptr) ? ptr - message : lstrlen(message), &count, NULL);
1084 if (ptr) {
1085 if (++line_count >= max_height - 1) {
1086 line_count = 0;
1087 WCMD_output_asis (anykey);
1088 ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string), &count, NULL);
1091 } while ((message = ptr) != NULL);
1092 } else {
1093 WriteFile (GetStdHandle(STD_OUTPUT_HANDLE), message, lstrlen(message), &count, NULL);
1098 /***************************************************************************
1099 * WCMD_strtrim_leading_spaces
1101 * Remove leading spaces from a string. Return a pointer to the first
1102 * non-space character. Does not modify the input string
1105 char *WCMD_strtrim_leading_spaces (char *string) {
1107 char *ptr;
1109 ptr = string;
1110 while (*ptr == ' ') ptr++;
1111 return ptr;
1114 /*************************************************************************
1115 * WCMD_strtrim_trailing_spaces
1117 * Remove trailing spaces from a string. This routine modifies the input
1118 * string by placing a null after the last non-space character
1121 void WCMD_strtrim_trailing_spaces (char *string) {
1123 char *ptr;
1125 ptr = string + lstrlen (string) - 1;
1126 while ((*ptr == ' ') && (ptr >= string)) {
1127 *ptr = '\0';
1128 ptr--;
1132 /*************************************************************************
1133 * WCMD_opt_s_strip_quotes
1135 * Remove first and last quote characters, preserving all other text
1138 void WCMD_opt_s_strip_quotes(char *cmd) {
1139 char *src = cmd + 1, *dest = cmd, *lastq = NULL;
1140 while((*dest=*src) != '\0') {
1141 if (*src=='\"')
1142 lastq=dest;
1143 dest++, src++;
1145 if (lastq) {
1146 dest=lastq++;
1147 while ((*dest++=*lastq++) != 0)
1152 /*************************************************************************
1153 * WCMD_pipe
1155 * Handle pipes within a command - the DOS way using temporary files.
1158 void WCMD_pipe (char *command) {
1160 char *p;
1161 char temp_path[MAX_PATH], temp_file[MAX_PATH], temp_file2[MAX_PATH], temp_cmd[1024];
1163 GetTempPath (sizeof(temp_path), temp_path);
1164 GetTempFileName (temp_path, "CMD", 0, temp_file);
1165 p = strchr(command, '|');
1166 *p++ = '\0';
1167 wsprintf (temp_cmd, "%s > %s", command, temp_file);
1168 WCMD_process_command (temp_cmd);
1169 command = p;
1170 while ((p = strchr(command, '|'))) {
1171 *p++ = '\0';
1172 GetTempFileName (temp_path, "CMD", 0, temp_file2);
1173 wsprintf (temp_cmd, "%s < %s > %s", command, temp_file, temp_file2);
1174 WCMD_process_command (temp_cmd);
1175 DeleteFile (temp_file);
1176 lstrcpy (temp_file, temp_file2);
1177 command = p;
1179 wsprintf (temp_cmd, "%s < %s", command, temp_file);
1180 WCMD_process_command (temp_cmd);
1181 DeleteFile (temp_file);
1184 /*************************************************************************
1185 * WCMD_expand_envvar
1187 * Expands environment variables, allowing for character substitution
1189 static char *WCMD_expand_envvar(char *start) {
1190 char *endOfVar = NULL, *s;
1191 char *colonpos = NULL;
1192 char thisVar[MAXSTRING];
1193 char thisVarContents[MAXSTRING];
1194 char savedchar = 0x00;
1195 int len;
1197 /* Find the end of the environment variable, and extract name */
1198 endOfVar = strchr(start+1, '%');
1199 if (endOfVar == NULL) {
1200 /* FIXME: Some special conditions here depending opn whether
1201 in batch, complex or not, and whether env var exists or not! */
1202 return start+1;
1204 strncpy(thisVar, start, (endOfVar - start)+1);
1205 thisVar[(endOfVar - start)+1] = 0x00;
1206 colonpos = strchr(thisVar+1, ':');
1208 /* If there's complex substitution, just need %var% for now
1209 to get the expanded data to play with */
1210 if (colonpos) {
1211 *colonpos = '%';
1212 savedchar = *(colonpos+1);
1213 *(colonpos+1) = 0x00;
1216 /* Expand to contents, if unchanged, return */
1217 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
1218 /* override if existing env var called that name */
1219 if ((CompareString (LOCALE_USER_DEFAULT,
1220 NORM_IGNORECASE | SORT_STRINGSORT,
1221 thisVar, 12, "%ERRORLEVEL%", -1) == 2) &&
1222 (GetEnvironmentVariable("ERRORLEVEL", thisVarContents, 1) == 0) &&
1223 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1224 sprintf(thisVarContents, "%d", errorlevel);
1225 len = strlen(thisVarContents);
1227 } else if ((CompareString (LOCALE_USER_DEFAULT,
1228 NORM_IGNORECASE | SORT_STRINGSORT,
1229 thisVar, 6, "%DATE%", -1) == 2) &&
1230 (GetEnvironmentVariable("DATE", thisVarContents, 1) == 0) &&
1231 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1233 GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
1234 NULL, thisVarContents, MAXSTRING);
1235 len = strlen(thisVarContents);
1237 } else if ((CompareString (LOCALE_USER_DEFAULT,
1238 NORM_IGNORECASE | SORT_STRINGSORT,
1239 thisVar, 6, "%TIME%", -1) == 2) &&
1240 (GetEnvironmentVariable("TIME", thisVarContents, 1) == 0) &&
1241 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1242 GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
1243 NULL, thisVarContents, MAXSTRING);
1244 len = strlen(thisVarContents);
1246 } else if ((CompareString (LOCALE_USER_DEFAULT,
1247 NORM_IGNORECASE | SORT_STRINGSORT,
1248 thisVar, 4, "%CD%", -1) == 2) &&
1249 (GetEnvironmentVariable("CD", thisVarContents, 1) == 0) &&
1250 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1251 GetCurrentDirectory (MAXSTRING, thisVarContents);
1252 len = strlen(thisVarContents);
1254 } else if ((CompareString (LOCALE_USER_DEFAULT,
1255 NORM_IGNORECASE | SORT_STRINGSORT,
1256 thisVar, 8, "%RANDOM%", -1) == 2) &&
1257 (GetEnvironmentVariable("RANDOM", thisVarContents, 1) == 0) &&
1258 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1259 sprintf(thisVarContents, "%d", rand() % 32768);
1260 len = strlen(thisVarContents);
1262 } else {
1264 len = ExpandEnvironmentStrings(thisVar, thisVarContents,
1265 sizeof(thisVarContents));
1268 if (len == 0)
1269 return endOfVar+1;
1271 /* In a batch program, unknown env vars are replaced with nothing,
1272 note syntax %garbage:1,3% results in anything after the ':'
1273 except the %
1274 From the command line, you just get back what you entered */
1275 if (lstrcmpi(thisVar, thisVarContents) == 0) {
1277 /* Restore the complex part after the compare */
1278 if (colonpos) {
1279 *colonpos = ':';
1280 *(colonpos+1) = savedchar;
1283 s = strdup (endOfVar + 1);
1285 /* Command line - just ignore this */
1286 if (context == NULL) return endOfVar+1;
1288 /* Batch - replace unknown env var with nothing */
1289 if (colonpos == NULL) {
1290 strcpy (start, s);
1292 } else {
1293 len = strlen(thisVar);
1294 thisVar[len-1] = 0x00;
1295 /* If %:...% supplied, : is retained */
1296 if (colonpos == thisVar+1) {
1297 strcpy (start, colonpos);
1298 } else {
1299 strcpy (start, colonpos+1);
1301 strcat (start, s);
1303 free (s);
1304 return start;
1308 /* See if we need to do complex substitution (any ':'s), if not
1309 then our work here is done */
1310 if (colonpos == NULL) {
1311 s = strdup (endOfVar + 1);
1312 strcpy (start, thisVarContents);
1313 strcat (start, s);
1314 free(s);
1315 return start;
1318 /* Restore complex bit */
1319 *colonpos = ':';
1320 *(colonpos+1) = savedchar;
1323 Handle complex substitutions:
1324 xxx=yyy (replace xxx with yyy)
1325 *xxx=yyy (replace up to and including xxx with yyy)
1326 ~x (from x chars in)
1327 ~-x (from x chars from the end)
1328 ~x,y (from x chars in for y characters)
1329 ~x,-y (from x chars in until y characters from the end)
1332 /* ~ is substring manipulation */
1333 if (savedchar == '~') {
1335 int substrposition, substrlength;
1336 char *commapos = strchr(colonpos+2, ',');
1337 char *startCopy;
1339 substrposition = atol(colonpos+2);
1340 if (commapos) substrlength = atol(commapos+1);
1342 s = strdup (endOfVar + 1);
1344 /* Check bounds */
1345 if (substrposition >= 0) {
1346 startCopy = &thisVarContents[min(substrposition, len)];
1347 } else {
1348 startCopy = &thisVarContents[max(0, len+substrposition-1)];
1351 if (commapos == NULL) {
1352 strcpy (start, startCopy); /* Copy the lot */
1353 } else if (substrlength < 0) {
1355 int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
1356 if (copybytes > len) copybytes = len;
1357 else if (copybytes < 0) copybytes = 0;
1358 strncpy (start, startCopy, copybytes); /* Copy the lot */
1359 start[copybytes] = 0x00;
1360 } else {
1361 strncpy (start, startCopy, substrlength); /* Copy the lot */
1362 start[substrlength] = 0x00;
1365 strcat (start, s);
1366 free(s);
1367 return start;
1369 /* search and replace manipulation */
1370 } else {
1371 char *equalspos = strstr(colonpos, "=");
1372 char *replacewith = equalspos+1;
1373 char *found = NULL;
1374 char *searchIn;
1375 char *searchFor;
1377 s = strdup (endOfVar + 1);
1378 if (equalspos == NULL) return start+1;
1380 /* Null terminate both strings */
1381 thisVar[strlen(thisVar)-1] = 0x00;
1382 *equalspos = 0x00;
1384 /* Since we need to be case insensitive, copy the 2 buffers */
1385 searchIn = strdup(thisVarContents);
1386 CharUpperBuff(searchIn, strlen(thisVarContents));
1387 searchFor = strdup(colonpos+1);
1388 CharUpperBuff(searchFor, strlen(colonpos+1));
1391 /* Handle wildcard case */
1392 if (*(colonpos+1) == '*') {
1393 /* Search for string to replace */
1394 found = strstr(searchIn, searchFor+1);
1396 if (found) {
1397 /* Do replacement */
1398 strcpy(start, replacewith);
1399 strcat(start, thisVarContents + (found-searchIn) + strlen(searchFor+1));
1400 strcat(start, s);
1401 free(s);
1402 } else {
1403 /* Copy as it */
1404 strcpy(start, thisVarContents);
1405 strcat(start, s);
1408 } else {
1409 /* Loop replacing all instances */
1410 char *lastFound = searchIn;
1411 char *outputposn = start;
1413 *start = 0x00;
1414 while ((found = strstr(lastFound, searchFor))) {
1415 strncpy(outputposn,
1416 thisVarContents + (lastFound-searchIn),
1417 (found - lastFound));
1418 outputposn = outputposn + (found - lastFound);
1419 *outputposn = 0x00;
1420 strcat(outputposn, replacewith);
1421 outputposn = outputposn + strlen(replacewith);
1422 lastFound = found + strlen(searchFor);
1424 strcat(outputposn,
1425 thisVarContents + (lastFound-searchIn));
1426 strcat(outputposn, s);
1428 free(searchIn);
1429 free(searchFor);
1430 return start;
1432 return start+1;