Hi, These changes have been verified against the CMD.EXE shipped with Windows XP SP1. -Ryan ChangeLog: -Never allocate a new console -Recognize .CMD as a valid extension for batch files -Fix case sensitivity in batch file extension checks -Fix case sensitivity on command line parameter checks -CTTY is only present as a builtin in COMMAND.COM, not CMD.EXE. Remove its stub from WCMD -Implement COLOR -Don't show the version banner on /K -Remove the (disabled) AUTOEXEC.BAT code. CMD.EXE does not run AUTOEXEC on startup, that's another COMMAND.COMism. -Fix parsing of REM command -Fix the "ECHO ." special case -Fix the CALL command to be able to invoke non-batch files -Add support for $A, $C, $F, and $S in PROMPT strings -Set our default prompt to $P$G to match Windows -A few fixes to format our output more like the real CMD.EXE, especially wrt newlines
Index: batch.c =================================================================== RCS file: /home/wine/wine/programs/wcmd/batch.c,v retrieving revision 1.9 diff -u -r1.9 batch.c --- batch.c 24 Jul 2002 19:00:05 -0000 1.9 +++ batch.c 6 Nov 2002 05:42:24 -0000 @@ -38,8 +38,8 @@ * Open and execute a batch file. * On entry *command includes the complete command line beginning with the name * of the batch file (if a CALL command was entered the CALL has been removed). - * *file is the name of the file, which might not exist and may not have the - * .BAT suffix on. Called is 1 for a CALL, 0 otherwise. + * *file is the name of the file, which might not exist + * Called is 1 for a CALL, 0 otherwise. * * We need to handle recursion correctly, since one batch program might call another. * So parameters for this batch file are held in a BATCH_CONTEXT structure. @@ -53,7 +53,7 @@ strcpy (string, file); CharLower (string); - if (strstr (string, ".bat") == NULL) strcat (string, ".bat"); + h = CreateFile (string, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (h == INVALID_HANDLE_VALUE) { SetLastError (ERROR_FILE_NOT_FOUND); @@ -167,7 +167,7 @@ WCMD_output ("%s\n", cmd2); } - WCMD_process_command (cmd2); + WCMD_process_command (cmd2, 0); } /******************************************************************* Index: builtins.c =================================================================== RCS file: /home/wine/wine/programs/wcmd/builtins.c,v retrieving revision 1.15 diff -u -r1.15 builtins.c --- builtins.c 24 Jul 2002 19:00:25 -0000 1.15 +++ builtins.c 6 Nov 2002 05:42:25 -0000 @@ -74,15 +74,65 @@ } /**************************************************************************** - * WCMD_change_tty + * WCMD_color * - * Change the default i/o device (ie redirect STDin/STDout). + * Change the console colors */ -void WCMD_change_tty () { +void WCMD_color (char *parm) { - WCMD_output (nyi); + unsigned int color; + WORD attrs = 0; + HANDLE hstdout; + CONSOLE_SCREEN_BUFFER_INFO screen_info; + COORD topleft; + if (!*parm) { + parm = "0F"; + } + + /* The color is a two-digit hex number */ + /* The first digit is the background, the second is the foreground */ + sscanf(parm, "%x", &color); + + if (color & 0x1) { + attrs |= FOREGROUND_BLUE; + } + if (color & 0x2) { + attrs |= FOREGROUND_GREEN; + } + if (color & 0x4) { + attrs |= FOREGROUND_RED; + } + if (color & 0x8) { + attrs |= FOREGROUND_INTENSITY; + } + + if (color & 0x10) { + attrs |= BACKGROUND_BLUE; + } + if (color & 0x20) { + attrs |= BACKGROUND_GREEN; + } + if (color & 0x40) { + attrs |= BACKGROUND_RED; + } + if (color & 0x80) { + attrs |= BACKGROUND_INTENSITY; + } + + hstdout = GetStdHandle(STD_OUTPUT_HANDLE); + + /* Figure out how big the console is */ + GetConsoleScreenBufferInfo(hstdout, &screen_info); + + /* Set the attributes of all the existing characters */ + topleft.X = 0; + topleft.Y = 0; + FillConsoleOutputAttribute(hstdout, attrs, screen_info.dwSize.X * screen_info.dwSize.Y, topleft, NULL); + + /* Set the attributes of all the new characters */ + SetConsoleTextAttribute(hstdout, attrs); } /**************************************************************************** @@ -215,10 +265,6 @@ else WCMD_output (eoff); return; } - if ((count == 1) && (command[0] == '.')) { - WCMD_output (newline); - return; - } if (lstrcmpi(command, "ON") == 0) { echo_mode = 1; return; @@ -841,7 +887,7 @@ DWORD count, serial; char string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH]; BOOL status; -static char syntax[] = "Syntax Error\n\n"; +static char syntax[] = "Syntax error\n"; if (lstrlen(path) == 0) { status = GetCurrentDirectory (sizeof(curdir), curdir); @@ -865,7 +911,7 @@ WCMD_print_error (); return 0; } - WCMD_output ("Volume in drive %c is %s\nVolume Serial Number is %04x-%04x\n\n", + WCMD_output ("Volume in drive %c is %s\nVolume Serial Number is %04x-%04x\n", curdir[0], label, HIWORD(serial), LOWORD(serial)); if (mode) { WCMD_output ("Volume label (11 characters, ENTER for none)?"); Index: directory.c =================================================================== RCS file: /home/wine/wine/programs/wcmd/directory.c,v retrieving revision 1.13 diff -u -r1.13 directory.c --- directory.c 28 Oct 2002 23:54:08 -0000 1.13 +++ directory.c 6 Nov 2002 05:42:25 -0000 @@ -100,10 +100,10 @@ if (recurse) { WCMD_output ("\n\n Total files listed:\n%8d files%25s bytes\n", file_total, WCMD_filesize64 (byte_total)); - WCMD_output ("%8d directories %18s bytes free\n\n", + WCMD_output ("%8d directories %18s bytes free\n", dir_total, WCMD_filesize64 (free.QuadPart)); } else { - WCMD_output (" %18s bytes free\n\n", WCMD_filesize64 (free.QuadPart)); + WCMD_output (" %18s bytes free\n", WCMD_filesize64 (free.QuadPart)); } } } Index: wcmd.h =================================================================== RCS file: /home/wine/wine/programs/wcmd/wcmd.h,v retrieving revision 1.8 diff -u -r1.8 wcmd.h --- wcmd.h 31 May 2002 23:40:59 -0000 1.8 +++ wcmd.h 6 Nov 2002 05:42:25 -0000 @@ -33,8 +33,8 @@ #endif /* !WINELIB */ void WCMD_batch (char *, char *, int); -void WCMD_change_tty (void); void WCMD_clear_screen (void); +void WCMD_color (char *); void WCMD_copy (void); void WCMD_create_dir (void); void WCMD_delete (int recurse); @@ -51,11 +51,11 @@ void WCMD_pause (void); void WCMD_pipe (char *command); void WCMD_print_error (void); -void WCMD_process_command (char *command); +void WCMD_process_command (char *command, int called); int WCMD_read_console (char *string, int str_len); void WCMD_remove_dir (void); void WCMD_rename (void); -void WCMD_run_program (char *command); +void WCMD_run_program (char *command, int called); void WCMD_setshow_attrib (void); void WCMD_setshow_date (void); void WCMD_setshow_default (void); @@ -75,6 +75,7 @@ char *WCMD_parameter (char *s, int n, char **where); char *WCMD_strtrim_leading_spaces (char *string); void WCMD_strtrim_trailing_spaces (char *string); +char *WCMD_strcasestr (const char *haystack, const char *needle); /* Data structure to hold context when executing batch files */ @@ -101,8 +102,8 @@ #define WCMD_CD 2 #define WCMD_CHDIR 3 #define WCMD_CLS 4 -#define WCMD_COPY 5 -#define WCMD_CTTY 6 +#define WCMD_COLOR 5 +#define WCMD_COPY 6 #define WCMD_DATE 7 #define WCMD_DEL 8 #define WCMD_DIR 9 Index: wcmdmain.c =================================================================== RCS file: /home/wine/wine/programs/wcmd/wcmdmain.c,v retrieving revision 1.18 diff -u -r1.18 wcmdmain.c --- wcmdmain.c 4 Nov 2002 22:36:07 -0000 1.18 +++ wcmdmain.c 6 Nov 2002 05:42:25 -0000 @@ -26,7 +26,7 @@ #include "wcmd.h" -char *inbuilt[] = {"ATTRIB", "CALL", "CD", "CHDIR", "CLS", "COPY", "CTTY", +char *inbuilt[] = {"ATTRIB", "CALL", "CD", "CHDIR", "CLS", "COLOR", "COPY", "DATE", "DEL", "DIR", "ECHO", "ERASE", "FOR", "GOTO", "HELP", "IF", "LABEL", "MD", "MKDIR", "MOVE", "PATH", "PAUSE", "PROMPT", "REM", "REN", "RENAME", "RD", "RMDIR", "SET", "SHIFT", @@ -35,10 +35,10 @@ HINSTANCE hinst; DWORD errorlevel; int echo_mode = 1, verify_mode = 0; -char nyi[] = "Not Yet Implemented\n\n"; +char nyi[] = "Not Yet Implemented\n"; char newline[] = "\n"; -char version_string[] = "WCMD Version 0.17\n\n"; -char anykey[] = "Press any key to continue: "; +char version_string[] = "WCMD Version 0.17\n"; +char anykey[] = "Press any key to continue . . . "; char quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH]; BATCH_CONTEXT *context = NULL; @@ -50,9 +50,8 @@ int main (int argc, char *argv[]) { char string[1024], args[MAX_PATH], param[MAX_PATH]; -int status, i; +int i; DWORD count; -HANDLE h; args[0] = param[0] = '\0'; if (argc > 1) { @@ -67,24 +66,16 @@ } } - /* If we do a "wcmd /c command", we don't want to allocate a new - * console since the command returns immediately. Rather, we use - * the surrently allocated input and output handles. This allows - * us to pipe to and read from the command interpreter. - */ - if (strstr(args, "/c") != NULL) { - WCMD_process_command (param); + if (WCMD_strcasestr(args, "/c") != NULL) { + WCMD_process_command (param, 0); return 0; } /* - * Allocate a console and set it up. + * Set up the current console, if any + * Never allocate a new console */ - status = FreeConsole (); - if (!status) WCMD_print_error(); - status = AllocConsole(); - if (!status) WCMD_print_error(); SetConsoleMode (GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT); SetConsoleTitle("Wine Command Prompt"); @@ -93,32 +84,23 @@ * Execute any command-line options. */ - if (strstr(args, "/q") != NULL) { + if (WCMD_strcasestr(args, "/q") != NULL) { WCMD_echo ("OFF"); } - if (strstr(args, "/k") != NULL) { - WCMD_process_command (param); + if (WCMD_strcasestr(args, "/k") != NULL) { + WCMD_process_command (param, 0); } - -/* - * If there is an AUTOEXEC.BAT file, try to execute it. - */ - - GetFullPathName ("\\autoexec.bat", sizeof(string), string, NULL); - h = CreateFile (string, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (h != INVALID_HANDLE_VALUE) { - CloseHandle (h); -#if 0 - WCMD_batch (string, " "); -#endif + else { + WCMD_version (); } + WCMD_output(newline); + /* * Loop forever getting commands and executing them. */ - WCMD_version (); while (TRUE) { WCMD_show_prompt (); ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string), &count, NULL); @@ -130,8 +112,10 @@ WCMD_pipe (string); } else { - WCMD_process_command (string); + WCMD_process_command (string, 0); } + + WCMD_output(newline); } } } @@ -144,7 +128,7 @@ */ -void WCMD_process_command (char *command) { +void WCMD_process_command (char *command, int called) { char cmd[1024]; char *p; @@ -174,8 +158,25 @@ return; } +/* + * Strip leading whitespaces, and a '@' if supplied + */ + whichcmd = WCMD_strtrim_leading_spaces(cmd); + if (whichcmd[0] == '@') whichcmd++; + +/* + * REM is a "special" command, exit before the redirection code + */ + if (lstrlen(whichcmd) >= 3) { + if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, + whichcmd, 3, "REM", 3) == CSTR_EQUAL) { + return; + } + } + /* Dont issue newline WCMD_output (newline); @JED*/ + /* * Redirect stdin and/or stdout if required. */ @@ -204,12 +205,6 @@ if ((p = strchr(cmd,'<')) != NULL) *p = '\0'; /* - * Strip leading whitespaces, and a '@' if supplied - */ - whichcmd = WCMD_strtrim_leading_spaces(cmd); - if (whichcmd[0] == '@') whichcmd++; - -/* * Check if the command entered is internal. If it is, pass the rest of the * line down to the command. If not try to run a program. */ @@ -220,7 +215,7 @@ } for (i=0; i<=WCMD_EXIT; i++) { if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, - whichcmd, count, inbuilt[i], -1) == 2) break; + whichcmd, count, inbuilt[i], -1) == CSTR_EQUAL) break; } p = WCMD_strtrim_leading_spaces (&whichcmd[count]); WCMD_parse (p, quals, param1, param2); @@ -230,7 +225,7 @@ WCMD_setshow_attrib (); break; case WCMD_CALL: - WCMD_batch (param1, p, 1); + WCMD_process_command (p, 1); break; case WCMD_CD: case WCMD_CHDIR: @@ -239,12 +234,12 @@ case WCMD_CLS: WCMD_clear_screen (); break; + case WCMD_COLOR: + WCMD_color(p); + break; case WCMD_COPY: WCMD_copy (); break; - case WCMD_CTTY: - WCMD_change_tty (); - break; case WCMD_DATE: WCMD_setshow_date (); break; @@ -332,7 +327,7 @@ case WCMD_EXIT: ExitProcess (0); default: - WCMD_run_program (whichcmd); + WCMD_run_program (whichcmd, called); }; if (old_stdin) { CloseHandle (GetStdHandle (STD_INPUT_HANDLE)); @@ -349,11 +344,9 @@ * * Execute a command line as an external program. If no extension given then * precedence is given to .BAT files. Must allow recursion. - * - * FIXME: Case sensitivity in suffixes! */ -void WCMD_run_program (char *command) { +void WCMD_run_program (char *command, int called) { STARTUPINFO st; PROCESS_INFORMATION pe; @@ -368,16 +361,25 @@ if (!(*param1) && !(*param2)) return; if (strpbrk (param1, "\\:") == NULL) { /* No explicit path given */ - if ((strchr (param1, '.') == NULL) || (strstr (param1, ".bat") != NULL)) { + if ((strchr (param1, '.') == NULL) || (WCMD_strcasestr (param1, ".bat") != NULL) || + (WCMD_strcasestr (param1, ".cmd") != NULL)) { if (SearchPath (NULL, param1, ".bat", sizeof(filetorun), filetorun, NULL)) { - WCMD_batch (filetorun, command, 0); + WCMD_batch (filetorun, command, called); + return; + } + if (SearchPath (NULL, param1, ".cmd", sizeof(filetorun), filetorun, NULL)) { + WCMD_batch (filetorun, command, called); return; } } } else { /* Explicit path given */ - if (strstr (param1, ".bat") != NULL) { - WCMD_batch (param1, command, 0); + if (WCMD_strcasestr (param1, ".bat") != NULL) { + WCMD_batch (param1, command, called); + return; + } + if (WCMD_strcasestr (param1, ".cmd") != NULL) { + WCMD_batch (param1, command, called); return; } if (strchr (param1, '.') == NULL) { @@ -386,7 +388,15 @@ h = CreateFile (filetorun, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (h != INVALID_HANDLE_VALUE) { CloseHandle (h); - WCMD_batch (param1, command, 0); + WCMD_batch (filetorun, command, called); + return; + } + strcpy (filetorun, param1); + strcat (filetorun, ".cmd"); + h = CreateFile (filetorun, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (h != INVALID_HANDLE_VALUE) { + CloseHandle (h); + WCMD_batch (filetorun, command, called); return; } } @@ -432,7 +442,7 @@ status = GetEnvironmentVariable ("PROMPT", prompt_string, sizeof(prompt_string)); if ((status == 0) || (status > sizeof(prompt_string))) { - lstrcpy (prompt_string, "$N$G"); + lstrcpy (prompt_string, "$P$G"); } p = prompt_string; q = out_string; @@ -448,9 +458,15 @@ case '$': *q++ = '$'; break; + case 'A': + *q++ = '&'; + break; case 'B': *q++ = '|'; break; + case 'C': + *q++ = '('; + break; case 'D': GetDateFormat (LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH); while (*q) q++; @@ -458,6 +474,9 @@ case 'E': *q++ = '\E'; break; + case 'F': + *q++ = ')'; + break; case 'G': *q++ = '>'; break; @@ -480,6 +499,9 @@ case 'Q': *q++ = '='; break; + case 'S': + *q++ = ' '; + break; case 'T': GetTimeFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH); while (*q) q++; @@ -516,7 +538,6 @@ } WCMD_output (lpMsgBuf); LocalFree ((HLOCAL)lpMsgBuf); - WCMD_output (newline); return; } @@ -652,18 +673,52 @@ p = strchr(command, '|'); *p++ = '\0'; wsprintf (temp_cmd, "%s > %s", command, temp_file); - WCMD_process_command (temp_cmd); + WCMD_process_command (temp_cmd, 0); command = p; while ((p = strchr(command, '|'))) { *p++ = '\0'; GetTempFileName (temp_path, "WCMD", 0, temp_file2); wsprintf (temp_cmd, "%s < %s > %s", command, temp_file, temp_file2); - WCMD_process_command (temp_cmd); + WCMD_process_command (temp_cmd, 0); DeleteFile (temp_file); lstrcpy (temp_file, temp_file2); command = p; } wsprintf (temp_cmd, "%s < %s", command, temp_file); - WCMD_process_command (temp_cmd); + WCMD_process_command (temp_cmd, 0); DeleteFile (temp_file); +} + +/*************************************************************************** + * WCMD_strcasestr + * + * Case-insensitive version of strstr + * Simply lowercases both strings and calls strstr, could be a lot more + * clever + */ + +char *WCMD_strcasestr (const char *haystack, const char *needle) { + +char *duphaystack; +char *dupneedle; +char *ret; + + duphaystack = strdup(haystack); + dupneedle = strdup(needle); + + CharLower(duphaystack); + CharLower(dupneedle); + + ret = strstr(duphaystack, dupneedle); + + if (ret) { + /* Convert from an address within the duplicated haystack to one + within the original */ + ret = (ret - duphaystack) + (char*)haystack; + } + + free(duphaystack); + free(dupneedle); + + return ret; }