All the special environment variables from the command shell which track directory use are stripped out from the C runtime environ/wenviron.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=45320 ---
Note this cannot be tested by the test suites because it only gets triggered for C applications launched from the command shell - if you launch them from explorer on windows for example, they are not set either. I've attached a comprehensive set of tests onto the bug as an attachment, which tests the interaction of these special variables, the current directory and the C runtime vs the command shell.
The way I have chosen to implement this leaves the special variables content in the allocated memory, allowing the memcpy of the environment strings, but does not put a pointer to that data in the char** array of strings. An alternative implementation would copy in string by string and skip them, but I have chosen to leave that unless something is broken because of it.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=45320 Signed-off-by: Jason Edmeades us@edmeades.me.uk --- dlls/msvcrt/data.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/dlls/msvcrt/data.c b/dlls/msvcrt/data.c index 06e16a865e..f08d9b1683 100644 --- a/dlls/msvcrt/data.c +++ b/dlls/msvcrt/data.c @@ -73,7 +73,8 @@ char ** msvcrt_SnapshotOfEnvironmentA(char **blk)
for (ptr = environ_strings; *ptr; ptr += strlen(ptr) + 1) { - count++; + /* Don't count environment variables starting with '=' which are command shell specific */ + if (*ptr != '=') count++; len += strlen(ptr) + 1; } if (blk) @@ -88,7 +89,8 @@ char ** msvcrt_SnapshotOfEnvironmentA(char **blk) memcpy(&blk[count],environ_strings,len); for (ptr = (char*) &blk[count]; *ptr; ptr += strlen(ptr) + 1) { - blk[i++] = ptr; + /* Skip special environment strings set by the command shell */ + if (*ptr != '=') blk[i++] = ptr; } } blk[i] = NULL; @@ -105,7 +107,8 @@ MSVCRT_wchar_t ** msvcrt_SnapshotOfEnvironmentW(MSVCRT_wchar_t **wblk)
for (wptr = wenviron_strings; *wptr; wptr += strlenW(wptr) + 1) { - count++; + /* Don't count environment variables starting with '=' which are command shell specific */ + if (*wptr != '=') count++; len += strlenW(wptr) + 1; } if (wblk) @@ -119,7 +122,8 @@ MSVCRT_wchar_t ** msvcrt_SnapshotOfEnvironmentW(MSVCRT_wchar_t **wblk) memcpy(&wblk[count],wenviron_strings,len * sizeof(MSVCRT_wchar_t)); for (wptr = (MSVCRT_wchar_t*)&wblk[count]; *wptr; wptr += strlenW(wptr) + 1) { - wblk[i++] = wptr; + /* Skip special environment strings set by the command shell */ + if (*wptr != '=') wblk[i++] = wptr; } } wblk[i] = NULL;
Add support for longer than 1024 characters for initial settings of environment variables. Given windows behaviour changes after 2048, use that as the new limit for the variable size.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=45810
---
Manual experiments on windows 10 shows that the registry keys can be over 1024 characters, and as per the links on the bug, it definitely is possible to have longer variables. We could go higher than 2048, but as per [1] behavious change after 2048, so until someone hits the new limit, 2048 should suffice [1] https://software.intel.com/en-us/articles/limitation-to-the-length-of-the-sy... Signed-off-by: Jason Edmeades us@edmeades.me.uk --- dlls/kernel32/process.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/dlls/kernel32/process.c b/dlls/kernel32/process.c index b0b5ccf5bc..b6b2c38d52 100644 --- a/dlls/kernel32/process.c +++ b/dlls/kernel32/process.c @@ -335,7 +335,8 @@ static BOOL build_initial_environment(void) * * Set environment variables by enumerating the values of a key; * helper for set_registry_environment(). - * Note that Windows happily truncates the value if it's too big. + * Note that early versions of Windows happily truncate the value if + * it's too big. */ static void set_registry_variables( HANDLE hkey, ULONG type ) { @@ -345,8 +346,8 @@ static void set_registry_variables( HANDLE hkey, ULONG type ) NTSTATUS status; DWORD size; int index; - char buffer[1024*sizeof(WCHAR) + sizeof(KEY_VALUE_FULL_INFORMATION)]; - WCHAR tmpbuf[1024]; + char buffer[2048*sizeof(WCHAR) + sizeof(KEY_VALUE_FULL_INFORMATION)]; + WCHAR tmpbuf[2048]; UNICODE_STRING tmp; KEY_VALUE_FULL_INFORMATION *info = (KEY_VALUE_FULL_INFORMATION *)buffer;
Jason Edmeades us@edmeades.me.uk writes:
Add support for longer than 1024 characters for initial settings of environment variables. Given windows behaviour changes after 2048, use that as the new limit for the variable size.
It doesn't seem too hard to make the buffer size dynamic.
cmd already handles exe, cmd, bat etc but if you run a file with another extension, then use the associations set in the registry (for example via ftype / assoc) to launch a program. This enables you to run test.txt and notepad to pop up, or fred.msi for msiexec to be launched.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=18154 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=36646
---
Test for this added in the next patch, as ftype is broken at the moment, and needs fixing first Signed-off-by: Jason Edmeades us@edmeades.me.uk --- programs/cmd/wcmdmain.c | 140 +++++++++++++++++++++++++++++----------- 1 file changed, 104 insertions(+), 36 deletions(-)
diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 5ef4a2bf34..348eec8ad9 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -1143,8 +1143,10 @@ void WCMD_run_program (WCHAR *command, BOOL called)
/* 1. If extension supplied, see if that file exists */ if (extensionsupplied) { - if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) { + DWORD attribs = GetFileAttributesW(thisDir); + if (attribs != INVALID_FILE_ATTRIBUTES && !(attribs&FILE_ATTRIBUTE_DIRECTORY)) { found = TRUE; + WINE_TRACE("Found as file with extension as '%s'\n", wine_dbgstr_w(thisDir)); } }
@@ -1175,6 +1177,7 @@ void WCMD_run_program (WCHAR *command, BOOL called) }
if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) { + WINE_TRACE("Found via search and pathext as '%s'\n", wine_dbgstr_w(thisDir)); found = TRUE; thisExt = NULL; } @@ -1192,58 +1195,123 @@ void WCMD_run_program (WCHAR *command, BOOL called) WCHAR *ext = strrchrW( thisDir, '.' ); static const WCHAR batExt[] = {'.','b','a','t','\0'}; static const WCHAR cmdExt[] = {'.','c','m','d','\0'}; + static const WCHAR exeExt[] = {'.','e','x','e','\0'}; + static const WCHAR comExt[] = {'.','c','o','m','\0'};
WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
/* Special case BAT and CMD */ if (ext && (!strcmpiW(ext, batExt) || !strcmpiW(ext, cmdExt))) { BOOL oldinteractive = interactive; + WINE_TRACE("Calling batch program\n"); interactive = FALSE; WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE); interactive = oldinteractive; + WINE_TRACE("Back from call to batch program\n"); return; - } else { + }
- /* thisDir contains the file to be launched, but with what? - eg. a.exe will require a.exe to be launched, a.html may be iexplore */ - hinst = FindExecutableW (thisDir, NULL, temp); - if ((INT_PTR)hinst < 32) - console = 0; - else - console = SHGetFileInfoW(temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE); - - ZeroMemory (&st, sizeof(STARTUPINFOW)); - st.cb = sizeof(STARTUPINFOW); - init_msvcrt_io_block(&st); - - /* Launch the process and if a CUI wait on it to complete - Note: Launching internal wine processes cannot specify a full path to exe */ - status = CreateProcessW(thisDir, - command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe); - heap_free(st.lpReserved2); - if ((opt_c || opt_k) && !opt_s && !status - && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='"') { - /* strip first and last quote WCHARacters and try again */ - WCMD_strip_quotes(command); - opt_s = TRUE; - WCMD_run_program(command, called); + /* Calculate what program will be launched, and whether it is a + console application or not. Note the program may be different + from the parameter (eg running a .txt file will launch notepad.exe) */ + hinst = FindExecutableW (thisDir, NULL, temp); + if ((INT_PTR)hinst < 32) + console = 0; /* Assume not console app by default */ + else + console = SHGetFileInfoW(temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE); + + + /* If it is not a .com or .exe, try to launch through ShellExecuteExW + which takes into account the association for the extension. */ + if (ext && (strcmpiW(ext, exeExt) && strcmpiW(ext, comExt))) { + + SHELLEXECUTEINFOW shexw; + BOOL rc; + WCHAR *rawarg; + + WCMD_parameter(command, 1, &rawarg, FALSE, TRUE); + WINE_TRACE("Launching via ShellExecuteEx\n"); + memset(&shexw, 0x00, sizeof(shexw)); + shexw.cbSize = sizeof(SHELLEXECUTEINFOW); + shexw.fMask = SEE_MASK_NO_CONSOLE | /* Run in same console as currently using */ + SEE_MASK_NOCLOSEPROCESS; /* We need a process handle to possibly wait on */ + shexw.lpFile = thisDir; + shexw.lpParameters = rawarg; + shexw.nShow = SW_SHOWNORMAL; + + /* Try to launch the binary or its associated program */ + rc = ShellExecuteExW(&shexw); + + if (rc && (INT_PTR)shexw.hInstApp >= 32) { + + WINE_TRACE("Successfully launched\n"); + + /* It worked... Always wait when non-interactive (cmd /c or in + batch program), or for console applications */ + if (!interactive || (console && !HIWORD(console))) { + WINE_TRACE("Waiting for process to end\n"); + WaitForSingleObject (shexw.hProcess, INFINITE); + } + + GetExitCodeProcess (shexw.hProcess, &errorlevel); + if (errorlevel == STILL_ACTIVE) { + WINE_TRACE("Process still running, but returning anyway\n"); + errorlevel = 0; + } else { + WINE_TRACE("Process ended, errorlevel %d\n", errorlevel); + } + + CloseHandle(pe.hProcess); return; + } + }
- if (!status) - break; + /* If its a .exe or .com or the shellexecute failed due to no association, + CreateProcess directly */ + ZeroMemory (&st, sizeof(STARTUPINFOW)); + st.cb = sizeof(STARTUPINFOW); + init_msvcrt_io_block(&st); + + /* Launch the process and if a CUI wait on it to complete + Note: Launching internal wine processes cannot specify a full path to exe */ + WINE_TRACE("Launching via CreateProcess\n"); + status = CreateProcessW(thisDir, + command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe); + heap_free(st.lpReserved2); + if ((opt_c || opt_k) && !opt_s && !status + && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='"') { + /* strip first and last quote WCHARacters and try again */ + WCMD_strip_quotes(command); + opt_s = TRUE; + WCMD_run_program(command, called); + return; + }
- /* Always wait when non-interactive (cmd /c or in batch program), - or for console applications */ - if (!interactive || (console && !HIWORD(console))) - WaitForSingleObject (pe.hProcess, INFINITE); - GetExitCodeProcess (pe.hProcess, &errorlevel); - if (errorlevel == STILL_ACTIVE) errorlevel = 0; + if (!status) { + WINE_TRACE("Failed to launch via CreateProcess, rc %d (%d)\n", + status, GetLastError()); + break; + }
- CloseHandle(pe.hProcess); - CloseHandle(pe.hThread); - return; + /* Always wait when non-interactive (cmd /c or in batch program), + or for console applications */ + if (!interactive || (console && !HIWORD(console))) { + WINE_TRACE("Waiting for process to end\n"); + WaitForSingleObject (pe.hProcess, INFINITE); } + + GetExitCodeProcess (pe.hProcess, &errorlevel); + if (errorlevel == STILL_ACTIVE) { + WINE_TRACE("Process still running, but returning anyway\n"); + errorlevel = 0; + } else { + WINE_TRACE("Process ended, errorlevel %d\n", errorlevel); + } + + CloseHandle(pe.hProcess); + CloseHandle(pe.hThread); + return; } }
If a file association was set (e.g. ftype fred=xxx), ftype fred= needs to clear it, but previously it failed to do so
--- Fixes a fixme from the tests and implements a test for the patch which adds ShellExecute support for launching programs
Signed-off-by: Jason Edmeades us@edmeades.me.uk --- programs/cmd/builtins.c | 6 +++--- programs/cmd/tests/test_builtins.cmd | 9 +++++++++ programs/cmd/tests/test_builtins.cmd.exp | 5 ++++- 3 files changed, 16 insertions(+), 4 deletions(-)
diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index 25b98813ba..879cb8dcb6 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -4939,11 +4939,11 @@ void WCMD_assoc (const WCHAR *args, BOOL assoc) { /* If nothing after '=' then clear value - only valid for ASSOC */ if (*newValue == 0x00) {
- if (assoc) rc = RegDeleteKeyW(key, args); - if (assoc && rc == ERROR_SUCCESS) { + rc = RegDeleteTreeW(key, args); + if (rc == ERROR_SUCCESS) { WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args));
- } else if (assoc && rc != ERROR_FILE_NOT_FOUND) { + } else if (rc != ERROR_FILE_NOT_FOUND) { WCMD_print_error(); errorlevel = 2;
diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd index 79233d8d6f..0651056d1f 100644 --- a/programs/cmd/tests/test_builtins.cmd +++ b/programs/cmd/tests/test_builtins.cmd @@ -2398,6 +2398,12 @@ echo echo +++>> tmp.cmd echo ftype footype>> tmp.cmd cmd /c tmp.cmd
+echo --- testing association +ftype footype=cmd.exe /c "echo '%%1'" +echo dummy>test.foo +test.foo +del test.foo + echo --- resetting association assoc .foo=
@@ -2429,6 +2435,9 @@ echo .foo=footype echo footype=foo_opencmd echo +++ echo footype=foo_opencmd +echo --- testing association +echo footype=cmd.exe /c "echo '%%1'" +echo Skipped as not enough permissions echo --- resetting association echo original value
diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index 797a9cc8ae..52759f1b69 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -1430,8 +1430,11 @@ footype=foo_opencmd footype=foo_opencmd +++ footype=foo_opencmd +--- testing association +footype=cmd.exe /c "echo '%1'" +'@drive@@path@foobar\test.foo'@or_broken@Skipped as not enough permissions --- resetting association -@todo_wine@original value@or_broken@buggyXP@or_broken@!WINE_FOO! +original value@or_broken@buggyXP@or_broken@!WINE_FOO! ------------ Testing CALL ------------ --- external script foo@space@