From: Santino Mazza <smazza@codeweavers.com> --- programs/cmd/builtins.c | 333 ++++++++++++++++------- programs/cmd/cmd.rc | 1 + programs/cmd/tests/test_builtins.bat.exp | 4 +- programs/cmd/tests/test_builtins.cmd.exp | 4 +- programs/cmd/wcmd.h | 1 + 5 files changed, 237 insertions(+), 106 deletions(-) diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index b706feb39c7..d8cc545d00f 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -3292,129 +3292,258 @@ RETURN_CODE WCMD_shift(const WCHAR *args) /**************************************************************************** * WCMD_start + * + * Start a program with ShellExecute. + * + * Big part of the code is just copied from programs/start/start.c + * This implementation doesn't contain Wine-specific extensions. */ RETURN_CODE WCMD_start(WCHAR *args) { RETURN_CODE return_code = NO_ERROR; - int argno; - int have_title; - WCHAR file[MAX_PATH]; - WCHAR *cmdline, *cmdline_params; - STARTUPINFOW st; - PROCESS_INFORMATION pi; - - GetSystemDirectoryW( file, MAX_PATH ); - lstrcatW(file, L"\\start.exe"); - cmdline = xalloc( (wcslen(file) + wcslen(args) + 8) * sizeof(WCHAR) ); - lstrcpyW( cmdline, file ); - lstrcatW(cmdline, L" "); - cmdline_params = cmdline + lstrlenW(cmdline); - - /* The start built-in has some special command-line parsing properties - * which will be outlined here. - * - * both '\t' and ' ' are argument separators - * '/' has a special double role as both separator and switch prefix, e.g. - * - * > start /low/i - * or - * > start "title"/i - * - * are valid ways to pass multiple options to start. In the latter case - * '/i' is not a part of the title but parsed as a switch. - * - * However, '=', ';' and ',' are not separators: - * > start "deus"=ex,machina - * - * will in fact open a console titled 'deus=ex,machina' - * - * The title argument parsing code is only interested in quotes themselves, - * it does not respect escaping of any kind and all quotes are dropped - * from the resulting title, therefore: - * - * > start "\"" hello"/low - * - * actually opens a console titled '\ hello' with low priorities. - * - * To not break compatibility with wine programs relying on - * wine's separate 'start.exe', this program's peculiar console - * title parsing is actually implemented in 'cmd.exe' which is the - * application native Windows programs will use to invoke 'start'. - * - * WCMD_parameter_with_delims will take care of everything for us. - */ - /* FIXME: using an external start.exe has several caveats: - * - cannot discriminate syntax error in arguments from child's return code - * - need to access start.exe's child to get its running state - * (not start.exe itself) - */ - have_title = FALSE; - for (argno=0; ; argno++) { - WCHAR *thisArg, *argN; + DWORD binary_type, creation_flags; + int argn = 0; + SHELLEXECUTEINFOW sei = { 0 }; + USHORT machine = 0; + WCHAR *thisparam, *rawarg, *qual; + WCHAR path_argument[MAXSTRING] = L"cmd.exe"; /* the path parameter can also be a URL which can be larger than MAX_PATH. */ + WCHAR lpDirectory[MAX_PATH] = L""; + WCHAR *title = NULL; + struct search_command sc; + + sei.cbSize = sizeof(sei); + sei.lpVerb = L"open"; + sei.nShow = SW_SHOWNORMAL; + /* Dunno what these mean, but it looks like winMe's start uses them */ + sei.fMask = SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FLAG_NO_UI; + creation_flags = CREATE_NEW_CONSOLE; + + while (!!(thisparam = WCMD_parameter(args, argn, &rawarg, FALSE, FALSE)) && !!rawarg) + { + if (!title && rawarg[0] == '"') + { + title = wcsdup(thisparam); + argn++; + continue; + } - argN = NULL; - thisArg = WCMD_parameter_with_delims(args, argno, &argN, FALSE, FALSE, L" \t/"); + if (rawarg[0] != '/') break; - /* No more parameters */ - if (!argN) - break; + qual = wcstok(thisparam, L"/", NULL); + while (qual) + { + /* Qualifiers with values just break the loop. */ + if (towupper(*qual) == 'D') + { + WCHAR *dir = ++qual; - /* Found the title */ - if (argN[0] == '"') { - TRACE("detected console title: %s\n", wine_dbgstr_w(thisArg)); - have_title = TRUE; + if (!*dir && !*(dir = WCMD_parameter(args, ++argn, &rawarg, FALSE, FALSE))) + WINE_ERR("you must specify a directory path for the /d option\n"); - /* Copy all of the cmdline processed */ - memcpy(cmdline_params, args, sizeof(WCHAR) * (argN - args)); - cmdline_params[argN - args] = '\0'; + if (!WCMD_get_fullpath(dir, ARRAY_SIZE(lpDirectory), lpDirectory, NULL)) + goto error; - /* Add quoted title */ - lstrcatW(cmdline_params, L"\"\\\""); - lstrcatW(cmdline_params, thisArg); - lstrcatW(cmdline_params, L"\\\"\""); + TRACE("%s\n", wine_dbgstr_w(lpDirectory)); - /* Concatenate remaining command-line */ - thisArg = WCMD_parameter_with_delims(args, argno, &argN, TRUE, FALSE, L" \t/"); - lstrcatW(cmdline_params, argN + lstrlenW(thisArg)); + sei.lpDirectory = lpDirectory; + break; + } + else if (towupper(*qual) == 'B') + creation_flags &= ~CREATE_NEW_CONSOLE; + else if (towupper(*qual) == 'I') + FIXME("/i is ignored\n"); /* FIXME */ + else if (!_wcsicmp(qual, L"MIN")) + sei.nShow = SW_SHOWMINIMIZED; + else if (!_wcsicmp(qual, L"MAX")) + sei.nShow = SW_SHOWMAXIMIZED; + else if (!_wcsicmp(qual, L"LOW")) + creation_flags |= IDLE_PRIORITY_CLASS; + else if (!_wcsicmp(qual, L"NORMAL")) + creation_flags |= NORMAL_PRIORITY_CLASS; + else if (!_wcsicmp(qual, L"HIGH")) + creation_flags |= HIGH_PRIORITY_CLASS; + else if (!_wcsicmp(qual, L"REALTIME")) + creation_flags |= REALTIME_PRIORITY_CLASS; + else if (!_wcsicmp(qual, L"ABOVENORMAL")) + creation_flags |= ABOVE_NORMAL_PRIORITY_CLASS; + else if (!_wcsicmp(qual, L"BELOWNORMAL")) + creation_flags |= BELOW_NORMAL_PRIORITY_CLASS; + else if (!_wcsicmp(qual, L"SEPARATE")) + creation_flags |= CREATE_SEPARATE_WOW_VDM; + else if (!_wcsicmp(qual, L"SHARED")) + creation_flags |= CREATE_SHARED_WOW_VDM; + else if (towupper(*qual) == 'W' || !_wcsicmp(qual, L"WAIT")) + sei.fMask |= SEE_MASK_NOCLOSEPROCESS; + else if (!_wcsicmp(qual, L"NODE")) + FIXME("start /node is ignored\n"); /* FIXME */ + else if (!_wcsicmp(qual, L"AFFINITY")) + { + if (!*(thisparam = WCMD_parameter(args, ++argn, &rawarg, FALSE, FALSE))) + { + SetLastError(return_code = ERROR_INVALID_PARAMETER); + WCMD_print_error(); + goto error; + } + FIXME("start /affinity is ignored\n"); /* FIXME */ + break; + } + else if (!_wcsicmp(qual, L"MACHINE")) + { + if (!*(thisparam = WCMD_parameter(args, ++argn, &rawarg, FALSE, FALSE))) + { + SetLastError(return_code = ERROR_INVALID_PARAMETER); + WCMD_print_error(); + goto error; + } - break; - } + if (!_wcsicmp( thisparam, L"x86" )) machine = IMAGE_FILE_MACHINE_I386; + else if (!_wcsicmp( thisparam, L"amd64" )) machine = IMAGE_FILE_MACHINE_AMD64; + else if (!_wcsicmp( thisparam, L"arm" )) machine = IMAGE_FILE_MACHINE_ARMNT; + else if (!_wcsicmp( thisparam, L"arm64" )) machine = IMAGE_FILE_MACHINE_ARM64; + else + { + SetLastError(return_code = ERROR_INVALID_PARAMETER); + WCMD_print_error(); + goto error; + } + break; + } + else + { + TRACE("Error %s\n", wine_dbgstr_w(thisparam)); + SetLastError(return_code = ERROR_INVALID_PARAMETER); + WCMD_print_error(); + goto error; + } - /* Skipping a regular argument? */ - else if (argN != args && argN[-1] == '/') { - continue; + qual = wcstok(NULL, L"/", NULL); + } - /* Not an argument nor the title, start of program arguments, - * stop looking for title. - */ - } else - break; + argn++; } - /* build command-line if not built yet */ - if (!have_title) { - lstrcatW( cmdline, args ); - } + thisparam = WCMD_parameter(args, argn, &rawarg, FALSE, FALSE); + if (*(thisparam)) + wcscpy(path_argument, thisparam); + + if (*(thisparam = WCMD_parameter(args, ++argn, &rawarg, FALSE, FALSE))) + sei.lpParameters = rawarg; + else + sei.lpParameters = L""; - memset( &st, 0, sizeof(STARTUPINFOW) ); - st.cb = sizeof(STARTUPINFOW); + if (WCMD_search_command(path_argument, &sc, FALSE) == NO_ERROR) + sei.lpFile = sc.path; + else sei.lpFile = path_argument; - if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi )) + if (GetBinaryTypeW(sei.lpFile, &binary_type)) { - DWORD exit_code; - WaitForSingleObject( pi.hProcess, INFINITE ); - GetExitCodeProcess( pi.hProcess, &exit_code ); - errorlevel = (exit_code == STILL_ACTIVE) ? NO_ERROR : exit_code; - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); + WCHAR *commandline; + PROCESS_INFORMATION process_information; + BOOL ret; + STARTUPINFOEXW si = {{ sizeof(si.StartupInfo) }}; + struct _PROC_THREAD_ATTRIBUTE_LIST *attribute_list = NULL; + int len = lstrlenW(sei.lpFile) + 4 + lstrlenW(sei.lpParameters); + + /* explorer on windows always quotes the filename when running a binary on windows (see bug 5224) so we have to use CreateProcessW in this case */ + + if (machine) + { + SIZE_T size = 1024; + attribute_list = malloc( size ); + InitializeProcThreadAttributeList( attribute_list, 1, 0, &size ); + UpdateProcThreadAttribute( attribute_list, 0, PROC_THREAD_ATTRIBUTE_MACHINE_TYPE, &machine, sizeof(machine), NULL, NULL ); + si.StartupInfo.cb = sizeof(si); + si.lpAttributeList = attribute_list; + creation_flags |= EXTENDED_STARTUPINFO_PRESENT; + } + + commandline = malloc(len * sizeof(WCHAR)); + swprintf(commandline, len, L"\"%s\" %s", path_argument, sei.lpParameters); + + si.StartupInfo.wShowWindow = sei.nShow; + si.StartupInfo.dwFlags |= STARTF_USESHOWWINDOW; + si.StartupInfo.lpTitle = title; + + ret = CreateProcessW( sei.lpFile, commandline, NULL, NULL, FALSE, + creation_flags, NULL, sei.lpDirectory, + &si.StartupInfo, &process_information ); + free(attribute_list); + free(commandline); + if (!ret) + { + ERR("failed to create process %ld\n", GetLastError()); + return_code = GetLastError(); + goto error; + } + sei.hProcess = process_information.hProcess; } - else + else if (!ShellExecuteExW(&sei)) { - SetLastError(ERROR_FILE_NOT_FOUND); - WCMD_print_error (); - return_code = errorlevel = ERROR_INVALID_FUNCTION; + const WCHAR *filename = sei.lpFile; + DWORD size, filename_len; + WCHAR pathext[MAX_PATH]; + WCHAR *name; + + size = GetEnvironmentVariableW(L"PATHEXT", pathext, ARRAY_SIZE(pathext)); + if (size) + { + WCHAR *start, *ptr; + + filename_len = lstrlenW(filename); + name = malloc((filename_len + size) * sizeof(WCHAR)); + if (!name) + { + return_code = ERROR_OUTOFMEMORY; + goto error; + } + + sei.lpFile = name; + start = pathext; + return_code = ERROR_FILE_NOT_FOUND; + while ((ptr = wcschr(start, ';'))) + { + if (start == ptr) + { + start = ptr + 1; + continue; + } + + lstrcpyW(name, filename); + memcpy(&name[filename_len], start, (ptr - start) * sizeof(WCHAR)); + name[filename_len + (ptr - start)] = 0; + + if (ShellExecuteExW(&sei)) { + return_code = NO_ERROR; + break; + } + start = ptr + 1; + } + + free(name); + + if (return_code != NO_ERROR) + goto error; + } } - free(cmdline); + + if (sei.fMask & SEE_MASK_NOCLOSEPROCESS) + { + DWORD exitcode; + + SetConsoleCtrlHandler(NULL, TRUE); + WaitForSingleObject(sei.hProcess, INFINITE); + GetExitCodeProcess(sei.hProcess, &exitcode); + SetConsoleCtrlHandler(NULL, FALSE); + + errorlevel = exitcode; + } + + error: + free(title); + + if (return_code != NO_ERROR) + return_code = ERROR_INVALID_FUNCTION; + return return_code; } diff --git a/programs/cmd/cmd.rc b/programs/cmd/cmd.rc index bc738faa5e9..ead40fc8296 100644 --- a/programs/cmd/cmd.rc +++ b/programs/cmd/cmd.rc @@ -408,4 +408,5 @@ Enter HELP <command> for further information on any of the above commands.\n" WCMD_ENDOFFILE, "End of file" WCMD_NUMCOPIED, "%t%1!u! file(s) copied\n" WCMD_NOCOPYTOSELF, "File cannot be copied onto itself.\n" + WCMD_STRING_UNIXFAIL, "Could not translate the specified Unix filename to a DOS filename.\n" } diff --git a/programs/cmd/tests/test_builtins.bat.exp b/programs/cmd/tests/test_builtins.bat.exp index 4952fe4dda3..6ecc7e49c19 100644 --- a/programs/cmd/tests/test_builtins.bat.exp +++ b/programs/cmd/tests/test_builtins.bat.exp @@ -58,10 +58,10 @@ SUCCESS 0 FAILURE 255 FAILURE 255 --- success/failure for START command -@todo_wine@FAILURE 1 +FAILURE 1 foo@space@ SUCCESS 1024 -@todo_wine@SUCCESS 666 +SUCCESS 666 0 @todo_wine@FAILURE 1 --- success/failure for TYPE command diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index d2f076a2dc7..d3f561d6b9f 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -577,10 +577,10 @@ SUCCESS 0 FAILURE 255 FAILURE 255 --- success/failure for START command -@todo_wine@FAILURE 1 +FAILURE 1 foo@space@ SUCCESS 1024 -@todo_wine@SUCCESS 666 +SUCCESS 666 0 @todo_wine@FAILURE 1 --- success/failure for TYPE command diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index dd912ea33c7..dc461ccc786 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -495,3 +495,4 @@ extern WCHAR version_string[]; #define WCMD_ENDOFFILE 1049 #define WCMD_NUMCOPIED 1050 #define WCMD_NOCOPYTOSELF 1051 +#define WCMD_STRING_UNIXFAIL 1052 -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11608