From a77ad3fba9005db7766fe2dc55dbe2227fe91e0f Mon Sep 17 00:00:00 2001 From: Hugh McMaster Date: Sun, 11 Aug 2013 18:15:45 +1000 Subject: ctrl-c --- programs/cmd/builtins.c | 95 ++++++++++++++++++++++++++++++++++++++++++----- programs/cmd/cmd.rc | 2 + programs/cmd/wcmd.h | 7 ++++ programs/cmd/wcmdmain.c | 65 +++++++++++++++++++++++++++++++- 4 files changed, 158 insertions(+), 11 deletions(-) diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index 64e12a0..248cc69 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -37,6 +37,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(cmd); extern int defaultColor; extern BOOL echo_mode; extern BOOL interactive; +BOOL ctrlc_stop; struct env_stack *pushd_directories; const WCHAR dotW[] = {'.','\0'}; @@ -177,8 +178,7 @@ static struct * set to TRUE * */ -static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText, - BOOL *optionAll) { +BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText, BOOL *optionAll) { UINT msgid; WCHAR confirm[MAXSTRING]; @@ -365,7 +365,7 @@ void WCMD_choice (const WCHAR * args) { WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout); if (have_console) - SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0); + SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT); /* use default keys, when needed: localized versions of "Y"es and "No" */ if (!opt_c) { @@ -402,7 +402,7 @@ void WCMD_choice (const WCHAR * args) { /* FIXME: Add support for option /T */ answer[1] = 0; /* terminate single character string */ WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count); - + if (WCMD_check_abort()) goto exitreturn; /* Stop all commands and exit */ if (!opt_s) answer[0] = toupperW(answer[0]); @@ -425,6 +425,15 @@ void WCMD_choice (const WCHAR * args) { WCMD_output_asis(bellW); } } + +/* Exit out of the routine, freeing any remaining allocated memory */ +exitreturn: + if (WCMD_check_abort()) + { + if (have_console) SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode); + heap_free(my_command); + } + return; } /**************************************************************************** @@ -605,6 +614,8 @@ void WCMD_copy(WCHAR * args) { WCHAR *pos1, *pos2; BOOL inquotes; + if (WCMD_check_abort()) goto exitreturn; /* Stop all commands and exit */ + WINE_TRACE("Working on parameter '%s'\n", wine_dbgstr_w(thisparam)); /* Handle switches */ @@ -655,10 +666,12 @@ void WCMD_copy(WCHAR * args) { WINE_FIXME("Unexpected copy switch %s\n", wine_dbgstr_w(thisparam)); } thisparam++; + if (WCMD_check_abort()) goto exitreturn; /* Stop all commands and exit */ } /* This parameter was purely switches, get the next one */ thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE); + if (WCMD_check_abort()) goto exitreturn; /* Stop all commands and exit */ continue; } @@ -869,6 +882,8 @@ void WCMD_copy(WCHAR * args) { DWORD attributes; BOOL srcisdevice = FALSE; + if (WCMD_check_abort()) goto exitreturn; /* Stop all commands and exit */ + /* If it was not explicit, we now know whether we are concatenating or not and hence whether to copy as binary or ascii */ if (thiscopy->binarycopy == -1) thiscopy->binarycopy = !anyconcats; @@ -922,6 +937,8 @@ void WCMD_copy(WCHAR * args) { WCHAR outname[MAX_PATH]; BOOL overwrite; + if (WCMD_check_abort()) goto exitreturn; /* Stop all commands and exit */ + /* Skip . and .., and directories */ if (!srcisdevice && fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { WINE_TRACE("Skipping directories\n"); @@ -1255,6 +1272,7 @@ static BOOL WCMD_delete_one (const WCHAR *thisArg) { /* Build the filename to delete as \ */ strcpyW (fpath, argCopy); do { + if (WCMD_check_abort()) return FALSE; /* Stop all commands and exit */ p = strrchrW (fpath, '\\'); if (p != NULL) { *++p = '\0'; @@ -1332,6 +1350,7 @@ static BOOL WCMD_delete_one (const WCHAR *thisArg) { DIRECTORY_STACK *lastEntry = NULL; do { + if (WCMD_check_abort()) break; if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (strcmpW(fd.cFileName, dotdotW) != 0) && (strcmpW(fd.cFileName, dotW) != 0)) { @@ -1354,6 +1373,13 @@ static BOOL WCMD_delete_one (const WCHAR *thisArg) { lastEntry = nextDir; nextDir->next = NULL; nextDir->dirName = heap_strdupW(subParm); + + if (WCMD_check_abort()) /* Stop all commands and exit */ + { + if (allDirs != NULL) allDirs = NULL; + if (nextDir != NULL) heap_free(nextDir); + return FALSE; + } } } while (FindNextFileW(hff, &fd) != 0); FindClose (hff); @@ -1361,7 +1387,11 @@ static BOOL WCMD_delete_one (const WCHAR *thisArg) { /* Go through each subdir doing the delete */ while (allDirs != NULL) { DIRECTORY_STACK *tempDir; - + if (WCMD_check_abort()) /* Stop all commands and exit */ + { + if (allDirs != NULL) heap_free(allDirs); + return FALSE; + } tempDir = allDirs->next; found |= WCMD_delete_one (allDirs->dirName); @@ -1528,6 +1558,8 @@ static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd, while (*cmdList) { static const WCHAR ifElse[] = {'e','l','s','e'}; + if (WCMD_check_abort()) break; /* Stop all commands and exit */ + /* execute all appropriate commands */ curPosition = *cmdList; @@ -1541,6 +1573,7 @@ static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd, if ((*cmdList)->prevDelim == CMD_ONFAILURE || (*cmdList)->prevDelim == CMD_ONSUCCESS) { if (processThese && (*cmdList)->command) { + if (WCMD_check_abort()) break; /* Stop all commands and exit */ WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, cmdList, FALSE); } @@ -1549,6 +1582,7 @@ static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd, /* Execute any appended to the statement with (...) */ } else if ((*cmdList)->bracketDepth > myDepth) { if (processThese) { + if (WCMD_check_abort()) break; /* Stop all commands and exit */ *cmdList = WCMD_process_commands(*cmdList, TRUE, FALSE); WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList); } @@ -1571,6 +1605,7 @@ static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd, /* Skip leading whitespace between condition and the command */ while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++; if (*cmd) { + if (WCMD_check_abort()) break; /* Stop all commands and exit */ WCMD_execute (cmd, (*cmdList)->redirects, cmdList, FALSE); } } @@ -1630,6 +1665,7 @@ static BOOL WCMD_parse_forf_options(WCHAR *options, WCHAR *eol, int *skip, /* Process each keyword */ while (pos && *pos) { + if (WCMD_check_abort()) break; /* Stop all commands and exit */ if (*pos == ' ' || *pos == '\t') { pos++; @@ -1788,6 +1824,8 @@ static int WCMD_for_nexttoken(int lasttoken, WCHAR *tokenstr, int nextnumber1, nextnumber2 = -1; WCHAR *nextchar; + if (WCMD_check_abort()) break; /* Stop all commands and exit */ + /* Get the next number */ nextnumber1 = strtoulW(pos, &nextchar, 10); @@ -2207,6 +2245,8 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) { firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */ itemNum = 0; + if (WCMD_check_abort()) break; /* Stop all commands and exit */ + /* If we are recursing directories (ie /R), add all sub directories now, then prefix the root when searching for the item */ if (dirsToWalk) WCMD_add_dirstowalk(dirsToWalk); @@ -2223,6 +2263,7 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) { WCHAR buffer[MAXSTRING]; WINE_TRACE("Processing for set %p\n", thisSet); + if (WCMD_check_abort()) break; /* Stop all commands and exit */ i = 0; while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, TRUE, FALSE))) { @@ -2231,6 +2272,7 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) { * otherwise do a literal substitution. */ static const WCHAR wildcards[] = {'*','?','\0'}; + if (WCMD_check_abort()) break; /* Stop all commands and exit */ thisCmdStart = cmdStart; itemNum++; @@ -2257,7 +2299,7 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) { if (hff != INVALID_HANDLE_VALUE) { do { BOOL isDirectory = FALSE; - + if (WCMD_check_abort()) break; /* Stop all commands and exit */ if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE; /* Handle as files or dirs appropriately, but ignore . and .. */ @@ -2348,6 +2390,7 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) { /* Read line by line until end of file */ while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) { + if (WCMD_check_abort()) break; /* Stop all commands and exit */ WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted, &forf_skip, forf_eol, forf_delims, forf_tokens); buffer[0] = 0; @@ -2403,6 +2446,8 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) { (numbers[1]<0)? i>=numbers[2] : i<=numbers[2]; i=i + numbers[1]) { + if (WCMD_check_abort()) break; /* Stop all commands and exit */ + sprintfW(thisNum, fmt, i); WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum)); @@ -2420,6 +2465,7 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) { } cmdEnd = thisCmdStart; } + if (WCMD_check_abort()) break; /* Stop all commands and exit */ /* If we are walking directories, move on to any which remain */ if (dirsToWalk != NULL) { @@ -2537,6 +2583,7 @@ void WCMD_goto (CMD_LIST **cmdList) { while (*paramStart && WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) { str = string; + if (WCMD_check_abort()) break; /* Stop all commands and exit */ /* Ignore leading whitespace or no-echo character */ while (*str=='@' || isspaceW (*str)) str++; @@ -2862,6 +2909,8 @@ void WCMD_move (void) WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName)); + if (WCMD_check_abort()) break; /* Stop all commands and exit */ + /* Build src & dest name */ strcpyW(src, drive); strcatW(src, dir); @@ -4343,6 +4392,8 @@ void WCMD_type (WCHAR *args) { WCHAR buffer[512]; DWORD count; + if (WCMD_check_abort()) break; /* Stop all commands and exit */ + if (!argN) break; WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg)); @@ -4358,6 +4409,7 @@ void WCMD_type (WCHAR *args) { WCMD_output(fmt, thisArg); } while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) { + if (WCMD_check_abort()) break; /* Stop all commands and exit */ if (count == 0) break; /* ReadFile reports success on EOF! */ buffer[count] = 0; WCMD_output_asis (buffer); @@ -4403,16 +4455,19 @@ void WCMD_more (WCHAR *args) { HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + + if (WCMD_check_abort()) return; /* Stop all commands and exit */ + WINE_TRACE("No parms - working probably in pipe mode\n"); SetStdHandle(STD_INPUT_HANDLE, hConIn); - /* Warning: No easy way of ending the stream (ctrl+z on windows) so - once you get in this bit unless due to a pipe, its going to end badly... */ wsprintfW(moreStrPage, moreFmt, moreStr); WCMD_enter_paged_mode(moreStrPage); while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) { - if (count == 0) break; /* ReadFile reports success on EOF! */ + if (((char*)buffer)[0] == 26 || ((char*)buffer)[0] == 4) break; + if (WCMD_check_abort()) break; /* Stop all commands and exit */ + if (count == 0) break; /* ReadFile reports success on EOF! */ buffer[count] = 0; WCMD_output_asis (buffer); } @@ -4426,6 +4481,8 @@ void WCMD_more (WCHAR *args) { } else { BOOL needsPause = FALSE; + if (WCMD_check_abort()) return; /* Stop all commands and exit */ + /* Loop through all args */ WINE_TRACE("Parms supplied - working through each file\n"); WCMD_enter_paged_mode(moreStrPage); @@ -4434,6 +4491,7 @@ void WCMD_more (WCHAR *args) { WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE); HANDLE h; + if (WCMD_check_abort()) break; /* Stop all commands and exit */ if (!argN) break; if (needsPause) { @@ -4465,7 +4523,8 @@ void WCMD_more (WCHAR *args) { needsPause = TRUE; while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) { - if (count == 0) break; /* ReadFile reports success on EOF! */ + if (WCMD_check_abort()) break; /* Stop all commands and exit */ + if (count == 0) break; /* ReadFile reports success on EOF! */ buffer[count] = 0; curPos += count; @@ -4833,3 +4892,19 @@ void WCMD_color (void) { SetConsoleTextAttribute(hStdOut, color); } } +/************************************************************************** + * WCMD_check_abort + * + * Check to see if abort requested. Pauses current command and asks + * user to input 'y' or 'n'. + * + * Aborting will return TRUE. + * + */ +BOOL WCMD_check_abort(void) { + BOOL retVal = FALSE; + EnterCriticalSection(&ctrlc_critsect); + retVal = ctrlc_stop; + LeaveCriticalSection(&ctrlc_critsect); + return retVal; +} diff --git a/programs/cmd/cmd.rc b/programs/cmd/cmd.rc index 69bafc6..29a1c24 100644 --- a/programs/cmd/cmd.rc +++ b/programs/cmd/cmd.rc @@ -356,4 +356,6 @@ Enter HELP for further information on any of the above commands.\n" WCMD_NOOPERATOR, "Expected an operator.\n" WCMD_BADPAREN, "Mismatch in parentheses.\n" WCMD_BADHEXOCT, "Badly formed number - must be one of decimal (12),\n hexadecimal (0x34) or octal (056).\n" + WCMD_STOPBATCH_PROMPT, "Do you want to stop the current batch script?" + WCMD_STOPBATCH_YES, "Stopping.\n" } diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index e5a0138..fbafb76 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -125,6 +125,8 @@ CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket, BOOL retryca void WCMD_free_commands(CMD_LIST *cmds); void WCMD_execute (const WCHAR *orig_command, const WCHAR *redirects, CMD_LIST **cmdList, BOOL retrycall); +BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText, BOOL *optionALL); +BOOL WCMD_check_abort(void); void *heap_alloc(size_t); @@ -174,6 +176,9 @@ struct env_stack BOOL delayedsubst; /* Is delayed substitution in effect */ }; +extern BOOL ctrlc_stop; /* Boolean to stop current command and exit. */ +extern CRITICAL_SECTION ctrlc_critsect; /* Critical section to pause code execution during Ctrl-C */ + /* Data structure to save setlocal and pushd information */ typedef struct _DIRECTORY_STACK @@ -324,3 +329,5 @@ extern WCHAR version_string[]; #define WCMD_NOOPERATOR 1043 #define WCMD_BADPAREN 1044 #define WCMD_BADHEXOCT 1045 +#define WCMD_STOPBATCH_PROMPT 1046 +#define WCMD_STOPBATCH_YES 1047 diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index a211efa..2fcfbbb 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -45,6 +45,9 @@ BOOL delayedsubst = FALSE; /* The current delayed substitution setting */ int defaultColor = 7; BOOL echo_mode = TRUE; +static CMD_LIST *toExecute = NULL; /* Commands left to be executed */ +BOOL ctrlc_stop = FALSE; +CRITICAL_SECTION ctrlc_critsect; WCHAR anykey[100], version_string[100]; const WCHAR newlineW[] = {'\r','\n','\0'}; @@ -2277,6 +2280,8 @@ CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket, CMD_LIST *origCmd = thisCmd; + if (WCMD_check_abort()) return thisCmd->nextcommand; /* Stop all commands and exit */ + /* If processing one bracket only, and we find the end bracket entry (or less), return */ if (oneBracket && !thisCmd->command && @@ -2320,6 +2325,53 @@ void WCMD_free_commands(CMD_LIST *cmds) { } } +/*************************************************************************** + * ConsoleCtrlHandler + * + * Handle Ctrl-C and Ctrl-Break events. + */ +BOOL ConsoleCtrlHandler(DWORD fdwCtrlType) +{ + BOOL doCancel = TRUE; + + if (fdwCtrlType == CTRL_C_EVENT || fdwCtrlType == CTRL_BREAK_EVENT) + { + if (interactive && toExecute == NULL) return TRUE; + + EnterCriticalSection(&ctrlc_critsect); + + if (context) + { + doCancel = WCMD_ask_confirm(WCMD_LoadMessage(WCMD_STOPBATCH_PROMPT), FALSE, NULL); + } + + if (doCancel) + { + if (interactive) /* interactive command line */ + { + WINE_TRACE("Ctrl-C interrupt: interactive mode\n"); + } + else if (context) /* batch program */ + { + context->skip_rest = TRUE; + context->toExecute = NULL; + WCMD_output(WCMD_LoadMessage(WCMD_STOPBATCH_YES)); + WINE_TRACE("Ctrl-C interrupt: batch context mode\n"); + } + else if (!interactive && !context) /* cmd /c or cmd /k */ + { + WINE_TRACE("Ctrl-C interrupt: cmd /c or cmd /k modes\n"); + } + ctrlc_stop = TRUE; + } + + LeaveCriticalSection(&ctrlc_critsect); + + return TRUE; + } + + return FALSE; +} /***************************************************************************** * Main entry point. This is a console application so we have a main() not a @@ -2339,7 +2391,6 @@ int wmain (int argc, WCHAR *argvW[]) static const WCHAR offW[] = {'O','F','F','\0'}; static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'}; static const WCHAR defaultpromptW[] = {'$','P','$','G','\0'}; - CMD_LIST *toExecute = NULL; /* Commands left to be executed */ OSVERSIONINFOW osv; char osver[50]; @@ -2358,6 +2409,17 @@ int wmain (int argc, WCHAR *argvW[]) LocalFree(cmd); cmd = NULL; + /* Handle Ctrl-C and Ctrl-Break keyboard interrupts */ + InitializeCriticalSection(&ctrlc_critsect); + if (SetConsoleCtrlHandler((PHANDLER_ROUTINE)ConsoleCtrlHandler, TRUE)) + { + WINE_TRACE("cmd: Ctrl-C control handler was installed.\n"); + } + else + { + WINE_ERR("cmd: Unable to install Ctrl-C control handler.\n"); + } + /* Can't use argc/argv as it will have stripped quotes from parameters * meaning cmd.exe /C echo "quoted string" is impossible */ @@ -2697,6 +2759,7 @@ int wmain (int argc, WCHAR *argvW[]) WCMD_process_commands(toExecute, FALSE, FALSE); WCMD_free_commands(toExecute); toExecute = NULL; + if (ctrlc_stop != FALSE) ctrlc_stop = FALSE; } return 0; } -- 1.7.10.4