Allow the user to press Ctrl-C to abort lengthy DIR (or DIR /p, etc.) operations.
-- v2: programs/cmd: Implement ability to abort lengthy directory operations via Ctrl-C.
From: Joe Souza jsouza@yahoo.com
--- programs/cmd/directory.c | 15 +++++++++------ programs/cmd/wcmd.h | 2 ++ programs/cmd/wcmdmain.c | 19 +++++++++++++++++++ 3 files changed, 30 insertions(+), 6 deletions(-)
diff --git a/programs/cmd/directory.c b/programs/cmd/directory.c index 3a2a5955d12..a378c205e5a 100644 --- a/programs/cmd/directory.c +++ b/programs/cmd/directory.c @@ -333,7 +333,7 @@ static DIRECTORY_STACK *WCMD_list_directory (DIRECTORY_STACK *inputparms, int le } WINE_TRACE("cols=%d, rows=%d\n", numCols, numRows);
- for (rows=0; rows<numRows; rows++) { + for (rows=0; rows<numRows && errorlevel == NO_ERROR; rows++) { BOOL addNewLine = TRUE; for (cols=0; cols<numCols; cols++) { WCHAR username[24]; @@ -428,9 +428,12 @@ static DIRECTORY_STACK *WCMD_list_directory (DIRECTORY_STACK *inputparms, int le } if (addNewLine) WCMD_output_asis(L"\r\n"); cur_width = 0; + + /* Allow command to be aborted if user presses Ctrl-C. */ + errorlevel = WCMD_ctrlc_status(); }
- if (!bare) { + if (!bare && errorlevel == NO_ERROR) { if (file_count == 1) { WCMD_output (L" 1 file %1!25s! bytes\n", WCMD_filesize64 (byte_count.QuadPart)); } @@ -442,7 +445,7 @@ static DIRECTORY_STACK *WCMD_list_directory (DIRECTORY_STACK *inputparms, int le file_total = file_total + file_count; dir_total = dir_total + dir_count;
- if (!bare && !recurse) { + if (!bare && !recurse && errorlevel == NO_ERROR) { if (dir_count == 1) { WCMD_output (L"%1!8d! directory ", 1); } else { @@ -453,7 +456,7 @@ static DIRECTORY_STACK *WCMD_list_directory (DIRECTORY_STACK *inputparms, int le free(fd);
/* When recursing, look in all subdirectories for matches */ - if (recurse) { + if (recurse && errorlevel == NO_ERROR) { DIRECTORY_STACK *dirStack = NULL; DIRECTORY_STACK *lastEntry = NULL; WIN32_FIND_DATAW finddata; @@ -950,7 +953,7 @@ RETURN_CODE WCMD_directory(WCHAR *args) thisEntry = fullParms; trailerReqd = FALSE;
- while (thisEntry != NULL) { + while (thisEntry != NULL && errorlevel != STATUS_CONTROL_C_EXIT) {
/* Output disk free (trailer) and volume information (header) if the drive letter changes */ @@ -986,7 +989,7 @@ RETURN_CODE WCMD_directory(WCHAR *args) errorlevel = NO_ERROR; prevEntry = thisEntry; thisEntry = WCMD_list_directory (thisEntry, 0); - if (errorlevel) + if (errorlevel && errorlevel != STATUS_CONTROL_C_EXIT) num_empty++; else num_with_data++; diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index caacd44995d..225ebd44310 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -224,6 +224,8 @@ RETURN_CODE WCMD_run_builtin_command(int cmd_index, WCHAR *cmd); BOOL WCMD_find_label(HANDLE h, const WCHAR*, LARGE_INTEGER *pos); void WCMD_set_label_end(WCHAR *string);
+RETURN_CODE WCMD_ctrlc_status(void); + void *xrealloc(void *, size_t) __WINE_ALLOC_SIZE(2) __WINE_DEALLOC(free);
static inline void *xalloc(size_t sz) __WINE_MALLOC; diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 223bb55d2c6..77f1112be23 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -57,6 +57,8 @@ static int max_height; static int max_width; static int numChars;
+static HANDLE control_c_event; + #define MAX_WRITECONSOLE_SIZE 65535
/* @@ -3848,9 +3850,19 @@ RETURN_CODE node_execute(CMD_NODE *node) return return_code; }
+ +RETURN_CODE WCMD_ctrlc_status(void) +{ + return (WAIT_OBJECT_0 == WaitForSingleObject(control_c_event, 0)) ? STATUS_CONTROL_C_EXIT : NO_ERROR; +} + static BOOL WINAPI my_event_handler(DWORD ctrl) { WCMD_output(L"\n"); + if (ctrl == CTRL_C_EVENT) + { + SetEvent(control_c_event); + } return ctrl == CTRL_C_EVENT; }
@@ -3901,6 +3913,11 @@ int __cdecl wmain (int argc, WCHAR *argvW[]) forloopcontext = NULL; WCMD_save_for_loop_context(TRUE);
+ /* Initialize the event here because the command loop at the bottom will + * reset it unconditionally even if the Control-C handler is not installed. + */ + control_c_event = CreateEventW(NULL, TRUE, FALSE, NULL); + /* Can't use argc/argv as it will have stripped quotes from parameters * meaning cmd.exe /C echo "quoted string" is impossible */ @@ -4234,10 +4251,12 @@ int __cdecl wmain (int argc, WCHAR *argvW[]) { if (rpl_status == RPL_SUCCESS && toExecute) { + ResetEvent(control_c_event); node_execute(toExecute); node_dispose_tree(toExecute); if (echo_mode) WCMD_output_asis(L"\r\n"); } } + CloseHandle(control_c_event); return 0; }
On Thu Feb 13 19:18:24 2025 +0000, eric pouech wrote:
yes, that what I meant... you spelled it way clearer <g>
Great, thank you.
OK, I think all issues are fixed and errorlevel returned seems to be the correct value now when Ctrl-C aborts the listing. Let me know what you think.
thanks for the update...
that's better however, that's not exactly what should be done
some background first:
- cmd.exe for its builtin commands use a RETURN_CODE; this return code gets the status of the command execution (NO_ERROR success, error codes, but also propagating an 'EXIT /B' return value...) - each command eventually sets the ERRORLEVEL based on the RETURN_CODE (some builtin commands always do, some only do it in case of != 0 return code - and leave ERRORLEVEL untouched in case of success -, and some even have a different behavior if run from a .BAT or a .CMD command file) - the return code is used as a basis in command chaining (esp. && and || with success/failure of LHS to decide or not to execute RHS)
note: this some evolution in code base that has been started last year and the target is that all builtin implementation follow that scheme. not all of them have been fully migrated
from what's tested DIR seem to always set the ERRORLEVEL to the return_code value (file not found...)
but in case of ctrl-c, the return code shall be STATUS_CTRL_C_EXIT and errorlevel 1 (we'll need the return_code to be STATUS_CTRL_C_EXIT so that upper functions can decide to implement the 'Terminate batch job (Y/N)' at some point)
so we globally need to invert the logic: in WCMD_dir and its helpers, always set the return_code, and set the errorlevel at WCMD_dir exit based on return_code value
in this case, you should:
- have a local var `RETURN_CODE return_code; `in WCMD_list_directory - use `return_code == NO_ERROR` to continue looping (and only this pattern as we could in later patches have more return codes stored in return_code) - and return the return_code from the helper into the WCMD_dir (which shall then break the loop, free all directory structures and finally set error level based on return code)
simple test (by hand, should be harder to integrate in test suite):
`dir /s *.* || echo foobar`
interrupt with ctrl-c, shall print `foobar` at the end