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