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