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