blob: cbd83ffba5ef03ef8ea2f809d7998595a1c52f4b [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
160/* to hold SOFTWARE\Python\PythonCore\X.Y\InstallPath */
161#define IP_BASE_SIZE 40
162#define IP_SIZE (IP_BASE_SIZE + MAX_VERSION_SIZE)
163#define CORE_PATH L"SOFTWARE\\Python\\PythonCore"
164
165static wchar_t * location_checks[] = {
166 L"\\",
167 L"\\PCBuild\\",
168 L"\\PCBuild\\amd64\\",
169 NULL
170};
171
172static INSTALLED_PYTHON *
173find_existing_python(wchar_t * path)
174{
175 INSTALLED_PYTHON * result = NULL;
176 size_t i;
177 INSTALLED_PYTHON * ip;
178
179 for (i = 0, ip = installed_pythons; i < num_installed_pythons; i++, ip++) {
180 if (_wcsicmp(path, ip->executable) == 0) {
181 result = ip;
182 break;
183 }
184 }
185 return result;
186}
187
188static void
189locate_pythons_for_key(HKEY root, REGSAM flags)
190{
191 HKEY core_root, ip_key;
192 LSTATUS status = RegOpenKeyExW(root, CORE_PATH, 0, flags, &core_root);
193 wchar_t message[MSGSIZE];
194 DWORD i;
195 size_t n;
196 BOOL ok;
197 DWORD type, data_size, attrs;
198 INSTALLED_PYTHON * ip, * pip;
199 wchar_t ip_path[IP_SIZE];
200 wchar_t * check;
201 wchar_t ** checkp;
202 wchar_t *key_name = (root == HKEY_LOCAL_MACHINE) ? L"HKLM" : L"HKCU";
203
204 if (status != ERROR_SUCCESS)
Steve Dower84bcfb32015-01-02 18:07:46 -0800205 debug(L"locate_pythons_for_key: unable to open PythonCore key in %ls\n",
Brian Curtin07165f72012-06-20 15:36:14 -0500206 key_name);
207 else {
208 ip = &installed_pythons[num_installed_pythons];
209 for (i = 0; num_installed_pythons < MAX_INSTALLED_PYTHONS; i++) {
210 status = RegEnumKeyW(core_root, i, ip->version, MAX_VERSION_SIZE);
211 if (status != ERROR_SUCCESS) {
212 if (status != ERROR_NO_MORE_ITEMS) {
213 /* unexpected error */
214 winerror(status, message, MSGSIZE);
Steve Dower84bcfb32015-01-02 18:07:46 -0800215 debug(L"Can't enumerate registry key for version %ls: %ls\n",
Brian Curtin07165f72012-06-20 15:36:14 -0500216 ip->version, message);
217 }
218 break;
219 }
220 else {
221 _snwprintf_s(ip_path, IP_SIZE, _TRUNCATE,
Steve Dower84bcfb32015-01-02 18:07:46 -0800222 L"%ls\\%ls\\InstallPath", CORE_PATH, ip->version);
Brian Curtin07165f72012-06-20 15:36:14 -0500223 status = RegOpenKeyExW(root, ip_path, 0, flags, &ip_key);
224 if (status != ERROR_SUCCESS) {
225 winerror(status, message, MSGSIZE);
226 // Note: 'message' already has a trailing \n
Steve Dower84bcfb32015-01-02 18:07:46 -0800227 debug(L"%ls\\%ls: %ls", key_name, ip_path, message);
Brian Curtin07165f72012-06-20 15:36:14 -0500228 continue;
229 }
230 data_size = sizeof(ip->executable) - 1;
Martin v. Löwisaf21ebb2012-06-21 18:15:54 +0200231 status = RegQueryValueExW(ip_key, NULL, NULL, &type,
232 (LPBYTE)ip->executable, &data_size);
Brian Curtin07165f72012-06-20 15:36:14 -0500233 RegCloseKey(ip_key);
234 if (status != ERROR_SUCCESS) {
235 winerror(status, message, MSGSIZE);
Steve Dower84bcfb32015-01-02 18:07:46 -0800236 debug(L"%ls\\%ls: %ls\n", key_name, ip_path, message);
Brian Curtin07165f72012-06-20 15:36:14 -0500237 continue;
238 }
239 if (type == REG_SZ) {
240 data_size = data_size / sizeof(wchar_t) - 1; /* for NUL */
241 if (ip->executable[data_size - 1] == L'\\')
242 --data_size; /* reg value ended in a backslash */
243 /* ip->executable is data_size long */
244 for (checkp = location_checks; *checkp; ++checkp) {
245 check = *checkp;
246 _snwprintf_s(&ip->executable[data_size],
247 MAX_PATH - data_size,
248 MAX_PATH - data_size,
Steve Dower84bcfb32015-01-02 18:07:46 -0800249 L"%ls%ls", check, PYTHON_EXECUTABLE);
Brian Curtin07165f72012-06-20 15:36:14 -0500250 attrs = GetFileAttributesW(ip->executable);
251 if (attrs == INVALID_FILE_ATTRIBUTES) {
252 winerror(GetLastError(), message, MSGSIZE);
Steve Dower84bcfb32015-01-02 18:07:46 -0800253 debug(L"locate_pythons_for_key: %ls: %ls",
Brian Curtin07165f72012-06-20 15:36:14 -0500254 ip->executable, message);
255 }
256 else if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
Steve Dower84bcfb32015-01-02 18:07:46 -0800257 debug(L"locate_pythons_for_key: '%ls' is a \
Brian Curtin07165f72012-06-20 15:36:14 -0500258directory\n",
259 ip->executable, attrs);
260 }
261 else if (find_existing_python(ip->executable)) {
Steve Dower84bcfb32015-01-02 18:07:46 -0800262 debug(L"locate_pythons_for_key: %ls: already \
263found: %ls\n", ip->executable);
Brian Curtin07165f72012-06-20 15:36:14 -0500264 }
265 else {
266 /* check the executable type. */
267 ok = GetBinaryTypeW(ip->executable, &attrs);
268 if (!ok) {
Steve Dower84bcfb32015-01-02 18:07:46 -0800269 debug(L"Failure getting binary type: %ls\n",
Brian Curtin07165f72012-06-20 15:36:14 -0500270 ip->executable);
271 }
272 else {
273 if (attrs == SCS_64BIT_BINARY)
274 ip->bits = 64;
275 else if (attrs == SCS_32BIT_BINARY)
276 ip->bits = 32;
277 else
278 ip->bits = 0;
279 if (ip->bits == 0) {
Steve Dower84bcfb32015-01-02 18:07:46 -0800280 debug(L"locate_pythons_for_key: %ls: \
Brian Curtin07165f72012-06-20 15:36:14 -0500281invalid binary type: %X\n",
282 ip->executable, attrs);
283 }
284 else {
285 if (wcschr(ip->executable, L' ') != NULL) {
286 /* has spaces, so quote */
287 n = wcslen(ip->executable);
288 memmove(&ip->executable[1],
289 ip->executable, n * sizeof(wchar_t));
290 ip->executable[0] = L'\"';
291 ip->executable[n + 1] = L'\"';
292 ip->executable[n + 2] = L'\0';
293 }
Steve Dower84bcfb32015-01-02 18:07:46 -0800294 debug(L"locate_pythons_for_key: %ls \
Brian Curtin07165f72012-06-20 15:36:14 -0500295is a %dbit executable\n",
296 ip->executable, ip->bits);
297 ++num_installed_pythons;
298 pip = ip++;
299 if (num_installed_pythons >=
300 MAX_INSTALLED_PYTHONS)
301 break;
302 /* Copy over the attributes for the next */
303 *ip = *pip;
304 }
305 }
306 }
307 }
308 }
309 }
310 }
311 RegCloseKey(core_root);
312 }
313}
314
315static int
316compare_pythons(const void * p1, const void * p2)
317{
318 INSTALLED_PYTHON * ip1 = (INSTALLED_PYTHON *) p1;
319 INSTALLED_PYTHON * ip2 = (INSTALLED_PYTHON *) p2;
320 /* note reverse sorting on version */
321 int result = wcscmp(ip2->version, ip1->version);
322
323 if (result == 0)
324 result = ip2->bits - ip1->bits; /* 64 before 32 */
325 return result;
326}
327
328static void
329locate_all_pythons()
330{
331#if defined(_M_X64)
332 // If we are a 64bit process, first hit the 32bit keys.
333 debug(L"locating Pythons in 32bit registry\n");
334 locate_pythons_for_key(HKEY_CURRENT_USER, KEY_READ | KEY_WOW64_32KEY);
335 locate_pythons_for_key(HKEY_LOCAL_MACHINE, KEY_READ | KEY_WOW64_32KEY);
336#else
337 // If we are a 32bit process on a 64bit Windows, first hit the 64bit keys.
338 BOOL f64 = FALSE;
339 if (IsWow64Process(GetCurrentProcess(), &f64) && f64) {
340 debug(L"locating Pythons in 64bit registry\n");
341 locate_pythons_for_key(HKEY_CURRENT_USER, KEY_READ | KEY_WOW64_64KEY);
342 locate_pythons_for_key(HKEY_LOCAL_MACHINE, KEY_READ | KEY_WOW64_64KEY);
343 }
344#endif
345 // now hit the "native" key for this process bittedness.
346 debug(L"locating Pythons in native registry\n");
347 locate_pythons_for_key(HKEY_CURRENT_USER, KEY_READ);
348 locate_pythons_for_key(HKEY_LOCAL_MACHINE, KEY_READ);
349 qsort(installed_pythons, num_installed_pythons, sizeof(INSTALLED_PYTHON),
350 compare_pythons);
351}
352
353static INSTALLED_PYTHON *
354find_python_by_version(wchar_t const * wanted_ver)
355{
356 INSTALLED_PYTHON * result = NULL;
357 INSTALLED_PYTHON * ip = installed_pythons;
358 size_t i, n;
359 size_t wlen = wcslen(wanted_ver);
360 int bits = 0;
361
362 if (wcsstr(wanted_ver, L"-32"))
363 bits = 32;
364 for (i = 0; i < num_installed_pythons; i++, ip++) {
365 n = wcslen(ip->version);
366 if (n > wlen)
367 n = wlen;
368 if ((wcsncmp(ip->version, wanted_ver, n) == 0) &&
369 /* bits == 0 => don't care */
370 ((bits == 0) || (ip->bits == bits))) {
371 result = ip;
372 break;
373 }
374 }
375 return result;
376}
377
378
379static wchar_t appdata_ini_path[MAX_PATH];
380static wchar_t launcher_ini_path[MAX_PATH];
381
382/*
383 * Get a value either from the environment or a configuration file.
384 * The key passed in will either be "python", "python2" or "python3".
385 */
386static wchar_t *
387get_configured_value(wchar_t * key)
388{
389/*
390 * Note: this static value is used to return a configured value
391 * obtained either from the environment or configuration file.
392 * This should be OK since there wouldn't be any concurrent calls.
393 */
394 static wchar_t configured_value[MSGSIZE];
395 wchar_t * result = NULL;
396 wchar_t * found_in = L"environment";
397 DWORD size;
398
399 /* First, search the environment. */
Steve Dower84bcfb32015-01-02 18:07:46 -0800400 _snwprintf_s(configured_value, MSGSIZE, _TRUNCATE, L"py_%ls", key);
Brian Curtin07165f72012-06-20 15:36:14 -0500401 result = get_env(configured_value);
402 if (result == NULL && appdata_ini_path[0]) {
403 /* Not in environment: check local configuration. */
404 size = GetPrivateProfileStringW(L"defaults", key, NULL,
405 configured_value, MSGSIZE,
406 appdata_ini_path);
407 if (size > 0) {
408 result = configured_value;
409 found_in = appdata_ini_path;
410 }
411 }
412 if (result == NULL && launcher_ini_path[0]) {
413 /* Not in environment or local: check global configuration. */
414 size = GetPrivateProfileStringW(L"defaults", key, NULL,
415 configured_value, MSGSIZE,
416 launcher_ini_path);
417 if (size > 0) {
418 result = configured_value;
419 found_in = launcher_ini_path;
420 }
421 }
422 if (result) {
Steve Dower84bcfb32015-01-02 18:07:46 -0800423 debug(L"found configured value '%ls=%ls' in %ls\n",
Brian Curtin07165f72012-06-20 15:36:14 -0500424 key, result, found_in ? found_in : L"(unknown)");
425 } else {
Steve Dower84bcfb32015-01-02 18:07:46 -0800426 debug(L"found no configured value for '%ls'\n", key);
Brian Curtin07165f72012-06-20 15:36:14 -0500427 }
428 return result;
429}
430
431static INSTALLED_PYTHON *
432locate_python(wchar_t * wanted_ver)
433{
434 static wchar_t config_key [] = { L"pythonX" };
435 static wchar_t * last_char = &config_key[sizeof(config_key) /
436 sizeof(wchar_t) - 2];
437 INSTALLED_PYTHON * result = NULL;
438 size_t n = wcslen(wanted_ver);
439 wchar_t * configured_value;
440
441 if (num_installed_pythons == 0)
442 locate_all_pythons();
443
444 if (n == 1) { /* just major version specified */
445 *last_char = *wanted_ver;
446 configured_value = get_configured_value(config_key);
447 if (configured_value != NULL)
448 wanted_ver = configured_value;
449 }
450 if (*wanted_ver) {
451 result = find_python_by_version(wanted_ver);
Steve Dower84bcfb32015-01-02 18:07:46 -0800452 debug(L"search for Python version '%ls' found ", wanted_ver);
Brian Curtin07165f72012-06-20 15:36:14 -0500453 if (result) {
Steve Dower84bcfb32015-01-02 18:07:46 -0800454 debug(L"'%ls'\n", result->executable);
Brian Curtin07165f72012-06-20 15:36:14 -0500455 } else {
456 debug(L"no interpreter\n");
457 }
458 }
459 else {
460 *last_char = L'\0'; /* look for an overall default */
461 configured_value = get_configured_value(config_key);
462 if (configured_value)
463 result = find_python_by_version(configured_value);
464 if (result == NULL)
465 result = find_python_by_version(L"2");
466 if (result == NULL)
467 result = find_python_by_version(L"3");
468 debug(L"search for default Python found ");
469 if (result) {
Steve Dower84bcfb32015-01-02 18:07:46 -0800470 debug(L"version %ls at '%ls'\n",
Brian Curtin07165f72012-06-20 15:36:14 -0500471 result->version, result->executable);
472 } else {
473 debug(L"no interpreter\n");
474 }
475 }
476 return result;
477}
478
Vinay Sajipc985d082013-07-25 11:20:55 +0100479#if defined(SCRIPT_WRAPPER)
480/*
481 * Check for a script located alongside the executable
482 */
483
484#if defined(_WINDOWS)
485#define SCRIPT_SUFFIX L"-script.pyw"
486#else
487#define SCRIPT_SUFFIX L"-script.py"
488#endif
489
490static wchar_t wrapped_script_path[MAX_PATH];
491
492/* Locate the script being wrapped.
493 *
494 * This code should store the name of the wrapped script in
495 * wrapped_script_path, or terminate the program with an error if there is no
496 * valid wrapped script file.
497 */
498static void
499locate_wrapped_script()
500{
501 wchar_t * p;
502 size_t plen;
503 DWORD attrs;
504
505 plen = GetModuleFileNameW(NULL, wrapped_script_path, MAX_PATH);
506 p = wcsrchr(wrapped_script_path, L'.');
507 if (p == NULL) {
Steve Dower84bcfb32015-01-02 18:07:46 -0800508 debug(L"GetModuleFileNameW returned value has no extension: %ls\n",
Vinay Sajipc985d082013-07-25 11:20:55 +0100509 wrapped_script_path);
Steve Dower84bcfb32015-01-02 18:07:46 -0800510 error(RC_NO_SCRIPT, L"Wrapper name '%ls' is not valid.", wrapped_script_path);
Vinay Sajipc985d082013-07-25 11:20:55 +0100511 }
512
513 wcsncpy_s(p, MAX_PATH - (p - wrapped_script_path) + 1, SCRIPT_SUFFIX, _TRUNCATE);
514 attrs = GetFileAttributesW(wrapped_script_path);
515 if (attrs == INVALID_FILE_ATTRIBUTES) {
Steve Dower84bcfb32015-01-02 18:07:46 -0800516 debug(L"File '%ls' non-existent\n", wrapped_script_path);
517 error(RC_NO_SCRIPT, L"Script file '%ls' is not present.", wrapped_script_path);
Vinay Sajipc985d082013-07-25 11:20:55 +0100518 }
519
Steve Dower84bcfb32015-01-02 18:07:46 -0800520 debug(L"Using wrapped script file '%ls'\n", wrapped_script_path);
Vinay Sajipc985d082013-07-25 11:20:55 +0100521}
522#endif
523
Brian Curtin07165f72012-06-20 15:36:14 -0500524/*
525 * Process creation code
526 */
527
528static BOOL
529safe_duplicate_handle(HANDLE in, HANDLE * pout)
530{
531 BOOL ok;
532 HANDLE process = GetCurrentProcess();
533 DWORD rc;
534
535 *pout = NULL;
536 ok = DuplicateHandle(process, in, process, pout, 0, TRUE,
537 DUPLICATE_SAME_ACCESS);
538 if (!ok) {
539 rc = GetLastError();
540 if (rc == ERROR_INVALID_HANDLE) {
541 debug(L"DuplicateHandle returned ERROR_INVALID_HANDLE\n");
542 ok = TRUE;
543 }
544 else {
545 debug(L"DuplicateHandle returned %d\n", rc);
546 }
547 }
548 return ok;
549}
550
551static BOOL WINAPI
552ctrl_c_handler(DWORD code)
553{
554 return TRUE; /* We just ignore all control events. */
555}
556
557static void
558run_child(wchar_t * cmdline)
559{
560 HANDLE job;
561 JOBOBJECT_EXTENDED_LIMIT_INFORMATION info;
562 DWORD rc;
563 BOOL ok;
564 STARTUPINFOW si;
565 PROCESS_INFORMATION pi;
566
Vinay Sajip66fef9f2013-02-26 16:29:06 +0000567#if defined(_WINDOWS)
568 // When explorer launches a Windows (GUI) application, it displays
569 // the "app starting" (the "pointer + hourglass") cursor for a number
570 // of seconds, or until the app does something UI-ish (eg, creating a
571 // window, or fetching a message). As this launcher doesn't do this
572 // directly, that cursor remains even after the child process does these
573 // things. We avoid that by doing a simple post+get message.
574 // See http://bugs.python.org/issue17290 and
575 // https://bitbucket.org/vinay.sajip/pylauncher/issue/20/busy-cursor-for-a-long-time-when-running
576 MSG msg;
577
578 PostMessage(0, 0, 0, 0);
579 GetMessage(&msg, 0, 0, 0);
580#endif
581
Steve Dower84bcfb32015-01-02 18:07:46 -0800582 debug(L"run_child: about to run '%ls'\n", cmdline);
Brian Curtin07165f72012-06-20 15:36:14 -0500583 job = CreateJobObject(NULL, NULL);
584 ok = QueryInformationJobObject(job, JobObjectExtendedLimitInformation,
585 &info, sizeof(info), &rc);
586 if (!ok || (rc != sizeof(info)) || !job)
587 error(RC_CREATE_PROCESS, L"Job information querying failed");
588 info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE |
589 JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
590 ok = SetInformationJobObject(job, JobObjectExtendedLimitInformation, &info,
591 sizeof(info));
592 if (!ok)
593 error(RC_CREATE_PROCESS, L"Job information setting failed");
594 memset(&si, 0, sizeof(si));
595 si.cb = sizeof(si);
596 ok = safe_duplicate_handle(GetStdHandle(STD_INPUT_HANDLE), &si.hStdInput);
597 if (!ok)
598 error(RC_NO_STD_HANDLES, L"stdin duplication failed");
599 ok = safe_duplicate_handle(GetStdHandle(STD_OUTPUT_HANDLE), &si.hStdOutput);
600 if (!ok)
601 error(RC_NO_STD_HANDLES, L"stdout duplication failed");
602 ok = safe_duplicate_handle(GetStdHandle(STD_ERROR_HANDLE), &si.hStdError);
603 if (!ok)
604 error(RC_NO_STD_HANDLES, L"stderr duplication failed");
605
606 ok = SetConsoleCtrlHandler(ctrl_c_handler, TRUE);
607 if (!ok)
608 error(RC_CREATE_PROCESS, L"control handler setting failed");
609
610 si.dwFlags = STARTF_USESTDHANDLES;
611 ok = CreateProcessW(NULL, cmdline, NULL, NULL, TRUE,
612 0, NULL, NULL, &si, &pi);
613 if (!ok)
Steve Dower84bcfb32015-01-02 18:07:46 -0800614 error(RC_CREATE_PROCESS, L"Unable to create process using '%ls'", cmdline);
Brian Curtin07165f72012-06-20 15:36:14 -0500615 AssignProcessToJobObject(job, pi.hProcess);
616 CloseHandle(pi.hThread);
Martin v. Löwisb26a9b12013-01-25 14:25:48 +0100617 WaitForSingleObjectEx(pi.hProcess, INFINITE, FALSE);
Brian Curtin07165f72012-06-20 15:36:14 -0500618 ok = GetExitCodeProcess(pi.hProcess, &rc);
619 if (!ok)
620 error(RC_CREATE_PROCESS, L"Failed to get exit code of process");
621 debug(L"child process exit code: %d\n", rc);
622 ExitProcess(rc);
623}
624
625static void
626invoke_child(wchar_t * executable, wchar_t * suffix, wchar_t * cmdline)
627{
628 wchar_t * child_command;
629 size_t child_command_size;
630 BOOL no_suffix = (suffix == NULL) || (*suffix == L'\0');
631 BOOL no_cmdline = (*cmdline == L'\0');
632
633 if (no_suffix && no_cmdline)
634 run_child(executable);
635 else {
636 if (no_suffix) {
637 /* add 2 for space separator + terminating NUL. */
638 child_command_size = wcslen(executable) + wcslen(cmdline) + 2;
639 }
640 else {
641 /* add 3 for 2 space separators + terminating NUL. */
642 child_command_size = wcslen(executable) + wcslen(suffix) +
643 wcslen(cmdline) + 3;
644 }
645 child_command = calloc(child_command_size, sizeof(wchar_t));
646 if (child_command == NULL)
647 error(RC_CREATE_PROCESS, L"unable to allocate %d bytes for child command.",
648 child_command_size);
649 if (no_suffix)
650 _snwprintf_s(child_command, child_command_size,
Steve Dower84bcfb32015-01-02 18:07:46 -0800651 child_command_size - 1, L"%ls %ls",
Brian Curtin07165f72012-06-20 15:36:14 -0500652 executable, cmdline);
653 else
654 _snwprintf_s(child_command, child_command_size,
Steve Dower84bcfb32015-01-02 18:07:46 -0800655 child_command_size - 1, L"%ls %ls %ls",
Brian Curtin07165f72012-06-20 15:36:14 -0500656 executable, suffix, cmdline);
657 run_child(child_command);
658 free(child_command);
659 }
660}
661
Vinay Sajip22c039b2013-06-07 15:37:28 +0100662typedef struct {
663 wchar_t *shebang;
664 BOOL search;
665} SHEBANG;
666
667static SHEBANG builtin_virtual_paths [] = {
668 { L"/usr/bin/env python", TRUE },
669 { L"/usr/bin/python", FALSE },
670 { L"/usr/local/bin/python", FALSE },
671 { L"python", FALSE },
672 { NULL, FALSE },
Brian Curtin07165f72012-06-20 15:36:14 -0500673};
674
675/* For now, a static array of commands. */
676
677#define MAX_COMMANDS 100
678
679typedef struct {
680 wchar_t key[MAX_PATH];
681 wchar_t value[MSGSIZE];
682} COMMAND;
683
684static COMMAND commands[MAX_COMMANDS];
685static int num_commands = 0;
686
687#if defined(SKIP_PREFIX)
688
689static wchar_t * builtin_prefixes [] = {
690 /* These must be in an order that the longest matches should be found,
691 * i.e. if the prefix is "/usr/bin/env ", it should match that entry
692 * *before* matching "/usr/bin/".
693 */
694 L"/usr/bin/env ",
695 L"/usr/bin/",
696 L"/usr/local/bin/",
697 NULL
698};
699
700static wchar_t * skip_prefix(wchar_t * name)
701{
702 wchar_t ** pp = builtin_prefixes;
703 wchar_t * result = name;
704 wchar_t * p;
705 size_t n;
706
707 for (; p = *pp; pp++) {
708 n = wcslen(p);
709 if (_wcsnicmp(p, name, n) == 0) {
710 result += n; /* skip the prefix */
711 if (p[n - 1] == L' ') /* No empty strings in table, so n > 1 */
712 result = skip_whitespace(result);
713 break;
714 }
715 }
716 return result;
717}
718
719#endif
720
721#if defined(SEARCH_PATH)
722
723static COMMAND path_command;
724
725static COMMAND * find_on_path(wchar_t * name)
726{
727 wchar_t * pathext;
728 size_t varsize;
729 wchar_t * context = NULL;
730 wchar_t * extension;
731 COMMAND * result = NULL;
732 DWORD len;
733 errno_t rc;
734
735 wcscpy_s(path_command.key, MAX_PATH, name);
736 if (wcschr(name, L'.') != NULL) {
737 /* assume it has an extension. */
738 len = SearchPathW(NULL, name, NULL, MSGSIZE, path_command.value, NULL);
739 if (len) {
740 result = &path_command;
741 }
742 }
743 else {
744 /* No extension - search using registered extensions. */
745 rc = _wdupenv_s(&pathext, &varsize, L"PATHEXT");
746 if (rc == 0) {
747 extension = wcstok_s(pathext, L";", &context);
748 while (extension) {
749 len = SearchPathW(NULL, name, extension, MSGSIZE, path_command.value, NULL);
750 if (len) {
751 result = &path_command;
752 break;
753 }
754 extension = wcstok_s(NULL, L";", &context);
755 }
756 free(pathext);
757 }
758 }
759 return result;
760}
761
762#endif
763
764static COMMAND * find_command(wchar_t * name)
765{
766 COMMAND * result = NULL;
767 COMMAND * cp = commands;
768 int i;
769
770 for (i = 0; i < num_commands; i++, cp++) {
771 if (_wcsicmp(cp->key, name) == 0) {
772 result = cp;
773 break;
774 }
775 }
776#if defined(SEARCH_PATH)
777 if (result == NULL)
778 result = find_on_path(name);
779#endif
780 return result;
781}
782
783static void
784update_command(COMMAND * cp, wchar_t * name, wchar_t * cmdline)
785{
786 wcsncpy_s(cp->key, MAX_PATH, name, _TRUNCATE);
787 wcsncpy_s(cp->value, MSGSIZE, cmdline, _TRUNCATE);
788}
789
790static void
791add_command(wchar_t * name, wchar_t * cmdline)
792{
793 if (num_commands >= MAX_COMMANDS) {
Steve Dower84bcfb32015-01-02 18:07:46 -0800794 debug(L"can't add %ls = '%ls': no room\n", name, cmdline);
Brian Curtin07165f72012-06-20 15:36:14 -0500795 }
796 else {
797 COMMAND * cp = &commands[num_commands++];
798
799 update_command(cp, name, cmdline);
800 }
801}
802
803static void
804read_config_file(wchar_t * config_path)
805{
806 wchar_t keynames[MSGSIZE];
807 wchar_t value[MSGSIZE];
808 DWORD read;
809 wchar_t * key;
810 COMMAND * cp;
811 wchar_t * cmdp;
812
813 read = GetPrivateProfileStringW(L"commands", NULL, NULL, keynames, MSGSIZE,
814 config_path);
815 if (read == MSGSIZE - 1) {
Steve Dower84bcfb32015-01-02 18:07:46 -0800816 debug(L"read_commands: %ls: not enough space for names\n", config_path);
Brian Curtin07165f72012-06-20 15:36:14 -0500817 }
818 key = keynames;
819 while (*key) {
820 read = GetPrivateProfileStringW(L"commands", key, NULL, value, MSGSIZE,
821 config_path);
822 if (read == MSGSIZE - 1) {
Steve Dower84bcfb32015-01-02 18:07:46 -0800823 debug(L"read_commands: %ls: not enough space for %ls\n",
Brian Curtin07165f72012-06-20 15:36:14 -0500824 config_path, key);
825 }
826 cmdp = skip_whitespace(value);
827 if (*cmdp) {
828 cp = find_command(key);
829 if (cp == NULL)
830 add_command(key, value);
831 else
832 update_command(cp, key, value);
833 }
834 key += wcslen(key) + 1;
835 }
836}
837
838static void read_commands()
839{
840 if (launcher_ini_path[0])
841 read_config_file(launcher_ini_path);
842 if (appdata_ini_path[0])
843 read_config_file(appdata_ini_path);
844}
845
846static BOOL
847parse_shebang(wchar_t * shebang_line, int nchars, wchar_t ** command,
Vinay Sajip22c039b2013-06-07 15:37:28 +0100848 wchar_t ** suffix, BOOL *search)
Brian Curtin07165f72012-06-20 15:36:14 -0500849{
850 BOOL rc = FALSE;
Vinay Sajip22c039b2013-06-07 15:37:28 +0100851 SHEBANG * vpp;
Brian Curtin07165f72012-06-20 15:36:14 -0500852 size_t plen;
853 wchar_t * p;
854 wchar_t zapped;
855 wchar_t * endp = shebang_line + nchars - 1;
856 COMMAND * cp;
857 wchar_t * skipped;
858
859 *command = NULL; /* failure return */
860 *suffix = NULL;
Vinay Sajip22c039b2013-06-07 15:37:28 +0100861 *search = FALSE;
Brian Curtin07165f72012-06-20 15:36:14 -0500862
863 if ((*shebang_line++ == L'#') && (*shebang_line++ == L'!')) {
864 shebang_line = skip_whitespace(shebang_line);
865 if (*shebang_line) {
866 *command = shebang_line;
Vinay Sajip22c039b2013-06-07 15:37:28 +0100867 for (vpp = builtin_virtual_paths; vpp->shebang; ++vpp) {
868 plen = wcslen(vpp->shebang);
869 if (wcsncmp(shebang_line, vpp->shebang, plen) == 0) {
Brian Curtin07165f72012-06-20 15:36:14 -0500870 rc = TRUE;
Vinay Sajip22c039b2013-06-07 15:37:28 +0100871 *search = vpp->search;
Brian Curtin07165f72012-06-20 15:36:14 -0500872 /* We can do this because all builtin commands contain
873 * "python".
874 */
875 *command = wcsstr(shebang_line, L"python");
876 break;
877 }
878 }
Vinay Sajip22c039b2013-06-07 15:37:28 +0100879 if (vpp->shebang == NULL) {
Brian Curtin07165f72012-06-20 15:36:14 -0500880 /*
Vinay Sajip9c10d6b2013-11-15 20:58:13 +0000881 * Not found in builtins - look in customized commands.
Brian Curtin07165f72012-06-20 15:36:14 -0500882 *
883 * We can't permanently modify the shebang line in case
Vinay Sajip9c10d6b2013-11-15 20:58:13 +0000884 * it's not a customized command, but we can temporarily
Brian Curtin07165f72012-06-20 15:36:14 -0500885 * stick a NUL after the command while searching for it,
886 * then put back the char we zapped.
887 */
888#if defined(SKIP_PREFIX)
889 skipped = skip_prefix(shebang_line);
890#else
891 skipped = shebang_line;
892#endif
893 p = wcspbrk(skipped, L" \t\r\n");
894 if (p != NULL) {
895 zapped = *p;
896 *p = L'\0';
897 }
898 cp = find_command(skipped);
899 if (p != NULL)
900 *p = zapped;
901 if (cp != NULL) {
902 *command = cp->value;
903 if (p != NULL)
904 *suffix = skip_whitespace(p);
905 }
906 }
907 /* remove trailing whitespace */
908 while ((endp > shebang_line) && isspace(*endp))
909 --endp;
910 if (endp > shebang_line)
911 endp[1] = L'\0';
912 }
913 }
914 return rc;
915}
916
917/* #define CP_UTF8 65001 defined in winnls.h */
918#define CP_UTF16LE 1200
919#define CP_UTF16BE 1201
920#define CP_UTF32LE 12000
921#define CP_UTF32BE 12001
922
923typedef struct {
924 int length;
925 char sequence[4];
926 UINT code_page;
927} BOM;
928
929/*
Vinay Sajipc985d082013-07-25 11:20:55 +0100930 * Strictly, we don't need to handle UTF-16 and UTF-32, since Python itself
Brian Curtin07165f72012-06-20 15:36:14 -0500931 * doesn't. Never mind, one day it might - there's no harm leaving it in.
932 */
933static BOM BOMs[] = {
934 { 3, { 0xEF, 0xBB, 0xBF }, CP_UTF8 }, /* UTF-8 - keep first */
935 { 2, { 0xFF, 0xFE }, CP_UTF16LE }, /* UTF-16LE */
936 { 2, { 0xFE, 0xFF }, CP_UTF16BE }, /* UTF-16BE */
937 { 4, { 0xFF, 0xFE, 0x00, 0x00 }, CP_UTF32LE }, /* UTF-32LE */
938 { 4, { 0x00, 0x00, 0xFE, 0xFF }, CP_UTF32BE }, /* UTF-32BE */
939 { 0 } /* sentinel */
940};
941
942static BOM *
943find_BOM(char * buffer)
944{
945/*
946 * Look for a BOM in the input and return a pointer to the
947 * corresponding structure, or NULL if not found.
948 */
949 BOM * result = NULL;
950 BOM *bom;
951
952 for (bom = BOMs; bom->length; bom++) {
953 if (strncmp(bom->sequence, buffer, bom->length) == 0) {
954 result = bom;
955 break;
956 }
957 }
958 return result;
959}
960
961static char *
962find_terminator(char * buffer, int len, BOM *bom)
963{
964 char * result = NULL;
965 char * end = buffer + len;
966 char * p;
967 char c;
968 int cp;
969
970 for (p = buffer; p < end; p++) {
971 c = *p;
972 if (c == '\r') {
973 result = p;
974 break;
975 }
976 if (c == '\n') {
977 result = p;
978 break;
979 }
980 }
981 if (result != NULL) {
982 cp = bom->code_page;
983
984 /* adjustments to include all bytes of the char */
985 /* no adjustment needed for UTF-8 or big endian */
986 if (cp == CP_UTF16LE)
987 ++result;
988 else if (cp == CP_UTF32LE)
989 result += 3;
990 ++result; /* point just past terminator */
991 }
992 return result;
993}
994
995static BOOL
996validate_version(wchar_t * p)
997{
998 BOOL result = TRUE;
999
1000 if (!isdigit(*p)) /* expect major version */
1001 result = FALSE;
1002 else if (*++p) { /* more to do */
1003 if (*p != L'.') /* major/minor separator */
1004 result = FALSE;
1005 else {
1006 ++p;
1007 if (!isdigit(*p)) /* expect minor version */
1008 result = FALSE;
1009 else {
1010 ++p;
1011 if (*p) { /* more to do */
1012 if (*p != L'-')
1013 result = FALSE;
1014 else {
1015 ++p;
1016 if ((*p != '3') && (*++p != '2') && !*++p)
1017 result = FALSE;
1018 }
1019 }
1020 }
1021 }
1022 }
1023 return result;
1024}
1025
1026typedef struct {
1027 unsigned short min;
1028 unsigned short max;
1029 wchar_t version[MAX_VERSION_SIZE];
1030} PYC_MAGIC;
1031
1032static PYC_MAGIC magic_values[] = {
1033 { 0xc687, 0xc687, L"2.0" },
1034 { 0xeb2a, 0xeb2a, L"2.1" },
1035 { 0xed2d, 0xed2d, L"2.2" },
1036 { 0xf23b, 0xf245, L"2.3" },
1037 { 0xf259, 0xf26d, L"2.4" },
1038 { 0xf277, 0xf2b3, L"2.5" },
1039 { 0xf2c7, 0xf2d1, L"2.6" },
1040 { 0xf2db, 0xf303, L"2.7" },
1041 { 0x0bb8, 0x0c3b, L"3.0" },
1042 { 0x0c45, 0x0c4f, L"3.1" },
1043 { 0x0c58, 0x0c6c, L"3.2" },
1044 { 0x0c76, 0x0c76, L"3.3" },
1045 { 0 }
1046};
1047
1048static INSTALLED_PYTHON *
1049find_by_magic(unsigned short magic)
1050{
1051 INSTALLED_PYTHON * result = NULL;
1052 PYC_MAGIC * mp;
1053
1054 for (mp = magic_values; mp->min; mp++) {
1055 if ((magic >= mp->min) && (magic <= mp->max)) {
1056 result = locate_python(mp->version);
1057 if (result != NULL)
1058 break;
1059 }
1060 }
1061 return result;
1062}
1063
1064static void
1065maybe_handle_shebang(wchar_t ** argv, wchar_t * cmdline)
1066{
1067/*
1068 * Look for a shebang line in the first argument. If found
1069 * and we spawn a child process, this never returns. If it
1070 * does return then we process the args "normally".
1071 *
1072 * argv[0] might be a filename with a shebang.
1073 */
1074 FILE * fp;
1075 errno_t rc = _wfopen_s(&fp, *argv, L"rb");
1076 unsigned char buffer[BUFSIZE];
1077 wchar_t shebang_line[BUFSIZE + 1];
1078 size_t read;
1079 char *p;
1080 char * start;
1081 char * shebang_alias = (char *) shebang_line;
1082 BOM* bom;
1083 int i, j, nchars = 0;
1084 int header_len;
1085 BOOL is_virt;
Vinay Sajip22c039b2013-06-07 15:37:28 +01001086 BOOL search;
Brian Curtin07165f72012-06-20 15:36:14 -05001087 wchar_t * command;
1088 wchar_t * suffix;
Vinay Sajip22c039b2013-06-07 15:37:28 +01001089 COMMAND *cmd = NULL;
Brian Curtin07165f72012-06-20 15:36:14 -05001090 INSTALLED_PYTHON * ip;
1091
1092 if (rc == 0) {
1093 read = fread(buffer, sizeof(char), BUFSIZE, fp);
1094 debug(L"maybe_handle_shebang: read %d bytes\n", read);
1095 fclose(fp);
1096
1097 if ((read >= 4) && (buffer[3] == '\n') && (buffer[2] == '\r')) {
1098 ip = find_by_magic((buffer[1] << 8 | buffer[0]) & 0xFFFF);
1099 if (ip != NULL) {
Steve Dower84bcfb32015-01-02 18:07:46 -08001100 debug(L"script file is compiled against Python %ls\n",
Brian Curtin07165f72012-06-20 15:36:14 -05001101 ip->version);
1102 invoke_child(ip->executable, NULL, cmdline);
1103 }
1104 }
1105 /* Look for BOM */
1106 bom = find_BOM(buffer);
1107 if (bom == NULL) {
1108 start = buffer;
1109 debug(L"maybe_handle_shebang: BOM not found, using UTF-8\n");
1110 bom = BOMs; /* points to UTF-8 entry - the default */
1111 }
1112 else {
1113 debug(L"maybe_handle_shebang: BOM found, code page %d\n",
1114 bom->code_page);
1115 start = &buffer[bom->length];
1116 }
1117 p = find_terminator(start, BUFSIZE, bom);
1118 /*
1119 * If no CR or LF was found in the heading,
1120 * we assume it's not a shebang file.
1121 */
1122 if (p == NULL) {
1123 debug(L"maybe_handle_shebang: No line terminator found\n");
1124 }
1125 else {
1126 /*
1127 * Found line terminator - parse the shebang.
1128 *
1129 * Strictly, we don't need to handle UTF-16 anf UTF-32,
1130 * since Python itself doesn't.
1131 * Never mind, one day it might.
1132 */
1133 header_len = (int) (p - start);
1134 switch(bom->code_page) {
1135 case CP_UTF8:
1136 nchars = MultiByteToWideChar(bom->code_page,
1137 0,
1138 start, header_len, shebang_line,
1139 BUFSIZE);
1140 break;
1141 case CP_UTF16BE:
1142 if (header_len % 2 != 0) {
1143 debug(L"maybe_handle_shebang: UTF-16BE, but an odd number \
1144of bytes: %d\n", header_len);
1145 /* nchars = 0; Not needed - initialised to 0. */
1146 }
1147 else {
1148 for (i = header_len; i > 0; i -= 2) {
1149 shebang_alias[i - 1] = start[i - 2];
1150 shebang_alias[i - 2] = start[i - 1];
1151 }
1152 nchars = header_len / sizeof(wchar_t);
1153 }
1154 break;
1155 case CP_UTF16LE:
1156 if ((header_len % 2) != 0) {
1157 debug(L"UTF-16LE, but an odd number of bytes: %d\n",
1158 header_len);
1159 /* nchars = 0; Not needed - initialised to 0. */
1160 }
1161 else {
1162 /* no actual conversion needed. */
1163 memcpy(shebang_line, start, header_len);
1164 nchars = header_len / sizeof(wchar_t);
1165 }
1166 break;
1167 case CP_UTF32BE:
1168 if (header_len % 4 != 0) {
1169 debug(L"UTF-32BE, but not divisible by 4: %d\n",
1170 header_len);
1171 /* nchars = 0; Not needed - initialised to 0. */
1172 }
1173 else {
1174 for (i = header_len, j = header_len / 2; i > 0; i -= 4,
1175 j -= 2) {
1176 shebang_alias[j - 1] = start[i - 2];
1177 shebang_alias[j - 2] = start[i - 1];
1178 }
1179 nchars = header_len / sizeof(wchar_t);
1180 }
1181 break;
1182 case CP_UTF32LE:
1183 if (header_len % 4 != 0) {
1184 debug(L"UTF-32LE, but not divisible by 4: %d\n",
1185 header_len);
1186 /* nchars = 0; Not needed - initialised to 0. */
1187 }
1188 else {
1189 for (i = header_len, j = header_len / 2; i > 0; i -= 4,
1190 j -= 2) {
1191 shebang_alias[j - 1] = start[i - 3];
1192 shebang_alias[j - 2] = start[i - 4];
1193 }
1194 nchars = header_len / sizeof(wchar_t);
1195 }
1196 break;
1197 }
1198 if (nchars > 0) {
1199 shebang_line[--nchars] = L'\0';
1200 is_virt = parse_shebang(shebang_line, nchars, &command,
Vinay Sajip22c039b2013-06-07 15:37:28 +01001201 &suffix, &search);
Brian Curtin07165f72012-06-20 15:36:14 -05001202 if (command != NULL) {
Steve Dower84bcfb32015-01-02 18:07:46 -08001203 debug(L"parse_shebang: found command: %ls\n", command);
Brian Curtin07165f72012-06-20 15:36:14 -05001204 if (!is_virt) {
1205 invoke_child(command, suffix, cmdline);
1206 }
1207 else {
1208 suffix = wcschr(command, L' ');
1209 if (suffix != NULL) {
1210 *suffix++ = L'\0';
1211 suffix = skip_whitespace(suffix);
1212 }
1213 if (wcsncmp(command, L"python", 6))
1214 error(RC_BAD_VIRTUAL_PATH, L"Unknown virtual \
Steve Dower84bcfb32015-01-02 18:07:46 -08001215path '%ls'", command);
Brian Curtin07165f72012-06-20 15:36:14 -05001216 command += 6; /* skip past "python" */
Vinay Sajip22c039b2013-06-07 15:37:28 +01001217 if (search && ((*command == L'\0') || isspace(*command))) {
1218 /* Command is eligible for path search, and there
1219 * is no version specification.
1220 */
1221 debug(L"searching PATH for python executable\n");
1222 cmd = find_on_path(L"python");
Steve Dower84bcfb32015-01-02 18:07:46 -08001223 debug(L"Python on path: %ls\n", cmd ? cmd->value : L"<not found>");
Vinay Sajip22c039b2013-06-07 15:37:28 +01001224 if (cmd) {
Steve Dower84bcfb32015-01-02 18:07:46 -08001225 debug(L"located python on PATH: %ls\n", cmd->value);
Vinay Sajip22c039b2013-06-07 15:37:28 +01001226 invoke_child(cmd->value, suffix, cmdline);
1227 /* Exit here, as we have found the command */
1228 return;
1229 }
1230 /* FALL THROUGH: No python found on PATH, so fall
1231 * back to locating the correct installed python.
1232 */
1233 }
Brian Curtin07165f72012-06-20 15:36:14 -05001234 if (*command && !validate_version(command))
1235 error(RC_BAD_VIRTUAL_PATH, L"Invalid version \
Steve Dower84bcfb32015-01-02 18:07:46 -08001236specification: '%ls'.\nIn the first line of the script, 'python' needs to be \
Brian Curtin07165f72012-06-20 15:36:14 -05001237followed by a valid version specifier.\nPlease check the documentation.",
1238 command);
1239 /* TODO could call validate_version(command) */
1240 ip = locate_python(command);
1241 if (ip == NULL) {
1242 error(RC_NO_PYTHON, L"Requested Python version \
Steve Dower84bcfb32015-01-02 18:07:46 -08001243(%ls) is not installed", command);
Brian Curtin07165f72012-06-20 15:36:14 -05001244 }
1245 else {
1246 invoke_child(ip->executable, suffix, cmdline);
1247 }
1248 }
1249 }
1250 }
1251 }
1252 }
1253}
1254
1255static wchar_t *
1256skip_me(wchar_t * cmdline)
1257{
1258 BOOL quoted;
1259 wchar_t c;
1260 wchar_t * result = cmdline;
1261
1262 quoted = cmdline[0] == L'\"';
1263 if (!quoted)
1264 c = L' ';
1265 else {
1266 c = L'\"';
1267 ++result;
1268 }
1269 result = wcschr(result, c);
1270 if (result == NULL) /* when, for example, just exe name on command line */
1271 result = L"";
1272 else {
1273 ++result; /* skip past space or closing quote */
1274 result = skip_whitespace(result);
1275 }
1276 return result;
1277}
1278
1279static DWORD version_high = 0;
1280static DWORD version_low = 0;
1281
1282static void
1283get_version_info(wchar_t * version_text, size_t size)
1284{
1285 WORD maj, min, rel, bld;
1286
1287 if (!version_high && !version_low)
1288 wcsncpy_s(version_text, size, L"0.1", _TRUNCATE); /* fallback */
1289 else {
1290 maj = HIWORD(version_high);
1291 min = LOWORD(version_high);
1292 rel = HIWORD(version_low);
1293 bld = LOWORD(version_low);
1294 _snwprintf_s(version_text, size, _TRUNCATE, L"%d.%d.%d.%d", maj,
1295 min, rel, bld);
1296 }
1297}
1298
1299static int
1300process(int argc, wchar_t ** argv)
1301{
1302 wchar_t * wp;
1303 wchar_t * command;
1304 wchar_t * p;
1305 int rc = 0;
1306 size_t plen;
1307 INSTALLED_PYTHON * ip;
1308 BOOL valid;
1309 DWORD size, attrs;
1310 HRESULT hr;
1311 wchar_t message[MSGSIZE];
1312 wchar_t version_text [MAX_PATH];
1313 void * version_data;
1314 VS_FIXEDFILEINFO * file_info;
1315 UINT block_size;
Vinay Sajip2ae8c632013-01-29 22:29:25 +00001316 int index;
Vinay Sajipc985d082013-07-25 11:20:55 +01001317#if defined(SCRIPT_WRAPPER)
1318 int newlen;
1319 wchar_t * newcommand;
1320 wchar_t * av[2];
1321#endif
Brian Curtin07165f72012-06-20 15:36:14 -05001322
1323 wp = get_env(L"PYLAUNCH_DEBUG");
1324 if ((wp != NULL) && (*wp != L'\0'))
1325 log_fp = stderr;
1326
1327#if defined(_M_X64)
1328 debug(L"launcher build: 64bit\n");
1329#else
1330 debug(L"launcher build: 32bit\n");
1331#endif
1332#if defined(_WINDOWS)
1333 debug(L"launcher executable: Windows\n");
1334#else
1335 debug(L"launcher executable: Console\n");
1336#endif
1337 /* Get the local appdata folder (non-roaming) */
1338 hr = SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA,
1339 NULL, 0, appdata_ini_path);
1340 if (hr != S_OK) {
1341 debug(L"SHGetFolderPath failed: %X\n", hr);
1342 appdata_ini_path[0] = L'\0';
1343 }
1344 else {
1345 plen = wcslen(appdata_ini_path);
1346 p = &appdata_ini_path[plen];
1347 wcsncpy_s(p, MAX_PATH - plen, L"\\py.ini", _TRUNCATE);
1348 attrs = GetFileAttributesW(appdata_ini_path);
1349 if (attrs == INVALID_FILE_ATTRIBUTES) {
Steve Dower84bcfb32015-01-02 18:07:46 -08001350 debug(L"File '%ls' non-existent\n", appdata_ini_path);
Brian Curtin07165f72012-06-20 15:36:14 -05001351 appdata_ini_path[0] = L'\0';
1352 } else {
Steve Dower84bcfb32015-01-02 18:07:46 -08001353 debug(L"Using local configuration file '%ls'\n", appdata_ini_path);
Brian Curtin07165f72012-06-20 15:36:14 -05001354 }
1355 }
1356 plen = GetModuleFileNameW(NULL, launcher_ini_path, MAX_PATH);
1357 size = GetFileVersionInfoSizeW(launcher_ini_path, &size);
1358 if (size == 0) {
1359 winerror(GetLastError(), message, MSGSIZE);
Steve Dower84bcfb32015-01-02 18:07:46 -08001360 debug(L"GetFileVersionInfoSize failed: %ls\n", message);
Brian Curtin07165f72012-06-20 15:36:14 -05001361 }
1362 else {
1363 version_data = malloc(size);
1364 if (version_data) {
1365 valid = GetFileVersionInfoW(launcher_ini_path, 0, size,
1366 version_data);
1367 if (!valid)
1368 debug(L"GetFileVersionInfo failed: %X\n", GetLastError());
1369 else {
Vinay Sajip404229b2013-01-29 22:52:57 +00001370 valid = VerQueryValueW(version_data, L"\\",
1371 (LPVOID *) &file_info, &block_size);
Brian Curtin07165f72012-06-20 15:36:14 -05001372 if (!valid)
1373 debug(L"VerQueryValue failed: %X\n", GetLastError());
1374 else {
1375 version_high = file_info->dwFileVersionMS;
1376 version_low = file_info->dwFileVersionLS;
1377 }
1378 }
1379 free(version_data);
1380 }
1381 }
1382 p = wcsrchr(launcher_ini_path, L'\\');
1383 if (p == NULL) {
Steve Dower84bcfb32015-01-02 18:07:46 -08001384 debug(L"GetModuleFileNameW returned value has no backslash: %ls\n",
Brian Curtin07165f72012-06-20 15:36:14 -05001385 launcher_ini_path);
1386 launcher_ini_path[0] = L'\0';
1387 }
1388 else {
1389 wcsncpy_s(p, MAX_PATH - (p - launcher_ini_path), L"\\py.ini",
1390 _TRUNCATE);
1391 attrs = GetFileAttributesW(launcher_ini_path);
1392 if (attrs == INVALID_FILE_ATTRIBUTES) {
Steve Dower84bcfb32015-01-02 18:07:46 -08001393 debug(L"File '%ls' non-existent\n", launcher_ini_path);
Brian Curtin07165f72012-06-20 15:36:14 -05001394 launcher_ini_path[0] = L'\0';
1395 } else {
Steve Dower84bcfb32015-01-02 18:07:46 -08001396 debug(L"Using global configuration file '%ls'\n", launcher_ini_path);
Brian Curtin07165f72012-06-20 15:36:14 -05001397 }
1398 }
1399
1400 command = skip_me(GetCommandLineW());
Steve Dower84bcfb32015-01-02 18:07:46 -08001401 debug(L"Called with command line: %ls\n", command);
Vinay Sajipc985d082013-07-25 11:20:55 +01001402
1403#if defined(SCRIPT_WRAPPER)
1404 /* The launcher is being used in "script wrapper" mode.
1405 * There should therefore be a Python script named <exename>-script.py in
1406 * the same directory as the launcher executable.
1407 * Put the script name into argv as the first (script name) argument.
1408 */
1409
1410 /* Get the wrapped script name - if the script is not present, this will
1411 * terminate the program with an error.
1412 */
1413 locate_wrapped_script();
1414
1415 /* Add the wrapped script to the start of command */
1416 newlen = wcslen(wrapped_script_path) + wcslen(command) + 2; /* ' ' + NUL */
1417 newcommand = malloc(sizeof(wchar_t) * newlen);
1418 if (!newcommand) {
1419 error(RC_NO_MEMORY, L"Could not allocate new command line");
1420 }
1421 else {
1422 wcscpy_s(newcommand, newlen, wrapped_script_path);
1423 wcscat_s(newcommand, newlen, L" ");
1424 wcscat_s(newcommand, newlen, command);
Steve Dower84bcfb32015-01-02 18:07:46 -08001425 debug(L"Running wrapped script with command line '%ls'\n", newcommand);
Vinay Sajipc985d082013-07-25 11:20:55 +01001426 read_commands();
1427 av[0] = wrapped_script_path;
1428 av[1] = NULL;
1429 maybe_handle_shebang(av, newcommand);
1430 /* Returns if no shebang line - pass to default processing */
1431 command = newcommand;
1432 valid = FALSE;
1433 }
1434#else
Brian Curtin07165f72012-06-20 15:36:14 -05001435 if (argc <= 1) {
1436 valid = FALSE;
1437 p = NULL;
1438 }
1439 else {
1440 p = argv[1];
1441 plen = wcslen(p);
Brian Curtin07165f72012-06-20 15:36:14 -05001442 valid = (*p == L'-') && validate_version(&p[1]);
1443 if (valid) {
1444 ip = locate_python(&p[1]);
1445 if (ip == NULL)
Steve Dower84bcfb32015-01-02 18:07:46 -08001446 error(RC_NO_PYTHON, L"Requested Python version (%ls) not \
Brian Curtin07165f72012-06-20 15:36:14 -05001447installed", &p[1]);
1448 command += wcslen(p);
1449 command = skip_whitespace(command);
1450 }
Vinay Sajip2ae8c632013-01-29 22:29:25 +00001451 else {
1452 for (index = 1; index < argc; ++index) {
1453 if (*argv[index] != L'-')
1454 break;
1455 }
1456 if (index < argc) {
1457 read_commands();
1458 maybe_handle_shebang(&argv[index], command);
1459 }
1460 }
Brian Curtin07165f72012-06-20 15:36:14 -05001461 }
Vinay Sajipc985d082013-07-25 11:20:55 +01001462#endif
1463
Brian Curtin07165f72012-06-20 15:36:14 -05001464 if (!valid) {
1465 ip = locate_python(L"");
1466 if (ip == NULL)
1467 error(RC_NO_PYTHON, L"Can't find a default Python.");
1468 if ((argc == 2) && (!_wcsicmp(p, L"-h") || !_wcsicmp(p, L"--help"))) {
1469#if defined(_M_X64)
1470 BOOL canDo64bit = TRUE;
1471#else
1472 // If we are a 32bit process on a 64bit Windows, first hit the 64bit keys.
1473 BOOL canDo64bit = FALSE;
1474 IsWow64Process(GetCurrentProcess(), &canDo64bit);
1475#endif
1476
1477 get_version_info(version_text, MAX_PATH);
1478 fwprintf(stdout, L"\
Steve Dower84bcfb32015-01-02 18:07:46 -08001479Python Launcher for Windows Version %ls\n\n", version_text);
Brian Curtin07165f72012-06-20 15:36:14 -05001480 fwprintf(stdout, L"\
Steve Dower84bcfb32015-01-02 18:07:46 -08001481usage: %ls [ launcher-arguments ] [ python-arguments ] script [ script-arguments ]\n\n", argv[0]);
Brian Curtin07165f72012-06-20 15:36:14 -05001482 fputws(L"\
1483Launcher arguments:\n\n\
1484-2 : Launch the latest Python 2.x version\n\
1485-3 : Launch the latest Python 3.x version\n\
1486-X.Y : Launch the specified Python version\n", stdout);
1487 if (canDo64bit) {
1488 fputws(L"\
1489-X.Y-32: Launch the specified 32bit Python version", stdout);
1490 }
1491 fputws(L"\n\nThe following help text is from Python:\n\n", stdout);
1492 fflush(stdout);
1493 }
1494 }
1495 invoke_child(ip->executable, NULL, command);
1496 return rc;
1497}
1498
1499#if defined(_WINDOWS)
1500
1501int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
1502 LPWSTR lpstrCmd, int nShow)
1503{
1504 return process(__argc, __wargv);
1505}
1506
1507#else
1508
1509int cdecl wmain(int argc, wchar_t ** argv)
1510{
1511 return process(argc, argv);
1512}
1513
Vinay Sajip2ae8c632013-01-29 22:29:25 +00001514#endif