Home | History | Annotate | Download | only in PC
      1 /*
      2  * Copyright (C) 2011-2013 Vinay Sajip.
      3  * Licensed to PSF under a contributor agreement.
      4  *
      5  * Based on the work of:
      6  *
      7  * Mark Hammond (original author of Python version)
      8  * Curt Hagenlocher (job management)
      9  */
     10 
     11 #include <windows.h>
     12 #include <shlobj.h>
     13 #include <stdio.h>
     14 #include <tchar.h>
     15 
     16 #define BUFSIZE 256
     17 #define MSGSIZE 1024
     18 
     19 /* Build options. */
     20 #define SKIP_PREFIX
     21 #define SEARCH_PATH
     22 
     23 /* Error codes */
     24 
     25 #define RC_NO_STD_HANDLES   100
     26 #define RC_CREATE_PROCESS   101
     27 #define RC_BAD_VIRTUAL_PATH 102
     28 #define RC_NO_PYTHON        103
     29 #define RC_NO_MEMORY        104
     30 /*
     31  * SCRIPT_WRAPPER is used to choose between two variants of an executable built
     32  * from this source file. If not defined, the PEP 397 Python launcher is built;
     33  * if defined, a script launcher of the type used by setuptools is built, which
     34  * looks for a script name related to the executable name and runs that script
     35  * with the appropriate Python interpreter.
     36  *
     37  * SCRIPT_WRAPPER should be undefined in the source, and defined in a VS project
     38  * which builds the setuptools-style launcher.
     39  */
     40 #if defined(SCRIPT_WRAPPER)
     41 #define RC_NO_SCRIPT        105
     42 #endif
     43 
     44 /* Just for now - static definition */
     45 
     46 static FILE * log_fp = NULL;
     47 
     48 static wchar_t *
     49 skip_whitespace(wchar_t * p)
     50 {
     51     while (*p && isspace(*p))
     52         ++p;
     53     return p;
     54 }
     55 
     56 static void
     57 debug(wchar_t * format, ...)
     58 {
     59     va_list va;
     60 
     61     if (log_fp != NULL) {
     62         va_start(va, format);
     63         vfwprintf_s(log_fp, format, va);
     64     }
     65 }
     66 
     67 static void
     68 winerror(int rc, wchar_t * message, int size)
     69 {
     70     FormatMessageW(
     71         FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
     72         NULL, rc, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
     73         message, size, NULL);
     74 }
     75 
     76 static void
     77 error(int rc, wchar_t * format, ... )
     78 {
     79     va_list va;
     80     wchar_t message[MSGSIZE];
     81     wchar_t win_message[MSGSIZE];
     82     int len;
     83 
     84     va_start(va, format);
     85     len = _vsnwprintf_s(message, MSGSIZE, _TRUNCATE, format, va);
     86 
     87     if (rc == 0) {  /* a Windows error */
     88         winerror(GetLastError(), win_message, MSGSIZE);
     89         if (len >= 0) {
     90             _snwprintf_s(&message[len], MSGSIZE - len, _TRUNCATE, L": %ls",
     91                          win_message);
     92         }
     93     }
     94 
     95 #if !defined(_WINDOWS)
     96     fwprintf(stderr, L"%ls\n", message);
     97 #else
     98     MessageBox(NULL, message, TEXT("Python Launcher is sorry to say ..."),
     99                MB_OK);
    100 #endif
    101     exit(rc);
    102 }
    103 
    104 /*
    105  * This function is here to simplify memory management
    106  * and to treat blank values as if they are absent.
    107  */
    108 static wchar_t * get_env(wchar_t * key)
    109 {
    110     /* This is not thread-safe, just like getenv */
    111     static wchar_t buf[BUFSIZE];
    112     DWORD result = GetEnvironmentVariableW(key, buf, BUFSIZE);
    113 
    114     if (result >= BUFSIZE) {
    115         /* Large environment variable. Accept some leakage */
    116         wchar_t *buf2 = (wchar_t*)malloc(sizeof(wchar_t) * (result+1));
    117         if (buf2 == NULL) {
    118             error(RC_NO_MEMORY, L"Could not allocate environment buffer");
    119         }
    120         GetEnvironmentVariableW(key, buf2, result);
    121         return buf2;
    122     }
    123 
    124     if (result == 0)
    125         /* Either some error, e.g. ERROR_ENVVAR_NOT_FOUND,
    126            or an empty environment variable. */
    127         return NULL;
    128 
    129     return buf;
    130 }
    131 
    132 #if defined(_WINDOWS)
    133 
    134 #define PYTHON_EXECUTABLE L"pythonw.exe"
    135 
    136 #else
    137 
    138 #define PYTHON_EXECUTABLE L"python.exe"
    139 
    140 #endif
    141 
    142 #define MAX_VERSION_SIZE    4
    143 
    144 typedef struct {
    145     wchar_t version[MAX_VERSION_SIZE]; /* m.n */
    146     int bits;   /* 32 or 64 */
    147     wchar_t executable[MAX_PATH];
    148 } INSTALLED_PYTHON;
    149 
    150 /*
    151  * To avoid messing about with heap allocations, just assume we can allocate
    152  * statically and never have to deal with more versions than this.
    153  */
    154 #define MAX_INSTALLED_PYTHONS   100
    155 
    156 static INSTALLED_PYTHON installed_pythons[MAX_INSTALLED_PYTHONS];
    157 
    158 static size_t num_installed_pythons = 0;
    159 
    160 /*
    161  * To hold SOFTWARE\Python\PythonCore\X.Y...\InstallPath
    162  * The version name can be longer than MAX_VERSION_SIZE, but will be
    163  * truncated to just X.Y for comparisons.
    164  */
    165 #define IP_BASE_SIZE 40
    166 #define IP_VERSION_SIZE 8
    167 #define IP_SIZE (IP_BASE_SIZE + IP_VERSION_SIZE)
    168 #define CORE_PATH L"SOFTWARE\\Python\\PythonCore"
    169 
    170 static wchar_t * location_checks[] = {
    171     L"\\",
    172     L"\\PCBuild\\win32\\",
    173     L"\\PCBuild\\amd64\\",
    174     // To support early 32bit versions of Python that stuck the build binaries
    175     // directly in PCBuild...
    176     L"\\PCBuild\\",
    177     NULL
    178 };
    179 
    180 static INSTALLED_PYTHON *
    181 find_existing_python(wchar_t * path)
    182 {
    183     INSTALLED_PYTHON * result = NULL;
    184     size_t i;
    185     INSTALLED_PYTHON * ip;
    186 
    187     for (i = 0, ip = installed_pythons; i < num_installed_pythons; i++, ip++) {
    188         if (_wcsicmp(path, ip->executable) == 0) {
    189             result = ip;
    190             break;
    191         }
    192     }
    193     return result;
    194 }
    195 
    196 static void
    197 locate_pythons_for_key(HKEY root, REGSAM flags)
    198 {
    199     HKEY core_root, ip_key;
    200     LSTATUS status = RegOpenKeyExW(root, CORE_PATH, 0, flags, &core_root);
    201     wchar_t message[MSGSIZE];
    202     DWORD i;
    203     size_t n;
    204     BOOL ok;
    205     DWORD type, data_size, attrs;
    206     INSTALLED_PYTHON * ip, * pip;
    207     wchar_t ip_version[IP_VERSION_SIZE];
    208     wchar_t ip_path[IP_SIZE];
    209     wchar_t * check;
    210     wchar_t ** checkp;
    211     wchar_t *key_name = (root == HKEY_LOCAL_MACHINE) ? L"HKLM" : L"HKCU";
    212 
    213     if (status != ERROR_SUCCESS)
    214         debug(L"locate_pythons_for_key: unable to open PythonCore key in %ls\n",
    215               key_name);
    216     else {
    217         ip = &installed_pythons[num_installed_pythons];
    218         for (i = 0; num_installed_pythons < MAX_INSTALLED_PYTHONS; i++) {
    219             status = RegEnumKeyW(core_root, i, ip_version, IP_VERSION_SIZE);
    220             if (status != ERROR_SUCCESS) {
    221                 if (status != ERROR_NO_MORE_ITEMS) {
    222                     /* unexpected error */
    223                     winerror(status, message, MSGSIZE);
    224                     debug(L"Can't enumerate registry key for version %ls: %ls\n",
    225                           ip_version, message);
    226                 }
    227                 break;
    228             }
    229             else {
    230                 wcsncpy_s(ip->version, MAX_VERSION_SIZE, ip_version,
    231                           MAX_VERSION_SIZE-1);
    232                 _snwprintf_s(ip_path, IP_SIZE, _TRUNCATE,
    233                              L"%ls\\%ls\\InstallPath", CORE_PATH, ip_version);
    234                 status = RegOpenKeyExW(root, ip_path, 0, flags, &ip_key);
    235                 if (status != ERROR_SUCCESS) {
    236                     winerror(status, message, MSGSIZE);
    237                     // Note: 'message' already has a trailing \n
    238                     debug(L"%ls\\%ls: %ls", key_name, ip_path, message);
    239                     continue;
    240                 }
    241                 data_size = sizeof(ip->executable) - 1;
    242                 status = RegQueryValueExW(ip_key, NULL, NULL, &type,
    243                                           (LPBYTE)ip->executable, &data_size);
    244                 RegCloseKey(ip_key);
    245                 if (status != ERROR_SUCCESS) {
    246                     winerror(status, message, MSGSIZE);
    247                     debug(L"%ls\\%ls: %ls\n", key_name, ip_path, message);
    248                     continue;
    249                 }
    250                 if (type == REG_SZ) {
    251                     data_size = data_size / sizeof(wchar_t) - 1;  /* for NUL */
    252                     if (ip->executable[data_size - 1] == L'\\')
    253                         --data_size; /* reg value ended in a backslash */
    254                     /* ip->executable is data_size long */
    255                     for (checkp = location_checks; *checkp; ++checkp) {
    256                         check = *checkp;
    257                         _snwprintf_s(&ip->executable[data_size],
    258                                      MAX_PATH - data_size,
    259                                      MAX_PATH - data_size,
    260                                      L"%ls%ls", check, PYTHON_EXECUTABLE);
    261                         attrs = GetFileAttributesW(ip->executable);
    262                         if (attrs == INVALID_FILE_ATTRIBUTES) {
    263                             winerror(GetLastError(), message, MSGSIZE);
    264                             debug(L"locate_pythons_for_key: %ls: %ls",
    265                                   ip->executable, message);
    266                         }
    267                         else if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
    268                             debug(L"locate_pythons_for_key: '%ls' is a \
    269 directory\n",
    270                                   ip->executable, attrs);
    271                         }
    272                         else if (find_existing_python(ip->executable)) {
    273                             debug(L"locate_pythons_for_key: %ls: already \
    274 found\n", ip->executable);
    275                         }
    276                         else {
    277                             /* check the executable type. */
    278                             ok = GetBinaryTypeW(ip->executable, &attrs);
    279                             if (!ok) {
    280                                 debug(L"Failure getting binary type: %ls\n",
    281                                       ip->executable);
    282                             }
    283                             else {
    284                                 if (attrs == SCS_64BIT_BINARY)
    285                                     ip->bits = 64;
    286                                 else if (attrs == SCS_32BIT_BINARY)
    287                                     ip->bits = 32;
    288                                 else
    289                                     ip->bits = 0;
    290                                 if (ip->bits == 0) {
    291                                     debug(L"locate_pythons_for_key: %ls: \
    292 invalid binary type: %X\n",
    293                                           ip->executable, attrs);
    294                                 }
    295                                 else {
    296                                     if (wcschr(ip->executable, L' ') != NULL) {
    297                                         /* has spaces, so quote */
    298                                         n = wcslen(ip->executable);
    299                                         memmove(&ip->executable[1],
    300                                                 ip->executable, n * sizeof(wchar_t));
    301                                         ip->executable[0] = L'\"';
    302                                         ip->executable[n + 1] = L'\"';
    303                                         ip->executable[n + 2] = L'\0';
    304                                     }
    305                                     debug(L"locate_pythons_for_key: %ls \
    306 is a %dbit executable\n",
    307                                         ip->executable, ip->bits);
    308                                     ++num_installed_pythons;
    309                                     pip = ip++;
    310                                     if (num_installed_pythons >=
    311                                         MAX_INSTALLED_PYTHONS)
    312                                         break;
    313                                     /* Copy over the attributes for the next */
    314                                     *ip = *pip;
    315                                 }
    316                             }
    317                         }
    318                     }
    319                 }
    320             }
    321         }
    322         RegCloseKey(core_root);
    323     }
    324 }
    325 
    326 static int
    327 compare_pythons(const void * p1, const void * p2)
    328 {
    329     INSTALLED_PYTHON * ip1 = (INSTALLED_PYTHON *) p1;
    330     INSTALLED_PYTHON * ip2 = (INSTALLED_PYTHON *) p2;
    331     /* note reverse sorting on version */
    332     int result = wcscmp(ip2->version, ip1->version);
    333 
    334     if (result == 0)
    335         result = ip2->bits - ip1->bits; /* 64 before 32 */
    336     return result;
    337 }
    338 
    339 static void
    340 locate_all_pythons()
    341 {
    342 #if defined(_M_X64)
    343     // If we are a 64bit process, first hit the 32bit keys.
    344     debug(L"locating Pythons in 32bit registry\n");
    345     locate_pythons_for_key(HKEY_CURRENT_USER, KEY_READ | KEY_WOW64_32KEY);
    346     locate_pythons_for_key(HKEY_LOCAL_MACHINE, KEY_READ | KEY_WOW64_32KEY);
    347 #else
    348     // If we are a 32bit process on a 64bit Windows, first hit the 64bit keys.
    349     BOOL f64 = FALSE;
    350     if (IsWow64Process(GetCurrentProcess(), &f64) && f64) {
    351         debug(L"locating Pythons in 64bit registry\n");
    352         locate_pythons_for_key(HKEY_CURRENT_USER, KEY_READ | KEY_WOW64_64KEY);
    353         locate_pythons_for_key(HKEY_LOCAL_MACHINE, KEY_READ | KEY_WOW64_64KEY);
    354     }
    355 #endif
    356     // now hit the "native" key for this process bittedness.
    357     debug(L"locating Pythons in native registry\n");
    358     locate_pythons_for_key(HKEY_CURRENT_USER, KEY_READ);
    359     locate_pythons_for_key(HKEY_LOCAL_MACHINE, KEY_READ);
    360     qsort(installed_pythons, num_installed_pythons, sizeof(INSTALLED_PYTHON),
    361           compare_pythons);
    362 }
    363 
    364 static INSTALLED_PYTHON *
    365 find_python_by_version(wchar_t const * wanted_ver)
    366 {
    367     INSTALLED_PYTHON * result = NULL;
    368     INSTALLED_PYTHON * ip = installed_pythons;
    369     size_t i, n;
    370     size_t wlen = wcslen(wanted_ver);
    371     int bits = 0;
    372 
    373     if (wcsstr(wanted_ver, L"-32"))
    374         bits = 32;
    375     for (i = 0; i < num_installed_pythons; i++, ip++) {
    376         n = wcslen(ip->version);
    377         if (n > wlen)
    378             n = wlen;
    379         if ((wcsncmp(ip->version, wanted_ver, n) == 0) &&
    380             /* bits == 0 => don't care */
    381             ((bits == 0) || (ip->bits == bits))) {
    382             result = ip;
    383             break;
    384         }
    385     }
    386     return result;
    387 }
    388 
    389 
    390 static wchar_t *
    391 find_python_by_venv()
    392 {
    393     static wchar_t venv_python[MAX_PATH];
    394     wchar_t *virtual_env = get_env(L"VIRTUAL_ENV");
    395     DWORD attrs;
    396 
    397     /* Check for VIRTUAL_ENV environment variable */
    398     if (virtual_env == NULL || virtual_env[0] == L'\0') {
    399         return NULL;
    400     }
    401 
    402     /* Check for a python executable in the venv */
    403     debug(L"Checking for Python executable in virtual env '%ls'\n", virtual_env);
    404     _snwprintf_s(venv_python, MAX_PATH, _TRUNCATE,
    405             L"%ls\\Scripts\\%ls", virtual_env, PYTHON_EXECUTABLE);
    406     attrs = GetFileAttributesW(venv_python);
    407     if (attrs == INVALID_FILE_ATTRIBUTES) {
    408         debug(L"Python executable %ls missing from virtual env\n", venv_python);
    409         return NULL;
    410     }
    411 
    412     return venv_python;
    413 }
    414 
    415 static wchar_t appdata_ini_path[MAX_PATH];
    416 static wchar_t launcher_ini_path[MAX_PATH];
    417 
    418 /*
    419  * Get a value either from the environment or a configuration file.
    420  * The key passed in will either be "python", "python2" or "python3".
    421  */
    422 static wchar_t *
    423 get_configured_value(wchar_t * key)
    424 {
    425 /*
    426  * Note: this static value is used to return a configured value
    427  * obtained either from the environment or configuration file.
    428  * This should be OK since there wouldn't be any concurrent calls.
    429  */
    430     static wchar_t configured_value[MSGSIZE];
    431     wchar_t * result = NULL;
    432     wchar_t * found_in = L"environment";
    433     DWORD size;
    434 
    435     /* First, search the environment. */
    436     _snwprintf_s(configured_value, MSGSIZE, _TRUNCATE, L"py_%ls", key);
    437     result = get_env(configured_value);
    438     if (result == NULL && appdata_ini_path[0]) {
    439         /* Not in environment: check local configuration. */
    440         size = GetPrivateProfileStringW(L"defaults", key, NULL,
    441                                         configured_value, MSGSIZE,
    442                                         appdata_ini_path);
    443         if (size > 0) {
    444             result = configured_value;
    445             found_in = appdata_ini_path;
    446         }
    447     }
    448     if (result == NULL && launcher_ini_path[0]) {
    449         /* Not in environment or local: check global configuration. */
    450         size = GetPrivateProfileStringW(L"defaults", key, NULL,
    451                                         configured_value, MSGSIZE,
    452                                         launcher_ini_path);
    453         if (size > 0) {
    454             result = configured_value;
    455             found_in = launcher_ini_path;
    456         }
    457     }
    458     if (result) {
    459         debug(L"found configured value '%ls=%ls' in %ls\n",
    460               key, result, found_in ? found_in : L"(unknown)");
    461     } else {
    462         debug(L"found no configured value for '%ls'\n", key);
    463     }
    464     return result;
    465 }
    466 
    467 static INSTALLED_PYTHON *
    468 locate_python(wchar_t * wanted_ver, BOOL from_shebang)
    469 {
    470     static wchar_t config_key [] = { L"pythonX" };
    471     static wchar_t * last_char = &config_key[sizeof(config_key) /
    472                                              sizeof(wchar_t) - 2];
    473     INSTALLED_PYTHON * result = NULL;
    474     size_t n = wcslen(wanted_ver);
    475     wchar_t * configured_value;
    476 
    477     if (num_installed_pythons == 0)
    478         locate_all_pythons();
    479 
    480     if (n == 1) {   /* just major version specified */
    481         *last_char = *wanted_ver;
    482         configured_value = get_configured_value(config_key);
    483         if (configured_value != NULL)
    484             wanted_ver = configured_value;
    485     }
    486     if (*wanted_ver) {
    487         result = find_python_by_version(wanted_ver);
    488         debug(L"search for Python version '%ls' found ", wanted_ver);
    489         if (result) {
    490             debug(L"'%ls'\n", result->executable);
    491         } else {
    492             debug(L"no interpreter\n");
    493         }
    494     }
    495     else {
    496         *last_char = L'\0'; /* look for an overall default */
    497         configured_value = get_configured_value(config_key);
    498         if (configured_value)
    499             result = find_python_by_version(configured_value);
    500         /* Not found a value yet - try by major version.
    501          * If we're looking for an interpreter specified in a shebang line,
    502          * we want to try Python 2 first, then Python 3 (for Unix and backward
    503          * compatibility). If we're being called interactively, assume the user
    504          * wants the latest version available, so try Python 3 first, then
    505          * Python 2.
    506          */
    507         if (result == NULL)
    508             result = find_python_by_version(from_shebang ? L"2" : L"3");
    509         if (result == NULL)
    510             result = find_python_by_version(from_shebang ? L"3" : L"2");
    511         debug(L"search for default Python found ");
    512         if (result) {
    513             debug(L"version %ls at '%ls'\n",
    514                   result->version, result->executable);
    515         } else {
    516             debug(L"no interpreter\n");
    517         }
    518     }
    519     return result;
    520 }
    521 
    522 #if defined(SCRIPT_WRAPPER)
    523 /*
    524  * Check for a script located alongside the executable
    525  */
    526 
    527 #if defined(_WINDOWS)
    528 #define SCRIPT_SUFFIX L"-script.pyw"
    529 #else
    530 #define SCRIPT_SUFFIX L"-script.py"
    531 #endif
    532 
    533 static wchar_t wrapped_script_path[MAX_PATH];
    534 
    535 /* Locate the script being wrapped.
    536  *
    537  * This code should store the name of the wrapped script in
    538  * wrapped_script_path, or terminate the program with an error if there is no
    539  * valid wrapped script file.
    540  */
    541 static void
    542 locate_wrapped_script()
    543 {
    544     wchar_t * p;
    545     size_t plen;
    546     DWORD attrs;
    547 
    548     plen = GetModuleFileNameW(NULL, wrapped_script_path, MAX_PATH);
    549     p = wcsrchr(wrapped_script_path, L'.');
    550     if (p == NULL) {
    551         debug(L"GetModuleFileNameW returned value has no extension: %ls\n",
    552               wrapped_script_path);
    553         error(RC_NO_SCRIPT, L"Wrapper name '%ls' is not valid.", wrapped_script_path);
    554     }
    555 
    556     wcsncpy_s(p, MAX_PATH - (p - wrapped_script_path) + 1, SCRIPT_SUFFIX, _TRUNCATE);
    557     attrs = GetFileAttributesW(wrapped_script_path);
    558     if (attrs == INVALID_FILE_ATTRIBUTES) {
    559         debug(L"File '%ls' non-existent\n", wrapped_script_path);
    560         error(RC_NO_SCRIPT, L"Script file '%ls' is not present.", wrapped_script_path);
    561     }
    562 
    563     debug(L"Using wrapped script file '%ls'\n", wrapped_script_path);
    564 }
    565 #endif
    566 
    567 /*
    568  * Process creation code
    569  */
    570 
    571 static BOOL
    572 safe_duplicate_handle(HANDLE in, HANDLE * pout)
    573 {
    574     BOOL ok;
    575     HANDLE process = GetCurrentProcess();
    576     DWORD rc;
    577 
    578     *pout = NULL;
    579     ok = DuplicateHandle(process, in, process, pout, 0, TRUE,
    580                          DUPLICATE_SAME_ACCESS);
    581     if (!ok) {
    582         rc = GetLastError();
    583         if (rc == ERROR_INVALID_HANDLE) {
    584             debug(L"DuplicateHandle returned ERROR_INVALID_HANDLE\n");
    585             ok = TRUE;
    586         }
    587         else {
    588             debug(L"DuplicateHandle returned %d\n", rc);
    589         }
    590     }
    591     return ok;
    592 }
    593 
    594 static BOOL WINAPI
    595 ctrl_c_handler(DWORD code)
    596 {
    597     return TRUE;    /* We just ignore all control events. */
    598 }
    599 
    600 static void
    601 run_child(wchar_t * cmdline)
    602 {
    603     HANDLE job;
    604     JOBOBJECT_EXTENDED_LIMIT_INFORMATION info;
    605     DWORD rc;
    606     BOOL ok;
    607     STARTUPINFOW si;
    608     PROCESS_INFORMATION pi;
    609 
    610 #if defined(_WINDOWS)
    611     // When explorer launches a Windows (GUI) application, it displays
    612     // the "app starting" (the "pointer + hourglass") cursor for a number
    613     // of seconds, or until the app does something UI-ish (eg, creating a
    614     // window, or fetching a message).  As this launcher doesn't do this
    615     // directly, that cursor remains even after the child process does these
    616     // things.  We avoid that by doing a simple post+get message.
    617     // See http://bugs.python.org/issue17290 and
    618     // https://bitbucket.org/vinay.sajip/pylauncher/issue/20/busy-cursor-for-a-long-time-when-running
    619     MSG msg;
    620 
    621     PostMessage(0, 0, 0, 0);
    622     GetMessage(&msg, 0, 0, 0);
    623 #endif
    624 
    625     debug(L"run_child: about to run '%ls'\n", cmdline);
    626     job = CreateJobObject(NULL, NULL);
    627     ok = QueryInformationJobObject(job, JobObjectExtendedLimitInformation,
    628                                   &info, sizeof(info), &rc);
    629     if (!ok || (rc != sizeof(info)) || !job)
    630         error(RC_CREATE_PROCESS, L"Job information querying failed");
    631     info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE |
    632                                              JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
    633     ok = SetInformationJobObject(job, JobObjectExtendedLimitInformation, &info,
    634                                  sizeof(info));
    635     if (!ok)
    636         error(RC_CREATE_PROCESS, L"Job information setting failed");
    637     memset(&si, 0, sizeof(si));
    638     si.cb = sizeof(si);
    639     ok = safe_duplicate_handle(GetStdHandle(STD_INPUT_HANDLE), &si.hStdInput);
    640     if (!ok)
    641         error(RC_NO_STD_HANDLES, L"stdin duplication failed");
    642     ok = safe_duplicate_handle(GetStdHandle(STD_OUTPUT_HANDLE), &si.hStdOutput);
    643     if (!ok)
    644         error(RC_NO_STD_HANDLES, L"stdout duplication failed");
    645     ok = safe_duplicate_handle(GetStdHandle(STD_ERROR_HANDLE), &si.hStdError);
    646     if (!ok)
    647         error(RC_NO_STD_HANDLES, L"stderr duplication failed");
    648 
    649     ok = SetConsoleCtrlHandler(ctrl_c_handler, TRUE);
    650     if (!ok)
    651         error(RC_CREATE_PROCESS, L"control handler setting failed");
    652 
    653     si.dwFlags = STARTF_USESTDHANDLES;
    654     ok = CreateProcessW(NULL, cmdline, NULL, NULL, TRUE,
    655                         0, NULL, NULL, &si, &pi);
    656     if (!ok)
    657         error(RC_CREATE_PROCESS, L"Unable to create process using '%ls'", cmdline);
    658     AssignProcessToJobObject(job, pi.hProcess);
    659     CloseHandle(pi.hThread);
    660     WaitForSingleObjectEx(pi.hProcess, INFINITE, FALSE);
    661     ok = GetExitCodeProcess(pi.hProcess, &rc);
    662     if (!ok)
    663         error(RC_CREATE_PROCESS, L"Failed to get exit code of process");
    664     debug(L"child process exit code: %d\n", rc);
    665     exit(rc);
    666 }
    667 
    668 static void
    669 invoke_child(wchar_t * executable, wchar_t * suffix, wchar_t * cmdline)
    670 {
    671     wchar_t * child_command;
    672     size_t child_command_size;
    673     BOOL no_suffix = (suffix == NULL) || (*suffix == L'\0');
    674     BOOL no_cmdline = (*cmdline == L'\0');
    675 
    676     if (no_suffix && no_cmdline)
    677         run_child(executable);
    678     else {
    679         if (no_suffix) {
    680             /* add 2 for space separator + terminating NUL. */
    681             child_command_size = wcslen(executable) + wcslen(cmdline) + 2;
    682         }
    683         else {
    684             /* add 3 for 2 space separators + terminating NUL. */
    685             child_command_size = wcslen(executable) + wcslen(suffix) +
    686                                     wcslen(cmdline) + 3;
    687         }
    688         child_command = calloc(child_command_size, sizeof(wchar_t));
    689         if (child_command == NULL)
    690             error(RC_CREATE_PROCESS, L"unable to allocate %d bytes for child command.",
    691                   child_command_size);
    692         if (no_suffix)
    693             _snwprintf_s(child_command, child_command_size,
    694                          child_command_size - 1, L"%ls %ls",
    695                          executable, cmdline);
    696         else
    697             _snwprintf_s(child_command, child_command_size,
    698                          child_command_size - 1, L"%ls %ls %ls",
    699                          executable, suffix, cmdline);
    700         run_child(child_command);
    701         free(child_command);
    702     }
    703 }
    704 
    705 typedef struct {
    706     wchar_t *shebang;
    707     BOOL search;
    708 } SHEBANG;
    709 
    710 static SHEBANG builtin_virtual_paths [] = {
    711     { L"/usr/bin/env python", TRUE },
    712     { L"/usr/bin/python", FALSE },
    713     { L"/usr/local/bin/python", FALSE },
    714     { L"python", FALSE },
    715     { NULL, FALSE },
    716 };
    717 
    718 /* For now, a static array of commands. */
    719 
    720 #define MAX_COMMANDS 100
    721 
    722 typedef struct {
    723     wchar_t key[MAX_PATH];
    724     wchar_t value[MSGSIZE];
    725 } COMMAND;
    726 
    727 static COMMAND commands[MAX_COMMANDS];
    728 static int num_commands = 0;
    729 
    730 #if defined(SKIP_PREFIX)
    731 
    732 static wchar_t * builtin_prefixes [] = {
    733     /* These must be in an order that the longest matches should be found,
    734      * i.e. if the prefix is "/usr/bin/env ", it should match that entry
    735      * *before* matching "/usr/bin/".
    736      */
    737     L"/usr/bin/env ",
    738     L"/usr/bin/",
    739     L"/usr/local/bin/",
    740     NULL
    741 };
    742 
    743 static wchar_t * skip_prefix(wchar_t * name)
    744 {
    745     wchar_t ** pp = builtin_prefixes;
    746     wchar_t * result = name;
    747     wchar_t * p;
    748     size_t n;
    749 
    750     for (; p = *pp; pp++) {
    751         n = wcslen(p);
    752         if (_wcsnicmp(p, name, n) == 0) {
    753             result += n;   /* skip the prefix */
    754             if (p[n - 1] == L' ') /* No empty strings in table, so n > 1 */
    755                 result = skip_whitespace(result);
    756             break;
    757         }
    758     }
    759     return result;
    760 }
    761 
    762 #endif
    763 
    764 #if defined(SEARCH_PATH)
    765 
    766 static COMMAND path_command;
    767 
    768 static COMMAND * find_on_path(wchar_t * name)
    769 {
    770     wchar_t * pathext;
    771     size_t    varsize;
    772     wchar_t * context = NULL;
    773     wchar_t * extension;
    774     COMMAND * result = NULL;
    775     DWORD     len;
    776     errno_t   rc;
    777 
    778     wcscpy_s(path_command.key, MAX_PATH, name);
    779     if (wcschr(name, L'.') != NULL) {
    780         /* assume it has an extension. */
    781         len = SearchPathW(NULL, name, NULL, MSGSIZE, path_command.value, NULL);
    782         if (len) {
    783             result = &path_command;
    784         }
    785     }
    786     else {
    787         /* No extension - search using registered extensions. */
    788         rc = _wdupenv_s(&pathext, &varsize, L"PATHEXT");
    789         if (rc == 0) {
    790             extension = wcstok_s(pathext, L";", &context);
    791             while (extension) {
    792                 len = SearchPathW(NULL, name, extension, MSGSIZE, path_command.value, NULL);
    793                 if (len) {
    794                     result = &path_command;
    795                     break;
    796                 }
    797                 extension = wcstok_s(NULL, L";", &context);
    798             }
    799             free(pathext);
    800         }
    801     }
    802     return result;
    803 }
    804 
    805 #endif
    806 
    807 static COMMAND * find_command(wchar_t * name)
    808 {
    809     COMMAND * result = NULL;
    810     COMMAND * cp = commands;
    811     int i;
    812 
    813     for (i = 0; i < num_commands; i++, cp++) {
    814         if (_wcsicmp(cp->key, name) == 0) {
    815             result = cp;
    816             break;
    817         }
    818     }
    819 #if defined(SEARCH_PATH)
    820     if (result == NULL)
    821         result = find_on_path(name);
    822 #endif
    823     return result;
    824 }
    825 
    826 static void
    827 update_command(COMMAND * cp, wchar_t * name, wchar_t * cmdline)
    828 {
    829     wcsncpy_s(cp->key, MAX_PATH, name, _TRUNCATE);
    830     wcsncpy_s(cp->value, MSGSIZE, cmdline, _TRUNCATE);
    831 }
    832 
    833 static void
    834 add_command(wchar_t * name, wchar_t * cmdline)
    835 {
    836     if (num_commands >= MAX_COMMANDS) {
    837         debug(L"can't add %ls = '%ls': no room\n", name, cmdline);
    838     }
    839     else {
    840         COMMAND * cp = &commands[num_commands++];
    841 
    842         update_command(cp, name, cmdline);
    843     }
    844 }
    845 
    846 static void
    847 read_config_file(wchar_t * config_path)
    848 {
    849     wchar_t keynames[MSGSIZE];
    850     wchar_t value[MSGSIZE];
    851     DWORD read;
    852     wchar_t * key;
    853     COMMAND * cp;
    854     wchar_t * cmdp;
    855 
    856     read = GetPrivateProfileStringW(L"commands", NULL, NULL, keynames, MSGSIZE,
    857                                     config_path);
    858     if (read == MSGSIZE - 1) {
    859         debug(L"read_commands: %ls: not enough space for names\n", config_path);
    860     }
    861     key = keynames;
    862     while (*key) {
    863         read = GetPrivateProfileStringW(L"commands", key, NULL, value, MSGSIZE,
    864                                        config_path);
    865         if (read == MSGSIZE - 1) {
    866             debug(L"read_commands: %ls: not enough space for %ls\n",
    867                   config_path, key);
    868         }
    869         cmdp = skip_whitespace(value);
    870         if (*cmdp) {
    871             cp = find_command(key);
    872             if (cp == NULL)
    873                 add_command(key, value);
    874             else
    875                 update_command(cp, key, value);
    876         }
    877         key += wcslen(key) + 1;
    878     }
    879 }
    880 
    881 static void read_commands()
    882 {
    883     if (launcher_ini_path[0])
    884         read_config_file(launcher_ini_path);
    885     if (appdata_ini_path[0])
    886         read_config_file(appdata_ini_path);
    887 }
    888 
    889 static BOOL
    890 parse_shebang(wchar_t * shebang_line, int nchars, wchar_t ** command,
    891               wchar_t ** suffix, BOOL *search)
    892 {
    893     BOOL rc = FALSE;
    894     SHEBANG * vpp;
    895     size_t plen;
    896     wchar_t * p;
    897     wchar_t zapped;
    898     wchar_t * endp = shebang_line + nchars - 1;
    899     COMMAND * cp;
    900     wchar_t * skipped;
    901 
    902     *command = NULL;    /* failure return */
    903     *suffix = NULL;
    904     *search = FALSE;
    905 
    906     if ((*shebang_line++ == L'#') && (*shebang_line++ == L'!')) {
    907         shebang_line = skip_whitespace(shebang_line);
    908         if (*shebang_line) {
    909             *command = shebang_line;
    910             for (vpp = builtin_virtual_paths; vpp->shebang; ++vpp) {
    911                 plen = wcslen(vpp->shebang);
    912                 if (wcsncmp(shebang_line, vpp->shebang, plen) == 0) {
    913                     rc = TRUE;
    914                     *search = vpp->search;
    915                     /* We can do this because all builtin commands contain
    916                      * "python".
    917                      */
    918                     *command = wcsstr(shebang_line, L"python");
    919                     break;
    920                 }
    921             }
    922             if (vpp->shebang == NULL) {
    923                 /*
    924                  * Not found in builtins - look in customized commands.
    925                  *
    926                  * We can't permanently modify the shebang line in case
    927                  * it's not a customized command, but we can temporarily
    928                  * stick a NUL after the command while searching for it,
    929                  * then put back the char we zapped.
    930                  */
    931 #if defined(SKIP_PREFIX)
    932                 skipped = skip_prefix(shebang_line);
    933 #else
    934                 skipped = shebang_line;
    935 #endif
    936                 p = wcspbrk(skipped, L" \t\r\n");
    937                 if (p != NULL) {
    938                     zapped = *p;
    939                     *p = L'\0';
    940                 }
    941                 cp = find_command(skipped);
    942                 if (p != NULL)
    943                     *p = zapped;
    944                 if (cp != NULL) {
    945                     *command = cp->value;
    946                     if (p != NULL)
    947                         *suffix = skip_whitespace(p);
    948                 }
    949             }
    950             /* remove trailing whitespace */
    951             while ((endp > shebang_line) && isspace(*endp))
    952                 --endp;
    953             if (endp > shebang_line)
    954                 endp[1] = L'\0';
    955         }
    956     }
    957     return rc;
    958 }
    959 
    960 /* #define CP_UTF8             65001 defined in winnls.h */
    961 #define CP_UTF16LE          1200
    962 #define CP_UTF16BE          1201
    963 #define CP_UTF32LE          12000
    964 #define CP_UTF32BE          12001
    965 
    966 typedef struct {
    967     int length;
    968     char sequence[4];
    969     UINT code_page;
    970 } BOM;
    971 
    972 /*
    973  * Strictly, we don't need to handle UTF-16 and UTF-32, since Python itself
    974  * doesn't. Never mind, one day it might - there's no harm leaving it in.
    975  */
    976 static BOM BOMs[] = {
    977     { 3, { 0xEF, 0xBB, 0xBF }, CP_UTF8 },           /* UTF-8 - keep first */
    978     /* Test UTF-32LE before UTF-16LE since UTF-16LE BOM is a prefix
    979      * of UTF-32LE BOM. */
    980     { 4, { 0xFF, 0xFE, 0x00, 0x00 }, CP_UTF32LE },  /* UTF-32LE */
    981     { 4, { 0x00, 0x00, 0xFE, 0xFF }, CP_UTF32BE },  /* UTF-32BE */
    982     { 2, { 0xFF, 0xFE }, CP_UTF16LE },              /* UTF-16LE */
    983     { 2, { 0xFE, 0xFF }, CP_UTF16BE },              /* UTF-16BE */
    984     { 0 }                                           /* sentinel */
    985 };
    986 
    987 static BOM *
    988 find_BOM(char * buffer)
    989 {
    990 /*
    991  * Look for a BOM in the input and return a pointer to the
    992  * corresponding structure, or NULL if not found.
    993  */
    994     BOM * result = NULL;
    995     BOM *bom;
    996 
    997     for (bom = BOMs; bom->length; bom++) {
    998         if (strncmp(bom->sequence, buffer, bom->length) == 0) {
    999             result = bom;
   1000             break;
   1001         }
   1002     }
   1003     return result;
   1004 }
   1005 
   1006 static char *
   1007 find_terminator(char * buffer, int len, BOM *bom)
   1008 {
   1009     char * result = NULL;
   1010     char * end = buffer + len;
   1011     char  * p;
   1012     char c;
   1013     int cp;
   1014 
   1015     for (p = buffer; p < end; p++) {
   1016         c = *p;
   1017         if (c == '\r') {
   1018             result = p;
   1019             break;
   1020         }
   1021         if (c == '\n') {
   1022             result = p;
   1023             break;
   1024         }
   1025     }
   1026     if (result != NULL) {
   1027         cp = bom->code_page;
   1028 
   1029         /* adjustments to include all bytes of the char */
   1030         /* no adjustment needed for UTF-8 or big endian */
   1031         if (cp == CP_UTF16LE)
   1032             ++result;
   1033         else if (cp == CP_UTF32LE)
   1034             result += 3;
   1035         ++result; /* point just past terminator */
   1036     }
   1037     return result;
   1038 }
   1039 
   1040 static BOOL
   1041 validate_version(wchar_t * p)
   1042 {
   1043     BOOL result = TRUE;
   1044 
   1045     if (!isdigit(*p))               /* expect major version */
   1046         result = FALSE;
   1047     else if (*++p) {                /* more to do */
   1048         if (*p != L'.')             /* major/minor separator */
   1049             result = FALSE;
   1050         else {
   1051             ++p;
   1052             if (!isdigit(*p))       /* expect minor version */
   1053                 result = FALSE;
   1054             else {
   1055                 ++p;
   1056                 if (*p) {           /* more to do */
   1057                     if (*p != L'-')
   1058                         result = FALSE;
   1059                     else {
   1060                         ++p;
   1061                         if ((*p != '3') && (*++p != '2') && !*++p)
   1062                             result = FALSE;
   1063                     }
   1064                 }
   1065             }
   1066         }
   1067     }
   1068     return result;
   1069 }
   1070 
   1071 typedef struct {
   1072     unsigned short min;
   1073     unsigned short max;
   1074     wchar_t version[MAX_VERSION_SIZE];
   1075 } PYC_MAGIC;
   1076 
   1077 static PYC_MAGIC magic_values[] = {
   1078     { 50823, 50823, L"2.0" },
   1079     { 60202, 60202, L"2.1" },
   1080     { 60717, 60717, L"2.2" },
   1081     { 62011, 62021, L"2.3" },
   1082     { 62041, 62061, L"2.4" },
   1083     { 62071, 62131, L"2.5" },
   1084     { 62151, 62161, L"2.6" },
   1085     { 62171, 62211, L"2.7" },
   1086     { 3000, 3131, L"3.0" },
   1087     { 3141, 3151, L"3.1" },
   1088     { 3160, 3180, L"3.2" },
   1089     { 3190, 3230, L"3.3" },
   1090     { 3250, 3310, L"3.4" },
   1091     { 3320, 3351, L"3.5" },
   1092     { 3360, 3379, L"3.6" },
   1093     { 0 }
   1094 };
   1095 
   1096 static INSTALLED_PYTHON *
   1097 find_by_magic(unsigned short magic)
   1098 {
   1099     INSTALLED_PYTHON * result = NULL;
   1100     PYC_MAGIC * mp;
   1101 
   1102     for (mp = magic_values; mp->min; mp++) {
   1103         if ((magic >= mp->min) && (magic <= mp->max)) {
   1104             result = locate_python(mp->version, FALSE);
   1105             if (result != NULL)
   1106                 break;
   1107         }
   1108     }
   1109     return result;
   1110 }
   1111 
   1112 static void
   1113 maybe_handle_shebang(wchar_t ** argv, wchar_t * cmdline)
   1114 {
   1115 /*
   1116  * Look for a shebang line in the first argument.  If found
   1117  * and we spawn a child process, this never returns.  If it
   1118  * does return then we process the args "normally".
   1119  *
   1120  * argv[0] might be a filename with a shebang.
   1121  */
   1122     FILE * fp;
   1123     errno_t rc = _wfopen_s(&fp, *argv, L"rb");
   1124     char buffer[BUFSIZE];
   1125     wchar_t shebang_line[BUFSIZE + 1];
   1126     size_t read;
   1127     char *p;
   1128     char * start;
   1129     char * shebang_alias = (char *) shebang_line;
   1130     BOM* bom;
   1131     int i, j, nchars = 0;
   1132     int header_len;
   1133     BOOL is_virt;
   1134     BOOL search;
   1135     wchar_t * command;
   1136     wchar_t * suffix;
   1137     COMMAND *cmd = NULL;
   1138     INSTALLED_PYTHON * ip;
   1139 
   1140     if (rc == 0) {
   1141         read = fread(buffer, sizeof(char), BUFSIZE, fp);
   1142         debug(L"maybe_handle_shebang: read %d bytes\n", read);
   1143         fclose(fp);
   1144 
   1145         if ((read >= 4) && (buffer[3] == '\n') && (buffer[2] == '\r')) {
   1146             ip = find_by_magic((((unsigned char)buffer[1]) << 8 |
   1147                                 (unsigned char)buffer[0]) & 0xFFFF);
   1148             if (ip != NULL) {
   1149                 debug(L"script file is compiled against Python %ls\n",
   1150                       ip->version);
   1151                 invoke_child(ip->executable, NULL, cmdline);
   1152             }
   1153         }
   1154         /* Look for BOM */
   1155         bom = find_BOM(buffer);
   1156         if (bom == NULL) {
   1157             start = buffer;
   1158             debug(L"maybe_handle_shebang: BOM not found, using UTF-8\n");
   1159             bom = BOMs; /* points to UTF-8 entry - the default */
   1160         }
   1161         else {
   1162             debug(L"maybe_handle_shebang: BOM found, code page %d\n",
   1163                   bom->code_page);
   1164             start = &buffer[bom->length];
   1165         }
   1166         p = find_terminator(start, BUFSIZE, bom);
   1167         /*
   1168          * If no CR or LF was found in the heading,
   1169          * we assume it's not a shebang file.
   1170          */
   1171         if (p == NULL) {
   1172             debug(L"maybe_handle_shebang: No line terminator found\n");
   1173         }
   1174         else {
   1175             /*
   1176              * Found line terminator - parse the shebang.
   1177              *
   1178              * Strictly, we don't need to handle UTF-16 anf UTF-32,
   1179              * since Python itself doesn't.
   1180              * Never mind, one day it might.
   1181              */
   1182             header_len = (int) (p - start);
   1183             switch(bom->code_page) {
   1184             case CP_UTF8:
   1185                 nchars = MultiByteToWideChar(bom->code_page,
   1186                                              0,
   1187                                              start, header_len, shebang_line,
   1188                                              BUFSIZE);
   1189                 break;
   1190             case CP_UTF16BE:
   1191                 if (header_len % 2 != 0) {
   1192                     debug(L"maybe_handle_shebang: UTF-16BE, but an odd number \
   1193 of bytes: %d\n", header_len);
   1194                     /* nchars = 0; Not needed - initialised to 0. */
   1195                 }
   1196                 else {
   1197                     for (i = header_len; i > 0; i -= 2) {
   1198                         shebang_alias[i - 1] = start[i - 2];
   1199                         shebang_alias[i - 2] = start[i - 1];
   1200                     }
   1201                     nchars = header_len / sizeof(wchar_t);
   1202                 }
   1203                 break;
   1204             case CP_UTF16LE:
   1205                 if ((header_len % 2) != 0) {
   1206                     debug(L"UTF-16LE, but an odd number of bytes: %d\n",
   1207                           header_len);
   1208                     /* nchars = 0; Not needed - initialised to 0. */
   1209                 }
   1210                 else {
   1211                     /* no actual conversion needed. */
   1212                     memcpy(shebang_line, start, header_len);
   1213                     nchars = header_len / sizeof(wchar_t);
   1214                 }
   1215                 break;
   1216             case CP_UTF32BE:
   1217                 if (header_len % 4 != 0) {
   1218                     debug(L"UTF-32BE, but not divisible by 4: %d\n",
   1219                           header_len);
   1220                     /* nchars = 0; Not needed - initialised to 0. */
   1221                 }
   1222                 else {
   1223                     for (i = header_len, j = header_len / 2; i > 0; i -= 4,
   1224                                                                     j -= 2) {
   1225                         shebang_alias[j - 1] = start[i - 2];
   1226                         shebang_alias[j - 2] = start[i - 1];
   1227                     }
   1228                     nchars = header_len / sizeof(wchar_t);
   1229                 }
   1230                 break;
   1231             case CP_UTF32LE:
   1232                 if (header_len % 4 != 0) {
   1233                     debug(L"UTF-32LE, but not divisible by 4: %d\n",
   1234                           header_len);
   1235                     /* nchars = 0; Not needed - initialised to 0. */
   1236                 }
   1237                 else {
   1238                     for (i = header_len, j = header_len / 2; i > 0; i -= 4,
   1239                                                                     j -= 2) {
   1240                         shebang_alias[j - 1] = start[i - 3];
   1241                         shebang_alias[j - 2] = start[i - 4];
   1242                     }
   1243                     nchars = header_len / sizeof(wchar_t);
   1244                 }
   1245                 break;
   1246             }
   1247             if (nchars > 0) {
   1248                 shebang_line[--nchars] = L'\0';
   1249                 is_virt = parse_shebang(shebang_line, nchars, &command,
   1250                                         &suffix, &search);
   1251                 if (command != NULL) {
   1252                     debug(L"parse_shebang: found command: %ls\n", command);
   1253                     if (!is_virt) {
   1254                         invoke_child(command, suffix, cmdline);
   1255                     }
   1256                     else {
   1257                         suffix = wcschr(command, L' ');
   1258                         if (suffix != NULL) {
   1259                             *suffix++ = L'\0';
   1260                             suffix = skip_whitespace(suffix);
   1261                         }
   1262                         if (wcsncmp(command, L"python", 6))
   1263                             error(RC_BAD_VIRTUAL_PATH, L"Unknown virtual \
   1264 path '%ls'", command);
   1265                         command += 6;   /* skip past "python" */
   1266                         if (search && ((*command == L'\0') || isspace(*command))) {
   1267                             /* Command is eligible for path search, and there
   1268                              * is no version specification.
   1269                              */
   1270                             debug(L"searching PATH for python executable\n");
   1271                             cmd = find_on_path(PYTHON_EXECUTABLE);
   1272                             debug(L"Python on path: %ls\n", cmd ? cmd->value : L"<not found>");
   1273                             if (cmd) {
   1274                                 debug(L"located python on PATH: %ls\n", cmd->value);
   1275                                 invoke_child(cmd->value, suffix, cmdline);
   1276                                 /* Exit here, as we have found the command */
   1277                                 return;
   1278                             }
   1279                             /* FALL THROUGH: No python found on PATH, so fall
   1280                              * back to locating the correct installed python.
   1281                              */
   1282                         }
   1283                         if (*command && !validate_version(command))
   1284                             error(RC_BAD_VIRTUAL_PATH, L"Invalid version \
   1285 specification: '%ls'.\nIn the first line of the script, 'python' needs to be \
   1286 followed by a valid version specifier.\nPlease check the documentation.",
   1287                                   command);
   1288                         /* TODO could call validate_version(command) */
   1289                         ip = locate_python(command, TRUE);
   1290                         if (ip == NULL) {
   1291                             error(RC_NO_PYTHON, L"Requested Python version \
   1292 (%ls) is not installed", command);
   1293                         }
   1294                         else {
   1295                             invoke_child(ip->executable, suffix, cmdline);
   1296                         }
   1297                     }
   1298                 }
   1299             }
   1300         }
   1301     }
   1302 }
   1303 
   1304 static wchar_t *
   1305 skip_me(wchar_t * cmdline)
   1306 {
   1307     BOOL quoted;
   1308     wchar_t c;
   1309     wchar_t * result = cmdline;
   1310 
   1311     quoted = cmdline[0] == L'\"';
   1312     if (!quoted)
   1313         c = L' ';
   1314     else {
   1315         c = L'\"';
   1316         ++result;
   1317     }
   1318     result = wcschr(result, c);
   1319     if (result == NULL) /* when, for example, just exe name on command line */
   1320         result = L"";
   1321     else {
   1322         ++result; /* skip past space or closing quote */
   1323         result = skip_whitespace(result);
   1324     }
   1325     return result;
   1326 }
   1327 
   1328 static DWORD version_high = 0;
   1329 static DWORD version_low = 0;
   1330 
   1331 static void
   1332 get_version_info(wchar_t * version_text, size_t size)
   1333 {
   1334     WORD maj, min, rel, bld;
   1335 
   1336     if (!version_high && !version_low)
   1337         wcsncpy_s(version_text, size, L"0.1", _TRUNCATE);   /* fallback */
   1338     else {
   1339         maj = HIWORD(version_high);
   1340         min = LOWORD(version_high);
   1341         rel = HIWORD(version_low);
   1342         bld = LOWORD(version_low);
   1343         _snwprintf_s(version_text, size, _TRUNCATE, L"%d.%d.%d.%d", maj,
   1344                      min, rel, bld);
   1345     }
   1346 }
   1347 
   1348 static int
   1349 process(int argc, wchar_t ** argv)
   1350 {
   1351     wchar_t * wp;
   1352     wchar_t * command;
   1353     wchar_t * executable;
   1354     wchar_t * p;
   1355     int rc = 0;
   1356     size_t plen;
   1357     INSTALLED_PYTHON * ip;
   1358     BOOL valid;
   1359     DWORD size, attrs;
   1360     HRESULT hr;
   1361     wchar_t message[MSGSIZE];
   1362     wchar_t version_text [MAX_PATH];
   1363     void * version_data;
   1364     VS_FIXEDFILEINFO * file_info;
   1365     UINT block_size;
   1366     int index;
   1367 #if defined(SCRIPT_WRAPPER)
   1368     int newlen;
   1369     wchar_t * newcommand;
   1370     wchar_t * av[2];
   1371 #endif
   1372 
   1373     setvbuf(stderr, (char *)NULL, _IONBF, 0);
   1374     wp = get_env(L"PYLAUNCH_DEBUG");
   1375     if ((wp != NULL) && (*wp != L'\0'))
   1376         log_fp = stderr;
   1377 
   1378 #if defined(_M_X64)
   1379     debug(L"launcher build: 64bit\n");
   1380 #else
   1381     debug(L"launcher build: 32bit\n");
   1382 #endif
   1383 #if defined(_WINDOWS)
   1384     debug(L"launcher executable: Windows\n");
   1385 #else
   1386     debug(L"launcher executable: Console\n");
   1387 #endif
   1388     /* Get the local appdata folder (non-roaming) */
   1389     hr = SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA,
   1390                           NULL, 0, appdata_ini_path);
   1391     if (hr != S_OK) {
   1392         debug(L"SHGetFolderPath failed: %X\n", hr);
   1393         appdata_ini_path[0] = L'\0';
   1394     }
   1395     else {
   1396         plen = wcslen(appdata_ini_path);
   1397         p = &appdata_ini_path[plen];
   1398         wcsncpy_s(p, MAX_PATH - plen, L"\\py.ini", _TRUNCATE);
   1399         attrs = GetFileAttributesW(appdata_ini_path);
   1400         if (attrs == INVALID_FILE_ATTRIBUTES) {
   1401             debug(L"File '%ls' non-existent\n", appdata_ini_path);
   1402             appdata_ini_path[0] = L'\0';
   1403         } else {
   1404             debug(L"Using local configuration file '%ls'\n", appdata_ini_path);
   1405         }
   1406     }
   1407     plen = GetModuleFileNameW(NULL, launcher_ini_path, MAX_PATH);
   1408     size = GetFileVersionInfoSizeW(launcher_ini_path, &size);
   1409     if (size == 0) {
   1410         winerror(GetLastError(), message, MSGSIZE);
   1411         debug(L"GetFileVersionInfoSize failed: %ls\n", message);
   1412     }
   1413     else {
   1414         version_data = malloc(size);
   1415         if (version_data) {
   1416             valid = GetFileVersionInfoW(launcher_ini_path, 0, size,
   1417                                         version_data);
   1418             if (!valid)
   1419                 debug(L"GetFileVersionInfo failed: %X\n", GetLastError());
   1420             else {
   1421                 valid = VerQueryValueW(version_data, L"\\",
   1422                                        (LPVOID *) &file_info, &block_size);
   1423                 if (!valid)
   1424                     debug(L"VerQueryValue failed: %X\n", GetLastError());
   1425                 else {
   1426                     version_high = file_info->dwFileVersionMS;
   1427                     version_low = file_info->dwFileVersionLS;
   1428                 }
   1429             }
   1430             free(version_data);
   1431         }
   1432     }
   1433     p = wcsrchr(launcher_ini_path, L'\\');
   1434     if (p == NULL) {
   1435         debug(L"GetModuleFileNameW returned value has no backslash: %ls\n",
   1436               launcher_ini_path);
   1437         launcher_ini_path[0] = L'\0';
   1438     }
   1439     else {
   1440         wcsncpy_s(p, MAX_PATH - (p - launcher_ini_path), L"\\py.ini",
   1441                   _TRUNCATE);
   1442         attrs = GetFileAttributesW(launcher_ini_path);
   1443         if (attrs == INVALID_FILE_ATTRIBUTES) {
   1444             debug(L"File '%ls' non-existent\n", launcher_ini_path);
   1445             launcher_ini_path[0] = L'\0';
   1446         } else {
   1447             debug(L"Using global configuration file '%ls'\n", launcher_ini_path);
   1448         }
   1449     }
   1450 
   1451     command = skip_me(GetCommandLineW());
   1452     debug(L"Called with command line: %ls\n", command);
   1453 
   1454 #if defined(SCRIPT_WRAPPER)
   1455     /* The launcher is being used in "script wrapper" mode.
   1456      * There should therefore be a Python script named <exename>-script.py in
   1457      * the same directory as the launcher executable.
   1458      * Put the script name into argv as the first (script name) argument.
   1459      */
   1460 
   1461     /* Get the wrapped script name - if the script is not present, this will
   1462      * terminate the program with an error.
   1463      */
   1464     locate_wrapped_script();
   1465 
   1466     /* Add the wrapped script to the start of command */
   1467     newlen = wcslen(wrapped_script_path) + wcslen(command) + 2; /* ' ' + NUL */
   1468     newcommand = malloc(sizeof(wchar_t) * newlen);
   1469     if (!newcommand) {
   1470         error(RC_NO_MEMORY, L"Could not allocate new command line");
   1471     }
   1472     else {
   1473         wcscpy_s(newcommand, newlen, wrapped_script_path);
   1474         wcscat_s(newcommand, newlen, L" ");
   1475         wcscat_s(newcommand, newlen, command);
   1476         debug(L"Running wrapped script with command line '%ls'\n", newcommand);
   1477         read_commands();
   1478         av[0] = wrapped_script_path;
   1479         av[1] = NULL;
   1480         maybe_handle_shebang(av, newcommand);
   1481         /* Returns if no shebang line - pass to default processing */
   1482         command = newcommand;
   1483         valid = FALSE;
   1484     }
   1485 #else
   1486     if (argc <= 1) {
   1487         valid = FALSE;
   1488         p = NULL;
   1489     }
   1490     else {
   1491         p = argv[1];
   1492         plen = wcslen(p);
   1493         valid = (*p == L'-') && validate_version(&p[1]);
   1494         if (valid) {
   1495             ip = locate_python(&p[1], FALSE);
   1496             if (ip == NULL)
   1497                 error(RC_NO_PYTHON, L"Requested Python version (%ls) not \
   1498 installed", &p[1]);
   1499             executable = ip->executable;
   1500             command += wcslen(p);
   1501             command = skip_whitespace(command);
   1502         }
   1503         else {
   1504             for (index = 1; index < argc; ++index) {
   1505                 if (*argv[index] != L'-')
   1506                     break;
   1507             }
   1508             if (index < argc) {
   1509                 read_commands();
   1510                 maybe_handle_shebang(&argv[index], command);
   1511             }
   1512         }
   1513     }
   1514 #endif
   1515 
   1516     if (!valid) {
   1517         /* Look for an active virtualenv */
   1518         executable = find_python_by_venv();
   1519 
   1520         /* If we didn't find one, look for the default Python */
   1521         if (executable == NULL) {
   1522             ip = locate_python(L"", FALSE);
   1523             if (ip == NULL)
   1524                 error(RC_NO_PYTHON, L"Can't find a default Python.");
   1525             executable = ip->executable;
   1526         }
   1527         if ((argc == 2) && (!_wcsicmp(p, L"-h") || !_wcsicmp(p, L"--help"))) {
   1528 #if defined(_M_X64)
   1529             BOOL canDo64bit = TRUE;
   1530 #else
   1531     // If we are a 32bit process on a 64bit Windows, first hit the 64bit keys.
   1532             BOOL canDo64bit = FALSE;
   1533             IsWow64Process(GetCurrentProcess(), &canDo64bit);
   1534 #endif
   1535 
   1536             get_version_info(version_text, MAX_PATH);
   1537             fwprintf(stdout, L"\
   1538 Python Launcher for Windows Version %ls\n\n", version_text);
   1539             fwprintf(stdout, L"\
   1540 usage: %ls [ launcher-arguments ] [ python-arguments ] script [ script-arguments ]\n\n", argv[0]);
   1541             fputws(L"\
   1542 Launcher arguments:\n\n\
   1543 -2     : Launch the latest Python 2.x version\n\
   1544 -3     : Launch the latest Python 3.x version\n\
   1545 -X.Y   : Launch the specified Python version\n", stdout);
   1546             if (canDo64bit) {
   1547                 fputws(L"\
   1548 -X.Y-32: Launch the specified 32bit Python version", stdout);
   1549             }
   1550             fputws(L"\n\nThe following help text is from Python:\n\n", stdout);
   1551             fflush(stdout);
   1552         }
   1553     }
   1554     invoke_child(executable, NULL, command);
   1555     return rc;
   1556 }
   1557 
   1558 #if defined(_WINDOWS)
   1559 
   1560 int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
   1561                    LPWSTR lpstrCmd, int nShow)
   1562 {
   1563     return process(__argc, __wargv);
   1564 }
   1565 
   1566 #else
   1567 
   1568 int cdecl wmain(int argc, wchar_t ** argv)
   1569 {
   1570     return process(argc, argv);
   1571 }
   1572 
   1573 #endif
   1574