Allow the user to press Ctrl-C to abort lengthy DIR (or DIR /p, etc.) operations.
-- v13: programs/cmd: Implement ability to abort lengthy directory operations via Ctrl-C.
From: Joe Souza jsouza@yahoo.com
--- programs/cmd/directory.c | 81 +++++++++++++++++++++++----------------- programs/cmd/wcmd.h | 2 + programs/cmd/wcmdmain.c | 19 ++++++++++ 3 files changed, 68 insertions(+), 34 deletions(-)
diff --git a/programs/cmd/directory.c b/programs/cmd/directory.c index 3a2a5955d12..b7c036efd0d 100644 --- a/programs/cmd/directory.c +++ b/programs/cmd/directory.c @@ -241,7 +241,7 @@ static void WCMD_getfileowner(WCHAR *filename, WCHAR *owner, int ownerlen) { * FIXME: Assumes 24-line display for the /P qualifier. */
-static DIRECTORY_STACK *WCMD_list_directory (DIRECTORY_STACK *inputparms, int level) { +static RETURN_CODE WCMD_list_directory (DIRECTORY_STACK *inputparms, int level, DIRECTORY_STACK **outputparms) {
WCHAR string[1024], datestring[32], timestring[32]; WCHAR real_path[MAX_PATH]; @@ -256,6 +256,7 @@ static DIRECTORY_STACK *WCMD_list_directory (DIRECTORY_STACK *inputparms, int le DIRECTORY_STACK *parms; int concurrentDirs = 0; BOOL done_header = FALSE; + RETURN_CODE return_code = NO_ERROR;
dir_count = 0; file_count = 0; @@ -333,7 +334,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 && return_code == NO_ERROR; rows++) { BOOL addNewLine = TRUE; for (cols=0; cols<numCols; cols++) { WCHAR username[24]; @@ -428,9 +429,16 @@ 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. + * Don't overwrite any existing error code. + */ + if (return_code == NO_ERROR) { + return_code = WCMD_ctrlc_status(); + } }
- if (!bare) { + if (!bare && return_code == NO_ERROR) { if (file_count == 1) { WCMD_output (L" 1 file %1!25s! bytes\n", WCMD_filesize64 (byte_count.QuadPart)); } @@ -442,7 +450,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 && return_code == NO_ERROR) { if (dir_count == 1) { WCMD_output (L"%1!8d! directory ", 1); } else { @@ -453,7 +461,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 && return_code == NO_ERROR) { DIRECTORY_STACK *dirStack = NULL; DIRECTORY_STACK *lastEntry = NULL; WIN32_FIND_DATAW finddata; @@ -498,9 +506,10 @@ static DIRECTORY_STACK *WCMD_list_directory (DIRECTORY_STACK *inputparms, int le } while (FindNextFileW(hff, &finddata) != 0); FindClose (hff);
- while (dirStack != NULL) { + while (dirStack != NULL && return_code == NO_ERROR) { DIRECTORY_STACK *thisDir = dirStack; - dirStack = WCMD_list_directory (thisDir, 1); + return_code = WCMD_list_directory (thisDir, 1, &dirStack); + if (return_code != NO_ERROR) dirStack = NULL; while (thisDir != dirStack) { DIRECTORY_STACK *tempDir = thisDir->next; free(thisDir->dirName); @@ -514,12 +523,13 @@ static DIRECTORY_STACK *WCMD_list_directory (DIRECTORY_STACK *inputparms, int le
/* Handle case where everything is filtered out */ if ((file_total + dir_total == 0) && (level == 0)) { - SetLastError (ERROR_FILE_NOT_FOUND); + return_code = ERROR_FILE_NOT_FOUND; + SetLastError (return_code); WCMD_print_error (); - errorlevel = ERROR_INVALID_FUNCTION; }
- return parms; + *outputparms = parms; + return return_code; }
/***************************************************************************** @@ -535,7 +545,7 @@ static void WCMD_dir_trailer(const WCHAR *path) { WINE_TRACE("Writing trailer for '%s' gave %d(%ld)\n", wine_dbgstr_w(path), status, GetLastError());
- if (errorlevel == NO_ERROR && !bare) { + if (!bare) { if (recurse) { WCMD_output (L"\n Total files listed:\n%1!8d! files%2!25s! bytes\n", file_total, WCMD_filesize64 (byte_total)); WCMD_output (L"%1!8d! directories %2!18s! bytes free\n\n", dir_total, WCMD_filesize64 (freebytes.QuadPart)); @@ -672,8 +682,7 @@ RETURN_CODE WCMD_directory(WCHAR *args) WCHAR fname[MAX_PATH]; WCHAR ext[MAX_PATH]; unsigned num_empty = 0, num_with_data = 0; - - errorlevel = NO_ERROR; + RETURN_CODE return_code = NO_ERROR;
/* Prefill quals with (uppercased) DIRCMD env var */ if (GetEnvironmentVariableW(L"DIRCMD", string, ARRAY_SIZE(string))) { @@ -757,9 +766,9 @@ RETURN_CODE WCMD_directory(WCHAR *args) dirTime = Written; p = p - 1; /* So when step on, move to '/' */ } else { - SetLastError(ERROR_INVALID_PARAMETER); + SetLastError(return_code = ERROR_INVALID_PARAMETER); WCMD_print_error(); - return errorlevel = ERROR_INVALID_FUNCTION; + goto exit; } break; case 'O': p = p + 1; @@ -779,9 +788,9 @@ RETURN_CODE WCMD_directory(WCHAR *args) break; case 'G': orderGroupDirs = TRUE; break; default: - SetLastError(ERROR_INVALID_PARAMETER); + SetLastError(return_code = ERROR_INVALID_PARAMETER); WCMD_print_error(); - return errorlevel = ERROR_INVALID_FUNCTION; + goto exit; } p++; } @@ -816,9 +825,9 @@ RETURN_CODE WCMD_directory(WCHAR *args) case 'R': mask = FILE_ATTRIBUTE_READONLY; break; case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break; default: - SetLastError(ERROR_INVALID_PARAMETER); + SetLastError(return_code = ERROR_INVALID_PARAMETER); WCMD_print_error(); - return errorlevel = ERROR_INVALID_FUNCTION; + goto exit; }
/* Keep running list of bits we care about */ @@ -834,9 +843,9 @@ RETURN_CODE WCMD_directory(WCHAR *args) WINE_TRACE("Result: showattrs %lx, bits %lx\n", showattrs, attrsbits); break; default: - SetLastError(ERROR_INVALID_PARAMETER); + SetLastError(return_code = ERROR_INVALID_PARAMETER); WCMD_print_error(); - return errorlevel = ERROR_INVALID_FUNCTION; + goto exit; } p = p + 1; } @@ -950,7 +959,7 @@ RETURN_CODE WCMD_directory(WCHAR *args) thisEntry = fullParms; trailerReqd = FALSE;
- while (thisEntry != NULL) { + while (thisEntry != NULL && return_code == NO_ERROR) {
/* Output disk free (trailer) and volume information (header) if the drive letter changes */ @@ -959,7 +968,8 @@ RETURN_CODE WCMD_directory(WCHAR *args) /* Trailer Information */ if (lastDrive != '?') { trailerReqd = FALSE; - WCMD_dir_trailer(prevEntry->dirName); + if (return_code == NO_ERROR) + WCMD_dir_trailer(prevEntry->dirName); byte_total = file_total = dir_total = 0; }
@@ -974,7 +984,7 @@ RETURN_CODE WCMD_directory(WCHAR *args) drive[3] = L'\0'; trailerReqd = TRUE; if (!WCMD_print_volume_information(drive)) { - errorlevel = ERROR_INVALID_FUNCTION; + return_code = ERROR_INVALID_PARAMETER; goto exit; } } @@ -982,24 +992,27 @@ RETURN_CODE WCMD_directory(WCHAR *args) if (!bare) WCMD_output_asis (L"\n\n"); }
- /* Clear any errors from previous invocations, and process it */ - errorlevel = NO_ERROR; prevEntry = thisEntry; - thisEntry = WCMD_list_directory (thisEntry, 0); - if (errorlevel) - num_empty++; + return_code = WCMD_list_directory (thisEntry, 0, &thisEntry); + if (return_code == ERROR_FILE_NOT_FOUND) + num_empty++; else - num_with_data++; + num_with_data++; }
/* Trailer Information */ - if (trailerReqd) { + if (trailerReqd && return_code == NO_ERROR) { WCMD_dir_trailer(prevEntry->dirName); }
- if (num_empty && !num_with_data) - errorlevel = ERROR_INVALID_FUNCTION; exit: + if (return_code == STATUS_CONTROL_C_EXIT) + errorlevel = ERROR_INVALID_FUNCTION; + else if (return_code != NO_ERROR || (num_empty && !num_with_data)) + return_code = errorlevel = ERROR_INVALID_FUNCTION; + else + errorlevel = NO_ERROR; + if (paged_mode) WCMD_leave_paged_mode();
/* Free storage allocated for parms */ @@ -1011,5 +1024,5 @@ exit: free(prevEntry); }
- return errorlevel; + return return_code; } 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 Mon Feb 17 20:04:17 2025 +0000, Joe Souza wrote:
Thank you for the patch. I will try to get that integrated sometime today. I'll be traveling for the next few days and will have limited ability during that time to address further concerns. Automatic vs. manual reset were based on my thinking about whether the function WCMD_ctrlc_status would be called more than once per command iteration. If called only once then ownership of the status passes to the caller and thus automatic reset is cleaner. However, thinking further about this, it probably makes sense to switch back to manual reset, in case future callers (i.e. perhaps for different internal commands, etc.) might want to call it more than once per iteration. That, and the fact that there is a manual reset anyway before command launch in the loop toward the end of wmain suggests that a manual reset would be better, so I will switch back when I submit the changes containing your patch.
OK, your patch and changing the event from automatic back to manual reset have been integrated with the merge request.