This is part XXII of cmd.exe engine rewrite.
It tackles bug id 55151. The root cause is that native doesn't keep the batch/command file opened while processing each command in that file.
This allows: - the batch file to delete itself (described in here above mentionned bug report) - but also to modify the batch file on the fly...
Quite a bit of refactoring to no longer depend on the file handle as a reference the batch file.
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/tests/test_builtins.cmd | 6 ++++++ programs/cmd/tests/test_builtins.cmd.exp | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd index 945b68e25a9..e8a2cadd515 100644 --- a/programs/cmd/tests/test_builtins.cmd +++ b/programs/cmd/tests/test_builtins.cmd @@ -3911,6 +3911,12 @@ echo abc > 012345678901234 if exist 012345678901234 (echo Failure) else echo Success popd rmdir /s /q c:\abcdefghij +echo ---- Testing nasty bits ---- +echo erase /q foobar.bat > foobar.bat +echo echo shouldnot >> foobar.bat +call foobar.bat +if exist foobar.bat (echo stillthere & erase /q foobar.bat >NUL) + echo ------------ Testing combined CALLs/GOTOs ------------ echo @echo off>foo.cmd echo goto :eof>>foot.cmd diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index 03bcb674bdb..6195e8b7644 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -2009,7 +2009,8 @@ Normal+tab+garbage @todo_wine@21 Success @todo_wine@Success ------------- Testing combined CALLs/GOTOs ------------ +---- Testing nasty bits ---- +@todo_wine@------------ Testing combined CALLs/GOTOs ------------ world cheball barbare
From: Eric Pouech epouech@codeweavers.com
No longer using batch file HANDLE as a key to join the two.
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/batch.c | 25 +++++++------------------ programs/cmd/builtins.c | 6 +++--- programs/cmd/wcmd.h | 33 +++++++++++++++++---------------- 3 files changed, 27 insertions(+), 37 deletions(-)
diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c index d2ad690967d..776f2ea326f 100644 --- a/programs/cmd/batch.c +++ b/programs/cmd/batch.c @@ -22,8 +22,6 @@ #include "wcmd.h" #include "wine/debug.h"
-extern struct env_stack *saved_environment; - WINE_DEFAULT_DEBUG_CHANNEL(cmd);
/**************************************************************************** @@ -68,12 +66,12 @@ RETURN_CODE WCMD_batch(const WCHAR *file, WCHAR *command, const WCHAR *startLabe
prev_context = context; context = LocalAlloc (LMEM_FIXED, sizeof (BATCH_CONTEXT)); - context -> h = h; + context->h = h; context->batchfileW = xstrdupW(file); - context -> command = command; - memset(context -> shift_count, 0x00, sizeof(context -> shift_count)); - context -> prev_context = prev_context; - context -> skip_rest = FALSE; + context->command = command; + memset(context->shift_count, 0x00, sizeof(context->shift_count)); + context->prev_context = prev_context; + context->skip_rest = FALSE;
/* If processing a call :label, 'goto' the label in question */ if (startLabel) { @@ -106,17 +104,8 @@ RETURN_CODE WCMD_batch(const WCHAR *file, WCHAR *command, const WCHAR *startLabe } CloseHandle (h);
-/* - * If there are outstanding setlocal's to the current context, unwind them. - */ - while (saved_environment && saved_environment->batchhandle == context->h) { - WCMD_endlocal(); - } - -/* - * If invoked by a CALL, we return to the context of our caller. Otherwise return - * to the caller's caller. - */ + /* If there are outstanding setlocal's to the current context, unwind them. */ + while (WCMD_endlocal() == NO_ERROR) {}
free(context->batchfileW); LocalFree(context); diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index 2654ad750fa..a728c4141b9 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -94,7 +94,7 @@ static const WCHAR externals[][10] = { };
static HINSTANCE hinst; -struct env_stack *saved_environment; +static struct env_stack *saved_environment; static BOOL verify_mode = FALSE;
/* set /a routines work from single character operators, but some of the @@ -2372,7 +2372,7 @@ RETURN_CODE WCMD_setlocal(WCHAR *args) env_copy->strings = WCMD_dupenv (env); if (env_copy->strings) { - env_copy->batchhandle = context->h; + env_copy->context = context; env_copy->next = saved_environment; env_copy->delayedsubst = delayedsubst; delayedsubst = newdelay; @@ -2407,7 +2407,7 @@ RETURN_CODE WCMD_endlocal(void)
/* setlocal needs a saved environment from within the same context (batch program) as it was saved in */ - if (!saved_environment || saved_environment->batchhandle != context->h) + if (!saved_environment || saved_environment->context != context) return ERROR_INVALID_FUNCTION;
/* pop the old environment from the stack */ diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index c800da59821..9f983160b06 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -253,28 +253,29 @@ int evaluate_if_condition(WCHAR *p, WCHAR **command, int *test, int *negate);
/* Data structure to hold context when executing batch files */
-typedef struct _BATCH_CONTEXT { - WCHAR *command; /* The command which invoked the batch file */ - HANDLE h; /* Handle to the open batch file */ - WCHAR *batchfileW; /* Name of same */ - int shift_count[10]; /* Offset in terms of shifts for %0 - %9 */ - struct _BATCH_CONTEXT *prev_context; /* Pointer to the previous context block */ - BOOL skip_rest; /* Skip the rest of the batch program and exit */ - CMD_NODE *toExecute; /* Commands left to be executed */ +typedef struct _BATCH_CONTEXT +{ + WCHAR *command; /* The command which invoked the batch file */ + HANDLE h; /* Handle to the open batch file */ + WCHAR *batchfileW; /* Name of same */ + int shift_count[10]; /* Offset in terms of shifts for %0 - %9 */ + struct _BATCH_CONTEXT *prev_context; /* Pointer to the previous context block */ + BOOL skip_rest; /* Skip the rest of the batch program and exit */ } BATCH_CONTEXT;
/* Data structure to handle building lists during recursive calls */
struct env_stack { - struct env_stack *next; - union { - int stackdepth; /* Only used for pushd and popd */ - WCHAR cwd; /* Only used for set/endlocal */ - } u; - WCHAR *strings; - HANDLE batchhandle; /* Used to ensure set/endlocals stay in scope */ - BOOL delayedsubst; /* Is delayed substitution in effect */ + BATCH_CONTEXT *context; + struct env_stack *next; + union + { + int stackdepth; /* Only used for pushd and popd */ + WCHAR cwd; /* Only used for set/endlocal */ + } u; + WCHAR *strings; + BOOL delayedsubst; /* Is delayed substitution in effect */ };
/* Data structure to save setlocal and pushd information */
From: Eric Pouech epouech@codeweavers.com
Separating the cases of: - calling into a new batch file, - calling a label inside current batch file.
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/batch.c | 133 +++++++++++++++++++++------------------- programs/cmd/wcmd.h | 3 +- programs/cmd/wcmdmain.c | 2 +- 3 files changed, 73 insertions(+), 65 deletions(-)
diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c index 776f2ea326f..4416dab1705 100644 --- a/programs/cmd/batch.c +++ b/programs/cmd/batch.c @@ -24,6 +24,35 @@
WINE_DEFAULT_DEBUG_CHANNEL(cmd);
+static RETURN_CODE WCMD_batch_main_loop(void) +{ + RETURN_CODE return_code = NO_ERROR; + /* Work through the file line by line until an exit is called. */ + while (!context->skip_rest) + { + CMD_NODE *node; + + switch (WCMD_ReadAndParseLine(NULL, &node, context->h)) + { + case RPL_EOF: + context->skip_rest = TRUE; + break; + case RPL_SUCCESS: + return_code = node_execute(node); + node_dispose_tree(node); + break; + case RPL_SYNTAXERROR: + return_code = RETURN_CODE_SYNTAX_ERROR; + break; + } + } + + /* If there are outstanding setlocal's to the current context, unwind them. */ + while (WCMD_endlocal() == NO_ERROR) {} + + return return_code; +} + /**************************************************************************** * WCMD_batch * @@ -40,78 +69,38 @@ WINE_DEFAULT_DEBUG_CHANNEL(cmd); * a label to goto once opened. */
-RETURN_CODE WCMD_batch(const WCHAR *file, WCHAR *command, const WCHAR *startLabel, HANDLE pgmHandle) +RETURN_CODE WCMD_call_batch(const WCHAR *file, WCHAR *command) { - HANDLE h = INVALID_HANDLE_VALUE; - BATCH_CONTEXT *prev_context; - RETURN_CODE return_code = NO_ERROR; + HANDLE h = INVALID_HANDLE_VALUE; + BATCH_CONTEXT *prev_context; + RETURN_CODE return_code = NO_ERROR;
- if (startLabel == NULL) { h = CreateFileW (file, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (h == INVALID_HANDLE_VALUE) { - SetLastError (ERROR_FILE_NOT_FOUND); - WCMD_print_error (); - return ERROR_INVALID_FUNCTION; + SetLastError (ERROR_FILE_NOT_FOUND); + WCMD_print_error (); + return ERROR_INVALID_FUNCTION; } - } else { - DuplicateHandle(GetCurrentProcess(), pgmHandle, - GetCurrentProcess(), &h, - 0, FALSE, DUPLICATE_SAME_ACCESS); - }
-/* - * Create a context structure for this batch file. - */ + /* Create a context structure for this batch file. */ + prev_context = context; + context = malloc(sizeof (BATCH_CONTEXT)); + context->h = h; + context->batchfileW = xstrdupW(file); + context->command = command; + memset(context->shift_count, 0x00, sizeof(context->shift_count)); + context->prev_context = prev_context; + context->skip_rest = FALSE;
- prev_context = context; - context = LocalAlloc (LMEM_FIXED, sizeof (BATCH_CONTEXT)); - context->h = h; - context->batchfileW = xstrdupW(file); - context->command = command; - memset(context->shift_count, 0x00, sizeof(context->shift_count)); - context->prev_context = prev_context; - context->skip_rest = FALSE; - - /* If processing a call :label, 'goto' the label in question */ - if (startLabel) { - lstrcpyW(param1, startLabel); - WCMD_goto(); - } + return_code = WCMD_batch_main_loop(); + CloseHandle (h);
-/* - * Work through the file line by line. Specific batch commands are processed here, - * the rest are handled by the main command processor. - */ - - while (!context->skip_rest) - { - CMD_NODE *node; - - switch (WCMD_ReadAndParseLine(NULL, &node, h)) - { - case RPL_EOF: - context->skip_rest = TRUE; - break; - case RPL_SUCCESS: - return_code = node_execute(node); - node_dispose_tree(node); - break; - case RPL_SYNTAXERROR: - return_code = RETURN_CODE_SYNTAX_ERROR; - break; - } - } - CloseHandle (h); + free(context->batchfileW); + free(context); + context = prev_context;
- /* If there are outstanding setlocal's to the current context, unwind them. */ - while (WCMD_endlocal() == NO_ERROR) {} - - free(context->batchfileW); - LocalFree(context); - context = prev_context; - - return return_code; + return return_code; }
/******************************************************************* @@ -671,6 +660,7 @@ RETURN_CODE WCMD_call(WCHAR *command) { LARGE_INTEGER li; WCHAR gotoLabel[MAX_PATH]; + BATCH_CONTEXT *prev_context;
lstrcpyW(gotoLabel, param1);
@@ -683,7 +673,24 @@ RETURN_CODE WCMD_call(WCHAR *command) li.QuadPart = 0; li.u.LowPart = SetFilePointer(context->h, li.u.LowPart, &li.u.HighPart, FILE_CURRENT); - WCMD_batch(context->batchfileW, buffer, gotoLabel, context->h); + + prev_context = context; + context = malloc(sizeof (BATCH_CONTEXT)); + context->h = prev_context->h; + context->batchfileW = prev_context->batchfileW; + context->command = buffer; + memset(context->shift_count, 0x00, sizeof(context->shift_count)); + context->prev_context = prev_context; + context->skip_rest = FALSE; + + /* FIXME as commands here can temper with param1 global variable (ugly) */ + lstrcpyW(param1, gotoLabel); + WCMD_goto(); + + WCMD_batch_main_loop(); + + free(context); + context = prev_context; return_code = errorlevel; SetFilePointer(context->h, li.u.LowPart, &li.u.HighPart, FILE_BEGIN);
diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index 9f983160b06..4fad2884e1e 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -155,7 +155,6 @@ typedef int RETURN_CODE; BOOL WCMD_print_volume_information(const WCHAR *);
RETURN_CODE WCMD_assoc(const WCHAR *, BOOL); -RETURN_CODE WCMD_batch(const WCHAR *, WCHAR *, const WCHAR *, HANDLE); RETURN_CODE WCMD_call(WCHAR *command); RETURN_CODE WCMD_choice(WCHAR *); RETURN_CODE WCMD_clear_screen(void); @@ -221,6 +220,8 @@ enum read_parse_line WCMD_ReadAndParseLine(const WCHAR *initialcmd, CMD_NODE **o void node_dispose_tree(CMD_NODE *cmds); RETURN_CODE node_execute(CMD_NODE *node);
+RETURN_CODE WCMD_call_batch(const WCHAR *, WCHAR *); + 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 d3ad93837d4..61d5c0fcbd7 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -1583,7 +1583,7 @@ RETURN_CODE WCMD_run_program(WCHAR *command, BOOL called) BOOL oldinteractive = interactive;
interactive = FALSE; - return_code = WCMD_batch(thisDir, command, NULL, INVALID_HANDLE_VALUE); + return_code = WCMD_call_batch(thisDir, command); interactive = oldinteractive; if (context && !called) { TRACE("Batch completed, but was not 'called' so skipping outer batch too\n");
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/batch.c | 64 +++++++++++++++++++++++++ programs/cmd/builtins.c | 104 ++++++++++------------------------------ programs/cmd/wcmd.h | 2 + 3 files changed, 91 insertions(+), 79 deletions(-)
diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c index 4416dab1705..5ed7dda871e 100644 --- a/programs/cmd/batch.c +++ b/programs/cmd/batch.c @@ -702,3 +702,67 @@ RETURN_CODE WCMD_call(WCHAR *command) } return return_code; } + +void WCMD_set_label_end(WCHAR *string) +{ + static const WCHAR labelEndsW[] = L"><|& :\t"; + WCHAR *p; + + /* Label ends at whitespace or redirection characters */ + if ((p = wcspbrk(string, labelEndsW))) *p = L'\0'; +} + +static BOOL find_next_label(HANDLE h, ULONGLONG end, WCHAR candidate[MAXSTRING]) +{ + while (WCMD_fgets(candidate, MAXSTRING, h)) + { + WCHAR *str = candidate; + + /* Ignore leading whitespace or no-echo character */ + while (*str == L'@' || iswspace(*str)) str++; + + /* If the first real character is a : then this is a label */ + if (*str == L':') + { + /* Skip spaces between : and label */ + for (str++; iswspace(*str); str++) {} + memmove(candidate, str, (wcslen(str) + 1) * sizeof(WCHAR)); + WCMD_set_label_end(candidate); + + return TRUE; + } + if (end) + { + LARGE_INTEGER li = {.QuadPart = 0}, curli; + if (!SetFilePointerEx(h, li, &curli, FILE_CURRENT)) return FALSE; + if (curli.QuadPart > end) break; + } + } + return FALSE; +} + +BOOL WCMD_find_label(HANDLE h, const WCHAR *label, LARGE_INTEGER *pos) +{ + LARGE_INTEGER where = *pos, zeroli = {.QuadPart = 0}; + WCHAR candidate[MAXSTRING]; + + if (!*label) return FALSE; + + if (!SetFilePointerEx(h, *pos, NULL, FILE_BEGIN)) return FALSE; + while (find_next_label(h, ~(ULONGLONG)0, candidate)) + { + TRACE("comparing found label %s\n", wine_dbgstr_w(candidate)); + if (!lstrcmpiW(candidate, label)) + return SetFilePointerEx(h, zeroli, pos, FILE_CURRENT); + } + TRACE("Label not found, trying from beginning of file\n"); + if (!SetFilePointerEx(h, zeroli, NULL, FILE_BEGIN)) return FALSE; + while (find_next_label(h, where.QuadPart, candidate)) + { + TRACE("comparing found label %s\n", wine_dbgstr_w(candidate)); + if (!lstrcmpiW(candidate, label)) + return SetFilePointerEx(h, zeroli, pos, FILE_CURRENT); + } + TRACE("Reached wrap point, label not found\n"); + return FALSE; +} diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index a728c4141b9..4eec2119462 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -1815,89 +1815,35 @@ RETURN_CODE WCMD_give_help(WCHAR *args)
RETURN_CODE WCMD_goto(void) { - WCHAR string[MAX_PATH]; - WCHAR *labelend = NULL; - const WCHAR labelEndsW[] = L"><|& :\t"; - - if (context != NULL) { - WCHAR *paramStart = param1, *str; - - if (param1[0] == 0x00) { - WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG)); - return ERROR_INVALID_FUNCTION; - } - - /* Handle special :EOF label */ - if (lstrcmpiW(L":eof", param1) == 0) { - context -> skip_rest = TRUE; - return RETURN_CODE_ABORTED; - } - - /* Support goto :label as well as goto label plus remove trailing chars */ - if (*paramStart == ':') paramStart++; - labelend = wcspbrk(paramStart, labelEndsW); - if (labelend) *labelend = 0x00; - WINE_TRACE("goto label: '%s'\n", wine_dbgstr_w(paramStart)); - - /* Loop through potentially twice - once from current file position - through to the end, and second time from start to current file - position */ - if (*paramStart) { - int loop; - LARGE_INTEGER startli; - for (loop=0; loop<2; loop++) { - if (loop==0) { - /* On first loop, save the file size */ - startli.QuadPart = 0; - startli.u.LowPart = SetFilePointer(context -> h, startli.u.LowPart, - &startli.u.HighPart, FILE_CURRENT); - } else { - /* On second loop, start at the beginning of the file */ - WINE_TRACE("Label not found, trying from beginning of file\n"); - if (loop==1) SetFilePointer (context -> h, 0, NULL, FILE_BEGIN); - } - - while (WCMD_fgets (string, ARRAY_SIZE(string), context -> h)) { - str = string; - - /* Ignore leading whitespace or no-echo character */ - while (*str=='@' || iswspace (*str)) str++; - - /* If the first real character is a : then this is a label */ - if (*str == ':') { - str++; - - /* Skip spaces between : and label */ - while (iswspace (*str)) str++; - WINE_TRACE("str before brk %s\n", wine_dbgstr_w(str)); + if (context != NULL) + { + WCHAR *paramStart = param1; + LARGE_INTEGER li, zeroli = {.QuadPart = 0}; + if (!param1[0]) + { + WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG)); + return ERROR_INVALID_FUNCTION; + }
- /* Label ends at whitespace or redirection characters */ - labelend = wcspbrk(str, labelEndsW); - if (labelend) *labelend = 0x00; - WINE_TRACE("comparing found label %s\n", wine_dbgstr_w(str)); + /* Handle special :EOF label */ + if (lstrcmpiW(L":eof", param1) == 0) + { + context->skip_rest = TRUE; + return RETURN_CODE_ABORTED; + }
- if (lstrcmpiW (str, paramStart) == 0) return RETURN_CODE_ABORTED; - } + /* Support goto :label as well as goto label plus remove trailing chars */ + if (*paramStart == ':') paramStart++; + WCMD_set_label_end(paramStart); + TRACE("goto label: '%s'\n", wine_dbgstr_w(paramStart));
- /* See if we have gone beyond the end point if second time through */ - if (loop==1) { - LARGE_INTEGER curli; - curli.QuadPart = 0; - curli.u.LowPart = SetFilePointer(context -> h, curli.u.LowPart, - &curli.u.HighPart, FILE_CURRENT); - if (curli.QuadPart > startli.QuadPart) { - WINE_TRACE("Reached wrap point, label not found\n"); - break; - } - } - } - } + SetFilePointerEx(context->h, zeroli, &li, FILE_CURRENT); + if (WCMD_find_label(context->h, paramStart, &li)) + return RETURN_CODE_ABORTED; + WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET)); + context->skip_rest = TRUE; } - - WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET)); - context -> skip_rest = TRUE; - } - return ERROR_INVALID_FUNCTION; + return ERROR_INVALID_FUNCTION; }
/***************************************************************************** diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index 4fad2884e1e..5b76872b8e9 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -221,6 +221,8 @@ void node_dispose_tree(CMD_NODE *cmds); RETURN_CODE node_execute(CMD_NODE *node);
RETURN_CODE WCMD_call_batch(const WCHAR *, WCHAR *); +BOOL WCMD_find_label(HANDLE h, const WCHAR*, LARGE_INTEGER *pos); +void WCMD_set_label_end(WCHAR *string);
void *xrealloc(void *, size_t) __WINE_ALLOC_SIZE(2) __WINE_DEALLOC(free);
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/batch.c | 2 +- programs/cmd/wcmd.h | 2 +- programs/cmd/wcmdmain.c | 31 ++++++++++++++++++------------- 3 files changed, 20 insertions(+), 15 deletions(-)
diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c index 5ed7dda871e..0b36117e960 100644 --- a/programs/cmd/batch.c +++ b/programs/cmd/batch.c @@ -32,7 +32,7 @@ static RETURN_CODE WCMD_batch_main_loop(void) { CMD_NODE *node;
- switch (WCMD_ReadAndParseLine(NULL, &node, context->h)) + switch (WCMD_ReadAndParseLine(NULL, &node)) { case RPL_EOF: context->skip_rest = TRUE; diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index 5b76872b8e9..0b4004a0ed9 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -216,7 +216,7 @@ WCHAR *WCMD_strsubstW(WCHAR *start, const WCHAR* next, const WCHAR* insert, int BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars, LPDWORD charsRead);
enum read_parse_line {RPL_SUCCESS, RPL_EOF, RPL_SYNTAXERROR}; -enum read_parse_line WCMD_ReadAndParseLine(const WCHAR *initialcmd, CMD_NODE **output, HANDLE readFrom); +enum read_parse_line WCMD_ReadAndParseLine(const WCHAR *initialcmd, CMD_NODE **output); void node_dispose_tree(CMD_NODE *cmds); RETURN_CODE node_execute(CMD_NODE *node);
diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 61d5c0fcbd7..9775a73850c 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -2677,7 +2677,7 @@ static void lexer_push_command(struct node_builder *builder, *copyTo = command; }
-static WCHAR *fetch_next_line(BOOL feed, BOOL first_line, HANDLE from, WCHAR* buffer) +static WCHAR *fetch_next_line(BOOL feed, BOOL first_line, WCHAR* buffer) { /* display prompt */ if (interactive && !context) @@ -2689,10 +2689,14 @@ static WCHAR *fetch_next_line(BOOL feed, BOOL first_line, HANDLE from, WCHAR* bu WCMD_show_prompt(); }
- if (feed && !WCMD_fgets(buffer, MAXSTRING, from)) + if (feed) { - buffer[0] = L'\0'; - return NULL; + HANDLE from = context ? context->h : GetStdHandle(STD_INPUT_HANDLE); + if (!WCMD_fgets(buffer, MAXSTRING, from)) + { + buffer[0] = L'\0'; + return NULL; + } } /* Handle truncated input - issue warning */ if (wcslen(buffer) == MAXSTRING - 1) @@ -2753,7 +2757,7 @@ static WCHAR *fetch_next_line(BOOL feed, BOOL first_line, HANDLE from, WCHAR* bu * - Anything else gets put into the command string (including * redirects) */ -enum read_parse_line WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_NODE **output, HANDLE readFrom) +enum read_parse_line WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_NODE **output) { WCHAR *curPos; int inQuotes = 0; @@ -2788,7 +2792,7 @@ enum read_parse_line WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_NODE ** /* If initial command read in, use that, otherwise get input from handle */ if (optionalcmd) wcscpy(extraSpace, optionalcmd); - if (!(curPos = fetch_next_line(optionalcmd == NULL, TRUE, readFrom, extraSpace))) + if (!(curPos = fetch_next_line(optionalcmd == NULL, TRUE, extraSpace))) return RPL_EOF;
TRACE("About to parse line (%ls)\n", extraSpace); @@ -3051,12 +3055,13 @@ enum read_parse_line WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_NODE ** if (curPos[1] == L'\0') { TRACE("Caret found at end of line\n"); extraSpace[0] = L'^'; - if (!fetch_next_line(TRUE, FALSE, readFrom, extraSpace + 1)) + if (optionalcmd) break; + if (!fetch_next_line(TRUE, FALSE, extraSpace + 1)) break; if (!extraSpace[1]) /* empty line */ { extraSpace[1] = L'\r'; - if (!fetch_next_line(TRUE, FALSE, readFrom, extraSpace + 2)) + if (!fetch_next_line(TRUE, FALSE, extraSpace + 2)) break; } curPos = extraSpace; @@ -3127,7 +3132,7 @@ enum read_parse_line WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_NODE ** node_builder_push_token(&builder, TKN_EOL);
/* If we have reached the end of the string, see if bracketing is outstanding */ - if (builder.opened_parenthesis > 0 && readFrom != INVALID_HANDLE_VALUE) { + if (builder.opened_parenthesis > 0 && optionalcmd == NULL) { TRACE("Need to read more data as outstanding brackets or carets\n"); inOneLine = FALSE; ignoreBracket = FALSE; @@ -3137,7 +3142,7 @@ enum read_parse_line WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_NODE **
/* fetch next non empty line */ do { - curPos = fetch_next_line(TRUE, FALSE, readFrom, extraSpace); + curPos = fetch_next_line(TRUE, FALSE, extraSpace); } while (curPos && *curPos == L'\0'); if (!curPos) curPos = extraSpace; @@ -4053,7 +4058,7 @@ int __cdecl wmain (int argc, WCHAR *argvW[]) */
/* Parse the command string, without reading any more input */ - rpl_status = WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE); + rpl_status = WCMD_ReadAndParseLine(cmd, &toExecute); if (rpl_status == RPL_SUCCESS && toExecute) { node_execute(toExecute); @@ -4137,7 +4142,7 @@ int __cdecl wmain (int argc, WCHAR *argvW[])
if (opt_k) { - rpl_status = WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE); + rpl_status = WCMD_ReadAndParseLine(cmd, &toExecute); /* Parse the command string, without reading any more input */ if (rpl_status == RPL_SUCCESS && toExecute) { @@ -4157,7 +4162,7 @@ int __cdecl wmain (int argc, WCHAR *argvW[]) if (!opt_k) WCMD_output_asis(version_string); if (echo_mode) WCMD_output_asis(L"\r\n"); /* Read until EOF (which for std input is never, but if redirect in place, may occur */ - while ((rpl_status = WCMD_ReadAndParseLine(NULL, &toExecute, GetStdHandle(STD_INPUT_HANDLE))) != RPL_EOF) + while ((rpl_status = WCMD_ReadAndParseLine(NULL, &toExecute)) != RPL_EOF) { if (rpl_status == RPL_SUCCESS && toExecute) {
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/batch.c | 12 ++++-------- programs/cmd/builtins.c | 5 ++--- programs/cmd/wcmd.h | 1 + programs/cmd/wcmdmain.c | 14 ++++++++++++-- 4 files changed, 19 insertions(+), 13 deletions(-)
diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c index 0b36117e960..0b93753eda8 100644 --- a/programs/cmd/batch.c +++ b/programs/cmd/batch.c @@ -87,6 +87,7 @@ RETURN_CODE WCMD_call_batch(const WCHAR *file, WCHAR *command) prev_context = context; context = malloc(sizeof (BATCH_CONTEXT)); context->h = h; + context->file_position.QuadPart = 0; context->batchfileW = xstrdupW(file); context->command = command; memset(context->shift_count, 0x00, sizeof(context->shift_count)); @@ -658,7 +659,6 @@ RETURN_CODE WCMD_call(WCHAR *command) } else if (context) { - LARGE_INTEGER li; WCHAR gotoLabel[MAX_PATH]; BATCH_CONTEXT *prev_context;
@@ -668,15 +668,10 @@ RETURN_CODE WCMD_call(WCHAR *command) as for loop variables do not survive a call */ WCMD_save_for_loop_context(TRUE);
- /* Save the current file position, call the same file, - restore position */ - li.QuadPart = 0; - li.u.LowPart = SetFilePointer(context->h, li.u.LowPart, - &li.u.HighPart, FILE_CURRENT); - prev_context = context; context = malloc(sizeof (BATCH_CONTEXT)); context->h = prev_context->h; + context->file_position = prev_context->file_position; /* will be overwritten by WCMD_GOTO below */ context->batchfileW = prev_context->batchfileW; context->command = buffer; memset(context->shift_count, 0x00, sizeof(context->shift_count)); @@ -692,7 +687,8 @@ RETURN_CODE WCMD_call(WCHAR *command) free(context); context = prev_context; return_code = errorlevel; - SetFilePointer(context->h, li.u.LowPart, &li.u.HighPart, FILE_BEGIN); + if (!SetFilePointerEx(context->h, context->file_position, NULL, FILE_BEGIN)) + return ERROR_INVALID_FUNCTION;
/* Restore the for loop context */ WCMD_restore_for_loop_context(); diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index 4eec2119462..284cf1f84b6 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -1818,7 +1818,7 @@ RETURN_CODE WCMD_goto(void) if (context != NULL) { WCHAR *paramStart = param1; - LARGE_INTEGER li, zeroli = {.QuadPart = 0}; + if (!param1[0]) { WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG)); @@ -1837,8 +1837,7 @@ RETURN_CODE WCMD_goto(void) WCMD_set_label_end(paramStart); TRACE("goto label: '%s'\n", wine_dbgstr_w(paramStart));
- SetFilePointerEx(context->h, zeroli, &li, FILE_CURRENT); - if (WCMD_find_label(context->h, paramStart, &li)) + if (WCMD_find_label(context->h, paramStart, &context->file_position)) return RETURN_CODE_ABORTED; WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET)); context->skip_rest = TRUE; diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index 0b4004a0ed9..3023afc1cb0 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -260,6 +260,7 @@ typedef struct _BATCH_CONTEXT { WCHAR *command; /* The command which invoked the batch file */ HANDLE h; /* Handle to the open batch file */ + LARGE_INTEGER file_position; WCHAR *batchfileW; /* Name of same */ int shift_count[10]; /* Offset in terms of shifts for %0 - %9 */ struct _BATCH_CONTEXT *prev_context; /* Pointer to the previous context block */ diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 9775a73850c..c4f58a422b9 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -2691,8 +2691,18 @@ static WCHAR *fetch_next_line(BOOL feed, BOOL first_line, WCHAR* buffer)
if (feed) { - HANDLE from = context ? context->h : GetStdHandle(STD_INPUT_HANDLE); - if (!WCMD_fgets(buffer, MAXSTRING, from)) + BOOL ret; + if (context) + { + LARGE_INTEGER zeroli = {.QuadPart = 0}; + + ret = SetFilePointerEx(context->h, context->file_position, NULL, FILE_BEGIN) && + !!WCMD_fgets(buffer, MAXSTRING, context->h) && + SetFilePointerEx(context->h, zeroli, &context->file_position, FILE_CURRENT); + } + else + ret = !!WCMD_fgets(buffer, MAXSTRING, GetStdHandle(STD_INPUT_HANDLE)); + if (!ret) { buffer[0] = L'\0'; return NULL;
From: Eric Pouech epouech@codeweavers.com
Ensuring the batch file is opened/closed at each operation. This allows the batch file to delete itself.
Wine-Bug-Id: https://bugs.winehq.org/show_bug.cgi?id=55151
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/batch.c | 14 -------------- programs/cmd/builtins.c | 15 +++++++++++++-- programs/cmd/tests/test_builtins.cmd.exp | 2 +- programs/cmd/wcmd.h | 1 - programs/cmd/wcmdmain.c | 19 +++++++++++++++---- 5 files changed, 29 insertions(+), 22 deletions(-)
diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c index 0b93753eda8..0a1b364fe19 100644 --- a/programs/cmd/batch.c +++ b/programs/cmd/batch.c @@ -71,22 +71,12 @@ static RETURN_CODE WCMD_batch_main_loop(void)
RETURN_CODE WCMD_call_batch(const WCHAR *file, WCHAR *command) { - HANDLE h = INVALID_HANDLE_VALUE; BATCH_CONTEXT *prev_context; RETURN_CODE return_code = NO_ERROR;
- h = CreateFileW (file, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, - NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (h == INVALID_HANDLE_VALUE) { - SetLastError (ERROR_FILE_NOT_FOUND); - WCMD_print_error (); - return ERROR_INVALID_FUNCTION; - } - /* Create a context structure for this batch file. */ prev_context = context; context = malloc(sizeof (BATCH_CONTEXT)); - context->h = h; context->file_position.QuadPart = 0; context->batchfileW = xstrdupW(file); context->command = command; @@ -95,7 +85,6 @@ RETURN_CODE WCMD_call_batch(const WCHAR *file, WCHAR *command) context->skip_rest = FALSE;
return_code = WCMD_batch_main_loop(); - CloseHandle (h);
free(context->batchfileW); free(context); @@ -670,7 +659,6 @@ RETURN_CODE WCMD_call(WCHAR *command)
prev_context = context; context = malloc(sizeof (BATCH_CONTEXT)); - context->h = prev_context->h; context->file_position = prev_context->file_position; /* will be overwritten by WCMD_GOTO below */ context->batchfileW = prev_context->batchfileW; context->command = buffer; @@ -687,8 +675,6 @@ RETURN_CODE WCMD_call(WCHAR *command) free(context); context = prev_context; return_code = errorlevel; - if (!SetFilePointerEx(context->h, context->file_position, NULL, FILE_BEGIN)) - return ERROR_INVALID_FUNCTION;
/* Restore the for loop context */ WCMD_restore_for_loop_context(); diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index 284cf1f84b6..f8ec0fbb6f4 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -1818,6 +1818,8 @@ RETURN_CODE WCMD_goto(void) if (context != NULL) { WCHAR *paramStart = param1; + HANDLE h; + BOOL ret;
if (!param1[0]) { @@ -1831,14 +1833,23 @@ RETURN_CODE WCMD_goto(void) context->skip_rest = TRUE; return RETURN_CODE_ABORTED; } + h = CreateFileW(context->batchfileW, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (h == INVALID_HANDLE_VALUE) + { + SetLastError(ERROR_FILE_NOT_FOUND); + WCMD_print_error(); + return ERROR_INVALID_FUNCTION; + }
/* Support goto :label as well as goto label plus remove trailing chars */ if (*paramStart == ':') paramStart++; WCMD_set_label_end(paramStart); TRACE("goto label: '%s'\n", wine_dbgstr_w(paramStart));
- if (WCMD_find_label(context->h, paramStart, &context->file_position)) - return RETURN_CODE_ABORTED; + ret = WCMD_find_label(h, paramStart, &context->file_position); + CloseHandle(h); + if (ret) return RETURN_CODE_ABORTED; WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET)); context->skip_rest = TRUE; } diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index 6195e8b7644..d61d81fbfe5 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -2010,7 +2010,7 @@ Normal+tab+garbage Success @todo_wine@Success ---- Testing nasty bits ---- -@todo_wine@------------ Testing combined CALLs/GOTOs ------------ +------------ Testing combined CALLs/GOTOs ------------ world cheball barbare diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index 3023afc1cb0..cf51a5e44de 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -259,7 +259,6 @@ int evaluate_if_condition(WCHAR *p, WCHAR **command, int *test, int *negate); typedef struct _BATCH_CONTEXT { WCHAR *command; /* The command which invoked the batch file */ - HANDLE h; /* Handle to the open batch file */ LARGE_INTEGER file_position; WCHAR *batchfileW; /* Name of same */ int shift_count[10]; /* Offset in terms of shifts for %0 - %9 */ diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index c4f58a422b9..2c64785d44a 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -2695,10 +2695,21 @@ static WCHAR *fetch_next_line(BOOL feed, BOOL first_line, WCHAR* buffer) if (context) { LARGE_INTEGER zeroli = {.QuadPart = 0}; - - ret = SetFilePointerEx(context->h, context->file_position, NULL, FILE_BEGIN) && - !!WCMD_fgets(buffer, MAXSTRING, context->h) && - SetFilePointerEx(context->h, zeroli, &context->file_position, FILE_CURRENT); + HANDLE h = CreateFileW(context->batchfileW, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (h == INVALID_HANDLE_VALUE) + { + SetLastError(ERROR_FILE_NOT_FOUND); + WCMD_print_error(); + ret = FALSE; + } + else + { + ret = SetFilePointerEx(h, context->file_position, NULL, FILE_BEGIN) && + !!WCMD_fgets(buffer, MAXSTRING, h) && + SetFilePointerEx(h, zeroli, &context->file_position, FILE_CURRENT); + CloseHandle(h); + } } else ret = !!WCMD_fgets(buffer, MAXSTRING, GetStdHandle(STD_INPUT_HANDLE));