|  | /* | 
|  | * Start a program using ShellExecuteEx, optionally wait for it to finish | 
|  | * Compatible with Microsoft's "c:\windows\command\start.exe" | 
|  | * | 
|  | * Copyright 2003 Dan Kegel | 
|  | * Copyright 2007 Lyutin Anatoly (Etersoft) | 
|  | * | 
|  | * This program is free software; you can redistribute it and/or | 
|  | * modify it under the terms of the GNU Lesser General Public | 
|  | * License as published by the Free Software Foundation; either | 
|  | * version 2.1 of the License, or (at your option) any later version. | 
|  | * | 
|  | * This program is distributed in the hope that it will be useful, | 
|  | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
|  | * Lesser General Public License for more details. | 
|  | * | 
|  | * You should have received a copy of the GNU Lesser General Public | 
|  | * License along with this program; if not, write to the Free Software | 
|  | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | 
|  | */ | 
|  |  | 
|  | #include <windows.h> | 
|  | #include <winuser.h> | 
|  | #include <stdio.h> | 
|  | #include <stdlib.h> | 
|  | #include <shlobj.h> | 
|  |  | 
|  | #include <wine/unicode.h> | 
|  | #include <wine/debug.h> | 
|  |  | 
|  | #include "resources.h" | 
|  |  | 
|  | WINE_DEFAULT_DEBUG_CHANNEL(start); | 
|  |  | 
|  | /** | 
|  | Output given message to stdout without formatting. | 
|  | */ | 
|  | static void output(const WCHAR *message) | 
|  | { | 
|  | DWORD count; | 
|  | DWORD   res; | 
|  | int    wlen = strlenW(message); | 
|  |  | 
|  | if (!wlen) return; | 
|  |  | 
|  | res = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), message, wlen, &count, NULL); | 
|  |  | 
|  | /* If writing to console fails, assume its file | 
|  | i/o so convert to OEM codepage and output                  */ | 
|  | if (!res) | 
|  | { | 
|  | DWORD len; | 
|  | char  *mesA; | 
|  | /* Convert to OEM, then output */ | 
|  | len = WideCharToMultiByte( GetConsoleOutputCP(), 0, message, wlen, NULL, 0, NULL, NULL ); | 
|  | mesA = HeapAlloc(GetProcessHeap(), 0, len*sizeof(char)); | 
|  | if (!mesA) return; | 
|  | WideCharToMultiByte( GetConsoleOutputCP(), 0, message, wlen, mesA, len, NULL, NULL ); | 
|  | WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), mesA, len, &count, FALSE); | 
|  | HeapFree(GetProcessHeap(), 0, mesA); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | Output given message from string table, | 
|  | followed by ": ", | 
|  | followed by description of given GetLastError() value to stdout, | 
|  | followed by a trailing newline, | 
|  | then terminate. | 
|  | */ | 
|  |  | 
|  | static void fatal_error(const WCHAR *msg, DWORD error_code) | 
|  | { | 
|  | LPVOID lpMsgBuf; | 
|  | int status; | 
|  | static const WCHAR colonsW[] = { ':', ' ', 0 }; | 
|  | static const WCHAR newlineW[] = { '\n', 0 }; | 
|  |  | 
|  | output(msg); | 
|  | output(colonsW); | 
|  | status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, error_code, 0, (LPWSTR) & lpMsgBuf, 0, NULL); | 
|  | if (!status) | 
|  | { | 
|  | WINE_ERR("FormatMessage failed\n"); | 
|  | } else | 
|  | { | 
|  | output(lpMsgBuf); | 
|  | LocalFree((HLOCAL) lpMsgBuf); | 
|  | output(newlineW); | 
|  | } | 
|  | ExitProcess(1); | 
|  | } | 
|  |  | 
|  | static void fatal_string_error(int which, DWORD error_code) | 
|  | { | 
|  | WCHAR msg[2048]; | 
|  |  | 
|  | if (!LoadStringW(GetModuleHandle(NULL), which, | 
|  | msg, sizeof(msg)/sizeof(WCHAR))) | 
|  | WINE_ERR("LoadString failed, error %d\n", GetLastError()); | 
|  |  | 
|  | fatal_error(msg, error_code); | 
|  | } | 
|  |  | 
|  | static void fatal_string(int which) | 
|  | { | 
|  | WCHAR msg[2048]; | 
|  |  | 
|  | if (!LoadStringW(GetModuleHandle(NULL), which, | 
|  | msg, sizeof(msg)/sizeof(WCHAR))) | 
|  | WINE_ERR("LoadString failed, error %d\n", GetLastError()); | 
|  |  | 
|  | output(msg); | 
|  | ExitProcess(1); | 
|  | } | 
|  |  | 
|  | static void usage(void) | 
|  | { | 
|  | fatal_string(STRING_USAGE); | 
|  | } | 
|  |  | 
|  | static void license(void) | 
|  | { | 
|  | fatal_string(STRING_LICENSE); | 
|  | } | 
|  |  | 
|  | static WCHAR *build_args( int argc, WCHAR **argvW ) | 
|  | { | 
|  | int i, wlen = 1; | 
|  | WCHAR *ret, *p; | 
|  | static const WCHAR FormatQuotesW[] = { ' ', '\"', '%', 's', '\"', 0 }; | 
|  | static const WCHAR FormatW[] = { ' ', '%', 's', 0 }; | 
|  |  | 
|  | for (i = 0; i < argc; i++ ) | 
|  | { | 
|  | wlen += strlenW(argvW[i]) + 1; | 
|  | if (strchrW(argvW[i], ' ')) | 
|  | wlen += 2; | 
|  | } | 
|  | ret = HeapAlloc( GetProcessHeap(), 0, wlen*sizeof(WCHAR) ); | 
|  | ret[0] = 0; | 
|  |  | 
|  | for (i = 0, p = ret; i < argc; i++ ) | 
|  | { | 
|  | if (strchrW(argvW[i], ' ')) | 
|  | p += sprintfW(p, FormatQuotesW, argvW[i]); | 
|  | else | 
|  | p += sprintfW(p, FormatW, argvW[i]); | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static WCHAR *get_parent_dir(WCHAR* path) | 
|  | { | 
|  | WCHAR *last_slash; | 
|  | WCHAR *result; | 
|  | int len; | 
|  |  | 
|  | last_slash = strrchrW( path, '\\' ); | 
|  | if (last_slash == NULL) | 
|  | len = 1; | 
|  | else | 
|  | len = last_slash - path + 1; | 
|  |  | 
|  | result = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)); | 
|  | CopyMemory(result, path, (len-1)*sizeof(WCHAR)); | 
|  | result[len-1] = '\0'; | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | int wmain (int argc, WCHAR *argv[]) | 
|  | { | 
|  | SHELLEXECUTEINFOW sei; | 
|  | WCHAR *args = NULL; | 
|  | int i; | 
|  | int unix_mode = 0; | 
|  | WCHAR *dos_filename = NULL; | 
|  | WCHAR *parent_directory = NULL; | 
|  | DWORD binary_type; | 
|  |  | 
|  | static const WCHAR openW[] = { 'o', 'p', 'e', 'n', 0 }; | 
|  | static const WCHAR unixW[] = { 'u', 'n', 'i', 'x', 0 }; | 
|  |  | 
|  | memset(&sei, 0, sizeof(sei)); | 
|  | sei.cbSize = sizeof(sei); | 
|  | sei.lpVerb = openW; | 
|  | 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| | 
|  | SEE_MASK_NO_CONSOLE; | 
|  |  | 
|  | /* Canonical Microsoft commandline flag processing: | 
|  | * flags start with /, are case insensitive, | 
|  | * and may be run together in same word. | 
|  | */ | 
|  | for (i=1; i<argc; i++) { | 
|  | int ci; | 
|  |  | 
|  | if (argv[i][0] != '/') | 
|  | break; | 
|  |  | 
|  | /* Unix paths can start with / so we have to assume anything following /U is not a flag */ | 
|  | if (unix_mode) | 
|  | break; | 
|  |  | 
|  | /* Handle all options in this word */ | 
|  | for (ci=0; argv[i][ci]; ) { | 
|  | /* Skip slash */ | 
|  | ci++; | 
|  | switch(argv[i][ci]) { | 
|  | case 'l': | 
|  | case 'L': | 
|  | license(); | 
|  | break;	/* notreached */ | 
|  | case 'm': | 
|  | case 'M': | 
|  | if (argv[i][ci+1] == 'a' || argv[i][ci+1] == 'A') | 
|  | sei.nShow = SW_SHOWMAXIMIZED; | 
|  | else | 
|  | sei.nShow = SW_SHOWMINIMIZED; | 
|  | break; | 
|  | case 'r': | 
|  | case 'R': | 
|  | /* sei.nShow = SW_SHOWNORMAL; */ | 
|  | break; | 
|  | case 'u': | 
|  | case 'U': | 
|  | if (strncmpiW(&argv[i][ci], unixW, 4) == 0) | 
|  | unix_mode = 1; | 
|  | else { | 
|  | WINE_ERR("Option '%s' not recognized\n", wine_dbgstr_w( argv[i]+ci-1)); | 
|  | usage(); | 
|  | } | 
|  | break; | 
|  | case 'w': | 
|  | case 'W': | 
|  | sei.fMask |= SEE_MASK_NOCLOSEPROCESS; | 
|  | break; | 
|  | default: | 
|  | WINE_ERR("Option '%s' not recognized\n", wine_dbgstr_w( argv[i]+ci-1)); | 
|  | usage(); | 
|  | } | 
|  | /* Skip to next slash */ | 
|  | while (argv[i][ci] && (argv[i][ci] != '/')) | 
|  | ci++; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (i == argc) | 
|  | usage(); | 
|  |  | 
|  | sei.lpFile = argv[i++]; | 
|  |  | 
|  | args = build_args( argc - i, &argv[i] ); | 
|  | sei.lpParameters = args; | 
|  |  | 
|  | if (unix_mode) { | 
|  | LPWSTR (*wine_get_dos_file_name_ptr)(LPCSTR); | 
|  | char* multibyte_unixpath; | 
|  | int multibyte_unixpath_len; | 
|  |  | 
|  | wine_get_dos_file_name_ptr = (void*)GetProcAddress(GetModuleHandle("KERNEL32"), "wine_get_dos_file_name"); | 
|  |  | 
|  | if (!wine_get_dos_file_name_ptr) | 
|  | fatal_string(STRING_UNIXFAIL); | 
|  |  | 
|  | multibyte_unixpath_len = WideCharToMultiByte(CP_UNIXCP, 0, sei.lpFile, -1, NULL, 0, NULL, NULL); | 
|  | multibyte_unixpath = HeapAlloc(GetProcessHeap(), 0, multibyte_unixpath_len); | 
|  |  | 
|  | WideCharToMultiByte(CP_UNIXCP, 0, sei.lpFile, -1, multibyte_unixpath, multibyte_unixpath_len, NULL, NULL); | 
|  |  | 
|  | dos_filename = wine_get_dos_file_name_ptr(multibyte_unixpath); | 
|  |  | 
|  | HeapFree(GetProcessHeap(), 0, multibyte_unixpath); | 
|  |  | 
|  | if (!dos_filename) | 
|  | fatal_string(STRING_UNIXFAIL); | 
|  |  | 
|  | sei.lpFile = dos_filename; | 
|  | sei.lpDirectory = parent_directory = get_parent_dir(dos_filename); | 
|  |  | 
|  | if (GetBinaryTypeW(sei.lpFile, &binary_type)) { | 
|  | WCHAR *commandline; | 
|  | STARTUPINFOW startup_info; | 
|  | PROCESS_INFORMATION process_information; | 
|  | static WCHAR commandlineformat[] = {'"','%','s','"','%','s',0}; | 
|  |  | 
|  | /* 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 */ | 
|  |  | 
|  | commandline = HeapAlloc(GetProcessHeap(), 0, (strlenW(sei.lpFile)+3+strlenW(sei.lpParameters))*sizeof(WCHAR)); | 
|  | sprintfW(commandline, commandlineformat, sei.lpFile, sei.lpParameters); | 
|  |  | 
|  | ZeroMemory(&startup_info, sizeof(startup_info)); | 
|  | startup_info.cb = sizeof(startup_info); | 
|  |  | 
|  | if (!CreateProcessW( | 
|  | NULL, /* lpApplicationName */ | 
|  | commandline, /* lpCommandLine */ | 
|  | NULL, /* lpProcessAttributes */ | 
|  | NULL, /* lpThreadAttributes */ | 
|  | FALSE, /* bInheritHandles */ | 
|  | CREATE_NEW_CONSOLE, /* dwCreationFlags */ | 
|  | NULL, /* lpEnvironment */ | 
|  | sei.lpDirectory, /* lpCurrentDirectory */ | 
|  | &startup_info, /* lpStartupInfo */ | 
|  | &process_information /* lpProcessInformation */ )) | 
|  | { | 
|  | fatal_string_error(STRING_EXECFAIL, GetLastError()); | 
|  | } | 
|  | sei.hProcess = process_information.hProcess; | 
|  | goto done; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!ShellExecuteExW(&sei)) | 
|  | fatal_string_error(STRING_EXECFAIL, GetLastError()); | 
|  |  | 
|  | done: | 
|  | HeapFree( GetProcessHeap(), 0, args ); | 
|  | HeapFree( GetProcessHeap(), 0, dos_filename ); | 
|  | HeapFree( GetProcessHeap(), 0, parent_directory ); | 
|  |  | 
|  | if (sei.fMask & SEE_MASK_NOCLOSEPROCESS) { | 
|  | DWORD exitcode; | 
|  | WaitForSingleObject(sei.hProcess, INFINITE); | 
|  | GetExitCodeProcess(sei.hProcess, &exitcode); | 
|  | ExitProcess(exitcode); | 
|  | } | 
|  |  | 
|  | ExitProcess(0); | 
|  | } |