blob: d450d9f71e186c4a0e0509c9cf188fe8e21fee56 [file] [log] [blame]
Brian Curtin07165f72012-06-20 15:36:14 -05001/*
Vinay Sajip22c039b2013-06-07 15:37:28 +01002 * Copyright (C) 2011-2013 Vinay Sajip.
Martin v. Löwis6a8ca3e2012-06-21 19:29:37 +02003 * Licensed to PSF under a contributor agreement.
Brian Curtin07165f72012-06-20 15:36:14 -05004 *
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
Vinay Sajip22c039b2013-06-07 15:37:28 +010021#define SEARCH_PATH
Brian Curtin07165f72012-06-20 15:36:14 -050022
Vinay Sajipc985d082013-07-25 11:20:55 +010023/* 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
Brian Curtin07165f72012-06-20 15:36:14 -050044/* Just for now - static definition */
45
46static FILE * log_fp = NULL;
47
48static wchar_t *
49skip_whitespace(wchar_t * p)
50{
51 while (*p && isspace(*p))
52 ++p;
53 return p;
54}
55
Brian Curtin07165f72012-06-20 15:36:14 -050056static void
57debug(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
67static void
68winerror(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
76static void
77error(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) {
Steve Dower84bcfb32015-01-02 18:07:46 -080090 _snwprintf_s(&message[len], MSGSIZE - len, _TRUNCATE, L": %ls",
Brian Curtin07165f72012-06-20 15:36:14 -050091 win_message);
92 }
93 }
94
95#if !defined(_WINDOWS)
Steve Dower84bcfb32015-01-02 18:07:46 -080096 fwprintf(stderr, L"%ls\n", message);
Brian Curtin07165f72012-06-20 15:36:14 -050097#else
Vinay Sajipc985d082013-07-25 11:20:55 +010098 MessageBox(NULL, message, TEXT("Python Launcher is sorry to say ..."),
99 MB_OK);
Brian Curtin07165f72012-06-20 15:36:14 -0500100#endif
101 ExitProcess(rc);
102}
103
Vinay Sajipc985d082013-07-25 11:20:55 +0100104/*
105 * This function is here to simplify memory management
106 * and to treat blank values as if they are absent.
107 */
108static 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
Brian Curtin07165f72012-06-20 15:36:14 -0500132#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
Brian Curtin07165f72012-06-20 15:36:14 -0500142#define MAX_VERSION_SIZE 4
143
144typedef 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
156static INSTALLED_PYTHON installed_pythons[MAX_INSTALLED_PYTHONS];
157
158static size_t num_installed_pythons = 0;
159
Steve Dowerbb240872015-02-05 22:08:48 -0800160/*
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 */
Brian Curtin07165f72012-06-20 15:36:14 -0500165#define IP_BASE_SIZE 40
Steve Dowerbb240872015-02-05 22:08:48 -0800166#define IP_VERSION_SIZE 8
167#define IP_SIZE (IP_BASE_SIZE + IP_VERSION_SIZE)
Brian Curtin07165f72012-06-20 15:36:14 -0500168#define CORE_PATH L"SOFTWARE\\Python\\PythonCore"
169
170static wchar_t * location_checks[] = {
171 L"\\",
Steve Dowerbb240872015-02-05 22:08:48 -0800172 L"\\PCBuild\\win32\\",
Brian Curtin07165f72012-06-20 15:36:14 -0500173 L"\\PCBuild\\amd64\\",
174 NULL
175};
176
177static INSTALLED_PYTHON *
178find_existing_python(wchar_t * path)
179{
180 INSTALLED_PYTHON * result = NULL;
181 size_t i;
182 INSTALLED_PYTHON * ip;
183
184 for (i = 0, ip = installed_pythons; i < num_installed_pythons; i++, ip++) {
185 if (_wcsicmp(path, ip->executable) == 0) {
186 result = ip;
187 break;
188 }
189 }
190 return result;
191}
192
193static void
194locate_pythons_for_key(HKEY root, REGSAM flags)
195{
196 HKEY core_root, ip_key;
197 LSTATUS status = RegOpenKeyExW(root, CORE_PATH, 0, flags, &core_root);
198 wchar_t message[MSGSIZE];
199 DWORD i;
200 size_t n;
201 BOOL ok;
202 DWORD type, data_size, attrs;
203 INSTALLED_PYTHON * ip, * pip;
Steve Dowerbb240872015-02-05 22:08:48 -0800204 wchar_t ip_version[IP_VERSION_SIZE];
Brian Curtin07165f72012-06-20 15:36:14 -0500205 wchar_t ip_path[IP_SIZE];
206 wchar_t * check;
207 wchar_t ** checkp;
208 wchar_t *key_name = (root == HKEY_LOCAL_MACHINE) ? L"HKLM" : L"HKCU";
209
210 if (status != ERROR_SUCCESS)
Steve Dower84bcfb32015-01-02 18:07:46 -0800211 debug(L"locate_pythons_for_key: unable to open PythonCore key in %ls\n",
Brian Curtin07165f72012-06-20 15:36:14 -0500212 key_name);
213 else {
214 ip = &installed_pythons[num_installed_pythons];
215 for (i = 0; num_installed_pythons < MAX_INSTALLED_PYTHONS; i++) {
Steve Dowerbb240872015-02-05 22:08:48 -0800216 status = RegEnumKeyW(core_root, i, ip_version, IP_VERSION_SIZE);
Brian Curtin07165f72012-06-20 15:36:14 -0500217 if (status != ERROR_SUCCESS) {
218 if (status != ERROR_NO_MORE_ITEMS) {
219 /* unexpected error */
220 winerror(status, message, MSGSIZE);
Steve Dower84bcfb32015-01-02 18:07:46 -0800221 debug(L"Can't enumerate registry key for version %ls: %ls\n",
Steve Dowerbb240872015-02-05 22:08:48 -0800222 ip_version, message);
Brian Curtin07165f72012-06-20 15:36:14 -0500223 }
224 break;
225 }
226 else {
Steve Dowerbb240872015-02-05 22:08:48 -0800227 wcsncpy_s(ip->version, MAX_VERSION_SIZE, ip_version,
228 MAX_VERSION_SIZE-1);
Brian Curtin07165f72012-06-20 15:36:14 -0500229 _snwprintf_s(ip_path, IP_SIZE, _TRUNCATE,
Steve Dowerbb240872015-02-05 22:08:48 -0800230 L"%ls\\%ls\\InstallPath", CORE_PATH, ip_version);
Brian Curtin07165f72012-06-20 15:36:14 -0500231 status = RegOpenKeyExW(root, ip_path, 0, flags, &ip_key);
232 if (status != ERROR_SUCCESS) {
233 winerror(status, message, MSGSIZE);
234 // Note: 'message' already has a trailing \n
Steve Dower84bcfb32015-01-02 18:07:46 -0800235 debug(L"%ls\\%ls: %ls", key_name, ip_path, message);
Brian Curtin07165f72012-06-20 15:36:14 -0500236 continue;
237 }
238 data_size = sizeof(ip->executable) - 1;
Martin v. Löwisaf21ebb2012-06-21 18:15:54 +0200239 status = RegQueryValueExW(ip_key, NULL, NULL, &type,
240 (LPBYTE)ip->executable, &data_size);
Brian Curtin07165f72012-06-20 15:36:14 -0500241 RegCloseKey(ip_key);
242 if (status != ERROR_SUCCESS) {
243 winerror(status, message, MSGSIZE);
Steve Dower84bcfb32015-01-02 18:07:46 -0800244 debug(L"%ls\\%ls: %ls\n", key_name, ip_path, message);
Brian Curtin07165f72012-06-20 15:36:14 -0500245 continue;
246 }
247 if (type == REG_SZ) {
248 data_size = data_size / sizeof(wchar_t) - 1; /* for NUL */
249 if (ip->executable[data_size - 1] == L'\\')
250 --data_size; /* reg value ended in a backslash */
251 /* ip->executable is data_size long */
252 for (checkp = location_checks; *checkp; ++checkp) {
253 check = *checkp;
254 _snwprintf_s(&ip->executable[data_size],
255 MAX_PATH - data_size,
256 MAX_PATH - data_size,
Steve Dower84bcfb32015-01-02 18:07:46 -0800257 L"%ls%ls", check, PYTHON_EXECUTABLE);
Brian Curtin07165f72012-06-20 15:36:14 -0500258 attrs = GetFileAttributesW(ip->executable);
259 if (attrs == INVALID_FILE_ATTRIBUTES) {
260 winerror(GetLastError(), message, MSGSIZE);
Steve Dower84bcfb32015-01-02 18:07:46 -0800261 debug(L"locate_pythons_for_key: %ls: %ls",
Brian Curtin07165f72012-06-20 15:36:14 -0500262 ip->executable, message);
263 }
264 else if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
Steve Dower84bcfb32015-01-02 18:07:46 -0800265 debug(L"locate_pythons_for_key: '%ls' is a \
Brian Curtin07165f72012-06-20 15:36:14 -0500266directory\n",
267 ip->executable, attrs);
268 }
269 else if (find_existing_python(ip->executable)) {
Steve Dower84bcfb32015-01-02 18:07:46 -0800270 debug(L"locate_pythons_for_key: %ls: already \
271found: %ls\n", ip->executable);
Brian Curtin07165f72012-06-20 15:36:14 -0500272 }
273 else {
274 /* check the executable type. */
275 ok = GetBinaryTypeW(ip->executable, &attrs);
276 if (!ok) {
Steve Dower84bcfb32015-01-02 18:07:46 -0800277 debug(L"Failure getting binary type: %ls\n",
Brian Curtin07165f72012-06-20 15:36:14 -0500278 ip->executable);
279 }
280 else {
281 if (attrs == SCS_64BIT_BINARY)
282 ip->bits = 64;
283 else if (attrs == SCS_32BIT_BINARY)
284 ip->bits = 32;
285 else
286 ip->bits = 0;
287 if (ip->bits == 0) {
Steve Dower84bcfb32015-01-02 18:07:46 -0800288 debug(L"locate_pythons_for_key: %ls: \
Brian Curtin07165f72012-06-20 15:36:14 -0500289invalid binary type: %X\n",
290 ip->executable, attrs);
291 }
292 else {
293 if (wcschr(ip->executable, L' ') != NULL) {
294 /* has spaces, so quote */
295 n = wcslen(ip->executable);
296 memmove(&ip->executable[1],
297 ip->executable, n * sizeof(wchar_t));
298 ip->executable[0] = L'\"';
299 ip->executable[n + 1] = L'\"';
300 ip->executable[n + 2] = L'\0';
301 }
Steve Dower84bcfb32015-01-02 18:07:46 -0800302 debug(L"locate_pythons_for_key: %ls \
Brian Curtin07165f72012-06-20 15:36:14 -0500303is a %dbit executable\n",
304 ip->executable, ip->bits);
305 ++num_installed_pythons;
306 pip = ip++;
307 if (num_installed_pythons >=
308 MAX_INSTALLED_PYTHONS)
309 break;
310 /* Copy over the attributes for the next */
311 *ip = *pip;
312 }
313 }
314 }
315 }
316 }
317 }
318 }
319 RegCloseKey(core_root);
320 }
321}
322
323static int
324compare_pythons(const void * p1, const void * p2)
325{
326 INSTALLED_PYTHON * ip1 = (INSTALLED_PYTHON *) p1;
327 INSTALLED_PYTHON * ip2 = (INSTALLED_PYTHON *) p2;
328 /* note reverse sorting on version */
329 int result = wcscmp(ip2->version, ip1->version);
330
331 if (result == 0)
332 result = ip2->bits - ip1->bits; /* 64 before 32 */
333 return result;
334}
335
336static void
337locate_all_pythons()
338{
339#if defined(_M_X64)
340 // If we are a 64bit process, first hit the 32bit keys.
341 debug(L"locating Pythons in 32bit registry\n");
342 locate_pythons_for_key(HKEY_CURRENT_USER, KEY_READ | KEY_WOW64_32KEY);
343 locate_pythons_for_key(HKEY_LOCAL_MACHINE, KEY_READ | KEY_WOW64_32KEY);
344#else
345 // If we are a 32bit process on a 64bit Windows, first hit the 64bit keys.
346 BOOL f64 = FALSE;
347 if (IsWow64Process(GetCurrentProcess(), &f64) && f64) {
348 debug(L"locating Pythons in 64bit registry\n");
349 locate_pythons_for_key(HKEY_CURRENT_USER, KEY_READ | KEY_WOW64_64KEY);
350 locate_pythons_for_key(HKEY_LOCAL_MACHINE, KEY_READ | KEY_WOW64_64KEY);
351 }
352#endif
353 // now hit the "native" key for this process bittedness.
354 debug(L"locating Pythons in native registry\n");
355 locate_pythons_for_key(HKEY_CURRENT_USER, KEY_READ);
356 locate_pythons_for_key(HKEY_LOCAL_MACHINE, KEY_READ);
357 qsort(installed_pythons, num_installed_pythons, sizeof(INSTALLED_PYTHON),
358 compare_pythons);
359}
360
361static INSTALLED_PYTHON *
362find_python_by_version(wchar_t const * wanted_ver)
363{
364 INSTALLED_PYTHON * result = NULL;
365 INSTALLED_PYTHON * ip = installed_pythons;
366 size_t i, n;
367 size_t wlen = wcslen(wanted_ver);
368 int bits = 0;
369
370 if (wcsstr(wanted_ver, L"-32"))
371 bits = 32;
372 for (i = 0; i < num_installed_pythons; i++, ip++) {
373 n = wcslen(ip->version);
374 if (n > wlen)
375 n = wlen;
376 if ((wcsncmp(ip->version, wanted_ver, n) == 0) &&
377 /* bits == 0 => don't care */
378 ((bits == 0) || (ip->bits == bits))) {
379 result = ip;
380 break;
381 }
382 }
383 return result;
384}
385
386
387static wchar_t appdata_ini_path[MAX_PATH];
388static wchar_t launcher_ini_path[MAX_PATH];
389
390/*
391 * Get a value either from the environment or a configuration file.
392 * The key passed in will either be "python", "python2" or "python3".
393 */
394static wchar_t *
395get_configured_value(wchar_t * key)
396{
397/*
398 * Note: this static value is used to return a configured value
399 * obtained either from the environment or configuration file.
400 * This should be OK since there wouldn't be any concurrent calls.
401 */
402 static wchar_t configured_value[MSGSIZE];
403 wchar_t * result = NULL;
404 wchar_t * found_in = L"environment";
405 DWORD size;
406
407 /* First, search the environment. */
Steve Dower84bcfb32015-01-02 18:07:46 -0800408 _snwprintf_s(configured_value, MSGSIZE, _TRUNCATE, L"py_%ls", key);
Brian Curtin07165f72012-06-20 15:36:14 -0500409 result = get_env(configured_value);
410 if (result == NULL && appdata_ini_path[0]) {
411 /* Not in environment: check local configuration. */
412 size = GetPrivateProfileStringW(L"defaults", key, NULL,
413 configured_value, MSGSIZE,
414 appdata_ini_path);
415 if (size > 0) {
416 result = configured_value;
417 found_in = appdata_ini_path;
418 }
419 }
420 if (result == NULL && launcher_ini_path[0]) {
421 /* Not in environment or local: check global configuration. */
422 size = GetPrivateProfileStringW(L"defaults", key, NULL,
423 configured_value, MSGSIZE,
424 launcher_ini_path);
425 if (size > 0) {
426 result = configured_value;
427 found_in = launcher_ini_path;
428 }
429 }
430 if (result) {
Steve Dower84bcfb32015-01-02 18:07:46 -0800431 debug(L"found configured value '%ls=%ls' in %ls\n",
Brian Curtin07165f72012-06-20 15:36:14 -0500432 key, result, found_in ? found_in : L"(unknown)");
433 } else {
Steve Dower84bcfb32015-01-02 18:07:46 -0800434 debug(L"found no configured value for '%ls'\n", key);
Brian Curtin07165f72012-06-20 15:36:14 -0500435 }
436 return result;
437}
438
439static INSTALLED_PYTHON *
440locate_python(wchar_t * wanted_ver)
441{
442 static wchar_t config_key [] = { L"pythonX" };
443 static wchar_t * last_char = &config_key[sizeof(config_key) /
444 sizeof(wchar_t) - 2];
445 INSTALLED_PYTHON * result = NULL;
446 size_t n = wcslen(wanted_ver);
447 wchar_t * configured_value;
448
449 if (num_installed_pythons == 0)
450 locate_all_pythons();
451
452 if (n == 1) { /* just major version specified */
453 *last_char = *wanted_ver;
454 configured_value = get_configured_value(config_key);
455 if (configured_value != NULL)
456 wanted_ver = configured_value;
457 }
458 if (*wanted_ver) {
459 result = find_python_by_version(wanted_ver);
Steve Dower84bcfb32015-01-02 18:07:46 -0800460 debug(L"search for Python version '%ls' found ", wanted_ver);
Brian Curtin07165f72012-06-20 15:36:14 -0500461 if (result) {
Steve Dower84bcfb32015-01-02 18:07:46 -0800462 debug(L"'%ls'\n", result->executable);
Brian Curtin07165f72012-06-20 15:36:14 -0500463 } else {
464 debug(L"no interpreter\n");
465 }
466 }
467 else {
468 *last_char = L'\0'; /* look for an overall default */
469 configured_value = get_configured_value(config_key);
470 if (configured_value)
471 result = find_python_by_version(configured_value);
472 if (result == NULL)
473 result = find_python_by_version(L"2");
474 if (result == NULL)
475 result = find_python_by_version(L"3");
476 debug(L"search for default Python found ");
477 if (result) {
Steve Dower84bcfb32015-01-02 18:07:46 -0800478 debug(L"version %ls at '%ls'\n",
Brian Curtin07165f72012-06-20 15:36:14 -0500479 result->version, result->executable);
480 } else {
481 debug(L"no interpreter\n");
482 }
483 }
484 return result;
485}
486
Vinay Sajipc985d082013-07-25 11:20:55 +0100487#if defined(SCRIPT_WRAPPER)
488/*
489 * Check for a script located alongside the executable
490 */
491
492#if defined(_WINDOWS)
493#define SCRIPT_SUFFIX L"-script.pyw"
494#else
495#define SCRIPT_SUFFIX L"-script.py"
496#endif
497
498static wchar_t wrapped_script_path[MAX_PATH];
499
500/* Locate the script being wrapped.
501 *
502 * This code should store the name of the wrapped script in
503 * wrapped_script_path, or terminate the program with an error if there is no
504 * valid wrapped script file.
505 */
506static void
507locate_wrapped_script()
508{
509 wchar_t * p;
510 size_t plen;
511 DWORD attrs;
512
513 plen = GetModuleFileNameW(NULL, wrapped_script_path, MAX_PATH);
514 p = wcsrchr(wrapped_script_path, L'.');
515 if (p == NULL) {
Steve Dower84bcfb32015-01-02 18:07:46 -0800516 debug(L"GetModuleFileNameW returned value has no extension: %ls\n",
Vinay Sajipc985d082013-07-25 11:20:55 +0100517 wrapped_script_path);
Steve Dower84bcfb32015-01-02 18:07:46 -0800518 error(RC_NO_SCRIPT, L"Wrapper name '%ls' is not valid.", wrapped_script_path);
Vinay Sajipc985d082013-07-25 11:20:55 +0100519 }
520
521 wcsncpy_s(p, MAX_PATH - (p - wrapped_script_path) + 1, SCRIPT_SUFFIX, _TRUNCATE);
522 attrs = GetFileAttributesW(wrapped_script_path);
523 if (attrs == INVALID_FILE_ATTRIBUTES) {
Steve Dower84bcfb32015-01-02 18:07:46 -0800524 debug(L"File '%ls' non-existent\n", wrapped_script_path);
525 error(RC_NO_SCRIPT, L"Script file '%ls' is not present.", wrapped_script_path);
Vinay Sajipc985d082013-07-25 11:20:55 +0100526 }
527
Steve Dower84bcfb32015-01-02 18:07:46 -0800528 debug(L"Using wrapped script file '%ls'\n", wrapped_script_path);
Vinay Sajipc985d082013-07-25 11:20:55 +0100529}
530#endif
531
Brian Curtin07165f72012-06-20 15:36:14 -0500532/*
533 * Process creation code
534 */
535
536static BOOL
537safe_duplicate_handle(HANDLE in, HANDLE * pout)
538{
539 BOOL ok;
540 HANDLE process = GetCurrentProcess();
541 DWORD rc;
542
543 *pout = NULL;
544 ok = DuplicateHandle(process, in, process, pout, 0, TRUE,
545 DUPLICATE_SAME_ACCESS);
546 if (!ok) {
547 rc = GetLastError();
548 if (rc == ERROR_INVALID_HANDLE) {
549 debug(L"DuplicateHandle returned ERROR_INVALID_HANDLE\n");
550 ok = TRUE;
551 }
552 else {
553 debug(L"DuplicateHandle returned %d\n", rc);
554 }
555 }
556 return ok;
557}
558
559static BOOL WINAPI
560ctrl_c_handler(DWORD code)
561{
562 return TRUE; /* We just ignore all control events. */
563}
564
565static void
566run_child(wchar_t * cmdline)
567{
568 HANDLE job;
569 JOBOBJECT_EXTENDED_LIMIT_INFORMATION info;
570 DWORD rc;
571 BOOL ok;
572 STARTUPINFOW si;
573 PROCESS_INFORMATION pi;
574
Vinay Sajip66fef9f2013-02-26 16:29:06 +0000575#if defined(_WINDOWS)
576 // When explorer launches a Windows (GUI) application, it displays
577 // the "app starting" (the "pointer + hourglass") cursor for a number
578 // of seconds, or until the app does something UI-ish (eg, creating a
579 // window, or fetching a message). As this launcher doesn't do this
580 // directly, that cursor remains even after the child process does these
581 // things. We avoid that by doing a simple post+get message.
582 // See http://bugs.python.org/issue17290 and
583 // https://bitbucket.org/vinay.sajip/pylauncher/issue/20/busy-cursor-for-a-long-time-when-running
584 MSG msg;
585
586 PostMessage(0, 0, 0, 0);
587 GetMessage(&msg, 0, 0, 0);
588#endif
589
Steve Dower84bcfb32015-01-02 18:07:46 -0800590 debug(L"run_child: about to run '%ls'\n", cmdline);
Brian Curtin07165f72012-06-20 15:36:14 -0500591 job = CreateJobObject(NULL, NULL);
592 ok = QueryInformationJobObject(job, JobObjectExtendedLimitInformation,
593 &info, sizeof(info), &rc);
594 if (!ok || (rc != sizeof(info)) || !job)
595 error(RC_CREATE_PROCESS, L"Job information querying failed");
596 info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE |
597 JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
598 ok = SetInformationJobObject(job, JobObjectExtendedLimitInformation, &info,
599 sizeof(info));
600 if (!ok)
601 error(RC_CREATE_PROCESS, L"Job information setting failed");
602 memset(&si, 0, sizeof(si));
603 si.cb = sizeof(si);
604 ok = safe_duplicate_handle(GetStdHandle(STD_INPUT_HANDLE), &si.hStdInput);
605 if (!ok)
606 error(RC_NO_STD_HANDLES, L"stdin duplication failed");
607 ok = safe_duplicate_handle(GetStdHandle(STD_OUTPUT_HANDLE), &si.hStdOutput);
608 if (!ok)
609 error(RC_NO_STD_HANDLES, L"stdout duplication failed");
610 ok = safe_duplicate_handle(GetStdHandle(STD_ERROR_HANDLE), &si.hStdError);
611 if (!ok)
612 error(RC_NO_STD_HANDLES, L"stderr duplication failed");
613
614 ok = SetConsoleCtrlHandler(ctrl_c_handler, TRUE);
615 if (!ok)
616 error(RC_CREATE_PROCESS, L"control handler setting failed");
617
618 si.dwFlags = STARTF_USESTDHANDLES;
619 ok = CreateProcessW(NULL, cmdline, NULL, NULL, TRUE,
620 0, NULL, NULL, &si, &pi);
621 if (!ok)
Steve Dower84bcfb32015-01-02 18:07:46 -0800622 error(RC_CREATE_PROCESS, L"Unable to create process using '%ls'", cmdline);
Brian Curtin07165f72012-06-20 15:36:14 -0500623 AssignProcessToJobObject(job, pi.hProcess);
624 CloseHandle(pi.hThread);
Martin v. Löwisb26a9b12013-01-25 14:25:48 +0100625 WaitForSingleObjectEx(pi.hProcess, INFINITE, FALSE);
Brian Curtin07165f72012-06-20 15:36:14 -0500626 ok = GetExitCodeProcess(pi.hProcess, &rc);
627 if (!ok)
628 error(RC_CREATE_PROCESS, L"Failed to get exit code of process");
629 debug(L"child process exit code: %d\n", rc);
630 ExitProcess(rc);
631}
632
633static void
634invoke_child(wchar_t * executable, wchar_t * suffix, wchar_t * cmdline)
635{
636 wchar_t * child_command;
637 size_t child_command_size;
638 BOOL no_suffix = (suffix == NULL) || (*suffix == L'\0');
639 BOOL no_cmdline = (*cmdline == L'\0');
640
641 if (no_suffix && no_cmdline)
642 run_child(executable);
643 else {
644 if (no_suffix) {
645 /* add 2 for space separator + terminating NUL. */
646 child_command_size = wcslen(executable) + wcslen(cmdline) + 2;
647 }
648 else {
649 /* add 3 for 2 space separators + terminating NUL. */
650 child_command_size = wcslen(executable) + wcslen(suffix) +
651 wcslen(cmdline) + 3;
652 }
653 child_command = calloc(child_command_size, sizeof(wchar_t));
654 if (child_command == NULL)
655 error(RC_CREATE_PROCESS, L"unable to allocate %d bytes for child command.",
656 child_command_size);
657 if (no_suffix)
658 _snwprintf_s(child_command, child_command_size,
Steve Dower84bcfb32015-01-02 18:07:46 -0800659 child_command_size - 1, L"%ls %ls",
Brian Curtin07165f72012-06-20 15:36:14 -0500660 executable, cmdline);
661 else
662 _snwprintf_s(child_command, child_command_size,
Steve Dower84bcfb32015-01-02 18:07:46 -0800663 child_command_size - 1, L"%ls %ls %ls",
Brian Curtin07165f72012-06-20 15:36:14 -0500664 executable, suffix, cmdline);
665 run_child(child_command);
666 free(child_command);
667 }
668}
669
Vinay Sajip22c039b2013-06-07 15:37:28 +0100670typedef struct {
671 wchar_t *shebang;
672 BOOL search;
673} SHEBANG;
674
675static SHEBANG builtin_virtual_paths [] = {
676 { L"/usr/bin/env python", TRUE },
677 { L"/usr/bin/python", FALSE },
678 { L"/usr/local/bin/python", FALSE },
679 { L"python", FALSE },
680 { NULL, FALSE },
Brian Curtin07165f72012-06-20 15:36:14 -0500681};
682
683/* For now, a static array of commands. */
684
685#define MAX_COMMANDS 100
686
687typedef struct {
688 wchar_t key[MAX_PATH];
689 wchar_t value[MSGSIZE];
690} COMMAND;
691
692static COMMAND commands[MAX_COMMANDS];
693static int num_commands = 0;
694
695#if defined(SKIP_PREFIX)
696
697static wchar_t * builtin_prefixes [] = {
698 /* These must be in an order that the longest matches should be found,
699 * i.e. if the prefix is "/usr/bin/env ", it should match that entry
700 * *before* matching "/usr/bin/".
701 */
702 L"/usr/bin/env ",
703 L"/usr/bin/",
704 L"/usr/local/bin/",
705 NULL
706};
707
708static wchar_t * skip_prefix(wchar_t * name)
709{
710 wchar_t ** pp = builtin_prefixes;
711 wchar_t * result = name;
712 wchar_t * p;
713 size_t n;
714
715 for (; p = *pp; pp++) {
716 n = wcslen(p);
717 if (_wcsnicmp(p, name, n) == 0) {
718 result += n; /* skip the prefix */
719 if (p[n - 1] == L' ') /* No empty strings in table, so n > 1 */
720 result = skip_whitespace(result);
721 break;
722 }
723 }
724 return result;
725}
726
727#endif
728
729#if defined(SEARCH_PATH)
730
731static COMMAND path_command;
732
733static COMMAND * find_on_path(wchar_t * name)
734{
735 wchar_t * pathext;
736 size_t varsize;
737 wchar_t * context = NULL;
738 wchar_t * extension;
739 COMMAND * result = NULL;
740 DWORD len;
741 errno_t rc;
742
743 wcscpy_s(path_command.key, MAX_PATH, name);
744 if (wcschr(name, L'.') != NULL) {
745 /* assume it has an extension. */
746 len = SearchPathW(NULL, name, NULL, MSGSIZE, path_command.value, NULL);
747 if (len) {
748 result = &path_command;
749 }
750 }
751 else {
752 /* No extension - search using registered extensions. */
753 rc = _wdupenv_s(&pathext, &varsize, L"PATHEXT");
754 if (rc == 0) {
755 extension = wcstok_s(pathext, L";", &context);
756 while (extension) {
757 len = SearchPathW(NULL, name, extension, MSGSIZE, path_command.value, NULL);
758 if (len) {
759 result = &path_command;
760 break;
761 }
762 extension = wcstok_s(NULL, L";", &context);
763 }
764 free(pathext);
765 }
766 }
767 return result;
768}
769
770#endif
771
772static COMMAND * find_command(wchar_t * name)
773{
774 COMMAND * result = NULL;
775 COMMAND * cp = commands;
776 int i;
777
778 for (i = 0; i < num_commands; i++, cp++) {
779 if (_wcsicmp(cp->key, name) == 0) {
780 result = cp;
781 break;
782 }
783 }
784#if defined(SEARCH_PATH)
785 if (result == NULL)
786 result = find_on_path(name);
787#endif
788 return result;
789}
790
791static void
792update_command(COMMAND * cp, wchar_t * name, wchar_t * cmdline)
793{
794 wcsncpy_s(cp->key, MAX_PATH, name, _TRUNCATE);
795 wcsncpy_s(cp->value, MSGSIZE, cmdline, _TRUNCATE);
796}
797
798static void
799add_command(wchar_t * name, wchar_t * cmdline)
800{
801 if (num_commands >= MAX_COMMANDS) {
Steve Dower84bcfb32015-01-02 18:07:46 -0800802 debug(L"can't add %ls = '%ls': no room\n", name, cmdline);
Brian Curtin07165f72012-06-20 15:36:14 -0500803 }
804 else {
805 COMMAND * cp = &commands[num_commands++];
806
807 update_command(cp, name, cmdline);
808 }
809}
810
811static void
812read_config_file(wchar_t * config_path)
813{
814 wchar_t keynames[MSGSIZE];
815 wchar_t value[MSGSIZE];
816 DWORD read;
817 wchar_t * key;
818 COMMAND * cp;
819 wchar_t * cmdp;
820
821 read = GetPrivateProfileStringW(L"commands", NULL, NULL, keynames, MSGSIZE,
822 config_path);
823 if (read == MSGSIZE - 1) {
Steve Dower84bcfb32015-01-02 18:07:46 -0800824 debug(L"read_commands: %ls: not enough space for names\n", config_path);
Brian Curtin07165f72012-06-20 15:36:14 -0500825 }
826 key = keynames;
827 while (*key) {
828 read = GetPrivateProfileStringW(L"commands", key, NULL, value, MSGSIZE,
829 config_path);
830 if (read == MSGSIZE - 1) {
Steve Dower84bcfb32015-01-02 18:07:46 -0800831 debug(L"read_commands: %ls: not enough space for %ls\n",
Brian Curtin07165f72012-06-20 15:36:14 -0500832 config_path, key);
833 }
834 cmdp = skip_whitespace(value);
835 if (*cmdp) {
836 cp = find_command(key);
837 if (cp == NULL)
838 add_command(key, value);
839 else
840 update_command(cp, key, value);
841 }
842 key += wcslen(key) + 1;
843 }
844}
845
846static void read_commands()
847{
848 if (launcher_ini_path[0])
849 read_config_file(launcher_ini_path);
850 if (appdata_ini_path[0])
851 read_config_file(appdata_ini_path);
852}
853
854static BOOL
855parse_shebang(wchar_t * shebang_line, int nchars, wchar_t ** command,
Vinay Sajip22c039b2013-06-07 15:37:28 +0100856 wchar_t ** suffix, BOOL *search)
Brian Curtin07165f72012-06-20 15:36:14 -0500857{
858 BOOL rc = FALSE;
Vinay Sajip22c039b2013-06-07 15:37:28 +0100859 SHEBANG * vpp;
Brian Curtin07165f72012-06-20 15:36:14 -0500860 size_t plen;
861 wchar_t * p;
862 wchar_t zapped;
863 wchar_t * endp = shebang_line + nchars - 1;
864 COMMAND * cp;
865 wchar_t * skipped;
866
867 *command = NULL; /* failure return */
868 *suffix = NULL;
Vinay Sajip22c039b2013-06-07 15:37:28 +0100869 *search = FALSE;
Brian Curtin07165f72012-06-20 15:36:14 -0500870
871 if ((*shebang_line++ == L'#') && (*shebang_line++ == L'!')) {
872 shebang_line = skip_whitespace(shebang_line);
873 if (*shebang_line) {
874 *command = shebang_line;
Vinay Sajip22c039b2013-06-07 15:37:28 +0100875 for (vpp = builtin_virtual_paths; vpp->shebang; ++vpp) {
876 plen = wcslen(vpp->shebang);
877 if (wcsncmp(shebang_line, vpp->shebang, plen) == 0) {
Brian Curtin07165f72012-06-20 15:36:14 -0500878 rc = TRUE;
Vinay Sajip22c039b2013-06-07 15:37:28 +0100879 *search = vpp->search;
Brian Curtin07165f72012-06-20 15:36:14 -0500880 /* We can do this because all builtin commands contain
881 * "python".
882 */
883 *command = wcsstr(shebang_line, L"python");
884 break;
885 }
886 }
Vinay Sajip22c039b2013-06-07 15:37:28 +0100887 if (vpp->shebang == NULL) {
Brian Curtin07165f72012-06-20 15:36:14 -0500888 /*
Vinay Sajip9c10d6b2013-11-15 20:58:13 +0000889 * Not found in builtins - look in customized commands.
Brian Curtin07165f72012-06-20 15:36:14 -0500890 *
891 * We can't permanently modify the shebang line in case
Vinay Sajip9c10d6b2013-11-15 20:58:13 +0000892 * it's not a customized command, but we can temporarily
Brian Curtin07165f72012-06-20 15:36:14 -0500893 * stick a NUL after the command while searching for it,
894 * then put back the char we zapped.
895 */
896#if defined(SKIP_PREFIX)
897 skipped = skip_prefix(shebang_line);
898#else
899 skipped = shebang_line;
900#endif
901 p = wcspbrk(skipped, L" \t\r\n");
902 if (p != NULL) {
903 zapped = *p;
904 *p = L'\0';
905 }
906 cp = find_command(skipped);
907 if (p != NULL)
908 *p = zapped;
909 if (cp != NULL) {
910 *command = cp->value;
911 if (p != NULL)
912 *suffix = skip_whitespace(p);
913 }
914 }
915 /* remove trailing whitespace */
916 while ((endp > shebang_line) && isspace(*endp))
917 --endp;
918 if (endp > shebang_line)
919 endp[1] = L'\0';
920 }
921 }
922 return rc;
923}
924
925/* #define CP_UTF8 65001 defined in winnls.h */
926#define CP_UTF16LE 1200
927#define CP_UTF16BE 1201
928#define CP_UTF32LE 12000
929#define CP_UTF32BE 12001
930
931typedef struct {
932 int length;
933 char sequence[4];
934 UINT code_page;
935} BOM;
936
937/*
Vinay Sajipc985d082013-07-25 11:20:55 +0100938 * Strictly, we don't need to handle UTF-16 and UTF-32, since Python itself
Brian Curtin07165f72012-06-20 15:36:14 -0500939 * doesn't. Never mind, one day it might - there's no harm leaving it in.
940 */
941static BOM BOMs[] = {
942 { 3, { 0xEF, 0xBB, 0xBF }, CP_UTF8 }, /* UTF-8 - keep first */
943 { 2, { 0xFF, 0xFE }, CP_UTF16LE }, /* UTF-16LE */
944 { 2, { 0xFE, 0xFF }, CP_UTF16BE }, /* UTF-16BE */
945 { 4, { 0xFF, 0xFE, 0x00, 0x00 }, CP_UTF32LE }, /* UTF-32LE */
946 { 4, { 0x00, 0x00, 0xFE, 0xFF }, CP_UTF32BE }, /* UTF-32BE */
947 { 0 } /* sentinel */
948};
949
950static BOM *
951find_BOM(char * buffer)
952{
953/*
954 * Look for a BOM in the input and return a pointer to the
955 * corresponding structure, or NULL if not found.
956 */
957 BOM * result = NULL;
958 BOM *bom;
959
960 for (bom = BOMs; bom->length; bom++) {
961 if (strncmp(bom->sequence, buffer, bom->length) == 0) {
962 result = bom;
963 break;
964 }
965 }
966 return result;
967}
968
969static char *
970find_terminator(char * buffer, int len, BOM *bom)
971{
972 char * result = NULL;
973 char * end = buffer + len;
974 char * p;
975 char c;
976 int cp;
977
978 for (p = buffer; p < end; p++) {
979 c = *p;
980 if (c == '\r') {
981 result = p;
982 break;
983 }
984 if (c == '\n') {
985 result = p;
986 break;
987 }
988 }
989 if (result != NULL) {
990 cp = bom->code_page;
991
992 /* adjustments to include all bytes of the char */
993 /* no adjustment needed for UTF-8 or big endian */
994 if (cp == CP_UTF16LE)
995 ++result;
996 else if (cp == CP_UTF32LE)
997 result += 3;
998 ++result; /* point just past terminator */
999 }
1000 return result;
1001}
1002
1003static BOOL
1004validate_version(wchar_t * p)
1005{
1006 BOOL result = TRUE;
1007
1008 if (!isdigit(*p)) /* expect major version */
1009 result = FALSE;
1010 else if (*++p) { /* more to do */
1011 if (*p != L'.') /* major/minor separator */
1012 result = FALSE;
1013 else {
1014 ++p;
1015 if (!isdigit(*p)) /* expect minor version */
1016 result = FALSE;
1017 else {
1018 ++p;
1019 if (*p) { /* more to do */
1020 if (*p != L'-')
1021 result = FALSE;
1022 else {
1023 ++p;
1024 if ((*p != '3') && (*++p != '2') && !*++p)
1025 result = FALSE;
1026 }
1027 }
1028 }
1029 }
1030 }
1031 return result;
1032}
1033
1034typedef struct {
1035 unsigned short min;
1036 unsigned short max;
1037 wchar_t version[MAX_VERSION_SIZE];
1038} PYC_MAGIC;
1039
1040static PYC_MAGIC magic_values[] = {
1041 { 0xc687, 0xc687, L"2.0" },
1042 { 0xeb2a, 0xeb2a, L"2.1" },
1043 { 0xed2d, 0xed2d, L"2.2" },
1044 { 0xf23b, 0xf245, L"2.3" },
1045 { 0xf259, 0xf26d, L"2.4" },
1046 { 0xf277, 0xf2b3, L"2.5" },
1047 { 0xf2c7, 0xf2d1, L"2.6" },
1048 { 0xf2db, 0xf303, L"2.7" },
1049 { 0x0bb8, 0x0c3b, L"3.0" },
1050 { 0x0c45, 0x0c4f, L"3.1" },
1051 { 0x0c58, 0x0c6c, L"3.2" },
1052 { 0x0c76, 0x0c76, L"3.3" },
1053 { 0 }
1054};
1055
1056static INSTALLED_PYTHON *
1057find_by_magic(unsigned short magic)
1058{
1059 INSTALLED_PYTHON * result = NULL;
1060 PYC_MAGIC * mp;
1061
1062 for (mp = magic_values; mp->min; mp++) {
1063 if ((magic >= mp->min) && (magic <= mp->max)) {
1064 result = locate_python(mp->version);
1065 if (result != NULL)
1066 break;
1067 }
1068 }
1069 return result;
1070}
1071
1072static void
1073maybe_handle_shebang(wchar_t ** argv, wchar_t * cmdline)
1074{
1075/*
1076 * Look for a shebang line in the first argument. If found
1077 * and we spawn a child process, this never returns. If it
1078 * does return then we process the args "normally".
1079 *
1080 * argv[0] might be a filename with a shebang.
1081 */
1082 FILE * fp;
1083 errno_t rc = _wfopen_s(&fp, *argv, L"rb");
1084 unsigned char buffer[BUFSIZE];
1085 wchar_t shebang_line[BUFSIZE + 1];
1086 size_t read;
1087 char *p;
1088 char * start;
1089 char * shebang_alias = (char *) shebang_line;
1090 BOM* bom;
1091 int i, j, nchars = 0;
1092 int header_len;
1093 BOOL is_virt;
Vinay Sajip22c039b2013-06-07 15:37:28 +01001094 BOOL search;
Brian Curtin07165f72012-06-20 15:36:14 -05001095 wchar_t * command;
1096 wchar_t * suffix;
Vinay Sajip22c039b2013-06-07 15:37:28 +01001097 COMMAND *cmd = NULL;
Brian Curtin07165f72012-06-20 15:36:14 -05001098 INSTALLED_PYTHON * ip;
1099
1100 if (rc == 0) {
1101 read = fread(buffer, sizeof(char), BUFSIZE, fp);
1102 debug(L"maybe_handle_shebang: read %d bytes\n", read);
1103 fclose(fp);
1104
1105 if ((read >= 4) && (buffer[3] == '\n') && (buffer[2] == '\r')) {
1106 ip = find_by_magic((buffer[1] << 8 | buffer[0]) & 0xFFFF);
1107 if (ip != NULL) {
Steve Dower84bcfb32015-01-02 18:07:46 -08001108 debug(L"script file is compiled against Python %ls\n",
Brian Curtin07165f72012-06-20 15:36:14 -05001109 ip->version);
1110 invoke_child(ip->executable, NULL, cmdline);
1111 }
1112 }
1113 /* Look for BOM */
1114 bom = find_BOM(buffer);
1115 if (bom == NULL) {
1116 start = buffer;
1117 debug(L"maybe_handle_shebang: BOM not found, using UTF-8\n");
1118 bom = BOMs; /* points to UTF-8 entry - the default */
1119 }
1120 else {
1121 debug(L"maybe_handle_shebang: BOM found, code page %d\n",
1122 bom->code_page);
1123 start = &buffer[bom->length];
1124 }
1125 p = find_terminator(start, BUFSIZE, bom);
1126 /*
1127 * If no CR or LF was found in the heading,
1128 * we assume it's not a shebang file.
1129 */
1130 if (p == NULL) {
1131 debug(L"maybe_handle_shebang: No line terminator found\n");
1132 }
1133 else {
1134 /*
1135 * Found line terminator - parse the shebang.
1136 *
1137 * Strictly, we don't need to handle UTF-16 anf UTF-32,
1138 * since Python itself doesn't.
1139 * Never mind, one day it might.
1140 */
1141 header_len = (int) (p - start);
1142 switch(bom->code_page) {
1143 case CP_UTF8:
1144 nchars = MultiByteToWideChar(bom->code_page,
1145 0,
1146 start, header_len, shebang_line,
1147 BUFSIZE);
1148 break;
1149 case CP_UTF16BE:
1150 if (header_len % 2 != 0) {
1151 debug(L"maybe_handle_shebang: UTF-16BE, but an odd number \
1152of bytes: %d\n", header_len);
1153 /* nchars = 0; Not needed - initialised to 0. */
1154 }
1155 else {
1156 for (i = header_len; i > 0; i -= 2) {
1157 shebang_alias[i - 1] = start[i - 2];
1158 shebang_alias[i - 2] = start[i - 1];
1159 }
1160 nchars = header_len / sizeof(wchar_t);
1161 }
1162 break;
1163 case CP_UTF16LE:
1164 if ((header_len % 2) != 0) {
1165 debug(L"UTF-16LE, but an odd number of bytes: %d\n",
1166 header_len);
1167 /* nchars = 0; Not needed - initialised to 0. */
1168 }
1169 else {
1170 /* no actual conversion needed. */
1171 memcpy(shebang_line, start, header_len);
1172 nchars = header_len / sizeof(wchar_t);
1173 }
1174 break;
1175 case CP_UTF32BE:
1176 if (header_len % 4 != 0) {
1177 debug(L"UTF-32BE, but not divisible by 4: %d\n",
1178 header_len);
1179 /* nchars = 0; Not needed - initialised to 0. */
1180 }
1181 else {
1182 for (i = header_len, j = header_len / 2; i > 0; i -= 4,
1183 j -= 2) {
1184 shebang_alias[j - 1] = start[i - 2];
1185 shebang_alias[j - 2] = start[i - 1];
1186 }
1187 nchars = header_len / sizeof(wchar_t);
1188 }
1189 break;
1190 case CP_UTF32LE:
1191 if (header_len % 4 != 0) {
1192 debug(L"UTF-32LE, but not divisible by 4: %d\n",
1193 header_len);
1194 /* nchars = 0; Not needed - initialised to 0. */
1195 }
1196 else {
1197 for (i = header_len, j = header_len / 2; i > 0; i -= 4,
1198 j -= 2) {
1199 shebang_alias[j - 1] = start[i - 3];
1200 shebang_alias[j - 2] = start[i - 4];
1201 }
1202 nchars = header_len / sizeof(wchar_t);
1203 }
1204 break;
1205 }
1206 if (nchars > 0) {
1207 shebang_line[--nchars] = L'\0';
1208 is_virt = parse_shebang(shebang_line, nchars, &command,
Vinay Sajip22c039b2013-06-07 15:37:28 +01001209 &suffix, &search);
Brian Curtin07165f72012-06-20 15:36:14 -05001210 if (command != NULL) {
Steve Dower84bcfb32015-01-02 18:07:46 -08001211 debug(L"parse_shebang: found command: %ls\n", command);
Brian Curtin07165f72012-06-20 15:36:14 -05001212 if (!is_virt) {
1213 invoke_child(command, suffix, cmdline);
1214 }
1215 else {
1216 suffix = wcschr(command, L' ');
1217 if (suffix != NULL) {
1218 *suffix++ = L'\0';
1219 suffix = skip_whitespace(suffix);
1220 }
1221 if (wcsncmp(command, L"python", 6))
1222 error(RC_BAD_VIRTUAL_PATH, L"Unknown virtual \
Steve Dower84bcfb32015-01-02 18:07:46 -08001223path '%ls'", command);
Brian Curtin07165f72012-06-20 15:36:14 -05001224 command += 6; /* skip past "python" */
Vinay Sajip22c039b2013-06-07 15:37:28 +01001225 if (search && ((*command == L'\0') || isspace(*command))) {
1226 /* Command is eligible for path search, and there
1227 * is no version specification.
1228 */
1229 debug(L"searching PATH for python executable\n");
1230 cmd = find_on_path(L"python");
Steve Dower84bcfb32015-01-02 18:07:46 -08001231 debug(L"Python on path: %ls\n", cmd ? cmd->value : L"<not found>");
Vinay Sajip22c039b2013-06-07 15:37:28 +01001232 if (cmd) {
Steve Dower84bcfb32015-01-02 18:07:46 -08001233 debug(L"located python on PATH: %ls\n", cmd->value);
Vinay Sajip22c039b2013-06-07 15:37:28 +01001234 invoke_child(cmd->value, suffix, cmdline);
1235 /* Exit here, as we have found the command */
1236 return;
1237 }
1238 /* FALL THROUGH: No python found on PATH, so fall
1239 * back to locating the correct installed python.
1240 */
1241 }
Brian Curtin07165f72012-06-20 15:36:14 -05001242 if (*command && !validate_version(command))
1243 error(RC_BAD_VIRTUAL_PATH, L"Invalid version \
Steve Dower84bcfb32015-01-02 18:07:46 -08001244specification: '%ls'.\nIn the first line of the script, 'python' needs to be \
Brian Curtin07165f72012-06-20 15:36:14 -05001245followed by a valid version specifier.\nPlease check the documentation.",
1246 command);
1247 /* TODO could call validate_version(command) */
1248 ip = locate_python(command);
1249 if (ip == NULL) {
1250 error(RC_NO_PYTHON, L"Requested Python version \
Steve Dower84bcfb32015-01-02 18:07:46 -08001251(%ls) is not installed", command);
Brian Curtin07165f72012-06-20 15:36:14 -05001252 }
1253 else {
1254 invoke_child(ip->executable, suffix, cmdline);
1255 }
1256 }
1257 }
1258 }
1259 }
1260 }
1261}
1262
1263static wchar_t *
1264skip_me(wchar_t * cmdline)
1265{
1266 BOOL quoted;
1267 wchar_t c;
1268 wchar_t * result = cmdline;
1269
1270 quoted = cmdline[0] == L'\"';
1271 if (!quoted)
1272 c = L' ';
1273 else {
1274 c = L'\"';
1275 ++result;
1276 }
1277 result = wcschr(result, c);
1278 if (result == NULL) /* when, for example, just exe name on command line */
1279 result = L"";
1280 else {
1281 ++result; /* skip past space or closing quote */
1282 result = skip_whitespace(result);
1283 }
1284 return result;
1285}
1286
1287static DWORD version_high = 0;
1288static DWORD version_low = 0;
1289
1290static void
1291get_version_info(wchar_t * version_text, size_t size)
1292{
1293 WORD maj, min, rel, bld;
1294
1295 if (!version_high && !version_low)
1296 wcsncpy_s(version_text, size, L"0.1", _TRUNCATE); /* fallback */
1297 else {
1298 maj = HIWORD(version_high);
1299 min = LOWORD(version_high);
1300 rel = HIWORD(version_low);
1301 bld = LOWORD(version_low);
1302 _snwprintf_s(version_text, size, _TRUNCATE, L"%d.%d.%d.%d", maj,
1303 min, rel, bld);
1304 }
1305}
1306
1307static int
1308process(int argc, wchar_t ** argv)
1309{
1310 wchar_t * wp;
1311 wchar_t * command;
1312 wchar_t * p;
1313 int rc = 0;
1314 size_t plen;
1315 INSTALLED_PYTHON * ip;
1316 BOOL valid;
1317 DWORD size, attrs;
1318 HRESULT hr;
1319 wchar_t message[MSGSIZE];
1320 wchar_t version_text [MAX_PATH];
1321 void * version_data;
1322 VS_FIXEDFILEINFO * file_info;
1323 UINT block_size;
Vinay Sajip2ae8c632013-01-29 22:29:25 +00001324 int index;
Vinay Sajipc985d082013-07-25 11:20:55 +01001325#if defined(SCRIPT_WRAPPER)
1326 int newlen;
1327 wchar_t * newcommand;
1328 wchar_t * av[2];
1329#endif
Brian Curtin07165f72012-06-20 15:36:14 -05001330
1331 wp = get_env(L"PYLAUNCH_DEBUG");
1332 if ((wp != NULL) && (*wp != L'\0'))
1333 log_fp = stderr;
1334
1335#if defined(_M_X64)
1336 debug(L"launcher build: 64bit\n");
1337#else
1338 debug(L"launcher build: 32bit\n");
1339#endif
1340#if defined(_WINDOWS)
1341 debug(L"launcher executable: Windows\n");
1342#else
1343 debug(L"launcher executable: Console\n");
1344#endif
1345 /* Get the local appdata folder (non-roaming) */
1346 hr = SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA,
1347 NULL, 0, appdata_ini_path);
1348 if (hr != S_OK) {
1349 debug(L"SHGetFolderPath failed: %X\n", hr);
1350 appdata_ini_path[0] = L'\0';
1351 }
1352 else {
1353 plen = wcslen(appdata_ini_path);
1354 p = &appdata_ini_path[plen];
1355 wcsncpy_s(p, MAX_PATH - plen, L"\\py.ini", _TRUNCATE);
1356 attrs = GetFileAttributesW(appdata_ini_path);
1357 if (attrs == INVALID_FILE_ATTRIBUTES) {
Steve Dower84bcfb32015-01-02 18:07:46 -08001358 debug(L"File '%ls' non-existent\n", appdata_ini_path);
Brian Curtin07165f72012-06-20 15:36:14 -05001359 appdata_ini_path[0] = L'\0';
1360 } else {
Steve Dower84bcfb32015-01-02 18:07:46 -08001361 debug(L"Using local configuration file '%ls'\n", appdata_ini_path);
Brian Curtin07165f72012-06-20 15:36:14 -05001362 }
1363 }
1364 plen = GetModuleFileNameW(NULL, launcher_ini_path, MAX_PATH);
1365 size = GetFileVersionInfoSizeW(launcher_ini_path, &size);
1366 if (size == 0) {
1367 winerror(GetLastError(), message, MSGSIZE);
Steve Dower84bcfb32015-01-02 18:07:46 -08001368 debug(L"GetFileVersionInfoSize failed: %ls\n", message);
Brian Curtin07165f72012-06-20 15:36:14 -05001369 }
1370 else {
1371 version_data = malloc(size);
1372 if (version_data) {
1373 valid = GetFileVersionInfoW(launcher_ini_path, 0, size,
1374 version_data);
1375 if (!valid)
1376 debug(L"GetFileVersionInfo failed: %X\n", GetLastError());
1377 else {
Vinay Sajip404229b2013-01-29 22:52:57 +00001378 valid = VerQueryValueW(version_data, L"\\",
1379 (LPVOID *) &file_info, &block_size);
Brian Curtin07165f72012-06-20 15:36:14 -05001380 if (!valid)
1381 debug(L"VerQueryValue failed: %X\n", GetLastError());
1382 else {
1383 version_high = file_info->dwFileVersionMS;
1384 version_low = file_info->dwFileVersionLS;
1385 }
1386 }
1387 free(version_data);
1388 }
1389 }
1390 p = wcsrchr(launcher_ini_path, L'\\');
1391 if (p == NULL) {
Steve Dower84bcfb32015-01-02 18:07:46 -08001392 debug(L"GetModuleFileNameW returned value has no backslash: %ls\n",
Brian Curtin07165f72012-06-20 15:36:14 -05001393 launcher_ini_path);
1394 launcher_ini_path[0] = L'\0';
1395 }
1396 else {
1397 wcsncpy_s(p, MAX_PATH - (p - launcher_ini_path), L"\\py.ini",
1398 _TRUNCATE);
1399 attrs = GetFileAttributesW(launcher_ini_path);
1400 if (attrs == INVALID_FILE_ATTRIBUTES) {
Steve Dower84bcfb32015-01-02 18:07:46 -08001401 debug(L"File '%ls' non-existent\n", launcher_ini_path);
Brian Curtin07165f72012-06-20 15:36:14 -05001402 launcher_ini_path[0] = L'\0';
1403 } else {
Steve Dower84bcfb32015-01-02 18:07:46 -08001404 debug(L"Using global configuration file '%ls'\n", launcher_ini_path);
Brian Curtin07165f72012-06-20 15:36:14 -05001405 }
1406 }
1407
1408 command = skip_me(GetCommandLineW());
Steve Dower84bcfb32015-01-02 18:07:46 -08001409 debug(L"Called with command line: %ls\n", command);
Vinay Sajipc985d082013-07-25 11:20:55 +01001410
1411#if defined(SCRIPT_WRAPPER)
1412 /* The launcher is being used in "script wrapper" mode.
1413 * There should therefore be a Python script named <exename>-script.py in
1414 * the same directory as the launcher executable.
1415 * Put the script name into argv as the first (script name) argument.
1416 */
1417
1418 /* Get the wrapped script name - if the script is not present, this will
1419 * terminate the program with an error.
1420 */
1421 locate_wrapped_script();
1422
1423 /* Add the wrapped script to the start of command */
1424 newlen = wcslen(wrapped_script_path) + wcslen(command) + 2; /* ' ' + NUL */
1425 newcommand = malloc(sizeof(wchar_t) * newlen);
1426 if (!newcommand) {
1427 error(RC_NO_MEMORY, L"Could not allocate new command line");
1428 }
1429 else {
1430 wcscpy_s(newcommand, newlen, wrapped_script_path);
1431 wcscat_s(newcommand, newlen, L" ");
1432 wcscat_s(newcommand, newlen, command);
Steve Dower84bcfb32015-01-02 18:07:46 -08001433 debug(L"Running wrapped script with command line '%ls'\n", newcommand);
Vinay Sajipc985d082013-07-25 11:20:55 +01001434 read_commands();
1435 av[0] = wrapped_script_path;
1436 av[1] = NULL;
1437 maybe_handle_shebang(av, newcommand);
1438 /* Returns if no shebang line - pass to default processing */
1439 command = newcommand;
1440 valid = FALSE;
1441 }
1442#else
Brian Curtin07165f72012-06-20 15:36:14 -05001443 if (argc <= 1) {
1444 valid = FALSE;
1445 p = NULL;
1446 }
1447 else {
1448 p = argv[1];
1449 plen = wcslen(p);
Brian Curtin07165f72012-06-20 15:36:14 -05001450 valid = (*p == L'-') && validate_version(&p[1]);
1451 if (valid) {
1452 ip = locate_python(&p[1]);
1453 if (ip == NULL)
Steve Dower84bcfb32015-01-02 18:07:46 -08001454 error(RC_NO_PYTHON, L"Requested Python version (%ls) not \
Brian Curtin07165f72012-06-20 15:36:14 -05001455installed", &p[1]);
1456 command += wcslen(p);
1457 command = skip_whitespace(command);
1458 }
Vinay Sajip2ae8c632013-01-29 22:29:25 +00001459 else {
1460 for (index = 1; index < argc; ++index) {
1461 if (*argv[index] != L'-')
1462 break;
1463 }
1464 if (index < argc) {
1465 read_commands();
1466 maybe_handle_shebang(&argv[index], command);
1467 }
1468 }
Brian Curtin07165f72012-06-20 15:36:14 -05001469 }
Vinay Sajipc985d082013-07-25 11:20:55 +01001470#endif
1471
Brian Curtin07165f72012-06-20 15:36:14 -05001472 if (!valid) {
1473 ip = locate_python(L"");
1474 if (ip == NULL)
1475 error(RC_NO_PYTHON, L"Can't find a default Python.");
1476 if ((argc == 2) && (!_wcsicmp(p, L"-h") || !_wcsicmp(p, L"--help"))) {
1477#if defined(_M_X64)
1478 BOOL canDo64bit = TRUE;
1479#else
1480 // If we are a 32bit process on a 64bit Windows, first hit the 64bit keys.
1481 BOOL canDo64bit = FALSE;
1482 IsWow64Process(GetCurrentProcess(), &canDo64bit);
1483#endif
1484
1485 get_version_info(version_text, MAX_PATH);
1486 fwprintf(stdout, L"\
Steve Dower84bcfb32015-01-02 18:07:46 -08001487Python Launcher for Windows Version %ls\n\n", version_text);
Brian Curtin07165f72012-06-20 15:36:14 -05001488 fwprintf(stdout, L"\
Steve Dower84bcfb32015-01-02 18:07:46 -08001489usage: %ls [ launcher-arguments ] [ python-arguments ] script [ script-arguments ]\n\n", argv[0]);
Brian Curtin07165f72012-06-20 15:36:14 -05001490 fputws(L"\
1491Launcher arguments:\n\n\
1492-2 : Launch the latest Python 2.x version\n\
1493-3 : Launch the latest Python 3.x version\n\
1494-X.Y : Launch the specified Python version\n", stdout);
1495 if (canDo64bit) {
1496 fputws(L"\
1497-X.Y-32: Launch the specified 32bit Python version", stdout);
1498 }
1499 fputws(L"\n\nThe following help text is from Python:\n\n", stdout);
1500 fflush(stdout);
1501 }
1502 }
1503 invoke_child(ip->executable, NULL, command);
1504 return rc;
1505}
1506
1507#if defined(_WINDOWS)
1508
1509int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
1510 LPWSTR lpstrCmd, int nShow)
1511{
1512 return process(__argc, __wargv);
1513}
1514
1515#else
1516
1517int cdecl wmain(int argc, wchar_t ** argv)
1518{
1519 return process(argc, argv);
1520}
1521
Vinay Sajip2ae8c632013-01-29 22:29:25 +00001522#endif