diff options
Diffstat (limited to 'PC/launcher.c')
-rw-r--r-- | PC/launcher.c | 2003 |
1 files changed, 0 insertions, 2003 deletions
diff --git a/PC/launcher.c b/PC/launcher.c deleted file mode 100644 index 2749a4e..0000000 --- a/PC/launcher.c +++ /dev/null @@ -1,2003 +0,0 @@ -/* - * Copyright (C) 2011-2013 Vinay Sajip. - * Licensed to PSF under a contributor agreement. - * - * Based on the work of: - * - * Mark Hammond (original author of Python version) - * Curt Hagenlocher (job management) - */ - -#include <windows.h> -#include <shlobj.h> -#include <stdio.h> -#include <tchar.h> - -#define BUFSIZE 256 -#define MSGSIZE 1024 - -/* Build options. */ -#define SKIP_PREFIX -#define SEARCH_PATH - -/* Error codes */ - -#define RC_NO_STD_HANDLES 100 -#define RC_CREATE_PROCESS 101 -#define RC_BAD_VIRTUAL_PATH 102 -#define RC_NO_PYTHON 103 -#define RC_NO_MEMORY 104 -/* - * SCRIPT_WRAPPER is used to choose one of the variants of an executable built - * from this source file. If not defined, the PEP 397 Python launcher is built; - * if defined, a script launcher of the type used by setuptools is built, which - * looks for a script name related to the executable name and runs that script - * with the appropriate Python interpreter. - * - * SCRIPT_WRAPPER should be undefined in the source, and defined in a VS project - * which builds the setuptools-style launcher. - */ -#if defined(SCRIPT_WRAPPER) -#define RC_NO_SCRIPT 105 -#endif -/* - * VENV_REDIRECT is used to choose the variant that looks for an adjacent or - * one-level-higher pyvenv.cfg, and uses its "home" property to locate and - * launch the original python.exe. - */ -#if defined(VENV_REDIRECT) -#define RC_NO_VENV_CFG 106 -#define RC_BAD_VENV_CFG 107 -#endif - -/* Just for now - static definition */ - -static FILE * log_fp = NULL; - -static wchar_t * -skip_whitespace(wchar_t * p) -{ - while (*p && isspace(*p)) - ++p; - return p; -} - -static void -debug(wchar_t * format, ...) -{ - va_list va; - - if (log_fp != NULL) { - va_start(va, format); - vfwprintf_s(log_fp, format, va); - va_end(va); - } -} - -static void -winerror(int rc, wchar_t * message, int size) -{ - FormatMessageW( - FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, rc, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - message, size, NULL); -} - -static void -error(int rc, wchar_t * format, ... ) -{ - va_list va; - wchar_t message[MSGSIZE]; - wchar_t win_message[MSGSIZE]; - int len; - - va_start(va, format); - len = _vsnwprintf_s(message, MSGSIZE, _TRUNCATE, format, va); - va_end(va); - - if (rc == 0) { /* a Windows error */ - winerror(GetLastError(), win_message, MSGSIZE); - if (len >= 0) { - _snwprintf_s(&message[len], MSGSIZE - len, _TRUNCATE, L": %ls", - win_message); - } - } - -#if !defined(_WINDOWS) - fwprintf(stderr, L"%ls\n", message); -#else - MessageBoxW(NULL, message, L"Python Launcher is sorry to say ...", - MB_OK); -#endif - exit(rc); -} - -/* - * This function is here to simplify memory management - * and to treat blank values as if they are absent. - */ -static wchar_t * get_env(wchar_t * key) -{ - /* This is not thread-safe, just like getenv */ - static wchar_t buf[BUFSIZE]; - DWORD result = GetEnvironmentVariableW(key, buf, BUFSIZE); - - if (result >= BUFSIZE) { - /* Large environment variable. Accept some leakage */ - wchar_t *buf2 = (wchar_t*)malloc(sizeof(wchar_t) * (result+1)); - if (buf2 == NULL) { - error(RC_NO_MEMORY, L"Could not allocate environment buffer"); - } - GetEnvironmentVariableW(key, buf2, result); - return buf2; - } - - if (result == 0) - /* Either some error, e.g. ERROR_ENVVAR_NOT_FOUND, - or an empty environment variable. */ - return NULL; - - return buf; -} - -#if defined(_DEBUG) -/* Do not define EXECUTABLEPATH_VALUE in debug builds as it'll - never point to the debug build. */ -#if defined(_WINDOWS) - -#define PYTHON_EXECUTABLE L"pythonw_d.exe" - -#else - -#define PYTHON_EXECUTABLE L"python_d.exe" - -#endif -#else -#if defined(_WINDOWS) - -#define PYTHON_EXECUTABLE L"pythonw.exe" -#define EXECUTABLEPATH_VALUE L"WindowedExecutablePath" - -#else - -#define PYTHON_EXECUTABLE L"python.exe" -#define EXECUTABLEPATH_VALUE L"ExecutablePath" - -#endif -#endif - -#define MAX_VERSION_SIZE 8 - -typedef struct { - wchar_t version[MAX_VERSION_SIZE]; /* m.n */ - int bits; /* 32 or 64 */ - wchar_t executable[MAX_PATH]; - wchar_t exe_display[MAX_PATH]; -} INSTALLED_PYTHON; - -/* - * To avoid messing about with heap allocations, just assume we can allocate - * statically and never have to deal with more versions than this. - */ -#define MAX_INSTALLED_PYTHONS 100 - -static INSTALLED_PYTHON installed_pythons[MAX_INSTALLED_PYTHONS]; - -static size_t num_installed_pythons = 0; - -/* - * To hold SOFTWARE\Python\PythonCore\X.Y...\InstallPath - * The version name can be longer than MAX_VERSION_SIZE, but will be - * truncated to just X.Y for comparisons. - */ -#define IP_BASE_SIZE 80 -#define IP_VERSION_SIZE 8 -#define IP_SIZE (IP_BASE_SIZE + IP_VERSION_SIZE) -#define CORE_PATH L"SOFTWARE\\Python\\PythonCore" -/* - * Installations from the Microsoft Store will set the same registry keys, - * but because of a limitation in Windows they cannot be enumerated normally - * (unless you have no other Python installations... which is probably false - * because that's the most likely way to get this launcher!) - * This key is under HKEY_LOCAL_MACHINE - */ -#define LOOKASIDE_PATH L"SOFTWARE\\Microsoft\\AppModel\\Lookaside\\user\\Software\\Python\\PythonCore" - -static wchar_t * location_checks[] = { - L"\\", - L"\\PCbuild\\win32\\", - L"\\PCbuild\\amd64\\", - /* To support early 32bit versions of Python that stuck the build binaries - * directly in PCbuild... */ - L"\\PCbuild\\", - NULL -}; - -static INSTALLED_PYTHON * -find_existing_python(const wchar_t * path) -{ - INSTALLED_PYTHON * result = NULL; - size_t i; - INSTALLED_PYTHON * ip; - - for (i = 0, ip = installed_pythons; i < num_installed_pythons; i++, ip++) { - if (_wcsicmp(path, ip->executable) == 0) { - result = ip; - break; - } - } - return result; -} - -static INSTALLED_PYTHON * -find_existing_python2(int bits, const wchar_t * version) -{ - INSTALLED_PYTHON * result = NULL; - size_t i; - INSTALLED_PYTHON * ip; - - for (i = 0, ip = installed_pythons; i < num_installed_pythons; i++, ip++) { - if (bits == ip->bits && _wcsicmp(version, ip->version) == 0) { - result = ip; - break; - } - } - return result; -} - -static void -_locate_pythons_for_key(HKEY root, LPCWSTR subkey, REGSAM flags, int bits, - int display_name_only) -{ - HKEY core_root, ip_key; - LSTATUS status = RegOpenKeyExW(root, subkey, 0, flags, &core_root); - wchar_t message[MSGSIZE]; - DWORD i; - size_t n; - BOOL ok, append_name; - DWORD type, data_size, attrs; - INSTALLED_PYTHON * ip, * pip; - wchar_t ip_version[IP_VERSION_SIZE]; - wchar_t ip_path[IP_SIZE]; - wchar_t * check; - wchar_t ** checkp; - wchar_t *key_name = (root == HKEY_LOCAL_MACHINE) ? L"HKLM" : L"HKCU"; - - if (status != ERROR_SUCCESS) - debug(L"locate_pythons_for_key: unable to open PythonCore key in %ls\n", - key_name); - else { - ip = &installed_pythons[num_installed_pythons]; - for (i = 0; num_installed_pythons < MAX_INSTALLED_PYTHONS; i++) { - status = RegEnumKeyW(core_root, i, ip_version, IP_VERSION_SIZE); - if (status != ERROR_SUCCESS) { - if (status != ERROR_NO_MORE_ITEMS) { - /* unexpected error */ - winerror(status, message, MSGSIZE); - debug(L"Can't enumerate registry key for version %ls: %ls\n", - ip_version, message); - } - break; - } - else { - wcsncpy_s(ip->version, MAX_VERSION_SIZE, ip_version, - MAX_VERSION_SIZE-1); - /* Still treating version as "x.y" rather than sys.winver - * When PEP 514 tags are properly used, we shouldn't need - * to strip this off here. - */ - check = wcsrchr(ip->version, L'-'); - if (check && !wcscmp(check, L"-32")) { - *check = L'\0'; - } - _snwprintf_s(ip_path, IP_SIZE, _TRUNCATE, - L"%ls\\%ls\\InstallPath", subkey, ip_version); - status = RegOpenKeyExW(root, ip_path, 0, flags, &ip_key); - if (status != ERROR_SUCCESS) { - winerror(status, message, MSGSIZE); - /* Note: 'message' already has a trailing \n*/ - debug(L"%ls\\%ls: %ls", key_name, ip_path, message); - continue; - } - data_size = sizeof(ip->executable) - 1; - append_name = FALSE; -#ifdef EXECUTABLEPATH_VALUE - status = RegQueryValueExW(ip_key, EXECUTABLEPATH_VALUE, NULL, &type, - (LPBYTE)ip->executable, &data_size); -#else - status = ERROR_FILE_NOT_FOUND; /* actual error doesn't matter */ -#endif - if (status != ERROR_SUCCESS || type != REG_SZ || !data_size) { - append_name = TRUE; - data_size = sizeof(ip->executable) - 1; - status = RegQueryValueExW(ip_key, NULL, NULL, &type, - (LPBYTE)ip->executable, &data_size); - if (status != ERROR_SUCCESS) { - winerror(status, message, MSGSIZE); - debug(L"%ls\\%ls: %ls\n", key_name, ip_path, message); - RegCloseKey(ip_key); - continue; - } - } - RegCloseKey(ip_key); - if (type != REG_SZ) { - continue; - } - - data_size = data_size / sizeof(wchar_t) - 1; /* for NUL */ - if (ip->executable[data_size - 1] == L'\\') - --data_size; /* reg value ended in a backslash */ - /* ip->executable is data_size long */ - for (checkp = location_checks; *checkp; ++checkp) { - check = *checkp; - if (append_name) { - _snwprintf_s(&ip->executable[data_size], - MAX_PATH - data_size, - MAX_PATH - data_size, - L"%ls%ls", check, PYTHON_EXECUTABLE); - } - attrs = GetFileAttributesW(ip->executable); - if (attrs == INVALID_FILE_ATTRIBUTES) { - winerror(GetLastError(), message, MSGSIZE); - debug(L"locate_pythons_for_key: %ls: %ls", - ip->executable, message); - } - else if (attrs & FILE_ATTRIBUTE_DIRECTORY) { - debug(L"locate_pythons_for_key: '%ls' is a directory\n", - ip->executable, attrs); - } - else if (find_existing_python(ip->executable)) { - debug(L"locate_pythons_for_key: %ls: already found\n", - ip->executable); - } - else { - /* check the executable type. */ - if (bits) { - ip->bits = bits; - } else { - ok = GetBinaryTypeW(ip->executable, &attrs); - if (!ok) { - debug(L"Failure getting binary type: %ls\n", - ip->executable); - } - else { - if (attrs == SCS_64BIT_BINARY) - ip->bits = 64; - else if (attrs == SCS_32BIT_BINARY) - ip->bits = 32; - else - ip->bits = 0; - } - } - if (ip->bits == 0) { - debug(L"locate_pythons_for_key: %ls: \ -invalid binary type: %X\n", - ip->executable, attrs); - } - else { - if (display_name_only) { - /* display just the executable name. This is - * primarily for the Store installs */ - const wchar_t *name = wcsrchr(ip->executable, L'\\'); - if (name) { - wcscpy_s(ip->exe_display, MAX_PATH, name+1); - } - } - if (wcschr(ip->executable, L' ') != NULL) { - /* has spaces, so quote, and set original as - * the display name */ - if (!ip->exe_display[0]) { - wcscpy_s(ip->exe_display, MAX_PATH, ip->executable); - } - n = wcslen(ip->executable); - memmove(&ip->executable[1], - ip->executable, n * sizeof(wchar_t)); - ip->executable[0] = L'\"'; - ip->executable[n + 1] = L'\"'; - ip->executable[n + 2] = L'\0'; - } - debug(L"locate_pythons_for_key: %ls \ -is a %dbit executable\n", - ip->executable, ip->bits); - if (find_existing_python2(ip->bits, ip->version)) { - debug(L"locate_pythons_for_key: %ls-%i: already \ -found\n", ip->version, ip->bits); - } - else { - ++num_installed_pythons; - pip = ip++; - if (num_installed_pythons >= - MAX_INSTALLED_PYTHONS) - break; - } - } - } - } - } - } - RegCloseKey(core_root); - } -} - -static int -compare_pythons(const void * p1, const void * p2) -{ - INSTALLED_PYTHON * ip1 = (INSTALLED_PYTHON *) p1; - INSTALLED_PYTHON * ip2 = (INSTALLED_PYTHON *) p2; - /* note reverse sorting on version */ - int result = wcscmp(ip2->version, ip1->version); - - if (result == 0) - result = ip2->bits - ip1->bits; /* 64 before 32 */ - return result; -} - -static void -locate_pythons_for_key(HKEY root, REGSAM flags) -{ - _locate_pythons_for_key(root, CORE_PATH, flags, 0, FALSE); -} - -static void -locate_store_pythons() -{ -#if defined(_M_X64) - /* 64bit process, so look in native registry */ - _locate_pythons_for_key(HKEY_LOCAL_MACHINE, LOOKASIDE_PATH, - KEY_READ, 64, TRUE); -#else - /* 32bit process, so check that we're on 64bit OS */ - BOOL f64 = FALSE; - if (IsWow64Process(GetCurrentProcess(), &f64) && f64) { - _locate_pythons_for_key(HKEY_LOCAL_MACHINE, LOOKASIDE_PATH, - KEY_READ | KEY_WOW64_64KEY, 64, TRUE); - } -#endif -} - -static void -locate_venv_python() -{ - static wchar_t venv_python[MAX_PATH]; - INSTALLED_PYTHON * ip; - wchar_t *virtual_env = get_env(L"VIRTUAL_ENV"); - DWORD attrs; - - /* Check for VIRTUAL_ENV environment variable */ - if (virtual_env == NULL || virtual_env[0] == L'\0') { - return; - } - - /* Check for a python executable in the venv */ - debug(L"Checking for Python executable in virtual env '%ls'\n", virtual_env); - _snwprintf_s(venv_python, MAX_PATH, _TRUNCATE, - L"%ls\\Scripts\\%ls", virtual_env, PYTHON_EXECUTABLE); - attrs = GetFileAttributesW(venv_python); - if (attrs == INVALID_FILE_ATTRIBUTES) { - debug(L"Python executable %ls missing from virtual env\n", venv_python); - return; - } - - ip = &installed_pythons[num_installed_pythons++]; - wcscpy_s(ip->executable, MAX_PATH, venv_python); - ip->bits = 0; - wcscpy_s(ip->version, MAX_VERSION_SIZE, L"venv"); -} - -static void -locate_all_pythons() -{ - /* venv Python is highest priority */ - locate_venv_python(); -#if defined(_M_X64) - /* If we are a 64bit process, first hit the 32bit keys. */ - debug(L"locating Pythons in 32bit registry\n"); - locate_pythons_for_key(HKEY_CURRENT_USER, KEY_READ | KEY_WOW64_32KEY); - locate_pythons_for_key(HKEY_LOCAL_MACHINE, KEY_READ | KEY_WOW64_32KEY); -#else - /* If we are a 32bit process on a 64bit Windows, first hit the 64bit keys.*/ - BOOL f64 = FALSE; - if (IsWow64Process(GetCurrentProcess(), &f64) && f64) { - debug(L"locating Pythons in 64bit registry\n"); - locate_pythons_for_key(HKEY_CURRENT_USER, KEY_READ | KEY_WOW64_64KEY); - locate_pythons_for_key(HKEY_LOCAL_MACHINE, KEY_READ | KEY_WOW64_64KEY); - } -#endif - /* now hit the "native" key for this process bittedness. */ - debug(L"locating Pythons in native registry\n"); - locate_pythons_for_key(HKEY_CURRENT_USER, KEY_READ); - locate_pythons_for_key(HKEY_LOCAL_MACHINE, KEY_READ); - /* Store-installed Python is lowest priority */ - locate_store_pythons(); - qsort(installed_pythons, num_installed_pythons, sizeof(INSTALLED_PYTHON), - compare_pythons); -} - -static INSTALLED_PYTHON * -find_python_by_version(wchar_t const * wanted_ver) -{ - INSTALLED_PYTHON * result = NULL; - INSTALLED_PYTHON * ip = installed_pythons; - size_t i, n; - size_t wlen = wcslen(wanted_ver); - int bits = 0; - - if (wcsstr(wanted_ver, L"-32")) { - bits = 32; - wlen -= wcslen(L"-32"); - } - else if (wcsstr(wanted_ver, L"-64")) { /* Added option to select 64 bit explicitly */ - bits = 64; - wlen -= wcslen(L"-64"); - } - for (i = 0; i < num_installed_pythons; i++, ip++) { - n = wcslen(ip->version); - if (n > wlen) - n = wlen; - if ((wcsncmp(ip->version, wanted_ver, n) == 0) && - /* bits == 0 => don't care */ - ((bits == 0) || (ip->bits == bits))) { - result = ip; - break; - } - } - return result; -} - - -static wchar_t appdata_ini_path[MAX_PATH]; -static wchar_t launcher_ini_path[MAX_PATH]; - -/* - * Get a value either from the environment or a configuration file. - * The key passed in will either be "python", "python2" or "python3". - */ -static wchar_t * -get_configured_value(wchar_t * key) -{ -/* - * Note: this static value is used to return a configured value - * obtained either from the environment or configuration file. - * This should be OK since there wouldn't be any concurrent calls. - */ - static wchar_t configured_value[MSGSIZE]; - wchar_t * result = NULL; - wchar_t * found_in = L"environment"; - DWORD size; - - /* First, search the environment. */ - _snwprintf_s(configured_value, MSGSIZE, _TRUNCATE, L"py_%ls", key); - result = get_env(configured_value); - if (result == NULL && appdata_ini_path[0]) { - /* Not in environment: check local configuration. */ - size = GetPrivateProfileStringW(L"defaults", key, NULL, - configured_value, MSGSIZE, - appdata_ini_path); - if (size > 0) { - result = configured_value; - found_in = appdata_ini_path; - } - } - if (result == NULL && launcher_ini_path[0]) { - /* Not in environment or local: check global configuration. */ - size = GetPrivateProfileStringW(L"defaults", key, NULL, - configured_value, MSGSIZE, - launcher_ini_path); - if (size > 0) { - result = configured_value; - found_in = launcher_ini_path; - } - } - if (result) { - debug(L"found configured value '%ls=%ls' in %ls\n", - key, result, found_in ? found_in : L"(unknown)"); - } else { - debug(L"found no configured value for '%ls'\n", key); - } - return result; -} - -static INSTALLED_PYTHON * -locate_python(wchar_t * wanted_ver, BOOL from_shebang) -{ - static wchar_t config_key [] = { L"pythonX" }; - static wchar_t * last_char = &config_key[sizeof(config_key) / - sizeof(wchar_t) - 2]; - INSTALLED_PYTHON * result = NULL; - size_t n = wcslen(wanted_ver); - wchar_t * configured_value; - - if (num_installed_pythons == 0) - locate_all_pythons(); - - if (n == 1) { /* just major version specified */ - *last_char = *wanted_ver; - configured_value = get_configured_value(config_key); - if (configured_value != NULL) - wanted_ver = configured_value; - } - if (*wanted_ver) { - result = find_python_by_version(wanted_ver); - debug(L"search for Python version '%ls' found ", wanted_ver); - if (result) { - debug(L"'%ls'\n", result->executable); - } else { - debug(L"no interpreter\n"); - } - } - else { - *last_char = L'\0'; /* look for an overall default */ - result = find_python_by_version(L"venv"); - if (result == NULL) { - configured_value = get_configured_value(config_key); - if (configured_value) - result = find_python_by_version(configured_value); - } - /* Not found a value yet - try by major version. - * If we're looking for an interpreter specified in a shebang line, - * we want to try Python 2 first, then Python 3 (for Unix and backward - * compatibility). If we're being called interactively, assume the user - * wants the latest version available, so try Python 3 first, then - * Python 2. - */ - if (result == NULL) - result = find_python_by_version(from_shebang ? L"2" : L"3"); - if (result == NULL) - result = find_python_by_version(from_shebang ? L"3" : L"2"); - debug(L"search for default Python found "); - if (result) { - debug(L"version %ls at '%ls'\n", - result->version, result->executable); - } else { - debug(L"no interpreter\n"); - } - } - return result; -} - -#if defined(SCRIPT_WRAPPER) -/* - * Check for a script located alongside the executable - */ - -#if defined(_WINDOWS) -#define SCRIPT_SUFFIX L"-script.pyw" -#else -#define SCRIPT_SUFFIX L"-script.py" -#endif - -static wchar_t wrapped_script_path[MAX_PATH]; - -/* Locate the script being wrapped. - * - * This code should store the name of the wrapped script in - * wrapped_script_path, or terminate the program with an error if there is no - * valid wrapped script file. - */ -static void -locate_wrapped_script() -{ - wchar_t * p; - size_t plen; - DWORD attrs; - - plen = GetModuleFileNameW(NULL, wrapped_script_path, MAX_PATH); - p = wcsrchr(wrapped_script_path, L'.'); - if (p == NULL) { - debug(L"GetModuleFileNameW returned value has no extension: %ls\n", - wrapped_script_path); - error(RC_NO_SCRIPT, L"Wrapper name '%ls' is not valid.", wrapped_script_path); - } - - wcsncpy_s(p, MAX_PATH - (p - wrapped_script_path) + 1, SCRIPT_SUFFIX, _TRUNCATE); - attrs = GetFileAttributesW(wrapped_script_path); - if (attrs == INVALID_FILE_ATTRIBUTES) { - debug(L"File '%ls' non-existent\n", wrapped_script_path); - error(RC_NO_SCRIPT, L"Script file '%ls' is not present.", wrapped_script_path); - } - - debug(L"Using wrapped script file '%ls'\n", wrapped_script_path); -} -#endif - -/* - * Process creation code - */ - -static BOOL -safe_duplicate_handle(HANDLE in, HANDLE * pout) -{ - BOOL ok; - HANDLE process = GetCurrentProcess(); - DWORD rc; - - *pout = NULL; - ok = DuplicateHandle(process, in, process, pout, 0, TRUE, - DUPLICATE_SAME_ACCESS); - if (!ok) { - rc = GetLastError(); - if (rc == ERROR_INVALID_HANDLE) { - debug(L"DuplicateHandle returned ERROR_INVALID_HANDLE\n"); - ok = TRUE; - } - else { - debug(L"DuplicateHandle returned %d\n", rc); - } - } - return ok; -} - -static BOOL WINAPI -ctrl_c_handler(DWORD code) -{ - return TRUE; /* We just ignore all control events. */ -} - -static void -run_child(wchar_t * cmdline) -{ - HANDLE job; - JOBOBJECT_EXTENDED_LIMIT_INFORMATION info; - DWORD rc; - BOOL ok; - STARTUPINFOW si; - PROCESS_INFORMATION pi; - -#if defined(_WINDOWS) - /* - When explorer launches a Windows (GUI) application, it displays - the "app starting" (the "pointer + hourglass") cursor for a number - of seconds, or until the app does something UI-ish (eg, creating a - window, or fetching a message). As this launcher doesn't do this - directly, that cursor remains even after the child process does these - things. We avoid that by doing a simple post+get message. - See http://bugs.python.org/issue17290 and - https://bitbucket.org/vinay.sajip/pylauncher/issue/20/busy-cursor-for-a-long-time-when-running - */ - MSG msg; - - PostMessage(0, 0, 0, 0); - GetMessage(&msg, 0, 0, 0); -#endif - - debug(L"run_child: about to run '%ls'\n", cmdline); - job = CreateJobObject(NULL, NULL); - ok = QueryInformationJobObject(job, JobObjectExtendedLimitInformation, - &info, sizeof(info), &rc); - if (!ok || (rc != sizeof(info)) || !job) - error(RC_CREATE_PROCESS, L"Job information querying failed"); - info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | - JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK; - ok = SetInformationJobObject(job, JobObjectExtendedLimitInformation, &info, - sizeof(info)); - if (!ok) - error(RC_CREATE_PROCESS, L"Job information setting failed"); - memset(&si, 0, sizeof(si)); - GetStartupInfoW(&si); - ok = safe_duplicate_handle(GetStdHandle(STD_INPUT_HANDLE), &si.hStdInput); - if (!ok) - error(RC_NO_STD_HANDLES, L"stdin duplication failed"); - ok = safe_duplicate_handle(GetStdHandle(STD_OUTPUT_HANDLE), &si.hStdOutput); - if (!ok) - error(RC_NO_STD_HANDLES, L"stdout duplication failed"); - ok = safe_duplicate_handle(GetStdHandle(STD_ERROR_HANDLE), &si.hStdError); - if (!ok) - error(RC_NO_STD_HANDLES, L"stderr duplication failed"); - - ok = SetConsoleCtrlHandler(ctrl_c_handler, TRUE); - if (!ok) - error(RC_CREATE_PROCESS, L"control handler setting failed"); - - si.dwFlags = STARTF_USESTDHANDLES; - ok = CreateProcessW(NULL, cmdline, NULL, NULL, TRUE, - 0, NULL, NULL, &si, &pi); - if (!ok) - error(RC_CREATE_PROCESS, L"Unable to create process using '%ls'", cmdline); - AssignProcessToJobObject(job, pi.hProcess); - CloseHandle(pi.hThread); - WaitForSingleObjectEx(pi.hProcess, INFINITE, FALSE); - ok = GetExitCodeProcess(pi.hProcess, &rc); - if (!ok) - error(RC_CREATE_PROCESS, L"Failed to get exit code of process"); - debug(L"child process exit code: %d\n", rc); - exit(rc); -} - -static void -invoke_child(wchar_t * executable, wchar_t * suffix, wchar_t * cmdline) -{ - wchar_t * child_command; - size_t child_command_size; - BOOL no_suffix = (suffix == NULL) || (*suffix == L'\0'); - BOOL no_cmdline = (*cmdline == L'\0'); - - if (no_suffix && no_cmdline) - run_child(executable); - else { - if (no_suffix) { - /* add 2 for space separator + terminating NUL. */ - child_command_size = wcslen(executable) + wcslen(cmdline) + 2; - } - else { - /* add 3 for 2 space separators + terminating NUL. */ - child_command_size = wcslen(executable) + wcslen(suffix) + - wcslen(cmdline) + 3; - } - child_command = calloc(child_command_size, sizeof(wchar_t)); - if (child_command == NULL) - error(RC_CREATE_PROCESS, L"unable to allocate %zd bytes for child command.", - child_command_size); - if (no_suffix) - _snwprintf_s(child_command, child_command_size, - child_command_size - 1, L"%ls %ls", - executable, cmdline); - else - _snwprintf_s(child_command, child_command_size, - child_command_size - 1, L"%ls %ls %ls", - executable, suffix, cmdline); - run_child(child_command); - free(child_command); - } -} - -typedef struct { - wchar_t *shebang; - BOOL search; -} SHEBANG; - -static SHEBANG builtin_virtual_paths [] = { - { L"/usr/bin/env python", TRUE }, - { L"/usr/bin/python", FALSE }, - { L"/usr/local/bin/python", FALSE }, - { L"python", FALSE }, - { NULL, FALSE }, -}; - -/* For now, a static array of commands. */ - -#define MAX_COMMANDS 100 - -typedef struct { - wchar_t key[MAX_PATH]; - wchar_t value[MSGSIZE]; -} COMMAND; - -static COMMAND commands[MAX_COMMANDS]; -static int num_commands = 0; - -#if defined(SKIP_PREFIX) - -static wchar_t * builtin_prefixes [] = { - /* These must be in an order that the longest matches should be found, - * i.e. if the prefix is "/usr/bin/env ", it should match that entry - * *before* matching "/usr/bin/". - */ - L"/usr/bin/env ", - L"/usr/bin/", - L"/usr/local/bin/", - NULL -}; - -static wchar_t * skip_prefix(wchar_t * name) -{ - wchar_t ** pp = builtin_prefixes; - wchar_t * result = name; - wchar_t * p; - size_t n; - - for (; p = *pp; pp++) { - n = wcslen(p); - if (_wcsnicmp(p, name, n) == 0) { - result += n; /* skip the prefix */ - if (p[n - 1] == L' ') /* No empty strings in table, so n > 1 */ - result = skip_whitespace(result); - break; - } - } - return result; -} - -#endif - -#if defined(SEARCH_PATH) - -static COMMAND path_command; - -static COMMAND * find_on_path(wchar_t * name) -{ - wchar_t * pathext; - size_t varsize; - wchar_t * context = NULL; - wchar_t * extension; - COMMAND * result = NULL; - DWORD len; - errno_t rc; - - wcscpy_s(path_command.key, MAX_PATH, name); - if (wcschr(name, L'.') != NULL) { - /* assume it has an extension. */ - len = SearchPathW(NULL, name, NULL, MSGSIZE, path_command.value, NULL); - if (len) { - result = &path_command; - } - } - else { - /* No extension - search using registered extensions. */ - rc = _wdupenv_s(&pathext, &varsize, L"PATHEXT"); - if (rc == 0) { - extension = wcstok_s(pathext, L";", &context); - while (extension) { - len = SearchPathW(NULL, name, extension, MSGSIZE, path_command.value, NULL); - if (len) { - result = &path_command; - break; - } - extension = wcstok_s(NULL, L";", &context); - } - free(pathext); - } - } - return result; -} - -#endif - -static COMMAND * find_command(wchar_t * name) -{ - COMMAND * result = NULL; - COMMAND * cp = commands; - int i; - - for (i = 0; i < num_commands; i++, cp++) { - if (_wcsicmp(cp->key, name) == 0) { - result = cp; - break; - } - } -#if defined(SEARCH_PATH) - if (result == NULL) - result = find_on_path(name); -#endif - return result; -} - -static void -update_command(COMMAND * cp, wchar_t * name, wchar_t * cmdline) -{ - wcsncpy_s(cp->key, MAX_PATH, name, _TRUNCATE); - wcsncpy_s(cp->value, MSGSIZE, cmdline, _TRUNCATE); -} - -static void -add_command(wchar_t * name, wchar_t * cmdline) -{ - if (num_commands >= MAX_COMMANDS) { - debug(L"can't add %ls = '%ls': no room\n", name, cmdline); - } - else { - COMMAND * cp = &commands[num_commands++]; - - update_command(cp, name, cmdline); - } -} - -static void -read_config_file(wchar_t * config_path) -{ - wchar_t keynames[MSGSIZE]; - wchar_t value[MSGSIZE]; - DWORD read; - wchar_t * key; - COMMAND * cp; - wchar_t * cmdp; - - read = GetPrivateProfileStringW(L"commands", NULL, NULL, keynames, MSGSIZE, - config_path); - if (read == MSGSIZE - 1) { - debug(L"read_commands: %ls: not enough space for names\n", config_path); - } - key = keynames; - while (*key) { - read = GetPrivateProfileStringW(L"commands", key, NULL, value, MSGSIZE, - config_path); - if (read == MSGSIZE - 1) { - debug(L"read_commands: %ls: not enough space for %ls\n", - config_path, key); - } - cmdp = skip_whitespace(value); - if (*cmdp) { - cp = find_command(key); - if (cp == NULL) - add_command(key, value); - else - update_command(cp, key, value); - } - key += wcslen(key) + 1; - } -} - -static void read_commands() -{ - if (launcher_ini_path[0]) - read_config_file(launcher_ini_path); - if (appdata_ini_path[0]) - read_config_file(appdata_ini_path); -} - -static BOOL -parse_shebang(wchar_t * shebang_line, int nchars, wchar_t ** command, - wchar_t ** suffix, BOOL *search) -{ - BOOL rc = FALSE; - SHEBANG * vpp; - size_t plen; - wchar_t * p; - wchar_t zapped; - wchar_t * endp = shebang_line + nchars - 1; - COMMAND * cp; - wchar_t * skipped; - - *command = NULL; /* failure return */ - *suffix = NULL; - *search = FALSE; - - if ((*shebang_line++ == L'#') && (*shebang_line++ == L'!')) { - shebang_line = skip_whitespace(shebang_line); - if (*shebang_line) { - *command = shebang_line; - for (vpp = builtin_virtual_paths; vpp->shebang; ++vpp) { - plen = wcslen(vpp->shebang); - if (wcsncmp(shebang_line, vpp->shebang, plen) == 0) { - rc = TRUE; - *search = vpp->search; - /* We can do this because all builtin commands contain - * "python". - */ - *command = wcsstr(shebang_line, L"python"); - break; - } - } - if (vpp->shebang == NULL) { - /* - * Not found in builtins - look in customized commands. - * - * We can't permanently modify the shebang line in case - * it's not a customized command, but we can temporarily - * stick a NUL after the command while searching for it, - * then put back the char we zapped. - */ -#if defined(SKIP_PREFIX) - skipped = skip_prefix(shebang_line); -#else - skipped = shebang_line; -#endif - p = wcspbrk(skipped, L" \t\r\n"); - if (p != NULL) { - zapped = *p; - *p = L'\0'; - } - cp = find_command(skipped); - if (p != NULL) - *p = zapped; - if (cp != NULL) { - *command = cp->value; - if (p != NULL) - *suffix = skip_whitespace(p); - } - } - /* remove trailing whitespace */ - while ((endp > shebang_line) && isspace(*endp)) - --endp; - if (endp > shebang_line) - endp[1] = L'\0'; - } - } - return rc; -} - -/* #define CP_UTF8 65001 defined in winnls.h */ -#define CP_UTF16LE 1200 -#define CP_UTF16BE 1201 -#define CP_UTF32LE 12000 -#define CP_UTF32BE 12001 - -typedef struct { - int length; - char sequence[4]; - UINT code_page; -} BOM; - -/* - * Strictly, we don't need to handle UTF-16 and UTF-32, since Python itself - * doesn't. Never mind, one day it might - there's no harm leaving it in. - */ -static BOM BOMs[] = { - { 3, { 0xEF, 0xBB, 0xBF }, CP_UTF8 }, /* UTF-8 - keep first */ - /* Test UTF-32LE before UTF-16LE since UTF-16LE BOM is a prefix - * of UTF-32LE BOM. */ - { 4, { 0xFF, 0xFE, 0x00, 0x00 }, CP_UTF32LE }, /* UTF-32LE */ - { 4, { 0x00, 0x00, 0xFE, 0xFF }, CP_UTF32BE }, /* UTF-32BE */ - { 2, { 0xFF, 0xFE }, CP_UTF16LE }, /* UTF-16LE */ - { 2, { 0xFE, 0xFF }, CP_UTF16BE }, /* UTF-16BE */ - { 0 } /* sentinel */ -}; - -static BOM * -find_BOM(char * buffer) -{ -/* - * Look for a BOM in the input and return a pointer to the - * corresponding structure, or NULL if not found. - */ - BOM * result = NULL; - BOM *bom; - - for (bom = BOMs; bom->length; bom++) { - if (strncmp(bom->sequence, buffer, bom->length) == 0) { - result = bom; - break; - } - } - return result; -} - -static char * -find_terminator(char * buffer, int len, BOM *bom) -{ - char * result = NULL; - char * end = buffer + len; - char * p; - char c; - int cp; - - for (p = buffer; p < end; p++) { - c = *p; - if (c == '\r') { - result = p; - break; - } - if (c == '\n') { - result = p; - break; - } - } - if (result != NULL) { - cp = bom->code_page; - - /* adjustments to include all bytes of the char */ - /* no adjustment needed for UTF-8 or big endian */ - if (cp == CP_UTF16LE) - ++result; - else if (cp == CP_UTF32LE) - result += 3; - ++result; /* point just past terminator */ - } - return result; -} - -static BOOL -validate_version(wchar_t * p) -{ - /* - Version information should start with the major version, - Optionally followed by a period and a minor version, - Optionally followed by a minus and one of 32 or 64. - Valid examples: - 2 - 3 - 2.7 - 3.6 - 2.7-32 - The intent is to add to the valid patterns: - 3.10 - 3-32 - 3.6-64 - 3-64 - */ - BOOL result = (p != NULL); /* Default to False if null pointer. */ - - result = result && iswdigit(*p); /* Result = False if first string element is not a digit. */ - - while (result && iswdigit(*p)) /* Require a major version */ - ++p; /* Skip all leading digit(s) */ - if (result && (*p == L'.')) /* Allow . for major minor separator.*/ - { - result = iswdigit(*++p); /* Must be at least one digit */ - while (result && iswdigit(*++p)) ; /* Skip any more Digits */ - } - if (result && (*p == L'-')) { /* Allow - for Bits Separator */ - switch(*++p){ - case L'3': /* 3 is OK */ - result = (*++p == L'2') && !*++p; /* only if followed by 2 and ended.*/ - break; - case L'6': /* 6 is OK */ - result = (*++p == L'4') && !*++p; /* only if followed by 4 and ended.*/ - break; - default: - result = FALSE; - break; - } - } - result = result && !*p; /* Must have reached EOS */ - return result; - -} - -typedef struct { - unsigned short min; - unsigned short max; - wchar_t version[MAX_VERSION_SIZE]; -} PYC_MAGIC; - -static PYC_MAGIC magic_values[] = { - { 50823, 50823, L"2.0" }, - { 60202, 60202, L"2.1" }, - { 60717, 60717, L"2.2" }, - { 62011, 62021, L"2.3" }, - { 62041, 62061, L"2.4" }, - { 62071, 62131, L"2.5" }, - { 62151, 62161, L"2.6" }, - { 62171, 62211, L"2.7" }, - { 3000, 3131, L"3.0" }, - { 3141, 3151, L"3.1" }, - { 3160, 3180, L"3.2" }, - { 3190, 3230, L"3.3" }, - { 3250, 3310, L"3.4" }, - { 3320, 3351, L"3.5" }, - { 3360, 3379, L"3.6" }, - { 3390, 3399, L"3.7" }, - { 3400, 3419, L"3.8" }, - { 0 } -}; - -static INSTALLED_PYTHON * -find_by_magic(unsigned short magic) -{ - INSTALLED_PYTHON * result = NULL; - PYC_MAGIC * mp; - - for (mp = magic_values; mp->min; mp++) { - if ((magic >= mp->min) && (magic <= mp->max)) { - result = locate_python(mp->version, FALSE); - if (result != NULL) - break; - } - } - return result; -} - -static void -maybe_handle_shebang(wchar_t ** argv, wchar_t * cmdline) -{ -/* - * Look for a shebang line in the first argument. If found - * and we spawn a child process, this never returns. If it - * does return then we process the args "normally". - * - * argv[0] might be a filename with a shebang. - */ - FILE * fp; - errno_t rc = _wfopen_s(&fp, *argv, L"rb"); - char buffer[BUFSIZE]; - wchar_t shebang_line[BUFSIZE + 1]; - size_t read; - char *p; - char * start; - char * shebang_alias = (char *) shebang_line; - BOM* bom; - int i, j, nchars = 0; - int header_len; - BOOL is_virt; - BOOL search; - wchar_t * command; - wchar_t * suffix; - COMMAND *cmd = NULL; - INSTALLED_PYTHON * ip; - - if (rc == 0) { - read = fread(buffer, sizeof(char), BUFSIZE, fp); - debug(L"maybe_handle_shebang: read %zd bytes\n", read); - fclose(fp); - - if ((read >= 4) && (buffer[3] == '\n') && (buffer[2] == '\r')) { - ip = find_by_magic((((unsigned char)buffer[1]) << 8 | - (unsigned char)buffer[0]) & 0xFFFF); - if (ip != NULL) { - debug(L"script file is compiled against Python %ls\n", - ip->version); - invoke_child(ip->executable, NULL, cmdline); - } - } - /* Look for BOM */ - bom = find_BOM(buffer); - if (bom == NULL) { - start = buffer; - debug(L"maybe_handle_shebang: BOM not found, using UTF-8\n"); - bom = BOMs; /* points to UTF-8 entry - the default */ - } - else { - debug(L"maybe_handle_shebang: BOM found, code page %u\n", - bom->code_page); - start = &buffer[bom->length]; - } - p = find_terminator(start, BUFSIZE, bom); - /* - * If no CR or LF was found in the heading, - * we assume it's not a shebang file. - */ - if (p == NULL) { - debug(L"maybe_handle_shebang: No line terminator found\n"); - } - else { - /* - * Found line terminator - parse the shebang. - * - * Strictly, we don't need to handle UTF-16 anf UTF-32, - * since Python itself doesn't. - * Never mind, one day it might. - */ - header_len = (int) (p - start); - switch(bom->code_page) { - case CP_UTF8: - nchars = MultiByteToWideChar(bom->code_page, - 0, - start, header_len, shebang_line, - BUFSIZE); - break; - case CP_UTF16BE: - if (header_len % 2 != 0) { - debug(L"maybe_handle_shebang: UTF-16BE, but an odd number \ -of bytes: %d\n", header_len); - /* nchars = 0; Not needed - initialised to 0. */ - } - else { - for (i = header_len; i > 0; i -= 2) { - shebang_alias[i - 1] = start[i - 2]; - shebang_alias[i - 2] = start[i - 1]; - } - nchars = header_len / sizeof(wchar_t); - } - break; - case CP_UTF16LE: - if ((header_len % 2) != 0) { - debug(L"UTF-16LE, but an odd number of bytes: %d\n", - header_len); - /* nchars = 0; Not needed - initialised to 0. */ - } - else { - /* no actual conversion needed. */ - memcpy(shebang_line, start, header_len); - nchars = header_len / sizeof(wchar_t); - } - break; - case CP_UTF32BE: - if (header_len % 4 != 0) { - debug(L"UTF-32BE, but not divisible by 4: %d\n", - header_len); - /* nchars = 0; Not needed - initialised to 0. */ - } - else { - for (i = header_len, j = header_len / 2; i > 0; i -= 4, - j -= 2) { - shebang_alias[j - 1] = start[i - 2]; - shebang_alias[j - 2] = start[i - 1]; - } - nchars = header_len / sizeof(wchar_t); - } - break; - case CP_UTF32LE: - if (header_len % 4 != 0) { - debug(L"UTF-32LE, but not divisible by 4: %d\n", - header_len); - /* nchars = 0; Not needed - initialised to 0. */ - } - else { - for (i = header_len, j = header_len / 2; i > 0; i -= 4, - j -= 2) { - shebang_alias[j - 1] = start[i - 3]; - shebang_alias[j - 2] = start[i - 4]; - } - nchars = header_len / sizeof(wchar_t); - } - break; - } - if (nchars > 0) { - shebang_line[--nchars] = L'\0'; - is_virt = parse_shebang(shebang_line, nchars, &command, - &suffix, &search); - if (command != NULL) { - debug(L"parse_shebang: found command: %ls\n", command); - if (!is_virt) { - invoke_child(command, suffix, cmdline); - } - else { - suffix = wcschr(command, L' '); - if (suffix != NULL) { - *suffix++ = L'\0'; - suffix = skip_whitespace(suffix); - } - if (wcsncmp(command, L"python", 6)) - error(RC_BAD_VIRTUAL_PATH, L"Unknown virtual \ -path '%ls'", command); - command += 6; /* skip past "python" */ - if (search && ((*command == L'\0') || isspace(*command))) { - /* Command is eligible for path search, and there - * is no version specification. - */ - debug(L"searching PATH for python executable\n"); - cmd = find_on_path(PYTHON_EXECUTABLE); - debug(L"Python on path: %ls\n", cmd ? cmd->value : L"<not found>"); - if (cmd) { - debug(L"located python on PATH: %ls\n", cmd->value); - invoke_child(cmd->value, suffix, cmdline); - /* Exit here, as we have found the command */ - return; - } - /* FALL THROUGH: No python found on PATH, so fall - * back to locating the correct installed python. - */ - } - if (*command && !validate_version(command)) - error(RC_BAD_VIRTUAL_PATH, L"Invalid version \ -specification: '%ls'.\nIn the first line of the script, 'python' needs to be \ -followed by a valid version specifier.\nPlease check the documentation.", - command); - /* TODO could call validate_version(command) */ - ip = locate_python(command, TRUE); - if (ip == NULL) { - error(RC_NO_PYTHON, L"Requested Python version \ -(%ls) is not installed", command); - } - else { - invoke_child(ip->executable, suffix, cmdline); - } - } - } - } - } - } -} - -static wchar_t * -skip_me(wchar_t * cmdline) -{ - BOOL quoted; - wchar_t c; - wchar_t * result = cmdline; - - quoted = cmdline[0] == L'\"'; - if (!quoted) - c = L' '; - else { - c = L'\"'; - ++result; - } - result = wcschr(result, c); - if (result == NULL) /* when, for example, just exe name on command line */ - result = L""; - else { - ++result; /* skip past space or closing quote */ - result = skip_whitespace(result); - } - return result; -} - -static DWORD version_high = 0; -static DWORD version_low = 0; - -static void -get_version_info(wchar_t * version_text, size_t size) -{ - WORD maj, min, rel, bld; - - if (!version_high && !version_low) - wcsncpy_s(version_text, size, L"0.1", _TRUNCATE); /* fallback */ - else { - maj = HIWORD(version_high); - min = LOWORD(version_high); - rel = HIWORD(version_low); - bld = LOWORD(version_low); - _snwprintf_s(version_text, size, _TRUNCATE, L"%d.%d.%d.%d", maj, - min, rel, bld); - } -} - -static void -show_help_text(wchar_t ** argv) -{ - wchar_t version_text [MAX_PATH]; -#if defined(_M_X64) - BOOL canDo64bit = TRUE; -#else - /* If we are a 32bit process on a 64bit Windows, first hit the 64bit keys. */ - BOOL canDo64bit = FALSE; - IsWow64Process(GetCurrentProcess(), &canDo64bit); -#endif - - get_version_info(version_text, MAX_PATH); - fwprintf(stdout, L"\ -Python Launcher for Windows Version %ls\n\n", version_text); - fwprintf(stdout, L"\ -usage:\n\ -%ls [launcher-args] [python-args] script [script-args]\n\n", argv[0]); - fputws(L"\ -Launcher arguments:\n\n\ --2 : Launch the latest Python 2.x version\n\ --3 : Launch the latest Python 3.x version\n\ --X.Y : Launch the specified Python version\n", stdout); - if (canDo64bit) { - fputws(L"\ - The above all default to 64 bit if a matching 64 bit python is present.\n\ --X.Y-32: Launch the specified 32bit Python version\n\ --X-32 : Launch the latest 32bit Python X version\n\ --X.Y-64: Launch the specified 64bit Python version\n\ --X-64 : Launch the latest 64bit Python X version", stdout); - } - fputws(L"\n-0 --list : List the available pythons", stdout); - fputws(L"\n-0p --list-paths : List with paths", stdout); - fputws(L"\n\nThe following help text is from Python:\n\n", stdout); - fflush(stdout); -} - -static BOOL -show_python_list(wchar_t ** argv) -{ - /* - * Display options -0 - */ - INSTALLED_PYTHON * result = NULL; - INSTALLED_PYTHON * ip = installed_pythons; /* List of installed pythons */ - INSTALLED_PYTHON * defpy = locate_python(L"", FALSE); - size_t i = 0; - wchar_t *p = argv[1]; - wchar_t *ver_fmt = L"-%ls-%d"; - wchar_t *fmt = L"\n %ls"; - wchar_t *defind = L" *"; /* Default indicator */ - - /* - * Output informational messages to stderr to keep output - * clean for use in pipes, etc. - */ - fwprintf(stderr, - L"Installed Pythons found by %s Launcher for Windows", argv[0]); - if (!_wcsicmp(p, L"-0p") || !_wcsicmp(p, L"--list-paths")) - fmt = L"\n %-15ls%ls"; /* include path */ - - if (num_installed_pythons == 0) /* We have somehow got here without searching for pythons */ - locate_all_pythons(); /* Find them, Populates installed_pythons */ - - if (num_installed_pythons == 0) /* No pythons found */ - fwprintf(stderr, L"\nNo Installed Pythons Found!"); - else - { - for (i = 0; i < num_installed_pythons; i++, ip++) { - wchar_t version[BUFSIZ]; - if (wcscmp(ip->version, L"venv") == 0) { - wcscpy_s(version, BUFSIZ, L"(venv)"); - } - else { - swprintf_s(version, BUFSIZ, ver_fmt, ip->version, ip->bits); - } - - if (ip->exe_display[0]) { - fwprintf(stdout, fmt, version, ip->exe_display); - } - else { - fwprintf(stdout, fmt, version, ip->executable); - } - /* If there is a default indicate it */ - if (defpy == ip) - fwprintf(stderr, defind); - } - } - - if ((defpy == NULL) && (num_installed_pythons > 0)) - /* We have pythons but none is the default */ - fwprintf(stderr, L"\n\nCan't find a Default Python.\n\n"); - else - fwprintf(stderr, L"\n\n"); /* End with a blank line */ - return FALSE; /* If this has been called we cannot continue */ -} - -#if defined(VENV_REDIRECT) - -static int -find_home_value(const char *buffer, const char **start, DWORD *length) -{ - for (const char *s = strstr(buffer, "home"); s; s = strstr(s + 1, "\nhome")) { - if (*s == '\n') { - ++s; - } - for (int i = 4; i > 0 && *s; --i, ++s); - - while (*s && iswspace(*s)) { - ++s; - } - if (*s != L'=') { - continue; - } - - do { - ++s; - } while (*s && iswspace(*s)); - - *start = s; - char *nl = strchr(s, '\n'); - if (nl) { - *length = (DWORD)((ptrdiff_t)nl - (ptrdiff_t)s); - } else { - *length = (DWORD)strlen(s); - } - return 1; - } - return 0; -} -#endif - -static wchar_t * -wcsdup_pad(const wchar_t *s, int padding, int *newlen) -{ - size_t len = wcslen(s); - len += 1 + padding; - wchar_t *r = (wchar_t *)malloc(len * sizeof(wchar_t)); - if (!r) { - return NULL; - } - if (wcscpy_s(r, len, s)) { - free(r); - return NULL; - } - *newlen = len < MAXINT ? (int)len : MAXINT; - return r; -} - -static wchar_t * -get_process_name() -{ - DWORD bufferLen = MAX_PATH; - DWORD len = bufferLen; - wchar_t *r = NULL; - - while (!r) { - r = (wchar_t *)malloc(bufferLen * sizeof(wchar_t)); - if (!r) { - error(RC_NO_MEMORY, L"out of memory"); - return NULL; - } - len = GetModuleFileNameW(NULL, r, bufferLen); - if (len == 0) { - free(r); - error(0, L"Failed to get module name"); - return NULL; - } else if (len == bufferLen && - GetLastError() == ERROR_INSUFFICIENT_BUFFER) { - free(r); - r = NULL; - bufferLen *= 2; - } - } - - return r; -} - -static int -process(int argc, wchar_t ** argv) -{ - wchar_t * wp; - wchar_t * command; - wchar_t * executable; - wchar_t * p; - wchar_t * argv0; - int rc = 0; - INSTALLED_PYTHON * ip; - BOOL valid; - DWORD size, attrs; - wchar_t message[MSGSIZE]; - void * version_data; - VS_FIXEDFILEINFO * file_info; - UINT block_size; -#if defined(VENV_REDIRECT) - wchar_t * venv_cfg_path; - int newlen; -#elif defined(SCRIPT_WRAPPER) - wchar_t * newcommand; - wchar_t * av[2]; - int newlen; - HRESULT hr; - int index; -#else - HRESULT hr; - int index; -#endif - - setvbuf(stderr, (char *)NULL, _IONBF, 0); - wp = get_env(L"PYLAUNCH_DEBUG"); - if ((wp != NULL) && (*wp != L'\0')) - log_fp = stderr; - -#if defined(_M_X64) - debug(L"launcher build: 64bit\n"); -#else - debug(L"launcher build: 32bit\n"); -#endif -#if defined(_WINDOWS) - debug(L"launcher executable: Windows\n"); -#else - debug(L"launcher executable: Console\n"); -#endif -#if !defined(VENV_REDIRECT) - /* Get the local appdata folder (non-roaming) */ - hr = SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, - NULL, 0, appdata_ini_path); - if (hr != S_OK) { - debug(L"SHGetFolderPath failed: %X\n", hr); - appdata_ini_path[0] = L'\0'; - } - else { - wcsncat_s(appdata_ini_path, MAX_PATH, L"\\py.ini", _TRUNCATE); - attrs = GetFileAttributesW(appdata_ini_path); - if (attrs == INVALID_FILE_ATTRIBUTES) { - debug(L"File '%ls' non-existent\n", appdata_ini_path); - appdata_ini_path[0] = L'\0'; - } else { - debug(L"Using local configuration file '%ls'\n", appdata_ini_path); - } - } -#endif - argv0 = get_process_name(); - size = GetFileVersionInfoSizeW(argv0, &size); - if (size == 0) { - winerror(GetLastError(), message, MSGSIZE); - debug(L"GetFileVersionInfoSize failed: %ls\n", message); - } - else { - version_data = malloc(size); - if (version_data) { - valid = GetFileVersionInfoW(argv0, 0, size, - version_data); - if (!valid) - debug(L"GetFileVersionInfo failed: %X\n", GetLastError()); - else { - valid = VerQueryValueW(version_data, L"\\", - (LPVOID *) &file_info, &block_size); - if (!valid) - debug(L"VerQueryValue failed: %X\n", GetLastError()); - else { - version_high = file_info->dwFileVersionMS; - version_low = file_info->dwFileVersionLS; - } - } - free(version_data); - } - } - -#if defined(VENV_REDIRECT) - /* Allocate some extra space for new filenames */ - venv_cfg_path = wcsdup_pad(argv0, 32, &newlen); - if (!venv_cfg_path) { - error(RC_NO_MEMORY, L"Failed to copy module name"); - } - p = wcsrchr(venv_cfg_path, L'\\'); - - if (p == NULL) { - error(RC_NO_VENV_CFG, L"No pyvenv.cfg file"); - } - p[0] = L'\0'; - wcscat_s(venv_cfg_path, newlen, L"\\pyvenv.cfg"); - attrs = GetFileAttributesW(venv_cfg_path); - if (attrs == INVALID_FILE_ATTRIBUTES) { - debug(L"File '%ls' non-existent\n", venv_cfg_path); - p[0] = '\0'; - p = wcsrchr(venv_cfg_path, L'\\'); - if (p != NULL) { - p[0] = '\0'; - wcscat_s(venv_cfg_path, newlen, L"\\pyvenv.cfg"); - attrs = GetFileAttributesW(venv_cfg_path); - if (attrs == INVALID_FILE_ATTRIBUTES) { - debug(L"File '%ls' non-existent\n", venv_cfg_path); - error(RC_NO_VENV_CFG, L"No pyvenv.cfg file"); - } - } - } - debug(L"Using venv configuration file '%ls'\n", venv_cfg_path); -#else - /* Allocate some extra space for new filenames */ - if (wcscpy_s(launcher_ini_path, MAX_PATH, argv0)) { - error(RC_NO_MEMORY, L"Failed to copy module name"); - } - p = wcsrchr(launcher_ini_path, L'\\'); - - if (p == NULL) { - debug(L"GetModuleFileNameW returned value has no backslash: %ls\n", - launcher_ini_path); - launcher_ini_path[0] = L'\0'; - } - else { - p[0] = L'\0'; - wcscat_s(launcher_ini_path, MAX_PATH, L"\\py.ini"); - attrs = GetFileAttributesW(launcher_ini_path); - if (attrs == INVALID_FILE_ATTRIBUTES) { - debug(L"File '%ls' non-existent\n", launcher_ini_path); - launcher_ini_path[0] = L'\0'; - } else { - debug(L"Using global configuration file '%ls'\n", launcher_ini_path); - } - } -#endif - - command = skip_me(GetCommandLineW()); - debug(L"Called with command line: %ls\n", command); - -#if !defined(VENV_REDIRECT) - /* bpo-35811: The __PYVENV_LAUNCHER__ variable is used to - * override sys.executable and locate the original prefix path. - * However, if it is silently inherited by a non-venv Python - * process, that process will believe it is running in the venv - * still. This is the only place where *we* can clear it (that is, - * when py.exe is being used to launch Python), so we do. - */ - SetEnvironmentVariableW(L"__PYVENV_LAUNCHER__", NULL); -#endif - -#if defined(SCRIPT_WRAPPER) - /* The launcher is being used in "script wrapper" mode. - * There should therefore be a Python script named <exename>-script.py in - * the same directory as the launcher executable. - * Put the script name into argv as the first (script name) argument. - */ - - /* Get the wrapped script name - if the script is not present, this will - * terminate the program with an error. - */ - locate_wrapped_script(); - - /* Add the wrapped script to the start of command */ - newlen = wcslen(wrapped_script_path) + wcslen(command) + 2; /* ' ' + NUL */ - newcommand = malloc(sizeof(wchar_t) * newlen); - if (!newcommand) { - error(RC_NO_MEMORY, L"Could not allocate new command line"); - } - else { - wcscpy_s(newcommand, newlen, wrapped_script_path); - wcscat_s(newcommand, newlen, L" "); - wcscat_s(newcommand, newlen, command); - debug(L"Running wrapped script with command line '%ls'\n", newcommand); - read_commands(); - av[0] = wrapped_script_path; - av[1] = NULL; - maybe_handle_shebang(av, newcommand); - /* Returns if no shebang line - pass to default processing */ - command = newcommand; - valid = FALSE; - } -#elif defined(VENV_REDIRECT) - { - FILE *f; - char buffer[4096]; /* 4KB should be enough for anybody */ - char *start; - DWORD len, cch, cch_actual; - size_t cb; - if (_wfopen_s(&f, venv_cfg_path, L"r")) { - error(RC_BAD_VENV_CFG, L"Cannot read '%ls'", venv_cfg_path); - } - cb = fread_s(buffer, sizeof(buffer), sizeof(buffer[0]), - sizeof(buffer) / sizeof(buffer[0]), f); - fclose(f); - - if (!find_home_value(buffer, &start, &len)) { - error(RC_BAD_VENV_CFG, L"Cannot find home in '%ls'", - venv_cfg_path); - } - - cch = MultiByteToWideChar(CP_UTF8, 0, start, len, NULL, 0); - if (!cch) { - error(0, L"Cannot determine memory for home path"); - } - cch += (DWORD)wcslen(PYTHON_EXECUTABLE) + 1 + 1; /* include sep and null */ - executable = (wchar_t *)malloc(cch * sizeof(wchar_t)); - if (executable == NULL) { - error(RC_NO_MEMORY, L"A memory allocation failed"); - } - cch_actual = MultiByteToWideChar(CP_UTF8, 0, start, len, executable, cch); - if (!cch_actual) { - error(RC_BAD_VENV_CFG, L"Cannot decode home path in '%ls'", - venv_cfg_path); - } - if (executable[cch_actual - 1] != L'\\') { - executable[cch_actual++] = L'\\'; - executable[cch_actual] = L'\0'; - } - if (wcscat_s(executable, cch, PYTHON_EXECUTABLE)) { - error(RC_BAD_VENV_CFG, L"Cannot create executable path from '%ls'", - venv_cfg_path); - } - if (GetFileAttributesW(executable) == INVALID_FILE_ATTRIBUTES) { - error(RC_NO_PYTHON, L"No Python at '%ls'", executable); - } - if (!SetEnvironmentVariableW(L"__PYVENV_LAUNCHER__", argv0)) { - error(0, L"Failed to set launcher environment"); - } - valid = 1; - } -#else - if (argc <= 1) { - valid = FALSE; - p = NULL; - } - else { - p = argv[1]; - if ((argc == 2) && // list version args - (!wcsncmp(p, L"-0", wcslen(L"-0")) || - !wcsncmp(p, L"--list", wcslen(L"--list")))) - { - show_python_list(argv); - return rc; - } - valid = valid && (*p == L'-') && validate_version(&p[1]); - if (valid) { - ip = locate_python(&p[1], FALSE); - if (ip == NULL) - { - fwprintf(stdout, \ - L"Python %ls not found!\n", &p[1]); - valid = show_python_list(argv); - error(RC_NO_PYTHON, L"Requested Python version (%ls) not \ -installed, use -0 for available pythons", &p[1]); - } - executable = ip->executable; - command += wcslen(p); - command = skip_whitespace(command); - } - else { - for (index = 1; index < argc; ++index) { - if (*argv[index] != L'-') - break; - } - if (index < argc) { - read_commands(); - maybe_handle_shebang(&argv[index], command); - } - } - } -#endif - - if (!valid) { - if ((argc == 2) && (!_wcsicmp(p, L"-h") || !_wcsicmp(p, L"--help"))) - show_help_text(argv); - if ((argc == 2) && - (!_wcsicmp(p, L"-0") || !_wcsicmp(p, L"--list") || - !_wcsicmp(p, L"-0p") || !_wcsicmp(p, L"--list-paths"))) - { - executable = NULL; /* Info call only */ - } - else { - /* look for the default Python */ - ip = locate_python(L"", FALSE); - if (ip == NULL) - error(RC_NO_PYTHON, L"Can't find a default Python."); - executable = ip->executable; - } - } - if (executable != NULL) - invoke_child(executable, NULL, command); - else - rc = RC_NO_PYTHON; - return rc; -} - -#if defined(_WINDOWS) - -int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, - LPWSTR lpstrCmd, int nShow) -{ - return process(__argc, __wargv); -} - -#else - -int cdecl wmain(int argc, wchar_t ** argv) -{ - return process(argc, argv); -} - -#endif |